Bug 930414 - Add module scopes, using ModuleObject as the static scope and ModuleEnvironementObject as the dynamic scope r=shu
authorJon Coppeard <jcoppeard@mozilla.com>
Mon, 24 Aug 2015 15:58:36 +0100
changeset 259051 9ac1f5052b91cdc341570cb2f8c03efc561faa54
parent 259050 178d594d4934fa20df30f2883460ede1d15eb3ec
child 259052 0773712473c9cea41fa3a063f97cbd2dc55d86a4
push id29268
push userryanvm@gmail.com
push dateTue, 25 Aug 2015 00:37:23 +0000
treeherdermozilla-central@08015770c9d6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersshu
bugs930414
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 930414 - Add module scopes, using ModuleObject as the static scope and ModuleEnvironementObject as the dynamic scope r=shu
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/jsobjinlines.h
js/src/jsscript.cpp
js/src/jsscript.h
js/src/shell/js.cpp
js/src/vm/Interpreter.cpp
js/src/vm/ScopeObject-inl.h
js/src/vm/ScopeObject.cpp
js/src/vm/ScopeObject.h
js/src/vm/Stack.cpp
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -408,16 +408,18 @@ AppendPackedBindings(const ParseContext<
     }
 }
 
 template <typename ParseHandler>
 bool
 ParseContext<ParseHandler>::generateBindings(ExclusiveContext* cx, TokenStream& ts, LifoAlloc& alloc,
                                              MutableHandle<Bindings> bindings) const
 {
+    MOZ_ASSERT_IF(sc->isFunctionBox(), args_.length() < ARGNO_LIMIT);
+    MOZ_ASSERT_IF(sc->isModuleBox(), args_.length() == 0);
     MOZ_ASSERT(vars_.length() + bodyLevelLexicals_.length() < LOCALNO_LIMIT);
 
     /*
      * Avoid pathological edge cases by explicitly limiting the total number of
      * bindings to what will fit in a uint32_t.
      */
     if (UINT32_MAX - args_.length() <= vars_.length() + bodyLevelLexicals_.length())
         return ts.reportError(JSMSG_TOO_MANY_LOCALS);
@@ -443,39 +445,17 @@ ParseContext<ParseHandler>::generateBind
     AppendPackedBindings(this, args_, packedBindings);
     AppendPackedBindings(this, vars_, packedBindings + args_.length(), &numUnaliasedVars);
     AppendPackedBindings(this, bodyLevelLexicals_,
                          packedBindings + args_.length() + vars_.length(), &numUnaliasedBodyLevelLexicals);
 
     return Bindings::initWithTemporaryStorage(cx, bindings, args_.length(), vars_.length(),
                                               bodyLevelLexicals_.length(), blockScopeDepth,
                                               numUnaliasedVars, numUnaliasedBodyLevelLexicals,
-                                              packedBindings);
-}
-
-template <typename ParseHandler>
-bool
-ParseContext<ParseHandler>::generateFunctionBindings(ExclusiveContext* cx, TokenStream& ts,
-                                                     LifoAlloc& alloc,
-                                                     MutableHandle<Bindings> bindings) const
-{
-    MOZ_ASSERT(sc->isFunctionBox());
-    MOZ_ASSERT(args_.length() < ARGNO_LIMIT);
-    return generateBindings(cx, ts, alloc, bindings);
-}
-
-template <typename ParseHandler>
-bool
-ParseContext<ParseHandler>::generateModuleBindings(ExclusiveContext* cx, TokenStream& ts,
-                                                   LifoAlloc& alloc,
-                                                   MutableHandle<Bindings> bindings) const
-{
-    MOZ_ASSERT(sc->isModuleBox());
-    MOZ_ASSERT(args_.length() == 0);
-    return generateBindings(cx, ts, alloc, bindings);
+                                              packedBindings, sc->isModuleBox());
 }
 
 template <typename ParseHandler>
 bool
 Parser<ParseHandler>::reportHelper(ParseReportKind kind, bool strict, uint32_t offset,
                                    unsigned errorNumber, va_list args)
 {
     bool result = false;
@@ -878,17 +858,17 @@ Parser<ParseHandler>::standaloneModule(H
     if (!tokenStream.getToken(&tt, TokenStream::Operand))
         return null();
     MOZ_ASSERT(tt == TOK_EOF);
 
     if (!FoldConstants(context, &pn, this))
         return null();
 
     Rooted<Bindings> bindings(context, modulebox->bindings);
-    if (!modulepc.generateModuleBindings(context, tokenStream, alloc, &bindings))
+    if (!modulepc.generateBindings(context, tokenStream, alloc, &bindings))
         return null();
     modulebox->bindings = bindings;
 
     MOZ_ASSERT(mn->pn_modulebox == modulebox);
     return mn;
 }
 
 template <>
@@ -968,17 +948,17 @@ Parser<FullParseHandler>::standaloneFunc
             Definition* dn = r.front().value().get<FullParseHandler>();
             MOZ_ASSERT(dn->isPlaceholder());
 
             handler.deoptimizeUsesWithin(dn, fn->pn_pos);
         }
     }
 
     Rooted<Bindings> bindings(context, funbox->bindings);
-    if (!funpc.generateFunctionBindings(context, tokenStream, alloc, &bindings))
+    if (!funpc.generateBindings(context, tokenStream, alloc, &bindings))
         return null();
     funbox->bindings = bindings;
 
     return fn;
 }
 
 template <>
 bool
@@ -1524,17 +1504,17 @@ ConvertDefinitionToNamedLambdaUse(TokenS
     dn->pn_blockid = pc->blockid();
     dn->pn_dflags |= PND_BOUND;
     MOZ_ASSERT(dn->kind() == Definition::NAMED_LAMBDA);
 
     /*
      * Since 'dn' is a placeholder, it has not been defined in the
      * ParseContext and hence we must manually flag a closed-over
      * callee name as needing a dynamic scope (this is done for all
-     * definitions in the ParseContext by generateFunctionBindings).
+     * definitions in the ParseContext by generateBindings).
      *
      * If 'dn' has been assigned to, then we also flag the function
      * scope has needing a dynamic scope so that dynamic scope
      * setter can either ignore the set (in non-strict mode) or
      * produce an error (in strict mode).
      */
     if (dn->isClosed() || dn->isAssigned())
         funbox->setNeedsDeclEnvObject();
@@ -1685,17 +1665,17 @@ Parser<FullParseHandler>::leaveFunction(
             }
 
             /* Mark the outer dn as escaping. */
             outer_dn->pn_dflags |= PND_CLOSED;
         }
     }
 
     Rooted<Bindings> bindings(context, funbox->bindings);
-    if (!pc->generateFunctionBindings(context, tokenStream, alloc, &bindings))
+    if (!pc->generateBindings(context, tokenStream, alloc, &bindings))
         return false;
     funbox->bindings = bindings;
 
     return true;
 }
 
 template <>
 bool
@@ -2711,17 +2691,17 @@ Parser<FullParseHandler>::standaloneLazy
         if (AtomDefnPtr p = pc->lexdeps->lookup(fun->name())) {
             Definition* dn = p.value().get<FullParseHandler>();
             if (!ConvertDefinitionToNamedLambdaUse(tokenStream, pc, funbox, dn))
                 return nullptr;
         }
     }
 
     Rooted<Bindings> bindings(context, funbox->bindings);
-    if (!pc->generateFunctionBindings(context, tokenStream, alloc, &bindings))
+    if (!pc->generateBindings(context, tokenStream, alloc, &bindings))
         return null();
     funbox->bindings = bindings;
 
     if (!FoldConstants(context, &pn, this))
         return null();
 
     return pn;
 }
@@ -3226,17 +3206,17 @@ Parser<FullParseHandler>::bindLexical(Bi
 
     // For block-level lets, assign block-local index to pn->pn_scopecoord
     // right away. The emitter will adjust the node's slot based on its
     // stack depth model -- and, for global and eval code,
     // js::frontend::CompileScript will adjust the slot again to include
     // script->nfixed and body-level lets.
     //
     // For body-level lets, the index is bogus at this point and is adjusted
-    // when creating Bindings. See ParseContext::generateFunctionBindings and
+    // when creating Bindings. See ParseContext::generateBindings and
     // AppendPackedBindings.
     if (!pn->pn_scopecoord.setSlot(parser->tokenStream, index))
         return false;
 
     Definition* dn = pc->decls().lookupFirst(name);
     Definition::Kind bindingKind = data->isConst() ? Definition::CONST : Definition::LET;
 
     /*
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -207,29 +207,19 @@ struct MOZ_STACK_CLASS ParseContext : pu
      *    the use/def nodes, but the emitter occasionally needs 'bindings' for
      *    various scope-related queries.
      *  - Bindings provide the initial js::Shape to use when creating a dynamic
      *    scope object (js::CallObject). This shape is used during dynamic name
      *    lookup.
      *  - Sometimes a script's bindings are accessed at runtime to retrieve the
      *    contents of the lexical scope (e.g., from the debugger).
      */
-  private:
     bool generateBindings(ExclusiveContext* cx, TokenStream& ts, LifoAlloc& alloc,
                           MutableHandle<Bindings> bindings) const;
 
-  public:
-    bool generateFunctionBindings(ExclusiveContext* cx, TokenStream& ts,
-                                  LifoAlloc& alloc,
-                                  MutableHandle<Bindings> bindings) const;
-
-    bool generateModuleBindings(ExclusiveContext* cx, TokenStream& ts,
-                                LifoAlloc& alloc,
-                                MutableHandle<Bindings> bindings) const;
-
   private:
     ParseContext**  parserPC;     /* this points to the Parser's active pc
                                        and holds either |this| or one of
                                        |this|'s descendents */
 
     // Value for parserPC to restore at the end. Use 'parent' instead for
     // information about the parse chain, this may be nullptr if
     // parent != nullptr.
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -225,16 +225,17 @@ inline bool
 JSObject::isQualifiedVarObj() const
 {
     if (is<js::DebugScopeObject>())
         return as<js::DebugScopeObject>().scope().isQualifiedVarObj();
     bool rv = hasAllFlags(js::BaseShape::QUALIFIED_VAROBJ);
     MOZ_ASSERT_IF(rv,
                   is<js::GlobalObject>() ||
                   is<js::CallObject>() ||
+                  is<js::ModuleEnvironmentObject>() ||
                   is<js::NonSyntacticVariablesObject>() ||
                   (is<js::DynamicWithObject>() && !as<js::DynamicWithObject>().isSyntactic()));
     return rv;
 }
 
 inline bool
 JSObject::isUnqualifiedVarObj() const
 {
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -70,17 +70,17 @@ Bindings::argumentsBinding(ExclusiveCont
     return bi;
 }
 
 bool
 Bindings::initWithTemporaryStorage(ExclusiveContext* cx, MutableHandle<Bindings> self,
                                    uint32_t numArgs, uint32_t numVars,
                                    uint32_t numBodyLevelLexicals, uint32_t numBlockScoped,
                                    uint32_t numUnaliasedVars, uint32_t numUnaliasedBodyLevelLexicals,
-                                   const Binding* bindingArray)
+                                   const Binding* bindingArray, bool isModule /* = false */)
 {
     MOZ_ASSERT(!self.callObjShape());
     MOZ_ASSERT(self.bindingArrayUsingTemporaryStorage());
     MOZ_ASSERT(!self.bindingArray());
     MOZ_ASSERT(!(uintptr_t(bindingArray) & TEMPORARY_STORAGE_BIT));
     MOZ_ASSERT(numArgs <= ARGC_LIMIT);
     MOZ_ASSERT(numVars <= LOCALNO_LIMIT);
     MOZ_ASSERT(numBlockScoped <= LOCALNO_LIMIT);
@@ -136,19 +136,20 @@ Bindings::initWithTemporaryStorage(Exclu
         }
     }
     self.setAliasedBodyLevelLexicalBegin(aliasedBodyLevelLexicalBegin);
 
     // Put as many of nslots inline into the object header as possible.
     uint32_t nfixed = gc::GetGCKindSlots(gc::GetGCObjectKind(nslots));
 
     // Start with the empty shape and then append one shape per aliased binding.
+    const Class* cls = isModule ? &ModuleEnvironmentObject::class_ : &CallObject::class_;
     RootedShape shape(cx,
-        EmptyShape::getInitialShape(cx, &CallObject::class_, TaggedProto(nullptr),
-                                    nfixed, BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE));
+        EmptyShape::getInitialShape(cx, cls, TaggedProto(nullptr), nfixed,
+                                    BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE));
     if (!shape)
         return false;
 
 #ifdef DEBUG
     HashSet<PropertyName*> added(cx);
     if (!added.init()) {
         ReportOutOfMemory(cx);
         return false;
@@ -164,19 +165,17 @@ Bindings::initWithTemporaryStorage(Exclu
         // The caller ensures no duplicate aliased names.
         MOZ_ASSERT(!added.has(bi->name()));
         if (!added.put(bi->name())) {
             ReportOutOfMemory(cx);
             return false;
         }
 #endif
 
-        StackBaseShape stackBase(cx, &CallObject::class_,
-                                 BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE);
-
+        StackBaseShape stackBase(cx, cls, BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE);
         UnownedBaseShape* base = BaseShape::getUnowned(cx, stackBase);
         if (!base)
             return false;
 
         unsigned attrs = JSPROP_PERMANENT |
                          JSPROP_ENUMERATE |
                          (bi->kind() == Binding::CONSTANT ? JSPROP_READONLY : 0);
         Rooted<StackShape> child(cx, StackShape(base, NameToId(bi->name()), slot, attrs, 0));
--- a/js/src/jsscript.h
+++ b/js/src/jsscript.h
@@ -269,17 +269,18 @@ class Bindings : public JS::Traceable
      */
     static bool initWithTemporaryStorage(ExclusiveContext* cx, MutableHandle<Bindings> self,
                                          uint32_t numArgs,
                                          uint32_t numVars,
                                          uint32_t numBodyLevelLexicals,
                                          uint32_t numBlockScoped,
                                          uint32_t numUnaliasedVars,
                                          uint32_t numUnaliasedBodyLevelLexicals,
-                                         const Binding* bindingArray);
+                                         const Binding* bindingArray,
+                                         bool isModule = false);
 
     // Initialize a trivial Bindings with no slots and an empty callObjShape.
     bool initTrivial(ExclusiveContext* cx);
 
     // CompileScript parses and compiles one statement at a time, but the result
     // is one Script object.  There will be no vars or bindings, because those
     // go on the global, but there may be block-scoped locals, and the number of
     // block-scoped locals may increase as we parse more expressions.  This
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -4240,28 +4240,38 @@ DumpStaticScopeChain(JSContext* cx, unsi
     CallArgs args = CallArgsFromVp(argc, vp);
     RootedObject callee(cx, &args.callee());
 
     if (args.length() != 1) {
         ReportUsageError(cx, callee, "Wrong number of arguments");
         return false;
     }
 
-    if (!args[0].isObject() || !args[0].toObject().is<JSFunction>()) {
-        ReportUsageError(cx, callee, "Argument must be an interpreted function");
-        return false;
-    }
-
-    RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
-    if (!fun->isInterpreted()) {
-        ReportUsageError(cx, callee, "Argument must be an interpreted function");
-        return false;
-    }
-
-    js::DumpStaticScopeChain(fun->getOrCreateScript(cx));
+    if (!args[0].isObject() ||
+        !(args[0].toObject().is<JSFunction>() || args[0].toObject().is<ModuleObject>()))
+    {
+        ReportUsageError(cx, callee, "Argument must be an interpreted function or a module");
+        return false;
+    }
+
+    RootedObject obj(cx, &args[0].toObject());
+    RootedScript script(cx);
+
+    if (obj->is<JSFunction>()) {
+        RootedFunction fun(cx, &obj->as<JSFunction>());
+        if (!fun->isInterpreted()) {
+            ReportUsageError(cx, callee, "Argument must be an interpreted function");
+            return false;
+        }
+        script = fun->getOrCreateScript(cx);
+    } else {
+        script = obj->as<ModuleObject>().script();
+    }
+
+    js::DumpStaticScopeChain(script);
 
     args.rval().setUndefined();
     return true;
 }
 #endif
 
 namespace js {
 namespace shell {
@@ -4905,18 +4915,18 @@ static const JSFunctionSpecWithHelp fuzz
     JS_FN_HELP("trackedOpts", ReflectTrackedOptimizations, 1, 0,
 "trackedOpts(fun)",
 "  Returns an object describing the tracked optimizations of |fun|, if\n"
 "  any. If |fun| is not a scripted function or has not been compiled by\n"
 "  Ion, null is returned."),
 
 #ifdef DEBUG
     JS_FN_HELP("dumpStaticScopeChain", DumpStaticScopeChain, 1, 0,
-"dumpStaticScopeChain(fun)",
-"  Prints the static scope chain of an interpreted function fun."),
+"dumpStaticScopeChain(obj)",
+"  Prints the static scope chain of an interpreted function or a module."),
 #endif
 
     JS_FS_HELP_END
 };
 
 static const JSFunctionSpecWithHelp console_functions[] = {
     JS_FN_HELP("log", Print, 0, 0,
 "log([exp ...])",
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -1254,16 +1254,17 @@ PopScope(JSContext* cx, ScopeIter& si)
         if (cx->compartment()->isDebuggee())
             DebugScopes::onPopBlock(cx, si);
         if (si.staticBlock().needsClone())
             si.initialFrame().popBlock(cx);
         break;
       case ScopeIter::With:
         si.initialFrame().popWith(cx);
         break;
+      case ScopeIter::Module:
       case ScopeIter::Call:
       case ScopeIter::Eval:
       case ScopeIter::NonSyntactic:
         break;
     }
 }
 
 // Unwind scope chain and iterator to match the static scope corresponding to
--- a/js/src/vm/ScopeObject-inl.h
+++ b/js/src/vm/ScopeObject-inl.h
@@ -75,16 +75,18 @@ inline void
 StaticScopeIter<allowGC>::operator++(int)
 {
     if (obj->template is<NestedScopeObject>()) {
         obj = obj->template as<NestedScopeObject>().enclosingScopeForStaticScopeIter();
     } else if (obj->template is<StaticEvalObject>()) {
         obj = obj->template as<StaticEvalObject>().enclosingScopeForStaticScopeIter();
     } else if (obj->template is<StaticNonSyntacticScopeObjects>()) {
         obj = obj->template as<StaticNonSyntacticScopeObjects>().enclosingScopeForStaticScopeIter();
+    } else if (obj->template is<ModuleObject>()) {
+        obj = obj->template as<ModuleObject>().script()->enclosingStaticScope();
     } else if (onNamedLambda || !obj->template as<JSFunction>().isNamedLambda()) {
         onNamedLambda = false;
         JSFunction& fun = obj->template as<JSFunction>();
         if (fun.isBeingParsed())
             obj = fun.functionBox()->enclosingStaticScope();
         else
             obj = fun.nonLazyScript()->enclosingStaticScope();
     } else {
@@ -99,16 +101,18 @@ inline bool
 StaticScopeIter<allowGC>::hasSyntacticDynamicScopeObject() const
 {
     if (obj->template is<JSFunction>()) {
         JSFunction& fun = obj->template as<JSFunction>();
         if (fun.isBeingParsed())
             return fun.functionBox()->isHeavyweight();
         return fun.isHeavyweight();
     }
+    if (obj->template is<ModuleObject>())
+        return true;
     if (obj->template is<StaticBlockObject>())
         return obj->template as<StaticBlockObject>().needsClone();
     if (obj->template is<StaticWithObject>())
         return true;
     if (obj->template is<StaticEvalObject>())
         return obj->template as<StaticEvalObject>().isStrict();
     MOZ_ASSERT(obj->template is<StaticNonSyntacticScopeObjects>());
     return false;
@@ -117,34 +121,38 @@ StaticScopeIter<allowGC>::hasSyntacticDy
 template <AllowGC allowGC>
 inline Shape*
 StaticScopeIter<allowGC>::scopeShape() const
 {
     MOZ_ASSERT(hasSyntacticDynamicScopeObject());
     MOZ_ASSERT(type() != NamedLambda && type() != Eval);
     if (type() == Block)
         return block().lastProperty();
+    if (type() == Module)
+        return moduleScript()->callObjShape();
     return funScript()->callObjShape();
 }
 
 template <AllowGC allowGC>
 inline typename StaticScopeIter<allowGC>::Type
 StaticScopeIter<allowGC>::type() const
 {
     if (onNamedLambda)
         return NamedLambda;
-    return obj->template is<StaticBlockObject>()
-           ? Block
-           : (obj->template is<StaticWithObject>()
-              ? With
-              : (obj->template is<StaticEvalObject>()
-                 ? Eval
-                 : (obj->template is<StaticNonSyntacticScopeObjects>())
-                 ? NonSyntactic
-                 : Function));
+    if (obj->template is<StaticBlockObject>())
+        return Block;
+    if (obj->template is<StaticWithObject>())
+        return With;
+    if (obj->template is<StaticEvalObject>())
+        return Eval;
+    if (obj->template is<StaticNonSyntacticScopeObjects>())
+        return NonSyntactic;
+    if (obj->template is<ModuleObject>())
+        return Module;
+    return Function;
 }
 
 template <AllowGC allowGC>
 inline StaticBlockObject&
 StaticScopeIter<allowGC>::block() const
 {
     MOZ_ASSERT(type() == Block);
     return obj->template as<StaticBlockObject>();
@@ -195,16 +203,32 @@ inline frontend::FunctionBox*
 StaticScopeIter<allowGC>::maybeFunctionBox() const
 {
     MOZ_ASSERT(type() == Function);
     if (fun().isBeingParsed())
         return fun().functionBox();
     return nullptr;
 }
 
+template <AllowGC allowGC>
+inline JSScript*
+StaticScopeIter<allowGC>::moduleScript() const
+{
+    MOZ_ASSERT(type() == Module);
+    return obj->template as<ModuleObject>().script();
+}
+
+template <AllowGC allowGC>
+inline ModuleObject&
+StaticScopeIter<allowGC>::module() const
+{
+    MOZ_ASSERT(type() == Module);
+    return obj->template as<ModuleObject>();
+}
+
 }  /* namespace js */
 
 inline JSObject*
 JSObject::enclosingScope()
 {
     if (is<js::ScopeObject>())
         return &as<js::ScopeObject>().enclosingScope();
 
--- a/js/src/vm/ScopeObject.cpp
+++ b/js/src/vm/ScopeObject.cpp
@@ -186,18 +186,18 @@ CallObject::createTemplateObject(JSConte
     gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
     MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
     kind = gc::GetBackgroundAllocKind(kind);
 
     JSObject* obj = JSObject::create(cx, kind, heap, shape, group);
     if (!obj)
         return nullptr;
 
-    // Set uninitialized lexicals even on template objects, as Ion will use
-    // copy over the template object's slot values in the fast path.
+    // Set uninitialized lexicals even on template objects, as Ion will copy
+    // over the template object's slot values in the fast path.
     obj->as<CallObject>().initAliasedLexicalsToThrowOnTouch(script);
 
     return &obj->as<CallObject>();
 }
 
 /*
  * Construct a call object for the given bindings.  If this is a call object
  * for a function invocation, callee should be the function being called.
@@ -306,16 +306,69 @@ CallObject::createHollowForDebug(JSConte
     return callobj;
 }
 
 const Class CallObject::class_ = {
     "Call",
     JSCLASS_IS_ANONYMOUS | JSCLASS_HAS_RESERVED_SLOTS(CallObject::RESERVED_SLOTS)
 };
 
+/*****************************************************************************/
+
+const Class ModuleEnvironmentObject::class_ = {
+    "ModuleEnvironmentObject",
+    JSCLASS_IMPLEMENTS_BARRIERS |
+    JSCLASS_HAS_RESERVED_SLOTS(ModuleEnvironmentObject::RESERVED_SLOTS) |
+    JSCLASS_IS_ANONYMOUS
+};
+
+/* static */ ModuleEnvironmentObject*
+ModuleEnvironmentObject::create(ExclusiveContext* cx, HandleModuleObject module)
+{
+    RootedScript script(cx, module->script());
+    RootedShape shape(cx, script->bindings.callObjShape());
+    MOZ_ASSERT(shape->getObjectClass() == &class_);
+
+    RootedObjectGroup group(cx, ObjectGroup::defaultNewGroup(cx, &class_, TaggedProto(nullptr)));
+    if (!group)
+        return nullptr;
+
+    gc::AllocKind kind = gc::GetGCObjectKind(shape->numFixedSlots());
+    MOZ_ASSERT(CanBeFinalizedInBackground(kind, &class_));
+    kind = gc::GetBackgroundAllocKind(kind);
+
+    JSObject* obj = JSObject::create(cx, kind, TenuredHeap, shape, group);
+    if (!obj)
+        return nullptr;
+
+    Rooted<ModuleEnvironmentObject*> scope(cx, &obj->as<ModuleEnvironmentObject>());
+
+    // Set uninitialized lexicals even on template objects, as Ion will use
+    // copy over the template object's slot values in the fast path.
+    scope->initAliasedLexicalsToThrowOnTouch(script);
+
+    scope->initFixedSlot(MODULE_SLOT, ObjectValue(*module));
+    if (!JSObject::setSingleton(cx, scope))
+        return nullptr;
+
+    // Initialize this early so that we can manipulate the scope object without
+    // causing assertions.
+    scope->setEnclosingScope(cx->global());
+
+    return scope;
+}
+
+ModuleObject&
+ModuleEnvironmentObject::module() const
+{
+    return getReservedSlot(MODULE_SLOT).toObject().as<ModuleObject>();
+}
+
+/*****************************************************************************/
+
 const Class DeclEnvObject::class_ = {
     js_Object_str,
     JSCLASS_HAS_RESERVED_SLOTS(DeclEnvObject::RESERVED_SLOTS) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Object)
 };
 
 /*
  * Create a DeclEnvObject for a JSScript that is not initialized to any
@@ -1070,16 +1123,19 @@ ScopeIter::settle()
     // Check if we have left the extent of the initial frame after we've
     // settled on a static scope.
     if (frame_ && (ssi_.done() || maybeStaticScope() == frame_.script()->enclosingStaticScope()))
         frame_ = NullFramePtr();
 
 #ifdef DEBUG
     if (!ssi_.done() && hasAnyScopeObject()) {
         switch (ssi_.type()) {
+          case StaticScopeIter<CanGC>::Module:
+            MOZ_ASSERT(scope_->as<ModuleEnvironmentObject>().module() == ssi_.module());
+            break;
           case StaticScopeIter<CanGC>::Function:
             MOZ_ASSERT(scope_->as<CallObject>().callee().nonLazyScript() == ssi_.funScript());
             break;
           case StaticScopeIter<CanGC>::Block:
             MOZ_ASSERT(scope_->as<ClonedBlockObject>().staticBlock() == staticBlock());
             break;
           case StaticScopeIter<CanGC>::With:
             MOZ_ASSERT(scope_->as<DynamicWithObject>().staticScope() == &staticWith());
@@ -1113,16 +1169,18 @@ ScopeIter::operator++()
 }
 
 ScopeIter::Type
 ScopeIter::type() const
 {
     MOZ_ASSERT(!done());
 
     switch (ssi_.type()) {
+      case StaticScopeIter<CanGC>::Module:
+        return Module;
       case StaticScopeIter<CanGC>::Function:
         return Call;
       case StaticScopeIter<CanGC>::Block:
         return Block;
       case StaticScopeIter<CanGC>::With:
         return With;
       case StaticScopeIter<CanGC>::Eval:
         return Eval;
@@ -1146,16 +1204,18 @@ JSObject*
 ScopeIter::maybeStaticScope() const
 {
     if (ssi_.done())
         return nullptr;
 
     switch (ssi_.type()) {
       case StaticScopeIter<CanGC>::Function:
         return &fun();
+      case StaticScopeIter<CanGC>::Module:
+        return &module();
       case StaticScopeIter<CanGC>::Block:
         return &staticBlock();
       case StaticScopeIter<CanGC>::With:
         return &staticWith();
       case StaticScopeIter<CanGC>::Eval:
         return &staticEval();
       case StaticScopeIter<CanGC>::NonSyntactic:
         return &staticNonSyntactic();
@@ -2382,16 +2442,20 @@ GetDebugScopeForMissing(JSContext* cx, c
      * nice invariant that every DebugScopeObject has a ScopeObject.
      *
      * Note: to preserve scopeChain depth invariants, these lazily-reified
      * scopes must not be put on the frame's scope chain; instead, they are
      * maintained via DebugScopes hooks.
      */
     DebugScopeObject* debugScope = nullptr;
     switch (si.type()) {
+      case ScopeIter::Module:
+          MOZ_CRASH(); // TODO: Implement debug scopes for modules.
+          break;
+
       case ScopeIter::Call: {
         RootedFunction callee(cx, &si.fun());
         // Generators should always reify their scopes.
         MOZ_ASSERT(!callee->isGenerator());
 
         Rooted<CallObject*> callobj(cx);
         if (si.withinInitialFrame())
             callobj = CallObject::createForFunction(cx, si.initialFrame());
@@ -2587,16 +2651,19 @@ js::DumpStaticScopeChain(JSScript* scrip
     DumpStaticScopeChain(script->enclosingStaticScope());
 }
 
 void
 js::DumpStaticScopeChain(JSObject* staticScope)
 {
     for (StaticScopeIter<NoGC> ssi(staticScope); !ssi.done(); ssi++) {
         switch (ssi.type()) {
+          case StaticScopeIter<NoGC>::Module:
+            fprintf(stdout, "module [%p]", &ssi.module());
+            break;
           case StaticScopeIter<NoGC>::Function:
             if (ssi.fun().isBeingParsed())
                 fprintf(stdout, "funbox [%p fun=%p]", ssi.maybeFunctionBox(), &ssi.fun());
             else
                 fprintf(stdout, "function [%p]", &ssi.fun());
             break;
           case StaticScopeIter<NoGC>::Block:
             fprintf(stdout, "block [%p]", &ssi.block());
--- a/js/src/vm/ScopeObject.h
+++ b/js/src/vm/ScopeObject.h
@@ -125,26 +125,28 @@ class StaticScopeIter
     JSObject* staticScope() const { MOZ_ASSERT(!done()); return obj; }
 
     // Return whether this static scope will have a syntactic scope (i.e. a
     // ScopeObject that isn't a non-syntactic With or
     // NonSyntacticVariablesObject) on the dynamic scope chain.
     bool hasSyntacticDynamicScopeObject() const;
     Shape* scopeShape() const;
 
-    enum Type { Function, Block, With, NamedLambda, Eval, NonSyntactic };
+    enum Type { Module, Function, Block, With, NamedLambda, Eval, NonSyntactic };
     Type type() const;
 
     StaticBlockObject& block() const;
     StaticWithObject& staticWith() const;
     StaticEvalObject& eval() const;
     StaticNonSyntacticScopeObjects& nonSyntactic() const;
     JSScript* funScript() const;
     JSFunction& fun() const;
     frontend::FunctionBox* maybeFunctionBox() const;
+    JSScript* moduleScript() const;
+    ModuleObject& module() const;
 };
 
 /*****************************************************************************/
 
 /*
  * A "scope coordinate" describes how to get from head of the scope chain to a
  * given lexically-enclosing variable. A scope coordinate has two dimensions:
  *  - hops: the number of scope objects on the scope chain to skip
@@ -213,16 +215,18 @@ ScopeCoordinateFunctionScript(JSScript* 
  *     |   |   |   |   |
  *     |   |   |   |  StaticNonSyntacticScopeObjects  See NB2
  *     |   |   |   |
  *     |   |   |  StaticEvalObject  Placeholder so eval scopes may be iterated through
  *     |   |   |
  *     |   |  DeclEnvObject         Holds name of recursive/heavyweight named lambda
  *     |   |
  *     |  CallObject                Scope of entire function or strict eval
+ *     |   |
+ *     |  ModuleEnvironmentObject   Module top-level scope on run-time scope chain
  *     |
  *   NestedScopeObject              Statement scopes; don't cross script boundaries
  *     |   |   |
  *     |   |  StaticWithObject      Template for "with" object in static scope chain
  *     |   |
  *     |  DynamicWithObject         Run-time "with" object on scope chain
  *     |
  *   BlockObject                    Shared interface of cloned/static block objects
@@ -281,16 +285,17 @@ class ScopeObject : public NativeObject
 
     static size_t enclosingScopeSlot() {
         return SCOPE_CHAIN_SLOT;
     }
 };
 
 class CallObject : public ScopeObject
 {
+  protected:
     static const uint32_t CALLEE_SLOT = 1;
 
     static CallObject*
     create(JSContext* cx, HandleScript script, HandleObject enclosing, HandleFunction callee);
 
     inline void initRemainingSlotsToUninitializedLexicals(uint32_t begin);
     inline void initAliasedLexicalsToThrowOnTouch(JSScript* script);
 
@@ -366,16 +371,31 @@ class CallObject : public ScopeObject
         return getFixedSlotOffset(CALLEE_SLOT);
     }
 
     static size_t calleeSlot() {
         return CALLEE_SLOT;
     }
 };
 
+class ModuleEnvironmentObject : public CallObject
+{
+    static const uint32_t MODULE_SLOT = CallObject::CALLEE_SLOT;
+
+  public:
+    static const Class class_;
+
+    static ModuleEnvironmentObject* create(ExclusiveContext* cx, HandleModuleObject module);
+    ModuleObject& module() const;
+};
+
+typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
+typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject;
+typedef MutableHandle<ModuleEnvironmentObject*> MutableHandleModuleEnvironmentObject;
+
 class DeclEnvObject : public ScopeObject
 {
     // Pre-allocated slot for the named lambda.
     static const uint32_t LAMBDA_SLOT = 1;
 
   public:
     static const uint32_t RESERVED_SLOTS = 2;
     static const Class class_;
@@ -825,31 +845,32 @@ class ScopeIter
 
     inline bool done() const;
     ScopeIter& operator++();
 
     // If done():
     inline JSObject& enclosingScope() const;
 
     // If !done():
-    enum Type { Call, Block, With, Eval, NonSyntactic };
+    enum Type { Module, Call, Block, With, Eval, NonSyntactic };
     Type type() const;
 
     inline bool hasNonSyntacticScopeObject() const;
     inline bool hasSyntacticScopeObject() const;
     inline bool hasAnyScopeObject() const;
     inline bool canHaveSyntacticScopeObject() const;
     ScopeObject& scope() const;
 
     JSObject* maybeStaticScope() const;
     StaticBlockObject& staticBlock() const { return ssi_.block(); }
     StaticWithObject& staticWith() const { return ssi_.staticWith(); }
     StaticEvalObject& staticEval() const { return ssi_.eval(); }
     StaticNonSyntacticScopeObjects& staticNonSyntactic() const { return ssi_.nonSyntactic(); }
     JSFunction& fun() const { return ssi_.fun(); }
+    ModuleObject& module() const { return ssi_.module(); }
 
     bool withinInitialFrame() const { return !!frame_; }
     AbstractFramePtr initialFrame() const { MOZ_ASSERT(withinInitialFrame()); return frame_; }
     AbstractFramePtr maybeInitialFrame() const { return frame_; }
 
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
@@ -1073,16 +1094,24 @@ JSObject::is<js::NestedScopeObject>() co
 {
     return is<js::BlockObject>() ||
            is<js::StaticWithObject>() ||
            is<js::DynamicWithObject>();
 }
 
 template<>
 inline bool
+JSObject::is<js::CallObject>() const
+{
+    return getClass() == &js::CallObject::class_ ||
+           is<js::ModuleEnvironmentObject>();
+}
+
+template<>
+inline bool
 JSObject::is<js::ScopeObject>() const
 {
     return is<js::CallObject>() ||
            is<js::DeclEnvObject>() ||
            is<js::NestedScopeObject>() ||
            is<js::UninitializedLexicalObject>() ||
            is<js::NonSyntacticVariablesObject>();
 }
@@ -1169,25 +1198,26 @@ ScopeIter::hasAnyScopeObject() const
 
 inline bool
 ScopeIter::canHaveSyntacticScopeObject() const
 {
     if (ssi_.done())
         return false;
 
     switch (type()) {
+      case Module:
       case Call:
-        return true;
       case Block:
-        return true;
       case With:
         return true;
+
       case Eval:
         // Only strict eval scopes can have dynamic scope objects.
         return staticEval().isStrict();
+
       case NonSyntactic:
         return false;
     }
 
     // Silence warnings.
     return false;
 }
 
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -144,16 +144,20 @@ AssertDynamicScopeMatchesStaticScope(JSC
     for (StaticScopeIter<NoGC> i(enclosingScope); !i.done(); i++) {
         if (i.type() == StaticScopeIter<NoGC>::NonSyntactic) {
             while (scope->is<DynamicWithObject>() || scope->is<NonSyntacticVariablesObject>()) {
                 MOZ_ASSERT(!IsSyntacticScope(scope));
                 scope = &scope->as<ScopeObject>().enclosingScope();
             }
         } else if (i.hasSyntacticDynamicScopeObject()) {
             switch (i.type()) {
+              case StaticScopeIter<NoGC>::Module:
+                MOZ_ASSERT(scope->as<ModuleEnvironmentObject>().module().script() == i.moduleScript());
+                scope = &scope->as<ModuleEnvironmentObject>().enclosingScope();
+                break;
               case StaticScopeIter<NoGC>::Function:
                 MOZ_ASSERT(scope->as<CallObject>().callee().nonLazyScript() == i.funScript());
                 scope = &scope->as<CallObject>().enclosingScope();
                 break;
               case StaticScopeIter<NoGC>::Block:
                 MOZ_ASSERT(&i.block() == scope->as<ClonedBlockObject>().staticScope());
                 scope = &scope->as<ClonedBlockObject>().enclosingScope();
                 break;