diff --git a/js/src/doc/Debugger/Debugger.Frame.md b/js/src/doc/Debugger/Debugger.Frame.md index 68daaae6ce..813530901a 100644 --- a/js/src/doc/Debugger/Debugger.Frame.md +++ b/js/src/doc/Debugger/Debugger.Frame.md @@ -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. eval(code, [options]) : Evaluate code in the execution context of this frame, and return a [completion value][cv] describing how it completed. Code 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]. Code is interpreted as strict mode code when it contains a Use @@ -326,3 +332,5 @@ methods of other kinds of objects. The options 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`. diff --git a/js/src/frontend/EmitterScope.cpp b/js/src/frontend/EmitterScope.cpp index 1dbef14a1f..aab55568c8 100644 --- a/js/src/frontend/EmitterScope.cpp +++ b/js/src/frontend/EmitterScope.cpp @@ -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. diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 1376df5a4d..0171244436 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -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(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(scope->data_); + traverseEdge(scope, static_cast(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()) diff --git a/js/src/gc/Policy.h b/js/src/gc/Policy.h index 8a974dd0d4..98913065f9 100644 --- a/js/src/gc/Policy.h +++ b/js/src/gc/Policy.h @@ -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*) \ diff --git a/js/src/gdb/mozilla/Interpreter.py b/js/src/gdb/mozilla/Interpreter.py index e54210e790..413c26aeaa 100644 --- a/js/src/gdb/mozilla/Interpreter.py +++ b/js/src/gdb/mozilla/Interpreter.py @@ -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 diff --git a/js/src/gdb/tests/test-Interpreter.cpp b/js/src/gdb/tests/test-Interpreter.cpp index 8ba8fa8f4c..f0b067ffda 100644 --- a/js/src/gdb/tests/test-Interpreter.cpp +++ b/js/src/gdb/tests/test-Interpreter.cpp @@ -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; diff --git a/js/src/jit/MacroAssembler.h b/js/src/jit/MacroAssembler.h index 74d7f7cc5a..b14d91e64d 100644 --- a/js/src/jit/MacroAssembler.h +++ b/js/src/jit/MacroAssembler.h @@ -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. diff --git a/js/src/jit/arm/MacroAssembler-arm.cpp b/js/src/jit/arm/MacroAssembler-arm.cpp index e94632c207..a8a489af20 100644 --- a/js/src/jit/arm/MacroAssembler-arm.cpp +++ b/js/src/jit/arm/MacroAssembler-arm.cpp @@ -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(inst)->is() || + reinterpret_cast(inst)->is()); + + new (inst) InstBLImm(BOffImm(target - inst), Assembler::Always); +} + +void +MacroAssembler::patchCallToNop(uint8_t* call) +{ + uint8_t* inst = call - 4; + MOZ_ASSERT(reinterpret_cast(inst)->is() || + reinterpret_cast(inst)->is()); + new (inst) InstNOP(); +} + void MacroAssembler::pushReturnAddress() { diff --git a/js/src/jit/arm64/MacroAssembler-arm64.cpp b/js/src/jit/arm64/MacroAssembler-arm64.cpp index d69b82694b..4226916a46 100644 --- a/js/src/jit/arm64/MacroAssembler-arm64.cpp +++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp @@ -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() { diff --git a/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp index f52de202af..93a65b101c 100644 --- a/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp +++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp @@ -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() { diff --git a/js/src/jit/shared/Assembler-shared.h b/js/src/jit/shared/Assembler-shared.h index 61185b3835..9d550ee00d 100644 --- a/js/src/jit/shared/Assembler-shared.h +++ b/js/src/jit/shared/Assembler-shared.h @@ -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). diff --git a/js/src/jit/x86-shared/Assembler-x86-shared.h b/js/src/jit/x86-shared/Assembler-x86-shared.h index d7cba49aff..1947caddb2 100644 --- a/js/src/jit/x86-shared/Assembler-x86-shared.h +++ b/js/src/jit/x86-shared/Assembler-x86-shared.h @@ -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(); } diff --git a/js/src/jit/x86-shared/BaseAssembler-x86-shared.h b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h index 484039fd23..cfb4ba98ce 100644 --- a/js/src/jit/x86-shared/BaseAssembler-x86-shared.h +++ b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h @@ -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. diff --git a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp index 070113fcf8..cb52e65eef 100644 --- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp +++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp @@ -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. diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index ca51bb1b19..41fabb1724 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -823,6 +823,9 @@ js::XDRScript(XDRState* 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"); diff --git a/js/src/moz.build b/js/src/moz.build index b829d4f863..877b59e679 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -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', diff --git a/js/src/vm/CommonPropertyNames.h b/js/src/vm/CommonPropertyNames.h index 6edd49c6c0..0376bcca48 100644 --- a/js/src/vm/CommonPropertyNames.h +++ b/js/src/vm/CommonPropertyNames.h @@ -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") \ diff --git a/js/src/vm/Debugger-inl.h b/js/src/vm/Debugger-inl.h index 83b4f5c405..a216cceab1 100644 --- a/js/src/vm/Debugger-inl.h +++ b/js/src/vm/Debugger-inl.h @@ -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 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 wasmI } inline bool -js::Debugger::getScriptFrame(JSContext* cx, const ScriptFrameIter& iter, +js::Debugger::getScriptFrame(JSContext* cx, const FrameIter& iter, MutableHandle result) { return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, result); diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index d76efe65c0..e6de5ef1b5 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -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 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 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 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 /* 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(); - // 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 maybeIter; - if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter)) + Maybe 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 maybeIter; - if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter)) + Maybe maybeIter; + if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter)) return false; - ScriptFrameIter& iter = *maybeIter; + FrameIter& iter = *maybeIter; Rooted 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 maybeIter; - if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter)) + if (!requireScriptReferent(cx, frame)) return false; - ScriptFrameIter& iter = *maybeIter; + + Maybe 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 maybeIter; - if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter)) + Maybe 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 maybeIter; - if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter)) + Maybe 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 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::RangeisLive()); + if (!requireScriptReferent(cx, frame)) + return false; Debugger* dbg = frame->owner(); - Maybe maybeIter; - if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter)) + Maybe 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& result) +DebuggerFrame::getFrameIter(JSContext* cx, HandleDebuggerFrame frame, + Maybe& result) { AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate()); if (referent.isScriptFrameIterData()) { - result.emplace(*reinterpret_cast(referent.raw())); + result.emplace(*reinterpret_cast(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().getPrivate()); if (frame.isScriptFrameIterData()) - fop->delete_((ScriptFrameIter::Data*) frame.raw()); + fop->delete_((FrameIter::Data*) frame.raw()); obj->as().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 maybeIter; \ + Maybe 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 diff --git a/js/src/vm/Debugger.h b/js/src/vm/Debugger.h index c8b84d8b14..d821c72e94 100644 --- a/js/src/vm/Debugger.h +++ b/js/src/vm/Debugger.h @@ -297,7 +297,7 @@ class Debugger : private mozilla::LinkedListElement virtual const HashSet* 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 * 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 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 * 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& result); + [[nodiscard]] static bool getFrameIter(JSContext* cx, HandleDebuggerFrame frame, + mozilla::Maybe& result); + [[nodiscard]] static bool requireScriptReferent(JSContext* cx, HandleDebuggerFrame frame); static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp); diff --git a/js/src/vm/EnvironmentObject.cpp b/js/src/vm/EnvironmentObject.cpp index cb2d50f911..1c87e552ca 100644 --- a/js/src/vm/EnvironmentObject.cpp +++ b/js/src/vm/EnvironmentObject.cpp @@ -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 callobj(cx, &obj->as()); + callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment()); + + return callobj; +} + +/*****************************************************************************/ + WithEnvironmentObject* WithEnvironmentObject::create(JSContext* cx, HandleObject object, HandleObject enclosing, Handle 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() && @@ -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() || e.is() || e.is() || + e.is() || e.is(); } @@ -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()) { 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() || ei.scope().is() || + ei.scope().is() || ei.scope().is())); 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()) { + Rooted wasmFunctionScope(cx, &ei.scope().as()); + Rooted callobj(cx, WasmFunctionCallObject::createHollowForDebug(cx, wasmFunctionScope)); + if (!callobj) + return nullptr; + + debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug); } else { Rooted varScope(cx, &ei.scope().as()); Rooted env(cx, @@ -2947,6 +3008,7 @@ GetDebugEnvironment(JSContext* cx, const EnvironmentIter& ei) if (ei.scope().is() || ei.scope().is() || + ei.scope().is() || ei.scope().is()) { 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 PropertyNameSet; diff --git a/js/src/vm/EnvironmentObject.h b/js/src/vm/EnvironmentObject.h index a74018047a..63eded443b 100644 --- a/js/src/vm/EnvironmentObject.h +++ b/js/src/vm/EnvironmentObject.h @@ -425,6 +425,17 @@ typedef Rooted RootedModuleEnvironmentObject; typedef Handle HandleModuleEnvironmentObject; typedef MutableHandle 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() const return is() || is() || is() || + is() || is() || is() || is() || @@ -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); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index ad52234a31..bd41bb38b9 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -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; } } diff --git a/js/src/vm/SavedStacks.cpp b/js/src/vm/SavedStacks.cpp index 59b3e6bd14..e82cc46e9f 100644 --- a/js/src/vm/SavedStacks.cpp +++ b/js/src/vm/SavedStacks.cpp @@ -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(), diff --git a/js/src/vm/Scope.cpp b/js/src/vm/Scope.cpp index 1f8c8d1488..bad4b474c5 100644 --- a/js/src/vm/Scope.cpp +++ b/js/src/vm/Scope.cpp @@ -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().nextFrameSlot(); + case ScopeKind::WasmFunction: + // TODO return si.scope()->as().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(cx); + + { + // TODO pull the local variable names from the wasm function definition. + + Rooted> data(cx, NewEmptyScopeData(cx)); + if (!data) + return nullptr; + + Rooted 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->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().data()); break; + case ScopeKind::WasmFunction: + init(scope->as().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) { diff --git a/js/src/vm/Scope.h b/js/src/vm/Scope.h index b4e83dfc9c..01aba121cd 100644 --- a/js/src/vm/Scope.h +++ b/js/src/vm/Scope.h @@ -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 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_); + } + + const Data& data() const { + return *reinterpret_cast(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 diff --git a/js/src/vm/Stack-inl.h b/js/src/vm/Stack-inl.h index 5f66159c2c..90e8ad4ea3 100644 --- a/js/src/vm/Stack-inl.h +++ b/js/src/vm/Stack-inl.h @@ -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(); } diff --git a/js/src/vm/Stack.cpp b/js/src/vm/Stack.cpp index b1bb8a753a..60bb8e09bc 100644 --- a/js/src/vm/Stack.cpp +++ b/js/src/vm/Stack.cpp @@ -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().module()); env = &env->as().enclosingEnvironment(); break; + + case ScopeKind::WasmFunction: + env = &env->as().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"); } diff --git a/js/src/vm/Stack.h b/js/src/vm/Stack.h index 8c45d8d4d9..2190f024f6 100644 --- a/js/src/vm/Stack.h +++ b/js/src/vm/Stack.h @@ -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(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 { diff --git a/js/src/wasm/WasmBaselineCompile.cpp b/js/src/wasm/WasmBaselineCompile.cpp index f965a5c05b..a0c8456ae9 100644 --- a/js/src/wasm/WasmBaselineCompile.cpp +++ b/js/src/wasm/WasmBaselineCompile.cpp @@ -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 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; diff --git a/js/src/wasm/WasmCode.cpp b/js/src/wasm/WasmCode.cpp index 09b3122e08..39effecea7 100644 --- a/js/src/wasm/WasmCode.cpp +++ b/js/src/wasm/WasmCode.cpp @@ -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, diff --git a/js/src/wasm/WasmCode.h b/js/src/wasm/WasmCode.h index 7bb27a6665..7e5ad65993 100644 --- a/js/src/wasm/WasmCode.h +++ b/js/src/wasm/WasmCode.h @@ -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, 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, diff --git a/js/src/wasm/WasmCompile.cpp b/js/src/wasm/WasmCompile.cpp index 3e9f762786..9523416868 100644 --- a/js/src/wasm/WasmCompile.cpp +++ b/js/src/wasm/WasmCompile.cpp @@ -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); } diff --git a/js/src/wasm/WasmCompile.h b/js/src/wasm/WasmCompile.h index 1cc53705ad..9431eb43b4 100644 --- a/js/src/wasm/WasmCompile.h +++ b/js/src/wasm/WasmCompile.h @@ -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 diff --git a/js/src/wasm/WasmDebugFrame.cpp b/js/src/wasm/WasmDebugFrame.cpp new file mode 100644 index 0000000000..21be776478 --- /dev/null +++ b/js/src/wasm/WasmDebugFrame.cpp @@ -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; +} \ No newline at end of file diff --git a/js/src/wasm/WasmDebugFrame.h b/js/src/wasm/WasmDebugFrame.h new file mode 100644 index 0000000000..84ec6073af --- /dev/null +++ b/js/src/wasm/WasmDebugFrame.h @@ -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 \ No newline at end of file diff --git a/js/src/wasm/WasmFrameIterator.cpp b/js/src/wasm/WasmFrameIterator.cpp index b45ff404d9..25292fbc34 100644 --- a/js/src/wasm/WasmFrameIterator.cpp +++ b/js/src/wasm/WasmFrameIterator.cpp @@ -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(fp)->callerFP; } +static TlsData* +TlsDataFromFP(void *fp) +{ + void* debugFrame = (uint8_t*)fp - DebugFrame::offsetOfFrame(); + return reinterpret_cast(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(fp_ + callsite_->stackDepth()) - DebugFrame::offsetOfFrame(); + return static_cast(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)"; } diff --git a/js/src/wasm/WasmFrameIterator.h b/js/src/wasm/WasmFrameIterator.h index 834c0a7535..56867cf4a2 100644 --- a/js/src/wasm/WasmFrameIterator.h +++ b/js/src/wasm/WasmFrameIterator.h @@ -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 diff --git a/js/src/wasm/WasmGenerator.cpp b/js/src/wasm/WasmGenerator.cpp index 54324c1550..e9a30e171a 100644 --- a/js/src/wasm/WasmGenerator.cpp +++ b/js/src/wasm/WasmGenerator.cpp @@ -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; diff --git a/js/src/wasm/WasmGenerator.h b/js/src/wasm/WasmGenerator.h index ee5c5d3b16..22afb5e73a 100644 --- a/js/src/wasm/WasmGenerator.h +++ b/js/src/wasm/WasmGenerator.h @@ -144,6 +144,7 @@ class CompileTask Maybe alloc_; Maybe 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(); diff --git a/js/src/wasm/WasmInstance.cpp b/js/src/wasm/WasmInstance.cpp index 888919523d..1b94d943c0 100644 --- a/js/src/wasm/WasmInstance.cpp +++ b/js/src/wasm/WasmInstance.cpp @@ -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, diff --git a/js/src/wasm/WasmInstance.h b/js/src/wasm/WasmInstance.h index 5c2871259c..a17c6eaf33 100644 --- a/js/src/wasm/WasmInstance.h +++ b/js/src/wasm/WasmInstance.h @@ -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, diff --git a/js/src/wasm/WasmJS.cpp b/js/src/wasm/WasmJS.cpp index e1878c0e64..b853dd7e91 100644 --- a/js/src/wasm/WasmJS.cpp +++ b/js/src/wasm/WasmJS.cpp @@ -904,6 +904,7 @@ WasmInstanceObject::isNewborn() const WasmInstanceObject::finalize(FreeOp* fop, JSObject* obj) { fop->delete_(&obj->as().exports()); + fop->delete_(&obj->as().scopes()); if (!obj->as().isNewborn()) fop->delete_(&obj->as().instance()); } @@ -930,12 +931,20 @@ WasmInstanceObject::create(JSContext* cx, return nullptr; } + UniquePtr scopes = js::MakeUnique(cx->zone(), ScopeMap()); + if (!scopes || !scopes->init()) { + ReportOutOfMemory(cx); + return nullptr; + } + + AutoSetNewObjectMetadata metadata(cx); RootedWasmInstanceObject obj(cx, NewObjectWithGivenProto(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 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) { diff --git a/js/src/wasm/WasmJS.h b/js/src/wasm/WasmJS.h index df552a5f64..eac771f201 100644 --- a/js/src/wasm/WasmJS.h +++ b/js/src/wasm/WasmJS.h @@ -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; 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, + DefaultHasher, + SystemAllocPolicy>; + using WeakScopeMap = JS::WeakCache; + 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 diff --git a/js/src/wasm/WasmModule.cpp b/js/src/wasm/WasmModule.cpp index b4e74aa686..0f29e167e5 100644 --- a/js/src/wasm/WasmModule.cpp +++ b/js/src/wasm/WasmModule.cpp @@ -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. diff --git a/js/src/wasm/WasmStubs.cpp b/js/src/wasm/WasmStubs.cpp index e959227f25..18bed4bf36 100644 --- a/js/src/wasm/WasmStubs.cpp +++ b/js/src/wasm/WasmStubs.cpp @@ -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; +} diff --git a/js/src/wasm/WasmStubs.h b/js/src/wasm/WasmStubs.h index eb235120b9..a05544b496 100644 --- a/js/src/wasm/WasmStubs.h +++ b/js/src/wasm/WasmStubs.h @@ -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 diff --git a/js/src/wasm/WasmTypes.cpp b/js/src/wasm/WasmTypes.cpp index 7ba639023d..bed866641c 100644 --- a/js/src/wasm/WasmTypes.cpp +++ b/js/src/wasm/WasmTypes.cpp @@ -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: diff --git a/js/src/wasm/WasmTypes.h b/js/src/wasm/WasmTypes.h index 7a22cd4855..34585492ba 100644 --- a/js/src/wasm/WasmTypes.h +++ b/js/src/wasm/WasmTypes.h @@ -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