Bug 1286948 - onEnterFrame/onLeaveFrame wasm events and callstack. r?shu draft
authorYury Delendik <ydelendik@mozilla.com>
Sat, 07 Jan 2017 10:40:38 -0600
changeset 457376 88990f91be93e8e6b2ccd8deb2b51d37b350e37d
parent 457375 c5ef138fd59f59bc0186c1056b661a27e6a04d6e
child 541444 40ab67e0c569624d46907c4517aeca9aca40835c
push id40734
push userydelendik@mozilla.com
push dateSat, 07 Jan 2017 16:43:50 +0000
reviewersshu
bugs1286948
milestone53.0a1
Bug 1286948 - onEnterFrame/onLeaveFrame wasm events and callstack. r?shu Handles onEnterFrame and onLeaveFrame trap handling. The FrameIter is used in the DebuggerFrame instead of ScriptFrameIter. The debug wasm frame is created and can be found on callstack during those event. MozReview-Commit-ID: 8oyFkrINc0A
js/src/doc/Debugger/Debugger.Frame.md
js/src/jit-test/tests/debug/wasm-06-onEnterFrame-null.js
js/src/jit-test/tests/debug/wasm-06-onPop-null.js
js/src/jit-test/tests/debug/wasm-06.js
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
js/src/wasm/WasmTypes.cpp
--- a/js/src/doc/Debugger/Debugger.Frame.md
+++ b/js/src/doc/Debugger/Debugger.Frame.md
@@ -106,31 +106,36 @@ its prototype:
 
     * `"eval"`: a frame running code passed to `eval`.
 
     * `"global"`: a frame running global code (JavaScript that is neither of
       the above).
 
     * `"module"`: a frame running code at the top level of a module.
 
+    * `"wasmcall"`: a frame running a WebAssembly function call.
+
     * `"debugger"`: a frame for a call to user code invoked by the debugger
       (see the `eval` method below).
 
 `implementation`
 :   A string describing which tier of the JavaScript engine this frame is
     executing in:
 
     * `"interpreter"`: a frame running in the interpreter.
 
     * `"baseline"`: a frame running in the unoptimizing, baseline JIT.
 
     * `"ion"`: a frame running in the optimizing JIT.
 
+    * `"wasm"`: a frame running in WebAssembly baseline JIT.
+
 `this`
-:   The value of `this` for this frame (a debuggee value).
+:   The value of `this` for this frame (a debuggee value). For a `wasmcall`
+    frame, this property throws a `TypeError`.
 
 `older`
 :   The next-older visible frame, in which control will resume when this
     frame completes. If there is no older frame, this is `null`.
 
 `depth`
 :   The depth of this frame, counting from oldest to youngest; the oldest
     frame has a depth of zero.
@@ -144,16 +149,17 @@ its prototype:
 :   The script being executed in this frame (a [`Debugger.Script`][script]
     instance), or `null` on frames that do not represent calls to debuggee
     code. On frames whose `callee` property is not null, this is equal to
     `callee.script`.
 
 `offset`
 :   The offset of the bytecode instruction currently being executed in
     `script`, or `undefined` if the frame's `script` property is `null`.
+    For a `wasmcall` frame, this property throws a `TypeError`.
 
 `environment`
 :   The lexical environment within which evaluation is taking place (a
     [`Debugger.Environment`][environment] instance), or `null` on frames
     that do not represent the evaluation of debuggee code, like calls
     non-debuggee functions, host functions or `"debugger"` frames.
 
 `callee`
@@ -263,19 +269,19 @@ the compartment to which the handler met
 
 The functions described below may only be called with a `this` value
 referring to a `Debugger.Frame` instance; they may not be used as
 methods of other kinds of objects.
 
 <code id="eval">eval(<i>code</i>, [<i>options</i>])</code>
 :   Evaluate <i>code</i> in the execution context of this frame, and return
     a [completion value][cv] describing how it completed. <i>Code</i> is a
-    string. If this frame's `environment` property is `null`, throw a
-    `TypeError`. All extant handler methods, breakpoints, and
-    so on remain active during the call. This function follows the
+    string. If this frame's `environment` property is `null` or `type` property
+    is `wasmcall`, throw a `TypeError`. All extant handler methods, breakpoints,
+    and so on remain active during the call. This function follows the
     [invocation function conventions][inv fr].
 
     <i>Code</i> is interpreted as strict mode code when it contains a Use
     Strict Directive, or the code executing in this frame is strict mode
     code.
 
     If <i>code</i> is not strict mode code, then variable declarations in
     <i>code</i> affect the environment of this frame. (In the terms used by
@@ -321,8 +327,10 @@ methods of other kinds of objects.
     of the execution context that this frame represents, and the
     <i>bindings</i> appear in a new declarative environment, which is the
     eval code's `LexicalEnvironment`.) If implementation restrictions
     prevent SpiderMonkey from extending this frame's environment as
     requested, this call throws an `Error` exception.
 
     The <i>options</i> argument is as for
     [`Debugger.Frame.prototype.eval`][fr eval], described above.
+    Also like `eval`, if this frame's `environment` property is `null` or
+    `type` property is `wasmcall`, throw a `TypeError`.
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-06-onEnterFrame-null.js
@@ -0,0 +1,20 @@
+// |jit-test| test-also-wasm-baseline; exitstatus: 3
+// Checking resumption values for 'null' at onEnterFrame.
+
+load(libdir + "asserts.js");
+
+if (!wasmIsSupported())
+     quit(3);
+
+var g = newGlobal('');
+var dbg = new Debugger();
+dbg.addDebuggee(g);
+sandbox.eval(`
+var wasm = wasmTextToBinary('(module (func (nop)) (export "test" 0))');
+var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));`);
+dbg.onEnterFrame = function (frame) {
+    if (frame.type !== "wasmcall") return;
+    return null;
+};
+g.eval("m.exports.test()");
+assertEq(false, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-06-onPop-null.js
@@ -0,0 +1,22 @@
+// |jit-test| test-also-wasm-baseline; exitstatus: 3
+// Checking resumption values for 'null' at frame's onPop.
+
+load(libdir + "asserts.js");
+
+if (!wasmIsSupported())
+     quit(3);
+
+var g = newGlobal('');
+var dbg = new Debugger();
+dbg.addDebuggee(g);
+sandbox.eval(`
+var wasm = wasmTextToBinary('(module (func (nop)) (export "test" 0))');
+var m = new WebAssembly.Instance(new WebAssembly.Module(wasm));`);
+dbg.onEnterFrame = function (frame) {
+    if (frame.type !== "wasmcall") return;
+    frame.onPop = function () {
+        return null;
+    };
+};
+g.eval("m.exports.test()");
+assertEq(false, true);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-06.js
@@ -0,0 +1,335 @@
+// |jit-test| test-also-wasm-baseline; error: TestComplete
+// Tests that wasm module scripts raises onEnterFrame and onLeaveFrame events.
+
+load(libdir + "asserts.js");
+
+if (!wasmIsSupported())
+     throw "TestComplete";
+
+function runWasmWithDebugger(wast, lib, init, done) {
+    let g = newGlobal('');
+    let dbg = new Debugger(g);
+
+    g.eval(`
+var wasm = wasmTextToBinary('${wast}');
+var lib = ${lib || 'undefined'};
+var m = new WebAssembly.Instance(new WebAssembly.Module(wasm), lib);`);
+
+    init(dbg, g);
+    let result = undefined, error = undefined;
+    try {
+        result = g.eval("m.exports.test()");
+    } catch (ex) {
+        error = ex;
+    }
+    done(dbg, result, error, g);
+}
+
+// Checking if onEnterFrame is fired for wasm frames and verifying the content
+// of the frame and environment properties.
+var onEnterFrameCalled, onLeaveFrameCalled, onExceptionUnwindCalled, testComplete;
+runWasmWithDebugger(
+    '(module (func (result i32) (i32.const 42)) (export "test" 0))', undefined,
+    function (dbg) {
+        var wasmScript = dbg.findScripts().filter(s => s.format == 'wasm')[0];
+        assertEq(!!wasmScript, true);
+        onEnterFrameCalled = 0;
+        onLeaveFrameCalled = 0;
+        testComplete = false;
+        var evalFrame;
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== 'wasmcall') {
+                if (frame.type === 'eval')
+                    evalFrame = frame;
+                return;
+            }
+
+            onEnterFrameCalled++;
+
+            assertEq(frame.script, wasmScript);
+            assertEq(frame.older, evalFrame);
+            assertEq(frame.type, 'wasmcall');
+
+            let env = frame.environment;
+            assertEq(env instanceof Object, true);
+            assertEq(env.inspectable, true);
+            assertEq(env.parent !== null, true);
+            assertEq(env.type, 'declarative');
+            assertEq(env.callee, null);
+            assertEq(Array.isArray(env.names()), true);
+            assertEq(env.names().length, 0);
+
+            frame.onPop = function() {
+                onLeaveFrameCalled++;
+                testComplete = true;
+            };
+       };
+    },
+    function (dbg, result, error) {
+        assertEq(testComplete, true);
+        assertEq(onEnterFrameCalled, 1);
+        assertEq(onLeaveFrameCalled, 1);
+        assertEq(result, 42);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking the dbg.getNewestFrame() and frame.older.
+runWasmWithDebugger(
+    '(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
+    '{env: { ex: () => { }}}',
+    function (dbg) {
+        onEnterFrameCalled = 0;
+        onLeaveFrameCalled = 0;
+        testComplete = false;
+        var evalFrame, wasmFrame;
+        dbg.onEnterFrame = function (frame) {
+            onEnterFrameCalled++;
+
+            assertEq(dbg.getNewestFrame(), frame);
+
+            switch (frame.type) {
+              case 'eval':
+                evalFrame = frame;
+                break;
+              case 'wasmcall':
+                wasmFrame = frame;
+                break;
+              case 'call':
+                assertEq(frame.older, wasmFrame);
+                assertEq(frame.older.older, evalFrame);
+                assertEq(frame.older.older.older, null);
+                testComplete = true;
+                break;
+            }
+
+            frame.onPop = function() {
+                onLeaveFrameCalled++;
+            };
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(testComplete, true);
+        assertEq(onEnterFrameCalled, 3);
+        assertEq(onLeaveFrameCalled, 3);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking if we can enumerate frames and find 'wasmcall' one.
+runWasmWithDebugger(
+    '(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
+    '{env: { ex: () => { debugger; }}}',
+    function (dbg) {
+        testComplete = false;
+        dbg.onDebuggerStatement = function (frame) {
+            assertEq(frame.type, 'call');
+            assertEq(frame.older.type, 'wasmcall');
+            assertEq(frame.older.older.type, 'eval');
+            assertEq(frame.older.older.older, null);
+            testComplete = true;
+        }
+    },
+    function (dbg, result, error) {
+        assertEq(testComplete, true);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking if onPop works without onEnterFrame handler.
+runWasmWithDebugger(
+    '(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
+    '{env: { ex: () => { debugger; }}}',
+    function (dbg) {
+        onLeaveFrameCalled = 0;
+        dbg.onDebuggerStatement = function (frame) {
+            if (!frame.older || frame.older.type != 'wasmcall')
+                return;
+            frame.older.onPop = function () {
+                onLeaveFrameCalled++;
+            };
+        }
+    },
+    function (dbg, result, error) {
+        assertEq(onLeaveFrameCalled, 1);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking if function return values are not changed.
+runWasmWithDebugger(
+    '(module (func (result f64) (f64.const 0.42)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.onEnterFrame = function (frame) {
+            dbg.onPop = function () {};
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(result, 0.42);
+        assertEq(error, undefined);
+    }
+);
+runWasmWithDebugger(
+    '(module (func (result f32) (f32.const 4.25)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.onEnterFrame = function (frame) {
+            dbg.onPop = function () {};
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(result, 4.25);
+        assertEq(error, undefined);
+    }
+);
+
+// Checking if onEnterFrame/onExceptionUnwind work during exceptions --
+// `unreachable` causes wasm to throw WebAssembly.RuntimeError exception.
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+       onEnterFrameCalled = 0;
+       onLeaveFrameCalled = 0;
+       onExceptionUnwindCalled = 0;
+       dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            onEnterFrameCalled++;
+            frame.onPop = function() {
+                onLeaveFrameCalled++;
+            };
+       };
+       dbg.onExceptionUnwind = function (frame) {
+         if (frame.type !== "wasmcall") return;
+         onExceptionUnwindCalled++;
+       };
+    },
+    function (dbg, result, error, g) {
+        assertEq(onEnterFrameCalled, 1);
+        assertEq(onLeaveFrameCalled, 1);
+        assertEq(onExceptionUnwindCalled, 1);
+        assertEq(error instanceof g.WebAssembly.RuntimeError, true);
+    }
+);
+
+// Checking if onEnterFrame/onExceptionUnwind work during exceptions
+// originated in the JavaScript import call.
+runWasmWithDebugger(
+    '(module (import $fn1 "env" "ex") (func $fn2 (call $fn1)) (export "test" $fn2))',
+    '{env: { ex: () => { throw new Error(); }}}',
+    function (dbg) {
+       onEnterFrameCalled = 0;
+       onLeaveFrameCalled = 0;
+       onExceptionUnwindCalled = 0;
+       dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            onEnterFrameCalled++;
+            frame.onPop = function() {
+                onLeaveFrameCalled++;
+            };
+       };
+       dbg.onExceptionUnwind = function (frame) {
+         if (frame.type !== "wasmcall") return;
+         onExceptionUnwindCalled++;
+       };
+    },
+    function (dbg, result, error, g) {
+        assertEq(onEnterFrameCalled, 1);
+        assertEq(onLeaveFrameCalled, 1);
+        assertEq(onExceptionUnwindCalled, 1);
+        assertEq(error instanceof g.Error, true);
+    }
+);
+
+// Checking throwing in the handler.
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.uncaughtExceptionHook = function (value) {
+            assertEq(value instanceof Error, true);
+            return {throw: 'test'};
+        };
+        dbg.onEnterFrame = function (frame) {
+           if (frame.type !== "wasmcall") return;
+           throw new Error();
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(error, 'test');
+    }
+);
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.uncaughtExceptionHook = function (value) {
+            assertEq(value instanceof Error, true);
+            return {throw: 'test'};
+        };
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            frame.onPop = function () {
+                throw new Error();
+            }
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(error, 'test');
+    }
+);
+
+// Checking resumption values for JS_THROW.
+runWasmWithDebugger(
+    '(module (func (nop)) (export "test" 0))', undefined,
+    function (dbg, g) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            return {throw: 'test'};
+        };
+    },
+    function (dbg, result, error, g) {
+        assertEq(error, 'test');
+    }
+);
+runWasmWithDebugger(
+    '(module (func (nop)) (export "test" 0))', undefined,
+    function (dbg, g) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            frame.onPop = function () {
+                return {throw: 'test'};
+            }
+        };
+    },
+    function (dbg, result, error, g) {
+        assertEq(error, 'test');
+    }
+);
+
+// Checking resumption values for JS_RETURN (not implemented by wasm baseline).
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            return {return: 2};
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(result, undefined, 'NYI: result == 2, if JS_RETURN is implemented');
+        assertEq(error != undefined, true, 'NYI: error == undefined, if JS_RETURN is implemented');
+    }
+);
+runWasmWithDebugger(
+    '(module (func (unreachable)) (export "test" 0))', undefined,
+    function (dbg) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type !== "wasmcall") return;
+            frame.onPop = function () {
+                return {return: 2};
+            }
+        };
+    },
+    function (dbg, result, error) {
+        assertEq(result, undefined, 'NYI: result == 2, if JS_RETURN is implemented');
+        assertEq(error != undefined, true, 'NYI: error == undefined, if JS_RETURN is implemented');
+    }
+);
+throw "TestComplete";
--- a/js/src/vm/CommonPropertyNames.h
+++ b/js/src/vm/CommonPropertyNames.h
@@ -339,16 +339,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(WeakMapConstructorInit, WeakMapConstructorInit, "WeakMapConstructorInit") \
     macro(WeakSetConstructorInit, WeakSetConstructorInit, "WeakSetConstructorInit") \
     macro(WeakSet_add, WeakSet_add, "WeakSet_add") \
     macro(weekday, weekday, "weekday") \
     macro(weekendEnd, weekendEnd, "weekendEnd") \
     macro(weekendStart, weekendStart, "weekendStart") \
     macro(writable, writable, "writable") \
--- a/js/src/vm/Debugger-inl.h
+++ b/js/src/vm/Debugger-inl.h
@@ -10,17 +10,17 @@
 #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.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
     /* Traps must be cleared from eval frames, see slowPathOnLeaveFrame. */
     mozilla::DebugOnly<bool> evalTraps = frame.isEvalFrame() &&
                                          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.hasScript() && 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
@@ -734,35 +734,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.hasScript(), !referent.script()->selfHosted());
+
+    if (referent.hasScript() && !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);
 
@@ -1010,17 +1010,17 @@ Debugger::slowPathOnDebuggerStatement(JS
 Debugger::slowPathOnExceptionUnwind(JSContext* cx, AbstractFramePtr frame)
 {
     // Invoking more JS on an over-recursed stack or after OOM is only going
     // to result in more of the same error.
     if (cx->isThrowingOverRecursed() || cx->isThrowingOutOfMemory())
         return JSTRAP_CONTINUE;
 
     // The Debugger API mustn't muck with frames from self-hosted scripts.
-    if (frame.script()->selfHosted())
+    if (frame.hasScript() && frame.script()->selfHosted())
         return JSTRAP_CONTINUE;
 
     RootedValue rval(cx);
     JSTrapStatus status = dispatchHook(
         cx,
         [](Debugger* dbg) -> bool { return dbg->getHook(OnExceptionUnwind); },
         [&](Debugger* dbg) -> JSTrapStatus {
             return dbg->fireExceptionUnwind(cx, &rval);
@@ -1766,17 +1766,17 @@ Debugger::fireExceptionUnwind(JSContext*
     cx->clearPendingException();
 
     Maybe<AutoCompartment> ac;
     ac.emplace(cx, object);
 
     RootedValue scriptFrame(cx);
     RootedValue wrappedExc(cx, exc);
 
-    ScriptFrameIter iter(cx);
+    FrameIter iter(cx);
     if (!getScriptFrame(cx, iter, &scriptFrame) || !wrapDebuggeeValue(cx, &wrappedExc))
         return reportUncaughtException(ac);
 
     RootedValue fval(cx, ObjectValue(*hook));
     RootedValue rv(cx);
     bool ok = js::Call(cx, fval, object, scriptFrame, wrappedExc, &rv);
     JSTrapStatus st = processHandlerResult(ac, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
     if (st == JSTRAP_CONTINUE)
@@ -1791,17 +1791,17 @@ Debugger::fireEnterFrame(JSContext* cx, 
     MOZ_ASSERT(hook);
     MOZ_ASSERT(hook->isCallable());
 
     Maybe<AutoCompartment> ac;
     ac.emplace(cx, object);
 
     RootedValue scriptFrame(cx);
 
-    ScriptFrameIter iter(cx);
+    FrameIter iter(cx);
     if (!getScriptFrame(cx, iter, &scriptFrame))
         return reportUncaughtException(ac);
 
     RootedValue fval(cx, ObjectValue(*hook));
     RootedValue rv(cx);
     bool ok = js::Call(cx, fval, object, scriptFrame, &rv);
 
     return processHandlerResult(ac, ok, rv, iter.abstractFramePtr(), iter.pc(), vp);
@@ -2343,19 +2343,20 @@ 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 {
-        // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
-        // iter refers to one such, we know we don't match.
+    bool shouldMarkAsDebuggee(FrameIter& iter) const {
+        // AbstractFramePtr can't refer to non-remateralized Ion frames or
+        // non-debuggee wasm frames, so if iter refers to one such, we know we
+        // don't match.
         return iter.hasUsableAbstractFramePtr() && compartments_.has(iter.compartment());
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 // Given a particular AbstractFramePtr F that has become observable, this
 // represents the stack frames that need to be bailed out or marked as
@@ -2397,26 +2398,27 @@ 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_.hasScript() && script == frame_.script())
             return true;
 
         return frame_.isRematerializedFrame() &&
                script == frame_.asRematerializedFrame()->outerScript();
     }
 
-    bool shouldMarkAsDebuggee(ScriptFrameIter& iter) const {
-        // AbstractFramePtr can't refer to non-remateralized Ion frames, so if
-        // iter refers to one such, we know we don't match.
+    bool shouldMarkAsDebuggee(FrameIter& iter) const {
+        // AbstractFramePtr can't refer to non-remateralized Ion frames or
+        // non-debuggee wasm frames, so if iter refers to one such, we know we
+        // don't match.
         //
         // We never use this 'has' overload for frame invalidation, only for
         // frame debuggee marking; so this overload doesn't need a parallel to
         // the just-so inlining logic above.
         return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr() == frame_;
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
@@ -2434,26 +2436,29 @@ 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
         // have rematerialized any Ion frames.
+        //
+        // Also AbstractFramePtr can't refer to non-debuggee wasm frames, so if
+        // iter refers to one such, we know we don't match.
         return iter.hasUsableAbstractFramePtr() && iter.abstractFramePtr().script() == script_;
     }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 /* static */ bool
 Debugger::updateExecutionObservabilityOfFrames(JSContext* cx, const ExecutionObservableSet& obs,
@@ -2465,26 +2470,28 @@ 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();
                 }
+                if (iter.abstractFramePtr().isWasmDebugFrame())
+                    iter.abstractFramePtr().asWasmDebugFrame()->observeFrame(cx);
             } else {
 #ifdef DEBUG
                 // Debugger.Frame lifetimes are managed by the debug epilogue,
                 // so in general it's unsafe to unmark a frame if it has a
                 // Debugger.Frame associated with it.
                 MOZ_ASSERT(!inFrameMaps(iter.abstractFramePtr()));
 #endif
                 iter.abstractFramePtr().unsetIsDebuggee();
@@ -2583,16 +2590,27 @@ 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 ones that need to be updated.
+    for (JSCompartment* c : zone->compartments) {
+        for (wasm::Instance* instance : c->wasm.instances()) {
+            if (!instance->debugEnabled())
+                continue;
+
+            bool enableTrap = observing == Debugger::IsObserving::Observing;
+            instance->ensureEnterFrameTrapsState(cx, enableTrap);
+        }
+    }
+
     return true;
 }
 
 /* static */ bool
 Debugger::updateExecutionObservabilityOfScripts(JSContext* cx, const ExecutionObservableSet& obs,
                                                 IsObserving observing)
 {
     if (Zone* zone = obs.singleZone())
@@ -2606,17 +2624,17 @@ Debugger::updateExecutionObservabilityOf
 
     return true;
 }
 
 template <typename FrameFn>
 /* static */ void
 Debugger::forEachDebuggerFrame(AbstractFramePtr frame, FrameFn fn)
 {
-    GlobalObject* global = &frame.script()->global();
+    GlobalObject* global = frame.global();
     if (GlobalObject::DebuggerVector* debuggers = global->getDebuggers()) {
         for (auto p = debuggers->begin(); p != debuggers->end(); p++) {
             Debugger* dbg = *p;
             if (FrameMap::Ptr entry = dbg->frames.lookup(frame))
                 fn(entry->value());
         }
     }
 }
@@ -2665,17 +2683,18 @@ 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.hasScript() && frame.script()->isDebuggee(), frame.isDebuggee());
+    MOZ_ASSERT_IF(frame.isWasmDebugFrame(), frame.wasmInstance()->debugEnabled());
     if (frame.isDebuggee())
         return true;
     ExecutionObservableFrame obs(frame);
     return updateExecutionObservabilityOfFrames(cx, obs, Observing);
 }
 
 /* static */ bool
 Debugger::ensureExecutionObservabilityOfCompartment(JSContext* cx, JSCompartment* comp)
@@ -2771,17 +2790,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_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_DEBUG_NOT_IDLE);
             return false;
         }
     }
@@ -3740,25 +3759,25 @@ 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;
             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;
 }
@@ -4011,17 +4030,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.global() == global) {
             DebuggerFrame_freeScriptFrameIterData(fop, frameobj);
             DebuggerFrame_maybeDecrementFrameScriptStepModeCount(fop, frame, frameobj);
             e.removeFront();
         }
     }
 
     auto *globalDebuggersVector = global->getDebuggers();
     auto *zoneDebuggersVector = global->zone()->getDebuggers();
@@ -6291,19 +6310,19 @@ Debugger::observesScript(JSScript* scrip
     // Don't ever observe self-hosted scripts: the Debugger API can break
     // self-hosted invariants.
     return observesGlobal(&script->global()) && !script->selfHosted();
 }
 
 bool
 Debugger::observesWasm(wasm::Instance* instance) const
 {
-    if (!enabled || !instance->code().metadata().debugEnabled)
-        return false;
-    return false; // TODO check global
+    if (!enabled || !instance->debugEnabled())
+        return false;
+    return observesGlobal(&instance->object()->global());
 }
 
 /* static */ bool
 Debugger::replaceFrameGuts(JSContext* cx, AbstractFramePtr from, AbstractFramePtr to,
                            ScriptFrameIter& iter)
 {
     auto removeFromDebuggerFramesOnExit = MakeScopeExit([&] {
         // Remove any remaining old entries on exit, as the 'from' frame will
@@ -7325,25 +7344,25 @@ 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.
+  // Eagerly copy FrameIter data if we've already walked the stack.
   if (maybeIter) {
       AbstractFramePtr data = maybeIter->copyDataAsAbstractFramePtr();
       if (!data)
           return nullptr;
       frame.setPrivate(data.raw());
   } else {
       frame.setPrivate(referent.raw());
   }
@@ -7371,20 +7390,20 @@ DebuggerFrame::getCallee(JSContext* cx, 
     return dbg->wrapDebuggeeObject(cx, callee, result);
 }
 
 /* static */ bool
 DebuggerFrame::getIsConstructing(JSContext* cx, HandleDebuggerFrame frame, bool& result)
 {
     MOZ_ASSERT(frame->isLive());
 
-    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;
 
     result = iter.isFunctionFrame() && iter.isConstructing();
     return true;
 }
 
 static void
 UpdateFrameIterPc(FrameIter& iter)
 {
@@ -7430,68 +7449,71 @@ 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;
     }
 
     return dbg->wrapEnvironment(cx, env, result);
 }
 
 /* static */ bool
 DebuggerFrame::getIsGenerator(HandleDebuggerFrame frame)
 {
-    return DebuggerFrame::getReferent(frame).script()->isGenerator();
+    AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+    return referent.hasScript() && referent.script()->isGenerator();
 }
 
 /* static */ bool
 DebuggerFrame::getOffset(JSContext* cx, HandleDebuggerFrame frame, size_t& result)
 {
     MOZ_ASSERT(frame->isLive());
-
-    Maybe<ScriptFrameIter> maybeIter;
-    if (!DebuggerFrame::getScriptFrameIter(cx, frame, maybeIter))
-        return false;
-    ScriptFrameIter& iter = *maybeIter;
+    if (!requireScriptReferent(cx, frame))
+        return false;
+
+    Maybe<FrameIter> maybeIter;
+    if (!DebuggerFrame::getFrameIter(cx, frame, maybeIter))
+        return false;
+    FrameIter& iter = *maybeIter;
 
     JSScript* script = iter.script();
     UpdateFrameIterPc(iter);
     jsbytecode* pc = iter.pc();
     result = script->pcToOffset(pc);
     return true;
 }
 
 /* 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;
             return dbg->getScriptFrame(cx, iter, result);
         }
     }
@@ -7499,23 +7521,25 @@ DebuggerFrame::getOlder(JSContext* cx, H
     result.set(nullptr);
     return true;
 }
 
 /* static */ bool
 DebuggerFrame::getThis(JSContext* cx, HandleDebuggerFrame frame, MutableHandleValue result)
 {
     MOZ_ASSERT(frame->isLive());
+    if (!requireScriptReferent(cx, frame))
+        return false;
 
     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;
 
     {
         AbstractFramePtr frame = iter.abstractFramePtr();
         AutoCompartment ac(cx, frame.environmentChain());
 
         UpdateFrameIterPc(iter);
 
         if (!GetThisValueForDebuggerMaybeOptimizedOut(cx, frame, iter.pc(), result))
@@ -7537,42 +7561,50 @@ 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.isWasmDebugFrame())
+        return DebuggerFrameType::WasmCall;
     MOZ_CRASH("Unknown frame type");
 }
 
 /* static */ DebuggerFrameImplementation
 DebuggerFrame::getImplementation(HandleDebuggerFrame frame)
 {
     AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
 
     if (referent.isBaselineFrame())
         return DebuggerFrameImplementation::Baseline;
     else if (referent.isRematerializedFrame())
         return DebuggerFrameImplementation::Ion;
+    else if (referent.isWasmDebugFrame())
+        return DebuggerFrameImplementation::Wasm;
     return DebuggerFrameImplementation::Interpreter;
 }
 
 /*
  * If succesful, transfers the ownership of the given `handler` to this
  * Debugger.Frame. Note that on failure, the ownership of `handler` is not
  * transferred, and the caller is responsible for cleaning it up.
  */
 /* static */ bool
 DebuggerFrame::setOnStepHandler(JSContext* cx, HandleDebuggerFrame frame, OnStepHandler* handler)
 {
     MOZ_ASSERT(frame->isLive());
 
     AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+    if (referent.isWasmDebugFrame()) {
+        MOZ_CRASH();
+        return true;
+    }
 
     OnStepHandler* prior = frame->onStepHandler();
     if (prior && handler != prior) {
         prior->drop();
     }
 
     if (handler && !prior) {
         // Single stepping toggled off->on.
@@ -7683,17 +7715,17 @@ EvaluateInEnv(JSContext* cx, Handle<Env*
 
     return ExecuteKernel(cx, script, *env, NullValue(), frame, rval.address());
 }
 
 static bool
 DebuggerGenericEval(JSContext* cx, const mozilla::Range<const char16_t> chars,
                     HandleObject bindings, const EvalOptions& options,
                     JSTrapStatus& status, MutableHandleValue value,
-                    Debugger* dbg, HandleObject envArg, ScriptFrameIter* iter)
+                    Debugger* dbg, HandleObject envArg, FrameIter* iter)
 {
     /* Either we're specifying the frame, or a global. */
     MOZ_ASSERT_IF(iter, !envArg);
     MOZ_ASSERT_IF(!iter, envArg && IsGlobalLexicalEnvironment(envArg));
 
     /*
      * Gather keys and values of bindings, if any. This must be done in the
      * debugger compartment, since that is where any exceptions must be
@@ -7774,23 +7806,25 @@ DebuggerGenericEval(JSContext* cx, const
 }
 
 /* static */ bool
 DebuggerFrame::eval(JSContext* cx, HandleDebuggerFrame frame, mozilla::Range<const char16_t> chars,
                     HandleObject bindings, const EvalOptions& options, JSTrapStatus& status,
                     MutableHandleValue value)
 {
     MOZ_ASSERT(frame->isLive());
+    if (!requireScriptReferent(cx, frame))
+        return false;
 
     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;
 
     UpdateFrameIterPc(iter);
 
     return DebuggerGenericEval(cx, chars, bindings, options, status, value, dbg, nullptr, &iter);
 }
 
 /* statuc */ bool
 DebuggerFrame::isLive() const
@@ -7837,48 +7871,62 @@ 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)
+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()));
+        result.emplace(*reinterpret_cast<FrameIter::Data*>(referent.raw()));
     } else {
-        result.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
-        ScriptFrameIter& iter = *result;
+        result.emplace(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);
+        FrameIter& iter = *result;
         while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != referent)
             ++iter;
         AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();
         if (!data)
             return false;
         frame->setPrivate(data.raw());
     }
     return true;
 }
 
+/* static */ bool
+DebuggerFrame::requireScriptReferent(JSContext* cx, HandleDebuggerFrame frame)
+{
+    AbstractFramePtr referent = DebuggerFrame::getReferent(frame);
+    if (!referent.hasScript()) {
+        RootedValue frameobj(cx, ObjectValue(*frame));
+        ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_DEBUG_BAD_REFERENT,
+                              JSDVG_SEARCH_STACK, frameobj, nullptr,
+                              "a script frame", nullptr);
+        return false;
+    }
+    return true;
+}
+
 static void
 DebuggerFrame_freeScriptFrameIterData(FreeOp* fop, JSObject* obj)
 {
     AbstractFramePtr frame = AbstractFramePtr::FromRaw(obj->as<NativeObject>().getPrivate());
     if (frame.isScriptFrameIterData())
-        fop->delete_((ScriptFrameIter::Data*) frame.raw());
+        fop->delete_((FrameIter::Data*) frame.raw());
     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. */
@@ -7943,29 +7991,29 @@ DebuggerFrame_checkThis(JSContext* cx, c
             return nullptr;
     }
 
     return frame;
 }
 
 /*
  * To make frequently fired hooks like onEnterFrame more performant,
- * Debugger.Frame methods should not create a ScriptFrameIter unless it
+ * Debugger.Frame methods should not create a FrameIter unless it
  * absolutely needs to. That is, unless the method has to call a method on
- * ScriptFrameIter that's otherwise not available on AbstractFramePtr.
+ * FrameIter that's otherwise not available on AbstractFramePtr.
  *
  * When a Debugger.Frame is first created, its private slot is set to the
  * AbstractFramePtr itself. The first time the users asks for a
- * ScriptFrameIter, we construct one, have it settle on the frame pointed to
+ * FrameIter, we construct one, have it settle on the frame pointed to
  * by the AbstractFramePtr and cache its internal Data in the Debugger.Frame
  * object's private slot. Subsequent uses of the Debugger.Frame object will
- * always create a ScriptFrameIter from the cached Data.
+ * always create a FrameIter from the cached Data.
  *
  * Methods that only need the AbstractFramePtr should use THIS_FRAME.
- * Methods that need a ScriptFrameIterator should use THIS_FRAME_ITER.
+ * Methods that need a FrameIterator should use THIS_FRAME_ITER.
  */
 
 #define THIS_DEBUGGER_FRAME(cx, argc, vp, fnname, args, frame)                          \
     CallArgs args = CallArgsFromVp(argc, vp);                                           \
     RootedDebuggerFrame frame(cx, DebuggerFrame_checkThis(cx, args, fnname, true));     \
     if (!frame)                                                                         \
         return false;
 
@@ -7974,39 +8022,39 @@ DebuggerFrame_checkThis(JSContext* cx, c
     RootedNativeObject thisobj(cx, DebuggerFrame_checkThis(cx, args, fnname, true));  \
     if (!thisobj)                                                                     \
         return false
 
 #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()) {                                       \
-        ScriptFrameIter iter(*(ScriptFrameIter::Data*)(frame.raw()));          \
+        FrameIter iter(*(FrameIter::Data*)(frame.raw()));                      \
         frame = iter.abstractFramePtr();                                       \
     }
 
 #define THIS_FRAME_ITER(cx, argc, vp, fnname, args, thisobj, maybeIter, iter)  \
     THIS_FRAME_THISOBJ(cx, argc, vp, fnname, args, thisobj);                   \
-    Maybe<ScriptFrameIter> maybeIter;                                          \
+    Maybe<FrameIter> maybeIter;                                                \
     {                                                                          \
         AbstractFramePtr f = AbstractFramePtr::FromRaw(thisobj->getPrivate()); \
         if (f.isScriptFrameIterData()) {                                       \
-            maybeIter.emplace(*(ScriptFrameIter::Data*)(f.raw()));             \
+            maybeIter.emplace(*(FrameIter::Data*)(f.raw()));                   \
         } else {                                                               \
-            maybeIter.emplace(cx, ScriptFrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK); \
-            ScriptFrameIter& iter = *maybeIter;                                \
+            maybeIter.emplace(cx, FrameIter::IGNORE_DEBUGGER_EVAL_PREV_LINK);  \
+            FrameIter& iter = *maybeIter;                                      \
             while (!iter.hasUsableAbstractFramePtr() || iter.abstractFramePtr() != f) \
                 ++iter;                                                        \
             AbstractFramePtr data = iter.copyDataAsAbstractFramePtr();         \
             if (!data)                                                         \
                 return false;                                                  \
             thisobj->setPrivate(data.raw());                                   \
         }                                                                      \
     }                                                                          \
-    ScriptFrameIter& iter = *maybeIter
+    FrameIter& iter = *maybeIter
 
 #define THIS_FRAME_OWNER(cx, argc, vp, fnname, args, thisobj, frame, dbg)      \
     THIS_FRAME(cx, argc, vp, fnname, args, thisobj, frame);                    \
     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)
@@ -8027,16 +8075,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;
 }
 
@@ -8053,16 +8104,19 @@ DebuggerFrame::implementationGetter(JSCo
         s = "baseline";
         break;
       case DebuggerFrameImplementation::Ion:
         s = "ion";
         break;
       case DebuggerFrameImplementation::Interpreter:
         s = "interpreter";
         break;
+      case DebuggerFrameImplementation::Wasm:
+        s = "wasm";
+        break;
       default:
         MOZ_CRASH("bad DebuggerFrameImplementation value");
     }
 
     JSAtom* str = Atomize(cx, s, strlen(s));
     if (!str)
         return false;
 
@@ -8268,16 +8322,21 @@ DebuggerFrame_getScript(JSContext* cx, u
     if (frame.isFunctionFrame()) {
         RootedFunction callee(cx, frame.callee());
         if (callee->isInterpreted()) {
             RootedScript script(cx, callee->nonLazyScript());
             scriptObject = debug->wrapScript(cx, script);
             if (!scriptObject)
                 return false;
         }
+    } else if (frame.isWasmDebugFrame()) {
+        RootedWasmInstanceObject instance(cx, frame.wasmInstance()->object());
+        scriptObject = debug->wrapWasmScript(cx, instance);
+        if (!scriptObject)
+            return false;
     } else {
         /*
          * We got eval, JS_Evaluate*, or JS_ExecuteScript non-function script
          * frames.
          */
         RootedScript script(cx, frame.script());
         scriptObject = debug->wrapScript(cx, script);
         if (!scriptObject)
--- a/js/src/vm/Debugger.h
+++ b/js/src/vm/Debugger.h
@@ -298,17 +298,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
     };
@@ -770,20 +770,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,
@@ -997,21 +997,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
@@ -1146,23 +1146,25 @@ 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
+    Ion,
+    Wasm
 };
 
 /*
  * A Handler represents a reference to a handler function. These handler
  * functions are called by the Debugger API to notify the user of certain
  * events. For each event type, we define a separate subclass of Handler. This
  * allows users to define a single reference to an object that implements
  * multiple handlers, by inheriting from the appropriate subclasses.
@@ -1280,17 +1282,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 MOZ_MUST_USE bool getArguments(JSContext* cx, HandleDebuggerFrame frame,
                                           MutableHandleDebuggerArguments result);
     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,
@@ -1318,18 +1320,19 @@ class DebuggerFrame : public NativeObjec
 
   private:
     static const ClassOps classOps_;
 
     static const JSPropertySpec properties_[];
     static const JSFunctionSpec methods_[];
 
     static AbstractFramePtr getReferent(HandleDebuggerFrame frame);
-    static MOZ_MUST_USE bool getScriptFrameIter(JSContext* cx, HandleDebuggerFrame frame,
-                                                mozilla::Maybe<ScriptFrameIter>& result);
+    static MOZ_MUST_USE bool getFrameIter(JSContext* cx, HandleDebuggerFrame frame,
+                                          mozilla::Maybe<FrameIter>& result);
+    static MOZ_MUST_USE bool requireScriptReferent(JSContext* cx, HandleDebuggerFrame frame);
 
     static MOZ_MUST_USE bool construct(JSContext* cx, unsigned argc, Value* vp);
 
     static MOZ_MUST_USE bool argumentsGetter(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);
     static MOZ_MUST_USE bool generatorGetter(JSContext* cx, unsigned argc, Value* vp);
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -417,30 +417,38 @@ FrameIter::unaliasedForEachActual(JSCont
     MOZ_CRASH("Unexpected state");
 }
 
 inline HandleValue
 AbstractFramePtr::returnValue() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->returnValue();
+    if (isWasmDebugFrame())
+        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 (isWasmDebugFrame()) {
+        // TODO handle wasm function return value
+        // The function is called from Debugger::slowPathOnLeaveFrame --
+        // ignoring value for wasm.
+        return;
+    }
     asRematerializedFrame()->setReturnValue(rval);
 }
 
 inline JSObject*
 AbstractFramePtr::environmentChain() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->environmentChain();
@@ -884,16 +892,18 @@ AbstractFramePtr::newTarget() const
     if (isBaselineFrame())
         return asBaselineFrame()->newTarget();
     return asRematerializedFrame()->newTarget();
 }
 
 inline bool
 AbstractFramePtr::debuggerNeedsCheckPrimitiveReturn() const
 {
+    if (isWasmDebugFrame())
+        return false;
     return script()->isDerivedClassConstructor();
 }
 
 ActivationEntryMonitor::~ActivationEntryMonitor()
 {
     if (entryMonitor_)
         entryMonitor_->Exit(cx_);
 
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -27,16 +27,17 @@
 
 #include "jit/MacroAssembler.h"
 #include "js/Conversions.h"
 #include "vm/Interpreter.h"
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmSerialize.h"
 #include "wasm/WasmSignalHandlers.h"
 
+#include "vm/Debugger-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::IsNaN;
 using mozilla::IsPowerOfTwo;
@@ -110,23 +111,31 @@ WasmHandleDebugTrap()
     MOZ_ASSERT(site);
     if (site->kind() == CallSite::EnterFrame) {
         if (!iter.instance()->enterFrameTrapsEnabled())
             return true;
         DebugFrame* frame = iter.debugFrame();
         frame->setIsDebuggee();
         frame->observeFrame(cx);
         // TODO call onEnterFrame
-        return true;
+        JSTrapStatus status = Debugger::onEnterFrame(cx, frame);
+        if (status == JSTRAP_RETURN) {
+            // Ignoring forced return (JSTRAP_RETURN) -- changing code execution
+            // order is not yet implemented in the wasm baseline.
+            // TODO properly handle JSTRAP_RETURN and resume wasm execution.
+            JS_ReportErrorASCII(cx, "Unexpected resumption value from onEnterFrame");
+            return false;
+        }
+        return status == JSTRAP_CONTINUE;
     }
     if (site->kind() == CallSite::LeaveFrame) {
         DebugFrame* frame = iter.debugFrame();
-        // TODO call onLeaveFrame
+        bool ok = Debugger::onLeaveFrame(cx, frame, nullptr, true);
         frame->leaveFrame(cx);
-        return true;
+        return ok;
     }
     // TODO baseline debug traps
     MOZ_CRASH();
     return true;
 }
 
 static void
 WasmHandleDebugThrow()
@@ -134,17 +143,32 @@ WasmHandleDebugThrow()
     WasmActivation* activation = JSRuntime::innermostWasmActivation();
     JSContext* cx = activation->cx();
 
     for (FrameIterator iter(*activation); !iter.done(); ++iter) {
         if (!iter.debugEnabled())
             continue;
 
         DebugFrame* frame = iter.debugFrame();
-        // TODO call onExceptionUnwind and onLeaveFrame
+
+        JSTrapStatus status = Debugger::onExceptionUnwind(cx, frame);
+        if (status == JSTRAP_RETURN) {
+            // Unexpected trap return -- raising error since throw recovery
+            // is not yet implemented in the wasm baseline.
+            // TODO properly handle JSTRAP_RETURN and resume wasm execution.
+            JS_ReportErrorASCII(cx, "Unexpected resumption value from onExceptionUnwind");
+        }
+
+        bool ok = Debugger::onLeaveFrame(cx, frame, nullptr, false);
+        if (ok) {
+            // Unexpected success from the handler onLeaveFrame -- raising error
+            // since throw recovery is not yet implemented in the wasm baseline.
+            // TODO properly handle success and resume wasm execution.
+            JS_ReportErrorASCII(cx, "Unexpected success from onLeaveFrame");
+        }
         frame->leaveFrame(cx);
      }
 }
 
 static void
 WasmReportTrap(int32_t trapIndex)
 {
     JSContext* cx = JSRuntime::innermostWasmActivation()->cx();