Bug 1387115 - Expose WebAssembly instance memory and globals via debugger scope. r?luke draft
authorYury Delendik <ydelendik@mozilla.com>
Fri, 04 Aug 2017 16:53:12 -0500
changeset 621472 05a2fd6f71a0f396de5f948f0b3989e01b38cf5d
parent 621327 bc829569880635c52d6e3d54f51cd7d3df180186
child 621473 a635d61dd33bbd5a17ece3c7ea845bfa8f77bd79
push id72392
push userydelendik@mozilla.com
push dateFri, 04 Aug 2017 22:06:55 +0000
reviewersluke
bugs1387115
milestone57.0a1
Bug 1387115 - Expose WebAssembly instance memory and globals via debugger scope. r?luke MozReview-Commit-ID: 7EnxNmaiNOF
js/src/frontend/BytecodeEmitter.cpp
js/src/gc/Marking.cpp
js/src/jit-test/tests/debug/wasm-13.js
js/src/jit-test/tests/wasm/globals.js
js/src/jsscript.cpp
js/src/vm/EnvironmentObject.cpp
js/src/vm/EnvironmentObject.h
js/src/vm/Interpreter.cpp
js/src/vm/Scope.cpp
js/src/vm/Scope.h
js/src/vm/Stack.cpp
js/src/wasm/WasmDebug.cpp
js/src/wasm/WasmDebug.h
js/src/wasm/WasmInstance.cpp
js/src/wasm/WasmInstance.h
js/src/wasm/WasmJS.cpp
js/src/wasm/WasmJS.h
js/src/wasm/WasmModule.cpp
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -753,16 +753,17 @@ BytecodeEmitter::EmitterScope::searchInE
 
           case ScopeKind::Global:
             return NameLocation::Global(BindingKind::Var);
 
           case ScopeKind::With:
           case ScopeKind::NonSyntactic:
             return NameLocation::Dynamic();
 
+          case ScopeKind::WasmInstance:
           case ScopeKind::WasmFunction:
             MOZ_CRASH("No direct eval inside wasm functions");
         }
 
         if (hasEnv) {
             MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
             hops++;
         }
@@ -1453,16 +1454,17 @@ BytecodeEmitter::EmitterScope::leave(Byt
       case ScopeKind::StrictNamedLambda:
       case ScopeKind::Eval:
       case ScopeKind::StrictEval:
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
       case ScopeKind::Module:
         break;
 
+      case ScopeKind::WasmInstance:
       case ScopeKind::WasmFunction:
         MOZ_CRASH("No wasm function scopes in JS");
     }
 
     // Finish up the scope if we are leaving it in LIFO fashion.
     if (!nonLocal) {
         // Popping scopes due to non-local jumps generate additional scope
         // notes. See NonLocalExitControl::prepareForNonLocalJump.
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1280,19 +1280,24 @@ EvalScope::Data::trace(JSTracer* trc)
 }
 void
 ModuleScope::Data::trace(JSTracer* trc)
 {
     TraceNullableEdge(trc, &module, "scope module");
     TraceBindingNames(trc, names, length);
 }
 void
+WasmInstanceScope::Data::trace(JSTracer* trc)
+{
+    TraceNullableEdge(trc, &instance, "wasm instance");
+    TraceBindingNames(trc, names, length);
+}
+void
 WasmFunctionScope::Data::trace(JSTracer* trc)
 {
-    TraceNullableEdge(trc, &instance, "wasm function");
     TraceBindingNames(trc, names, length);
 }
 void
 Scope::traceChildren(JSTracer* trc)
 {
     TraceNullableEdge(trc, &enclosing_, "scope enclosing");
     TraceNullableEdge(trc, &environmentShape_, "scope env shape");
     switch (kind_) {
@@ -1318,16 +1323,19 @@ Scope::traceChildren(JSTracer* trc)
       case ScopeKind::StrictEval:
         reinterpret_cast<EvalScope::Data*>(data_)->trace(trc);
         break;
       case ScopeKind::Module:
         reinterpret_cast<ModuleScope::Data*>(data_)->trace(trc);
         break;
       case ScopeKind::With:
         break;
+      case ScopeKind::WasmInstance:
+        reinterpret_cast<WasmInstanceScope::Data*>(data_)->trace(trc);
+        break;
       case ScopeKind::WasmFunction:
         reinterpret_cast<WasmFunctionScope::Data*>(data_)->trace(trc);
         break;
     }
 }
 inline void
 js::GCMarker::eagerlyMarkChildren(Scope* scope)
 {
@@ -1387,19 +1395,26 @@ js::GCMarker::eagerlyMarkChildren(Scope*
         names = data->names;
         length = data->length;
         break;
       }
 
       case ScopeKind::With:
         break;
 
+      case ScopeKind::WasmInstance: {
+        WasmInstanceScope::Data* data = reinterpret_cast<WasmInstanceScope::Data*>(scope->data_);
+        traverseEdge(scope, static_cast<JSObject*>(data->instance));
+        names = data->names;
+        length = data->length;
+        break;
+      }
+
       case ScopeKind::WasmFunction: {
         WasmFunctionScope::Data* data = reinterpret_cast<WasmFunctionScope::Data*>(scope->data_);
-        traverseEdge(scope, static_cast<JSObject*>(data->instance));
         names = data->names;
         length = data->length;
         break;
       }
     }
     if (scope->kind_ == ScopeKind::Function) {
         for (uint32_t i = 0; i < length; i++) {
             if (JSAtom* name = names[i].name())
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-13.js
@@ -0,0 +1,95 @@
+// |jit-test| test-also-wasm-baseline
+// Tests that wasm module scripts has inspectable globals and memory.
+
+load(libdir + "wasm.js");
+load(libdir + 'eqArrayHelper.js');
+
+function monitorGlobalValues(wast, lib, expected) {
+    function setupFrame(frame) {
+        var globals = {};
+        framesGlobals.push(globals);
+        // Environment with globals follow function scope enviroment
+        var globalEnv = frame.environment.parent;
+        globalEnv.names().forEach(n => {
+            globals[n] = [globalEnv.getVariable(n)];
+        });
+        frame.onStep = function () {
+            var globalEnv = frame.environment.parent;
+            globalEnv.names().forEach(n => {
+                var prevValues = globals[n];
+                if (!prevValues)
+                    globals[n] = prevValues = [void 0];
+                var value = globalEnv.getVariable(n);
+                if (prevValues[prevValues.length - 1] !== value)
+                    prevValues.push(value);
+            });
+        }
+    }
+    var framesGlobals = [];
+    wasmRunWithDebugger(wast, lib,
+        function ({dbg}) {
+            dbg.onEnterFrame = function(frame) {
+                if (frame.type == "wasmcall")
+                    setupFrame(frame);
+            }
+        },
+        function ({error}) {
+            assertEq(error, undefined);
+        }
+    );
+    assertEq(framesGlobals.length, expected.length);
+    for (var i = 0; i < framesGlobals.length; i++) {
+        var frameGlobals = framesGlobals[i];
+        var expectedGlobals = expected[i];
+        var globalsNames = Object.keys(frameGlobals);
+        assertEq(globalsNames.length, Object.keys(expectedGlobals).length);
+        globalsNames.forEach(n => {
+            if (typeof expectedGlobals[n][0] === "function") {
+                // expectedGlobals are assert functions
+                expectedGlobals[n].forEach((assertFn, i) => {
+                    assertFn(frameGlobals[n][i]);
+                });
+                return;
+            }
+            assertEqArray(frameGlobals[n], expectedGlobals[n]);
+        });
+    }
+}
+
+monitorGlobalValues(
+    '(module (func (export "test") (nop)))',
+    undefined,
+    [{}]
+);
+monitorGlobalValues(
+    '(module (memory (export "memory") 1 1) (func (export "test") (nop) (nop)))',
+    undefined,
+    [{
+        memory0: [
+            function (actual) {
+                var bufferProp = actual.proto.getOwnPropertyDescriptor("buffer");
+                assertEq(!!bufferProp, true, "wasm memory buffer property");
+                var buffer = bufferProp.get.call(actual).return;
+                var bufferLengthProp = buffer.proto.getOwnPropertyDescriptor("byteLength");
+                var bufferLength = bufferLengthProp.get.call(buffer).return;
+                assertEq(bufferLength, 65536, "wasm memory size");
+            }
+        ]
+    }]
+);
+monitorGlobalValues(
+    '(module\
+     (global i32 (i32.const 1))(global i64 (i64.const 2))(global f32 (f32.const 3.5))(global f64 (f64.const 42.25))\
+     (func (export "test") (nop)))',
+    undefined,
+    [{global0: [1], global1: [2], global2: [3.5], global3: [42.25]}]
+);
+monitorGlobalValues(
+    '(module (global (mut i32) (i32.const 1))(global (mut i64) (i64.const 2))\
+             (global (mut f32) (f32.const 3.5))(global (mut f64) (f64.const 42.25))\
+     (func (export "test")\
+       (i32.const 2)(set_global 0)(i64.const 1)(set_global 1)\
+       (f32.const 42.25)(set_global 2)(f64.const 3.5)(set_global 3)))',
+    undefined,
+    [{global0: [1, 2], global1: [2, 1], global2: [3.5, 42.25], global3: [42.25, 3.5]}]
+)
--- a/js/src/jit-test/tests/wasm/globals.js
+++ b/js/src/jit-test/tests/wasm/globals.js
@@ -239,8 +239,25 @@ testInitExpr('f64', 13.37, 0.1989, x => 
      (export "defined" global 1)
     )`, { globals: {x: createI64('0x1234567887654321')} }).exports;
 
     assertEqI64(module.imported, createI64('0x1234567887654321'));
     assertEqI64(module.defined, createI64('0xFAFADADABABA'));
 
     setJitCompilerOption('wasm.test-mode', 0);
 }
+
+// Custom NaN.
+{
+    let dv = new DataView(new ArrayBuffer(8));
+    module = wasmEvalText(`(module
+     (global $g f64 (f64.const -nan:0xe7ffff1591120))
+     (global $h f32 (f32.const -nan:0x651234))
+     (export "nan64" (global $g))(export "nan32" (global $h))
+    )`, {}).exports;
+
+    dv.setFloat64(0, module.nan64, true);
+    assertEq(dv.getUint32(4, true), 0x7ff80000);
+    assertEq(dv.getUint32(0, true), 0x00000000);
+
+    dv.setFloat32(0, module.nan32, true);
+    assertEq(dv.getUint32(0, true), 0x7fc00000);
+}
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -785,16 +785,17 @@ js::XDRScript(XDRState<mode>* xdr, Handl
                     return false;
                 break;
               case ScopeKind::Global:
               case ScopeKind::NonSyntactic:
                 if (!GlobalScope::XDR(xdr, scopeKind, &scope))
                     return false;
                 break;
               case ScopeKind::Module:
+              case ScopeKind::WasmInstance:
                 MOZ_CRASH("NYI");
                 break;
               case ScopeKind::WasmFunction:
                 MOZ_CRASH("wasm functions cannot be nested in JSScripts");
                 break;
             }
 
             if (mode == XDR_DECODE)
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -631,23 +631,56 @@ ModuleEnvironmentObject::newEnumerate(JS
         properties.infallibleAppend(r.front().propid());
 
     MOZ_ASSERT(properties.length() == count);
     return true;
 }
 
 /*****************************************************************************/
 
+const Class WasmInstanceEnvironmentObject::class_ = {
+    "WasmInstance",
+    JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(WasmInstanceEnvironmentObject::RESERVED_SLOTS)
+};
+
+/* static */ WasmInstanceEnvironmentObject*
+WasmInstanceEnvironmentObject::createHollowForDebug(JSContext* cx, Handle<WasmInstanceScope*> scope)
+{
+    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
+    if (!group)
+        return nullptr;
+
+    RootedShape shape(cx, scope->getEmptyEnvironmentShape(cx));
+    if (!shape)
+        return nullptr;
+
+    gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
+    MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
+    kind = gc::GetBackgroundAllocKind(kind);
+
+    JSObject* obj;
+    JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, gc::DefaultHeap, shape, group));
+
+    Rooted<WasmInstanceEnvironmentObject*> callobj(cx, &obj->as<WasmInstanceEnvironmentObject>());
+    callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
+    callobj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
+
+    return callobj;
+}
+
+/*****************************************************************************/
+
 const Class WasmFunctionCallObject::class_ = {
     "WasmCall",
     JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(WasmFunctionCallObject::RESERVED_SLOTS)
 };
 
 /* static */ WasmFunctionCallObject*
-WasmFunctionCallObject::createHollowForDebug(JSContext* cx, Handle<WasmFunctionScope*> scope)
+WasmFunctionCallObject::createHollowForDebug(JSContext* cx, HandleObject enclosing,
+                                             Handle<WasmFunctionScope*> scope)
 {
     RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
     if (!group)
         return nullptr;
 
     RootedShape shape(cx, scope->getEmptyEnvironmentShape(cx));
     if (!shape)
         return nullptr;
@@ -655,17 +688,17 @@ WasmFunctionCallObject::createHollowForD
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     JSObject* obj;
     JS_TRY_VAR_OR_RETURN_NULL(cx, obj, NativeObject::create(cx, kind, gc::DefaultHeap, shape, group));
 
     Rooted<WasmFunctionCallObject*> callobj(cx, &obj->as<WasmFunctionCallObject>());
-    callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
+    callobj->initEnclosingEnvironment(enclosing);
     callobj->initReservedSlot(SCOPE_SLOT, PrivateGCThingValue(scope));
 
     return callobj;
 }
 
 /*****************************************************************************/
 
 WithEnvironmentObject*
@@ -1668,16 +1701,48 @@ class DebugEnvironmentProxyHandler : pub
                     // TODO
                 }
             } else {
                 *accessResult = ACCESS_LOST;
             }
             return true;
         }
 
+        if (env->is<WasmInstanceEnvironmentObject>()) {
+            RootedScope scope(cx, getEnvironmentScope(*env));
+            MOZ_ASSERT(scope->is<WasmInstanceScope>());
+            uint32_t index = 0;
+            for (BindingIter bi(scope); bi; bi++) {
+                if (JSID_IS_ATOM(id, bi.name()))
+                    break;
+                MOZ_ASSERT(!bi.isLast());
+                index++;
+            }
+            Rooted<WasmInstanceScope*> instanceScope(cx, &scope->as<WasmInstanceScope>());
+            wasm::Instance& instance = instanceScope->instance()->instance();
+
+            if (action == GET) {
+                if (instanceScope->memoriesStart() <= index && index < instanceScope->globalsStart()) {
+                    MOZ_ASSERT(instanceScope->memoriesStart() + 1 == instanceScope->globalsStart());
+                    vp.set(ObjectValue(*instance.memory()));
+                }
+                if (instanceScope->globalsStart() <= index) {
+                    MOZ_ASSERT(index < instanceScope->namesCount());
+                    if (!instance.debug().getGlobal(instance, index - instanceScope->globalsStart(), vp)) {
+                        ReportOutOfMemory(cx);
+                        return false;
+                    }
+                }
+                *accessResult = ACCESS_UNALIASED;
+            } else { // if (action == SET)
+                // TODO
+            }
+            return true;
+        }
+
         /* The rest of the internal scopes do not have unaliased vars. */
         MOZ_ASSERT(!IsSyntacticEnvironment(env) ||
                    env->is<WithEnvironmentObject>());
         return true;
     }
 
     static bool isArguments(JSContext* cx, jsid id)
     {
@@ -1702,16 +1767,18 @@ class DebugEnvironmentProxyHandler : pub
     static Scope* getEnvironmentScope(const JSObject& env)
     {
         if (isFunctionEnvironment(env))
             return env.as<CallObject>().callee().nonLazyScript()->bodyScope();
         if (isNonExtensibleLexicalEnvironment(env))
             return &env.as<LexicalEnvironmentObject>().scope();
         if (env.is<VarEnvironmentObject>())
             return &env.as<VarEnvironmentObject>().scope();
+        if (env.is<WasmInstanceEnvironmentObject>())
+            return &env.as<WasmInstanceEnvironmentObject>().scope();
         if (env.is<WasmFunctionCallObject>())
             return &env.as<WasmFunctionCallObject>().scope();
         return nullptr;
     }
 
     /*
      * In theory, every non-arrow function scope contains an 'arguments'
      * bindings.  However, the engine only adds a binding if 'arguments' is
@@ -2306,16 +2373,17 @@ DebugEnvironmentProxy::initSnapshot(Arra
 
 bool
 DebugEnvironmentProxy::isForDeclarative() const
 {
     EnvironmentObject& e = environment();
     return e.is<CallObject>() ||
            e.is<VarEnvironmentObject>() ||
            e.is<ModuleEnvironmentObject>() ||
+           e.is<WasmInstanceEnvironmentObject>() ||
            e.is<WasmFunctionCallObject>() ||
            e.is<LexicalEnvironmentObject>();
 }
 
 /* static */ bool
 DebugEnvironmentProxy::getMaybeSentinelValue(JSContext* cx, Handle<DebugEnvironmentProxy*> env,
                                              HandleId id, MutableHandleValue vp)
 {
@@ -2952,16 +3020,17 @@ GetDebugEnvironmentForEnvironmentObject(
 }
 
 static DebugEnvironmentProxy*
 GetDebugEnvironmentForMissing(JSContext* cx, const EnvironmentIter& ei)
 {
     MOZ_ASSERT(!ei.hasSyntacticEnvironment() &&
                (ei.scope().is<FunctionScope>() ||
                 ei.scope().is<LexicalScope>() ||
+                ei.scope().is<WasmInstanceScope>() ||
                 ei.scope().is<WasmFunctionScope>() ||
                 ei.scope().is<VarScope>()));
 
     if (DebugEnvironmentProxy* debugEnv = DebugEnvironments::hasDebugEnvironment(cx, ei))
         return debugEnv;
 
     EnvironmentIter copy(cx, ei);
     RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy));
@@ -2996,19 +3065,29 @@ GetDebugEnvironmentForMissing(JSContext*
     } else if (ei.scope().is<LexicalScope>()) {
         Rooted<LexicalScope*> lexicalScope(cx, &ei.scope().as<LexicalScope>());
         Rooted<LexicalEnvironmentObject*> env(cx,
             LexicalEnvironmentObject::createHollowForDebug(cx, lexicalScope));
         if (!env)
             return nullptr;
 
         debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
+    } else if (ei.scope().is<WasmInstanceScope>()) {
+        Rooted<WasmInstanceScope*> wasmInstanceScope(cx, &ei.scope().as<WasmInstanceScope>());
+        Rooted<WasmInstanceEnvironmentObject*> env(cx,
+            WasmInstanceEnvironmentObject::createHollowForDebug(cx, wasmInstanceScope));
+        if (!env)
+            return nullptr;
+
+        debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
     } else if (ei.scope().is<WasmFunctionScope>()) {
         Rooted<WasmFunctionScope*> wasmFunctionScope(cx, &ei.scope().as<WasmFunctionScope>());
-        Rooted<WasmFunctionCallObject*> callobj(cx, WasmFunctionCallObject::createHollowForDebug(cx, wasmFunctionScope));
+        RootedObject enclosing(cx, &enclosingDebug->as<DebugEnvironmentProxy>().environment());
+        Rooted<WasmFunctionCallObject*> callobj(cx,
+            WasmFunctionCallObject::createHollowForDebug(cx, enclosing, wasmFunctionScope));
         if (!callobj)
             return nullptr;
 
         debugEnv = DebugEnvironmentProxy::create(cx, *callobj, enclosingDebug);
     } else {
         Rooted<VarScope*> varScope(cx, &ei.scope().as<VarScope>());
         Rooted<VarEnvironmentObject*> env(cx,
             VarEnvironmentObject::createHollowForDebug(cx, varScope));
@@ -3048,16 +3127,17 @@ GetDebugEnvironment(JSContext* cx, const
     if (ei.done())
         return GetDebugEnvironmentForNonEnvironmentObject(ei);
 
     if (ei.hasAnyEnvironmentObject())
         return GetDebugEnvironmentForEnvironmentObject(cx, ei);
 
     if (ei.scope().is<FunctionScope>() ||
         ei.scope().is<LexicalScope>() ||
+        ei.scope().is<WasmInstanceScope>() ||
         ei.scope().is<WasmFunctionScope>() ||
         ei.scope().is<VarScope>())
     {
         return GetDebugEnvironmentForMissing(cx, ei);
     }
 
     EnvironmentIter copy(cx, ei);
     return GetDebugEnvironment(cx, ++copy);
--- a/js/src/vm/EnvironmentObject.h
+++ b/js/src/vm/EnvironmentObject.h
@@ -422,30 +422,52 @@ class ModuleEnvironmentObject : public E
     static bool newEnumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
                              bool enumerableOnly);
 };
 
 typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
 typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject;
 typedef MutableHandle<ModuleEnvironmentObject*> MutableHandleModuleEnvironmentObject;
 
+class WasmInstanceEnvironmentObject : public EnvironmentObject
+{
+    // Currently WasmInstanceScopes do not use their scopes in a
+    // meaningful way. However, it is an invariant of DebugEnvironments that
+    // environments kept in those maps have live scopes, thus this strong
+    // reference.
+    static const uint32_t SCOPE_SLOT = 1;
+
+  public:
+    static const Class class_;
+
+    static const uint32_t RESERVED_SLOTS = 2;
+
+    static WasmInstanceEnvironmentObject* createHollowForDebug(JSContext* cx,
+                                                               Handle<WasmInstanceScope*> scope);
+    WasmInstanceScope& scope() const {
+        Value v = getReservedSlot(SCOPE_SLOT);
+        MOZ_ASSERT(v.isPrivateGCThing());
+        return *static_cast<WasmInstanceScope*>(v.toGCThing());
+    }
+};
+
 class WasmFunctionCallObject : public EnvironmentObject
 {
     // Currently WasmFunctionCallObjects do not use their scopes in a
     // meaningful way. However, it is an invariant of DebugEnvironments that
     // environments kept in those maps have live scopes, thus this strong
     // reference.
     static const uint32_t SCOPE_SLOT = 1;
 
   public:
     static const Class class_;
 
     static const uint32_t RESERVED_SLOTS = 2;
 
-    static WasmFunctionCallObject* createHollowForDebug(JSContext* cx,
+    static WasmFunctionCallObject* createHollowForDebug(JSContext* cx, HandleObject enclosing,
                                                         Handle<WasmFunctionScope*> scope);
     WasmFunctionScope& scope() const {
         Value v = getReservedSlot(SCOPE_SLOT);
         MOZ_ASSERT(v.isPrivateGCThing());
         return *static_cast<WasmFunctionScope*>(v.toGCThing());
     }
 };
 
@@ -1010,16 +1032,17 @@ class DebugEnvironments
 
 template <>
 inline bool
 JSObject::is<js::EnvironmentObject>() const
 {
     return is<js::CallObject>() ||
            is<js::VarEnvironmentObject>() ||
            is<js::ModuleEnvironmentObject>() ||
+           is<js::WasmInstanceEnvironmentObject>() ||
            is<js::WasmFunctionCallObject>() ||
            is<js::LexicalEnvironmentObject>() ||
            is<js::WithEnvironmentObject>() ||
            is<js::NonSyntacticVariablesObject>() ||
            is<js::RuntimeLexicalErrorObject>();
 }
 
 template<>
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1066,16 +1066,17 @@ PopEnvironment(JSContext* cx, Environmen
         if (ei.scope().hasEnvironment())
             ei.initialFrame().popOffEnvironmentChain<VarEnvironmentObject>();
         break;
       case ScopeKind::Eval:
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
       case ScopeKind::Module:
         break;
+      case ScopeKind::WasmInstance:
       case ScopeKind::WasmFunction:
         MOZ_CRASH("wasm is not interpreted");
         break;
     }
 }
 
 // Unwind environment chain and iterator to match the env corresponding to
 // the given bytecode position.
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -72,16 +72,18 @@ js::ScopeKindString(ScopeKind kind)
       case ScopeKind::StrictEval:
         return "strict eval";
       case ScopeKind::Global:
         return "global";
       case ScopeKind::NonSyntactic:
         return "non-syntactic";
       case ScopeKind::Module:
         return "module";
+      case ScopeKind::WasmInstance:
+        return "wasm instance";
       case ScopeKind::WasmFunction:
         return "wasm function";
     }
     MOZ_CRASH("Bad ScopeKind");
 }
 
 static Shape*
 EmptyEnvironmentShape(JSContext* cx, const Class* cls, uint32_t numSlots,
@@ -422,16 +424,17 @@ Scope::clone(JSContext* cx, HandleScope 
         MOZ_CRASH("Use GlobalScope::clone.");
         break;
 
       case ScopeKind::WasmFunction:
         MOZ_CRASH("wasm functions are not nested in JSScript");
         break;
 
       case ScopeKind::Module:
+      case ScopeKind::WasmInstance:
         MOZ_CRASH("NYI");
         break;
 
     }
 
     return nullptr;
 }
 
@@ -509,16 +512,19 @@ LexicalScope::nextFrameSlot(Scope* scope
           case ScopeKind::Eval:
           case ScopeKind::StrictEval:
             return si.scope()->as<EvalScope>().nextFrameSlot();
           case ScopeKind::Global:
           case ScopeKind::NonSyntactic:
             return 0;
           case ScopeKind::Module:
             return si.scope()->as<ModuleScope>().nextFrameSlot();
+          case ScopeKind::WasmInstance:
+            // TODO return si.scope()->as<WasmInstanceScope>().nextFrameSlot();
+            return 0;
           case ScopeKind::WasmFunction:
             // TODO return si.scope()->as<WasmFunctionScope>().nextFrameSlot();
             return 0;
         }
     }
     MOZ_CRASH("Not an enclosing intra-frame Scope");
 }
 
@@ -1230,72 +1236,136 @@ ModuleScope::getEmptyEnvironmentShape(JS
 }
 
 JSScript*
 ModuleScope::script() const
 {
     return module()->script();
 }
 
-// TODO Check what Debugger behavior should be when it evaluates a
-// var declaration.
-static const uint32_t WasmFunctionEnvShapeFlags =
+static const uint32_t WasmInstanceEnvShapeFlags =
     BaseShape::NOT_EXTENSIBLE | BaseShape::DELEGATE;
 
+
+template <size_t ArrayLength>
 static JSAtom*
-GenerateWasmVariableName(JSContext* cx, uint32_t index)
+GenerateWasmName(JSContext* cx, const char (&prefix)[ArrayLength], uint32_t index)
 {
     StringBuffer sb(cx);
-    if (!sb.append("var"))
+    if (!sb.append(prefix))
         return nullptr;
     if (!NumberValueToStringBuffer(cx, Int32Value(index), sb))
         return nullptr;
 
     return sb.finishAtom();
 }
 
-/* static */ WasmFunctionScope*
-WasmFunctionScope::create(JSContext* cx, WasmInstanceObject* instance, uint32_t funcIndex)
+/* static */ WasmInstanceScope*
+WasmInstanceScope::create(JSContext* cx, WasmInstanceObject* instance)
 {
-    // WasmFunctionScope::Data has GCManagedDeletePolicy because it contains a
+    // WasmInstanceScope::Data has GCManagedDeletePolicy because it contains a
     // GCPtr. Destruction of |data| below may trigger calls into the GC.
-    Rooted<WasmFunctionScope*> wasmFunctionScope(cx);
+    Rooted<WasmInstanceScope*> wasmInstanceScope(cx);
 
     {
-        // TODO pull the local variable names from the wasm function definition.
-        wasm::ValTypeVector locals;
-        size_t argsLength;
-        if (!instance->instance().debug().debugGetLocalTypes(funcIndex, &locals, &argsLength))
-            return nullptr;
-        uint32_t namesCount = locals.length();
+        size_t namesCount = 0;
+        if (instance->instance().memory()) {
+            namesCount++;
+        }
+        size_t globalsStart = namesCount;
+        size_t globalsCount = instance->instance().metadata().globals.length();
+        namesCount += globalsCount;
 
-        Rooted<UniquePtr<Data>> data(cx, NewEmptyScopeData<WasmFunctionScope>(cx, namesCount));
+        Rooted<UniquePtr<Data>> data(cx, NewEmptyScopeData<WasmInstanceScope>(cx, namesCount));
         if (!data)
             return nullptr;
 
-        Rooted<Scope*> enclosingScope(cx, &cx->global()->emptyGlobalScope());
+        size_t nameIndex = 0;
+        if (instance->instance().memory()) {
+            RootedAtom name(cx, GenerateWasmName(cx, "memory", /* index = */ 0));
+            if (!name)
+                return nullptr;
+            data->names[nameIndex] = BindingName(name, false);
+            nameIndex++;
+        }
+        for (size_t i = 0; i < globalsCount; i++) {
+            RootedAtom name(cx, GenerateWasmName(cx, "global", i));
+            if (!name)
+                return nullptr;
+            data->names[nameIndex] = BindingName(name, false);
+            nameIndex++;
+        }
+        MOZ_ASSERT(nameIndex == namesCount);
 
         data->instance.init(instance);
-        data->funcIndex = funcIndex;
+        data->memoriesStart = 0;
+        data->globalsStart = globalsStart;
         data->length = namesCount;
-        for (size_t i = 0; i < namesCount; i++) {
-            RootedAtom name(cx, GenerateWasmVariableName(cx, i));
-            if (!name)
-                return nullptr;
-            data->names[i] = BindingName(name, false);
-        }
 
-        Scope* scope = Scope::create(cx, ScopeKind::WasmFunction, enclosingScope, /* envShape = */ nullptr);
+        Rooted<Scope*> enclosingScope(cx, &cx->global()->emptyGlobalScope());
+
+        Scope* scope = Scope::create(cx, ScopeKind::WasmInstance, enclosingScope, /* envShape = */ nullptr);
         if (!scope)
             return nullptr;
 
-        wasmFunctionScope = &scope->as<WasmFunctionScope>();
-        wasmFunctionScope->initData(Move(data.get()));
+        wasmInstanceScope = &scope->as<WasmInstanceScope>();
+        wasmInstanceScope->initData(Move(data.get()));
     }
 
+    return wasmInstanceScope;
+}
+
+/* static */ Shape*
+WasmInstanceScope::getEmptyEnvironmentShape(JSContext* cx)
+{
+    const Class* cls = &WasmInstanceEnvironmentObject::class_;
+    return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), WasmInstanceEnvShapeFlags);
+}
+
+// TODO Check what Debugger behavior should be when it evaluates a
+// var declaration.
+static const uint32_t WasmFunctionEnvShapeFlags =
+    BaseShape::NOT_EXTENSIBLE | BaseShape::DELEGATE;
+
+/* static */ WasmFunctionScope*
+WasmFunctionScope::create(JSContext* cx, HandleScope enclosing, uint32_t funcIndex)
+{
+    MOZ_ASSERT(enclosing->is<WasmInstanceScope>());
+
+    Rooted<WasmFunctionScope*> wasmFunctionScope(cx);
+
+    Rooted<WasmInstanceObject*> instance(cx, enclosing->as<WasmInstanceScope>().instance());
+
+    // TODO pull the local variable names from the wasm function definition.
+    wasm::ValTypeVector locals;
+    size_t argsLength;
+    if (!instance->instance().debug().debugGetLocalTypes(funcIndex, &locals, &argsLength))
+        return nullptr;
+    uint32_t namesCount = locals.length();
+
+    Rooted<UniquePtr<Data>> data(cx, NewEmptyScopeData<WasmFunctionScope>(cx, namesCount));
+    if (!data)
+        return nullptr;
+
+    data->funcIndex = funcIndex;
+    data->length = namesCount;
+    for (size_t i = 0; i < namesCount; i++) {
+        RootedAtom name(cx, GenerateWasmName(cx, "var", i));
+        if (!name)
+            return nullptr;
+        data->names[i] = BindingName(name, false);
+    }
+
+    Scope* scope = Scope::create(cx, ScopeKind::WasmFunction, enclosing, /* envShape = */ nullptr);
+    if (!scope)
+        return nullptr;
+
+    wasmFunctionScope = &scope->as<WasmFunctionScope>();
+    wasmFunctionScope->initData(Move(data.get()));
+
     return wasmFunctionScope;
 }
 
 /* static */ Shape*
 WasmFunctionScope::getEmptyEnvironmentShape(JSContext* cx)
 {
     const Class* cls = &WasmFunctionCallObject::class_;
     return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), WasmFunctionEnvShapeFlags);
@@ -1347,16 +1417,19 @@ BindingIter::BindingIter(Scope* scope)
         break;
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
         init(scope->as<GlobalScope>().data());
         break;
       case ScopeKind::Module:
         init(scope->as<ModuleScope>().data());
         break;
+      case ScopeKind::WasmInstance:
+        init(scope->as<WasmInstanceScope>().data());
+        break;
       case ScopeKind::WasmFunction:
         init(scope->as<WasmFunctionScope>().data());
         break;
     }
 }
 
 BindingIter::BindingIter(JSScript* script)
   : BindingIter(script->bodyScope())
@@ -1481,16 +1554,32 @@ BindingIter::init(ModuleScope::Data& dat
     //             consts - [data.constStart, data.length)
     init(data.varStart, data.varStart, data.varStart, data.varStart, data.letStart, data.constStart,
          CanHaveFrameSlots | CanHaveEnvironmentSlots,
          0, JSSLOT_FREE(&ModuleEnvironmentObject::class_),
          data.names, data.length);
 }
 
 void
+BindingIter::init(WasmInstanceScope::Data& data)
+{
+    //            imports - [0, 0)
+    // positional formals - [0, 0)
+    //      other formals - [0, 0)
+    //    top-level funcs - [0, 0)
+    //               vars - [0, data.length)
+    //               lets - [data.length, data.length)
+    //             consts - [data.length, data.length)
+    init(0, 0, 0, 0, data.length, data.length,
+         CanHaveFrameSlots | CanHaveEnvironmentSlots,
+         UINT32_MAX, UINT32_MAX,
+         data.names, data.length);
+}
+
+void
 BindingIter::init(WasmFunctionScope::Data& data)
 {
     //            imports - [0, 0)
     // positional formals - [0, 0)
     //      other formals - [0, 0)
     //    top-level funcs - [0, 0)
     //               vars - [0, data.length)
     //               lets - [data.length, data.length)
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -68,16 +68,19 @@ enum class ScopeKind : uint8_t
 
     // GlobalScope
     Global,
     NonSyntactic,
 
     // ModuleScope
     Module,
 
+    // WasmInstanceScope
+    WasmInstance,
+
     // WasmFunctionScope
     WasmFunction
 };
 
 static inline bool
 ScopeKindIsCatch(ScopeKind kind)
 {
     return kind == ScopeKind::SimpleCatch || kind == ScopeKind::Catch;
@@ -944,41 +947,39 @@ class ModuleScope : public Scope
         return data().module;
     }
 
     JSScript* script() const;
 
     static Shape* getEmptyEnvironmentShape(JSContext* cx);
 };
 
-// Scope corresponding to the wasm function. A WasmFunctionScope is used by
-// Debugger only, and not for wasm execution.
-//
-class WasmFunctionScope : public Scope
+class WasmInstanceScope : public Scope
 {
     friend class BindingIter;
     friend class Scope;
-    static const ScopeKind classScopeKind_ = ScopeKind::WasmFunction;
+    static const ScopeKind classScopeKind_ = ScopeKind::WasmInstance;
 
   public:
     struct Data
     {
+        uint32_t memoriesStart;
+        uint32_t globalsStart;
         uint32_t length;
         uint32_t nextFrameSlot;
-        uint32_t funcIndex;
 
         // The wasm instance of the scope.
         GCPtr<WasmInstanceObject*> instance;
 
         BindingName names[1];
 
         void trace(JSTracer* trc);
     };
 
-    static WasmFunctionScope* create(JSContext* cx, WasmInstanceObject* instance, uint32_t funcIndex);
+    static WasmInstanceScope* create(JSContext* cx, WasmInstanceObject* instance);
 
     static size_t sizeOfData(uint32_t length) {
         return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
     }
 
   private:
     Data& data() {
         return *reinterpret_cast<Data*>(data_);
@@ -988,16 +989,68 @@ class WasmFunctionScope : public Scope
         return *reinterpret_cast<Data*>(data_);
     }
 
   public:
     WasmInstanceObject* instance() const {
         return data().instance;
     }
 
+    uint32_t memoriesStart() const {
+        return data().memoriesStart;
+    }
+
+    uint32_t globalsStart() const {
+        return data().globalsStart;
+    }
+
+    uint32_t namesCount() const {
+        return data().length;
+    }
+
+    static Shape* getEmptyEnvironmentShape(JSContext* cx);
+};
+
+// Scope corresponding to the wasm function. A WasmFunctionScope is used by
+// Debugger only, and not for wasm execution.
+//
+class WasmFunctionScope : public Scope
+{
+    friend class BindingIter;
+    friend class Scope;
+    static const ScopeKind classScopeKind_ = ScopeKind::WasmFunction;
+
+  public:
+    struct Data
+    {
+        uint32_t length;
+        uint32_t nextFrameSlot;
+        uint32_t funcIndex;
+
+        BindingName names[1];
+
+        void trace(JSTracer* trc);
+    };
+
+    static WasmFunctionScope* create(JSContext* cx, HandleScope enclosing, uint32_t funcIndex);
+
+    static size_t sizeOfData(uint32_t length) {
+        return sizeof(Data) + (length ? length - 1 : 0) * sizeof(BindingName);
+    }
+
+  private:
+    Data& data() {
+        return *reinterpret_cast<Data*>(data_);
+    }
+
+    const Data& data() const {
+        return *reinterpret_cast<Data*>(data_);
+    }
+
+  public:
     uint32_t funcIndex() const {
         return data().funcIndex;
     }
 
     static Shape* getEmptyEnvironmentShape(JSContext* cx);
 };
 
 //
@@ -1104,16 +1157,17 @@ class BindingIter
     }
 
     void init(LexicalScope::Data& data, uint32_t firstFrameSlot, uint8_t flags);
     void init(FunctionScope::Data& data, uint8_t flags);
     void init(VarScope::Data& data, uint32_t firstFrameSlot);
     void init(GlobalScope::Data& data);
     void init(EvalScope::Data& data, bool strict);
     void init(ModuleScope::Data& data);
+    void init(WasmInstanceScope::Data& data);
     void init(WasmFunctionScope::Data& data);
 
     bool hasFormalParameterExprs() const {
         return flags_ & HasFormalParameterExprs;
     }
 
     bool ignoreDestructuredFormalParameters() const {
         return flags_ & IgnoreDestructuredFormalParameters;
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -154,16 +154,20 @@ AssertScopeMatchesEnvironment(Scope* sco
                 break;
 
               case ScopeKind::Module:
                 MOZ_ASSERT(env->as<ModuleEnvironmentObject>().module().script() ==
                            si.scope()->as<ModuleScope>().script());
                 env = &env->as<ModuleEnvironmentObject>().enclosingEnvironment();
                 break;
 
+              case ScopeKind::WasmInstance:
+                env = &env->as<WasmInstanceEnvironmentObject>().enclosingEnvironment();
+                break;
+
               case ScopeKind::WasmFunction:
                 env = &env->as<WasmFunctionCallObject>().enclosingEnvironment();
                 break;
             }
         }
     }
 
     // In the case of a non-syntactic env chain, the immediate parent of the
--- a/js/src/wasm/WasmDebug.cpp
+++ b/js/src/wasm/WasmDebug.cpp
@@ -21,16 +21,17 @@
 #include "mozilla/BinarySearch.h"
 
 #include "ds/Sort.h"
 #include "jit/ExecutableAllocator.h"
 #include "jit/MacroAssembler.h"
 #include "vm/Debugger.h"
 #include "vm/StringBuffer.h"
 #include "wasm/WasmBinaryToText.h"
+#include "wasm/WasmInstance.h"
 #include "wasm/WasmValidate.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::BinarySearchIf;
 
@@ -544,16 +545,71 @@ DebugState::debugGetLocalTypes(uint32_t 
 
 ExprType
 DebugState::debugGetResultType(uint32_t funcIndex)
 {
     MOZ_ASSERT(debugEnabled());
     return metadata().debugFuncReturnTypes[funcIndex];
 }
 
+bool
+DebugState::getGlobal(Instance& instance, uint32_t globalIndex, MutableHandleValue vp)
+{
+    const GlobalDesc& global = metadata().globals[globalIndex];
+
+    if (global.isConstant()) {
+        Val value = global.constantValue();
+        switch (value.type()) {
+          case ValType::I32:
+            vp.set(Int32Value(value.i32()));
+            break;
+          case ValType::I64:
+          // Just display as a Number; it's ok if we lose some precision
+            vp.set(NumberValue((double)value.i64()));
+            break;
+          case ValType::F32:
+            vp.set(NumberValue(JS::CanonicalizeNaN(value.f32())));
+            break;
+          case ValType::F64:
+            vp.set(NumberValue(JS::CanonicalizeNaN(value.f64())));
+            break;
+          default:
+            MOZ_CRASH("Global constant type");
+        }
+        return true;
+    }
+
+    uint8_t* globalData = instance.globalSegment().globalData();
+    void* dataPtr = globalData + global.offset();
+    switch (global.type()) {
+      case ValType::I32: {
+        vp.set(Int32Value(*static_cast<int32_t*>(dataPtr)));
+        break;
+      }
+      case ValType::I64: {
+        // Just display as a Number; it's ok if we lose some precision
+        vp.set(NumberValue((double)*static_cast<int64_t*>(dataPtr)));
+        break;
+      }
+      case ValType::F32: {
+        vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<float*>(dataPtr))));
+        break;
+      }
+      case ValType::F64: {
+        vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<double*>(dataPtr))));
+        break;
+      }
+      default:
+        MOZ_CRASH("Global variable type");
+        break;
+    }
+    return true;
+}
+
+
 JSString*
 DebugState::debugDisplayURL(JSContext* cx) const
 {
     // Build wasm module URL from following parts:
     // - "wasm:" as protocol;
     // - URI encoded filename from metadata (if can be encoded), plus ":";
     // - 64-bit hash of the module bytes (as hex dump).
     js::StringBuffer result(cx);
--- a/js/src/wasm/WasmDebug.h
+++ b/js/src/wasm/WasmDebug.h
@@ -138,16 +138,17 @@ class DebugState
     bool stepModeEnabled(uint32_t funcIndex) const;
     bool incrementStepModeCount(JSContext* cx, uint32_t funcIndex);
     bool decrementStepModeCount(FreeOp* fop, uint32_t funcIndex);
 
     // Stack inspection helpers.
 
     bool debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals, size_t* argsLength);
     ExprType debugGetResultType(uint32_t funcIndex);
+    bool getGlobal(Instance& instance, uint32_t globalIndex, MutableHandleValue vp);
 
     // Debug URL helpers.
 
     JSString* debugDisplayURL(JSContext* cx) const;
     bool getSourceMappingURL(JSContext* cx, MutableHandleString result) const;
 
     // Accessors for commonly used elements of linked structures.
 
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -518,16 +518,22 @@ Instance::trace(JSTracer* trc)
     // Technically, instead of having this method, the caller could use
     // Instance::object() to get the owning WasmInstanceObject to mark,
     // but this method is simpler and more efficient. The trace hook of
     // WasmInstanceObject will call Instance::tracePrivate at which point we
     // can mark the rest of the children.
     TraceEdge(trc, &object_, "wasm instance object");
 }
 
+WasmMemoryObject*
+Instance::memory() const
+{
+    return memory_;
+}
+
 SharedMem<uint8_t*>
 Instance::memoryBase() const
 {
     MOZ_ASSERT(metadata().usesMemory());
     MOZ_ASSERT(tlsData()->memoryBase == memory_->buffer().dataPointerEither());
     return memory_->buffer().dataPointerEither();
 }
 
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -111,16 +111,17 @@ class Instance
     const CodeSegment& codeSegment(Tier t) const { return code_->segment(t); }
     const GlobalSegment& globalSegment() const { return *globals_; }
     uint8_t* codeBase(Tier t) const { return code_->segment(t).base(); }
     const MetadataTier& metadata(Tier t) const { return code_->metadata(t); }
     const Metadata& metadata() const { return code_->metadata(); }
     bool isAsmJS() const { return metadata().isAsmJS(); }
     const SharedTableVector& tables() const { return tables_; }
     SharedMem<uint8_t*> memoryBase() const;
+    WasmMemoryObject* memory() const;
     size_t memoryLength() const;
     size_t memoryMappedSize() const;
     bool memoryAccessInGuardRegion(uint8_t* addr, unsigned numBytes) const;
     TlsData* tlsData() const { return globals_->tlsData(); }
 
     // 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
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -1030,16 +1030,17 @@ WasmInstanceObject::create(JSContext* cx
 
     AutoSetNewObjectMetadata metadata(cx);
     RootedWasmInstanceObject obj(cx, NewObjectWithGivenProto<WasmInstanceObject>(cx, proto));
     if (!obj)
         return nullptr;
 
     obj->setReservedSlot(EXPORTS_SLOT, PrivateValue(exports.release()));
     obj->setReservedSlot(SCOPES_SLOT, PrivateValue(scopes.release()));
+    obj->setReservedSlot(INSTANCE_SCOPE_SLOT, UndefinedValue());
     MOZ_ASSERT(obj->isNewborn());
 
     MOZ_ASSERT(obj->isTenured(), "assumed by WasmTableObject write barriers");
 
     // Root the Instance via WasmInstanceObject before any possible GC.
     auto* instance = cx->new_<Instance>(cx,
                                         obj,
                                         code,
@@ -1208,24 +1209,43 @@ const CodeRange&
 WasmInstanceObject::getExportedFunctionCodeRange(HandleFunction fun, Tier tier)
 {
     uint32_t funcIndex = ExportedFunctionToFuncIndex(fun);
     MOZ_ASSERT(exports().lookup(funcIndex)->value() == fun);
     const FuncExport& funcExport = instance().metadata(tier).lookupFuncExport(funcIndex);
     return instance().metadata(tier).codeRanges[funcExport.codeRangeIndex()];
 }
 
+/* static */ WasmInstanceScope*
+WasmInstanceObject::getScope(JSContext* cx, HandleWasmInstanceObject instanceObj)
+{
+    if (!instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT).isUndefined())
+        return (WasmInstanceScope*)instanceObj->getReservedSlot(INSTANCE_SCOPE_SLOT).toGCThing();
+
+    Rooted<WasmInstanceScope*> instanceScope(cx, WasmInstanceScope::create(cx, instanceObj));
+    if (!instanceScope)
+        return nullptr;
+
+    instanceObj->setReservedSlot(INSTANCE_SCOPE_SLOT, PrivateGCThingValue(instanceScope));
+
+    return instanceScope;
+}
+
 /* static */ WasmFunctionScope*
 WasmInstanceObject::getFunctionScope(JSContext* cx, HandleWasmInstanceObject instanceObj,
                                      uint32_t funcIndex)
 {
     if (ScopeMap::Ptr p = instanceObj->scopes().lookup(funcIndex))
         return p->value();
 
-    Rooted<WasmFunctionScope*> funcScope(cx, WasmFunctionScope::create(cx, instanceObj, funcIndex));
+    Rooted<WasmInstanceScope*> instanceScope(cx, WasmInstanceObject::getScope(cx, instanceObj));
+    if (!instanceScope)
+        return nullptr;
+
+    Rooted<WasmFunctionScope*> funcScope(cx, WasmFunctionScope::create(cx, instanceScope, funcIndex));
     if (!funcScope)
         return nullptr;
 
     if (!instanceObj->scopes().putNew(funcIndex, funcScope)) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -22,16 +22,17 @@
 #include "gc/Policy.h"
 #include "vm/NativeObject.h"
 #include "wasm/WasmTypes.h"
 
 namespace js {
 
 class TypedArrayObject;
 class WasmFunctionScope;
+class WasmInstanceScope;
 
 namespace wasm {
 
 // Creates a testing-only NaN JS object with fields as described above, for
 // T=float or T=double.
 
 template<typename T>
 JSObject*
@@ -142,16 +143,17 @@ class WasmModuleObject : public NativeOb
 // as internal implementation details of asm.js.
 
 class WasmInstanceObject : public NativeObject
 {
     static const unsigned INSTANCE_SLOT = 0;
     static const unsigned EXPORTS_OBJ_SLOT = 1;
     static const unsigned EXPORTS_SLOT = 2;
     static const unsigned SCOPES_SLOT = 3;
+    static const unsigned INSTANCE_SCOPE_SLOT = 4;
     static const ClassOps classOps_;
     static bool exportsGetterImpl(JSContext* cx, const CallArgs& args);
     static bool exportsGetter(JSContext* cx, unsigned argc, Value* vp);
     bool isNewborn() const;
     static void finalize(FreeOp* fop, JSObject* obj);
     static void trace(JSTracer* trc, JSObject* obj);
 
     // ExportMap maps from function index to exported function object.
@@ -169,17 +171,17 @@ class WasmInstanceObject : public Native
     // during debugging.
     using ScopeMap = JS::WeakCache<GCHashMap<uint32_t,
                                              ReadBarriered<WasmFunctionScope*>,
                                              DefaultHasher<uint32_t>,
                                              SystemAllocPolicy>>;
     ScopeMap& scopes() const;
 
   public:
-    static const unsigned RESERVED_SLOTS = 4;
+    static const unsigned RESERVED_SLOTS = 5;
     static const Class class_;
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static const JSFunctionSpec static_methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     static WasmInstanceObject* create(JSContext* cx,
                                       RefPtr<const wasm::Code> code,
@@ -197,16 +199,17 @@ class WasmInstanceObject : public Native
 
     static bool getExportedFunction(JSContext* cx,
                                     HandleWasmInstanceObject instanceObj,
                                     uint32_t funcIndex,
                                     MutableHandleFunction fun);
 
     const wasm::CodeRange& getExportedFunctionCodeRange(HandleFunction fun, wasm::Tier tier);
 
+    static WasmInstanceScope* getScope(JSContext* cx, HandleWasmInstanceObject instanceObj);
     static WasmFunctionScope* getFunctionScope(JSContext* cx,
                                                HandleWasmInstanceObject instanceObj,
                                                uint32_t funcIndex);
 };
 
 // The class of WebAssembly.Memory. A WasmMemoryObject references an ArrayBuffer
 // or SharedArrayBuffer object which owns the actual memory.
 
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -847,29 +847,29 @@ GetGlobalExport(JSContext* cx, const Glo
         float f = val.f32();
         if (JitOptions.wasmTestMode && IsNaN(f)) {
             RootedObject obj(cx, CreateCustomNaNObject(cx, &f));
             if (!obj)
                 return false;
             jsval.set(ObjectValue(*obj));
             return true;
         }
-        jsval.set(DoubleValue(double(f)));
+        jsval.set(DoubleValue(JS::CanonicalizeNaN(double(f))));
         return true;
       }
       case ValType::F64: {
         double d = val.f64();
         if (JitOptions.wasmTestMode && IsNaN(d)) {
             RootedObject obj(cx, CreateCustomNaNObject(cx, &d));
             if (!obj)
                 return false;
             jsval.set(ObjectValue(*obj));
             return true;
         }
-        jsval.set(DoubleValue(d));
+        jsval.set(DoubleValue(JS::CanonicalizeNaN(d)));
         return true;
       }
       default: {
         break;
       }
     }
     MOZ_CRASH("unexpected type when creating global exports");
 }