Bug 1286948 - [WIP] An attempt to return wasm frames to the debugger API. draft
authorYury Delendik <ydelendik@mozilla.com>
Fri, 16 Sep 2016 13:30:53 -0500
changeset 414584 556b7e75bec87cf2a509b6826ba2f25d7a7b29fc
parent 414583 39cfdeca2d67f3d6a64f2b0bdcddad38af551a06
child 414585 416f3d3a460f3cd96cd40af6e1694b5bce1da914
push id29713
push userydelendik@mozilla.com
push dateFri, 16 Sep 2016 18:54:39 +0000
bugs1286948
milestone51.0a1
Bug 1286948 - [WIP] An attempt to return wasm frames to the debugger API. MozReview-Commit-ID: 8oyFkrINc0A
js/src/asmjs/WasmCode.cpp
js/src/asmjs/WasmCode.h
js/src/asmjs/WasmInstance.cpp
js/src/asmjs/WasmInstance.h
js/src/asmjs/WasmModule.cpp
js/src/vm/CommonPropertyNames.h
js/src/vm/Debugger-inl.h
js/src/vm/Debugger.cpp
js/src/vm/Debugger.h
js/src/vm/Stack-inl.h
--- a/js/src/asmjs/WasmCode.cpp
+++ b/js/src/asmjs/WasmCode.cpp
@@ -27,16 +27,17 @@
 #include "asmjs/WasmModule.h"
 #include "asmjs/WasmSerialize.h"
 #include "jit/ExecutableAllocator.h"
 #include "jit/MacroAssembler.h"
 #ifdef JS_ION_PERF
 # include "jit/PerfSpewer.h"
 #endif
 #include "vm/StringBuffer.h"
+#include "vm/Debugger-inl.h"
 #ifdef MOZ_VTUNE
 # include "vtune/VTuneWrapper.h"
 #endif
 
 #include "jit/MacroAssembler-inl.h"
 #include "vm/ArrayBufferObject-inl.h"
 
 using namespace js;
@@ -969,27 +970,40 @@ Code::decrementLeaveFrameTrap(JSContext*
 
     decrementInstrumentationMode(cx);
     return true;
 }
 
 bool
 Code::handleDebugTrap(WasmActivation* activation)
 {
+    JSContext* cx = activation->cx();
     void* pc = activation->resumePC();
     const CodeRange* range = lookupRange(pc);
     if (!range)
         return false;
     MOZ_ASSERT(range->isFunction());
     if (segment_->base() + range->funcBeginDebugTrapReturn() == pc) {
-        // TODO enter frame
+        FrameIterator iter(*activation);
+        ShadowFrame* frame = activation->getShadowFrame(iter);
+        if (!frame)
+            return false;
+        frame->setIsDebuggee();
+        JSTrapStatus status = Debugger::onEnterFrame(cx, frame);
+        if (status != JSTRAP_CONTINUE)
+            return false;
         return true;
     }
     if (segment_->base() + range->funcEndDebugTrapReturn() == pc) {
-        // TODO leave frame
+        void* fp = activation->fp();
+        ShadowFrame* frame = activation->lookupShadowFrame(fp);
+        MOZ_ASSERT(frame);
+        if (!Debugger::onLeaveFrame(cx, frame, (jsbytecode*)pc, true))
+            return false;
+        activation->removeShadowFrame(fp);
         return true;
     }
     // TODO baseline debug traps
     MOZ_CRASH();
     return true;
 }
 
 void
--- a/js/src/asmjs/WasmCode.h
+++ b/js/src/asmjs/WasmCode.h
@@ -599,16 +599,22 @@ class Code
                        size_t* code,
                        size_t* data) const;
 
     WASM_DECLARE_SERIALIZABLE(Code);
 
 private:
     void incrementInstrumentationMode(JSContext* cx);
     void decrementInstrumentationMode(JSContext* cx);
+
+public:
+    void ensureInstrumentationMode(JSContext* cx) {
+        if (!instrumentationModeCounter_)
+            incrementInstrumentationMode(cx);
+    }
 };
 
 typedef UniquePtr<Code> UniqueCode;
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_code_h
--- a/js/src/asmjs/WasmInstance.cpp
+++ b/js/src/asmjs/WasmInstance.cpp
@@ -577,16 +577,22 @@ Instance::objectUnbarriered() const
 }
 
 WasmInstanceObject*
 Instance::object() const
 {
     return object_;
 }
 
+GlobalObject&
+Instance::objectGlobal() const
+{
+    return object_->global();
+}
+
 bool
 Instance::callExport(JSContext* cx, uint32_t funcDefIndex, CallArgs args)
 {
     // If there has been a moving grow, this Instance should have been notified.
     MOZ_RELEASE_ASSERT(!memory_ || tlsData_.memoryBase == memory_->buffer().dataPointerEither());
 
     if (!cx->compartment()->wasm.ensureProfilingState(cx))
         return false;
--- a/js/src/asmjs/WasmInstance.h
+++ b/js/src/asmjs/WasmInstance.h
@@ -95,16 +95,18 @@ class Instance
     // This method returns a pointer to the GC object that owns this Instance.
     // Instances may be reached via weak edges (e.g., Compartment::instances_)
     // so this perform a read-barrier on the returned object unless the barrier
     // is explicitly waived.
 
     WasmInstanceObject* object() const;
     WasmInstanceObject* objectUnbarriered() const;
 
+    GlobalObject& objectGlobal() const;
+
     // Execute the given export given the JS call arguments, storing the return
     // value in args.rval.
 
     MOZ_MUST_USE bool callExport(JSContext* cx, uint32_t funcDefIndex, CallArgs args);
 
     // Initially, calls to imports in wasm code call out through the generic
     // callImport method. If the imported callee gets JIT compiled and the types
     // match up, callImport will patch the code to instead call through a thunk
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -860,16 +860,21 @@ Module::instantiate(JSContext* cx,
 
     // Perform initialization as the final step after the instance is fully
     // constructed since this can make the instance live to content (even if the
     // start function fails).
 
     if (!initSegments(cx, instance, funcImports, memory, globalImports))
         return false;
 
+    // We need to enable instrumentation mode to start recording of frame
+    // pointers for wasm::FrameIterator.
+    if (maybeBytecode)
+        instance->instance().code().ensureInstrumentationMode(cx);
+
     // Now that the instance is fully live and initialized, the start function.
     // Note that failure may cause instantiation to throw, but the instance may
     // still be live via edges created by initSegments or the start function.
 
     if (metadata_->hasStartFunction()) {
         uint32_t startFuncIndex = metadata_->startFuncIndex();
         FixedInvokeArgs<0> args(cx);
         if (startFuncIndex < funcImports.length()) {
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -318,16 +318,17 @@
     macro(useStrict, useStrict, "use strict") \
     macro(value, value, "value") \
     macro(valueOf, valueOf, "valueOf") \
     macro(values, values, "values") \
     macro(var, var, "var") \
     macro(variable, variable, "variable") \
     macro(void0, void0, "(void 0)") \
     macro(wasm, wasm, "wasm") \
+    macro(wasmcall, wasmcall, "wasmcall") \
     macro(watch, watch, "watch") \
     macro(WeakSet_add, WeakSet_add, "WeakSet_add") \
     macro(weekday, weekday, "weekday") \
     macro(weekendEnd, weekendEnd, "weekendEnd") \
     macro(weekendStart, weekendStart, "weekendStart") \
     macro(writable, writable, "writable") \
     macro(year, year, "year") \
     macro(yield, yield, "yield") \
--- a/js/src/vm/Debugger-inl.h
+++ b/js/src/vm/Debugger-inl.h
@@ -10,19 +10,19 @@
 #include "vm/Debugger.h"
 
 #include "vm/Stack-inl.h"
 
 /* static */ inline bool
 js::Debugger::onLeaveFrame(JSContext* cx, AbstractFramePtr frame, jsbytecode* pc, bool ok)
 {
     MOZ_ASSERT_IF(frame.isInterpreterFrame(), frame.asInterpreterFrame() == cx->interpreterFrame());
-    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    MOZ_ASSERT_IF(!frame.isWasmShadowFrame() && frame.script()->isDebuggee(), frame.isDebuggee());
     /* Traps must be cleared from eval frames, see slowPathOnLeaveFrame. */
-    mozilla::DebugOnly<bool> evalTraps = frame.isEvalFrame() &&
+    mozilla::DebugOnly<bool> evalTraps = frame.isEvalFrame() && !frame.isWasmShadowFrame() &&
                                          frame.script()->hasAnyBreakpointsOrStepMode();
     MOZ_ASSERT_IF(evalTraps, frame.isDebuggee());
     if (frame.isDebuggee())
         ok = slowPathOnLeaveFrame(cx, frame, pc, ok);
     MOZ_ASSERT(!inFrameMaps(frame));
     return ok;
 }
 
@@ -39,17 +39,17 @@ js::Debugger::checkNoExecute(JSContext* 
     if (!cx->compartment()->isDebuggee() || !cx->runtime()->noExecuteDebuggerTop)
         return true;
     return slowPathCheckNoExecute(cx, script);
 }
 
 /* static */ JSTrapStatus
 js::Debugger::onEnterFrame(JSContext* cx, AbstractFramePtr frame)
 {
-    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    MOZ_ASSERT_IF(!frame.isWasmShadowFrame() && frame.script()->isDebuggee(), frame.isDebuggee());
     if (!frame.isDebuggee())
         return JSTRAP_CONTINUE;
     return slowPathOnEnterFrame(cx, frame);
 }
 
 /* static */ JSTrapStatus
 js::Debugger::onDebuggerStatement(JSContext* cx, AbstractFramePtr frame)
 {
@@ -69,17 +69,17 @@ js::Debugger::onExceptionUnwind(JSContex
 /* static */ void
 js::Debugger::onNewWasmInstance(JSContext* cx, Handle<WasmInstanceObject*> wasmInstance)
 {
     if (cx->compartment()->isDebuggee())
         slowPathOnNewWasmInstance(cx, wasmInstance);
 }
 
 inline bool
-js::Debugger::getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
+js::Debugger::getScriptFrame(JSContext* cx, const FrameIter& iter,
                              MutableHandle<DebuggerFrame*> result)
 {
     return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, result);
 }
 
 inline js::Debugger*
 js::DebuggerEnvironment::owner() const
 {
--- a/js/src/vm/Debugger.cpp
+++ b/js/src/vm/Debugger.cpp
@@ -728,35 +728,35 @@ DebuggerMemory&
 Debugger::memory() const
 {
     MOZ_ASSERT(hasMemory());
     return object->getReservedSlot(JSSLOT_DEBUG_MEMORY_INSTANCE).toObject().as<DebuggerMemory>();
 }
 
 bool
 Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
-                                 const ScriptFrameIter* maybeIter, MutableHandleValue vp)
+                                 const FrameIter* maybeIter, MutableHandleValue vp)
 {
     RootedDebuggerFrame result(cx);
     if (!Debugger::getScriptFrameWithIter(cx, referent, maybeIter, &result))
         return false;
 
     vp.setObject(*result);
     return true;
 }
 
 bool
 Debugger::getScriptFrameWithIter(JSContext* cx, AbstractFramePtr referent,
-                                 const ScriptFrameIter* maybeIter,
+                                 const FrameIter* maybeIter,
                                  MutableHandleDebuggerFrame result)
 {
     MOZ_ASSERT_IF(maybeIter, maybeIter->abstractFramePtr() == referent);
-    MOZ_ASSERT(!referent.script()->selfHosted());
-
-    if (!referent.script()->ensureHasAnalyzedArgsUsage(cx))
+    MOZ_ASSERT_IF(!referent.isWasmShadowFrame(), !referent.script()->selfHosted());
+
+    if (!referent.isWasmShadowFrame() && !referent.script()->ensureHasAnalyzedArgsUsage(cx))
         return false;
 
     FrameMap::AddPtr p = frames.lookupForAdd(referent);
     if (!p) {
         /* Create and populate the Debugger.Frame object. */
         RootedObject proto(cx, &object->getReservedSlot(JSSLOT_DEBUG_FRAME_PROTO).toObject());
         RootedNativeObject debugger(cx, object);
 
@@ -2323,17 +2323,17 @@ class MOZ_RAII ExecutionObservableCompar
 
     typedef HashSet<JSCompartment*>::Range CompartmentRange;
     const HashSet<JSCompartment*>* compartments() const { return &compartments_; }
 
     const HashSet<Zone*>* zones() const { return &zones_; }
     bool shouldRecompileOrInvalidate(JSScript* script) const {
         return script->hasBaselineScript() && compartments_.has(script->compartment());
     }
-    bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
+    bool shouldMarkAsDebuggee(FrameIter& iter) const {
         // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
         // iter refers to one such, we know we don't match.
         return iter.hasUsableAbstractFramePtr() && compartments_.has(iter.compartment());
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
@@ -2377,24 +2377,24 @@ class MOZ_RAII ExecutionObservableFrame 
         // Baseline script to recompile.
         //
         // Note that this does not, by design, invalidate *all* inliners of
         // frame_.script(), as only frame_ is made observable, not
         // frame_.script().
         if (!script->hasBaselineScript())
             return false;
 
-        if (script == frame_.script())
+        if (!frame_.isWasmShadowFrame() && script == frame_.script())
             return true;
 
         return frame_.isRematerializedFrame() &&
                script == frame_.asRematerializedFrame()->outerScript();
     }
 
-    bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
+    bool shouldMarkAsDebuggee(FrameIter& iter) const {
         // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
         // iter refers to one such, we know we don't match.
         //
         // We never use this 'has' overload for frame invalidation, only for
         // frame debuggee marking; so this overload doesn't need a parallel to
         // the just-so inlining logic above.
         return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr() == frame_;
     }
@@ -2414,17 +2414,17 @@ class MOZ_RAII ExecutionObservableScript
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 
     Zone* singleZone() const { return script_->compartment()->zone(); }
     JSScript* singleScriptForZoneInvalidation() const { return script_; }
     bool shouldRecompileOrInvalidate(JSScript* script) const {
         return script->hasBaselineScript() && script == script_;
     }
-    bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
+    bool shouldMarkAsDebuggee(FrameIter& iter) const {
         // AbstractFramePtr can't refer to non-remateralized Ion frames, and
         // while a non-rematerialized Ion frame may indeed be running script_,
         // we cannot mark them as debuggees until they bail out.
         //
         // 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
@@ -2445,17 +2445,17 @@ Debugger::updateExecutionObservabilityOf
         jit::JitContext jctx(cx, nullptr);
         if (!jit::RecompileOnStackBaselineScriptsForDebugMode(cx, obs, observing)) {
             ReportOutOfMemory(cx);
             return false;
         }
     }
 
     AbstractFramePtr oldestEnabledFrame;
-    for (ScriptFrameIter iter(cx);
+    for (FrameIter iter(cx);
          !iter.done();
          ++iter)
     {
         if (obs.shouldMarkAsDebuggee(iter)) {
             if (observing) {
                 if (!iter.abstractFramePtr().isDebuggee()) {
                     oldestEnabledFrame = iter.abstractFramePtr();
                     oldestEnabledFrame.setIsDebuggee();
@@ -2563,16 +2563,36 @@ UpdateExecutionObservabilityOfScriptsInZ
     // Iterate through the scripts again and finish discarding
     // BaselineScripts. This must be done as a separate phase as we can only
     // discard the BaselineScript on scripts that have no IonScript.
     for (size_t i = 0; i < scripts.length(); i++) {
         MOZ_ASSERT_IF(scripts[i]->isDebuggee(), observing);
         FinishDiscardBaselineScript(fop, scripts[i]);
     }
 
+    // Iterate through all wasm instances to find which are needed to be updated.
+    // FIXME optimize?
+    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
+        if (c->zone() != zone || c->wasm.instances().length() == 0)
+            continue;
+        auto debuggers = zone->getDebuggers();
+        if (!debuggers)
+            continue;
+        for (wasm::Instance* instance : c->wasm.instances()) {
+            bool enableTrap = false;
+            for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
+                Debugger* dbg = *p;
+                if (dbg->observesEnterFrame())
+                    enableTrap = true;
+            }
+            if (!instance->code().ensureEnterFrameTrapsState(cx, enableTrap))
+                return false;
+        }
+    }
+
     return true;
 }
 
 /* static */ bool
 Debugger::updateExecutionObservabilityOfScripts(JSContext* cx, const ExecutionObservableSet& obs,
                                                 IsObserving observing)
 {
     if (Zone* zone = obs.singleZone())
@@ -2586,17 +2606,17 @@ Debugger::updateExecutionObservabilityOf
 
     return true;
 }
 
 template <typename FrameFn>
 /* static */ void
 Debugger::forEachDebuggerFrame(AbstractFramePtr frame, FrameFn fn)
 {
-    GlobalObject* global = &frame.script()->global();
+    GlobalObject* global = frame.isWasmShadowFrame() ? &frame.wasmInstance()->objectGlobal() : &frame.script()->global();
     if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
         for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
             Debugger* dbg = *p;
             if (FrameMap::Ptr entry = dbg->frames.lookup(frame))
                 fn(entry->value());
         }
     }
 }
@@ -2645,17 +2665,17 @@ Debugger::ensureExecutionObservabilityOf
     }
     ExecutionObservableFrame obs(frame);
     return updateExecutionObservabilityOfFrames(cx, obs, Observing);
 }
 
 /* static */ bool
 Debugger::ensureExecutionObservabilityOfFrame(JSContext* cx, AbstractFramePtr frame)
 {
-    MOZ_ASSERT_IF(frame.script()->isDebuggee(), frame.isDebuggee());
+    MOZ_ASSERT_IF(!frame.isWasmShadowFrame() && frame.script()->isDebuggee(), frame.isDebuggee());
     if (frame.isDebuggee())
         return true;
     ExecutionObservableFrame obs(frame);
     return updateExecutionObservabilityOfFrames(cx, obs, Observing);
 }
 
 /* static */ bool
 Debugger::ensureExecutionObservabilityOfCompartment(JSContext* cx, JSCompartment* comp)
@@ -2751,17 +2771,17 @@ Debugger::updateObservesCoverageOnDebugg
         // dangling pointers to freed PCCounts.
         if (!obs.add(comp))
             return false;
     }
 
     // If any frame on the stack belongs to the debuggee, then we cannot update
     // the ScriptCounts, because this would imply to invalidate a Debugger.Frame
     // to recompile it with/without ScriptCount support.
-    for (ScriptFrameIter iter(cx);
+    for (FrameIter iter(cx);
          !iter.done();
          ++iter)
     {
         if (obs.shouldMarkAsDebuggee(iter)) {
             JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE);
             return false;
         }
     }
@@ -3720,25 +3740,27 @@ Debugger::getDebuggees(JSContext* cx, un
     return true;
 }
 
 /* static */ bool
 Debugger::getNewestFrame(JSContext* cx, unsigned argc, Value* vp)
 {
     THIS_DEBUGGER(cx, argc, vp, "getNewestFrame", args, dbg);
 
-    /* Since there may be multiple contexts, use AllScriptFramesIter. */
-    for (AllScriptFramesIter i(cx); !i.done(); ++i) {
+    /* Since there may be multiple contexts, use AllFramesIter. */
+    for (AllFramesIter i(cx); !i.done(); ++i) {
         if (dbg->observesFrame(i)) {
             // Ensure that Ion frames are rematerialized. Only rematerialized
             // Ion frames may be used as AbstractFramePtrs.
             if (i.isIon() && !i.ensureHasRematerializedFrame(cx))
                 return false;
+            if (i.isWasm() && !i.ensureHasWasmShadowFrame(cx))
+                return false;
             AbstractFramePtr frame = i.abstractFramePtr();
-            ScriptFrameIter iter(i.activation()->cx());
+            FrameIter iter(i.activation()->cx());
             while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != frame)
                 ++iter;
             return dbg->getScriptFrame(cx, iter, args.rval());
         }
     }
     args.rval().setNull();
     return true;
 }
@@ -3992,17 +4014,17 @@ Debugger::removeDebuggeeGlobal(FreeOp* f
      * have live Frame objects. So we take the easy way out and kill them here.
      * This is a bug, since it's observable and contrary to the spec. One
      * possible fix would be to put such objects into a compartment-wide bag
      * which slowPathOnLeaveFrame would have to examine.
      */
     for (FrameMap::Enum e(frames); !e.empty(); e.popFront()) {
         AbstractFramePtr frame = e.front().key();
         NativeObject* frameobj = e.front().value();
-        if (&frame.script()->global() == global) {
+        if ((frame.isWasmShadowFrame() ? &frame.wasmInstance()->objectGlobal() : &frame.script()->global()) == global) {
             DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
             DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
             e.removeFront();
         }
     }
 
     auto *globalDebuggersVector = global->getDebuggers();
     auto *zoneDebuggersVector = global->zone()->getDebuggers();
@@ -7213,17 +7235,17 @@ DebuggerFrame::initClass(JSContext* cx, 
     RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
 
     return InitClass(cx, dbgCtor, objProto, &class_, construct, 0, properties_,
                      methods_, nullptr, nullptr);
 }
 
 /* static */ DebuggerFrame*
 DebuggerFrame::create(JSContext* cx, HandleObject proto, AbstractFramePtr referent,
-                      const ScriptFrameIter* maybeIter, HandleNativeObject debugger)
+                      const FrameIter* maybeIter, HandleNativeObject debugger)
 {
   JSObject* obj = NewObjectWithGivenProto(cx, &DebuggerFrame::class_, proto);
   if (!obj)
     return nullptr;
 
   DebuggerFrame& frame = obj->as<DebuggerFrame>();
 
   // Eagerly copy ScriptFrameIter data if we've already walked the stack.
@@ -7313,20 +7335,20 @@ UpdateFrameIterPc(FrameIter& iter)
 /* static */ bool
 DebuggerFrame::getEnvironment(JSContext* cx, HandleDebuggerFrame frame,
                               MutableHandleDebuggerEnvironment result)
 {
     MOZ_ASSERT(frame->isLive());
 
     Debugger* dbg = frame->owner();
 
-    Maybe<ScriptFrameIter> maybeIter;
-    if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
-        return false;
-    ScriptFrameIter& iter = *maybeIter;
+    Maybe<FrameIter> maybeIter;
+    if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+        return false;
+    FrameIter& iter = *maybeIter;
 
     Rooted<Env*> env(cx);
     {
         AutoCompartment ac(cx, iter.abstractFramePtr().environmentChain());
         UpdateFrameIterPc(iter);
         env = GetDebugEnvironmentForFrame(cx, iter.abstractFramePtr(), iter.pc());
         if (!env)
             return false;
@@ -7361,25 +7383,27 @@ DebuggerFrame::getOffset(JSContext* cx, 
 /* static */ bool
 DebuggerFrame::getOlder(JSContext* cx, HandleDebuggerFrame frame,
                         MutableHandleDebuggerFrame result)
 {
     MOZ_ASSERT(frame->isLive());
 
     Debugger* dbg = frame->owner();
 
-    Maybe<ScriptFrameIter> maybeIter;
-    if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
-        return false;
-    ScriptFrameIter& iter = *maybeIter;
+    Maybe<FrameIter> maybeIter;
+    if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+        return false;
+    FrameIter& iter = *maybeIter;
 
     for (++iter; !iter.done(); ++iter) {
         if (dbg->observesFrame(iter)) {
             if (iter.isIon() && !iter.ensureHasRematerializedFrame(cx))
                 return false;
+            if (iter.isWasm() && !iter.ensureHasWasmShadowFrame(cx))
+                return false;
             return dbg->getScriptFrame(cx, iter, result);
         }
     }
 
     result.set(nullptr);
     return true;
 }
 
@@ -7420,16 +7444,18 @@ DebuggerFrame::getType(HandleDebuggerFra
     if (referent.isEvalFrame())
         return DebuggerFrameType::Eval;
     else if (referent.isGlobalFrame())
         return DebuggerFrameType::Global;
     else if (referent.isFunctionFrame())
         return DebuggerFrameType::Call;
     else if (referent.isModuleFrame())
         return DebuggerFrameType::Module;
+    else if (referent.isWasmShadowFrame())
+        return DebuggerFrameType::WasmCall;
     MOZ_CRASH("Unknown frame type");
 }
 
 /* static */ DebuggerFrameImplementation
 DebuggerFrame::getImplementation(HandleDebuggerFrame frame)
 {
     AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
 
@@ -7626,17 +7652,17 @@ DebuggerFrame::requireLive(JSContext* cx
     return true;
 }
 
 /* static */ AbstractFramePtr
 DebuggerFrame::getReferent(HandleDebuggerFrame frame)
 {
     AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate());
     if (referent.isScriptFrameIterData()) {
-        ScriptFrameIter iter(*(ScriptFrameIter::Data*)(referent.raw()));
+        FrameIter iter(*(FrameIter::Data*)(referent.raw()));
         referent = iter.abstractFramePtr();
     }
     return referent;
 }
 
 /* static */ bool
 DebuggerFrame::getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
                                   Maybe<ScriptFrameIter>& result)
@@ -7652,16 +7678,36 @@ DebuggerFrame::getScriptFrameIter(JSCont
         AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();
         if (!data)
             return false;
         frame->setPrivate(data.raw());
     }
     return true;
 }
 
+/* static */ bool
+DebuggerFrame::getFrameIter(JSContext* cx, HandleDebuggerFrame frame,
+                                  Maybe<FrameIter>& result)
+{
+    AbstractFramePtr referent = AbstractFramePtr::FromRaw(frame->getPrivate());
+    if (referent.isScriptFrameIterData()) {
+        result.emplace(*reinterpret_cast<ScriptFrameIter::Data*>(referent.raw()));
+    } else {
+        result.emplace(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
+        FrameIter& iter = *result;
+        while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != referent)
+            ++iter;
+        AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();
+        if (!data)
+            return false;
+        frame->setPrivate(data.raw());
+    }
+    return true;
+}
+
 static void
 DebuggerFrame_freeScriptFrameIterData(FreeOp* fop, JSObject* obj)
 {
     AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->as<NativeObject>().getPrivate());
     if (frame.isScriptFrameIterData())
         fop->delete_((ScriptFrameIter::Data*) frame.raw());
     obj->as<NativeObject>().setPrivate(nullptr);
 }
@@ -7797,16 +7843,19 @@ DebuggerFrame::typeGetter(JSContext* cx,
         str = cx->names().global;
         break;
       case DebuggerFrameType::Call:
         str = cx->names().call;
         break;
       case DebuggerFrameType::Module:
         str = cx->names().module;
         break;
+      case DebuggerFrameType::WasmCall:
+        str = cx->names().wasmcall;
+        break;
       default:
         MOZ_CRASH("bad DebuggerFrameType value");
     }
 
     args.rval().setString(str);
     return true;
 }
 
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -293,17 +293,17 @@ class Debugger : private mozilla::Linked
       public:
         typedef HashSet<Zone*>::Range ZoneRange;
 
         virtual Zone* singleZone() const { return nullptr; }
         virtual JSScript* singleScriptForZoneInvalidation() const { return nullptr; }
         virtual const HashSet<Zone*>* zones() const { return nullptr; }
 
         virtual bool shouldRecompileOrInvalidate(JSScript* script) const = 0;
-        virtual bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const = 0;
+        virtual bool shouldMarkAsDebuggee(FrameIter& iter) const = 0;
     };
 
     // This enum is converted to and compare with bool values; NotObserving
     // must be 0 and Observing must be 1.
     enum IsObserving {
         NotObserving = 0,
         Observing = 1
     };
@@ -763,20 +763,20 @@ class Debugger : private mozilla::Linked
     void fireOnGarbageCollectionHook(JSContext* cx,
                                      const JS::dbg::GarbageCollectionEvent::Ptr& gcData);
 
     /*
      * Gets a Debugger.Frame object. If maybeIter is non-null, we eagerly copy
      * its data if we need to make a new Debugger.Frame.
      */
     MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
-                                             const ScriptFrameIter* maybeIter,
+                                             const FrameIter* maybeIter,
                                              MutableHandleValue vp);
     MOZ_MUST_USE bool getScriptFrameWithIter(JSContext* cx, AbstractFramePtr frame,
-                                             const ScriptFrameIter* maybeIter,
+                                             const FrameIter* maybeIter,
                                              MutableHandleDebuggerFrame result);
 
     inline Breakpoint* firstBreakpoint() const;
 
     static inline Debugger* fromOnNewGlobalObjectWatchersLink(JSCList* link);
 
     static MOZ_MUST_USE bool replaceFrameGuts(JSContext* cx, AbstractFramePtr from,
                                               AbstractFramePtr to,
@@ -990,21 +990,21 @@ class Debugger : private mozilla::Linked
     /*
      * Store the Debugger.Frame object for iter in *vp/result. Eagerly copies a
      * ScriptFrameIter::Data.
      *
      * Use this if you had to make a ScriptFrameIter to get the required
      * frame, in which case the cost of walking the stack has already been
      * paid.
      */
-    MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
+    MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const FrameIter& iter,
                                      MutableHandleValue vp) {
         return getScriptFrameWithIter(cx, iter.abstractFramePtr(), &iter, vp);
     }
-    MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const ScriptFrameIter& iter,
+    MOZ_MUST_USE bool getScriptFrame(JSContext* cx, const FrameIter& iter,
                                      MutableHandleDebuggerFrame result);
 
 
     /*
      * Set |*status| and |*value| to a (JSTrapStatus, Value) pair reflecting a
      * standard SpiderMonkey call state: a boolean success value |ok|, a return
      * value |rv|, and a context |cx| that may or may not have an exception set.
      * If an exception was pending on |cx|, it is cleared (and |ok| is asserted
@@ -1141,17 +1141,18 @@ class DebuggerEnvironment : public Nativ
     static MOZ_MUST_USE bool getVariableMethod(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool setVariableMethod(JSContext* cx, unsigned argc, Value* vp);
 };
 
 enum class DebuggerFrameType {
     Eval,
     Global,
     Call,
-    Module
+    Module,
+    WasmCall
 };
 
 enum class DebuggerFrameImplementation {
     Interpreter,
     Baseline,
     Ion
 };
 
@@ -1163,17 +1164,17 @@ class DebuggerFrame : public NativeObjec
     };
 
     static const unsigned RESERVED_SLOTS = 1;
 
     static const Class class_;
 
     static NativeObject* initClass(JSContext* cx, HandleObject dbgCtor, HandleObject objProto);
     static DebuggerFrame* create(JSContext* cx, HandleObject proto, AbstractFramePtr referent,
-                                 const ScriptFrameIter* maybeIter, HandleNativeObject debugger);
+                                 const FrameIter* maybeIter, HandleNativeObject debugger);
     static DebuggerFrame* checkThis(JSContext* cx, const CallArgs& args, const char* fnname,
                                     bool checkLive);
 
     static MOZ_MUST_USE bool getCallee(JSContext* cx, HandleDebuggerFrame frame,
                                        MutableHandleDebuggerObject result);
     static MOZ_MUST_USE bool getIsConstructing(JSContext* cx, HandleDebuggerFrame frame,
                                                bool& result);
     static MOZ_MUST_USE bool getEnvironment(JSContext* cx, HandleDebuggerFrame frame,
@@ -1197,16 +1198,18 @@ class DebuggerFrame : public NativeObjec
   private:
     static const ClassOps classOps_;
 
     static const JSPropertySpec properties_[];
     static const JSFunctionSpec methods_[];
 
     static MOZ_MUST_USE bool requireLive(JSContext* cx, HandleDebuggerFrame frame);
     static AbstractFramePtr getReferent(HandleDebuggerFrame frame);
+    static MOZ_MUST_USE bool getFrameIter(JSContext* cx, HandleDebuggerFrame frame,
+                                          mozilla::Maybe<FrameIter>& result);
     static MOZ_MUST_USE bool getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
                                                 mozilla::Maybe<ScriptFrameIter>& result);
 
     static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
 
     static MOZ_MUST_USE bool calleeGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool constructingGetter(JSContext* cx, unsigned argc, Value* vp);
     static MOZ_MUST_USE bool environmentGetter(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -416,30 +416,35 @@ FrameIter::unaliasedForEachActual(JSCont
     MOZ_CRASH("Unexpected state");
 }
 
 inline HandleValue
 AbstractFramePtr::returnValue() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->returnValue();
+    if (isWasmShadowFrame())
+        return UndefinedHandleValue;
     return asBaselineFrame()->returnValue();
 }
 
 inline void
 AbstractFramePtr::setReturnValue(const Value& rval) const
 {
     if (isInterpreterFrame()) {
         asInterpreterFrame()->setReturnValue(rval);
         return;
     }
     if (isBaselineFrame()) {
         asBaselineFrame()->setReturnValue(rval);
         return;
     }
+    if (isWasmShadowFrame()) {
+        return;
+    }
     asRematerializedFrame()->setReturnValue(rval);
 }
 
 inline JSObject*
 AbstractFramePtr::environmentChain() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->environmentChain();
@@ -868,16 +873,18 @@ AbstractFramePtr::newTarget() const
     if (isBaselineFrame())
         return asBaselineFrame()->newTarget();
     return asRematerializedFrame()->newTarget();
 }
 
 inline bool
 AbstractFramePtr::debuggerNeedsCheckPrimitiveReturn() const
 {
+    if (isWasmShadowFrame())
+        return false;
     return script()->isDerivedClassConstructor();
 }
 
 ActivationEntryMonitor::~ActivationEntryMonitor()
 {
     if (entryMonitor_)
         entryMonitor_->Exit(cx_);