--- a/js/src/jit-test/tests/debug/wasm-04.js
+++ b/js/src/jit-test/tests/debug/wasm-04.js
@@ -13,22 +13,22 @@ dbg.onNewScript = (script) => {
s = script;
}
g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func) (export "" 0))')));`);
assertEq(s.format, "wasm");
assertThrowsInstanceOf(() => s.displayName, Error);
assertThrowsInstanceOf(() => s.url, Error);
-assertThrowsInstanceOf(() => s.startLine, Error);
-assertThrowsInstanceOf(() => s.lineCount, Error);
+//assertThrowsInstanceOf(() => s.startLine, Error);
+//assertThrowsInstanceOf(() => s.lineCount, Error);
assertThrowsInstanceOf(() => s.sourceStart, Error);
assertThrowsInstanceOf(() => s.sourceLength, Error);
assertThrowsInstanceOf(() => s.global, Error);
assertThrowsInstanceOf(() => s.getChildScripts(), Error);
assertThrowsInstanceOf(() => s.getAllOffsets(), Error);
assertThrowsInstanceOf(() => s.getAllColumnOffsets(), Error);
-assertThrowsInstanceOf(() => s.setBreakpoint(0, { hit: () => {} }), Error);
-assertThrowsInstanceOf(() => s.getBreakpoint(0), Error);
-assertThrowsInstanceOf(() => s.clearBreakpoint({}), Error);
-assertThrowsInstanceOf(() => s.clearAllBreakpoints(), Error);
+//assertThrowsInstanceOf(() => s.setBreakpoint(0, { hit: () => {} }), Error);
+//assertThrowsInstanceOf(() => s.getBreakpoint(0), Error);
+//assertThrowsInstanceOf(() => s.clearBreakpoint({}), Error);
+//assertThrowsInstanceOf(() => s.clearAllBreakpoints(), Error);
assertThrowsInstanceOf(() => s.isInCatchScope(0), Error);
assertThrowsInstanceOf(() => s.getOffsetsCoverage(), Error);
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -359,16 +359,17 @@ UNIFIED_SOURCES += [
'wasm/AsmJS.cpp',
'wasm/WasmBaselineCompile.cpp',
'wasm/WasmBinary.cpp',
'wasm/WasmBinaryFormat.cpp',
'wasm/WasmBinaryIterator.cpp',
'wasm/WasmBinaryToAST.cpp',
'wasm/WasmBinaryToExperimentalText.cpp',
'wasm/WasmBinaryToText.cpp',
+ 'wasm/WasmBreakpoint.cpp',
'wasm/WasmCode.cpp',
'wasm/WasmCompartment.cpp',
'wasm/WasmCompile.cpp',
'wasm/WasmDebugFrame.cpp',
'wasm/WasmFrameIterator.cpp',
'wasm/WasmGenerator.cpp',
'wasm/WasmInstance.cpp',
'wasm/WasmIonCompile.cpp',
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -646,16 +646,17 @@ Debugger::Debugger(JSContext* cx, Native
traceLoggerLastDrainedIteration(0),
#endif
traceLoggerScriptedCallsLastDrainedSize(0),
traceLoggerScriptedCallsLastDrainedIteration(0)
{
assertSameCompartment(cx, dbg);
JS_INIT_CLIST(&breakpoints);
+ JS_INIT_CLIST(&wasmBreakpoints);
JS_INIT_CLIST(&onNewGlobalObjectWatchersLink);
#ifdef JS_TRACE_LOGGING
TraceLoggerThread* logger = TraceLoggerForMainThread(cx->runtime());
if (logger) {
#ifdef NIGHTLY_BUILD
logger->getIterationAndSize(&traceLoggerLastDrainedIteration, &traceLoggerLastDrainedSize);
#endif
@@ -728,16 +729,32 @@ Debugger::hasMemory() const
DebuggerMemory&
Debugger::memory() const
{
MOZ_ASSERT(hasMemory());
return object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE).toObject().as<DebuggerMemory>();
}
bool
+Debugger::hasWasmFrameWithStepMode(JSContext* cx, js::jit::AsmJSFrame* fp, void* pc)
+{
+ AbstractFramePtr referent = AbstractFramePtr::FromRaw(fp);
+
+ Rooted<DebuggerFrame*> result(cx);
+
+ FrameMap::AddPtr p = frames.lookupForAdd(referent);
+ if (!p)
+ return false;
+
+ Rooted<DebuggerFrame*> dbgFrame(cx, &p->value()->as<DebuggerFrame>());
+ RootedValue fval(cx, dbgFrame->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER));
+ return !fval.isUndefined();
+}
+
+bool
Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
const FrameIter* maybeIter, MutableHandleValue vp)
{
RootedDebuggerFrame result(cx);
if (!Debugger::getScriptFrameWithIter(cx, referent, maybeIter, &result))
return false;
vp.setObject(*result);
@@ -1980,17 +1997,17 @@ Debugger::onTrap(JSContext* cx, MutableH
/* By convention, return the true op to the interpreter in vp. */
vp.setInt32(op);
return JSTRAP_CONTINUE;
}
/* 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
* pending exception for a 'catch' clause to handle. Don't let the
* onStep handlers mess with that (other than by returning a resumption
* value).
*/
RootedValue exception(cx, UndefinedValue());
@@ -2014,16 +2031,17 @@ Debugger::onSingleStep(JSContext* cx, Mu
* Validate the single-step count on this frame's script, to ensure that
* we're not receiving traps we didn't ask for. Even when frames is
* non-empty (and thus we know this trap was requested), do the check
* anyway, to make sure the count has the correct non-zero value.
*
* The converse --- ensuring that we do receive traps when we should --- can
* be done with unit tests.
*/
+ if (!iter.isWasm()) // FIXME
{
uint32_t stepperCount = 0;
JSScript* trappingScript = iter.script();
GlobalObject* global = cx->global();
if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
Debugger* dbg = *p;
for (FrameMap::Range r = dbg->frames.all(); !r.empty(); r.popFront()) {
@@ -2424,17 +2442,18 @@ class MOZ_RAII ExecutionObservableScript
// while a non-rematerialized Ion frame may indeed be running script_,
// we cannot mark them as debuggees until they bail out.
//
// Upon bailing out, any newly constructed Baseline frames that came
// from Ion frames with scripts that are isDebuggee() is marked as
// 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.
- return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr().script() == script_;
+ return iter.hasUsableAbstractFramePtr() && iter.hasScript() &&
+ iter.abstractFramePtr().script() == script_;
}
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
/* static */ bool
Debugger::updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObservableSet& obs,
IsObserving observing)
@@ -3029,16 +3048,29 @@ Debugger::markAllIteratively(GCMarker* t
* Therefore the breakpoint handler is live.
*/
if (!IsMarked(&bp->getHandlerRef())) {
TraceEdge(trc, &bp->getHandlerRef(), "breakpoint handler");
markedAny = true;
}
}
}
+ for (wasm::WasmBreakpoint* bp = wasm::WasmBreakpoint::firstDebuggerBreakpoint(dbg);
+ bp; bp = bp->nextInDebugger()) {
+ if (IsMarkedUnbarriered(&bp->site->wasmInstance)) {
+ /*
+ * The debugger and the wasm instance are both live.
+ * Therefore the breakpoint handler is live.
+ */
+ if (!IsMarked(&bp->getHandlerRef())) {
+ TraceEdge(trc, &bp->getHandlerRef(), "wasm breakpoint handler");
+ markedAny = true;
+ }
+ }
+ }
}
}
}
}
return markedAny;
}
/*
@@ -3063,16 +3095,21 @@ Debugger::markAll(JSTracer* trc)
dbg->environments.trace(trc);
dbg->wasmInstanceScripts.trace(trc);
dbg->wasmInstanceSources.trace(trc);
for (Breakpoint* bp = dbg->firstBreakpoint(); bp; bp = bp->nextInDebugger()) {
TraceManuallyBarrieredEdge(trc, &bp->site->script, "breakpoint script");
TraceEdge(trc, &bp->getHandlerRef(), "breakpoint handler");
}
+ for (wasm::WasmBreakpoint* bp = wasm::WasmBreakpoint::firstDebuggerBreakpoint(dbg);
+ bp; bp = bp->nextInDebugger()) {
+ TraceManuallyBarrieredEdge(trc, &bp->site->wasmInstance, "breakpoint wasm instance");
+ TraceEdge(trc, &bp->getHandlerRef(), "wasm breakpoint handler");
+ }
}
}
/* static */ void
Debugger::traceObject(JSTracer* trc, JSObject* obj)
{
if (Debugger* dbg = Debugger::fromJSObject(obj))
dbg->trace(trc);
@@ -5519,31 +5556,56 @@ DebuggerScript_getUrl(JSContext* cx, uns
return false;
args.rval().setString(str);
} else {
args.rval().setNull();
}
return true;
}
+struct DebuggerScriptGetStartLineMatcher
+{
+ using ReturnType = uint32_t;
+
+ ReturnType match(HandleScript script) {
+ return uint32_t(script->lineno());
+ }
+ ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
+ return 1;
+ }
+};
+
static bool
DebuggerScript_getStartLine(JSContext* cx, unsigned argc, Value* vp)
{
- THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get startLine)", args, obj, script);
- args.rval().setNumber(uint32_t(script->lineno()));
- return true;
-}
+ THIS_DEBUGSCRIPT_REFERENT(cx, argc, vp, "(get startLine)", args, obj, referent);
+ DebuggerScriptGetStartLineMatcher matcher;
+ args.rval().setNumber(referent.match(matcher));
+ return true;
+}
+
+struct DebuggerScriptGetLineCountMatcher
+{
+ using ReturnType = double;
+
+ ReturnType match(HandleScript script) {
+ unsigned maxLine = GetScriptLineExtent(script);
+ return double(maxLine);
+ }
+ ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
+ return double(wasmInstance->instance().code().totalSourceLines());
+ }
+};
static bool
DebuggerScript_getLineCount(JSContext* cx, unsigned argc, Value* vp)
{
- THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "(get lineCount)", args, obj, script);
-
- unsigned maxLine = GetScriptLineExtent(script);
- args.rval().setNumber(double(maxLine));
+ THIS_DEBUGSCRIPT_REFERENT(cx, argc, vp, "(get lineCount)", args, obj, referent);
+ DebuggerScriptGetLineCountMatcher matcher;
+ args.rval().setNumber(referent.match(matcher));
return true;
}
class DebuggerScriptGetSourceMatcher
{
JSContext* cx_;
Debugger* dbg_;
@@ -5664,16 +5726,35 @@ DebuggerScript_getChildScripts(JSContext
}
}
}
args.rval().setObject(*result);
return true;
}
static bool
+ScriptOffset(JSContext* cx, const Value& v, size_t* offsetp)
+{
+ double d;
+ size_t off;
+
+ bool ok = v.isNumber();
+ if (ok) {
+ d = v.toNumber();
+ off = size_t(d);
+ }
+ if (!ok || off != d) {
+ JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_OFFSET);
+ return false;
+ }
+ *offsetp = off;
+ return true;
+}
+
+static bool
ScriptOffset(JSContext* cx, JSScript* script, const Value& v, size_t* offsetp)
{
double d;
size_t off;
bool ok = v.isNumber();
if (ok) {
d = v.toNumber();
@@ -5972,82 +6053,131 @@ class FlowGraphSummary {
entries_[targetOffset] = Entry::createWithMultipleEdgesFromSingleLine(sourceLineno);
}
Vector<Entry> entries_;
};
} /* anonymous namespace */
+class DebuggerScriptGetOffsetLocationMatcher
+{
+ JSContext* cx_;
+ size_t offset_;
+ RootedPlainObject result_;
+ public:
+ explicit DebuggerScriptGetOffsetLocationMatcher(JSContext* cx, size_t offset)
+ : cx_(cx), offset_(offset), result_(cx, NewBuiltinClassInstance<PlainObject>(cx)) { }
+ using ReturnType = bool;
+ ReturnType match(HandleScript script) {
+ if (!result_)
+ return false;
+
+ if (!IsValidBytecodeOffset(cx_, script, offset_)) {
+ JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_OFFSET);
+ return false;
+ }
+
+ FlowGraphSummary flowData(cx_);
+ if (!flowData.populate(cx_, script))
+ return false;
+
+ BytecodeRangeWithPosition r(cx_, script);
+ while (!r.empty() && r.frontOffset() < offset_)
+ r.popFront();
+
+ offset_ = r.frontOffset();
+ bool isEntryPoint = r.frontIsEntryPoint();
+
+ // Line numbers are only correctly defined on entry points. Thus looks
+ // either for the next valid offset in the flowData, being the last entry
+ // point flowing into the current offset, or for the next valid entry point.
+ while (!r.frontIsEntryPoint() && !flowData[r.frontOffset()].hasSingleEdge()) {
+ r.popFront();
+ MOZ_ASSERT(!r.empty());
+ }
+
+ // If this is an entry point, take the line number associated with the entry
+ // point, otherwise settle on the next instruction and take the incoming
+ // edge position.
+ size_t lineno;
+ size_t column;
+ if (r.frontIsEntryPoint()) {
+ lineno = r.frontLineNumber();
+ column = r.frontColumnNumber();
+ } else {
+ MOZ_ASSERT(flowData[r.frontOffset()].hasSingleEdge());
+ lineno = flowData[r.frontOffset()].lineno();
+ column = flowData[r.frontOffset()].column();
+ }
+
+ RootedId id(cx_, NameToId(cx_->names().lineNumber));
+ RootedValue value(cx_, NumberValue(lineno));
+ if (!DefineProperty(cx_, result_, id, value))
+ return false;
+
+ value = NumberValue(column);
+ if (!DefineProperty(cx_, result_, cx_->names().columnNumber, value))
+ return false;
+
+ // The same entry point test that is used by getAllColumnOffsets.
+ isEntryPoint = (isEntryPoint &&
+ !flowData[offset_].hasNoEdges() &&
+ (flowData[offset_].lineno() != r.frontLineNumber() ||
+ flowData[offset_].column() != r.frontColumnNumber()));
+ value.setBoolean(isEntryPoint);
+ if (!DefineProperty(cx_, result_, cx_->names().isEntryPoint, value))
+ return false;
+
+ return true;
+ }
+
+ ReturnType match(Handle<WasmInstanceObject*> instance) {
+ if (!result_)
+ return false;
+
+ size_t lineno;
+ size_t column;
+ if (!instance->instance().code().getOffsetLocation(offset_, &lineno, &column))
+ return false;
+
+ RootedId id(cx_, NameToId(cx_->names().lineNumber));
+ RootedValue value(cx_, NumberValue(lineno));
+ if (!DefineProperty(cx_, result_, id, value))
+ return false;
+
+ value = NumberValue(column);
+ if (!DefineProperty(cx_, result_, cx_->names().columnNumber, value))
+ return false;
+
+ value.setBoolean(true);
+ if (!DefineProperty(cx_, result_, cx_->names().isEntryPoint, value))
+ return false;
+
+ return true;
+ }
+
+ RootedPlainObject& result() { return result_; }
+};
+
static bool
DebuggerScript_getOffsetLocation(JSContext* cx, unsigned argc, Value* vp)
{
- THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getOffsetLocation", args, obj, script);
+ THIS_DEBUGSCRIPT_REFERENT(cx, argc, vp, "getOffsetLocation", args, obj, referent);
if (!args.requireAtLeast(cx, "Debugger.Script.getOffsetLocation", 1))
return false;
size_t offset;
- if (!ScriptOffset(cx, script, args[0], &offset))
- return false;
-
- FlowGraphSummary flowData(cx);
- if (!flowData.populate(cx, script))
- return false;
-
- RootedPlainObject result(cx, NewBuiltinClassInstance<PlainObject>(cx));
- if (!result)
- return false;
-
- BytecodeRangeWithPosition r(cx, script);
- while (!r.empty() && r.frontOffset() < offset)
- r.popFront();
-
- offset = r.frontOffset();
- bool isEntryPoint = r.frontIsEntryPoint();
-
- // Line numbers are only correctly defined on entry points. Thus looks
- // either for the next valid offset in the flowData, being the last entry
- // point flowing into the current offset, or for the next valid entry point.
- while (!r.frontIsEntryPoint() && !flowData[r.frontOffset()].hasSingleEdge()) {
- r.popFront();
- MOZ_ASSERT(!r.empty());
- }
-
- // If this is an entry point, take the line number associated with the entry
- // point, otherwise settle on the next instruction and take the incoming
- // edge position.
- size_t lineno;
- size_t column;
- if (r.frontIsEntryPoint()) {
- lineno = r.frontLineNumber();
- column = r.frontColumnNumber();
- } else {
- MOZ_ASSERT(flowData[r.frontOffset()].hasSingleEdge());
- lineno = flowData[r.frontOffset()].lineno();
- column = flowData[r.frontOffset()].column();
- }
-
- RootedId id(cx, NameToId(cx->names().lineNumber));
- RootedValue value(cx, NumberValue(lineno));
- if (!DefineProperty(cx, result, id, value))
- return false;
-
- value = NumberValue(column);
- if (!DefineProperty(cx, result, cx->names().columnNumber, value))
- return false;
-
- // The same entry point test that is used by getAllColumnOffsets.
- isEntryPoint = (isEntryPoint &&
- !flowData[offset].hasNoEdges() &&
- (flowData[offset].lineno() != r.frontLineNumber() ||
- flowData[offset].column() != r.frontColumnNumber()));
- value.setBoolean(isEntryPoint);
- if (!DefineProperty(cx, result, cx->names().isEntryPoint, value))
- return false;
-
- args.rval().setObject(*result);
+ if (!ScriptOffset(cx, args[0], &offset))
+ return false;
+
+ DebuggerScriptGetOffsetLocationMatcher matcher(cx, offset);
+ if (!referent.match(matcher))
+ return false;
+
+ args.rval().setObject(*matcher.result());
return true;
}
static bool
DebuggerScript_getAllOffsets(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getAllOffsets", args, obj, script);
@@ -6462,56 +6592,101 @@ Debugger::propagateForcedReturn(JSContex
// The interrupt handler then returns false with no exception set,
// signaling an uncatchable exception. In the exception handlers, we then
// check for the special propagating-forced-return flag.
MOZ_ASSERT(!cx->isExceptionPending());
cx->setPropagatingForcedReturn();
frame.setReturnValue(rval);
}
+struct DebuggerScriptSetBreakpointMatcher
+{
+ JSContext* cx_;
+ Debugger* dbg_;
+ size_t offset_;
+ JSObject* handler_;
+
+ public:
+ explicit DebuggerScriptSetBreakpointMatcher(JSContext* cx, Debugger* dbg, size_t offset, JSObject* handler)
+ : cx_(cx), dbg_(dbg), offset_(offset), handler_(handler)
+ { }
+
+ using ReturnType = bool;
+
+ ReturnType match(HandleScript script) {
+ if (!dbg_->observesScript(script)) {
+ JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGING);
+ return false;
+ }
+
+ if (!IsValidBytecodeOffset(cx_, script, offset_)) {
+ JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_OFFSET);
+ return false;
+ }
+
+ // Ensure observability *before* setting the breakpoint. If the script is
+ // not already a debuggee, trying to ensure observability after setting
+ // the breakpoint (and thus marking the script as a debuggee) will skip
+ // actually ensuring observability.
+ if (!dbg_->ensureExecutionObservabilityOfScript(cx_, script))
+ return false;
+
+ jsbytecode* pc = script->offsetToPC(offset_);
+ BreakpointSite* site = script->getOrCreateBreakpointSite(cx_, pc);
+ if (!site)
+ return false;
+ site->inc(cx_->runtime()->defaultFreeOp());
+ if (cx_->runtime()->new_<Breakpoint>(dbg_, site, handler_))
+ return true;
+ site->dec(cx_->runtime()->defaultFreeOp());
+ site->destroyIfEmpty(cx_->runtime()->defaultFreeOp());
+ return false;
+ }
+
+ ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
+ wasm::Instance& instance = wasmInstance->instance();
+ if (!instance.code().hasBreakpointOffset(offset_)) {
+ JS_ReportErrorNumberASCII(cx_, GetErrorMessage, nullptr, JSMSG_DEBUG_BAD_OFFSET);
+ return false;
+ }
+ wasm::WasmBreakpointSite* site = instance.code().getOrCreateBreakpointSite(instance.object(), cx_, offset_);
+ if (!site)
+ return false;
+ site->inc(cx_);
+ if (cx_->runtime()->new_<wasm::WasmBreakpoint>(dbg_, site, handler_))
+ return true;
+ site->dec(cx_);
+ site->destroyIfEmpty(cx_->runtime()->defaultFreeOp());
+ return false;
+ }
+
+};
+
static bool
DebuggerScript_setBreakpoint(JSContext* cx, unsigned argc, Value* vp)
{
- THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "setBreakpoint", args, obj, script);
+ THIS_DEBUGSCRIPT_REFERENT(cx, argc, vp, "setBreakpoint", args, obj, referent);
if (!args.requireAtLeast(cx, "Debugger.Script.setBreakpoint", 2))
return false;
Debugger* dbg = Debugger::fromChildJSObject(obj);
- if (!dbg->observesScript(script)) {
- JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_DEBUGGING);
- return false;
- }
-
size_t offset;
- if (!ScriptOffset(cx, script, args[0], &offset))
+ if (!ScriptOffset(cx, args[0], &offset))
return false;
RootedObject handler(cx, NonNullObject(cx, args[1]));
if (!handler)
return false;
- // Ensure observability *before* setting the breakpoint. If the script is
- // not already a debuggee, trying to ensure observability after setting
- // the breakpoint (and thus marking the script as a debuggee) will skip
- // actually ensuring observability.
- if (!dbg->ensureExecutionObservabilityOfScript(cx, script))
- return false;
-
- jsbytecode* pc = script->offsetToPC(offset);
- BreakpointSite* site = script->getOrCreateBreakpointSite(cx, pc);
- if (!site)
- return false;
- site->inc(cx->runtime()->defaultFreeOp());
- if (cx->runtime()->new_<Breakpoint>(dbg, site, handler)) {
- args.rval().setUndefined();
- return true;
- }
- site->dec(cx->runtime()->defaultFreeOp());
- site->destroyIfEmpty(cx->runtime()->defaultFreeOp());
- return false;
+
+ DebuggerScriptSetBreakpointMatcher matcher(cx, dbg, offset, handler);
+ if (!referent.match(matcher))
+ return false;
+ args.rval().setUndefined();
+ return true;
}
static bool
DebuggerScript_getBreakpoints(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "getBreakpoints", args, obj, script);
Debugger* dbg = Debugger::fromChildJSObject(obj);
@@ -6540,39 +6715,70 @@ DebuggerScript_getBreakpoints(JSContext*
}
}
}
}
args.rval().setObject(*arr);
return true;
}
+class DebuggerScriptClearBreakpointMatcher
+{
+ JSContext* cx_;
+ Debugger* dbg_;
+ JSObject* handler_;
+ public:
+ explicit DebuggerScriptClearBreakpointMatcher(JSContext* cx, Debugger* dbg, JSObject* handler) : cx_(cx), dbg_(dbg), handler_(handler) { }
+ using ReturnType = bool;
+
+ ReturnType match(HandleScript script) {
+ script->clearBreakpointsIn(cx_->runtime()->defaultFreeOp(), dbg_, handler_);
+ return true;
+ }
+
+ ReturnType match(Handle<WasmInstanceObject*> instance) {
+ for (wasm::WasmBreakpoint* bp = wasm::WasmBreakpoint::firstDebuggerBreakpoint(dbg_); bp; ) {
+ wasm::WasmBreakpoint* next = bp->nextInDebugger();
+ if (!handler_ || bp->getHandler() == handler_) {
+ bp->destroy(cx_->runtime()->defaultFreeOp());
+ }
+ bp = next;
+ }
+ return true;
+ }
+};
+
static bool
DebuggerScript_clearBreakpoint(JSContext* cx, unsigned argc, Value* vp)
{
- THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearBreakpoint", args, obj, script);
+ THIS_DEBUGSCRIPT_REFERENT(cx, argc, vp, "clearBreakpoint", args, obj, referent);
if (!args.requireAtLeast(cx, "Debugger.Script.clearBreakpoint", 1))
return false;
Debugger* dbg = Debugger::fromChildJSObject(obj);
JSObject* handler = NonNullObject(cx, args[0]);
if (!handler)
return false;
- script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, handler);
+ DebuggerScriptClearBreakpointMatcher matcher(cx, dbg, handler);
+ if (!referent.match(matcher))
+ return false;
+
args.rval().setUndefined();
return true;
}
static bool
DebuggerScript_clearAllBreakpoints(JSContext* cx, unsigned argc, Value* vp)
{
- THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "clearAllBreakpoints", args, obj, script);
+ THIS_DEBUGSCRIPT_REFERENT(cx, argc, vp, "clearAllBreakpoints", args, obj, referent);
Debugger* dbg = Debugger::fromChildJSObject(obj);
- script->clearBreakpointsIn(cx->runtime()->defaultFreeOp(), dbg, nullptr);
+ DebuggerScriptClearBreakpointMatcher matcher(cx, dbg, nullptr);
+ if (!referent.match(matcher))
+ return false;
args.rval().setUndefined();
return true;
}
static bool
DebuggerScript_isInCatchScope(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGSCRIPT_SCRIPT(cx, argc, vp, "isInCatchScope", args, obj, script);
@@ -7227,16 +7433,31 @@ static const JSPropertySpec DebuggerSour
static const JSFunctionSpec DebuggerSource_methods[] = {
JS_FS_END
};
/*** Debugger.Frame ******************************************************************************/
+
+static inline DebuggerFrameReferent
+GetFrameReferent(JSObject* obj)
+{
+ AbstractFramePtr referent = AbstractFramePtr::FromRaw(obj->as<NativeObject>().getPrivate());
+ if (referent.isScriptFrameIterData()) {
+ FrameIter iter(*(FrameIter::Data*)(referent.raw()));
+ referent = iter.abstractFramePtr();
+ }
+ if (referent.isWasmDebugFrame()) {
+ return AsVariant(referent.asWasmDebugFrame());
+ }
+ return AsVariant(referent);
+}
+
/* static */ NativeObject*
DebuggerFrame::initClass(JSContext* cx, HandleObject dbgCtor, HandleObject obj)
{
Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
return InitClass(cx, dbgCtor, objProto, &class_, construct, 0, properties_,
methods_, nullptr, nullptr);
@@ -7262,38 +7483,47 @@ DebuggerFrame::create(JSContext* cx, Han
frame.setPrivate(referent.raw());
}
frame.setReservedSlot(JSSLOT_DEBUGFRAME_OWNER, ObjectValue(*debugger));
return &frame;
}
+#define FRAME_IS_NOT_WASM(frame) MOZ_ASSERT(!frame->as<DebuggerFrame>().isWasm(), "not wasm frame!")
+
/* static */ bool
DebuggerFrame::getCallee(JSContext* cx, HandleDebuggerFrame frame,
MutableHandleDebuggerObject result)
{
MOZ_ASSERT(frame->isLive());
+ MOZ_ASSERT(!frame->isWasm());
AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
if (!referent.isFunctionFrame()) {
result.set(nullptr);
return true;
}
Debugger* dbg = frame->owner();
RootedObject callee(cx, referent.callee());
return dbg->wrapDebuggeeObject(cx, callee, result);
}
/* static */ bool
DebuggerFrame::getIsConstructing(JSContext* cx, HandleDebuggerFrame frame, bool& result)
{
+ if (frame->isWasm()) {
+ result = false;
+ return true;
+ }
+
MOZ_ASSERT(frame->isLive());
+ MOZ_ASSERT(!frame->isWasm());
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
result = iter.isFunctionFrame() && iter.isConstructing();
return true;
@@ -7359,24 +7589,47 @@ DebuggerFrame::getEnvironment(JSContext*
}
return dbg->wrapEnvironment(cx, env, result);
}
/* static */ bool
DebuggerFrame::getIsGenerator(HandleDebuggerFrame frame)
{
+ if (frame->isWasm()) {
+ return false;
+ }
+
+ MOZ_ASSERT(!frame->isWasm());
+
return DebuggerFrame::getReferent(frame).script()->isGenerator();
}
/* static */ bool
DebuggerFrame::getOffset(JSContext* cx, HandleDebuggerFrame frame, size_t& result)
{
MOZ_ASSERT(frame->isLive());
+ if (frame->isWasm()) {
+ Maybe<FrameIter> maybeIter;
+ if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+ return false;
+ MOZ_ASSERT(maybeIter->isWasm());
+ RootedWasmInstanceObject wasmInstance(cx, maybeIter->wasmInstance()->object());
+ uint32_t offset = 0;
+ FrameIter& iter = *maybeIter;
+ UpdateFrameIterPc(iter);
+ if (!wasmInstance->instance().code().pcToOffset(maybeIter->pc(), &offset))
+ return false;
+ result = (size_t)offset;
+ return true;
+ }
+
+ MOZ_ASSERT(!frame->isWasm());
+
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
JSScript* script = iter.script();
UpdateFrameIterPc(iter);
jsbytecode* pc = iter.pc();
@@ -7409,17 +7662,23 @@ DebuggerFrame::getOlder(JSContext* cx, H
result.set(nullptr);
return true;
}
/* static */ bool
DebuggerFrame::getThis(JSContext* cx, HandleDebuggerFrame frame, MutableHandleValue result)
{
+ if (frame->isWasm()) {
+ result.setNull();
+ return true;
+ }
+
MOZ_ASSERT(frame->isLive());
+ MOZ_ASSERT(!frame->isWasm());
Debugger* dbg = frame->owner();
Maybe<ScriptFrameIter> maybeIter;
if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
return false;
ScriptFrameIter& iter = *maybeIter;
@@ -7456,16 +7715,18 @@ DebuggerFrame::getType(HandleDebuggerFra
else if (referent.isWasmDebugFrame())
return DebuggerFrameType::WasmCall;
MOZ_CRASH("Unknown frame type");
}
/* static */ DebuggerFrameImplementation
DebuggerFrame::getImplementation(HandleDebuggerFrame frame)
{
+ MOZ_ASSERT(!frame->isWasm());
+
AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
if (referent.isBaselineFrame())
return DebuggerFrameImplementation::Baseline;
else if (referent.isRematerializedFrame())
return DebuggerFrameImplementation::Ion;
return DebuggerFrameImplementation::Interpreter;
}
@@ -7639,16 +7900,27 @@ DebuggerFrame::eval(JSContext* cx, Handl
}
/* statuc */ bool
DebuggerFrame::isLive() const
{
return !!getPrivate();
}
+bool
+DebuggerFrame::isWasm() const
+{
+ AbstractFramePtr referent = AbstractFramePtr::FromRaw(getPrivate());
+ if (referent.isScriptFrameIterData()) {
+ FrameIter iter(*(FrameIter::Data*)(referent.raw()));
+ referent = iter.abstractFramePtr();
+ }
+ return referent.isWasmDebugFrame();
+}
+
static bool
DebuggerFrame_requireLive(JSContext* cx, HandleDebuggerFrame frame)
{
if (!frame->isLive()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_LIVE,
"Debugger.Frame");
return false;
}
@@ -7666,16 +7938,18 @@ DebuggerFrame::getReferent(HandleDebugge
}
return referent;
}
/* static */ bool
DebuggerFrame::getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
Maybe<ScriptFrameIter>& result)
{
+ MOZ_ASSERT(!frame->isWasm());
+
AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate());
if (referent.isScriptFrameIterData()) {
result.emplace(*reinterpret_cast<ScriptFrameIter::Data*>(referent.raw()));
} else {
result.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
ScriptFrameIter& iter = *result;
while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != referent)
++iter;
@@ -7716,18 +7990,24 @@ DebuggerFrame_freeScriptFrameIterData(Fr
obj->as<NativeObject>().setPrivate(nullptr);
}
static void
DebuggerFrame_maybeDecrementFrameScriptStepModeCount(FreeOp* fop, AbstractFramePtr frame,
NativeObject* frameobj)
{
/* If this frame has an onStep handler, decrement the script's count. */
- if (!frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
+ if (frameobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER).isUndefined())
+ return;
+ if (!frame.isWasmDebugFrame())
frame.script()->decrementStepModeCount(fop);
+ else {
+ wasm::Instance* instance = frame.wasmInstance();
+ instance->code().decrementStepModeCount(instance->cx(), frame.asWasmDebugFrame()->pc());
+ }
}
static void
DebuggerFrame_finalize(FreeOp* fop, JSObject* obj)
{
MOZ_ASSERT(fop->maybeOffMainThread());
DebuggerFrame_freeScriptFrameIterData(fop, obj);
}
@@ -7792,26 +8072,31 @@ DebuggerFrame_checkThis(JSContext* cx, c
return false;
#define THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj) \
CallArgs args = CallArgsFromVp(argc, vp); \
RootedNativeObject thisobj(cx, DebuggerFrame_checkThis(cx, args, fnname, true)); \
if (!thisobj) \
return false
+#define THIS_FRAME_REFERENT(cx, argc, vp, fnname, args, thisobj, referent) \
+ THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \
+ DebuggerFrameReferent referent = GetFrameReferent(thisobj);
+
#define THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame) \
THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj); \
AbstractFramePtr frame = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
if (frame.isScriptFrameIterData()) { \
FrameIter iter(*(ScriptFrameIter::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); \
+ FRAME_IS_NOT_WASM(thisobj); \
Maybe<ScriptFrameIter> maybeIter; \
{ \
AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
if (f.isScriptFrameIterData()) { \
maybeIter.emplace(*(ScriptFrameIter::Data*)(f.raw())); \
} else { \
maybeIter.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); \
ScriptFrameIter& iter = *maybeIter; \
@@ -7822,16 +8107,17 @@ DebuggerFrame_checkThis(JSContext* cx, c
return false; \
thisobj->setPrivate(data.raw()); \
} \
} \
ScriptFrameIter& iter = *maybeIter
#define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, frame, dbg) \
THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame); \
+ FRAME_IS_NOT_WASM(thisobj); \
Debugger* dbg = Debugger::fromChildJSObject(thisobj)
#define THIS_FRAME_OWNER_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter, dbg) \
THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter); \
Debugger* dbg = Debugger::fromChildJSObject(thisobj)
/* static */ bool
DebuggerFrame::typeGetter(JSContext* cx, unsigned argc, Value* vp)
@@ -7865,16 +8151,24 @@ DebuggerFrame::typeGetter(JSContext* cx,
return true;
}
/* static */ bool
DebuggerFrame::implementationGetter(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGGER_FRAME(cx, argc, vp, "get implementation", args, frame);
+ if (frame->isWasm()) {
+ JSAtom* str = Atomize(cx, "wasm", strlen("wasm"));
+ if (!str)
+ return false;
+ args.rval().setString(str);
+ return true;
+ }
+
DebuggerFrameImplementation implementation = DebuggerFrame::getImplementation(frame);
const char* s;
switch (implementation) {
case DebuggerFrameImplementation::Baseline:
s = "baseline";
break;
case DebuggerFrameImplementation::Ion:
@@ -7908,16 +8202,21 @@ DebuggerFrame::environmentGetter(JSConte
return true;
}
/* static */ bool
DebuggerFrame::calleeGetter(JSContext* cx, unsigned argc, Value* vp)
{
THIS_DEBUGGER_FRAME(cx, argc, vp, "get callee", args, frame);
+ if (frame->isWasm()) {
+ args.rval().setNull();
+ return true;
+ }
+
RootedDebuggerObject result(cx);
if (!DebuggerFrame::getCallee(cx, frame, &result))
return false;
args.rval().setObjectOrNull(result);
return true;
}
@@ -7982,16 +8281,17 @@ DebuggerArguments_getArg(JSContext* cx,
}
/*
* Put the Debugger.Frame into the this-value slot, then use THIS_FRAME
* to check that it is still live and get the fp.
*/
args.setThis(argsobj->as<NativeObject>().getReservedSlot(JSSLOT_DEBUGARGUMENTS_FRAME));
THIS_FRAME(cx, argc, vp, "get argument", ca2, thisobj, frame);
+ FRAME_IS_NOT_WASM(thisobj);
/*
* Since getters can be extracted and applied to other objects,
* there is no guarantee this object has an ith argument.
*/
MOZ_ASSERT(i >= 0);
RootedValue arg(cx);
RootedScript script(cx);
@@ -8157,42 +8457,71 @@ DebuggerFrame_getOnStep(JSContext* cx, u
THIS_FRAME(cx, argc, vp, "get onStep", args, thisobj, frame);
(void) frame; // Silence GCC warning
RootedValue handler(cx, thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER));
MOZ_ASSERT(IsValidHook(handler));
args.rval().set(handler);
return true;
}
+struct DebuggerFrameSetOnStepMatcher
+{
+ JSContext* cx_;
+ RootedNativeObject thisobj_;
+ bool enabled_;
+
+ public:
+ explicit DebuggerFrameSetOnStepMatcher(JSContext* cx, Handle<NativeObject*> thisobj, bool enabled)
+ : cx_(cx), thisobj_(cx, thisobj), enabled_(enabled) {}
+ using ReturnType = bool;
+ ReturnType match(AbstractFramePtr frame) {
+ if (enabled_) {
+ AutoCompartment ac(cx_, frame.environmentChain());
+ // Ensure observability *before* incrementing the step mode
+ // count. Calling this function after calling incrementStepModeCount
+ // will make it a no-op.
+ Debugger* dbg = Debugger::fromChildJSObject(thisobj_);
+ if (!dbg->ensureExecutionObservabilityOfScript(cx_, frame.script()))
+ return false;
+ if (!frame.script()->incrementStepModeCount(cx_))
+ return false;
+ } else {
+ frame.script()->decrementStepModeCount(cx_->runtime()->defaultFreeOp());
+ }
+ return true;
+ }
+ ReturnType match(wasm::DebugFrame* wasmFrame) {
+ wasm::Instance* instance = wasmFrame->instance();
+ return enabled_ ? instance->code().incrementStepModeCount(cx_, wasmFrame->pc()) :
+ instance->code().decrementStepModeCount(cx_, wasmFrame->pc());
+ }
+};
+
static bool
DebuggerFrame_setOnStep(JSContext* cx, unsigned argc, Value* vp)
{
- THIS_FRAME(cx, argc, vp, "set onStep", args, thisobj, frame);
+ THIS_FRAME_REFERENT(cx, argc, vp, "set onStep", args, thisobj, referent);
if (!args.requireAtLeast(cx, "Debugger.Frame.set onStep", 1))
return false;
if (!IsValidHook(args[0])) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_CALLABLE_OR_UNDEFINED);
return false;
}
Value prior = thisobj->getReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER);
if (!args[0].isUndefined() && prior.isUndefined()) {
// Single stepping toggled off->on.
- AutoCompartment ac(cx, frame.environmentChain());
- // Ensure observability *before* incrementing the step mode
- // count. Calling this function after calling incrementStepModeCount
- // will make it a no-op.
- Debugger* dbg = Debugger::fromChildJSObject(thisobj);
- if (!dbg->ensureExecutionObservabilityOfScript(cx, frame.script()))
- return false;
- if (!frame.script()->incrementStepModeCount(cx))
+ DebuggerFrameSetOnStepMatcher matcher(cx, thisobj, true);
+ if (!referent.match(matcher))
return false;
} else if (args[0].isUndefined() && !prior.isUndefined()) {
// Single stepping toggled on->off.
- frame.script()->decrementStepModeCount(cx->runtime()->defaultFreeOp());
+ DebuggerFrameSetOnStepMatcher matcher(cx, thisobj, false);
+ if (!referent.match(matcher))
+ return false;
}
/* Now that the step mode switch has succeeded, we can install the handler. */
thisobj->setReservedSlot(JSSLOT_DEBUGFRAME_ONSTEP_HANDLER, args[0]);
args.rval().setUndefined();
return true;
}
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -21,16 +21,17 @@
#include "builtin/Promise.h"
#include "ds/TraceableFifo.h"
#include "gc/Barrier.h"
#include "js/Debug.h"
#include "js/GCVariant.h"
#include "js/HashTable.h"
#include "vm/GlobalObject.h"
#include "vm/SavedStacks.h"
+#include "wasm/WasmBreakpoint.h"
#include "wasm/WasmJS.h"
enum JSTrapStatus {
JSTRAP_ERROR,
JSTRAP_CONTINUE,
JSTRAP_RETURN,
JSTRAP_THROW,
JSTRAP_LIMIT
@@ -41,16 +42,29 @@ namespace js {
class Breakpoint;
class DebuggerMemory;
class WasmInstanceObject;
typedef HashSet<ReadBarrieredGlobalObject,
MovableCellHasher<ReadBarrieredGlobalObject>,
RuntimeAllocPolicy> WeakGlobalObjectSet;
+namespace jit {
+
+struct AsmJSFrame;
+
+}
+
+namespace wasm {
+
+class Code;
+struct DebugFrame;
+
+}
+
/*
* A weakmap from GC thing keys to JSObject values that supports the keys being
* in different compartments to the values. All values must be in the same
* compartment.
*
* The purpose of this is to allow the garbage collector to easily find edges
* from debuggee object compartments to debugger compartments when calculating
* the compartment groups. Note that these edges are the inverse of the edges
@@ -241,23 +255,29 @@ typedef JSObject Env;
// 2. A wasm JSFunction, denoting a synthesized wasm function script.
// NYI!
typedef mozilla::Variant<JSScript*, WasmInstanceObject*> DebuggerScriptReferent;
// Either a ScriptSourceObject, for ordinary JS, or a WasmInstanceObject,
// denoting the synthesized source of a wasm module.
typedef mozilla::Variant<ScriptSourceObject*, WasmInstanceObject*> DebuggerSourceReferent;
+// Either a AbstractFramePtr, for ordinary JS, or a wasm::DebugFrame,
+// for synthesized frame of a wasm code.
+typedef mozilla::Variant<AbstractFramePtr, wasm::DebugFrame*> DebuggerFrameReferent;
+
class Debugger : private mozilla::LinkedListElement<Debugger>
{
friend class Breakpoint;
friend class DebuggerMemory;
friend class SavedStacks;
friend class mozilla::LinkedListElement<Debugger>;
friend class mozilla::LinkedList<Debugger>;
+ friend class wasm::WasmBreakpoint;
+ friend class wasm::Code;
friend bool (::JS_DefineDebuggerObject)(JSContext* cx, JS::HandleObject obj);
friend bool (::JS::dbg::IsDebugger)(JSObject&);
friend bool (::JS::dbg::GetDebuggeeGlobals)(JSContext*, JSObject&, AutoObjectVector&);
friend void JS::dbg::onNewPromise(JSContext* cx, HandleObject promise);
friend void JS::dbg::onPromiseSettled(JSContext* cx, HandleObject promise);
friend bool JS::dbg::FireOnGarbageCollectionHook(JSContext* cx,
JS::dbg::GarbageCollectionEvent::Ptr&& data);
@@ -371,16 +391,17 @@ class Debugger : private mozilla::Linked
js::GCPtrObject uncaughtExceptionHook; /* Strong reference. */
bool enabled;
bool allowUnobservedAsmJS;
// Whether to enable code coverage on the Debuggee.
bool collectCoverageInfo;
JSCList breakpoints; /* Circular list of all js::Breakpoints in this debugger */
+ JSCList wasmBreakpoints; /* Circular list of all js::wasm::WasmBreakpoints in this debugger */
// The set of GC numbers for which one or more of this Debugger's observed
// debuggees participated in.
using GCNumberSet = HashSet<uint64_t, DefaultHasher<uint64_t>, RuntimeAllocPolicy>;
GCNumberSet observedGCs;
using AllocationsLog = js::TraceableFifo<AllocationsLogEntry>;
@@ -771,16 +792,20 @@ class Debugger : private mozilla::Linked
*/
MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
const FrameIter* maybeIter,
MutableHandleValue vp);
MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
const FrameIter* maybeIter,
MutableHandleDebuggerFrame result);
+ bool hasWasmFrameWithStepMode(JSContext* cx, js::jit::AsmJSFrame* fp, void* pc);
+ bool handleWasmOnLeaveFrame(JSContext* cx, js::jit::AsmJSFrame* fp, void* pc);
+ bool updateObservesWasmEnterFrame(JSContext* cx, bool observes);
+
inline Breakpoint* firstBreakpoint() const;
static inline Debugger* fromOnNewGlobalObjectWatchersLink(JSCList* link);
static MOZ_MUST_USE bool replaceFrameGuts(JSContext* cx, AbstractFramePtr from,
AbstractFramePtr to,
ScriptFrameIter& iter);
@@ -1187,16 +1212,17 @@ class DebuggerFrame : public NativeObjec
static DebuggerFrameImplementation getImplementation(HandleDebuggerFrame frame);
static MOZ_MUST_USE bool eval(JSContext* cx, HandleDebuggerFrame frame,
mozilla::Range<const char16_t> chars, HandleObject bindings,
const EvalOptions& options, JSTrapStatus& status,
MutableHandleValue value);
bool isLive() const;
+ bool isWasm() const;
private:
static const ClassOps classOps_;
static const JSPropertySpec properties_[];
static const JSFunctionSpec methods_[];
static AbstractFramePtr getReferent(HandleDebuggerFrame frame);
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -508,18 +508,17 @@ class BaseCompiler
size_t lastReadCallSite_;
TempAllocator& alloc_;
const ValTypeVector& locals_; // Types of parameters and locals
int32_t localSize_; // Size of local area in bytes (stable after beginFunction)
int32_t varLow_; // Low byte offset of local area for true locals (not parameters)
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 compileDebugInformation_;
- FuncDebugTraps* debugTraps_;
+ FuncDebugInformation* debugInformation_;
ValTypeVector SigI64I64_;
ValTypeVector SigDD_;
ValTypeVector SigD_;
ValTypeVector SigF_;
ValTypeVector SigI_;
ValTypeVector Sig_;
Label returnLabel_;
Label outOfLinePrologue_;
@@ -576,18 +575,17 @@ class BaseCompiler
// More members: see the stk_ and ctl_ vectors, defined below.
public:
BaseCompiler(const ModuleGeneratorData& mg,
Decoder& decoder,
const FuncBytes& func,
const ValTypeVector& locals,
- bool compileDebugInformation,
- FuncDebugTraps* debugTraps,
+ FuncDebugInformation* debugInformation,
FuncCompileResults& compileResults);
MOZ_MUST_USE bool init();
void finish();
MOZ_MUST_USE bool emitFunction();
@@ -2023,17 +2021,17 @@ class BaseCompiler
default:
MOZ_CRASH("Function argument type");
}
}
// 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 (compileDebugInformation_) {
+ if (debugInformation_) {
ScratchI32 scratch(*this);
masm.mov(ImmWord(0), scratch);
size_t debugFrameOffset = localInfo_[tlsSlot_].offs() + DebugFrame::sizeWithoutTlsData();
storeToFramePtr(scratch, debugFrameOffset + DebugFrame::offsetOfPc());
}
// Initialize the stack locals to zero.
//
@@ -2049,20 +2047,21 @@ class BaseCompiler
if (varLow_ < varHigh_) {
ScratchI32 scratch(*this);
masm.mov(ImmWord(0), scratch);
for (int32_t i = varLow_ ; i < varHigh_ ; i+=4)
storeToFrameI32(scratch, i+4);
}
- if (compileDebugInformation_) {
+ if (debugInformation_) {
const bool beginDebugTrapEnabled = false;
- debugTraps_->enterFrameTrap.trapOp = masm.toggledDebugTrap(beginDebugTrapEnabled).offset();
- debugTraps_->enterFrameTrap.returnOp = masm.currentOffset();
+ FuncDebugTraps& debugTraps = debugInformation_->debugTraps();
+ debugTraps.enterFrameTrap.trapOp = masm.toggledDebugTrap(beginDebugTrapEnabled).offset();
+ debugTraps.enterFrameTrap.returnOp = masm.currentOffset();
}
}
bool endFunction() {
// Out-of-line prologue. Assumes that the in-line prologue has
// been executed and that a frame of size = localSize_ + sizeof(Frame)
// has been allocated.
@@ -2083,20 +2082,21 @@ class BaseCompiler
// stack so that, when the trap exit stub executes, it is a safe
// distance away from the end of the native stack.
if (localSize_)
masm.addToStackPtr(Imm32(localSize_));
masm.jump(TrapDesc(prologueTrapOffset_, Trap::StackOverflow, /* framePushed = */ 0));
masm.bind(&returnLabel_);
- if (compileDebugInformation_) {
+ if (debugInformation_) {
const bool endDebugTrapEnabled = false;
- debugTraps_->leaveFrameTrap.trapOp = masm.toggledDebugTrap(endDebugTrapEnabled).offset();
- debugTraps_->leaveFrameTrap.returnOp = masm.currentOffset();
+ FuncDebugTraps& debugTraps = debugInformation_->debugTraps();
+ debugTraps.leaveFrameTrap.trapOp = masm.toggledDebugTrap(endDebugTrapEnabled).offset();
+ debugTraps.leaveFrameTrap.returnOp = masm.currentOffset();
}
// Restore the TLS register in case it was overwritten by the function.
loadFromFramePtr(WasmTlsReg, frameOffsetFromSlot(tlsSlot_, MIRType::Pointer));
GenerateFunctionEpilogue(masm, localSize_, &compileResults_.offsets());
#if defined(JS_ION_PERF)
@@ -2106,18 +2106,19 @@ class BaseCompiler
//gen->perfSpewer().noteEndInlineCode(masm);
#endif
if (!generateOutOfLineCode())
return false;
masm.wasmEmitTrapOutOfLineCode();
- if (compileDebugInformation_) {
- debugTraps_->farJumpOffset = masm.wasmDebugTrapFarJump().offset();
+ if (debugInformation_) {
+ FuncDebugTraps& debugTraps = debugInformation_->debugTraps();
+ debugTraps.farJumpOffset = masm.wasmDebugTrapFarJump().offset();
}
compileResults_.offsets().end = masm.currentOffset();
// A frame greater than 256KB is implausible, probably an attack,
// so fail the compilation.
if (maxFramePushed_ > 256 * 1024)
@@ -3642,16 +3643,18 @@ class BaseCompiler
void endBlock(ExprType type, bool isFunctionBody);
void endLoop(ExprType type);
void endIfThen();
void endIfThenElse(ExprType type);
void doReturn(ExprType returnType);
void pushReturned(const FunctionCall& call, ExprType type);
+ DebugTrapAddress emitDebugTrap();
+
void emitCompareI32(JSOp compareOp, MCompare::CompareType compareType);
void emitCompareI64(JSOp compareOp, MCompare::CompareType compareType);
void emitCompareF32(JSOp compareOp, MCompare::CompareType compareType);
void emitCompareF64(JSOp compareOp, MCompare::CompareType compareType);
void emitAddI32();
void emitAddI64();
void emitAddF64();
@@ -6529,16 +6532,26 @@ BaseCompiler::emitCurrentMemory()
builtinInstanceMethodCall(SymbolicAddress::CurrentMemory, instanceArg, baselineCall);
endCall(baselineCall);
pushReturned(baselineCall, ExprType::I32);
return true;
}
+DebugTrapAddress
+BaseCompiler::emitDebugTrap()
+{
+ bool enabled = false;
+ CodeOffset label = masm.toggledDebugTrap(enabled);
+ Label afterCall;
+ masm.bind(&afterCall); // TODO use ToggledCallSize ?
+ return DebugTrapAddress(label.offset(), afterCall.offset());
+}
+
bool
BaseCompiler::emitBody()
{
uint32_t overhead = 0;
for (;;) {
Nothing unused_a, unused_b;
@@ -6585,19 +6598,28 @@ BaseCompiler::emitBody()
CHECK(stk_.reserve(stk_.length() + overhead * 2));
}
overhead--;
if (done())
return true;
+ uint32_t exprOffset = iter_.currentOffset();
+
Expr expr;
CHECK(iter_.readExpr(&expr));
+ if (debugInformation_ && expr != Expr::End) {
+ DebugTrapAddress trapAddress = emitDebugTrap();
+ // TODO Compression of the DebugTrapInfo
+ if (!debugInformation_->breakpoints().emplaceBack(exprOffset, trapAddress))
+ MOZ_CRASH("cannot emit debug trap");
+ }
+
switch (expr) {
// Control opcodes
case Expr::Nop:
CHECK(iter_.readNop());
NEXT();
case Expr::Drop:
CHECK(iter_.readDrop());
if (!deadCode_)
@@ -7242,32 +7264,30 @@ BaseCompiler::emitFunction()
return true;
}
BaseCompiler::BaseCompiler(const ModuleGeneratorData& mg,
Decoder& decoder,
const FuncBytes& func,
const ValTypeVector& locals,
- bool compileDebugInformation,
- FuncDebugTraps* debugTraps,
+ FuncDebugInformation* debugInformation,
FuncCompileResults& compileResults)
: mg_(mg),
iter_(decoder, func.lineOrBytecode()),
func_(func),
lastReadCallSite_(0),
alloc_(compileResults.alloc()),
locals_(locals),
localSize_(0),
varLow_(0),
varHigh_(0),
maxFramePushed_(0),
deadCode_(false),
- compileDebugInformation_(compileDebugInformation),
- debugTraps_(debugTraps),
+ debugInformation_(debugInformation),
prologueTrapOffset_(trapOffset()),
compileResults_(compileResults),
masm(compileResults_.masm()),
availGPR_(GeneralRegisterSet::All()),
availFPU_(FloatRegisterSet::All()),
#ifdef DEBUG
scratchRegisterTaken_(false),
#endif
@@ -7336,17 +7356,17 @@ BaseCompiler::init()
if (!localInfo_.resize(locals_.length() + 1))
return false;
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 (compileDebugInformation_) {
+ if (debugInformation_) {
localSize_ += DebugFrame::sizeWithoutTlsData();
}
for (ABIArgIter<const ValTypeVector> i(args); !i.done(); i++) {
Local& l = localInfo_[i.index()];
switch (i.mirType()) {
case MIRType::Int32:
if (i->argInRegister())
@@ -7463,18 +7483,17 @@ js::wasm::BaselineCanCompile(const Funct
bool
js::wasm::BaselineCompileFunction(IonCompileTask* task)
{
MOZ_ASSERT(task->mode() == IonCompileTask::CompileMode::Baseline);
const FuncBytes& func = task->func();
FuncCompileResults& results = task->results();
- bool compileDebugInformation = task->compileDebugInformation();
- FuncDebugTraps* maybeDebugTraps = task->maybeDebugTraps();
+ FuncDebugInformation* maybeDebugInformation = task->maybeDebugInformation();
Decoder d(func.bytes());
// Build the local types vector.
ValTypeVector locals;
if (!locals.appendAll(func.sig().args()))
return false;
@@ -7482,17 +7501,17 @@ js::wasm::BaselineCompileFunction(IonCom
return false;
// The MacroAssembler will sometimes access the jitContext.
JitContext jitContext(&results.alloc());
// One-pass baseline compilation.
- BaseCompiler f(task->mg(), d, func, locals, compileDebugInformation, maybeDebugTraps, results);
+ BaseCompiler f(task->mg(), d, func, locals, maybeDebugInformation, results);
if (!f.init())
return false;
if (!f.emitFunction())
return false;
f.finish();
--- a/js/src/wasm/WasmBinaryToExperimentalText.cpp
+++ b/js/src/wasm/WasmBinaryToExperimentalText.cpp
@@ -1847,16 +1847,19 @@ PrintModule(WasmPrintContext& c, AstModu
return false;
if (!PrintCodeSection(c, module.funcs(), module.sigs()))
return false;
if (!PrintDataSection(c, module))
return false;
+ if (c.maybeSourceMap)
+ c.maybeSourceMap->setTotalLines(c.buffer.lineno());
+
return true;
}
/*****************************************************************************/
// Top-level functions
bool
wasm::BinaryToExperimentalText(JSContext* cx, const uint8_t* bytes, size_t length,
new file mode 100644
--- /dev/null
+++ b/js/src/wasm/WasmBreakpoint.cpp
@@ -0,0 +1,120 @@
+/* -*- 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/WasmBreakpoint.h"
+
+#include "vm/Debugger.h"
+#include "wasm/WasmCode.h"
+#include "wasm/WasmInstance.h"
+
+using namespace js;
+using namespace js::wasm;
+
+WasmBreakpoint*
+WasmBreakpointSite::firstBreakpoint() const
+{
+ if (JS_CLIST_IS_EMPTY(&breakpoints))
+ return nullptr;
+ return WasmBreakpoint::fromSiteLinks(JS_NEXT_LINK(&breakpoints));
+}
+
+WasmBreakpointSite::WasmBreakpointSite(WasmInstanceObject* wasmInstance_, Code* code_, uint32_t offset_)
+ : wasmInstance(wasmInstance_), code(code_), offset(offset_), enabledCount(0)
+{
+ JS_INIT_CLIST(&breakpoints);
+}
+
+void
+WasmBreakpointSite::recompile(JSContext* cx)
+{
+ code->toggleDebugTrap(cx, offset, enabledCount > 0);
+}
+
+void
+WasmBreakpointSite::inc(JSContext* cx)
+{
+ enabledCount++;
+ if (enabledCount == 1)
+ recompile(cx);
+}
+
+void
+WasmBreakpointSite::dec(JSContext* cx)
+{
+ MOZ_ASSERT(enabledCount > 0);
+ enabledCount--;
+ if (enabledCount == 0)
+ recompile(cx);
+}
+
+void
+WasmBreakpointSite::destroyIfEmpty(FreeOp* fop)
+{
+ if (JS_CLIST_IS_EMPTY(&breakpoints))
+ code->destroyBreakpointSite(fop, offset);
+}
+
+WasmBreakpoint*
+WasmBreakpoint::firstDebuggerBreakpoint(Debugger* debugger)
+{
+ if (JS_CLIST_IS_EMPTY(&debugger->wasmBreakpoints))
+ return nullptr;
+ return fromDebuggerLinks(JS_NEXT_LINK(&debugger->wasmBreakpoints));
+}
+
+WasmBreakpoint*
+WasmBreakpoint::fromDebuggerLinks(JSCList* links)
+{
+ return (WasmBreakpoint*) ((unsigned char*) links - offsetof(WasmBreakpoint, debuggerLinks));
+}
+
+WasmBreakpoint*
+WasmBreakpoint::fromSiteLinks(JSCList* links)
+{
+ return (WasmBreakpoint*) ((unsigned char*) links - offsetof(WasmBreakpoint, siteLinks));
+}
+
+WasmBreakpoint::WasmBreakpoint(Debugger* debugger_, WasmBreakpointSite* site_, JSObject* handler)
+ : debugger(debugger_), site(site_), handler_(handler)
+{
+ JS_APPEND_LINK(&debuggerLinks, &debugger->wasmBreakpoints);
+ JS_APPEND_LINK(&siteLinks, &site->breakpoints);
+}
+
+void
+WasmBreakpoint::destroy(FreeOp* fop)
+{
+ JS_REMOVE_LINK(&debuggerLinks);
+ JS_REMOVE_LINK(&siteLinks);
+ site->destroyIfEmpty(fop);
+ fop->delete_(this);
+}
+
+WasmBreakpoint*
+WasmBreakpoint::nextInSite()
+{
+ JSCList* link = JS_NEXT_LINK(&siteLinks);
+ return (link == &site->breakpoints) ? nullptr : fromSiteLinks(link);
+}
+
+WasmBreakpoint*
+WasmBreakpoint::nextInDebugger()
+{
+ JSCList* link = JS_NEXT_LINK(&debuggerLinks);
+ return (link == &debugger->wasmBreakpoints) ? nullptr : fromDebuggerLinks(link);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/wasm/WasmBreakpoint.h
@@ -0,0 +1,88 @@
+/* -*- 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 wasm_breakpoint_h
+#define wasm_breakpoint_h
+
+#include "jsclist.h"
+#include "jscntxt.h"
+
+namespace js {
+
+class Debugger;
+
+namespace wasm {
+
+// Breakpoint helper classes.
+
+class Instance;
+class WasmBreakpoint;
+
+class WasmBreakpointSite {
+ friend class WasmBreakpoint;
+
+ public:
+ WasmInstanceObject* wasmInstance;
+ Code* code;
+ uint32_t offset;
+
+ private:
+ JSCList breakpoints;
+ uint32_t enabledCount;
+
+ void recompile(JSContext* cx);
+
+ public:
+ WasmBreakpointSite(WasmInstanceObject* wasmInstance_, Code* code_, uint32_t offset_);
+ WasmBreakpoint* firstBreakpoint() const;
+ void inc(JSContext* cx);
+ void dec(JSContext* cx);
+ void destroyIfEmpty(FreeOp* fop);
+};
+
+class WasmBreakpoint {
+ public:
+ Debugger* const debugger;
+ WasmBreakpointSite* const site;
+
+ private:
+ js::PreBarrieredObject handler_;
+
+ JSCList siteLinks;
+ JSCList debuggerLinks;
+
+ public:
+ static WasmBreakpoint* firstDebuggerBreakpoint(Debugger* debugger);
+ static WasmBreakpoint* fromDebuggerLinks(JSCList* links);
+ static WasmBreakpoint* fromSiteLinks(JSCList* links);
+ WasmBreakpoint(Debugger* debugger_, WasmBreakpointSite* site_, JSObject* handler);
+ void destroy(FreeOp* fop);
+ WasmBreakpoint* nextInSite();
+ WasmBreakpoint* nextInDebugger();
+ const js::PreBarrieredObject& getHandler() const { return handler_; }
+ js::PreBarrieredObject& getHandlerRef() { return handler_; }
+};
+
+typedef Vector<WasmBreakpointSite*, 0, TempAllocPolicy> WasmBreakpointSiteVector;
+typedef UniquePtr<WasmBreakpointSiteVector> UniqueWasmBreakpointSiteVector;
+
+} // namespace wasm
+
+} // namespace js
+
+#endif // namespace wasm_breakpoint_h
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -33,24 +33,27 @@
#include "vm/Debugger-inl.h"
#ifdef MOZ_VTUNE
# include "vtune/VTuneWrapper.h"
#endif
#include "wasm/WasmBinaryToText.h"
#include "wasm/WasmModule.h"
#include "wasm/WasmSerialize.h"
+#include "jsobjinlines.h"
+
#include "jit/MacroAssembler-inl.h"
#include "vm/ArrayBufferObject-inl.h"
using namespace js;
using namespace js::jit;
using namespace js::wasm;
using mozilla::Atomic;
using mozilla::BinarySearch;
+using mozilla::BinarySearchIf;
using mozilla::MakeEnumeratedRange;
using JS::GenericNaN;
// Limit the number of concurrent wasm code allocations per process. Note that
// on Linux, the real maximum is ~32k, as each module requires 2 maps (RW/RX),
// and the kernel's default max_map_count is ~65k.
//
// Note: this can be removed once writable/non-executable global data stops
@@ -583,20 +586,22 @@ Metadata::getFuncName(JSContext* cx, con
return false;
CopyAndInflateChars(name->begin(), chars.get(), name->length());
return true;
}
Code::Code(UniqueCodeSegment segment,
const Metadata& metadata,
- const ShareableBytes* maybeBytecode)
+ const ShareableBytes* maybeBytecode,
+ UniqueWasmBreakpointSiteVector&& breakpointSites)
: segment_(Move(segment)),
metadata_(&metadata),
maybeBytecode_(maybeBytecode),
+ breakpointSites_(Move(breakpointSites)),
instrumentationModeCounter_(0),
profilingEnabled_(false),
debugEnabled_(false),
enterFrameTrapsEnabled_(false)
{}
struct CallSiteRetAddrOffset
{
@@ -739,16 +744,24 @@ Code::createText(JSContext* cx)
#endif
} else {
if (!buffer.append(enabledMessage))
return nullptr;
}
return buffer.finishString();
}
+uint32_t
+Code::totalSourceLines() const
+{
+ if (!maybeSourceMap_)
+ return 0;
+ return maybeSourceMap_->totalLines() + experimentalWarningLinesCount;
+}
+
bool
Code::getLineOffsets(size_t lineno, Vector<uint32_t>& offsets) const
{
// TODO Ensure text was generated?
if (!maybeSourceMap_)
return false;
if (lineno < experimentalWarningLinesCount)
@@ -772,16 +785,199 @@ Code::getLineOffsets(size_t lineno, Vect
if (!offsets.append(exprlocs[i].offset))
return false;
}
return true;
}
bool
+Code::getOffsetLocation(uint32_t offset, size_t* lineno, size_t* column) const
+{
+ // TODO Ensure text was generated?
+ if (!maybeSourceMap_)
+ return false;
+
+ ExprLocVector& exprlocs = maybeSourceMap_->exprlocs();
+ for (size_t i = 0; i < exprlocs.length(); i++) {
+ if (exprlocs[i].offset != offset)
+ continue;
+ *lineno = exprlocs[i].lineno + experimentalWarningLinesCount;
+ *column = exprlocs[i].column;
+ return true;
+ }
+ return false;
+}
+
+bool
+Code::pcToOffset(void* pc, uint32_t* offset) const
+{
+ size_t address = (size_t)(static_cast<uint8_t*>(pc) - segment_->base());
+ const size_t MAX_SIZE_T = (size_t)~0;
+ size_t minDist = MAX_SIZE_T;
+ for (auto p = metadata_->breakpoints.begin(); p != metadata_->breakpoints.end(); p++) {
+ if (p->address.returnOp == address) {
+ *offset = p->offset;
+ return true;
+ }
+ if (p->address.returnOp > address && minDist > p->address.returnOp - address) {
+ minDist = p->address.returnOp - address;
+ *offset = p->offset;
+ }
+ }
+ return minDist < MAX_SIZE_T;
+}
+
+struct SearchDebugTrapInfoByOffset
+{
+ uint32_t offset;
+ explicit SearchDebugTrapInfoByOffset(uint32_t offset_) : offset(offset_) {}
+ int operator() (const DebugTrapInfo& info) const {
+ return offset == info.offset ? 0 : offset < info.offset ? -1 : 1;
+ }
+};
+
+bool
+Code::hasBreakpointOffset(uint32_t offset)
+{
+ size_t index;
+ return BinarySearchIf(metadata_->breakpoints.begin(), 0, metadata_->breakpoints.length(),
+ SearchDebugTrapInfoByOffset(offset), &index);
+}
+
+void
+Code::toggleDebugTrap(JSContext* cx, uint32_t offset, bool enabled)
+{
+ size_t index;
+ if (!BinarySearchIf(metadata_->breakpoints.begin(), 0, metadata_->breakpoints.length(),
+ SearchDebugTrapInfoByOffset(offset), &index))
+ return;
+ size_t address = metadata_->breakpoints[index].address.trapOp;
+
+ const CodeRange* codeRange = lookupRange(segment_->base() + address);
+ MOZ_ASSERT(codeRange && codeRange->isFunction());
+
+ if (stepModeCounters_ && stepModeCounters_->lookup(codeRange->funcIndex()))
+ return; // no need to toggle when step mode is enabled
+
+ AutoWritableJitCode awjc(cx->runtime(), segment_->base(), segment_->codeLength());
+ CodeLocationLabel label(segment_->base() + address);
+ Assembler::ToggleCall(label, enabled);
+}
+
+struct BreakpointSiteWithOffsetComparator
+{
+ int operator()(const WasmBreakpointSite* site) const {
+ return offset == site->offset ? 0 : offset < site->offset ? -1 : 1;
+ }
+ explicit BreakpointSiteWithOffsetComparator(uint32_t offset_) : offset(offset_) {}
+ uint32_t offset;
+};
+
+WasmBreakpointSite*
+Code::getOrCreateBreakpointSite(WasmInstanceObject* object, JSContext* cx, uint32_t offset)
+{
+ WasmBreakpointSite* site;
+ size_t index;
+ if (!BinarySearchIf(*breakpointSites_, 0, breakpointSites_->length(),
+ BreakpointSiteWithOffsetComparator(offset), &index)) {
+ site = cx->runtime()->new_<WasmBreakpointSite>(object, this, offset);
+ if (!site ||
+ !breakpointSites_->insert(breakpointSites_->begin() + index, site)) {
+ ReportOutOfMemory(cx);
+ return nullptr;
+ }
+ } else
+ site = (*breakpointSites_)[index];
+ return site;
+}
+
+void
+Code::destroyBreakpointSite(FreeOp* fop, uint32_t offset)
+{
+ size_t index;
+ DebugOnly<bool> found = BinarySearchIf(*breakpointSites_, 0, breakpointSites_->length(),
+ BreakpointSiteWithOffsetComparator(offset), &index);
+ MOZ_ASSERT(found);
+
+ WasmBreakpointSite* site = (*breakpointSites_)[index];
+ breakpointSites_->erase(breakpointSites_->begin() + index);
+ fop->delete_(site);
+}
+
+bool
+Code::handleWasmBreakpointTrap(JSContext* cx, WasmActivation* activation, MutableHandleValue vp)
+{
+ MOZ_ASSERT(activation->cx() == cx);
+ FrameIter iter(cx);
+ MOZ_ASSERT(iter.isWasm() && iter.activation()->asWasm() == activation);
+ void* pc = iter.pc();
+
+ uint32_t offset = 0;
+ DebugOnly<bool> foundAddress = pcToOffset(pc, &offset);
+ MOZ_ASSERT(foundAddress);
+
+ size_t index;
+ if (!BinarySearchIf(*breakpointSites_, 0, breakpointSites_->length(),
+ BreakpointSiteWithOffsetComparator(offset), &index))
+ return true;
+
+ Vector<WasmBreakpoint*> breakpoints(cx);
+ WasmBreakpointSite* site = (*breakpointSites_)[index];
+ for (WasmBreakpoint* p = site->firstBreakpoint(); p; p = p->nextInSite()) {
+ if (!breakpoints.append(p))
+ return false;
+ }
+
+ for (size_t i = 0; i < breakpoints.length(); i++) {
+ WasmBreakpoint* p = breakpoints[i];
+ Rooted<JSObject*> handler(cx, p->getHandler());
+
+ Debugger* dbg = p->debugger;
+
+ Maybe<AutoCompartment> ac;
+ ac.emplace(cx, dbg->object);
+
+ RootedValue scriptFrame(cx);
+ if (!iter.isWasm() || !iter.ensureHasWasmDebugFrame(cx))
+ return false;
+ AbstractFramePtr referent = iter.abstractFramePtr();
+ if (!dbg->getScriptFrame(cx, referent, &scriptFrame))
+ return dbg->handleUncaughtException(ac);
+ RootedValue rv(cx);
+
+ const char* hitName = "hit";
+ JSAtom* atom = Atomize(cx, hitName, strlen(hitName));
+ if (!atom)
+ return JSTRAP_ERROR;
+ RootedId id(cx, AtomToId(atom));
+ RootedValue fval(cx);
+ if (!GetProperty(cx, handler, handler, id, &fval))
+ return JSTRAP_ERROR;
+
+ if (!IsCallable(fval))
+ return JSTRAP_ERROR;
+
+ InvokeArgs args(cx);
+ if (!args.init(cx, 1))
+ return JSTRAP_ERROR;
+ args[0].set(scriptFrame);
+
+ RootedValue thisArg(cx);
+ thisArg.setObject(*handler);
+
+ bool ok = js::Call(cx, fval, thisArg, args, &rv);
+ if (!ok)
+ return JSTRAP_ERROR;
+ }
+ vp.set(UndefinedValue());
+ return JSTRAP_CONTINUE;
+}
+
+bool
Code::ensureProfilingState(JSContext* cx, bool newProfilingEnabled)
{
if (profilingEnabled_ == newProfilingEnabled)
return true;
// When enabled, generate profiling labels for every name in funcNames_
// that is the name of some Function CodeRange. This involves malloc() so
// do it now since, once we start sampling, we'll be in a signal-handing
@@ -967,16 +1163,99 @@ Code::decrementLeaveFrameTrap(JSContext*
codeRange->end() - codeRange->begin());
CodeLocationLabel debugTrapLabel(segment_->base() + trapAddress);
Assembler::ToggleCall(debugTrapLabel, false);
return true;
}
bool
+Code::incrementStepModeCount(JSContext* cx, uint8_t* pc)
+{
+ MOZ_ASSERT(debugEnabled_);
+ const CodeRange* codeRange = lookupRange(pc);
+ if (!codeRange)
+ return false;
+ MOZ_ASSERT(codeRange->isFunction());
+ uint32_t funcIndex = codeRange->funcIndex();
+
+ if (!stepModeCounters_) {
+ stepModeCounters_.reset(cx->runtime()->new_<StepModeCounters>(cx));
+ if (!stepModeCounters_->init())
+ return false;
+ }
+ StepModeCounters::AddPtr p = stepModeCounters_->lookupForAdd(funcIndex);
+ bool alreadyEnabled = !!p;
+ if (!p) {
+ if (!stepModeCounters_->add(p, funcIndex, 1))
+ return false;
+ } else
+ p->value()++;
+ if (alreadyEnabled)
+ return true;
+
+ AutoWritableJitCode awjc(cx->runtime(), segment_->base() + codeRange->begin(),
+ codeRange->end() - codeRange->begin());
+ AutoFlushICache afc("Code::incrementStepModeCount");
+
+ for (auto p = metadata_->breakpoints.begin(); p != metadata_->breakpoints.end(); p++) {
+ if (codeRange->begin() <= p->address.trapOp && p->address.returnOp <= codeRange->end()) {
+ CodeLocationLabel debugTrapLabel(segment_->base() + p->address.trapOp);
+ Assembler::ToggleCall(debugTrapLabel, true);
+ }
+ }
+ return true;
+}
+
+bool
+Code::decrementStepModeCount(JSContext* cx, uint8_t* pc)
+{
+ MOZ_ASSERT(debugEnabled_);
+ const CodeRange* codeRange = lookupRange(pc);
+ if (!codeRange)
+ return false;
+
+ MOZ_ASSERT(codeRange->isFunction());
+ uint32_t funcIndex = codeRange->funcIndex();
+
+ MOZ_ASSERT(stepModeCounters_ && !stepModeCounters_->empty());
+ StepModeCounters::Ptr p = stepModeCounters_->lookup(funcIndex);
+ MOZ_ASSERT(p);
+ if (--p->value())
+ return true;
+ stepModeCounters_->remove(p);
+
+
+ AutoWritableJitCode awjc(cx->runtime(), segment_->base() + codeRange->begin(),
+ codeRange->end() - codeRange->begin());
+ AutoFlushICache afc("Code::decrementStepModeCount");
+
+ for (auto p = metadata_->breakpoints.begin(); p != metadata_->breakpoints.end(); p++) {
+ if (codeRange->begin() <= p->address.trapOp && p->address.returnOp <= codeRange->end()) {
+ CodeLocationLabel debugTrapLabel(segment_->base() + p->address.trapOp);
+ Assembler::ToggleCall(debugTrapLabel, false);
+ }
+ }
+
+ // TODO optimize turning individual breakpoints on.
+ for (auto site = breakpointSites_->begin(); site != breakpointSites_->end(); site++) {
+ uint32_t offset = (*site)->offset;
+ size_t index;
+ MOZ_ALWAYS_TRUE(BinarySearchIf(metadata_->breakpoints.begin(), 0, metadata_->breakpoints.length(),
+ SearchDebugTrapInfoByOffset(offset), &index));
+ size_t address = metadata_->breakpoints[index].address.trapOp;
+ if (address < codeRange->begin() || address >= codeRange->end())
+ continue;
+ CodeLocationLabel debugTrapLabel(segment_->base() + address);
+ Assembler::ToggleCall(debugTrapLabel, true);
+ }
+ return true;
+}
+
+bool
Code::handleDebugTrap(WasmActivation* activation)
{
MOZ_ASSERT(debugEnabled_);
JSContext* cx = activation->cx();
void* pc = activation->resumePC();
const CodeRange* range = lookupRange(pc);
if (!range)
return false;
@@ -996,18 +1275,26 @@ Code::handleDebugTrap(WasmActivation* ac
if (segment_->base() + debugTraps.leaveFrameTrap.returnOp == pc) {
void* fp = activation->fp();
DebugFrame* frame = activation->lookupDebugFrame(fp);
MOZ_ASSERT(frame);
bool ok = Debugger::onLeaveFrame(cx, frame, (jsbytecode*)pc, true);
activation->removeDebugFrame(fp);
return ok;
}
- // TODO baseline debug traps
- MOZ_CRASH();
+
+ if (stepModeCounters_ && stepModeCounters_->lookup(range->funcIndex())) {
+ RootedValue result(cx, UndefinedValue());
+ Unused << Debugger::onSingleStep(cx, &result);
+ }
+ uint32_t offset;
+ if (pcToOffset(pc, &offset) && hasBreakpointOffset(offset)) {
+ RootedValue result(cx, UndefinedValue());
+ Unused << handleWasmBreakpointTrap(cx, activation, &result);
+ }
return true;
}
void
Code::handleDebugThrowTrap(WasmActivation* activation, void* fp)
{
JSContext* cx = activation->cx();
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -17,22 +17,25 @@
*/
#ifndef wasm_code_h
#define wasm_code_h
#include "jscntxt.h"
#include "js/HashTable.h"
+#include "vm/Debugger.h"
+#include "wasm/WasmBreakpoint.h"
#include "wasm/WasmGeneratedSourceMap.h"
#include "wasm/WasmTypes.h"
namespace js {
struct AsmJSMetadata;
+class Debugger;
class WasmActivation;
namespace wasm {
struct LinkData;
struct Metadata;
// A wasm CodeSegment owns the allocated executable code for a wasm module.
@@ -434,16 +437,21 @@ struct MetadataCacheablePod
explicit MetadataCacheablePod(ModuleKind kind)
: kind(kind),
memoryUsage(MemoryUsage::None),
minMemoryLength(0)
{}
};
+struct DebugTrapsData
+{
+ DebugTrapInfoVector breakpoints;
+};
+
struct Metadata : ShareableBase<Metadata>, MetadataCacheablePod
{
explicit Metadata(ModuleKind kind = ModuleKind::Wasm) : MetadataCacheablePod(kind) {}
virtual ~Metadata() {}
MetadataCacheablePod& pod() { return *this; }
const MetadataCacheablePod& pod() const { return *this; }
@@ -457,16 +465,17 @@ struct Metadata : ShareableBase<Metadata
BoundsCheckVector boundsChecks;
CodeRangeVector codeRanges;
CallSiteVector callSites;
CallThunkVector callThunks;
NameInBytecodeVector funcNames;
CacheableChars filename;
bool debugEnabled;
FuncDebugTrapsVector funcDebugTraps;
+ DebugTrapInfoVector breakpoints;
bool usesMemory() const { return UsesMemory(memoryUsage); }
bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; }
const FuncExport& lookupFuncExport(uint32_t funcIndex) const;
// AsmJSMetadata derives Metadata iff isAsmJS(). Mostly this distinction is
// encapsulated within AsmJS.cpp, but the additional virtual functions allow
@@ -501,34 +510,41 @@ typedef RefPtr<const Metadata> SharedMet
// Code objects own executable code and the metadata that describes it. At the
// moment, Code objects are owned uniquely by instances since CodeSegments are
// not shareable. However, once this restriction is removed, a single Code
// object will be shared between a module and all its instances.
class Code
{
typedef HashMap<uint32_t, uint32_t> LeaveFrameAddressCounters;
+ typedef HashMap<uint32_t, uint32_t> StepModeCounters;
const UniqueCodeSegment segment_;
const SharedMetadata metadata_;
const SharedBytes maybeBytecode_;
UniqueGeneratedSourceMap maybeSourceMap_;
CacheableCharsVector funcLabels_;
+ UniqueWasmBreakpointSiteVector breakpointSites_;
uint32_t instrumentationModeCounter_;
bool profilingEnabled_;
bool debugEnabled_;
bool enterFrameTrapsEnabled_;
// Recording all debuggers requests to react on leave frames.
UniquePtr<LeaveFrameAddressCounters> leaveFrameCounters_;
+ // Recording all requests to turn on/off step mode.
+ UniquePtr<StepModeCounters> stepModeCounters_;
+
+ struct HandleDebugTrapOnStepOp;
public:
Code(UniqueCodeSegment segment,
const Metadata& metadata,
- const ShareableBytes* maybeBytecode);
+ const ShareableBytes* maybeBytecode,
+ UniqueWasmBreakpointSiteVector&& breakpointSites);
CodeSegment& segment() { return *segment_; }
const CodeSegment& segment() const { return *segment_; }
const Metadata& metadata() const { return *metadata_; }
// Frame iterator support:
const CallSite* lookupCallSite(void* returnAddress) const;
@@ -542,41 +558,58 @@ class Code
JSAtom* getFuncAtom(JSContext* cx, uint32_t funcIndex) const;
// If the source bytecode was saved when this Code was constructed, this
// method will render the binary as text. Otherwise, a diagnostic string
// will be returned.
JSString* createText(JSContext* cx);
bool getLineOffsets(size_t lineno, Vector<uint32_t>& offsets) const;
+ bool getOffsetLocation(uint32_t offset, size_t* lineno, size_t* column) const;
+ uint32_t totalSourceLines() const;
// Each Code has a profiling mode that is updated to match the runtime's
// profiling mode when there are no other activations of the code live on
// the stack. Once in profiling mode, ProfilingFrameIterator can be used to
// asynchronously walk the stack. Otherwise, the ProfilingFrameIterator will
// skip any activations of this code.
MOZ_MUST_USE bool ensureProfilingState(JSContext* cx, bool enabled);
bool profilingEnabled() const { return profilingEnabled_; }
const char* profilingLabel(uint32_t funcIndex) const { return funcLabels_[funcIndex].get(); }
// Debug support:
bool debugEnabled() const { return debugEnabled_; }
bool ensureDebugEnabled(JSContext* cx, bool enabled);
+ bool pcToOffset(void* pc, uint32_t* offset) const;
// 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.
bool ensureEnterFrameTrapsState(JSContext* cx, bool enabled);
bool incrementLeaveFrameTrap(JSContext* cx, uint8_t* pc);
bool decrementLeaveFrameTrap(JSContext* cx, uint8_t* pc);
bool handleDebugTrap(WasmActivation* activation);
void handleDebugThrowTrap(WasmActivation* activation, void* fp);
+ // Managing state of an individual breakpoint.
+
+ bool hasBreakpointOffset(uint32_t offset);
+ void toggleDebugTrap(JSContext* cx, uint32_t offset, bool enabled);
+ WasmBreakpointSite* getOrCreateBreakpointSite(WasmInstanceObject* object, JSContext* cx, uint32_t offset);
+ void destroyBreakpointSite(FreeOp* fop, uint32_t offset);
+ bool handleWasmBreakpointTrap(JSContext* cx, WasmActivation* activation, MutableHandleValue vp);
+
+ // Managing state of the step mode.
+
+ bool incrementStepModeCount(JSContext* cx, uint8_t* pc);
+ bool decrementStepModeCount(JSContext* cx, uint8_t* pc);
+
+
// about:memory reporting:
void addSizeOfMisc(MallocSizeOf mallocSizeOf,
Metadata::SeenSet* seenMetadata,
ShareableBytes::SeenSet* seenBytes,
size_t* code,
size_t* data) const;
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -1137,17 +1137,16 @@ DecodeNameSection(Decoder& d, ModuleGene
{
uint32_t sectionStart, sectionSize;
if (!d.startUserDefinedSection(NameSectionName, §ionStart, §ionSize))
return false;
if (sectionStart == Decoder::NotStarted)
return true;
// Once started, user-defined sections do not report validation errors.
-
MaybeDecodeNameSectionBody(d, mg);
d.finishUserDefinedSection(sectionStart, sectionSize);
return true;
}
bool
CompileArgs::initFromContext(ExclusiveContext* cx, ScriptedCaller&& scriptedCaller)
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -18,16 +18,17 @@
#include "wasm/WasmGenerator.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/EnumeratedRange.h"
#include <algorithm>
+#include "ds/Sort.h"
#include "wasm/WasmBaselineCompile.h"
#include "wasm/WasmIonCompile.h"
#include "wasm/WasmStubs.h"
#include "jit/MacroAssembler-inl.h"
using namespace js;
using namespace js::jit;
@@ -370,22 +371,31 @@ ModuleGenerator::finishTask(IonCompileTa
uint32_t offsetInWhole = masm_.size();
results.offsets().offsetBy(offsetInWhole);
// Add the CodeRange for this function.
uint32_t funcCodeRangeIndex = metadata_->codeRanges.length();
if (!metadata_->codeRanges.emplaceBack(func.index(), func.lineOrBytecode(), results.offsets()))
return false;
- if (task->compileDebugInformation()) {
- FuncDebugTraps& debugTraps = *task->maybeDebugTraps();
+ FuncDebugInformation* maybeDebugInformation = task->maybeDebugInformation();
+ if (maybeDebugInformation) {
+ FuncDebugTraps& debugTraps = maybeDebugInformation->debugTraps();
debugTraps.enterFrameTrap.offsetBy(offsetInWhole);
debugTraps.leaveFrameTrap.offsetBy(offsetInWhole);
debugTraps.farJumpOffset += offsetInWhole;
debugTraps_[func.index()] = debugTraps;
+
+ DebugTrapInfoVector& breakpoints = maybeDebugInformation->breakpoints();
+ for (auto p = breakpoints.begin(); p != breakpoints.end(); p++) {
+ p->address.offsetBy(offsetInWhole);
+ p->offset += func.lineOrBytecode();
+ }
+ if (!breakpoints_.append(breakpoints.begin(), breakpoints.end()))
+ return false;
}
MOZ_ASSERT(!funcIsCompiled(func.index()));
funcToCodeRange_[func.index()] = funcCodeRangeIndex;
// Merge the compiled results into the whole-module masm.
mozilla::DebugOnly<size_t> sizeBefore = masm_.size();
if (!masm_.asmMergeWith(results.masm()))
@@ -1105,16 +1115,23 @@ ModuleGenerator::initSigTableElems(uint3
InitExpr offset(Val(uint32_t(0)));
if (!elemSegments_.emplaceBack(tableIndex, offset, Move(elemFuncIndices)))
return false;
elemSegments_.back().elemCodeRangeIndices = Move(codeRangeIndices);
return true;
}
+static bool
+SortComparatorDebugTrapInfoOffset(const DebugTrapInfo& a, const DebugTrapInfo& b, bool* lessOrEqualp)
+{
+ *lessOrEqualp = (a.offset <= b.offset);
+ return true;
+}
+
SharedModule
ModuleGenerator::finish(const ShareableBytes& bytecode)
{
MOZ_ASSERT(!activeFuncDef_);
MOZ_ASSERT(finishedFuncDefs_);
if (!finishFuncExports())
return nullptr;
@@ -1138,16 +1155,23 @@ ModuleGenerator::finish(const ShareableB
{
AutoFlushICache afc("ModuleGenerator::finish", /* inhibit = */ true);
masm_.executableCopy(code.begin());
}
// Zero the padding, since we used resizeUninitialized above.
memset(code.begin() + bytesNeeded, 0, padding);
+ if (!metadata_->breakpoints.append(breakpoints_.begin(), breakpoints_.end()))
+ return nullptr;
+ // Using debugTraps_ as scratch array.
+ MOZ_ALWAYS_TRUE(MergeSort(metadata_->breakpoints.begin(), metadata_->breakpoints.length(),
+ breakpoints_.begin(), SortComparatorDebugTrapInfoOffset));
+ breakpoints_.clear();
+
// Convert the CallSiteAndTargetVector (needed during generation) to a
// CallSiteVector (what is stored in the Module).
if (!metadata_->callSites.appendAll(masm_.callSites()))
return nullptr;
// The MacroAssembler has accumulated all the memory accesses during codegen.
metadata_->memoryAccesses = masm_.extractMemoryAccesses();
metadata_->memoryPatches = masm_.extractMemoryPatches();
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -87,16 +87,17 @@ class MOZ_STACK_CLASS ModuleGenerator
Assumptions assumptions_;
LinkData linkData_;
MutableMetadata metadata_;
ExportVector exports_;
ImportVector imports_;
DataSegmentVector dataSegments_;
ElemSegmentVector elemSegments_;
FuncDebugTrapsVector debugTraps_;
+ DebugTrapInfoVector breakpoints_;
// Data scoped to the ModuleGenerator's lifetime
UniqueModuleGeneratorData shared_;
uint32_t numSigs_;
uint32_t numTables_;
LifoAlloc lifo_;
jit::JitContext jcx_;
jit::TempAllocator masmAlloc_;
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -15,16 +15,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "wasm/WasmInstance.h"
#include "jit/BaselineJIT.h"
#include "jit/JitCommon.h"
+#include "vm/Debugger.h"
#include "wasm/WasmModule.h"
#include "jsobjinlines.h"
#include "vm/ArrayBufferObject-inl.h"
using namespace js;
using namespace js::jit;
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -15,16 +15,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef wasm_instance_h
#define wasm_instance_h
#include "gc/Barrier.h"
+#include "wasm/WasmBreakpoint.h"
#include "wasm/WasmCode.h"
#include "wasm/WasmTable.h"
namespace js {
namespace wasm {
// Instance represents a wasm instance and provides all the support for runtime
// execution of code in the instance. Instances share various immutable data
--- a/js/src/wasm/WasmIonCompile.h
+++ b/js/src/wasm/WasmIonCompile.h
@@ -61,16 +61,19 @@ class FuncBytes
uint32_t index() const { return index_; }
const SigWithId& sig() const { return sig_; }
uint32_t lineOrBytecode() const { return lineOrBytecode_; }
const Uint32Vector& callSiteLineNums() const { return callSiteLineNums_; }
};
typedef UniquePtr<FuncBytes> UniqueFuncBytes;
+struct DebugTrapInfo;
+struct DebugTrapAddress;
+
// The FuncCompileResults class contains the results of compiling a single
// function body, ready to be merged into the whole-module MacroAssembler.
class FuncCompileResults
{
jit::TempAllocator alloc_;
jit::MacroAssembler masm_;
FuncOffsets offsets_;
@@ -84,16 +87,34 @@ class FuncCompileResults
masm_(jit::MacroAssembler::WasmToken(), alloc_)
{}
jit::TempAllocator& alloc() { return alloc_; }
jit::MacroAssembler& masm() { return masm_; }
FuncOffsets& offsets() { return offsets_; }
};
+class FuncDebugInformation
+{
+ UniquePtr<FuncDebugTraps> debugTraps_;
+ UniquePtr<DebugTrapInfoVector> breakpoints_;
+
+ FuncDebugInformation(const FuncDebugInformation&) = delete;
+ FuncDebugInformation& operator=(const FuncDebugInformation&) = delete;
+
+ public:
+ explicit FuncDebugInformation() {
+ debugTraps_ = js::MakeUnique<FuncDebugTraps>();
+ breakpoints_ = js::MakeUnique<DebugTrapInfoVector>();
+ }
+
+ FuncDebugTraps& debugTraps() { return *debugTraps_; }
+ DebugTrapInfoVector& breakpoints() { return *breakpoints_; }
+};
+
// An IonCompileTask represents the task of compiling a single function body. An
// IonCompileTask is filled with the wasm code to be compiled on the main
// validation thread, sent off to an Ion compilation helper thread which creates
// the FuncCompileResults, and finally sent back to the validation thread. To
// save time allocating and freeing memory, IonCompileTasks are reset() and
// reused.
class IonCompileTask
@@ -101,70 +122,64 @@ class IonCompileTask
public:
enum class CompileMode { None, Baseline, Ion };
private:
const ModuleGeneratorData& mg_;
LifoAlloc lifo_;
UniqueFuncBytes func_;
CompileMode mode_;
- bool compileDebugInformation_;
+ Maybe<FuncDebugInformation> debugInformation_;
Maybe<FuncCompileResults> results_;
- Maybe<FuncDebugTraps> debugTraps_;
IonCompileTask(const IonCompileTask&) = delete;
IonCompileTask& operator=(const IonCompileTask&) = delete;
public:
IonCompileTask(const ModuleGeneratorData& mg, size_t defaultChunkSize)
: mg_(mg),
lifo_(defaultChunkSize),
func_(nullptr),
- mode_(CompileMode::None),
- compileDebugInformation_(false)
+ mode_(CompileMode::None)
{}
LifoAlloc& lifo() {
return lifo_;
}
const ModuleGeneratorData& mg() const {
return mg_;
}
void init(UniqueFuncBytes func, CompileMode mode, bool compileDebugInformation) {
MOZ_ASSERT(!func_);
func_ = Move(func);
results_.emplace(lifo_);
mode_ = mode;
- compileDebugInformation_ = compileDebugInformation;
if (compileDebugInformation)
- debugTraps_.emplace();
+ debugInformation_.emplace();
}
CompileMode mode() const {
return mode_;
}
- bool compileDebugInformation() const {
- return compileDebugInformation_;
+ FuncDebugInformation* maybeDebugInformation() {
+ return debugInformation_.ptrOr(nullptr);
}
const FuncBytes& func() const {
MOZ_ASSERT(func_);
return *func_;
}
FuncCompileResults& results() {
return *results_;
}
- FuncDebugTraps* maybeDebugTraps() {
- return debugTraps_.ptrOr(nullptr);
- }
void reset(Bytes* recycled) {
if (func_)
*recycled = Move(func_->bytes());
func_.reset(nullptr);
results_.reset();
lifo_.releaseAll();
mode_ = CompileMode::None;
- debugTraps_.reset();
+ debugInformation_.reset();
}
};
MOZ_MUST_USE bool
IonCompileFunction(IonCompileTask* task);
bool
CompileFunction(IonCompileTask* task);
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -973,21 +973,26 @@ Module::instantiate(JSContext* cx,
// developer actually cares: when the compartment is debuggable (which is
// true when the web console is open) or a names section is present (since
// this going to be stripped for non-developer builds).
const ShareableBytes* maybeBytecode = nullptr;
if (metadata_->debugEnabled || !metadata_->funcNames.empty())
maybeBytecode = bytecode_.get();
+ // Getting references to created breakpoint offsets by the baseline
+ // compiler.
+ UniqueWasmBreakpointSiteVector breakpointSites(
+ cx->runtime()->new_<WasmBreakpointSiteVector>(cx));
+
auto codeSegment = CodeSegment::create(cx, code_, linkData_, *metadata_, memory);
if (!codeSegment)
return false;
- auto code = cx->make_unique<Code>(Move(codeSegment), *metadata_, maybeBytecode);
+ auto code = cx->make_unique<Code>(Move(codeSegment), *metadata_, maybeBytecode, Move(breakpointSites));
if (!code)
return false;
instance.set(WasmInstanceObject::create(cx,
Move(code),
memory,
Move(tables),
funcImports,
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -864,16 +864,29 @@ struct DebugTrapAddress
void offsetBy(size_t offset) {
trapOp += offset;
returnOp += offset;
}
};
WASM_DECLARE_POD_VECTOR(DebugTrapAddress, DebugTrapAddressVector)
+struct DebugTrapInfo
+{
+ uint32_t offset;
+ DebugTrapAddress address;
+
+ DebugTrapInfo() = default;
+ DebugTrapInfo(uint32_t offset_, const DebugTrapAddress& address_) : offset(offset_), address(address_) {}
+ DebugTrapInfo(uint32_t offset_, size_t trapOpAddress_, size_t returnOpAddress_)
+ : offset(offset_), address(DebugTrapAddress(trapOpAddress_, returnOpAddress_)) {}
+};
+
+WASM_DECLARE_POD_VECTOR(DebugTrapInfo, DebugTrapInfoVector)
+
struct FuncDebugTraps
{
static const uint32_t UNDEFINED_JUMP_OFFSET = 0xC0000000;
DebugTrapAddress enterFrameTrap;
DebugTrapAddress leaveFrameTrap;
uint32_t farJumpOffset;