Bug 930414 - Hook up module environements, alising everything at top level for now r=shu
authorJon Coppeard <jcoppeard@mozilla.com>
Mon, 24 Aug 2015 15:58:36 +0100
changeset 259033 0773712473c9cea41fa3a063f97cbd2dc55d86a4
parent 259032 9ac1f5052b91cdc341570cb2f8c03efc561faa54
child 259034 697f5d72aebe8d661e31424f328ffb1539d8e904
push id64110
push userjcoppeard@mozilla.com
push dateMon, 24 Aug 2015 15:00:44 +0000
treeherdermozilla-inbound@0773712473c9 [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 - Hook up module environements, alising everything at top level for now r=shu
js/src/builtin/ModuleObject.cpp
js/src/builtin/ModuleObject.h
js/src/frontend/BytecodeCompiler.cpp
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
js/src/frontend/Parser-inl.h
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/frontend/SharedContext.h
js/src/jit-test/tests/modules/module-environment.js
js/src/jit-test/tests/modules/shell-parse.js
js/src/jsscript.cpp
js/src/jsweakmap.h
js/src/vm/Stack.cpp
js/src/vm/Stack.h
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -262,16 +262,22 @@ ModuleObject::create(ExclusiveContext* c
 
 void
 ModuleObject::init(HandleScript script)
 {
     initReservedSlot(ScriptSlot, PrivateValue(script));
 }
 
 void
+ModuleObject::setInitialEnvironment(HandleModuleEnvironmentObject initialEnvironment)
+{
+    initReservedSlot(InitialEnvironmentSlot, ObjectValue(*initialEnvironment));
+}
+
+void
 ModuleObject::initImportExportData(HandleArrayObject requestedModules,
                                    HandleArrayObject importEntries,
                                    HandleArrayObject localExportEntries,
                                    HandleArrayObject indirectExportEntries,
                                    HandleArrayObject starExportEntries)
 {
     initReservedSlot(RequestedModulesSlot, ObjectValue(*requestedModules));
     initReservedSlot(ImportEntriesSlot, ObjectValue(*importEntries));
@@ -281,35 +287,43 @@ ModuleObject::initImportExportData(Handl
 }
 
 JSScript*
 ModuleObject::script() const
 {
     return static_cast<JSScript*>(getReservedSlot(ScriptSlot).toPrivate());
 }
 
+ModuleEnvironmentObject&
+ModuleObject::initialEnvironment() const
+{
+    return getReservedSlot(InitialEnvironmentSlot).toObject().as<ModuleEnvironmentObject>();
+}
+
 /* static */ void
 ModuleObject::trace(JSTracer* trc, JSObject* obj)
 {
     ModuleObject& module = obj->as<ModuleObject>();
     JSScript* script = module.script();
     TraceManuallyBarrieredEdge(trc, &script, "Module script");
     module.setReservedSlot(ScriptSlot, PrivateValue(script));
 }
 
+DEFINE_GETTER_FUNCTIONS(ModuleObject, initialEnvironment, InitialEnvironmentSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, requestedModules, RequestedModulesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, importEntries, ImportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, localExportEntries, LocalExportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot)
 DEFINE_GETTER_FUNCTIONS(ModuleObject, starExportEntries, StarExportEntriesSlot)
 
 JSObject*
 js::InitModuleClass(JSContext* cx, HandleObject obj)
 {
     static const JSPropertySpec protoAccessors[] = {
+        JS_PSG("initialEnvironment", ModuleObject_initialEnvironmentGetter, 0),
         JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0),
         JS_PSG("importEntries", ModuleObject_importEntriesGetter, 0),
         JS_PSG("localExportEntries", ModuleObject_localExportEntriesGetter, 0),
         JS_PSG("indirectExportEntries", ModuleObject_indirectExportEntriesGetter, 0),
         JS_PSG("starExportEntries", ModuleObject_starExportEntriesGetter, 0),
         JS_PS_END
     };
 
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -10,16 +10,18 @@
 #include "jsapi.h"
 
 #include "js/TraceableVector.h"
 
 #include "vm/NativeObject.h"
 
 namespace js {
 
+class ModuleEnvironmentObject;
+
 namespace frontend {
 class ParseNode;
 } /* namespace frontend */
 
 class ImportEntryObject : public NativeObject
 {
   public:
     enum
@@ -69,37 +71,40 @@ class ExportEntryObject : public NativeO
 };
 
 class ModuleObject : public NativeObject
 {
   public:
     enum
     {
         ScriptSlot = 0,
+        InitialEnvironmentSlot,
         RequestedModulesSlot,
         ImportEntriesSlot,
         LocalExportEntriesSlot,
         IndirectExportEntriesSlot,
         StarExportEntriesSlot,
         SlotCount
     };
 
     static const Class class_;
 
     static bool isInstance(HandleValue value);
 
     static ModuleObject* create(ExclusiveContext* cx);
     void init(HandleScript script);
+    void setInitialEnvironment(Handle<ModuleEnvironmentObject*> initialEnvironment);
     void initImportExportData(HandleArrayObject requestedModules,
                               HandleArrayObject importEntries,
                               HandleArrayObject localExportEntries,
                               HandleArrayObject indiretExportEntries,
                               HandleArrayObject starExportEntries);
 
     JSScript* script() const;
+    ModuleEnvironmentObject& initialEnvironment() const;
     ArrayObject& requestedModules() const;
     ArrayObject& importEntries() const;
     ArrayObject& localExportEntries() const;
     ArrayObject& indirectExportEntries() const;
     ArrayObject& starExportEntries() const;
 
   private:
     static void trace(JSTracer* trc, JSObject* obj);
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -632,16 +632,22 @@ ModuleObject* BytecodeCompiler::compileM
         !maybeSetDisplayURL(parser->tokenStream) ||
         !maybeSetSourceMap(parser->tokenStream))
     {
         return nullptr;
     }
 
     script->bindings = pn->pn_modulebox->bindings;
 
+    RootedModuleEnvironmentObject dynamicScope(cx, ModuleEnvironmentObject::create(cx, module));
+    if (!dynamicScope)
+        return nullptr;
+
+    module->setInitialEnvironment(dynamicScope);
+
     if (!createEmitter(pn->pn_modulebox) ||
         !emitter->emitModuleScript(pn->pn_body))
     {
         return nullptr;
     }
 
     ModuleBuilder builder(cx->asJSContext());
     if (!builder.buildAndInit(pn, module))
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -810,17 +810,17 @@ BytecodeEmitter::computeAliasedSlots(Han
     MOZ_ASSERT_IF(sc->allLocalsAliased(), AllLocalsAliased(*blockObj));
 
     return true;
 }
 
 void
 BytecodeEmitter::computeLocalOffset(Handle<StaticBlockObject*> blockObj)
 {
-    unsigned nbodyfixed = sc->isFunctionBox()
+    unsigned nbodyfixed = !sc->isGlobalContext()
                           ? script->bindings.numUnaliasedBodyLevelLocals()
                           : 0;
     unsigned localOffset = nbodyfixed;
 
     if (StmtInfoBCE* stmt = innermostScopeStmt()) {
         Rooted<NestedScopeObject*> outer(cx, stmt->staticScope);
         for (; outer; outer = outer->enclosingNestedScope()) {
             if (outer->is<StaticBlockObject>()) {
@@ -1422,17 +1422,19 @@ BytecodeEmitter::computeDefinitionIsAlia
 {
     if (dn->isKnownAliased()) {
         *op = UnaliasedVarOpToAliasedVarOp(*op);
     } else if (isAliasedName(bceOfDef, dn)) {
         // Translate the frame slot to a slot on the dynamic scope
         // object. Aliased block bindings do not need adjusting; see
         // computeAliasedSlots.
         uint32_t slot = dn->pn_scopecoord.slot();
-        if (blockScopeOfDef(dn)->is<JSFunction>()) {
+        if (blockScopeOfDef(dn)->is<JSFunction>() ||
+            blockScopeOfDef(dn)->is<ModuleObject>())
+        {
             MOZ_ASSERT(IsArgOp(*op) || slot < bceOfDef->script->bindings.numBodyLevelLocals());
             MOZ_ALWAYS_TRUE(bceOfDef->lookupAliasedName(bceOfDef->script, dn->name(), &slot));
         }
         if (!dn->pn_scopecoord.setSlot(parser->tokenStream, slot))
             return false;
 
         *op = UnaliasedVarOpToAliasedVarOp(*op);
 
@@ -3513,16 +3515,17 @@ BytecodeEmitter::emitModuleScript(ParseN
     /*
      * IonBuilder has assumptions about what may occur immediately after
      * script->main (e.g., in the case of destructuring params). Thus, put the
      * following ops into the range [script->code, script->main). Note:
      * execution starts from script->code, so this has no semantic effect.
      */
 
     ModuleBox* modulebox = sc->asModuleBox();
+    MOZ_ASSERT(modulebox);
 
     // Link the module and the script to each other, so that StaticScopeIter
     // may walk the scope chain of currently compiling scripts.
     JSScript::linkToModuleFromEmitter(cx, script, modulebox);
 
     if (!emitTree(body))
         return false;
 
@@ -5875,17 +5878,17 @@ BytecodeEmitter::emitFunction(ParseNode*
      * For a script we emit the code as we parse. Thus the bytecode for
      * top-level functions should go in the prologue to predefine their
      * names in the variable object before the already-generated main code
      * is executed. This extra work for top-level scripts is not necessary
      * when we emit the code for a function. It is fully parsed prior to
      * invocation of the emitter and calls to emitTree for function
      * definitions can be scheduled before generating the rest of code.
      */
-    if (!sc->isFunctionBox()) {
+    if (sc->isGlobalContext()) {
         MOZ_ASSERT(pn->pn_scopecoord.isFree());
         MOZ_ASSERT(pn->getOp() == JSOP_NOP);
         MOZ_ASSERT(!innermostStmt() || sc->isModuleBox());
         switchToPrologue();
         if (!emitIndex32(JSOP_DEFFUN, index))
             return false;
         if (!updateSourceCoordNotes(pn->pn_pos.begin))
             return false;
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -219,17 +219,18 @@ struct BytecodeEmitter
     bool init();
     bool updateLocalsToFrameSlots();
 
     StmtInfoBCE* innermostStmt() const { return stmtStack.innermost(); }
     StmtInfoBCE* innermostScopeStmt() const { return stmtStack.innermostScopeStmt(); }
     JSObject* innermostStaticScope() const;
     JSObject* blockScopeOfDef(ParseNode* pn) const {
         MOZ_ASSERT(pn->resolve());
-        return parser->blockScopes[pn->resolve()->pn_blockid];
+        unsigned blockid = pn->resolve()->pn_blockid;
+        return parser->blockScopes[blockid];
     }
 
     uint32_t computeHops(ParseNode* pn, BytecodeEmitter** bceOfDefOut);
     bool isAliasedName(BytecodeEmitter* bceOfDef, ParseNode* pn);
     bool computeDefinitionIsAliased(BytecodeEmitter* bceOfDef, Definition* dn, JSOp* op);
 
     MOZ_ALWAYS_INLINE
     bool makeAtomIndex(JSAtom* atom, jsatomid* indexp) {
--- a/js/src/frontend/Parser-inl.h
+++ b/js/src/frontend/Parser-inl.h
@@ -13,23 +13,18 @@
 
 namespace js {
 namespace frontend {
 
 template <typename ParseHandler>
 bool
 ParseContext<ParseHandler>::init(Parser<ParseHandler>& parser)
 {
-    if (!parser.generateBlockId(sc->isFunctionBox()
-                                ? sc->asFunctionBox()->function()
-                                : sc->staticScope(),
-                                &this->bodyid))
-    {
+    if (!parser.generateBlockId(sc->staticScope(), &this->bodyid))
         return false;
-    }
 
     if (!decls_.init() || !lexdeps.ensureMap(sc->context)) {
         ReportOutOfMemory(sc->context);
         return false;
     }
 
     return true;
 }
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -237,35 +237,39 @@ ParseContext<FullParseHandler>::define(T
         if (name == ts.names().empty)
             break;
         if (!decls_.addUnique(name, dn))
             return false;
         break;
 
       case Definition::GLOBALCONST:
       case Definition::VAR:
-        if (sc->isFunctionBox()) {
+        if (!sc->isGlobalContext()) {
             dn->setOp((js_CodeSpec[dn->getOp()].format & JOF_SET) ? JSOP_SETLOCAL : JSOP_GETLOCAL);
             dn->pn_blockid = bodyid;
             dn->pn_dflags |= PND_BOUND;
             if (!dn->pn_scopecoord.setSlot(ts, vars_.length()))
                 return false;
             if (!vars_.append(dn))
                 return false;
             if (!checkLocalsOverflow(ts))
                 return false;
         }
+        if (atModuleScope())
+            dn->pn_dflags |= PND_CLOSED;
         if (!decls_.addUnique(name, dn))
             return false;
         break;
 
       case Definition::LET:
       case Definition::CONST:
         dn->setOp(JSOP_INITLEXICAL);
         dn->pn_dflags |= (PND_LEXICAL | PND_BOUND);
+        if (atModuleLevel())
+            dn->pn_dflags |= PND_CLOSED;
         if (atBodyLevel()) {
             if (!bodyLevelLexicals_.append(dn))
                 return false;
             if (!checkLocalsOverflow(ts))
                 return false;
         }
 
         // In ES6, lexical bindings cannot be accessed until initialized. If
@@ -846,16 +850,18 @@ Parser<ParseHandler>::standaloneModule(H
     ParseContext<FullParseHandler> modulepc(this, pc, mn, modulebox, nullptr, 0);
     if (!modulepc.init(*this))
         return null();
 
     ParseNode* pn = statements(YieldIsKeyword);
     if (!pn)
         return null();
 
+    pn->pn_blockid = modulepc.blockid();
+
     MOZ_ASSERT(pn->isKind(PNK_STATEMENTLIST));
     mn->pn_body = pn;
 
     TokenKind tt;
     if (!tokenStream.getToken(&tt, TokenStream::Operand))
         return null();
     MOZ_ASSERT(tt == TOK_EOF);
 
@@ -2088,18 +2094,17 @@ Parser<FullParseHandler>::checkFunctionD
             }
 
             if (!pc->define(tokenStream, funName, pn, Definition::VAR))
                 return false;
         }
 
         if (bodyLevel) {
             MOZ_ASSERT(pn->functionIsHoisted());
-            MOZ_ASSERT_IF(pc->sc->isFunctionBox(), !pn->pn_scopecoord.isFree());
-            MOZ_ASSERT_IF(!pc->sc->isFunctionBox(), pn->pn_scopecoord.isFree());
+            MOZ_ASSERT(pc->sc->isGlobalContext() == pn->pn_scopecoord.isFree());
         } else {
             /*
              * As a SpiderMonkey-specific extension, non-body-level function
              * statements (e.g., functions in an "if" or "while" block) are
              * dynamically bound when control flow reaches the statement.
              */
             MOZ_ASSERT(!pc->sc->strict());
             MOZ_ASSERT(pn->pn_scopecoord.isFree());
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -301,17 +301,27 @@ struct MOZ_STACK_CLASS ParseContext : pu
     //   function f1() { function f2() { } }
     //   if (cond) { function f3() { if (cond) { function f4() { } } } }
     //
     bool atBodyLevel() {
         return !innermostStmt();
     }
 
     bool atGlobalLevel() {
-        return atBodyLevel() && !sc->isFunctionBox() && !innermostScopeStmt();
+        return atBodyLevel() && sc->isGlobalContext() && !innermostScopeStmt();
+    }
+
+    // True if we are at the topmost level of a module only.
+    bool atModuleLevel() {
+        return atBodyLevel() && sc->isModuleBox();
+    }
+
+    // True if the current lexical scope is the topmost level of a module.
+    bool atModuleScope() {
+        return sc->isModuleBox() && !innermostScopeStmt();
     }
 
     // True if this is the ParseContext for the body of a function created by
     // the Function constructor.
     bool isFunctionConstructorBody() const {
         return sc->isFunctionBox() && !parent && sc->asFunctionBox()->function()->isLambda();
     }
 
--- a/js/src/frontend/SharedContext.h
+++ b/js/src/frontend/SharedContext.h
@@ -216,16 +216,17 @@ class SharedContext
     void computeInWith(JSObject* staticScope);
 
     virtual ObjectBox* toObjectBox() { return nullptr; }
     bool isObjectBox() { return toObjectBox() != nullptr; }
     bool isFunctionBox() { return isObjectBox() && toObjectBox()->isFunctionBox(); }
     inline FunctionBox* asFunctionBox();
     bool isModuleBox() { return isObjectBox() && toObjectBox()->isModuleBox(); }
     inline ModuleBox* asModuleBox();
+    bool isGlobalContext() { return !toObjectBox(); }
 
     bool allowNewTarget()              const { return allowNewTarget_; }
     bool allowSuperProperty()          const { return allowSuperProperty_; }
     bool inWith()                      const { return inWith_; }
     void markSuperScopeNeedsHomeObject();
 
     bool hasExplicitUseStrict()        const { return anyCxFlags.hasExplicitUseStrict; }
     bool bindingsAccessedDynamically() const { return anyCxFlags.bindingsAccessedDynamically; }
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/module-environment.js
@@ -0,0 +1,27 @@
+// Test top-level module environment
+
+function testInitialEnvironment(source, expected) {
+    print(source);
+    let m = parseModule(source);
+    let scope = m.initialEnvironment;
+    let keys = Object.keys(scope);
+    assertEq(keys.length, expected.length);
+    expected.forEach(function(name) {
+        assertEq(name in scope, true);
+    });
+}
+
+testInitialEnvironment('', []);
+testInitialEnvironment('var x = 1;', ['x']);
+testInitialEnvironment('let x = 1;', ['x']);
+testInitialEnvironment("if (true) { let x = 1; }", []);
+testInitialEnvironment("if (true) { var x = 1; }", ['x']);
+testInitialEnvironment('function x() {}', ['x']);
+testInitialEnvironment("class x { constructor() {} }", ['x']);
+testInitialEnvironment('export var x = 1;', ['x']);
+testInitialEnvironment('export let x = 1;', ['x']);
+testInitialEnvironment('export default class x { constructor() {} };', ['x']);
+testInitialEnvironment('export default function x() {};', ['x']);
+testInitialEnvironment('export default 1;', []);
+testInitialEnvironment('export default class { constructor() {} };', ['*default*']);
+testInitialEnvironment('export default function() {};', ['*default*']);
--- a/js/src/jit-test/tests/modules/shell-parse.js
+++ b/js/src/jit-test/tests/modules/shell-parse.js
@@ -14,15 +14,17 @@ function testEvalError(source) {
 function testModuleSource(source) {
     // Test |source| parses as a module, but throws when passed to eval.
     testEvalError(source);
     parseModule(source);
 }
 
 parseModule("");
 parseModule("const foo = 1;");
+parseModule("var foo = 1;");
+parseModule("let foo = 1; var bar = 2; const baz = 3");
 
 testModuleSource("import * as ns from 'bar';");
 testModuleSource("export { a } from 'b';");
 testModuleSource("export * from 'b';");
 testModuleSource("export const foo = 1;");
 testModuleSource("export default function() {};");
 testModuleSource("export default 1;");
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -153,16 +153,17 @@ Bindings::initWithTemporaryStorage(Exclu
     if (!added.init()) {
         ReportOutOfMemory(cx);
         return false;
     }
 #endif
 
     uint32_t slot = CallObject::RESERVED_SLOTS;
     for (BindingIter bi(self); bi; bi++) {
+        MOZ_ASSERT_IF(isModule, bi->aliased());
         if (!bi->aliased())
             continue;
 
 #ifdef DEBUG
         // The caller ensures no duplicate aliased names.
         MOZ_ASSERT(!added.has(bi->name()));
         if (!added.put(bi->name())) {
             ReportOutOfMemory(cx);
@@ -3723,16 +3724,18 @@ JSScript::getStaticBlockScope(jsbytecode
     return blockChain;
 }
 
 JSObject*
 JSScript::innermostStaticScopeInScript(jsbytecode* pc)
 {
     if (JSObject* scope = getStaticBlockScope(pc))
         return scope;
+    if (module())
+        return module();
     return functionNonDelazifying();
 }
 
 JSObject*
 JSScript::innermostStaticScope(jsbytecode* pc)
 {
     if (JSObject* scope = innermostStaticScopeInScript(pc))
         return scope;
--- a/js/src/jsweakmap.h
+++ b/js/src/jsweakmap.h
@@ -14,16 +14,18 @@
 #include "jsobj.h"
 
 #include "gc/Marking.h"
 #include "gc/StoreBuffer.h"
 #include "js/HashTable.h"
 
 namespace js {
 
+class WeakMapBase;
+
 // A subclass template of js::HashMap whose keys and values may be garbage-collected. When
 // a key is collected, the table entry disappears, dropping its reference to the value.
 //
 // More precisely:
 //
 //     A WeakMap entry is collected if and only if either the WeakMap or the entry's key
 //     is collected. If an entry is not collected, it remains in the WeakMap and it has a
 //     strong reference to the value.
--- a/js/src/vm/Stack.cpp
+++ b/js/src/vm/Stack.cpp
@@ -44,17 +44,17 @@ InterpreterFrame::initExecuteFrame(JSCon
      */
     flags_ = type | HAS_SCOPECHAIN;
 
     JSObject* callee = nullptr;
 
     // newTarget = NullValue is an initial sentinel for "please fill me in from the stack".
     // It should never be passed from Ion code.
     RootedValue newTarget(cx, newTargetValue);
-    if (!(flags_ & (GLOBAL))) {
+    if (!(flags_ & (GLOBAL | MODULE))) {
         if (evalInFramePrev) {
             MOZ_ASSERT(evalInFramePrev.isFunctionFrame() || evalInFramePrev.isGlobalFrame());
             if (evalInFramePrev.isFunctionFrame()) {
                 callee = evalInFramePrev.callee();
                 if (newTarget.isNull())
                     newTarget = evalInFramePrev.newTarget();
                 flags_ |= FUNCTION;
             } else {
@@ -78,17 +78,17 @@ InterpreterFrame::initExecuteFrame(JSCon
     Value* dstvp = (Value*)this - 3;
     dstvp[2] = thisv;
 
     if (isFunctionFrame()) {
         dstvp[1] = ObjectValue(*callee);
         exec.fun = &callee->as<JSFunction>();
         u.evalScript = script;
     } else {
-        MOZ_ASSERT(isGlobalFrame());
+        MOZ_ASSERT(isGlobalFrame() || isModuleFrame());
         dstvp[1] = NullValue();
         exec.script = script;
 #ifdef DEBUG
         u.evalScript = (JSScript*)0xbad;
 #endif
     }
     dstvp[0] = newTarget;
 
@@ -193,35 +193,43 @@ InterpreterFrame::initFunctionScopeObjec
     return true;
 }
 
 bool
 InterpreterFrame::prologue(JSContext* cx)
 {
     RootedScript script(cx, this->script());
 
+    MOZ_ASSERT(isModuleFrame() == !!script->module());
     MOZ_ASSERT(cx->interpreterRegs().pc == script->code());
 
     if (isEvalFrame()) {
         if (script->strict()) {
             CallObject* callobj = CallObject::createForStrictEval(cx, this);
             if (!callobj)
                 return false;
             pushOnScopeChain(*callobj);
             flags_ |= HAS_CALL_OBJ;
         }
         return probes::EnterScript(cx, script, nullptr, this);
     }
 
     if (isGlobalFrame())
         return probes::EnterScript(cx, script, nullptr, this);
 
-    MOZ_ASSERT(isNonEvalFunctionFrame());
     AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain());
 
+    if (isModuleFrame()) {
+        RootedModuleEnvironmentObject scope(cx, &script->module()->initialEnvironment());
+        MOZ_ASSERT(&scope->enclosingScope() == scopeChain());
+        pushOnScopeChain(*scope);
+        return probes::EnterScript(cx, script, nullptr, this);
+    }
+
+    MOZ_ASSERT(isNonEvalFunctionFrame());
     if (fun()->isHeavyweight() && !initFunctionScopeObjects(cx))
         return false;
 
     if (isConstructing() && functionThis().isPrimitive()) {
         RootedObject callee(cx, &this->callee());
         JSObject* obj = CreateThisForFunction(cx, callee,
                                               createSingleton() ? SingletonObject : GenericObject);
         if (!obj)
@@ -263,16 +271,19 @@ InterpreterFrame::epilogue(JSContext* cx
         return;
     }
 
     if (isGlobalFrame()) {
         MOZ_ASSERT(!IsSyntacticScope(scopeChain()));
         return;
     }
 
+    if (isModuleFrame())
+        return;
+
     MOZ_ASSERT(isNonEvalFunctionFrame());
 
     if (fun()->isHeavyweight()) {
         MOZ_ASSERT_IF(hasCallObj() && !fun()->isGenerator(),
                       scopeChain()->as<CallObject>().callee().nonLazyScript() == script);
     } else {
         AssertDynamicScopeMatchesStaticScope(cx, script, scopeChain());
     }
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -270,60 +270,62 @@ class NullFramePtr : public AbstractFram
     { }
 };
 
 /*****************************************************************************/
 
 /* Flags specified for a frame as it is constructed. */
 enum InitialFrameFlags {
     INITIAL_NONE           =          0,
-    INITIAL_CONSTRUCT      =       0x10, /* == InterpreterFrame::CONSTRUCTING, asserted below */
+    INITIAL_CONSTRUCT      =       0x20, /* == InterpreterFrame::CONSTRUCTING, asserted below */
 };
 
 enum ExecuteType {
     EXECUTE_GLOBAL         =        0x1, /* == InterpreterFrame::GLOBAL */
-    EXECUTE_DIRECT_EVAL    =        0x4, /* == InterpreterFrame::EVAL */
-    EXECUTE_INDIRECT_EVAL  =        0x5, /* == InterpreterFrame::GLOBAL | EVAL */
-    EXECUTE_DEBUG          =        0xc, /* == InterpreterFrame::EVAL | DEBUGGER */
-    EXECUTE_DEBUG_GLOBAL   =        0xd  /* == InterpreterFrame::EVAL | DEBUGGER | GLOBAL */
+    EXECUTE_MODULE         =        0x4, /* == InterpreterFrame::GLOBAL */
+    EXECUTE_DIRECT_EVAL    =        0x8, /* == InterpreterFrame::EVAL */
+    EXECUTE_INDIRECT_EVAL  =        0x9, /* == InterpreterFrame::GLOBAL | EVAL */
+    EXECUTE_DEBUG          =       0x18, /* == InterpreterFrame::EVAL | DEBUGGER */
+    EXECUTE_DEBUG_GLOBAL   =       0x19  /* == InterpreterFrame::EVAL | DEBUGGER | GLOBAL */
 };
 
 /*****************************************************************************/
 
 class InterpreterFrame
 {
   public:
     enum Flags : uint32_t {
         /* Primary frame type */
         GLOBAL                 =        0x1,  /* frame pushed for a global script */
         FUNCTION               =        0x2,  /* frame pushed for a scripted call */
+        MODULE                 =        0x4,  /* frame pushed for a module */
 
         /* Frame subtypes */
-        EVAL                   =        0x4,  /* frame pushed for eval() or debugger eval */
+        EVAL                   =        0x8,  /* frame pushed for eval() or debugger eval */
 
 
         /*
          * Frame pushed for debugger eval.
          * - Don't bother to JIT it, because it's probably short-lived.
          * - It is required to have a scope chain object outside the
          *   js::ScopeObject hierarchy: either a global object, or a
          *   DebugScopeObject (not a ScopeObject, despite the name)
          * - If evalInFramePrev_ is set, then this frame was created for an
          *   "eval in frame" call, which can push a successor to any live
          *   frame; so its logical "prev" frame is not necessarily the
          *   previous frame in memory. Iteration should treat
          *   evalInFramePrev_ as this frame's previous frame.
          */
-        DEBUGGER_EVAL          =        0x8,
+        DEBUGGER_EVAL          =       0x10,
 
-        CONSTRUCTING           =       0x10,  /* frame is for a constructor invocation */
+        CONSTRUCTING           =       0x20,  /* frame is for a constructor invocation */
 
-        RESUMED_GENERATOR      =       0x20,  /* frame is for a resumed generator invocation */
+        RESUMED_GENERATOR      =       0x40,  /* frame is for a resumed generator invocation */
 
-        /* (0x40 and 0x80 are unused) */
+        /* (0x80 is unused) */
 
         /* Function prologue state */
         HAS_CALL_OBJ           =      0x100,  /* CallObject created for heavyweight fun */
         HAS_ARGS_OBJ           =      0x200,  /* ArgumentsObject created for needsArgsObj script */
 
         /* Lazy frame initialization */
         HAS_RVAL               =      0x800,  /* frame has rval_ set */
         HAS_SCOPECHAIN         =     0x1000,  /* frame has scopeChain_ set */
@@ -358,16 +360,17 @@ class InterpreterFrame
         HAS_CACHED_SAVED_FRAME =    0x80000,
     };
 
   private:
     mutable uint32_t    flags_;         /* bits described by Flags */
     union {                             /* describes what code is executing in a */
         JSScript*       script;        /*   global frame */
         JSFunction*     fun;           /*   function frame, pre GetScopeChain */
+        ModuleObject*   module;        /*   module frame */
     } exec;
     union {                             /* describes the arguments of a function */
         unsigned        nactual;        /*   for non-eval frames */
         JSScript*       evalScript;    /*   the script of an eval-in-function */
     } u;
     mutable JSObject*   scopeChain_;   /* if HAS_SCOPECHAIN, current scope chain */
     Value               rval_;          /* if HAS_RVAL, return value of the frame */
     ArgumentsObject*    argsObj_;      /* if HAS_ARGS_OBJ, the call's arguments object */
@@ -461,26 +464,31 @@ class InterpreterFrame
     /*
      * Stack frame type
      *
      * A stack frame may have one of three types, which determines which
      * members of the frame may be accessed and other invariants:
      *
      *  global frame:   execution of global code or an eval in global code
      *  function frame: execution of function code or an eval in a function
+     *  module frame: execution of a module
      */
 
     bool isFunctionFrame() const {
         return !!(flags_ & FUNCTION);
     }
 
     bool isGlobalFrame() const {
         return !!(flags_ & GLOBAL);
     }
 
+    bool isModuleFrame() const {
+        return !!(flags_ & MODULE);
+    }
+
     /*
      * Eval frames
      *
      * As noted above, global and function frames may optionally be 'eval
      * frames'. Eval code shares its parent's arguments which means that the
      * arg-access members of InterpreterFrame may not be used for eval frames.
      * Search for 'hasArgs' below for more details.
      *
@@ -646,21 +654,20 @@ class InterpreterFrame
      *
      * - Inlined frames have the same scope chain as the outer frame.
      * - Inlined frames have the same strictness as the outer frame.
      * - Inlined frames can only make calls to other JIT frames associated with
      *   the same VMFrame. Other calls force expansion of the inlined frames.
      */
 
     JSScript* script() const {
-        return isFunctionFrame()
-               ? isEvalFrame()
-                 ? u.evalScript
-                 : fun()->nonLazyScript()
-               : exec.script;
+        if (isFunctionFrame())
+            return isEvalFrame() ? u.evalScript : fun()->nonLazyScript();
+        MOZ_ASSERT(isGlobalFrame() || isModuleFrame());
+        return exec.script;
     }
 
     /* Return the previous frame's pc. */
     jsbytecode* prevpc() {
         MOZ_ASSERT(prev_);
         return prevpc_;
     }
 
@@ -683,16 +690,27 @@ class InterpreterFrame
         MOZ_ASSERT(isFunctionFrame());
         return exec.fun;
     }
 
     JSFunction* maybeFun() const {
         return isFunctionFrame() ? fun() : nullptr;
     }
 
+    /* Module */
+
+    ModuleObject* module() const {
+        MOZ_ASSERT(isModuleFrame());
+        return exec.module;
+    }
+
+    ModuleObject* maybeModule() const {
+        return isModuleFrame() ? module() : nullptr;
+    }
+
     /*
      * This value
      *
      * Every frame has a this value although, until 'this' is computed, the
      * value may not be the semantically-correct 'this' value.
      *
      * The 'this' value is stored before the formal arguments for function
      * frames and directly before the frame for global frames. The *Args