From 9754fb007b62349e829b938ef179b5964725b899 Mon Sep 17 00:00:00 2001 From: win7-7 Date: Mon, 29 Dec 2025 21:03:14 +0200 Subject: [PATCH] 1286948 1286948 - Adds debug mode for WASM baseline compiler. 1286948 - Adds scope and environment for wasm calls (Adds artificial JS scope and environment for wasm frames. That allows debugger to properly handle call stack). 1286948 - Adds prolog and epilog debug traps and handlers (Using toggled call/traps to invoke handler to process enter and leave frame events). 1286948 - onEnterFrame/onLeaveFrame wasm events and callstack (Handles onEnterFrame and onLeaveFrame trap handling. The FrameIter is used in the DebuggerFrame instead of ScriptFrameIter. The debug wasm frame is created and can be found on callstack during those event). Does not compile without 1331452 - Keep scope in a slot in WasmFunctionCallObjects along with 1335773 - Inspects wasm function locals to build --- js/src/doc/Debugger/Debugger.Frame.md | 16 +- js/src/frontend/EmitterScope.cpp | 6 + js/src/gc/Marking.cpp | 20 ++ js/src/gc/Policy.h | 2 + js/src/gdb/mozilla/Interpreter.py | 7 +- js/src/gdb/tests/test-Interpreter.cpp | 10 + js/src/jit/MacroAssembler.h | 7 + js/src/jit/arm/MacroAssembler-arm.cpp | 28 +++ js/src/jit/arm64/MacroAssembler-arm64.cpp | 19 ++ .../MacroAssembler-mips-shared.cpp | 19 ++ js/src/jit/shared/Assembler-shared.h | 22 -- js/src/jit/x86-shared/Assembler-x86-shared.h | 7 + .../jit/x86-shared/BaseAssembler-x86-shared.h | 34 +++ .../x86-shared/MacroAssembler-x86-shared.cpp | 22 ++ js/src/jsscript.cpp | 3 + js/src/moz.build | 1 + js/src/vm/CommonPropertyNames.h | 1 + js/src/vm/Debugger-inl.h | 6 +- js/src/vm/Debugger.cpp | 197 ++++++++++++------ js/src/vm/Debugger.h | 28 +-- js/src/vm/EnvironmentObject.cpp | 104 ++++++++- js/src/vm/EnvironmentObject.h | 22 ++ js/src/vm/Interpreter.cpp | 3 + js/src/vm/SavedStacks.cpp | 4 +- js/src/vm/Scope.cpp | 70 +++++++ js/src/vm/Scope.h | 65 +++++- js/src/vm/Stack-inl.h | 66 +++++- js/src/vm/Stack.cpp | 30 +-- js/src/vm/Stack.h | 38 +++- js/src/wasm/WasmBaselineCompile.cpp | 99 ++++++++- js/src/wasm/WasmCode.cpp | 57 ++++- js/src/wasm/WasmCode.h | 19 ++ js/src/wasm/WasmCompile.cpp | 7 + js/src/wasm/WasmCompile.h | 4 +- js/src/wasm/WasmDebugFrame.cpp | 65 ++++++ js/src/wasm/WasmDebugFrame.h | 110 ++++++++++ js/src/wasm/WasmFrameIterator.cpp | 48 +++++ js/src/wasm/WasmFrameIterator.h | 12 +- js/src/wasm/WasmGenerator.cpp | 63 +++++- js/src/wasm/WasmGenerator.h | 12 +- js/src/wasm/WasmInstance.cpp | 13 +- js/src/wasm/WasmInstance.h | 6 + js/src/wasm/WasmJS.cpp | 34 +++ js/src/wasm/WasmJS.h | 22 +- js/src/wasm/WasmModule.cpp | 12 +- js/src/wasm/WasmStubs.cpp | 56 +++++ js/src/wasm/WasmStubs.h | 3 + js/src/wasm/WasmTypes.cpp | 74 +++++++ js/src/wasm/WasmTypes.h | 32 ++- 49 files changed, 1447 insertions(+), 158 deletions(-) create mode 100644 js/src/wasm/WasmDebugFrame.cpp create mode 100644 js/src/wasm/WasmDebugFrame.h 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