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