Bug 1286948 - [WIP] wasm step and break mode. draft
authorYury Delendik <ydelendik@mozilla.com>
Tue, 18 Oct 2016 17:47:14 -0500
changeset 434996 c6243fca4f8f98e36d74104cedf4e25fde392b47
parent 434995 9024b1fd4fb3fffba0071f67ac2db518ac831049
child 434997 59badd983dac506af5a684666a420a2feef3542c
push id34906
push userydelendik@mozilla.com
push dateMon, 07 Nov 2016 22:16:36 +0000
bugs1286948
milestone52.0a1
Bug 1286948 - [WIP] wasm step and break mode. MozReview-Commit-ID: DSGYMPRSO0z
js/src/jit-test/tests/debug/wasm-04.js
js/src/moz.build
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/wasm/WasmBaselineCompile.cpp
js/src/wasm/WasmBinaryToExperimentalText.cpp
js/src/wasm/WasmBreakpoint.cpp
js/src/wasm/WasmBreakpoint.h
js/src/wasm/WasmCode.cpp
js/src/wasm/WasmCode.h
js/src/wasm/WasmCompile.cpp
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmGenerator.h
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmInstance.h
js/src/wasm/WasmIonCompile.h
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmTypes.h
--- 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, &sectionStart, &sectionSize))
         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;