Bug 1286948 - Adds scope and environment for wasm calls. r?shu, luke draft
authorYury Delendik <ydelendik@mozilla.com>
Sat, 07 Jan 2017 10:34:31 -0600
changeset 457373 9ffdce038fd7497e9d5ac2e82ffab6856cdc2288
parent 457372 851685cbd4397aca314382094f89a76ae16d0792
child 457374 1f11073d790aae82c14fcee802ad106a20ea4416
push id40734
push userydelendik@mozilla.com
push dateSat, 07 Jan 2017 16:43:50 +0000
reviewersshu, luke
bugs1286948
milestone53.0a1
Bug 1286948 - Adds scope and environment for wasm calls. r?shu, luke Adds artificial JS scope and environment for wasm frames. That allows debugger to properly handle call stack. MozReview-Commit-ID: AgUDw03kK4o
js/src/frontend/BytecodeEmitter.cpp
js/src/gc/Marking.cpp
js/src/gc/Policy.h
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/WasmJS.cpp
js/src/wasm/WasmJS.h
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -731,16 +731,19 @@ BytecodeEmitter::EmitterScope::searchInE
             return NameLocation::Dynamic();
 
           case ScopeKind::Global:
             return NameLocation::Global(BindingKind::Var);
 
           case ScopeKind::With:
           case ScopeKind::NonSyntactic:
             return NameLocation::Dynamic();
+
+          case ScopeKind::WasmFunction:
+            MOZ_CRASH("No direct eval inside wasm functions");
         }
 
         if (hasEnv) {
             MOZ_ASSERT(hops < ENVCOORD_HOPS_LIMIT - 1);
             hops++;
         }
     }
 
@@ -1428,16 +1431,19 @@ BytecodeEmitter::EmitterScope::leave(Byt
       case ScopeKind::NamedLambda:
       case ScopeKind::StrictNamedLambda:
       case ScopeKind::Eval:
       case ScopeKind::StrictEval:
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
       case ScopeKind::Module:
         break;
+
+      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.
         if (ScopeKindIsInBody(kind)) {
             // The extra function var scope is never popped once it's pushed,
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -1259,16 +1259,22 @@ EvalScope::Data::trace(JSTracer* trc)
 }
 void
 ModuleScope::Data::trace(JSTracer* trc)
 {
     TraceNullableEdge(trc, &module, "scope module");
     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_) {
       case ScopeKind::Function:
         reinterpret_cast<FunctionScope::Data*>(data_)->trace(trc);
         break;
@@ -1291,16 +1297,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::WasmFunction:
+        reinterpret_cast<WasmFunctionScope::Data*>(data_)->trace(trc);
+        break;
     }
 }
 inline void
 js::GCMarker::eagerlyMarkChildren(Scope* scope)
 {
     if (scope->enclosing_)
         traverseEdge(scope, static_cast<Scope*>(scope->enclosing_));
     if (scope->environmentShape_)
@@ -1356,16 +1365,24 @@ js::GCMarker::eagerlyMarkChildren(Scope*
         traverseEdge(scope, static_cast<JSObject*>(data->module));
         names = data->names;
         length = data->length;
         break;
       }
 
       case ScopeKind::With:
         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())
                 traverseEdge(scope, static_cast<JSString*>(name));
         }
     } else {
         for (uint32_t i = 0; i < length; i++)
--- a/js/src/gc/Policy.h
+++ b/js/src/gc/Policy.h
@@ -47,16 +47,17 @@ class RegExpObject;
 class SavedFrame;
 class Scope;
 class EnvironmentObject;
 class ScriptSourceObject;
 class Shape;
 class SharedArrayBufferObject;
 class StructTypeDescr;
 class UnownedBaseShape;
+class WasmFunctionScope;
 class WasmMemoryObject;
 namespace jit {
 class JitCode;
 } // namespace jit
 } // namespace js
 
 // Expand the given macro D for each valid GC reference type.
 #define FOR_EACH_INTERNAL_GC_POINTER_TYPE(D) \
@@ -87,16 +88,17 @@ class JitCode;
     D(js::RegExpObject*) \
     D(js::SavedFrame*) \
     D(js::Scope*) \
     D(js::ScriptSourceObject*) \
     D(js::Shape*) \
     D(js::SharedArrayBufferObject*) \
     D(js::StructTypeDescr*) \
     D(js::UnownedBaseShape*) \
+    D(js::WasmFunctionScope*) \
     D(js::WasmInstanceObject*) \
     D(js::WasmMemoryObject*) \
     D(js::WasmTableObject*) \
     D(js::jit::JitCode*)
 
 // Expand the given macro D for each internal tagged GC pointer type.
 #define FOR_EACH_INTERNAL_TAGGED_GC_POINTER_TYPE(D) \
     D(js::TaggedProto)
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -747,16 +747,19 @@ js::XDRScript(XDRState<mode>* xdr, Handl
               case ScopeKind::Global:
               case ScopeKind::NonSyntactic:
                 if (!GlobalScope::XDR(xdr, scopeKind, &scope))
                     return false;
                 break;
               case ScopeKind::Module:
                 MOZ_CRASH("NYI");
                 break;
+              case ScopeKind::WasmFunction:
+                MOZ_CRASH("wasm functions cannot be nested in JSScripts");
+                break;
             }
 
             if (mode == XDR_DECODE)
                 vector[i].init(scope);
         }
     }
 
     /*
--- a/js/src/vm/EnvironmentObject.cpp
+++ b/js/src/vm/EnvironmentObject.cpp
@@ -623,16 +623,47 @@ ModuleEnvironmentObject::enumerate(JSCon
         properties.infallibleAppend(r.front().propid());
 
     MOZ_ASSERT(properties.length() == count);
     return true;
 }
 
 /*****************************************************************************/
 
+const Class WasmFunctionCallObject::class_ = {
+    "WasmCall",
+    JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(WasmFunctionCallObject::RESERVED_SLOTS)
+};
+
+/* static */ WasmFunctionCallObject*
+WasmFunctionCallObject::createHollowForDebug(JSContext* cx, 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;
+
+    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, JSObject::create(cx, kind, gc::DefaultHeap, shape, group));
+
+    Rooted<WasmFunctionCallObject*> callobj(cx, &obj->as<WasmFunctionCallObject>());
+    callobj->initEnclosingEnvironment(&cx->global()->lexicalEnvironment());
+
+    return callobj;
+}
+
+/*****************************************************************************/
+
 WithEnvironmentObject*
 WithEnvironmentObject::create(JSContext* cx, HandleObject object, HandleObject enclosing,
                               Handle<WithScope*> scope)
 {
     Rooted<WithEnvironmentObject*> obj(cx);
     obj = NewObjectWithNullTaggedProto<WithEnvironmentObject>(cx, GenericObject,
                                                               BaseShape::DELEGATE);
     if (!obj)
@@ -2221,16 +2252,17 @@ DebugEnvironmentProxy::initSnapshot(Arra
 
 bool
 DebugEnvironmentProxy::isForDeclarative() const
 {
     EnvironmentObject& e = environment();
     return e.is<CallObject>() ||
            e.is<VarEnvironmentObject>() ||
            e.is<ModuleEnvironmentObject>() ||
+           e.is<WasmFunctionCallObject>() ||
            e.is<LexicalEnvironmentObject>();
 }
 
 bool
 DebugEnvironmentProxy::getMaybeSentinelValue(JSContext* cx, HandleId id, MutableHandleValue vp)
 {
     Rooted<DebugEnvironmentProxy*> self(cx, this);
     return DebugEnvironmentProxyHandler::singleton.getMaybeSentinelValue(cx, self, id, vp);
@@ -2849,16 +2881,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<WasmFunctionScope>() ||
                 ei.scope().is<VarScope>()));
 
     if (DebugEnvironmentProxy* debugEnv = DebugEnvironments::hasDebugEnvironment(cx, ei))
         return debugEnv;
 
     EnvironmentIter copy(cx, ei);
     RootedObject enclosingDebug(cx, GetDebugEnvironment(cx, ++copy));
     if (!enclosingDebug)
@@ -2891,16 +2924,23 @@ 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<WasmFunctionScope>()) {
+        Rooted<WasmFunctionScope*> wasmFunctionScope(cx, &ei.scope().as<WasmFunctionScope>());
+        Rooted<WasmFunctionCallObject*> callobj(cx, WasmFunctionCallObject::createHollowForDebug(cx, 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));
         if (!env)
             return nullptr;
 
         debugEnv = DebugEnvironmentProxy::create(cx, *env, enclosingDebug);
@@ -2935,16 +2975,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<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
@@ -421,16 +421,27 @@ class ModuleEnvironmentObject : public E
     static bool enumerate(JSContext* cx, HandleObject obj, AutoIdVector& properties,
                           bool enumerableOnly);
 };
 
 typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
 typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject;
 typedef MutableHandle<ModuleEnvironmentObject*> MutableHandleModuleEnvironmentObject;
 
+class WasmFunctionCallObject : public EnvironmentObject
+{
+  public:
+    static const Class class_;
+
+    static const uint32_t RESERVED_SLOTS = 1;
+
+    static WasmFunctionCallObject* createHollowForDebug(JSContext* cx,
+                                                        WasmFunctionScope* scope);
+};
+
 class LexicalEnvironmentObject : public EnvironmentObject
 {
     // Global and non-syntactic lexical environments need to store a 'this'
     // value and all other lexical environments have a fixed shape and store a
     // backpointer to the LexicalScope.
     //
     // Since the two sets are disjoint, we only use one slot to save space.
     static const unsigned THIS_VALUE_OR_SCOPE_SLOT = 1;
@@ -979,16 +990,17 @@ class DebugEnvironments
 
 template <>
 inline bool
 JSObject::is<js::EnvironmentObject>() const
 {
     return is<js::CallObject>() ||
            is<js::VarEnvironmentObject>() ||
            is<js::ModuleEnvironmentObject>() ||
+           is<js::WasmFunctionCallObject>() ||
            is<js::LexicalEnvironmentObject>() ||
            is<js::WithEnvironmentObject>() ||
            is<js::NonSyntacticVariablesObject>() ||
            is<js::RuntimeLexicalErrorObject>();
 }
 
 template<>
 bool
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1015,16 +1015,19 @@ 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::WasmFunction:
+        MOZ_CRASH("wasm is not interpreted");
+        break;
     }
 }
 
 // Unwind environment chain and iterator to match the env corresponding to
 // the given bytecode position.
 void
 js::UnwindEnvironment(JSContext* cx, EnvironmentIter& ei, jsbytecode* pc)
 {
--- a/js/src/vm/Scope.cpp
+++ b/js/src/vm/Scope.cpp
@@ -70,16 +70,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::WasmFunction:
+        return "wasm function";
     }
     MOZ_CRASH("Bad ScopeKind");
 }
 
 static Shape*
 EmptyEnvironmentShape(ExclusiveContext* cx, const Class* cls, uint32_t numSlots,
                       uint32_t baseShapeFlags)
 {
@@ -374,19 +376,24 @@ Scope::clone(JSContext* cx, HandleScope 
         return create(cx, scope->kind_, enclosing, envShape, Move(dataClone));
       }
 
       case ScopeKind::Global:
       case ScopeKind::NonSyntactic:
         MOZ_CRASH("Use GlobalScope::clone.");
         break;
 
+      case ScopeKind::WasmFunction:
+        MOZ_CRASH("wasm functions are not nested in JSScript");
+        break;
+
       case ScopeKind::Module:
         MOZ_CRASH("NYI");
         break;
+
     }
 
     return nullptr;
 }
 
 void
 Scope::finalize(FreeOp* fop)
 {
@@ -458,16 +465,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::WasmFunction:
+            // TODO return si.scope()->as<WasmFunctionScope>().nextFrameSlot();
+            return 0;
         }
     }
     MOZ_CRASH("Not an enclosing intra-frame Scope");
 }
 
 /* static */ LexicalScope*
 LexicalScope::create(ExclusiveContext* cx, ScopeKind kind, Handle<Data*> data,
                      uint32_t firstFrameSlot, HandleScope enclosing)
@@ -1137,16 +1147,58 @@ ModuleScope::getEmptyEnvironmentShape(Ex
 }
 
 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 =
+    BaseShape::NOT_EXTENSIBLE | BaseShape::DELEGATE;
+
+/* static */ WasmFunctionScope*
+WasmFunctionScope::create(JSContext* cx, WasmInstanceObject* instance, uint32_t funcIndex)
+{
+    // WasmFunctionScope::Data has GCManagedDeletePolicy because it contains a
+    // GCPtr. Destruction of |data| below may trigger calls into the GC.
+    Rooted<WasmFunctionScope*> wasmFunctionScope(cx);
+
+    {
+        // TODO pull the local variable names from the wasm function definition.
+
+        Rooted<UniquePtr<Data>> data(cx, NewEmptyScopeData<WasmFunctionScope>(cx));
+        if (!data)
+            return nullptr;
+
+        Rooted<Scope*> enclosingScope(cx, &cx->global()->emptyGlobalScope());
+
+        data->instance.init(instance);
+        data->funcIndex = funcIndex;
+
+        Scope* scope = Scope::create(cx, ScopeKind::WasmFunction, enclosingScope, /* envShape = */ nullptr);
+        if (!scope)
+            return nullptr;
+
+        wasmFunctionScope = &scope->as<WasmFunctionScope>();
+        wasmFunctionScope->initData(Move(data.get()));
+    }
+
+    return wasmFunctionScope;
+}
+
+/* static */ Shape*
+WasmFunctionScope::getEmptyEnvironmentShape(ExclusiveContext* cx)
+{
+    const Class* cls = &WasmFunctionCallObject::class_;
+    return EmptyEnvironmentShape(cx, cls, JSSLOT_FREE(cls), WasmFunctionEnvShapeFlags);
+}
+
 ScopeIter::ScopeIter(JSScript* script)
   : scope_(script->bodyScope())
 { }
 
 bool
 ScopeIter::hasSyntacticEnvironment() const
 {
     return scope()->hasEnvironment() && scope()->kind() != ScopeKind::NonSyntactic;
@@ -1188,16 +1240,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::WasmFunction:
+        init(scope->as<WasmFunctionScope>().data());
+        break;
     }
 }
 
 BindingIter::BindingIter(JSScript* script)
   : BindingIter(script->bodyScope())
 { }
 
 void
@@ -1318,16 +1373,32 @@ BindingIter::init(ModuleScope::Data& dat
     //               lets - [data.letStart, data.constStart)
     //             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(WasmFunctionScope::Data& data)
+{
+    //            imports - [0, 0)
+    // positional formals - [0, 0)
+    //      other formals - [0, 0)
+    //    top-level funcs - [0, 0)
+    //               vars - [0, 0)
+    //               lets - [0, 0)
+    //             consts - [0, 0)
+    init(0, 0, 0, 0, 0, 0,
+         CanHaveFrameSlots | CanHaveEnvironmentSlots,
+         UINT32_MAX, UINT32_MAX,
+         data.names, data.length);
+}
+
 PositionalFormalParameterIter::PositionalFormalParameterIter(JSScript* script)
   : BindingIter(script)
 {
     // Reinit with flags = 0, i.e., iterate over all positional parameters.
     if (script->bodyScope()->is<FunctionScope>())
         init(script->bodyScope()->as<FunctionScope>().data(), /* flags = */ 0);
     settle();
 }
--- a/js/src/vm/Scope.h
+++ b/js/src/vm/Scope.h
@@ -65,17 +65,20 @@ enum class ScopeKind : uint8_t
     Eval,
     StrictEval,
 
     // GlobalScope
     Global,
     NonSyntactic,
 
     // ModuleScope
-    Module
+    Module,
+
+    // WasmFunctionScope
+    WasmFunction
 };
 
 static inline bool
 ScopeKindIsCatch(ScopeKind kind)
 {
     return kind == ScopeKind::SimpleCatch || kind == ScopeKind::Catch;
 }
 
@@ -872,16 +875,67 @@ class ModuleScope : public Scope
         return data().module;
     }
 
     JSScript* script() const;
 
     static Shape* getEmptyEnvironmentShape(ExclusiveContext* 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;
+
+        // 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 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:
+    WasmInstanceObject* instance() const {
+        return data().instance;
+    }
+
+    uint32_t funcIndex() const {
+        return data().funcIndex;
+    }
+
+    static Shape* getEmptyEnvironmentShape(ExclusiveContext* cx);
+};
+
 //
 // An iterator for a Scope's bindings. This is the source of truth for frame
 // and environment object layout.
 //
 // It may be placed in GC containers; for example:
 //
 //   for (Rooted<BindingIter> bi(cx, BindingIter(scope)); bi; bi++) {
 //     use(bi);
@@ -981,16 +1035,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(WasmFunctionScope::Data& data);
 
     bool hasFormalParameterExprs() const {
         return flags_ & HasFormalParameterExprs;
     }
 
     bool ignoreDestructuredFormalParameters() const {
         return flags_ & IgnoreDestructuredFormalParameters;
     }
@@ -1053,16 +1108,20 @@ class BindingIter
     explicit BindingIter(GlobalScope::Data& data) {
         init(data);
     }
 
     explicit BindingIter(ModuleScope::Data& data) {
         init(data);
     }
 
+    explicit BindingIter(WasmFunctionScope::Data& data) {
+        init(data);
+    }
+
     BindingIter(EvalScope::Data& data, bool strict) {
         init(data, strict);
     }
 
     explicit BindingIter(const BindingIter& bi) = default;
 
     bool done() const {
         return index_ == length_;
@@ -1409,16 +1468,17 @@ struct ScopeDataGCPolicy
     { }
 
 DEFINE_SCOPE_DATA_GCPOLICY(js::LexicalScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::FunctionScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::VarScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::GlobalScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::EvalScope::Data);
 DEFINE_SCOPE_DATA_GCPOLICY(js::ModuleScope::Data);
+DEFINE_SCOPE_DATA_GCPOLICY(js::WasmFunctionScope::Data);
 
 #undef DEFINE_SCOPE_DATA_GCPOLICY
 
 namespace ubi {
 
 template <>
 class Concrete<js::Scope> : TracerConcrete<js::Scope>
 {
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -151,16 +151,20 @@ AssertScopeMatchesEnvironment(Scope* sco
                 MOZ_CRASH("NonSyntactic should not have a syntactic environment");
                 break;
 
               case ScopeKind::Module:
                 MOZ_ASSERT(env->as<ModuleEnvironmentObject>().module().script() ==
                            si.scope()->as<ModuleScope>().script());
                 env = &env->as<ModuleEnvironmentObject>().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
     // outermost non-syntactic env may be the global lexical env, or, if
     // called from Debugger, a DebugEnvironmentProxy.
     //
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -903,16 +903,17 @@ WasmInstanceObject::isNewborn() const
     MOZ_ASSERT(is<WasmInstanceObject>());
     return getReservedSlot(INSTANCE_SLOT).isUndefined();
 }
 
 /* static */ void
 WasmInstanceObject::finalize(FreeOp* fop, JSObject* obj)
 {
     fop->delete_(&obj->as<WasmInstanceObject>().exports());
+    fop->delete_(&obj->as<WasmInstanceObject>().scopes());
     if (!obj->as<WasmInstanceObject>().isNewborn())
         fop->delete_(&obj->as<WasmInstanceObject>().instance());
 }
 
 /* static */ void
 WasmInstanceObject::trace(JSTracer* trc, JSObject* obj)
 {
     WasmInstanceObject& instanceObj = obj->as<WasmInstanceObject>();
@@ -931,22 +932,29 @@ WasmInstanceObject::create(JSContext* cx
                            HandleObject proto)
 {
     UniquePtr<ExportMap> exports = js::MakeUnique<ExportMap>();
     if (!exports || !exports->init()) {
         ReportOutOfMemory(cx);
         return nullptr;
     }
 
+    UniquePtr<WeakScopeMap> scopes = js::MakeUnique<WeakScopeMap>(cx->zone(), ScopeMap());
+    if (!scopes || !scopes->init()) {
+        ReportOutOfMemory(cx);
+        return nullptr;
+    }
+
     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()));
     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,
                                         Move(code),
@@ -1030,16 +1038,22 @@ WasmInstanceObject::instance() const
 }
 
 WasmInstanceObject::ExportMap&
 WasmInstanceObject::exports() const
 {
     return *(ExportMap*)getReservedSlot(EXPORTS_SLOT).toPrivate();
 }
 
+WasmInstanceObject::WeakScopeMap&
+WasmInstanceObject::scopes() const
+{
+    return *(WeakScopeMap*)getReservedSlot(SCOPES_SLOT).toPrivate();
+}
+
 static bool
 WasmCall(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedFunction callee(cx, &args.callee().as<JSFunction>());
 
     Instance& instance = ExportedFunctionToInstance(callee);
     uint32_t funcIndex = ExportedFunctionToFuncIndex(callee);
@@ -1094,16 +1108,35 @@ const CodeRange&
 WasmInstanceObject::getExportedFunctionCodeRange(HandleFunction fun)
 {
     uint32_t funcIndex = ExportedFunctionToFuncIndex(fun);
     MOZ_ASSERT(exports().lookup(funcIndex)->value() == fun);
     const Metadata& metadata = instance().metadata();
     return metadata.codeRanges[metadata.lookupFuncExport(funcIndex).codeRangeIndex()];
 }
 
+/* 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));
+    if (!funcScope)
+        return nullptr;
+
+    if (!instanceObj->scopes().putNew(funcIndex, funcScope)) {
+        ReportOutOfMemory(cx);
+        return nullptr;
+    }
+
+    return funcScope;
+}
+
 bool
 wasm::IsExportedFunction(JSFunction* fun)
 {
     return fun->maybeNative() == WasmCall;
 }
 
 bool
 wasm::IsExportedWasmFunction(JSFunction* fun)
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -21,16 +21,17 @@
 
 #include "gc/Policy.h"
 #include "vm/NativeObject.h"
 #include "wasm/WasmTypes.h"
 
 namespace js {
 
 class TypedArrayObject;
+class WasmFunctionScope;
 
 namespace wasm {
 
 // Creates a testing-only NaN JS object with fields as described above, for
 // T=float or T=double.
 
 template<typename T>
 JSObject*
@@ -143,33 +144,44 @@ class WasmModuleObject : public NativeOb
 // The class of WebAssembly.Instance. Each WasmInstanceObject owns a
 // wasm::Instance. These objects are used both as content-facing JS objects and
 // as internal implementation details of asm.js.
 
 class WasmInstanceObject : public NativeObject
 {
     static const unsigned INSTANCE_SLOT = 0;
     static const unsigned EXPORTS_SLOT = 1;
+    static const unsigned SCOPES_SLOT = 2;
     static const ClassOps classOps_;
     bool isNewborn() const;
     static void finalize(FreeOp* fop, JSObject* obj);
     static void trace(JSTracer* trc, JSObject* obj);
 
-    // ExportMap maps from function definition index to exported function
-    // object. This allows the instance to lazily create exported function
+    // ExportMap maps from function index to exported function object.
+    // This allows the instance to lazily create exported function
     // objects on demand (instead up-front for all table elements) while
     // correctly preserving observable function object identity.
     using ExportMap = GCHashMap<uint32_t,
                                 HeapPtr<JSFunction*>,
                                 DefaultHasher<uint32_t>,
                                 SystemAllocPolicy>;
     ExportMap& exports() const;
 
+    // WeakScopeMap maps from function index to js::Scope. This maps is weak
+    // to avoid holding scope objects alive. The scopes are normally created
+    // during debugging.
+    using ScopeMap = GCHashMap<uint32_t,
+                               ReadBarriered<WasmFunctionScope*>,
+                               DefaultHasher<uint32_t>,
+                               SystemAllocPolicy>;
+    using WeakScopeMap = JS::WeakCache<ScopeMap>;
+    WeakScopeMap& scopes() const;
+
   public:
-    static const unsigned RESERVED_SLOTS = 2;
+    static const unsigned RESERVED_SLOTS = 3;
     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,
                                       UniquePtr<wasm::Code> code,
@@ -181,16 +193,20 @@ class WasmInstanceObject : public Native
     wasm::Instance& instance() const;
 
     static bool getExportedFunction(JSContext* cx,
                                     HandleWasmInstanceObject instanceObj,
                                     uint32_t funcIndex,
                                     MutableHandleFunction fun);
 
     const wasm::CodeRange& getExportedFunctionCodeRange(HandleFunction fun);
+
+    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.
 
 class WasmMemoryObject : public NativeObject
 {
     static const unsigned BUFFER_SLOT = 0;