1286948 - Adds debug mode for WASM baseline compiler.

1286948 - Adds scope and environment for wasm calls (Adds artificial JS scope and environment for wasm frames. That allows
debugger to properly handle call stack).

1286948 - Adds prolog and epilog debug traps and handlers (Using toggled call/traps to invoke handler to process enter and leave
frame events).

1286948 - onEnterFrame/onLeaveFrame wasm events and callstack (Handles onEnterFrame and onLeaveFrame trap handling. The FrameIter is used in
the DebuggerFrame instead of ScriptFrameIter. The debug wasm frame is created
and can be found on callstack during those event).

Does not compile without 1331452 - Keep scope in a slot in WasmFunctionCallObjects along with 1335773 - Inspects wasm function locals to build
This commit is contained in:
win7-7 2025-12-29 21:03:14 +02:00 committed by wuggy
commit 9754fb007b
49 changed files with 1447 additions and 158 deletions

View file

@ -111,6 +111,8 @@ its prototype:
* `"module"`: a frame running code at the top level of a module.
* `"wasmcall"`: a frame running a WebAssembly function call.
* `"debugger"`: a frame for a call to user code invoked by the debugger
(see the `eval` method below).
@ -124,8 +126,11 @@ its prototype:
* `"ion"`: a frame running in the optimizing JIT.
* `"wasm"`: a frame running in WebAssembly baseline JIT.
`this`
: The value of `this` for this frame (a debuggee value).
: The value of `this` for this frame (a debuggee value). For a `wasmcall`
frame, this property throws a `TypeError`.
`older`
: The next-older visible frame, in which control will resume when this
@ -149,6 +154,7 @@ its prototype:
`offset`
: The offset of the bytecode instruction currently being executed in
`script`, or `undefined` if the frame's `script` property is `null`.
For a `wasmcall` frame, this property throws a `TypeError`.
`environment`
: The lexical environment within which evaluation is taking place (a
@ -268,9 +274,9 @@ methods of other kinds of objects.
<code id="eval">eval(<i>code</i>, [<i>options</i>])</code>
: Evaluate <i>code</i> in the execution context of this frame, and return
a [completion value][cv] describing how it completed. <i>Code</i> is a
string. If this frame's `environment` property is `null`, throw a
`TypeError`. All extant handler methods, breakpoints, and
so on remain active during the call. This function follows the
string. If this frame's `environment` property is `null` or `type` property
is `wasmcall`, throw a `TypeError`. All extant handler methods, breakpoints,
and so on remain active during the call. This function follows the
[invocation function conventions][inv fr].
<i>Code</i> is interpreted as strict mode code when it contains a Use
@ -326,3 +332,5 @@ methods of other kinds of objects.
The <i>options</i> argument is as for
[`Debugger.Frame.prototype.eval`][fr eval], described above.
Also like `eval`, if this frame's `environment` property is `null` or
`type` property is `wasmcall`, throw a `TypeError`.

View file

@ -278,6 +278,10 @@ EmitterScope::searchInEnclosingScope(JSAtom* name, Scope* scope, uint8_t hops)
case ScopeKind::With:
case ScopeKind::NonSyntactic:
return NameLocation::Dynamic();
case ScopeKind::WasmFunction:
MOZ_CRASH("No direct eval inside wasm functions");
}
if (hasEnv) {
@ -1034,6 +1038,8 @@ EmitterScope::leave(BytecodeEmitter* bce, bool nonLocal)
case ScopeKind::NonSyntactic:
case ScopeKind::Module:
break;
case ScopeKind::WasmFunction:
MOZ_CRASH("No wasm function scopes in JS");
}
// Finish up the scope if we are leaving it in LIFO fashion.

View file

@ -1272,6 +1272,14 @@ ModuleScope::Data::trace(JSTracer* trc)
TraceNullableEdge(trc, &module, "scope module");
TraceBindingNames(trc, trailingNames.start(), length);
}
void
WasmFunctionScope::Data::trace(JSTracer* trc)
{
TraceNullableEdge(trc, &instance, "wasm function");
TraceBindingNames(trc, trailingNames.start(), length);
}
void
Scope::traceChildren(JSTracer* trc)
{
@ -1305,6 +1313,10 @@ Scope::traceChildren(JSTracer* trc)
break;
case ScopeKind::With:
break;
case ScopeKind::WasmFunction:
reinterpret_cast<WasmFunctionScope::Data*>(data_)->trace(trc);
break;
}
}
inline void
@ -1370,7 +1382,15 @@ js::GCMarker::eagerlyMarkChildren(Scope* scope)
case ScopeKind::With:
break;
case ScopeKind::WasmFunction: {
WasmFunctionScope::Data* data = reinterpret_cast<WasmFunctionScope::Data*>(scope->data_);
traverseEdge(scope, static_cast<JSObject*>(data->instance));
names = &data->trailingNames;
length = data->length;
break;
}
}
if (scope->kind_ == ScopeKind::Function) {
for (uint32_t i = 0; i < length; i++) {
if (JSAtom* name = names->operator[](i).name())

View file

@ -51,6 +51,7 @@ class Shape;
class SharedArrayBufferObject;
class StructTypeDescr;
class UnownedBaseShape;
class WasmFunctionScope;
class WasmMemoryObject;
namespace jit {
class JitCode;
@ -92,6 +93,7 @@ class JitCode;
D(js::SharedArrayBufferObject*) \
D(js::StructTypeDescr*) \
D(js::UnownedBaseShape*) \
D(js::WasmFunctionScope*) \
D(js::WasmInstanceObject*) \
D(js::WasmMemoryObject*) \
D(js::WasmTableObject*) \

View file

@ -16,6 +16,7 @@ class InterpreterTypeCache(object):
self.tInterpreterFrame = gdb.lookup_type('js::InterpreterFrame')
self.tBaselineFrame = gdb.lookup_type('js::jit::BaselineFrame')
self.tRematerializedFrame = gdb.lookup_type('js::jit::RematerializedFrame')
self.tDebugFrame = gdb.lookup_type('js::wasm::DebugFrame')
@pretty_printer('js::InterpreterRegs')
class InterpreterRegs(object):
@ -47,7 +48,8 @@ class AbstractFramePtr(object):
Tag_InterpreterFrame = 0x1
Tag_BaselineFrame = 0x2
Tag_RematerializedFrame = 0x3
TagMask = 0x3
Tag_WasmDebugFrame = 0x4
TagMask = 0x7
def __init__(self, value, cache):
self.value = value
@ -72,6 +74,9 @@ class AbstractFramePtr(object):
if tag == AbstractFramePtr.Tag_RematerializedFrame:
label = 'js::jit::RematerializedFrame'
ptr = ptr.cast(self.itc.tRematerializedFrame.pointer())
if tag == AbstractFramePtr.Tag_WasmDebugFrame:
label = 'js::wasm::DebugFrame'
ptr = ptr.cast(self.itc.tDebugFrame.pointer())
return 'AbstractFramePtr (({} *) {})'.format(label, ptr)
# Provide the ptr_ field as a child, so it prints after the pretty string

View file

@ -49,6 +49,13 @@ GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, jit::RematerializedFrame* p
frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_RematerializedFrame;
}
void
GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, wasm::DebugFrame* ptr)
{
MOZ_ASSERT((uintptr_t(ptr) & AbstractFramePtr::TagMask) == 0);
frame.ptr_ = uintptr_t(ptr) | AbstractFramePtr::Tag_WasmDebugFrame;
}
} // namespace js
FRAGMENT(Interpreter, Regs) {
@ -82,6 +89,9 @@ FRAGMENT(Interpreter, AbstractFramePtr) {
js::AbstractFramePtr rfptr;
GDBTestInitAbstractFramePtr(rfptr, (js::jit::RematerializedFrame*) uintptr_t(0xdabbad00));
js::AbstractFramePtr sfptr;
GDBTestInitAbstractFramePtr(sfptr, (js::wasm::DebugFrame*) uintptr_t(0xcb98ad00));
breakpoint();
(void) sfidptr;

View file

@ -523,6 +523,13 @@ class MacroAssembler : public MacroAssemblerSpecific
static void patchNopToNearJump(uint8_t* jump, uint8_t* target) PER_SHARED_ARCH;
static void patchNearJumpToNop(uint8_t* jump) PER_SHARED_ARCH;
// Emit a nop that can be patched to and from a nop and a call with int32
// relative displacement.
CodeOffset nopPatchableToCall(const wasm::CallSiteDesc& desc) PER_SHARED_ARCH;
static void patchNopToCall(uint8_t* callsite, uint8_t* target) PER_SHARED_ARCH;
static void patchCallToNop(uint8_t* callsite) PER_SHARED_ARCH;
public:
// ===============================================================
// ABI function calls.

View file

@ -5130,6 +5130,34 @@ MacroAssembler::patchNearJumpToNop(uint8_t* jump)
new (jump) InstNOP();
}
CodeOffset
MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
{
CodeOffset offset(currentOffset());
ma_nop();
append(desc, CodeOffset(currentOffset()), framePushed());
return offset;
}
void
MacroAssembler::patchNopToCall(uint8_t* call, uint8_t* target)
{
uint8_t* inst = call - 4;
MOZ_ASSERT(reinterpret_cast<Instruction*>(inst)->is<InstBLImm>() ||
reinterpret_cast<Instruction*>(inst)->is<InstNOP>());
new (inst) InstBLImm(BOffImm(target - inst), Assembler::Always);
}
void
MacroAssembler::patchCallToNop(uint8_t* call)
{
uint8_t* inst = call - 4;
MOZ_ASSERT(reinterpret_cast<Instruction*>(inst)->is<InstBLImm>() ||
reinterpret_cast<Instruction*>(inst)->is<InstNOP>());
new (inst) InstNOP();
}
void
MacroAssembler::pushReturnAddress()
{

View file

@ -580,6 +580,25 @@ MacroAssembler::patchNearJumpToNop(uint8_t* jump)
MOZ_CRASH("NYI");
}
CodeOffset
MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
{
MOZ_CRASH("NYI");
return CodeOffset();
}
void
MacroAssembler::patchNopToCall(uint8_t* call, uint8_t* target)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::patchCallToNop(uint8_t* call)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::pushReturnAddress()
{

View file

@ -1901,6 +1901,25 @@ MacroAssembler::call(JitCode* c)
callJitNoProfiler(ScratchRegister);
}
CodeOffset
MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
{
MOZ_CRASH("NYI");
return CodeOffset();
}
void
MacroAssembler::patchNopToCall(uint8_t* call, uint8_t* target)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::patchCallToNop(uint8_t* call)
{
MOZ_CRASH("NYI");
}
void
MacroAssembler::pushReturnAddress()
{

View file

@ -661,28 +661,6 @@ class CodeLocationLabel
namespace wasm {
// As an invariant across architectures, within wasm code:
// $sp % WasmStackAlignment = (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment
// Thus, wasm::Frame represents the bytes pushed after the call (which occurred
// with a WasmStackAlignment-aligned StackPointer) that are not included in
// masm.framePushed.
struct Frame
{
// The caller's saved frame pointer. In non-profiling mode, internal
// wasm-to-wasm calls don't update fp and thus don't save the caller's
// frame pointer; the space is reserved, however, so that profiling mode can
// reuse the same function body without recompiling.
uint8_t* callerFP;
// The return address pushed by the call (in the case of ARM/MIPS the return
// address is pushed by the first instruction of the prologue).
void* returnAddress;
};
static_assert(sizeof(Frame) == 2 * sizeof(void*), "?!");
static const uint32_t FrameBytesAfterReturnAddress = sizeof(void*);
// Represents an instruction to be patched and the intended pointee. These
// links are accumulated in the MacroAssembler, but patching is done outside
// the MacroAssembler (in Module::staticallyLink).

View file

@ -1090,6 +1090,13 @@ class AssemblerX86Shared : public AssemblerShared
X86Encoding::BaseAssembler::patchJumpToTwoByteNop(jump);
}
static void patchFiveByteNopToCall(uint8_t* callsite, uint8_t* target) {
X86Encoding::BaseAssembler::patchFiveByteNopToCall(callsite, target);
}
static void patchCallToFiveByteNop(uint8_t* callsite) {
X86Encoding::BaseAssembler::patchCallToFiveByteNop(callsite);
}
void breakpoint() {
masm.int3();
}

View file

@ -110,6 +110,40 @@ public:
jump[1] = OP_NOP;
}
static void patchFiveByteNopToCall(uint8_t* callsite, uint8_t* target)
{
// Note: the offset is relative to the address of the instruction after
// the call which is five bytes.
uint8_t* inst = callsite - sizeof(int32_t) - 1;
// The nop can be already patched as call, overriding the call.
// See also nop_five.
MOZ_ASSERT(inst[0] == OP_NOP_0F || inst[0] == OP_CALL_rel32);
MOZ_ASSERT_IF(inst[0] == OP_NOP_0F, inst[1] == OP_NOP_1F ||
inst[2] == OP_NOP_44 ||
inst[3] == OP_NOP_00 ||
inst[4] == OP_NOP_00);
inst[0] = OP_CALL_rel32;
SetRel32(callsite, target);
}
static void patchCallToFiveByteNop(uint8_t* callsite)
{
// See also patchFiveByteNopToCall and nop_five.
uint8_t* inst = callsite - sizeof(int32_t) - 1;
// The call can be already patched as nop.
if (inst[0] == OP_NOP_0F) {
MOZ_ASSERT(inst[1] == OP_NOP_1F || inst[2] == OP_NOP_44 ||
inst[3] == OP_NOP_00 || inst[4] == OP_NOP_00);
return;
}
MOZ_ASSERT(inst[0] == OP_CALL_rel32);
inst[0] = OP_NOP_0F;
inst[1] = OP_NOP_1F;
inst[2] = OP_NOP_44;
inst[3] = OP_NOP_00;
inst[4] = OP_NOP_00;
}
/*
* The nop multibytes sequences are directly taken from the Intel's
* architecture software developer manual.

View file

@ -697,6 +697,28 @@ MacroAssembler::patchNearJumpToNop(uint8_t* jump)
Assembler::patchJumpToTwoByteNop(jump);
}
CodeOffset
MacroAssembler::nopPatchableToCall(const wasm::CallSiteDesc& desc)
{
CodeOffset offset(currentOffset());
masm.nop_five();
append(desc, CodeOffset(currentOffset()), framePushed());
MOZ_ASSERT_IF(!oom(), size() - offset.offset() == ToggledCallSize(nullptr));
return offset;
}
void
MacroAssembler::patchNopToCall(uint8_t* callsite, uint8_t* target)
{
Assembler::patchFiveByteNopToCall(callsite, target);
}
void
MacroAssembler::patchCallToNop(uint8_t* callsite)
{
Assembler::patchCallToFiveByteNop(callsite);
}
// ===============================================================
// Jit Frames.

View file

@ -823,6 +823,9 @@ js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
case ScopeKind::Module:
MOZ_CRASH("NYI");
break;
case ScopeKind::WasmFunction:
MOZ_CRASH("wasm functions cannot be nested in JSScripts");
break;
default:
// Fail in debug, but only soft-fail in release
MOZ_ASSERT(false, "Bad XDR scope kind");

View file

@ -356,6 +356,7 @@ main_deunified_sources = [
'wasm/WasmCode.cpp',
'wasm/WasmCompartment.cpp',
'wasm/WasmCompile.cpp',
'wasm/WasmDebugFrame.cpp',
'wasm/WasmFrameIterator.cpp',
'wasm/WasmGenerator.cpp',
'wasm/WasmInstance.cpp',

View file

@ -463,6 +463,7 @@
macro(variable, variable, "variable") \
macro(void0, void0, "(void 0)") \
macro(wasm, wasm, "wasm") \
macro(wasmcall, wasmcall, "wasmcall") \
macro(watch, watch, "watch") \
macro(WeakMapConstructorInit, WeakMapConstructorInit, "WeakMapConstructorInit") \
macro(WeakSetConstructorInit, WeakSetConstructorInit, "WeakSetConstructorInit") \

View file

@ -14,7 +14,7 @@
js::Debugger::onLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok)
{
MOZ_ASSERT_IF(frame.isInterpreterFrame(), frame.asInterpreterFrame() == cx->interpreterFrame());
MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
/* Traps must be cleared from eval frames, see slowPathOnLeaveFrame. */
mozilla::DebugOnly<bool> evalTraps = frame.isEvalFrame() &&
frame.script()->hasAnyBreakpointsOrStepMode();
@ -43,7 +43,7 @@ js::Debugger::checkNoExecute(JSContext* cx, HandleScript script)
/* static */ JSTrapStatus
js::Debugger::onEnterFrame(JSContext* cx, AbstractFramePtr frame)
{
MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
if (!frame.isDebuggee())
return JSTRAP_CONTINUE;
return slowPathOnEnterFrame(cx, frame);
@ -73,7 +73,7 @@ js::Debugger::onNewWasmInstance(JSContext* cx, Handle<WasmInstanceObject*> wasmI
}
inline bool
js::Debugger::getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
js::Debugger::getScriptFrame(JSContext* cx, const FrameIter& iter,
MutableHandle<DebuggerFrame*> result)
{
return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, result);

View file

@ -737,7 +737,7 @@ Debugger::memory() const
bool
Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
const ScriptFrameIter* maybeIter, MutableHandleValue vp)
const FrameIter* maybeIter, MutableHandleValue vp)
{
RootedDebuggerFrame result(cx);
if (!Debugger::getScriptFrameWithIter(cx, referent, maybeIter, &result))
@ -749,13 +749,13 @@ Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
bool
Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
const ScriptFrameIter* maybeIter,
const FrameIter* maybeIter,
MutableHandleDebuggerFrame result)
{
MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == referent);
MOZ_ASSERT(!referent.script()->selfHosted());
MOZ_ASSERT_IF(referent.hasScript(), !referent.script()->selfHosted());
if (!referent.script()->ensureHasAnalyzedArgsUsage(cx))
if (referent.hasScript() && !referent.script()->ensureHasAnalyzedArgsUsage(cx))
return false;
FrameMap::AddPtr p = frames.lookupForAdd(referent);
@ -1015,7 +1015,7 @@ Debugger::slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame)
return JSTRAP_CONTINUE;
// The Debugger API mustn't muck with frames from self-hosted scripts.
if (frame.script()->selfHosted())
if (frame.hasScript() && frame.script()->selfHosted())
return JSTRAP_CONTINUE;
RootedValue rval(cx);
@ -1737,7 +1737,7 @@ Debugger::fireDebuggerStatement(JSContext* cx, MutableHandleValue vp)
Maybe<AutoCompartment> ac;
ac.emplace(cx, object);
ScriptFrameIter iter(cx);
FrameIter iter(cx);
RootedValue scriptFrame(cx);
if (!getScriptFrame(cx, iter, &scriptFrame))
return reportUncaughtException(ac);
@ -1767,7 +1767,7 @@ Debugger::fireExceptionUnwind(JSContext* cx, MutableHandleValue vp)
RootedValue scriptFrame(cx);
RootedValue wrappedExc(cx, exc);
ScriptFrameIter iter(cx);
FrameIter iter(cx);
if (!getScriptFrame(cx, iter, &scriptFrame) || !wrapDebuggeeValue(cx, &wrappedExc))
return reportUncaughtException(ac);
@ -1792,7 +1792,7 @@ Debugger::fireEnterFrame(JSContext* cx, MutableHandleValue vp)
RootedValue scriptFrame(cx);
ScriptFrameIter iter(cx);
FrameIter iter(cx);
if (!getScriptFrame(cx, iter, &scriptFrame))
return reportUncaughtException(ac);
@ -1937,7 +1937,7 @@ Debugger::slowPathOnNewWasmInstance(JSContext* cx, Handle<WasmInstanceObject*> w
/* static */ JSTrapStatus
Debugger::onTrap(JSContext* cx, MutableHandleValue vp)
{
ScriptFrameIter iter(cx);
FrameIter iter(cx);
RootedScript script(cx, iter.script());
MOZ_ASSERT(script->isDebuggee());
Rooted<GlobalObject*> scriptGlobal(cx, &script->global());
@ -2001,7 +2001,7 @@ Debugger::onTrap(JSContext* cx, MutableHandleValue vp)
/* static */ JSTrapStatus
Debugger::onSingleStep(JSContext* cx, MutableHandleValue vp)
{
ScriptFrameIter iter(cx);
FrameIter iter(cx);
/*
* We may be stepping over a JSOP_EXCEPTION, that pushes the context's
@ -2039,6 +2039,8 @@ Debugger::onSingleStep(JSContext* cx, MutableHandleValue vp)
for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) {
AbstractFramePtr frame = r.front().key();
NativeObject* frameobj = r.front().value();
if (frame.isWasmDebugFrame())
continue;
if (frame.script() == trappingScript &&
!frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
{
@ -2336,9 +2338,10 @@ class MOZ_RAII ExecutionObservableCompartments : public Debugger::ExecutionObser
bool shouldRecompileOrInvalidate(JSScript* script) const {
return script->hasBaselineScript() && compartments_.has(script->compartment());
}
bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames, so if
// iter refers to one such, we know we don't match.
bool shouldMarkAsDebuggee(FrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames or
// non-debuggee wasm frames, so if iter refers to one such, we know we
// don't match.
return iter.hasUsableAbstractFramePtr() && compartments_.has(iter.compartment());
}
@ -2397,9 +2400,10 @@ class MOZ_RAII ExecutionObservableFrame : public Debugger::ExecutionObservableSe
script == frame_.asRematerializedFrame()->outerScript();
}
bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames, so if
// iter refers to one such, we know we don't match.
bool shouldMarkAsDebuggee(FrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames or
// non-debuggee wasm frames, so if iter refers to one such, we know we
// don't match.
//
// We never use this 'has' overload for frame invalidation, only for
// frame debuggee marking; so this overload doesn't need a parallel to
@ -2427,7 +2431,7 @@ class MOZ_RAII ExecutionObservableScript : public Debugger::ExecutionObservableS
bool shouldRecompileOrInvalidate(JSScript* script) const {
return script->hasBaselineScript() && script == script_;
}
bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
bool shouldMarkAsDebuggee(FrameIter& iter) const {
// AbstractFramePtr can't refer to non-remateralized Ion frames, and
// while a non-rematerialized Ion frame may indeed be running script_,
// we cannot mark them as debuggees until they bail out.
@ -2437,6 +2441,9 @@ class MOZ_RAII ExecutionObservableScript : public Debugger::ExecutionObservableS
// debuggee. This is correct in that the only other way a frame may be
// marked as debuggee is via Debugger.Frame reflection, which would
// have rematerialized any Ion frames.
//
// Also AbstractFramePtr can't refer to non-debuggee wasm frames, so if
// iter refers to one such, we know we don't match.
return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr().script() == script_;
}
@ -2458,7 +2465,7 @@ Debugger::updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObs
}
AbstractFramePtr oldestEnabledFrame;
for (ScriptFrameIter iter(cx);
for (FrameIter iter(cx);
!iter.done();
++iter)
{
@ -2468,6 +2475,8 @@ Debugger::updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObs
oldestEnabledFrame = iter.abstractFramePtr();
oldestEnabledFrame.setIsDebuggee();
}
if (iter.abstractFramePtr().isWasmDebugFrame())
iter.abstractFramePtr().asWasmDebugFrame()->observeFrame(cx);
} else {
#ifdef DEBUG
// Debugger.Frame lifetimes are managed by the debug epilogue,
@ -2576,6 +2585,17 @@ UpdateExecutionObservabilityOfScriptsInZone(JSContext* cx, Zone* zone,
FinishDiscardBaselineScript(fop, scripts[i]);
}
// Iterate through all wasm instances to find ones that need to be updated.
for (JSCompartment* c : zone->compartments()) {
for (wasm::Instance* instance : c->wasm.instances()) {
if (!instance->debugEnabled())
continue;
bool enableTrap = observing == Debugger::IsObserving::Observing;
instance->ensureEnterFrameTrapsState(cx, enableTrap);
}
}
return true;
}
@ -2599,7 +2619,7 @@ template <typename FrameFn>
/* static */ void
Debugger::forEachDebuggerFrame(AbstractFramePtr frame, FrameFn fn)
{
GlobalObject* global = &frame.script()->global();
GlobalObject* global = frame.global();
if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
Debugger* dbg = *p;
@ -2658,7 +2678,8 @@ Debugger::ensureExecutionObservabilityOfOsrFrame(JSContext* cx, InterpreterFrame
/* static */ bool
Debugger::ensureExecutionObservabilityOfFrame(JSContext* cx, AbstractFramePtr frame)
{
MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
MOZ_ASSERT_IF(frame.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
MOZ_ASSERT_IF(frame.isWasmDebugFrame(), frame.wasmInstance()->debugEnabled());
if (frame.isDebuggee())
return true;
ExecutionObservableFrame obs(frame);
@ -2764,7 +2785,7 @@ Debugger::updateObservesCoverageOnDebuggees(JSContext* cx, IsObserving observing
// If any frame on the stack belongs to the debuggee, then we cannot update
// the ScriptCounts, because this would imply to invalidate a Debugger.Frame
// to recompile it with/without ScriptCount support.
for (ScriptFrameIter iter(cx);
for (FrameIter iter(cx);
!iter.done();
++iter)
{
@ -3733,15 +3754,15 @@ Debugger::getNewestFrame(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
/* Since there may be multiple contexts, use AllScriptFramesIter. */
for (AllScriptFramesIter i(cx); !i.done(); ++i) {
/* Since there may be multiple contexts, use AllFramesIter. */
for (AllFramesIter i(cx); !i.done(); ++i) {
if (dbg->observesFrame(i)) {
// Ensure that Ion frames are rematerialized. Only rematerialized
// Ion frames may be used as AbstractFramePtrs.
if (i.isIon() && !i.ensureHasRematerializedFrame(cx))
return false;
AbstractFramePtr frame = i.abstractFramePtr();
ScriptFrameIter iter(i.activation()->cx());
FrameIter iter(i.activation()->cx());
while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame)
++iter;
return dbg->getScriptFrame(cx, iter, args.rval());
@ -4004,7 +4025,7 @@ Debugger::removeDebuggeeGlobal(FreeOp* fop, GlobalObject* global,
for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
AbstractFramePtr frame = e.front().key();
NativeObject* frameobj = e.front().value();
if (&frame.script()->global() == global) {
if (frame.global() == global) {
DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
e.removeFront();
@ -6251,6 +6272,8 @@ DebuggerScript_getLineOffsets(JSContext* cx, unsigned argc, Value* vp)
bool
Debugger::observesFrame(AbstractFramePtr frame) const
{
if (frame.isWasmDebugFrame())
return observesWasm(frame.wasmInstance());
return observesScript(frame.script());
}
@ -6264,7 +6287,7 @@ Debugger::observesFrame(const FrameIter& iter) const
return false;
}
if (iter.isWasm())
return false;
return observesWasm(iter.wasmInstance());
return observesScript(iter.script());
}
@ -6278,6 +6301,14 @@ Debugger::observesScript(JSScript* script) const
return observesGlobal(&script->global()) && !script->selfHosted();
}
bool
Debugger::observesWasm(wasm::Instance* instance) const
{
if (!enabled || !instance->debugEnabled())
return false;
return observesGlobal(&instance->object()->global());
}
/* static */ bool
Debugger::replaceFrameGuts(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to,
ScriptFrameIter& iter)
@ -7227,7 +7258,7 @@ DebuggerFrame::initClass(JSContext* cx, HandleObject dbgCtor, HandleObject obj)
/* static */ DebuggerFrame*
DebuggerFrame::create(JSContext* cx, HandleObject proto, AbstractFramePtr referent,
const ScriptFrameIter* maybeIter, HandleNativeObject debugger)
const FrameIter* maybeIter, HandleNativeObject debugger)
{
JSObject* obj = NewObjectWithGivenProto(cx, &DebuggerFrame::class_, proto);
if (!obj)
@ -7235,7 +7266,7 @@ DebuggerFrame::create(JSContext* cx, HandleObject proto, AbstractFramePtr refere
DebuggerFrame& frame = obj->as<DebuggerFrame>();
// Eagerly copy ScriptFrameIter data if we've already walked the stack.
// Eagerly copy FrameIter data if we've already walked the stack.
if (maybeIter) {
AbstractFramePtr data = maybeIter->copyDataAsAbstractFramePtr();
if (!data)
@ -7273,10 +7304,10 @@ DebuggerFrame::getIsConstructing(JSContext* cx, HandleDebuggerFrame frame, bool&
{
MOZ_ASSERT(frame->isLive());
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
result = iter.isFunctionFrame() && iter.isConstructing();
return true;
@ -7285,6 +7316,11 @@ DebuggerFrame::getIsConstructing(JSContext* cx, HandleDebuggerFrame frame, bool&
static void
UpdateFrameIterPc(FrameIter& iter)
{
if (iter.abstractFramePtr().isWasmDebugFrame()) {
// Wasm debug frames don't need their pc updated -- it's null.
return;
}
if (iter.abstractFramePtr().isRematerializedFrame()) {
#ifdef DEBUG
// Rematerialized frames don't need their pc updated. The reason we
@ -7327,10 +7363,10 @@ DebuggerFrame::getEnvironment(JSContext* cx, HandleDebuggerFrame frame,
Debugger* dbg = frame->owner();
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
Rooted<Env*> env(cx);
{
@ -7347,8 +7383,9 @@ DebuggerFrame::getEnvironment(JSContext* cx, HandleDebuggerFrame frame,
/* static */ bool
DebuggerFrame::getIsGenerator(HandleDebuggerFrame frame)
{
return DebuggerFrame::getReferent(frame).script()->isStarGenerator() ||
DebuggerFrame::getReferent(frame).script()->isLegacyGenerator();
AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
return referent.hasScript() && referent.script()->isStarGenerator() ||
referent.hasScript() && referent.script()->isLegacyGenerator();
}
/* static */ bool
@ -7356,10 +7393,13 @@ DebuggerFrame::getOffset(JSContext* cx, HandleDebuggerFrame frame, size_t& resul
{
MOZ_ASSERT(frame->isLive());
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
if (!requireScriptReferent(cx, frame))
return false;
ScriptFrameIter& iter = *maybeIter;
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
FrameIter& iter = *maybeIter;
JSScript* script = iter.script();
UpdateFrameIterPc(iter);
@ -7376,10 +7416,10 @@ DebuggerFrame::getOlder(JSContext* cx, HandleDebuggerFrame frame,
Debugger* dbg = frame->owner();
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
for (++iter; !iter.done(); ++iter) {
if (dbg->observesFrame(iter)) {
@ -7397,13 +7437,15 @@ DebuggerFrame::getOlder(JSContext* cx, HandleDebuggerFrame frame,
DebuggerFrame::getThis(JSContext* cx, HandleDebuggerFrame frame, MutableHandleValue result)
{
MOZ_ASSERT(frame->isLive());
if (!requireScriptReferent(cx, frame))
return false;
Debugger* dbg = frame->owner();
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
{
AbstractFramePtr frame = iter.abstractFramePtr();
@ -7435,6 +7477,8 @@ DebuggerFrame::getType(HandleDebuggerFrame frame)
return DebuggerFrameType::Call;
else if (referent.isModuleFrame())
return DebuggerFrameType::Module;
else if (referent.isWasmDebugFrame())
return DebuggerFrameType::WasmCall;
MOZ_CRASH("Unknown frame type");
}
@ -7447,6 +7491,8 @@ DebuggerFrame::getImplementation(HandleDebuggerFrame frame)
return DebuggerFrameImplementation::Baseline;
else if (referent.isRematerializedFrame())
return DebuggerFrameImplementation::Ion;
else if (referent.isWasmDebugFrame())
return DebuggerFrameImplementation::Wasm;
return DebuggerFrameImplementation::Interpreter;
}
@ -7514,7 +7560,7 @@ static bool
DebuggerGenericEval(JSContext* cx, const mozilla::Range<const char16_t> chars,
HandleObject bindings, const EvalOptions& options,
JSTrapStatus& status, MutableHandleValue value,
Debugger* dbg, HandleObject envArg, ScriptFrameIter* iter)
Debugger* dbg, HandleObject envArg, FrameIter* iter)
{
/* Either we're specifying the frame, or a global. */
MOZ_ASSERT_IF(iter, !envArg);
@ -7605,13 +7651,15 @@ DebuggerFrame::eval(JSContext* cx, HandleDebuggerFrame frame, mozilla::Range<con
MutableHandleValue value)
{
MOZ_ASSERT(frame->isLive());
if (!requireScriptReferent(cx, frame))
return false;
Debugger* dbg = frame->owner();
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
Maybe<FrameIter> maybeIter;
if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
FrameIter& iter = *maybeIter;
UpdateFrameIterPc(iter);
@ -7641,22 +7689,22 @@ DebuggerFrame::getReferent(HandleDebuggerFrame frame)
{
AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate());
if (referent.isScriptFrameIterData()) {
ScriptFrameIter iter(*(ScriptFrameIter::Data*)(referent.raw()));
FrameIter iter(*(FrameIter::Data*)(referent.raw()));
referent = iter.abstractFramePtr();
}
return referent;
}
/* static */ bool
DebuggerFrame::getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
Maybe<ScriptFrameIter>& result)
DebuggerFrame::getFrameIter(JSContext* cx, HandleDebuggerFrame frame,
Maybe<FrameIter>& result)
{
AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate());
if (referent.isScriptFrameIterData()) {
result.emplace(*reinterpret_cast<ScriptFrameIter::Data*>(referent.raw()));
result.emplace(*reinterpret_cast<FrameIter::Data*>(referent.raw()));
} else {
result.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
ScriptFrameIter& iter = *result;
result.emplace(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
FrameIter& iter = *result;
while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != referent)
++iter;
AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();
@ -7667,12 +7715,26 @@ DebuggerFrame::getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
return true;
}
/* static */ bool
DebuggerFrame::requireScriptReferent(JSContext* cx, HandleDebuggerFrame frame)
{
AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
if (!referent.hasScript()) {
RootedValue frameobj(cx, ObjectValue(*frame));
ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_BAD_REFERENT,
JSDVG_SEARCH_STACK, frameobj, nullptr,
"a script frame", nullptr);
return false;
}
return true;
}
static void
DebuggerFrame_freeScriptFrameIterData(FreeOp* fop, JSObject* obj)
{
AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->as<NativeObject>().getPrivate());
if (frame.isScriptFrameIterData())
fop->delete_((ScriptFrameIter::Data*) frame.raw());
fop->delete_((FrameIter::Data*) frame.raw());
obj->as<NativeObject>().setPrivate(nullptr);
}
@ -7761,20 +7823,20 @@ DebuggerFrame_checkThis(JSContext* cx, const CallArgs& args, const char* fnname,
THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \
AbstractFramePtr frame = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
if (frame.isScriptFrameIterData()) { \
ScriptFrameIter iter(*(ScriptFrameIter::Data*)(frame.raw())); \
FrameIter iter(*(FrameIter::Data*)(frame.raw())); \
frame = iter.abstractFramePtr(); \
}
#define THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter) \
THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \
Maybe<ScriptFrameIter> maybeIter; \
Maybe<FrameIter> maybeIter; \
{ \
AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
if (f.isScriptFrameIterData()) { \
maybeIter.emplace(*(ScriptFrameIter::Data*)(f.raw())); \
maybeIter.emplace(*(FrameIter::Data*)(f.raw())); \
} else { \
maybeIter.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); \
ScriptFrameIter& iter = *maybeIter; \
maybeIter.emplace(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); \
FrameIter& iter = *maybeIter; \
while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != f) \
++iter; \
AbstractFramePtr data = iter.copyDataAsAbstractFramePtr(); \
@ -7783,7 +7845,7 @@ DebuggerFrame_checkThis(JSContext* cx, const CallArgs& args, const char* fnname,
thisobj->setPrivate(data.raw()); \
} \
} \
ScriptFrameIter& iter = *maybeIter
FrameIter& iter = *maybeIter
#define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, frame, dbg) \
THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame); \
@ -7814,6 +7876,9 @@ DebuggerFrame::typeGetter(JSContext* cx, unsigned argc, Value* vp)
case DebuggerFrameType::Module:
str = cx->names().module;
break;
case DebuggerFrameType::WasmCall:
str = cx->names().wasmcall;
break;
default:
MOZ_CRASH("bad DebuggerFrameType value");
}
@ -7840,6 +7905,9 @@ DebuggerFrame::implementationGetter(JSContext* cx, unsigned argc, Value* vp)
case DebuggerFrameImplementation::Interpreter:
s = "interpreter";
break;
case DebuggerFrameImplementation::Wasm:
s = "wasm";
break;
default:
MOZ_CRASH("bad DebuggerFrameImplementation value");
}
@ -8058,6 +8126,11 @@ DebuggerFrame_getScript(JSContext* cx, unsigned argc, Value* vp)
if (!scriptObject)
return false;
}
} else if (frame.isWasmDebugFrame()) {
RootedWasmInstanceObject instance(cx, frame.wasmInstance()->object());
scriptObject = debug->wrapWasmScript(cx, instance);
if (!scriptObject)
return false;
} else {
/*
* We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script

View file

@ -297,7 +297,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
virtual const HashSet<Zone*>* zones() const { return nullptr; }
virtual bool shouldRecompileOrInvalidate(JSScript* script) const = 0;
virtual bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const = 0;
virtual bool shouldMarkAsDebuggee(FrameIter& iter) const = 0;
};
// This enum is converted to and compare with bool values; NotObserving
@ -768,11 +768,11 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
* Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy
* its data if we need to make a new Debugger.Frame.
*/
MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
const ScriptFrameIter* maybeIter,
[[nodiscard]] bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
const FrameIter* maybeIter,
MutableHandleValue vp);
MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
const ScriptFrameIter* maybeIter,
[[nodiscard]] bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
const FrameIter* maybeIter,
MutableHandleDebuggerFrame result);
inline Breakpoint* firstBreakpoint() const;
@ -911,6 +911,7 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
bool observesFrame(AbstractFramePtr frame) const;
bool observesFrame(const FrameIter& iter) const;
bool observesScript(JSScript* script) const;
bool observesWasm(wasm::Instance* instance) const;
/*
* If env is nullptr, call vp->setNull() and return true. Otherwise, find
@ -995,11 +996,11 @@ class Debugger : private mozilla::LinkedListElement<Debugger>
* frame, in which case the cost of walking the stack has already been
* paid.
*/
MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
[[nodiscard]] bool getScriptFrame(JSContext* cx, const FrameIter& iter,
MutableHandleValue vp) {
return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp);
}
MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
[[nodiscard]] bool getScriptFrame(JSContext* cx, const FrameIter& iter,
MutableHandleDebuggerFrame result);
@ -1144,13 +1145,15 @@ enum class DebuggerFrameType {
Eval,
Global,
Call,
Module
Module,
WasmCall
};
enum class DebuggerFrameImplementation {
Interpreter,
Baseline,
Ion
Ion,
Wasm
};
class DebuggerFrame : public NativeObject
@ -1166,7 +1169,7 @@ class DebuggerFrame : public NativeObject
static NativeObject* initClass(JSContext* cx, HandleObject dbgCtor, HandleObject objProto);
static DebuggerFrame* create(JSContext* cx, HandleObject proto, AbstractFramePtr referent,
const ScriptFrameIter* maybeIter, HandleNativeObject debugger);
const FrameIter* maybeIter, HandleNativeObject debugger);
static MOZ_MUST_USE bool getCallee(JSContext* cx, HandleDebuggerFrame frame,
MutableHandleDebuggerObject result);
@ -1197,8 +1200,9 @@ class DebuggerFrame : public NativeObject
static const JSFunctionSpec methods_[];
static AbstractFramePtr getReferent(HandleDebuggerFrame frame);
static MOZ_MUST_USE bool getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
mozilla::Maybe<ScriptFrameIter>& result);
[[nodiscard]] static bool getFrameIter(JSContext* cx, HandleDebuggerFrame frame,
mozilla::Maybe<FrameIter>& result);
[[nodiscard]] static bool requireScriptReferent(JSContext* cx, HandleDebuggerFrame frame);
static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);

View file

@ -21,6 +21,7 @@
#include "vm/ProxyObject.h"
#include "vm/Shape.h"
#include "vm/Xdr.h"
#include "wasm/WasmInstance.h"
#include "jsatominlines.h"
#include "jsobjinlines.h"
@ -626,6 +627,37 @@ ModuleEnvironmentObject::enumerate(JSContext* cx, HandleObject obj, AutoIdVector
/*****************************************************************************/
const Class WasmFunctionCallObject::class_ = {
"WasmCall",
JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(WasmFunctionCallObject::RESERVED_SLOTS)
};
/* static */ WasmFunctionCallObject*
WasmFunctionCallObject::createHollowForDebug(JSContext* cx, WasmFunctionScope* scope)
{
RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
if (!group)
return nullptr;
RootedShape shape(cx, scope->getEmptyEnvironmentShape(cx));
if (!shape)
return nullptr;
gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
kind = gc::GetBackgroundAllocKind(kind);
JSObject* obj;
JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, gc::DefaultHeap, shape, group));
Rooted<WasmFunctionCallObject*> callobj(cx, &obj->as<WasmFunctionCallObject>());
callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
return callobj;
}
/*****************************************************************************/
WithEnvironmentObject*
WithEnvironmentObject::create(JSContext* cx, HandleObject object, HandleObject enclosing,
Handle<WithScope*> scope)
@ -1198,6 +1230,17 @@ EnvironmentIter::EnvironmentIter(JSContext* cx, AbstractFramePtr frame, jsbyteco
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
EnvironmentIter::EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope, AbstractFramePtr frame
MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
: si_(cx, ScopeIter(scope)),
env_(cx, env),
frame_(frame)
{
assertSameCompartment(cx, frame);
settle();
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
void
EnvironmentIter::incrementScopeIter()
{
@ -1218,7 +1261,9 @@ EnvironmentIter::settle()
{
// Check for trying to iterate a function or eval frame before the prologue has
// created the CallObject, in which case we have to skip.
if (frame_ && frame_.script()->initialEnvironmentShape() && !frame_.hasInitialEnvironment()) {
if (frame_ && frame_.hasScript() &&
frame_.script()->initialEnvironmentShape() && !frame_.hasInitialEnvironment())
{
// Skip until we're at the enclosing scope of the script.
while (si_.scope() != frame_.script()->enclosingScope()) {
if (env_->is<LexicalEnvironmentObject>() &&
@ -1235,9 +1280,11 @@ EnvironmentIter::settle()
// Check if we have left the extent of the initial frame after we've
// settled on a static scope.
if (frame_ && (!si_ || si_.scope() == frame_.script()->enclosingScope()))
if (frame_ && (frame_.isWasmDebugFrame() ||
(!si_ || si_.scope() == frame_.script()->enclosingScope())))
{
frame_ = NullFramePtr();
}
#ifdef DEBUG
if (si_) {
if (hasSyntacticEnvironment()) {
@ -2222,6 +2269,7 @@ DebugEnvironmentProxy::isForDeclarative() const
return e.is<CallObject>() ||
e.is<VarEnvironmentObject>() ||
e.is<ModuleEnvironmentObject>() ||
e.is<WasmFunctionCallObject>() ||
e.is<LexicalEnvironmentObject>();
}
@ -2736,7 +2784,12 @@ DebugEnvironments::updateLiveEnvironments(JSContext* cx)
if (!frame.isDebuggee())
continue;
for (EnvironmentIter ei(cx, frame, i.pc()); ei.withinInitialFrame(); ei++) {
RootedObject env(cx);
RootedScope scope(cx);
if (!GetFrameEnvironmentAndScope(cx, frame, i.pc(), &env, &scope))
return false;
for (EnvironmentIter ei(cx, env, scope, frame); ei.withinInitialFrame(); ei++) {
if (ei.hasSyntacticEnvironment() && !ei.scope().is<GlobalScope>()) {
MOZ_ASSERT(ei.environment().compartment() == cx->compartment());
DebugEnvironments* envs = ensureCompartmentData(cx);
@ -2860,6 +2913,7 @@ GetDebugEnvironmentForMissing(JSContext* cx, const EnvironmentIter& ei)
MOZ_ASSERT(!ei.hasSyntacticEnvironment() &&
(ei.scope().is<FunctionScope>() ||
ei.scope().is<LexicalScope>() ||
ei.scope().is<WasmFunctionScope>() ||
ei.scope().is<VarScope>()));
if (DebugEnvironmentProxy* debugEnv = DebugEnvironments::hasDebugEnvironment(cx, ei))
@ -2903,6 +2957,13 @@ GetDebugEnvironmentForMissing(JSContext* cx, const EnvironmentIter& ei)
return nullptr;
debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
} else if (ei.scope().is<WasmFunctionScope>()) {
Rooted<WasmFunctionScope*> wasmFunctionScope(cx, &ei.scope().as<WasmFunctionScope>());
Rooted<WasmFunctionCallObject*> callobj(cx, WasmFunctionCallObject::createHollowForDebug(cx, wasmFunctionScope));
if (!callobj)
return nullptr;
debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug);
} else {
Rooted<VarScope*> varScope(cx, &ei.scope().as<VarScope>());
Rooted<VarEnvironmentObject*> env(cx,
@ -2947,6 +3008,7 @@ GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei)
if (ei.scope().is<FunctionScope>() ||
ei.scope().is<LexicalScope>() ||
ei.scope().is<WasmFunctionScope>() ||
ei.scope().is<VarScope>())
{
return GetDebugEnvironmentForMissing(cx, ei);
@ -2977,7 +3039,12 @@ js::GetDebugEnvironmentForFrame(JSContext* cx, AbstractFramePtr frame, jsbytecod
if (CanUseDebugEnvironmentMaps(cx) && !DebugEnvironments::updateLiveEnvironments(cx))
return nullptr;
EnvironmentIter ei(cx, frame, pc);
RootedObject env(cx);
RootedScope scope(cx);
if (!GetFrameEnvironmentAndScope(cx, frame, pc, &env, &scope))
return nullptr;
EnvironmentIter ei(cx, env, scope, frame);
return GetDebugEnvironment(cx, ei);
}
@ -3099,7 +3166,12 @@ bool
js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
MutableHandleValue res)
{
for (EnvironmentIter ei(cx, frame, pc); ei; ei++) {
RootedObject scopeChain(cx);
RootedScope scope(cx);
if (!GetFrameEnvironmentAndScope(cx, frame, pc, &scopeChain, &scope))
return false;
for (EnvironmentIter ei(cx, scopeChain, scope, frame); ei; ei++) {
if (ei.scope().kind() == ScopeKind::Module) {
res.setUndefined();
return true;
@ -3172,8 +3244,6 @@ js::GetThisValueForDebuggerMaybeOptimizedOut(JSContext* cx, AbstractFramePtr fra
MOZ_CRASH("'this' binding must be found");
}
RootedObject scopeChain(cx, frame.environmentChain());
return GetNonSyntacticGlobalThis(cx, scopeChain, res);
}
@ -3442,6 +3512,24 @@ js::PushVarEnvironmentObject(JSContext* cx, HandleScope scope, AbstractFramePtr
return true;
}
bool
js::GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
MutableHandleObject env, MutableHandleScope scope)
{
env.set(frame.environmentChain());
if (frame.isWasmDebugFrame()) {
RootedWasmInstanceObject instance(cx, frame.wasmInstance()->object());
uint32_t funcIndex = frame.asWasmDebugFrame()->funcIndex();
scope.set(WasmInstanceObject::getFunctionScope(cx, instance, funcIndex));
if (!scope)
return false;
} else {
scope.set(frame.script()->innermostScope(pc));
}
return true;
}
#ifdef DEBUG
typedef HashSet<PropertyName*> PropertyNameSet;

View file

@ -425,6 +425,17 @@ typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject;
typedef MutableHandle<ModuleEnvironmentObject*> MutableHandleModuleEnvironmentObject;
class WasmFunctionCallObject : public EnvironmentObject
{
public:
static const Class class_;
static const uint32_t RESERVED_SLOTS = 1;
static WasmFunctionCallObject* createHollowForDebug(JSContext* cx,
WasmFunctionScope* scope);
};
class LexicalEnvironmentObject : public EnvironmentObject
{
// Global and non-syntactic lexical environments need to store a 'this'
@ -660,6 +671,12 @@ class MOZ_RAII EnvironmentIter
EnvironmentIter(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
// Constructing from an environment, scope and frame. The frame is given
// to initialize to proper enclosing environment/scope.
EnvironmentIter(JSContext* cx, JSObject* env, Scope* scope, AbstractFramePtr frame
MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
bool done() const {
return si_.done();
}
@ -984,6 +1001,7 @@ JSObject::is<js::EnvironmentObject>() const
return is<js::CallObject>() ||
is<js::VarEnvironmentObject>() ||
is<js::ModuleEnvironmentObject>() ||
is<js::WasmFunctionCallObject>() ||
is<js::LexicalEnvironmentObject>() ||
is<js::WithEnvironmentObject>() ||
is<js::NonSyntacticVariablesObject>() ||
@ -1114,6 +1132,10 @@ InitFunctionEnvironmentObjects(JSContext* cx, AbstractFramePtr frame);
MOZ_MUST_USE bool
PushVarEnvironmentObject(JSContext* cx, HandleScope scope, AbstractFramePtr frame);
[[nodiscard]] bool
GetFrameEnvironmentAndScope(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc,
MutableHandleObject env, MutableHandleScope scope);
#ifdef DEBUG
bool
AnalyzeEntrainedVariables(JSContext* cx, HandleScript script);

View file

@ -896,6 +896,9 @@ PopEnvironment(JSContext* cx, EnvironmentIter& ei)
case ScopeKind::NonSyntactic:
case ScopeKind::Module:
break;
case ScopeKind::WasmFunction:
MOZ_CRASH("wasm is not interpreted");
break;
}
}

View file

@ -156,8 +156,8 @@ struct SavedFrame::Lookup {
activation(activation)
{
MOZ_ASSERT(source);
MOZ_ASSERT_IF(framePtr.isSome(), pc);
MOZ_ASSERT_IF(framePtr.isSome(), activation);
MOZ_ASSERT_IF(framePtr.isSome() && !activation->isWasm(), pc);
#ifdef JS_MORE_DETERMINISTIC
column = 0;
@ -1323,7 +1323,7 @@ SavedStacks::insertFrames(JSContext* cx, FrameIter& iter, MutableHandleSavedFram
parentIsInCache = iter.hasCachedSavedFrame();
auto principals = iter.compartment()->principals();
auto displayAtom = iter.isFunctionFrame() ? iter.functionDisplayAtom() : nullptr;
auto displayAtom = (iter.isWasm() || iter.isFunctionFrame()) ? iter.functionDisplayAtom() : nullptr;
if (!stackChain->emplaceBack(location.source(),
location.line(),
location.column(),

View file

@ -74,6 +74,8 @@ js::ScopeKindString(ScopeKind kind)
return "non-syntactic";
case ScopeKind::Module:
return "module";
case ScopeKind::WasmFunction:
return "wasm function";
}
MOZ_CRASH("Bad ScopeKind");
}
@ -398,6 +400,10 @@ Scope::clone(JSContext* cx, HandleScope scope, HandleScope enclosing)
MOZ_CRASH("Use GlobalScope::clone.");
break;
case ScopeKind::WasmFunction:
MOZ_CRASH("wasm functions are not nested in JSScript");
break;
case ScopeKind::Module:
MOZ_CRASH("NYI");
break;
@ -485,6 +491,9 @@ LexicalScope::nextFrameSlot(Scope* scope)
return 0;
case ScopeKind::Module:
return si.scope()->as<ModuleScope>().nextFrameSlot();
case ScopeKind::WasmFunction:
// TODO return si.scope()->as<WasmFunctionScope>().nextFrameSlot();
return 0;
}
}
MOZ_CRASH("Not an enclosing intra-frame Scope");
@ -1191,6 +1200,48 @@ ModuleScope::script() const
return module()->script();
}
// TODO Check what Debugger behavior should be when it evaluates a
// var declaration.
static const uint32_t WasmFunctionEnvShapeFlags =
BaseShape::NOT_EXTENSIBLE | BaseShape::DELEGATE;
/* static */ WasmFunctionScope*
WasmFunctionScope::create(JSContext* cx, WasmInstanceObject* instance, uint32_t funcIndex)
{
// WasmFunctionScope::Data has GCManagedDeletePolicy because it contains a
// GCPtr. Destruction of |data| below may trigger calls into the GC.
Rooted<WasmFunctionScope*> wasmFunctionScope(cx);
{
// TODO pull the local variable names from the wasm function definition.
Rooted<UniquePtr<Data>> data(cx, NewEmptyScopeData<WasmFunctionScope>(cx));
if (!data)
return nullptr;
Rooted<Scope*> enclosingScope(cx, &cx->global()->emptyGlobalScope());
data->instance.init(instance);
data->funcIndex = funcIndex;
Scope* scope = Scope::create(cx, ScopeKind::WasmFunction, enclosingScope, /* envShape = */ nullptr);
if (!scope)
return nullptr;
wasmFunctionScope = &scope->as<WasmFunctionScope>();
wasmFunctionScope->initData(Move(data.get()));
}
return wasmFunctionScope;
}
/* static */ Shape*
WasmFunctionScope::getEmptyEnvironmentShape(JSContext* cx)
{
const Class* cls = &WasmFunctionCallObject::class_;
return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), WasmFunctionEnvShapeFlags);
}
ScopeIter::ScopeIter(JSScript* script)
: scope_(script->bodyScope())
{ }
@ -1242,6 +1293,9 @@ BindingIter::BindingIter(Scope* scope)
case ScopeKind::Module:
init(scope->as<ModuleScope>().data());
break;
case ScopeKind::WasmFunction:
init(scope->as<WasmFunctionScope>().data());
break;
}
}
@ -1372,6 +1426,22 @@ BindingIter::init(ModuleScope::Data& data)
data.trailingNames.start(), data.length);
}
void
BindingIter::init(WasmFunctionScope::Data& data)
{
// imports - [0, 0)
// positional formals - [0, 0)
// other formals - [0, 0)
// top-level funcs - [0, 0)
// vars - [0, 0)
// lets - [0, 0)
// consts - [0, 0)
init(0, 0, 0, 0, 0, 0,
CanHaveFrameSlots | CanHaveEnvironmentSlots,
UINT32_MAX, UINT32_MAX,
data.trailingNames.start(), data.length);
}
PositionalFormalParameterIter::PositionalFormalParameterIter(JSScript* script)
: BindingIter(script)
{

View file

@ -71,7 +71,10 @@ enum class ScopeKind : uint8_t
NonSyntactic,
// ModuleScope
Module
Module,
// WasmFunctionScope
WasmFunction
};
static inline bool
@ -963,6 +966,60 @@ class ModuleScope : public Scope
static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx);
};
// Scope corresponding to the wasm function. A WasmFunctionScope is used by
// Debugger only, and not for wasm execution.
//
class WasmFunctionScope : public Scope
{
friend class BindingIter;
friend class Scope;
static const ScopeKind classScopeKind_ = ScopeKind::WasmFunction;
public:
struct Data
{
uint32_t length= 0;
uint32_t nextFrameSlot= 0;
uint32_t funcIndex= 0;
// The wasm instance of the scope.
GCPtr<WasmInstanceObject*> instance = {};
TrailingNamesArray trailingNames;
explicit Data(size_t nameCount) : trailingNames(nameCount) {}
Data() = delete;
void trace(JSTracer* trc);
};
static WasmFunctionScope* create(JSContext* cx, WasmInstanceObject* instance, uint32_t funcIndex);
static size_t sizeOfData(uint32_t length) {
return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
}
private:
Data& data() {
return *reinterpret_cast<Data*>(data_);
}
const Data& data() const {
return *reinterpret_cast<Data*>(data_);
}
public:
WasmInstanceObject* instance() const {
return data().instance;
}
uint32_t funcIndex() const {
return data().funcIndex;
}
static Shape* getEmptyEnvironmentShape(JSContext* cx);
};
//
// An iterator for a Scope's bindings. This is the source of truth for frame
// and environment object layout.
@ -1072,6 +1129,7 @@ class BindingIter
void init(GlobalScope::Data& data);
void init(EvalScope::Data& data, bool strict);
void init(ModuleScope::Data& data);
void init(WasmFunctionScope::Data& data);
bool hasFormalParameterExprs() const {
return flags_ & HasFormalParameterExprs;
@ -1144,6 +1202,10 @@ class BindingIter
init(data);
}
explicit BindingIter(WasmFunctionScope::Data& data) {
init(data);
}
BindingIter(EvalScope::Data& data, bool strict) {
init(data, strict);
}
@ -1448,6 +1510,7 @@ DEFINE_SCOPE_DATA_GCPOLICY(js::VarScope::Data);
DEFINE_SCOPE_DATA_GCPOLICY(js::GlobalScope::Data);
DEFINE_SCOPE_DATA_GCPOLICY(js::EvalScope::Data);
DEFINE_SCOPE_DATA_GCPOLICY(js::ModuleScope::Data);
DEFINE_SCOPE_DATA_GCPOLICY(js::WasmFunctionScope::Data);
#undef DEFINE_SCOPE_DATA_GCPOLICY

View file

@ -18,6 +18,8 @@
#include "js/Debug.h"
#include "vm/EnvironmentObject.h"
#include "vm/GeneratorObject.h"
#include "wasm/WasmDebugFrame.h"
#include "wasm/WasmInstance.h"
#include "jsobjinlines.h"
#include "jsscriptinlines.h"
@ -429,6 +431,8 @@ AbstractFramePtr::returnValue() const
{
if (isInterpreterFrame())
return asInterpreterFrame()->returnValue();
if (isWasmDebugFrame())
return UndefinedHandleValue;
return asBaselineFrame()->returnValue();
}
@ -443,6 +447,12 @@ AbstractFramePtr::setReturnValue(const Value& rval) const
asBaselineFrame()->setReturnValue(rval);
return;
}
if (isWasmDebugFrame()) {
// TODO handle wasm function return value
// The function is called from Debugger::slowPathOnLeaveFrame --
// ignoring value for wasm.
return;
}
asRematerializedFrame()->setReturnValue(rval);
}
@ -453,6 +463,8 @@ AbstractFramePtr::environmentChain() const
return asInterpreterFrame()->environmentChain();
if (isBaselineFrame())
return asBaselineFrame()->environmentChain();
if (isWasmDebugFrame())
return asWasmDebugFrame()->environmentChain();
return asRematerializedFrame()->environmentChain();
}
@ -589,6 +601,8 @@ AbstractFramePtr::isGlobalFrame() const
return asInterpreterFrame()->isGlobalFrame();
if (isBaselineFrame())
return asBaselineFrame()->isGlobalFrame();
if (isWasmDebugFrame())
return false;
return asRematerializedFrame()->isGlobalFrame();
}
@ -599,6 +613,8 @@ AbstractFramePtr::isModuleFrame() const
return asInterpreterFrame()->isModuleFrame();
if (isBaselineFrame())
return asBaselineFrame()->isModuleFrame();
if (isWasmDebugFrame())
return false;
return asRematerializedFrame()->isModuleFrame();
}
@ -609,6 +625,8 @@ AbstractFramePtr::isEvalFrame() const
return asInterpreterFrame()->isEvalFrame();
if (isBaselineFrame())
return asBaselineFrame()->isEvalFrame();
if (isWasmDebugFrame())
return false;
MOZ_ASSERT(isRematerializedFrame());
return false;
}
@ -631,6 +649,8 @@ AbstractFramePtr::hasCachedSavedFrame() const
return asInterpreterFrame()->hasCachedSavedFrame();
if (isBaselineFrame())
return asBaselineFrame()->hasCachedSavedFrame();
if (isWasmDebugFrame())
return asWasmDebugFrame()->hasCachedSavedFrame();
return asRematerializedFrame()->hasCachedSavedFrame();
}
@ -641,6 +661,8 @@ AbstractFramePtr::setHasCachedSavedFrame()
asInterpreterFrame()->setHasCachedSavedFrame();
else if (isBaselineFrame())
asBaselineFrame()->setHasCachedSavedFrame();
else if (isWasmDebugFrame())
asWasmDebugFrame()->setHasCachedSavedFrame();
else
asRematerializedFrame()->setHasCachedSavedFrame();
}
@ -652,6 +674,8 @@ AbstractFramePtr::isDebuggee() const
return asInterpreterFrame()->isDebuggee();
if (isBaselineFrame())
return asBaselineFrame()->isDebuggee();
if (isWasmDebugFrame())
return asWasmDebugFrame()->isDebuggee();
return asRematerializedFrame()->isDebuggee();
}
@ -662,6 +686,8 @@ AbstractFramePtr::setIsDebuggee()
asInterpreterFrame()->setIsDebuggee();
else if (isBaselineFrame())
asBaselineFrame()->setIsDebuggee();
else if (isWasmDebugFrame())
asWasmDebugFrame()->setIsDebuggee();
else
asRematerializedFrame()->setIsDebuggee();
}
@ -673,6 +699,8 @@ AbstractFramePtr::unsetIsDebuggee()
asInterpreterFrame()->unsetIsDebuggee();
else if (isBaselineFrame())
asBaselineFrame()->unsetIsDebuggee();
else if (isWasmDebugFrame())
asWasmDebugFrame()->unsetIsDebuggee();
else
asRematerializedFrame()->unsetIsDebuggee();
}
@ -690,10 +718,17 @@ AbstractFramePtr::isConstructing() const
}
inline bool
AbstractFramePtr::hasArgs() const {
AbstractFramePtr::hasArgs() const
{
return isFunctionFrame();
}
inline bool
AbstractFramePtr::hasScript() const
{
return !isWasmDebugFrame();
}
inline JSScript*
AbstractFramePtr::script() const
{
@ -704,6 +739,21 @@ AbstractFramePtr::script() const
return asRematerializedFrame()->script();
}
inline wasm::Instance*
AbstractFramePtr::wasmInstance() const
{
return asWasmDebugFrame()->instance();
}
inline GlobalObject*
AbstractFramePtr::global() const
{
if (isWasmDebugFrame())
return &wasmInstance()->object()->global();
return &script()->global();
}
inline JSFunction*
AbstractFramePtr::callee() const
{
@ -731,6 +781,8 @@ AbstractFramePtr::isFunctionFrame() const
return asInterpreterFrame()->isFunctionFrame();
if (isBaselineFrame())
return asBaselineFrame()->isFunctionFrame();
if (isWasmDebugFrame())
return false;
return asRematerializedFrame()->isFunctionFrame();
}
@ -803,6 +855,8 @@ AbstractFramePtr::prevUpToDate() const
return asInterpreterFrame()->prevUpToDate();
if (isBaselineFrame())
return asBaselineFrame()->prevUpToDate();
if (isWasmDebugFrame())
return asWasmDebugFrame()->prevUpToDate();
return asRematerializedFrame()->prevUpToDate();
}
@ -817,6 +871,10 @@ AbstractFramePtr::setPrevUpToDate() const
asBaselineFrame()->setPrevUpToDate();
return;
}
if (isWasmDebugFrame()) {
asWasmDebugFrame()->setPrevUpToDate();
return;
}
asRematerializedFrame()->setPrevUpToDate();
}
@ -831,6 +889,10 @@ AbstractFramePtr::unsetPrevUpToDate() const
asBaselineFrame()->unsetPrevUpToDate();
return;
}
if (isWasmDebugFrame()) {
asWasmDebugFrame()->unsetPrevUpToDate();
return;
}
asRematerializedFrame()->unsetPrevUpToDate();
}
@ -857,6 +919,8 @@ AbstractFramePtr::newTarget() const
inline bool
AbstractFramePtr::debuggerNeedsCheckPrimitiveReturn() const
{
if (isWasmDebugFrame())
return false;
return script()->isDerivedClassConstructor();
}

View file

@ -16,6 +16,7 @@
#include "js/GCAPI.h"
#include "vm/Debugger.h"
#include "vm/Opcodes.h"
#include "wasm/WasmDebugFrame.h"
#include "jit/JitFrameIterator-inl.h"
#include "vm/EnvironmentObject-inl.h"
@ -155,6 +156,10 @@ AssertScopeMatchesEnvironment(Scope* scope, JSObject* originalEnv)
si.scope()->as<ModuleScope>().module());
env = &env->as<ModuleEnvironmentObject>().enclosingEnvironment();
break;
case ScopeKind::WasmFunction:
env = &env->as<WasmFunctionCallObject>().enclosingEnvironment();
break;
}
}
}
@ -551,7 +556,7 @@ FrameIter::settleOnActivation()
continue;
}
data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
data_.pc_ = nullptr;
data_.state_ = WASM;
return;
}
@ -684,7 +689,7 @@ FrameIter::popWasmFrame()
MOZ_ASSERT(data_.state_ == WASM);
++data_.wasmFrames_;
data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
data_.pc_ = nullptr;
if (data_.wasmFrames_.done())
popActivation();
}
@ -732,7 +737,6 @@ FrameIter::copyData() const
if (!data)
return nullptr;
MOZ_ASSERT(data_.state_ != WASM);
if (data && data_.jitFrames_.isIonScripted())
data->ionInlineFrameNo_ = ionInlineFrames_.frameNo();
return data;
@ -758,7 +762,7 @@ FrameIter::rawFramePtr() const
case INTERP:
return interpFrame();
case WASM:
return data_.wasmFrames_.fp();
return nullptr;
}
MOZ_CRASH("Unexpected state");
}
@ -810,7 +814,7 @@ FrameIter::isFunctionFrame() const
return data_.jitFrames_.baselineFrame()->isFunctionFrame();
return script()->functionNonDelazifying();
case WASM:
return true;
return false;
}
MOZ_CRASH("Unexpected state");
}
@ -818,15 +822,15 @@ FrameIter::isFunctionFrame() const
JSAtom*
FrameIter::functionDisplayAtom() const
{
MOZ_ASSERT(isFunctionFrame());
switch (data_.state_) {
case DONE:
break;
case INTERP:
case JIT:
MOZ_ASSERT(isFunctionFrame());
return calleeTemplate()->displayAtom();
case WASM:
MOZ_ASSERT(isWasm());
return data_.wasmFrames_.functionDisplayAtom();
}
@ -945,7 +949,6 @@ FrameIter::hasUsableAbstractFramePtr() const
{
switch (data_.state_) {
case DONE:
case WASM:
return false;
case JIT:
if (data_.jitFrames_.isBaselineJS())
@ -957,6 +960,8 @@ FrameIter::hasUsableAbstractFramePtr() const
break;
case INTERP:
return true;
case WASM:
return data_.wasmFrames_.debugEnabled();
}
MOZ_CRASH("Unexpected state");
}
@ -967,7 +972,6 @@ FrameIter::abstractFramePtr() const
MOZ_ASSERT(hasUsableAbstractFramePtr());
switch (data_.state_) {
case DONE:
case WASM:
break;
case JIT: {
if (data_.jitFrames_.isBaselineJS())
@ -981,6 +985,9 @@ FrameIter::abstractFramePtr() const
case INTERP:
MOZ_ASSERT(interpFrame());
return AbstractFramePtr(interpFrame());
case WASM:
MOZ_ASSERT(data_.wasmFrames_.debugEnabled());
return data_.wasmFrames_.debugFrame();
}
MOZ_CRASH("Unexpected state");
}
@ -990,6 +997,7 @@ FrameIter::updatePcQuadratic()
{
switch (data_.state_) {
case DONE:
case WASM:
break;
case INTERP: {
InterpreterFrame* frame = interpFrame();
@ -1027,10 +1035,6 @@ FrameIter::updatePcQuadratic()
return;
}
break;
case WASM:
// Update the pc.
data_.pc_ = (jsbytecode*)data_.wasmFrames_.pc();
break;
}
MOZ_CRASH("Unexpected state");
}

View file

@ -61,6 +61,7 @@ namespace jit {
class CommonFrameLayout;
}
namespace wasm {
class DebugFrame;
class Instance;
}
@ -134,7 +135,8 @@ class AbstractFramePtr
Tag_InterpreterFrame = 0x1,
Tag_BaselineFrame = 0x2,
Tag_RematerializedFrame = 0x3,
TagMask = 0x3
Tag_WasmDebugFrame = 0x4,
TagMask = 0x7
};
public:
@ -160,6 +162,12 @@ class AbstractFramePtr
MOZ_ASSERT_IF(fp, asRematerializedFrame() == fp);
}
MOZ_IMPLICIT AbstractFramePtr(wasm::DebugFrame* fp)
: ptr_(fp ? uintptr_t(fp) | Tag_WasmDebugFrame : 0)
{
MOZ_ASSERT_IF(fp, asWasmDebugFrame() == fp);
}
static AbstractFramePtr FromRaw(void* raw) {
AbstractFramePtr frame;
frame.ptr_ = uintptr_t(raw);
@ -196,6 +204,15 @@ class AbstractFramePtr
MOZ_ASSERT(res);
return res;
}
bool isWasmDebugFrame() const {
return (ptr_ & TagMask) == Tag_WasmDebugFrame;
}
wasm::DebugFrame* asWasmDebugFrame() const {
MOZ_ASSERT(isWasmDebugFrame());
wasm::DebugFrame* res = (wasm::DebugFrame*)(ptr_ & ~TagMask);
MOZ_ASSERT(res);
return res;
}
void* raw() const { return reinterpret_cast<void*>(ptr_); }
@ -223,7 +240,10 @@ class AbstractFramePtr
inline bool hasCachedSavedFrame() const;
inline void setHasCachedSavedFrame();
inline bool hasScript() const;
inline JSScript* script() const;
inline wasm::Instance* wasmInstance() const;
inline GlobalObject* global() const;
inline JSFunction* callee() const;
inline Value calleev() const;
inline Value& thisArgument() const;
@ -268,6 +288,7 @@ class AbstractFramePtr
friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, InterpreterFrame*);
friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::BaselineFrame*);
friend void GDBTestInitAbstractFramePtr(AbstractFramePtr&, jit::RematerializedFrame*);
friend void GDBTestInitAbstractFramePtr(AbstractFramePtr& frame, wasm::DebugFrame* ptr);
};
class NullFramePtr : public AbstractFramePtr
@ -1808,6 +1829,11 @@ class FrameIter
bool hasScript() const { return !isWasm(); }
// -----------------------------------------------------------
// The following functions can only be called when isWasm()
// -----------------------------------------------------------
inline wasm::Instance* wasmInstance() const;
// -----------------------------------------------------------
// The following functions can only be called when hasScript()
// -----------------------------------------------------------
@ -1869,7 +1895,7 @@ class FrameIter
// -----------------------------------------------------------
// The following functions can only be called when isInterp(),
// isBaseline(), or isIon(). Further, abstractFramePtr() can
// isBaseline(), isWasm() or isIon(). Further, abstractFramePtr() can
// only be called when hasUsableAbstractFramePtr().
// -----------------------------------------------------------
@ -2054,6 +2080,14 @@ FrameIter::script() const
return data_.jitFrames_.script();
}
inline wasm::Instance*
FrameIter::wasmInstance() const
{
MOZ_ASSERT(!done());
MOZ_ASSERT(data_.state_ == WASM);
return data_.wasmFrames_.instance();
}
inline bool
FrameIter::isIon() const
{

View file

@ -118,6 +118,7 @@
#endif
#include "wasm/WasmBinaryIterator.h"
#include "wasm/WasmDebugFrame.h"
#include "wasm/WasmGenerator.h"
#include "wasm/WasmSignalHandlers.h"
#include "wasm/WasmValidate.h"
@ -507,6 +508,7 @@ class BaseCompiler
int32_t varHigh_; // High byte offset + 1 of local area for true locals
int32_t maxFramePushed_; // Max value of masm.framePushed() observed
bool deadCode_; // Flag indicating we should decode & discard the opcode
bool debugEnabled_;
ValTypeVector SigI64I64_;
ValTypeVector SigDD_;
ValTypeVector SigD_;
@ -570,7 +572,10 @@ class BaseCompiler
Decoder& decoder,
const FuncBytes& func,
const ValTypeVector& locals,
FuncCompileResults& compileResults);
bool debugEnabled,
TempAllocator* alloc,
MacroAssembler* masm);
MOZ_MUST_USE bool init();
@ -2059,6 +2064,11 @@ class BaseCompiler
//
// Labels
void insertBreakablePoint(CallSiteDesc::Kind kind) {
const uint32_t offset = iter_.currentOffset();
masm.nopPatchableToCall(CallSiteDesc(offset, kind));
}
//////////////////////////////////////////////////////////////////////
//
// Function prologue and epilogue.
@ -2115,6 +2125,14 @@ class BaseCompiler
// The TLS pointer is always passed as a hidden argument in WasmTlsReg.
// Save it into its assigned local slot.
storeToFramePtr(WasmTlsReg, localInfo_[tlsSlot_].offs());
if (debugEnabled_) {
// Initialize funcIndex and flag fields of DebugFrame.
size_t debugFrame = masm.framePushed() - DebugFrame::offsetOfFrame();
masm.store32(Imm32(func_.index()),
Address(masm.getStackPointer(), debugFrame + DebugFrame::offsetOfFuncIndex()));
masm.storePtr(ImmWord(0),
Address(masm.getStackPointer(), debugFrame + DebugFrame::offsetOfFlagsWord()));
}
// Initialize the stack locals to zero.
//
@ -2136,8 +2154,59 @@ class BaseCompiler
for (int32_t i = varLow_ ; i < varHigh_ ; i += 4)
storeToFrameI32(scratch, i + 4);
}
if (debugEnabled_)
insertBreakablePoint(CallSiteDesc::EnterFrame);
}
void saveResult() {
MOZ_ASSERT(debugEnabled_);
size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
Address resultsAddress(StackPointer, debugFrameOffset + DebugFrame::offsetOfResults());
switch (func_.sig().ret()) {
case ExprType::Void:
break;
case ExprType::I32:
masm.store32(RegI32(ReturnReg), resultsAddress);
break;
case ExprType::I64:
masm.store64(RegI64(ReturnReg64), resultsAddress);
break;
case ExprType::F64:
masm.storeDouble(RegF64(ReturnDoubleReg), resultsAddress);
break;
case ExprType::F32:
masm.storeFloat32(RegF32(ReturnFloat32Reg), resultsAddress);
break;
default:
MOZ_CRASH("Function return type");
}
}
void restoreResult() {
MOZ_ASSERT(debugEnabled_);
size_t debugFrameOffset = masm.framePushed() - DebugFrame::offsetOfFrame();
Address resultsAddress(StackPointer, debugFrameOffset + DebugFrame::offsetOfResults());
switch (func_.sig().ret()) {
case ExprType::Void:
break;
case ExprType::I32:
masm.load32(resultsAddress, RegI32(ReturnReg));
break;
case ExprType::I64:
masm.load64(resultsAddress, RegI64(ReturnReg64));
break;
case ExprType::F64:
masm.loadDouble(resultsAddress, RegF64(ReturnDoubleReg));
break;
case ExprType::F32:
masm.loadFloat32(resultsAddress, RegF32(ReturnFloat32Reg));
break;
default:
MOZ_CRASH("Function return type");
}
}
bool endFunction() {
// Out-of-line prologue. Assumes that the in-line prologue has
// been executed and that a frame of size = localSize_ + sizeof(Frame)
@ -2166,6 +2235,14 @@ class BaseCompiler
masm.bind(&returnLabel_);
if (debugEnabled_) {
// Store and reload the return value from DebugFrame::return so that
// it can be clobbered, and/or modified by the debug trap.
saveResult();
insertBreakablePoint(CallSiteDesc::LeaveFrame);
restoreResult();
}
// Restore the TLS register in case it was overwritten by the function.
loadFromFramePtr(WasmTlsReg, frameOffsetFromSlot(tlsSlot_, MIRType::Pointer));
@ -8186,6 +8263,7 @@ BaseCompiler::BaseCompiler(const ModuleEnvironment& env,
Decoder& decoder,
const FuncBytes& func,
const ValTypeVector& locals,
bool debugEnabled,
TempAllocator* alloc,
MacroAssembler* masm)
: env_(env),
@ -8199,6 +8277,7 @@ BaseCompiler::BaseCompiler(const ModuleEnvironment& env,
varHigh_(0),
maxFramePushed_(0),
deadCode_(false),
debugEnabled_(debugEnabled),
prologueTrapOffset_(trapOffset()),
compileResults_(compileResults),
masm(compileResults_.masm()),
@ -8276,6 +8355,18 @@ BaseCompiler::init()
localSize_ = 0;
// Reserve a stack slot for the TLS pointer outside the varLow..varHigh
// range so it isn't zero-filled like the normal locals.
localInfo_[tlsSlot_].init(MIRType::Pointer, pushLocal(sizeof(void*)));
if (debugEnabled_) {
// If debug information is generated, constructing DebugFrame record:
// reserving some data before TLS pointer. The TLS pointer allocated
// above and regular wasm::Frame data starts after locals.
localSize_ += DebugFrame::offsetOfTlsData();
MOZ_ASSERT(DebugFrame::offsetOfFrame() == localSize_);
}
for (ABIArgIter<const ValTypeVector> i(args); !i.done(); i++) {
Local& l = localInfo_[i.index()];
switch (i.mirType()) {
@ -8308,10 +8399,6 @@ BaseCompiler::init()
}
}
// Reserve a stack slot for the TLS pointer outside the varLow..varHigh
// range so it isn't zero-filled like the normal locals.
localInfo_[tlsSlot_].init(MIRType::Pointer, pushLocal(sizeof(void*)));
varLow_ = localSize_;
for (size_t i = args.length(); i < locals_.length(); i++) {
@ -8425,7 +8512,7 @@ js::wasm::BaselineCompileFunction(CompileTask* task, FuncCompileUnit* unit, Uniq
// One-pass baseline compilation.
BaseCompiler f(task->env(), d, func, locals, &task->alloc(), &task->masm());
BaseCompiler f(task->env(), d, func, locals, task->debugEnabled(), &task->alloc(), &task->masm());
if (!f.init())
return false;

View file

@ -364,7 +364,8 @@ CodeRange::CodeRange(Kind kind, Offsets offsets)
kind_(kind)
{
MOZ_ASSERT(begin_ <= end_);
MOZ_ASSERT(kind_ == Entry || kind_ == Inline || kind_ == FarJumpIsland);
MOZ_ASSERT(kind_ == Entry || kind_ == Inline ||
kind_ == FarJumpIsland || kind_ == DebugTrap);
}
CodeRange::CodeRange(Kind kind, ProfilingOffsets offsets)
@ -476,6 +477,7 @@ Metadata::serializedSize() const
uint8_t*
Metadata::serialize(uint8_t* cursor) const
{
MOZ_ASSERT(!debugEnabled && debugTrapFarJumpOffsets.empty());
cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
cursor = SerializeVector(cursor, funcImports);
cursor = SerializeVector(cursor, funcExports);
@ -512,6 +514,8 @@ Metadata::deserialize(const uint8_t* cursor)
(cursor = DeserializePodVector(cursor, &funcNames)) &&
(cursor = DeserializePodVector(cursor, &customSections)) &&
(cursor = filename.deserialize(cursor));
debugEnabled = false;
debugTrapFarJumpOffsets.clear();
return cursor;
}
@ -608,8 +612,11 @@ Code::Code(UniqueCodeSegment segment,
: segment_(Move(segment)),
metadata_(&metadata),
maybeBytecode_(maybeBytecode),
enterAndLeaveFrameTrapsCounter_(0),
profilingEnabled_(false)
{}
{
MOZ_ASSERT_IF(metadata_->debugEnabled, maybeBytecode);
}
struct CallSiteRetAddrOffset
{
@ -821,6 +828,52 @@ Code::ensureProfilingState(JSContext* cx, bool newProfilingEnabled)
return true;
}
void
Code::toggleDebugTrap(uint32_t offset, bool enabled)
{
MOZ_ASSERT(offset);
uint8_t* trap = segment_->base() + offset;
const Uint32Vector& farJumpOffsets = metadata_->debugTrapFarJumpOffsets;
if (enabled) {
MOZ_ASSERT(farJumpOffsets.length() > 0);
size_t i = 0;
while (i < farJumpOffsets.length() && offset < farJumpOffsets[i])
i++;
if (i >= farJumpOffsets.length() ||
(i > 0 && offset - farJumpOffsets[i - 1] < farJumpOffsets[i] - offset))
i--;
uint8_t* farJump = segment_->base() + farJumpOffsets[i];
MacroAssembler::patchNopToCall(trap, farJump);
} else {
MacroAssembler::patchCallToNop(trap);
}
}
void
Code::adjustEnterAndLeaveFrameTrapsState(JSContext* cx, bool enabled)
{
MOZ_ASSERT(metadata_->debugEnabled);
MOZ_ASSERT_IF(!enabled, enterAndLeaveFrameTrapsCounter_ > 0);
bool wasEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
if (enabled)
++enterAndLeaveFrameTrapsCounter_;
else
--enterAndLeaveFrameTrapsCounter_;
bool stillEnabled = enterAndLeaveFrameTrapsCounter_ > 0;
if (wasEnabled == stillEnabled)
return;
AutoWritableJitCode awjc(cx->runtime(), segment_->base(), segment_->codeLength());
AutoFlushICache afc("Code::adjustEnterAndLeaveFrameTrapsState");
AutoFlushICache::setRange(uintptr_t(segment_->base()), segment_->codeLength());
for (const CallSite& callSite : metadata_->callSites) {
if (callSite.kind() != CallSite::EnterFrame && callSite.kind() != CallSite::LeaveFrame)
continue;
toggleDebugTrap(callSite.returnAddressOffset(), stillEnabled);
}
}
void
Code::addSizeOfMisc(MallocSizeOf mallocSizeOf,
Metadata::SeenSet* seenMetadata,

View file

@ -19,16 +19,20 @@
#define wasm_code_h
#include "wasm/WasmGeneratedSourceMap.h"
#include "js/HashTable.h"
#include "wasm/WasmTypes.h"
namespace js {
struct AsmJSMetadata;
class WasmActivation;
namespace wasm {
struct LinkData;
struct Metadata;
class FrameIterator;
// A wasm CodeSegment owns the allocated executable code for a wasm module.
// This allocation also currently includes the global data segment, which allows
@ -240,6 +244,8 @@ class CodeRange
ImportJitExit, // fast-path calling from wasm into JIT code
ImportInterpExit, // slow-path calling from wasm into C++ interp
TrapExit, // calls C++ to report and jumps to throw stub
DebugTrap, // calls C++ to handle debug event such as
// enter/leave frame or breakpoint
FarJumpIsland, // inserted to connect otherwise out-of-range insns
Inline // stub that is jumped-to, not called, and thus
// replaces/loses preceding innermost frame
@ -468,6 +474,10 @@ struct Metadata : ShareableBase<Metadata>, MetadataCacheablePod
CustomSectionVector customSections;
CacheableChars filename;
// Debug-enabled code is not serialized.
bool debugEnabled;
Uint32Vector debugTrapFarJumpOffsets;
bool usesMemory() const { return UsesMemory(memoryUsage); }
bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; }
@ -515,8 +525,11 @@ class Code
const SharedBytes maybeBytecode_;
UniqueGeneratedSourceMap maybeSourceMap_;
CacheableCharsVector funcLabels_;
uint32_t enterAndLeaveFrameTrapsCounter_;
bool profilingEnabled_;
void toggleDebugTrap(uint32_t offset, bool enabled);
public:
Code(UniqueCodeSegment segment,
const Metadata& metadata,
@ -555,6 +568,12 @@ class Code
bool profilingEnabled() const { return profilingEnabled_; }
const char* profilingLabel(uint32_t funcIndex) const { return funcLabels_[funcIndex].get(); }
// The Code can track enter/leave frame events. Any such event triggers
// debug trap. The enter frame events enabled across all functions, but
// the leave frame events only for particular function.
void adjustEnterAndLeaveFrameTrapsState(JSContext* cx, bool enabled);
// about:memory reporting:
void addSizeOfMisc(MallocSizeOf mallocSizeOf,

View file

@ -95,6 +95,13 @@ bool
CompileArgs::initFromContext(ExclusiveContext* cx, ScriptedCaller&& scriptedCaller)
{
alwaysBaseline = cx->options().wasmAlwaysBaseline();
// Debug information such as source view or debug traps will require
// additional memory and permanently stay in baseline code, so we try to
// only enable it when a developer actually cares: when the debugger tab
// is open.
debugEnabled = cx->compartment()->debuggerObservesAsmJS();
this->scriptedCaller = Move(scriptedCaller);
return assumptions.initBuildIdFromContext(cx);
}

View file

@ -39,11 +39,13 @@ struct CompileArgs
Assumptions assumptions;
ScriptedCaller scriptedCaller;
bool alwaysBaseline;
bool debugEnabled;
CompileArgs(Assumptions&& assumptions, ScriptedCaller&& scriptedCaller)
: assumptions(Move(assumptions)),
scriptedCaller(Move(scriptedCaller)),
alwaysBaseline(false)
alwaysBaseline(false),
debugEnabled(false)
{}
// If CompileArgs is constructed without arguments, initFromContext() must

View file

@ -0,0 +1,65 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
*
* Copyright 2016 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wasm/WasmDebugFrame.h"
#include "vm/EnvironmentObject.h"
#include "wasm/WasmInstance.h"
#include "jsobjinlines.h"
using namespace js;
using namespace js::wasm;
Instance*
DebugFrame::instance() const
{
return tlsData_->instance;
}
GlobalObject*
DebugFrame::global() const
{
return &instance()->object()->global();
}
JSObject*
DebugFrame::environmentChain() const
{
return &global()->lexicalEnvironment();
}
void
DebugFrame::observeFrame(JSContext* cx)
{
if (observing_)
return;
instance()->code().adjustEnterAndLeaveFrameTrapsState(cx, /* enabled = */ true);
observing_ = true;
}
void
DebugFrame::leaveFrame(JSContext* cx)
{
if (!observing_)
return;
instance()->code().adjustEnterAndLeaveFrameTrapsState(cx, /* enabled = */ false);
observing_ = false;
}

View file

@ -0,0 +1,110 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
*
* Copyright 2016 Mozilla Foundation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef wasmdebugframe_js_h
#define wasmdebugframe_js_h
#include "gc/Barrier.h"
#include "js/RootingAPI.h"
#include "js/TracingAPI.h"
#include "wasm/WasmTypes.h"
namespace js {
class WasmFunctionCallObject;
namespace wasm {
class DebugFrame
{
union
{
int32_t resultI32_;
int64_t resultI64_;
float resultF32_;
double resultF64_;
};
// The fields below are initialized by the baseline compiler.
uint32_t funcIndex_;
uint32_t reserved0_;
union
{
struct
{
bool observing_ : 1;
bool isDebuggee_ : 1;
bool prevUpToDate_ : 1;
bool hasCachedSavedFrame_ : 1;
};
void* reserved1_;
};
TlsData* tlsData_;
Frame frame_;
explicit DebugFrame() {}
public:
inline uint32_t funcIndex() const { return funcIndex_; }
inline TlsData* tlsData() const { return tlsData_; }
inline Frame& frame() { return frame_; }
Instance* instance() const;
GlobalObject* global() const;
JSObject* environmentChain() const;
void observeFrame(JSContext* cx);
void leaveFrame(JSContext* cx);
void trace(JSTracer* trc);
// These are opaque boolean flags used by the debugger and
// saved-frame-chains code.
inline bool isDebuggee() const { return isDebuggee_; }
inline void setIsDebuggee() { isDebuggee_ = true; }
inline void unsetIsDebuggee() { isDebuggee_ = false; }
inline bool prevUpToDate() const { return prevUpToDate_; }
inline void setPrevUpToDate() { prevUpToDate_ = true; }
inline void unsetPrevUpToDate() { prevUpToDate_ = false; }
inline bool hasCachedSavedFrame() const { return hasCachedSavedFrame_; }
inline void setHasCachedSavedFrame() { hasCachedSavedFrame_ = true; }
inline void* resultsPtr() { return &resultI32_; }
static constexpr size_t offsetOfResults() { return offsetof(DebugFrame, resultI32_); }
static constexpr size_t offsetOfFlagsWord() { return offsetof(DebugFrame, reserved1_); }
static constexpr size_t offsetOfFuncIndex() { return offsetof(DebugFrame, funcIndex_); }
static constexpr size_t offsetOfTlsData() { return offsetof(DebugFrame, tlsData_); }
static constexpr size_t offsetOfFrame() { return offsetof(DebugFrame, frame_); }
};
static_assert(DebugFrame::offsetOfResults() == 0, "results shall be at offset 0");
static_assert(DebugFrame::offsetOfTlsData() + sizeof(TlsData*) == DebugFrame::offsetOfFrame(),
"TLS pointer must be a field just before the wasm frame");
static_assert(sizeof(DebugFrame) % 8 == 0 && DebugFrame::offsetOfFrame() % 8 == 0,
"DebugFrame and its portion is 8-bytes aligned for AbstractFramePtr");
} // namespace wasm
} // namespace js
#endif // wasmdebugframe_js_h

View file

@ -17,6 +17,7 @@
#include "wasm/WasmFrameIterator.h"
#include "wasm/WasmDebugFrame.h"
#include "wasm/WasmInstance.h"
#include "jit/MacroAssembler-inl.h"
@ -43,6 +44,13 @@ CallerFPFromFP(void* fp)
return reinterpret_cast<Frame*>(fp)->callerFP;
}
static TlsData*
TlsDataFromFP(void *fp)
{
void* debugFrame = (uint8_t*)fp - DebugFrame::offsetOfFrame();
return reinterpret_cast<DebugFrame*>(debugFrame)->tlsData();
}
FrameIterator::FrameIterator()
: activation_(nullptr),
code_(nullptr),
@ -142,6 +150,7 @@ FrameIterator::settle()
case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit:
case CodeRange::TrapExit:
case CodeRange::DebugTrap:
case CodeRange::Inline:
case CodeRange::FarJumpIsland:
MOZ_CRASH("Should not encounter an exit during iteration");
@ -207,6 +216,38 @@ FrameIterator::lineOrBytecode() const
: (codeRange_ ? codeRange_->funcLineOrBytecode() : 0);
}
Instance*
FrameIterator::instance() const
{
MOZ_ASSERT(!done() && debugEnabled());
return TlsDataFromFP(fp_ + callsite_->stackDepth())->instance;
}
bool
FrameIterator::debugEnabled() const
{
MOZ_ASSERT(!done() && code_);
MOZ_ASSERT_IF(!missingFrameMessage_, codeRange_->kind() == CodeRange::Function);
return code_->metadata().debugEnabled;
}
DebugFrame*
FrameIterator::debugFrame() const
{
MOZ_ASSERT(!done() && debugEnabled());
// The fp() points to wasm::Frame.
void* buf = static_cast<uint8_t*>(fp_ + callsite_->stackDepth()) - DebugFrame::offsetOfFrame();
return static_cast<DebugFrame*>(buf);
}
const CallSite*
FrameIterator::debugTrapCallsite() const
{
MOZ_ASSERT(!done() && debugEnabled());
MOZ_ASSERT(callsite_->kind() == CallSite::EnterFrame || callsite_->kind() == CallSite::LeaveFrame);
return callsite_;
}
/*****************************************************************************/
// Prologue/epilogue code generation
@ -566,6 +607,7 @@ ProfilingFrameIterator::initFromFP()
case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit:
case CodeRange::TrapExit:
case CodeRange::DebugTrap:
case CodeRange::Inline:
case CodeRange::FarJumpIsland:
MOZ_CRASH("Unexpected CodeRange kind");
@ -698,6 +740,7 @@ ProfilingFrameIterator::ProfilingFrameIterator(const WasmActivation& activation,
callerFP_ = nullptr;
break;
}
case CodeRange::DebugTrap:
case CodeRange::Inline: {
// The throw stub clears WasmActivation::fp on it's way out.
if (!fp) {
@ -754,6 +797,7 @@ ProfilingFrameIterator::operator++()
case CodeRange::ImportJitExit:
case CodeRange::ImportInterpExit:
case CodeRange::TrapExit:
case CodeRange::DebugTrap:
case CodeRange::Inline:
case CodeRange::FarJumpIsland:
stackAddress_ = callerFP_;
@ -780,6 +824,7 @@ ProfilingFrameIterator::label() const
const char* importInterpDescription = "slow FFI trampoline (in asm.js)";
const char* nativeDescription = "native call (in asm.js)";
const char* trapDescription = "trap handling (in asm.js)";
const char* debugTrapDescription = "debug trap handling (in asm.js)";
switch (exitReason_) {
case ExitReason::None:
@ -792,6 +837,8 @@ ProfilingFrameIterator::label() const
return nativeDescription;
case ExitReason::Trap:
return trapDescription;
case ExitReason::DebugTrap:
return debugTrapDescription;
}
switch (codeRange_->kind()) {
@ -800,6 +847,7 @@ ProfilingFrameIterator::label() const
case CodeRange::ImportJitExit: return importJitDescription;
case CodeRange::ImportInterpExit: return importInterpDescription;
case CodeRange::TrapExit: return trapDescription;
case CodeRange::DebugTrap: return debugTrapDescription;
case CodeRange::Inline: return "inline stub (in asm.js)";
case CodeRange::FarJumpIsland: return "interstitial (in asm.js)";
}

View file

@ -32,6 +32,8 @@ namespace wasm {
class CallSite;
class Code;
class CodeRange;
class DebugFrame;
class Instance;
class SigIdDesc;
struct CallThunk;
struct FuncOffsets;
@ -68,8 +70,11 @@ class FrameIterator
bool mutedErrors() const;
JSAtom* functionDisplayAtom() const;
unsigned lineOrBytecode() const;
inline void* fp() const { return fp_; }
inline uint8_t* pc() const { return pc_; }
const CodeRange* codeRange() const { return codeRange_; }
Instance* instance() const;
bool debugEnabled() const;
DebugFrame* debugFrame() const;
const CallSite* debugTrapCallsite() const;
};
// An ExitReason describes the possible reasons for leaving compiled wasm code
@ -80,7 +85,8 @@ enum class ExitReason : uint32_t
ImportJit, // fast-path call directly into JIT code
ImportInterp, // slow-path call into C++ Invoke()
Native, // call to native C++ code (e.g., Math.sin, ToInt32(), interrupt)
Trap // call to trap handler for the trap in WasmActivation::trap
Trap, // call to trap handler for the trap in WasmActivation::trap
DebugTrap // call to debug trap handler
};
// Iterates over the frames of a single WasmActivation, given an

View file

@ -46,6 +46,7 @@ static const uint32_t BAD_CODE_RANGE = UINT32_MAX;
ModuleGenerator::ModuleGenerator(UniqueChars* error)
: alwaysBaseline_(false),
debugEnabled_(false),
error_(error),
numSigs_(0),
numTables_(0),
@ -198,6 +199,7 @@ ModuleGenerator::init(UniqueModuleEnvironment env, const CompileArgs& args,
linkData_.globalDataLength = AlignBytes(InitialGlobalDataBytes, sizeof(void*));
alwaysBaseline_ = args.alwaysBaseline;
debugEnabled_ = args.debugEnabled;
if (!funcToCodeRange_.appendN(BAD_CODE_RANGE, env_->funcSigs.length()))
return false;
@ -372,6 +374,28 @@ ModuleGenerator::patchCallSites(TrapExitOffsetArray* maybeTrapExits)
masm_.patchCall(callerOffset, *existingTrapFarJumps[cs.trap()]);
break;
}
case CallSiteDesc::EnterFrame:
case CallSiteDesc::LeaveFrame: {
Uint32Vector& jumps = metadata_->debugTrapFarJumpOffsets;
if (jumps.empty() ||
uint32_t(abs(int32_t(jumps.back()) - int32_t(callerOffset))) >= JumpRange())
{
Offsets offsets;
offsets.begin = masm_.currentOffset();
uint32_t jumpOffset = masm_.farJumpWithPatch().offset();
offsets.end = masm_.currentOffset();
if (masm_.oom())
return false;
if (!metadata_->codeRanges.emplaceBack(CodeRange::FarJumpIsland, offsets))
return false;
if (!debugTrapFarJumps_.emplaceBack(jumpOffset))
return false;
if (!jumps.emplaceBack(offsets.begin))
return false;
}
break;
}
}
}
@ -380,7 +404,7 @@ ModuleGenerator::patchCallSites(TrapExitOffsetArray* maybeTrapExits)
}
bool
ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits)
ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits, const Offsets& debugTrapStub)
{
MacroAssembler::AutoPrepareForPatching patching(masm_);
@ -394,6 +418,9 @@ ModuleGenerator::patchFarJumps(const TrapExitOffsetArray& trapExits)
for (const TrapFarJump& farJump : masm_.trapFarJumps())
masm_.patchFarJump(farJump.jump, trapExits[farJump.trap].begin);
for (uint32_t debugTrapFarJump : debugTrapFarJumps_)
masm_.patchFarJump(CodeOffset(debugTrapFarJump), debugTrapStub.begin);
return true;
}
@ -504,6 +531,7 @@ ModuleGenerator::finishCodegen()
Offsets unalignedAccessExit;
Offsets interruptExit;
Offsets throwStub;
Offsets debugTrapStub;
{
TempAllocator alloc(&lifo_);
@ -531,6 +559,7 @@ ModuleGenerator::finishCodegen()
unalignedAccessExit = GenerateUnalignedExit(masm, &throwLabel);
interruptExit = GenerateInterruptExit(masm, &throwLabel);
throwStub = GenerateThrowStub(masm, &throwLabel);
debugTrapStub = GenerateDebugTrapStub(masm, &throwLabel);
if (masm.oom() || !masm_.asmMergeWith(masm))
return false;
@ -580,6 +609,10 @@ ModuleGenerator::finishCodegen()
if (!metadata_->codeRanges.emplaceBack(CodeRange::Inline, throwStub))
return false;
debugTrapStub.offsetBy(offsetInWhole);
if (!metadata_->codeRanges.emplaceBack(CodeRange::DebugTrap, debugTrapStub))
return false;
// Fill in LinkData with the offsets of these stubs.
linkData_.outOfBoundsOffset = outOfBoundsExit.begin;
@ -592,7 +625,7 @@ ModuleGenerator::finishCodegen()
if (!patchCallSites(&trapExits))
return false;
if (!patchFarJumps(trapExits))
if (!patchFarJumps(trapExits, debugTrapStub))
return false;
// Code-generation is complete!
@ -890,6 +923,8 @@ ModuleGenerator::launchBatchCompile()
{
MOZ_ASSERT(currentTask_);
currentTask_->setDebugEnabled(debugEnabled_);
size_t numBatchedFuncs = currentTask_->units().length();
MOZ_ASSERT(numBatchedFuncs);
@ -924,9 +959,15 @@ ModuleGenerator::finishFuncDef(uint32_t funcIndex, FunctionGenerator* fg)
if (!func)
return false;
auto mode = alwaysBaseline_ && BaselineCanCompile(fg)
? IonCompileTask::CompileMode::Baseline
: IonCompileTask::CompileMode::Ion;
CompileMode mode;
if ((alwaysBaseline_ || debugEnabled_) && BaselineCanCompile(fg)) {
mode = CompileMode::Baseline;
} else {
mode = CompileMode::Ion;
// Ion does not support debugging -- reset debugEnabled_ flags to avoid
// turning debugging for wasm::Code.
debugEnabled_ = false;
}
fg->task_->init(Move(func), mode);
@ -1119,12 +1160,15 @@ ModuleGenerator::finish(const ShareableBytes& bytecode)
metadata_->codeRanges.podResizeToFit();
metadata_->callSites.podResizeToFit();
metadata_->callThunks.podResizeToFit();
metadata_->debugTrapFarJumpOffsets.podResizeToFit();
// For asm.js, the tables vector is over-allocated (to avoid resize during
// parallel copilation). Shrink it back down to fit.
if (isAsmJS() && !metadata_->tables.resize(numTables_))
return nullptr;
metadata_->debugEnabled = debugEnabled_;
// Assert CodeRanges are sorted.
#ifdef DEBUG
uint32_t lastEnd = 0;
@ -1134,6 +1178,15 @@ ModuleGenerator::finish(const ShareableBytes& bytecode)
}
#endif
// Assert debugTrapFarJumpOffsets are sorted.
#ifdef DEBUG
uint32_t lastOffset = 0;
for (uint32_t debugTrapFarJumpOffset : metadata_->debugTrapFarJumpOffsets) {
MOZ_ASSERT(debugTrapFarJumpOffset >= lastOffset);
lastOffset = debugTrapFarJumpOffset;
}
#endif
if (!finishLinkData(code))
return nullptr;

View file

@ -144,6 +144,7 @@ class CompileTask
Maybe<jit::TempAllocator> alloc_;
Maybe<jit::MacroAssembler> masm_;
FuncCompileUnitVector units_;
bool debugEnabled_;
CompileTask(const CompileTask&) = delete;
CompileTask& operator=(const CompileTask&) = delete;
@ -151,6 +152,7 @@ class CompileTask
void init() {
alloc_.emplace(&lifo_);
masm_.emplace(jit::MacroAssembler::WasmToken(), *alloc_);
debugEnabled_ = false;
}
public:
@ -175,6 +177,12 @@ class CompileTask
FuncCompileUnitVector& units() {
return units_;
}
bool debugEnabled() const {
return debugEnabled_;
}
void setDebugEnabled(bool enabled) {
debugEnabled_ = enabled;
}
bool reset(UniqueFuncBytesVector* freeFuncBytes) {
for (FuncCompileUnit& unit : units_) {
if (!freeFuncBytes->emplaceBack(Move(unit.recycle())))
@ -205,6 +213,7 @@ class MOZ_STACK_CLASS ModuleGenerator
// Constant parameters
bool alwaysBaseline_;
bool debugEnabled_;
UniqueChars* error_;
// Data that is moved into the result of finish()
@ -224,6 +233,7 @@ class MOZ_STACK_CLASS ModuleGenerator
Uint32Set exportedFuncs_;
uint32_t lastPatchedCallsite_;
uint32_t startOfUnpatchedCallsites_;
Uint32Vector debugTrapFarJumps_;
// Parallel compilation
bool parallel_;
@ -243,7 +253,7 @@ public:
uint32_t numFuncImports() const;
private:
[[nodiscard]] bool patchCallSites(TrapExitOffsetArray* maybeTrapExits = nullptr);
[[nodiscard]] bool patchFarJumps(const TrapExitOffsetArray& trapExits);
[[nodiscard]] bool patchFarJumps(const TrapExitOffsetArray& trapExits, const Offsets& debugTrapStub);
[[nodiscard]] bool finishTask(CompileTask* task);
[[nodiscard]] bool finishOutstandingTask();
[[nodiscard]] bool finishFuncExports();

View file

@ -318,7 +318,8 @@ Instance::Instance(JSContext* cx,
object_(object),
code_(Move(code)),
memory_(memory),
tables_(Move(tables))
tables_(Move(tables)),
enterFrameTrapsEnabled_(false)
{
MOZ_ASSERT(funcImports.length() == metadata().funcImports.length());
MOZ_ASSERT(tables_.length() == metadata().tables.length());
@ -734,6 +735,16 @@ Instance::ensureProfilingState(JSContext* cx, bool newProfilingEnabled)
return true;
}
void
Instance::ensureEnterFrameTrapsState(JSContext* cx, bool enabled)
{
if (enterFrameTrapsEnabled_ == enabled)
return;
code_->adjustEnterAndLeaveFrameTrapsState(cx, enabled);
enterFrameTrapsEnabled_ = enabled;
}
void
Instance::addSizeOfMisc(MallocSizeOf mallocSizeOf,
Metadata::SeenSet* seenMetadata,

View file

@ -40,6 +40,7 @@ class Instance
GCPtrWasmMemoryObject memory_;
SharedTableVector tables_;
TlsData tlsData_;
bool enterFrameTrapsEnabled_;
// Internal helpers:
const void** addressOfSigId(const SigIdDesc& sigId) const;
@ -123,6 +124,11 @@ class Instance
MOZ_MUST_USE bool ensureProfilingState(JSContext* cx, bool enabled);
// Debug support:
bool debugEnabled() const { return code_->metadata().debugEnabled; }
bool enterFrameTrapsEnabled() const { return enterFrameTrapsEnabled_; }
void ensureEnterFrameTrapsState(JSContext* cx, bool enabled);
// about:memory reporting:
void addSizeOfMisc(MallocSizeOf mallocSizeOf,

View file

@ -904,6 +904,7 @@ WasmInstanceObject::isNewborn() const
WasmInstanceObject::finalize(FreeOp* fop, JSObject* obj)
{
fop->delete_(&obj->as<WasmInstanceObject>().exports());
fop->delete_(&obj->as<WasmInstanceObject>().scopes());
if (!obj->as<WasmInstanceObject>().isNewborn())
fop->delete_(&obj->as<WasmInstanceObject>().instance());
}
@ -930,12 +931,20 @@ WasmInstanceObject::create(JSContext* cx,
return nullptr;
}
UniquePtr<WeakScopeMap> scopes = js::MakeUnique<WeakScopeMap>(cx->zone(), ScopeMap());
if (!scopes || !scopes->init()) {
ReportOutOfMemory(cx);
return nullptr;
}
AutoSetNewObjectMetadata metadata(cx);
RootedWasmInstanceObject obj(cx, NewObjectWithGivenProto<WasmInstanceObject>(cx, proto));
if (!obj)
return nullptr;
obj->setReservedSlot(EXPORTS_SLOT, PrivateValue(exports.release()));
obj->setReservedSlot(SCOPES_SLOT, PrivateValue(scopes.release()));
MOZ_ASSERT(obj->isNewborn());
MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");
@ -1021,6 +1030,12 @@ WasmInstanceObject::exports() const
return *(WeakExportMap*)getReservedSlot(EXPORTS_SLOT).toPrivate();
}
WasmInstanceObject::WeakScopeMap&
WasmInstanceObject::scopes() const
{
return *(WeakScopeMap*)getReservedSlot(SCOPES_SLOT).toPrivate();
}
static bool
WasmCall(JSContext* cx, unsigned argc, Value* vp)
{
@ -1085,6 +1100,25 @@ WasmInstanceObject::getExportedFunctionCodeRange(HandleFunction fun)
return metadata.codeRanges[metadata.lookupFuncExport(funcIndex).codeRangeIndex()];
}
/* static */ WasmFunctionScope*
WasmInstanceObject::getFunctionScope(JSContext* cx, HandleWasmInstanceObject instanceObj,
uint32_t funcIndex)
{
if (ScopeMap::Ptr p = instanceObj->scopes().lookup(funcIndex))
return p->value();
Rooted<WasmFunctionScope*> funcScope(cx, WasmFunctionScope::create(cx, instanceObj, funcIndex));
if (!funcScope)
return nullptr;
if (!instanceObj->scopes().putNew(funcIndex, funcScope)) {
ReportOutOfMemory(cx);
return nullptr;
}
return funcScope;
}
bool
wasm::IsExportedFunction(JSFunction* fun)
{

View file

@ -25,6 +25,7 @@
namespace js {
class TypedArrayObject;
class WasmFunctionScope;
namespace wasm {
@ -147,13 +148,14 @@ class WasmInstanceObject : public NativeObject
{
static const unsigned INSTANCE_SLOT = 0;
static const unsigned EXPORTS_SLOT = 1;
static const unsigned SCOPES_SLOT = 2;
static const ClassOps classOps_;
bool isNewborn() const;
static void finalize(FreeOp* fop, JSObject* obj);
static void trace(JSTracer* trc, JSObject* obj);
// ExportMap maps from function definition index to exported function
// object. This map is weak to avoid holding objects alive; the point is
// ExportMap maps from function index to exported function object.
// This allows the instance to lazily create exported function
// just to ensure a unique object identity for any given function object.
using ExportMap = GCHashMap<uint32_t,
ReadBarrieredFunction,
@ -162,8 +164,18 @@ class WasmInstanceObject : public NativeObject
using WeakExportMap = JS::WeakCache<ExportMap>;
WeakExportMap& exports() const;
// WeakScopeMap maps from function index to js::Scope. This maps is weak
// to avoid holding scope objects alive. The scopes are normally created
// during debugging.
using ScopeMap = GCHashMap<uint32_t,
ReadBarriered<WasmFunctionScope*>,
DefaultHasher<uint32_t>,
SystemAllocPolicy>;
using WeakScopeMap = JS::WeakCache<ScopeMap>;
WeakScopeMap& scopes() const;
public:
static const unsigned RESERVED_SLOTS = 2;
static const unsigned RESERVED_SLOTS = 3;
static const Class class_;
static const JSPropertySpec properties[];
static const JSFunctionSpec methods[];
@ -185,6 +197,10 @@ class WasmInstanceObject : public NativeObject
MutableHandleFunction fun);
const wasm::CodeRange& getExportedFunctionCodeRange(HandleFunction fun);
static WasmFunctionScope* getFunctionScope(JSContext* cx,
HandleWasmInstanceObject instanceObj,
uint32_t funcIndex);
};
// The class of WebAssembly.Memory. A WasmMemoryObject references an ArrayBuffer

View file

@ -144,7 +144,13 @@ Module::serializedSize(size_t* maybeBytecodeSize, size_t* maybeCompiledSize) con
if (maybeBytecodeSize)
*maybeBytecodeSize = bytecode_->bytes.length();
if (maybeCompiledSize) {
// The compiled debug code must not be saved, set compiled size to 0,
// so Module::assumptionsMatch will return false during assumptions
// deserialization.
if (maybeCompiledSize && metadata_->debugEnabled)
*maybeCompiledSize = 0;
if (maybeCompiledSize && !metadata_->debugEnabled) {
*maybeCompiledSize = assumptions_.serializedSize() +
SerializedPodVectorSize(code_) +
linkData_.serializedSize() +
@ -175,7 +181,9 @@ Module::serialize(uint8_t* maybeBytecodeBegin, size_t maybeBytecodeSize,
MOZ_RELEASE_ASSERT(bytecodeEnd == maybeBytecodeBegin + maybeBytecodeSize);
}
if (maybeCompiledBegin) {
MOZ_ASSERT_IF(maybeCompiledBegin && metadata_->debugEnabled, maybeCompiledSize == 0);
if (maybeCompiledBegin && !metadata_->debugEnabled) {
// Assumption must be serialized at the beginning of the compiled bytes so
// that compiledAssumptionsMatch can detect a build-id mismatch before any
// other decoding occurs.

View file

@ -897,6 +897,10 @@ static const LiveRegisterSet AllRegsExceptSP(
GeneralRegisterSet(Registers::AllMask & ~(uint32_t(1) << Registers::StackPointer)),
FloatRegisterSet(FloatRegisters::AllMask));
static const LiveRegisterSet AllAllocatableRegs = LiveRegisterSet(
GeneralRegisterSet(Registers::AllocatableMask),
FloatRegisterSet(FloatRegisters::AllMask));
// The async interrupt-callback exit is called from arbitrarily-interrupted wasm
// code. That means we must first save *all* registers and restore *all*
// registers (except the stack pointer) when we resume. The address to resume to
@ -1109,6 +1113,11 @@ wasm::GenerateThrowStub(MacroAssembler& masm, Label* throwLabel)
Offsets offsets;
offsets.begin = masm.currentOffset();
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
if (ShadowStackSpace)
masm.subFromStackPtr(Imm32(ShadowStackSpace));
masm.call(SymbolicAddress::HandleDebugThrow);
// We are about to pop all frames in this WasmActivation. Set fp to null to
// maintain the invariant that fp is either null or pointing to a valid
// frame.
@ -1128,3 +1137,50 @@ wasm::GenerateThrowStub(MacroAssembler& masm, Label* throwLabel)
offsets.end = masm.currentOffset();
return offsets;
}
// Generate a stub that handle toggable enter/leave frame traps or breakpoints.
// The trap records frame pointer (via GenerateExitPrologue) and saves most of
// registers to not affect the code generated by WasmBaselineCompile.
Offsets
wasm::GenerateDebugTrapStub(MacroAssembler& masm, Label* throwLabel)
{
masm.haltingAlign(CodeAlignment);
masm.setFramePushed(0);
ProfilingOffsets offsets;
GenerateExitPrologue(masm, 0, ExitReason::DebugTrap, &offsets);
// Save all registers used between baseline compiler operations.
masm.PushRegsInMask(AllAllocatableRegs);
uint32_t framePushed = masm.framePushed();
// This method might be called with unaligned stack -- aligning and
// saving old stack pointer at the top.
Register scratch = ABINonArgReturnReg0;
masm.moveStackPtrTo(scratch);
masm.subFromStackPtr(Imm32(sizeof(intptr_t)));
masm.andToStackPtr(Imm32(~(ABIStackAlignment - 1)));
masm.storePtr(scratch, Address(masm.getStackPointer(), 0));
if (ShadowStackSpace)
masm.subFromStackPtr(Imm32(ShadowStackSpace));
masm.assertStackAlignment(ABIStackAlignment);
masm.call(SymbolicAddress::HandleDebugTrap);
masm.branchIfFalseBool(ReturnReg, throwLabel);
if (ShadowStackSpace)
masm.addToStackPtr(Imm32(ShadowStackSpace));
masm.Pop(scratch);
masm.moveToStackPtr(scratch);
masm.setFramePushed(framePushed);
masm.PopRegsInMask(AllAllocatableRegs);
GenerateExitEpilogue(masm, 0, ExitReason::DebugTrap, &offsets);
offsets.end = masm.currentOffset();
return offsets;
}

View file

@ -57,6 +57,9 @@ GenerateInterruptExit(jit::MacroAssembler& masm, jit::Label* throwLabel);
extern Offsets
GenerateThrowStub(jit::MacroAssembler& masm, jit::Label* throwLabel);
extern Offsets
GenerateDebugTrapStub(jit::MacroAssembler& masm, jit::Label* throwLabel);
} // namespace wasm
} // namespace js

View file

@ -32,6 +32,7 @@
#include "wasm/WasmSerialize.h"
#include "wasm/WasmSignalHandlers.h"
#include "vm/Debugger-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
@ -89,6 +90,75 @@ WasmHandleExecutionInterrupt()
return success;
}
static bool
WasmHandleDebugTrap()
{
WasmActivation* activation = JSContext::innermostWasmActivation();
JSContext* cx = activation->cx();
FrameIterator iter(*activation);
MOZ_ASSERT(iter.debugEnabled());
const CallSite* site = iter.debugTrapCallsite();
MOZ_ASSERT(site);
if (site->kind() == CallSite::EnterFrame) {
if (!iter.instance()->enterFrameTrapsEnabled())
return true;
DebugFrame* frame = iter.debugFrame();
frame->setIsDebuggee();
frame->observeFrame(cx);
// TODO call onEnterFrame
JSTrapStatus status = Debugger::onEnterFrame(cx, frame);
if (status == JSTRAP_RETURN) {
// Ignoring forced return (JSTRAP_RETURN) -- changing code execution
// order is not yet implemented in the wasm baseline.
// TODO properly handle JSTRAP_RETURN and resume wasm execution.
JS_ReportErrorASCII(cx, "Unexpected resumption value from onEnterFrame");
return false;
}
return status == JSTRAP_CONTINUE;
}
if (site->kind() == CallSite::LeaveFrame) {
DebugFrame* frame = iter.debugFrame();
bool ok = Debugger::onLeaveFrame(cx, frame, nullptr, true);
frame->leaveFrame(cx);
return ok;
}
// TODO baseline debug traps
MOZ_CRASH();
return true;
}
static void
WasmHandleDebugThrow()
{
WasmActivation* activation = JSContext::innermostWasmActivation();
JSContext* cx = activation->cx();
for (FrameIterator iter(*activation); !iter.done(); ++iter) {
if (!iter.debugEnabled())
continue;
DebugFrame* frame = iter.debugFrame();
JSTrapStatus status = Debugger::onExceptionUnwind(cx, frame);
if (status == JSTRAP_RETURN) {
// Unexpected trap return -- raising error since throw recovery
// is not yet implemented in the wasm baseline.
// TODO properly handle JSTRAP_RETURN and resume wasm execution.
JS_ReportErrorASCII(cx, "Unexpected resumption value from onExceptionUnwind");
}
bool ok = Debugger::onLeaveFrame(cx, frame, nullptr, false);
if (ok) {
// Unexpected success from the handler onLeaveFrame -- raising error
// since throw recovery is not yet implemented in the wasm baseline.
// TODO properly handle success and resume wasm execution.
JS_ReportErrorASCII(cx, "Unexpected success from onLeaveFrame");
}
frame->leaveFrame(cx);
}
}
static void
WasmReportTrap(int32_t trapIndex)
{
@ -265,6 +335,10 @@ wasm::AddressOf(SymbolicAddress imm, ExclusiveContext* cx)
return FuncCast(WasmReportOverRecursed, Args_General0);
case SymbolicAddress::HandleExecutionInterrupt:
return FuncCast(WasmHandleExecutionInterrupt, Args_General0);
case SymbolicAddress::HandleDebugTrap:
return FuncCast(WasmHandleDebugTrap, Args_General0);
case SymbolicAddress::HandleDebugThrow:
return FuncCast(WasmHandleDebugThrow, Args_General0);
case SymbolicAddress::ReportTrap:
return FuncCast(WasmReportTrap, Args_General1);
case SymbolicAddress::ReportOutOfBounds:

View file

@ -733,14 +733,16 @@ struct TrapOffset
class CallSiteDesc
{
uint32_t lineOrBytecode_ : 30;
uint32_t kind_ : 2;
uint32_t lineOrBytecode_ : 29;
uint32_t kind_ : 3;
public:
enum Kind {
Func, // pc-relative call to a specific function
Dynamic, // dynamic callee called via register
Symbolic, // call to a single symbolic callee
TrapExit // call to a trap exit
TrapExit, // call to a trap exit
EnterFrame, // call to a enter frame handler
LeaveFrame // call to a leave frame handler
};
CallSiteDesc() {}
explicit CallSiteDesc(Kind kind)
@ -857,6 +859,8 @@ enum class SymbolicAddress
InterruptUint32,
ReportOverRecursed,
HandleExecutionInterrupt,
HandleDebugTrap,
HandleDebugThrow,
ReportTrap,
ReportOutOfBounds,
ReportUnalignedAccess,
@ -1333,6 +1337,28 @@ struct MemoryPatch
WASM_DECLARE_POD_VECTOR(MemoryPatch, MemoryPatchVector)
// As an invariant across architectures, within wasm code:
// $sp % WasmStackAlignment = (sizeof(wasm::Frame) + masm.framePushed) % WasmStackAlignment
// Thus, wasm::Frame represents the bytes pushed after the call (which occurred
// with a WasmStackAlignment-aligned StackPointer) that are not included in
// masm.framePushed.
struct Frame
{
// The caller's saved frame pointer. In non-profiling mode, internal
// wasm-to-wasm calls don't update fp and thus don't save the caller's
// frame pointer; the space is reserved, however, so that profiling mode can
// reuse the same function body without recompiling.
uint8_t* callerFP;
// The return address pushed by the call (in the case of ARM/MIPS the return
// address is pushed by the first instruction of the prologue).
void* returnAddress;
};
static_assert(sizeof(Frame) == 2 * sizeof(void*), "?!");
static const uint32_t FrameBytesAfterReturnAddress = sizeof(void*);
} // namespace wasm
} // namespace js