From adf4b1ae8725c18a4a08010c1be583aeac3d0f92 Mon Sep 17 00:00:00 2001 From: Jon Coppeard Date: Mon, 24 Aug 2015 15:58:35 +0100 Subject: [PATCH] Bug 930414 - Add ModuleObject and CompileModule() function r=shu --- js/src/builtin/ModuleObject.cpp | 61 +++++++++ js/src/builtin/ModuleObject.h | 39 ++++++ js/src/frontend/BytecodeCompiler.cpp | 62 ++++++++++ js/src/frontend/BytecodeCompiler.h | 5 + js/src/frontend/BytecodeEmitter.cpp | 53 ++++++++ js/src/frontend/BytecodeEmitter.h | 3 + js/src/frontend/FoldConstants.cpp | 18 +++ js/src/frontend/FullParseHandler.h | 10 +- js/src/frontend/NameFunctions.cpp | 3 +- js/src/frontend/ParseNode.cpp | 11 ++ js/src/frontend/ParseNode.h | 18 ++- js/src/frontend/Parser.cpp | 124 ++++++++++++++++++- js/src/frontend/Parser.h | 34 +++-- js/src/frontend/SharedContext.h | 27 +++- js/src/gc/Marking.cpp | 1 + js/src/jit-test/tests/modules/shell-parse.js | 4 + js/src/jsscript.cpp | 22 ++++ js/src/jsscript.h | 14 ++- js/src/jsscriptinlines.h | 8 ++ js/src/moz.build | 1 + js/src/shell/js.cpp | 51 +++++++- js/src/vm/ScopeObject.h | 11 +- js/src/vm/TraceLogging.cpp | 1 + js/src/vm/TraceLoggingTypes.h | 1 + 24 files changed, 556 insertions(+), 26 deletions(-) create mode 100644 js/src/builtin/ModuleObject.cpp create mode 100644 js/src/builtin/ModuleObject.h create mode 100644 js/src/jit-test/tests/modules/shell-parse.js diff --git a/js/src/builtin/ModuleObject.cpp b/js/src/builtin/ModuleObject.cpp new file mode 100644 index 000000000000..ff1f6da1611d --- /dev/null +++ b/js/src/builtin/ModuleObject.cpp @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "builtin/ModuleObject.h" + +#include "jsobjinlines.h" + +using namespace js; + +/////////////////////////////////////////////////////////////////////////// +// ModuleObject + +const Class ModuleObject::class_ = { + "Module", + JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) | + JSCLASS_IS_ANONYMOUS | + JSCLASS_IMPLEMENTS_BARRIERS, + nullptr, /* addProperty */ + nullptr, /* delProperty */ + nullptr, /* getProperty */ + nullptr, /* setProperty */ + nullptr, /* enumerate */ + nullptr, /* resolve */ + nullptr, /* mayResolve */ + nullptr, /* convert */ + nullptr, /* finalize */ + nullptr, /* call */ + nullptr, /* hasInstance */ + nullptr, /* construct */ + ModuleObject::trace +}; + +/* static */ ModuleObject* +ModuleObject::create(ExclusiveContext* cx) +{ + return NewBuiltinClassInstance(cx, TenuredObject); +} + +void +ModuleObject::init(HandleScript script) +{ + initReservedSlot(ScriptSlot, PrivateValue(script)); +} + +JSScript* +ModuleObject::script() const +{ + return static_cast(getReservedSlot(ScriptSlot).toPrivate()); +} + +/* static */ void +ModuleObject::trace(JSTracer* trc, JSObject* obj) +{ + ModuleObject& module = obj->as(); + JSScript* script = module.script(); + TraceManuallyBarrieredEdge(trc, &script, "Module script"); + module.setReservedSlot(ScriptSlot, PrivateValue(script)); +} diff --git a/js/src/builtin/ModuleObject.h b/js/src/builtin/ModuleObject.h new file mode 100644 index 000000000000..04bd627213fc --- /dev/null +++ b/js/src/builtin/ModuleObject.h @@ -0,0 +1,39 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=8 sts=4 et sw=4 tw=99: + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef builtin_ModuleObject_h +#define builtin_ModuleObject_h + +#include "vm/NativeObject.h" + +namespace js { + +class ModuleObject : public NativeObject +{ + public: + enum + { + ScriptSlot = 0, + SlotCount + }; + + static const Class class_; + + static ModuleObject* create(ExclusiveContext* cx); + void init(HandleScript script); + + JSScript* script() const; + + private: + static void trace(JSTracer* trc, JSObject* obj); +}; + +typedef Rooted RootedModuleObject; +typedef Handle HandleModuleObject; + +} // namespace js + +#endif /* builtin_ModuleObject_h */ diff --git a/js/src/frontend/BytecodeCompiler.cpp b/js/src/frontend/BytecodeCompiler.cpp index 51845c3ba1d3..bd64dd459250 100644 --- a/js/src/frontend/BytecodeCompiler.cpp +++ b/js/src/frontend/BytecodeCompiler.cpp @@ -10,6 +10,7 @@ #include "jsscript.h" #include "asmjs/AsmJSLink.h" +#include "builtin/ModuleObject.h" #include "frontend/BytecodeEmitter.h" #include "frontend/FoldConstants.h" #include "frontend/NameFunctions.h" @@ -57,6 +58,7 @@ class MOZ_STACK_CLASS BytecodeCompiler void setSourceArgumentsNotIncluded(); JSScript* compileScript(HandleObject scopeChain, HandleScript evalCaller); + ModuleObject* compileModule(); bool compileFunctionBody(MutableHandleFunction fun, Handle formals, GeneratorKind generatorKind); @@ -606,6 +608,50 @@ BytecodeCompiler::compileScript(HandleObject scopeChain, HandleScript evalCaller return script; } +ModuleObject* BytecodeCompiler::compileModule() +{ + MOZ_ASSERT(!enclosingStaticScope); + + if (!createSourceAndParser()) + return nullptr; + + if (!createScript()) + return nullptr; + + Rooted module(cx, ModuleObject::create(cx)); + if (!module) + return nullptr; + + module->init(script); + + ParseNode* pn = parser->standaloneModule(module); + if (!pn) + return nullptr; + + if (!NameFunctions(cx, pn) || + !maybeSetDisplayURL(parser->tokenStream) || + !maybeSetSourceMap(parser->tokenStream)) + { + return nullptr; + } + + script->bindings = pn->pn_modulebox->bindings; + + if (!createEmitter(pn->pn_modulebox) || + !emitter->emitModuleScript(pn->pn_body)) + { + return nullptr; + } + + if (!maybeCompleteCompressSource()) + return nullptr; + + parser->handler.freeTree(pn); + + MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending()); + return module; +} + bool BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun, Handle formals, @@ -721,6 +767,22 @@ frontend::CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, HandleObject sco return compiler.compileScript(scopeChain, evalCaller); } +ModuleObject* +frontend::CompileModule(JSContext* cx, HandleObject obj, + const ReadOnlyCompileOptions& optionsInput, + SourceBufferHolder& srcBuf) +{ + MOZ_ASSERT(srcBuf.get()); + + CompileOptions options(cx, optionsInput); + options.maybeMakeStrictMode(true); // ES6 10.2.1 Module code is always strict mode code. + options.setIsRunOnce(true); + + BytecodeCompiler compiler(cx, &cx->tempLifoAlloc(), options, srcBuf, nullptr, + TraceLogger_ParserCompileModule); + return compiler.compileModule(); +} + bool frontend::CompileLazyFunction(JSContext* cx, Handle lazy, const char16_t* chars, size_t length) { diff --git a/js/src/frontend/BytecodeCompiler.h b/js/src/frontend/BytecodeCompiler.h index 6cb1a3b8409a..a05059033f62 100644 --- a/js/src/frontend/BytecodeCompiler.h +++ b/js/src/frontend/BytecodeCompiler.h @@ -17,6 +17,7 @@ namespace js { class LazyScript; class LifoAlloc; +class ModuleObject; class ScriptSourceObject; class ScopeObject; struct SourceCompressionTask; @@ -30,6 +31,10 @@ CompileScript(ExclusiveContext* cx, LifoAlloc* alloc, SourceBufferHolder& srcBuf, JSString* source_ = nullptr, SourceCompressionTask* extraSct = nullptr); +ModuleObject * +CompileModule(JSContext *cx, HandleObject obj, const ReadOnlyCompileOptions &options, + SourceBufferHolder &srcBuf); + bool CompileLazyFunction(JSContext* cx, Handle lazy, const char16_t* chars, size_t length); diff --git a/js/src/frontend/BytecodeEmitter.cpp b/js/src/frontend/BytecodeEmitter.cpp index ae6a5748b8f2..e9cdab8b98ab 100644 --- a/js/src/frontend/BytecodeEmitter.cpp +++ b/js/src/frontend/BytecodeEmitter.cpp @@ -2212,6 +2212,10 @@ BytecodeEmitter::checkSideEffects(ParseNode* pn, bool* answer) *answer = false; return true; + case PNK_MODULE: + *answer = false; + return true; + // Generator expressions have no side effects on their own. case PNK_GENEXP: MOZ_ASSERT(pn->isArity(PN_LIST)); @@ -3500,6 +3504,55 @@ BytecodeEmitter::emitFunctionScript(ParseNode* body) return true; } +bool +BytecodeEmitter::emitModuleScript(ParseNode* body) +{ + if (!updateLocalsToFrameSlots()) + return false; + + /* + * 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(); + + // 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; + + // Always end the script with a JSOP_RETRVAL. Some other parts of the codebase + // depend on this opcode, e.g. InterpreterRegs::setToEndOfScript. + if (!emit1(JSOP_RETRVAL)) + return false; + + // If all locals are aliased, the frame's block slots won't be used, so we + // can set numBlockScoped = 0. This is nice for generators as it ensures + // nfixed == 0, so we don't have to initialize any local slots when resuming + // a generator. + if (sc->allLocalsAliased()) + script->bindings.setAllLocalsAliased(); + + if (!JSScript::fullyInitFromEmitter(cx, script, this)) + return false; + + /* + * Since modules are only run once. Mark the script so that initializers + * created within it may be given more precise types. + */ + script->setTreatAsRunOnce(); + MOZ_ASSERT(!script->hasRunOnce()); + + tellDebuggerAboutCompiledScript(cx); + + return true; +} + bool BytecodeEmitter::maybeEmitVarDecl(JSOp prologueOp, ParseNode* pn, jsatomid* result) { diff --git a/js/src/frontend/BytecodeEmitter.h b/js/src/frontend/BytecodeEmitter.h index d0df5c9b969c..c068846f0621 100644 --- a/js/src/frontend/BytecodeEmitter.h +++ b/js/src/frontend/BytecodeEmitter.h @@ -320,6 +320,9 @@ struct BytecodeEmitter // Emit function code for the tree rooted at body. bool emitFunctionScript(ParseNode* body); + // Emit module code for the tree rooted at body. + bool emitModuleScript(ParseNode* body); + // If op is JOF_TYPESET (see the type barriers comment in TypeInference.h), // reserve a type set to store its result. void checkTypeSet(JSOp op); diff --git a/js/src/frontend/FoldConstants.cpp b/js/src/frontend/FoldConstants.cpp index 86e917cad62e..d7efc8b99a46 100644 --- a/js/src/frontend/FoldConstants.cpp +++ b/js/src/frontend/FoldConstants.cpp @@ -100,6 +100,10 @@ ContainsHoistedDeclaration(ExclusiveContext* cx, ParseNode* node, bool* result) *result = false; return true; + case PNK_MODULE: + *result = false; + return true; + // Statements with no sub-components at all. case PNK_NOP: // induced by function f() {} function f() {} case PNK_DEBUGGER: @@ -1114,6 +1118,17 @@ ComputeBinary(ParseNodeKind kind, double left, double right) return int32_t((kind == PNK_LSH) ? uint32_t(i) << j : i >> j); } +static bool +FoldModule(ExclusiveContext* cx, ParseNode* node, Parser& parser) +{ + MOZ_ASSERT(node->isKind(PNK_MODULE)); + MOZ_ASSERT(node->isArity(PN_CODE)); + + ParseNode*& moduleBody = node->pn_body; + MOZ_ASSERT(moduleBody); + return Fold(cx, &moduleBody, parser, false); +} + static bool FoldBinaryArithmetic(ExclusiveContext* cx, ParseNode* node, Parser& parser, bool inGenexpLambda) @@ -1787,6 +1802,9 @@ Fold(ExclusiveContext* cx, ParseNode** pnp, Parser& parser, bo case PNK_FUNCTION: return FoldFunction(cx, pn, parser, inGenexpLambda); + case PNK_MODULE: + return FoldModule(cx, pn, parser); + case PNK_SUB: case PNK_STAR: case PNK_LSH: diff --git a/js/src/frontend/FullParseHandler.h b/js/src/frontend/FullParseHandler.h index ada1a85cf278..7387f41df3b9 100644 --- a/js/src/frontend/FullParseHandler.h +++ b/js/src/frontend/FullParseHandler.h @@ -635,7 +635,7 @@ class FullParseHandler inline void setLastFunctionArgumentDestructuring(ParseNode* funcpn, ParseNode* pn); ParseNode* newFunctionDefinition() { - return new_(pos()); + return new_(PNK_FUNCTION, pos()); } void setFunctionBody(ParseNode* pn, ParseNode* kid) { pn->pn_body = kid; @@ -652,6 +652,14 @@ class FullParseHandler pn->pn_funbox->setDerivedClassConstructor(); } + ParseNode* newModule() { + return new_(PNK_MODULE, pos()); + } + void setModuleBox(ParseNode* pn, ModuleBox* modulebox) { + MOZ_ASSERT(pn->isKind(PNK_MODULE)); + pn->pn_modulebox = modulebox; + } + ParseNode* newLexicalScope(ObjectBox* blockBox) { return new_(blockBox, pos()); } diff --git a/js/src/frontend/NameFunctions.cpp b/js/src/frontend/NameFunctions.cpp index a8eb84f5c4f1..352f2291dae2 100644 --- a/js/src/frontend/NameFunctions.cpp +++ b/js/src/frontend/NameFunctions.cpp @@ -337,7 +337,7 @@ class NameResolver if (cur == nullptr) return true; - MOZ_ASSERT(cur->isKind(PNK_FUNCTION) == cur->isArity(PN_CODE)); + MOZ_ASSERT((cur->isKind(PNK_FUNCTION) || cur->isKind(PNK_MODULE)) == cur->isArity(PN_CODE)); if (cur->isKind(PNK_FUNCTION)) { RootedAtom prefix2(cx); if (!resolveFun(cur, prefix, &prefix2)) @@ -767,6 +767,7 @@ class NameResolver break; case PNK_FUNCTION: + case PNK_MODULE: MOZ_ASSERT(cur->isArity(PN_CODE)); if (!resolve(cur->pn_body, prefix)) return false; diff --git a/js/src/frontend/ParseNode.cpp b/js/src/frontend/ParseNode.cpp index d4a10d08a243..74b2132e57ff 100644 --- a/js/src/frontend/ParseNode.cpp +++ b/js/src/frontend/ParseNode.cpp @@ -521,6 +521,7 @@ PushNodeChildren(ParseNode* pn, NodeStack* stack) return PushNameNodeChildren(pn, stack); case PNK_FUNCTION: + case PNK_MODULE: return PushCodeNodeChildren(pn, stack); case PNK_LIMIT: // invalid sentinel value @@ -1151,6 +1152,13 @@ ObjectBox::asFunctionBox() return static_cast(this); } +ModuleBox* +ObjectBox::asModuleBox() +{ + MOZ_ASSERT(isModuleBox()); + return static_cast(this); +} + void ObjectBox::trace(JSTracer* trc) { @@ -1162,6 +1170,9 @@ ObjectBox::trace(JSTracer* trc) funbox->bindings.trace(trc); if (funbox->enclosingStaticScope_) TraceRoot(trc, &funbox->enclosingStaticScope_, "funbox-enclosingStaticScope"); + } else if (box->isModuleBox()) { + ModuleBox* modulebox = box->asModuleBox(); + modulebox->bindings.trace(trc); } box = box->traceLink; } diff --git a/js/src/frontend/ParseNode.h b/js/src/frontend/ParseNode.h index cead3676fe49..56914017beab 100644 --- a/js/src/frontend/ParseNode.h +++ b/js/src/frontend/ParseNode.h @@ -19,6 +19,7 @@ struct ParseContext; class FullParseHandler; class FunctionBox; +class ModuleBox; class ObjectBox; // A packed ScopeCoordinate for use in the frontend during bytecode @@ -118,6 +119,7 @@ class PackedScopeCoordinate F(NULL) \ F(THIS) \ F(FUNCTION) \ + F(MODULE) \ F(IF) \ F(SWITCH) \ F(CASE) \ @@ -636,9 +638,10 @@ class ParseNode } unary; struct { /* name, labeled statement, etc. */ union { - JSAtom* atom; /* lexical name or label atom */ - ObjectBox* objbox; /* block or regexp object */ + JSAtom* atom; /* lexical name or label atom */ + ObjectBox* objbox; /* block or regexp object */ FunctionBox* funbox; /* function object */ + ModuleBox* modulebox; /* module object */ }; union { ParseNode* expr; /* module or function body, var @@ -662,6 +665,7 @@ class ParseNode } pn_u; #define pn_modulebox pn_u.name.modulebox +#define pn_objbox pn_u.name.objbox #define pn_funbox pn_u.name.funbox #define pn_body pn_u.name.expr #define pn_scopecoord pn_u.name.scopeCoord @@ -1072,15 +1076,17 @@ struct ListNode : public ParseNode struct CodeNode : public ParseNode { - explicit CodeNode(const TokenPos& pos) - : ParseNode(PNK_FUNCTION, JSOP_NOP, PN_CODE, pos) + CodeNode(ParseNodeKind kind, const TokenPos& pos) + : ParseNode(kind, JSOP_NOP, PN_CODE, pos) { + MOZ_ASSERT(kind == PNK_FUNCTION || kind == PNK_MODULE); MOZ_ASSERT(!pn_body); - MOZ_ASSERT(!pn_funbox); + MOZ_ASSERT(!pn_objbox); MOZ_ASSERT(pn_dflags == 0); pn_scopecoord.makeFree(); } + public: #ifdef DEBUG void dump(int indent); #endif @@ -1687,6 +1693,8 @@ class ObjectBox ObjectBox(JSObject* object, ObjectBox* traceLink); bool isFunctionBox() { return object->is(); } FunctionBox* asFunctionBox(); + bool isModuleBox() { return object->is(); } + ModuleBox* asModuleBox(); void trace(JSTracer* trc); protected: diff --git a/js/src/frontend/Parser.cpp b/js/src/frontend/Parser.cpp index 22f81cc73fe7..f7a7f4e55c29 100644 --- a/js/src/frontend/Parser.cpp +++ b/js/src/frontend/Parser.cpp @@ -29,6 +29,7 @@ #include "jstypes.h" #include "asmjs/AsmJSValidate.h" +#include "builtin/ModuleObject.h" #include "frontend/BytecodeCompiler.h" #include "frontend/FoldConstants.h" #include "frontend/ParseMaps.h" @@ -409,12 +410,9 @@ AppendPackedBindings(const ParseContext* pc, const DeclVector& vec template bool -ParseContext::generateFunctionBindings(ExclusiveContext* cx, TokenStream& ts, - LifoAlloc& alloc, - MutableHandle bindings) const +ParseContext::generateBindings(ExclusiveContext* cx, TokenStream& ts, LifoAlloc& alloc, + MutableHandle bindings) const { - MOZ_ASSERT(sc->isFunctionBox()); - MOZ_ASSERT(args_.length() < ARGNO_LIMIT); MOZ_ASSERT(vars_.length() + bodyLevelLexicals_.length() < LOCALNO_LIMIT); /* @@ -453,6 +451,28 @@ ParseContext::generateFunctionBindings(ExclusiveContext* cx, Token packedBindings); } +template +bool +ParseContext::generateFunctionBindings(ExclusiveContext* cx, TokenStream& ts, + LifoAlloc& alloc, + MutableHandle bindings) const +{ + MOZ_ASSERT(sc->isFunctionBox()); + MOZ_ASSERT(args_.length() < ARGNO_LIMIT); + return generateBindings(cx, ts, alloc, bindings); +} + +template +bool +ParseContext::generateModuleBindings(ExclusiveContext* cx, TokenStream& ts, + LifoAlloc& alloc, + MutableHandle bindings) const +{ + MOZ_ASSERT(sc->isModuleBox()); + MOZ_ASSERT(args_.length() == 0); + return generateBindings(cx, ts, alloc, bindings); +} + template bool Parser::reportHelper(ParseReportKind kind, bool strict, uint32_t offset, @@ -688,6 +708,49 @@ Parser::newFunctionBox(Node fn, JSFunction* fun, return funbox; } +template +ModuleBox::ModuleBox(ExclusiveContext* cx, ObjectBox* traceListHead, ModuleObject* module, + ParseContext* outerpc) + : ObjectBox(module, traceListHead), + SharedContext(cx, Directives(true), extraWarnings), + bindings() +{} + +template +ModuleBox* +Parser::newModuleBox(Node pn, HandleModuleObject module) +{ + MOZ_ASSERT(module); + + /* + * We use JSContext.tempLifoAlloc to allocate parsed objects and place them + * on a list in this Parser to ensure GC safety. Thus the tempLifoAlloc + * arenas containing the entries must be alive until we are done with + * scanning, parsing and code generation for the whole module. + */ + ParseContext* outerpc = nullptr; + ModuleBox* modbox = + alloc.new_(context, traceListHead, module, outerpc); + if (!modbox) { + ReportOutOfMemory(context); + return nullptr; + } + + traceListHead = modbox; + if (pn) + handler.setModuleBox(pn, modbox); + + return modbox; +} + +template <> +ModuleBox* +Parser::newModuleBox(Node pn, HandleModuleObject module) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return nullptr; +} + template void Parser::trace(JSTracer* trc) @@ -785,6 +848,57 @@ Parser::checkStrictBinding(PropertyName* name, Node pn) return true; } +template +typename ParseHandler::Node +Parser::standaloneModule(HandleModuleObject module) +{ + MOZ_ASSERT(checkOptionsCalled); + + Node mn = handler.newModule(); + if (!mn) + return null(); + + ModuleBox* modulebox = newModuleBox(mn, module); + if (!modulebox) + return null(); + handler.setModuleBox(mn, modulebox); + + ParseContext modulepc(this, pc, mn, modulebox, nullptr, 0); + if (!modulepc.init(*this)) + return null(); + + ParseNode* pn = statements(YieldIsKeyword); + if (!pn) + return null(); + + 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); + + if (!FoldConstants(context, &pn, this)) + return null(); + + Rooted bindings(context, modulebox->bindings); + if (!modulepc.generateModuleBindings(context, tokenStream, alloc, &bindings)) + return null(); + modulebox->bindings = bindings; + + MOZ_ASSERT(mn->pn_modulebox == modulebox); + return mn; +} + +template <> +SyntaxParseHandler::Node +Parser::standaloneModule(HandleModuleObject module) +{ + MOZ_ALWAYS_FALSE(abortIfSyntaxParser()); + return SyntaxParseHandler::NodeFailure; +} + template <> ParseNode* Parser::standaloneFunctionBody(HandleFunction fun, diff --git a/js/src/frontend/Parser.h b/js/src/frontend/Parser.h index 2169cb078581..a99c81c56751 100644 --- a/js/src/frontend/Parser.h +++ b/js/src/frontend/Parser.h @@ -22,7 +22,9 @@ namespace js { +class ModuleObject; class StaticFunctionBoxScopeObject; +class StaticModuleBoxScopeObject; namespace frontend { @@ -197,23 +199,32 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext void updateDecl(JSAtom* atom, Node newDecl); /* - * After a function body has been parsed, the parser generates the - * function's "bindings". Bindings are a data-structure, ultimately stored - * in the compiled JSScript, that serve three purposes: + * After a function body or module has been parsed, the parser generates the + * code's "bindings". Bindings are a data-structure, ultimately stored in + * the compiled JSScript, that serve three purposes: * - After parsing, the ParseContext is destroyed and 'decls' along with * it. Mostly, the emitter just uses the binding information stored in * 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) for the function. This shape is used - * during dynamic name lookup. + * 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) const; + + public: bool generateFunctionBindings(ExclusiveContext* cx, TokenStream& ts, LifoAlloc& alloc, MutableHandle bindings) const; + bool generateModuleBindings(ExclusiveContext* cx, TokenStream& ts, + LifoAlloc& alloc, + MutableHandle bindings) const; + private: ParseContext** parserPC; /* this points to the Parser's active pc and holds either |this| or one of @@ -300,8 +311,13 @@ struct MOZ_STACK_CLASS ParseContext : public GenericParseContext // function f1() { function f2() { } } // if (cond) { function f3() { if (cond) { function f4() { } } } } // - bool atBodyLevel() { return !innermostStmt(); } - bool atGlobalLevel() { return atBodyLevel() && !sc->isFunctionBox() && !innermostScopeStmt(); } + bool atBodyLevel() { + return !innermostStmt(); + } + + bool atGlobalLevel() { + return atBodyLevel() && !sc->isFunctionBox() && !innermostScopeStmt(); + } // True if this is the ParseContext for the body of a function created by // the Function constructor. @@ -490,6 +506,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter return newFunctionBox(fn, fun, outerpc, directives, generatorKind, enclosing); } + ModuleBox* newModuleBox(Node pn, HandleModuleObject module); + /* * Create a new function object given a name (which is optional if this is * a function expression). @@ -542,6 +560,8 @@ class Parser : private JS::AutoGCRooter, public StrictModeGetter bool maybeParseDirective(Node list, Node pn, bool* cont); + Node standaloneModule(Handle module); + // Parse a function, given only its body. Used for the Function and // Generator constructors. Node standaloneFunctionBody(HandleFunction fun, Handle formals, diff --git a/js/src/frontend/SharedContext.h b/js/src/frontend/SharedContext.h index 47e08112ead5..e1605710c042 100644 --- a/js/src/frontend/SharedContext.h +++ b/js/src/frontend/SharedContext.h @@ -13,6 +13,7 @@ #include "jsscript.h" #include "jstypes.h" +#include "builtin/ModuleObject.h" #include "frontend/ParseMaps.h" #include "frontend/ParseNode.h" #include "frontend/TokenStream.h" @@ -215,8 +216,11 @@ class SharedContext void computeInWith(JSObject* staticScope); virtual ObjectBox* toObjectBox() { return nullptr; } - inline bool isFunctionBox() { return toObjectBox() && toObjectBox()->isFunctionBox(); } + bool isObjectBox() { return toObjectBox() != nullptr; } + bool isFunctionBox() { return isObjectBox() && toObjectBox()->isFunctionBox(); } inline FunctionBox* asFunctionBox(); + bool isModuleBox() { return isObjectBox() && toObjectBox()->isModuleBox(); } + inline ModuleBox* asModuleBox(); bool allowNewTarget() const { return allowNewTarget_; } bool allowSuperProperty() const { return allowSuperProperty_; } @@ -368,6 +372,20 @@ class FunctionBox : public ObjectBox, public SharedContext } }; +class ModuleBox : public ObjectBox, public SharedContext +{ + public: + Bindings bindings; + + template + ModuleBox(ExclusiveContext* cx, ObjectBox* traceListHead, ModuleObject* module, + ParseContext* pc); + + ObjectBox* toObjectBox() override { return this; } + ModuleObject* module() const { return &object->as(); } + JSObject* staticScope() const override { return module(); } +}; + inline FunctionBox* SharedContext::asFunctionBox() { @@ -375,6 +393,13 @@ SharedContext::asFunctionBox() return static_cast(this); } +inline ModuleBox* +SharedContext::asModuleBox() +{ + MOZ_ASSERT(isModuleBox()); + return static_cast(this); +} + // In generators, we treat all locals as aliased so that they get stored on the // heap. This way there is less information to copy off the stack when diff --git a/js/src/gc/Marking.cpp b/js/src/gc/Marking.cpp index 49d83ddca4c3..b49d17d772cd 100644 --- a/js/src/gc/Marking.cpp +++ b/js/src/gc/Marking.cpp @@ -361,6 +361,7 @@ AssertRootMarkingPhase(JSTracer* trc) D(GlobalObject*) \ D(JSObject*) \ D(JSFunction*) \ + D(ModuleObject*) \ D(NestedScopeObject*) \ D(PlainObject*) \ D(SavedFrame*) \ diff --git a/js/src/jit-test/tests/modules/shell-parse.js b/js/src/jit-test/tests/modules/shell-parse.js new file mode 100644 index 000000000000..9d93888548f1 --- /dev/null +++ b/js/src/jit-test/tests/modules/shell-parse.js @@ -0,0 +1,4 @@ +// Exercise shell parseModule function. + +parseModule(""); +parseModule("const foo = 1;"); diff --git a/js/src/jsscript.cpp b/js/src/jsscript.cpp index 48d38b8a8302..6bf8f1e131f0 100644 --- a/js/src/jsscript.cpp +++ b/js/src/jsscript.cpp @@ -2652,6 +2652,25 @@ JSScript::linkToFunctionFromEmitter(js::ExclusiveContext* cx, JS::HandlesetScript(script); } +/* static */ void +JSScript::linkToModuleFromEmitter(js::ExclusiveContext* cx, JS::Handle script, + js::frontend::ModuleBox* modulebox) +{ + script->funHasExtensibleScope_ = false; + script->funNeedsDeclEnvObject_ = false; + script->needsHomeObject_ = false; + script->isDerivedClassConstructor_ = false; + script->funLength_ = 0; + + script->isGeneratorExp_ = false; + script->setGeneratorKind(NotGenerator); + + // Link the module and the script to each other, so that StaticScopeIter + // may walk the scope chain of currently compiling scripts. + RootedModuleObject module(cx, modulebox->module()); + script->setModule(module); +} + /* static */ bool JSScript::fullyInitFromEmitter(ExclusiveContext* cx, HandleScript script, BytecodeEmitter* bce) { @@ -3595,6 +3614,9 @@ JSScript::traceChildren(JSTracer* trc) if (functionNonDelazifying()) TraceEdge(trc, &function_, "function"); + if (module_) + TraceEdge(trc, &module_, "module"); + if (enclosingStaticScope_) TraceEdge(trc, &enclosingStaticScope_, "enclosingStaticScope"); diff --git a/js/src/jsscript.h b/js/src/jsscript.h index 187163363607..9fa78a9e6b53 100644 --- a/js/src/jsscript.h +++ b/js/src/jsscript.h @@ -18,6 +18,7 @@ #include "jsopcode.h" #include "jstypes.h" +#include "builtin/ModuleObject.h" #include "gc/Barrier.h" #include "gc/Rooting.h" #include "jit/IonCode.h" @@ -46,15 +47,16 @@ class BreakpointSite; class BindingIter; class Debugger; class LazyScript; +class NestedScopeObject; class RegExpObject; struct SourceCompressionTask; class Shape; -class NestedScopeObject; namespace frontend { struct BytecodeEmitter; class UpvarCookie; class FunctionBox; + class ModuleBox; } // namespace frontend namespace detail { @@ -949,6 +951,7 @@ class JSScript : public js::gc::TenuredCell js::HeapPtrObject sourceObject_; js::HeapPtrFunction function_; + js::HeapPtr module_; js::HeapPtrObject enclosingStaticScope_; /* @@ -1139,7 +1142,7 @@ class JSScript : public js::gc::TenuredCell // instead of private to suppress -Wunused-private-field compiler warnings. protected: #if JS_BITS_PER_WORD == 32 - uint32_t padding; + // No padding currently required. #endif // @@ -1167,6 +1170,8 @@ class JSScript : public js::gc::TenuredCell js::frontend::BytecodeEmitter* bce); static void linkToFunctionFromEmitter(js::ExclusiveContext* cx, JS::Handle script, js::frontend::FunctionBox* funbox); + static void linkToModuleFromEmitter(js::ExclusiveContext* cx, JS::Handle script, + js::frontend::ModuleBox* funbox); // Initialize a no-op script. static bool fullyInitTrivial(js::ExclusiveContext* cx, JS::Handle script); @@ -1547,6 +1552,11 @@ class JSScript : public js::gc::TenuredCell */ inline void ensureNonLazyCanonicalFunction(JSContext* cx); + js::ModuleObject* module() const { + return module_; + } + inline void setModule(js::ModuleObject* module); + JSFlatString* sourceData(JSContext* cx); static bool loadSource(JSContext* cx, js::ScriptSource* ss, bool* worked); diff --git a/js/src/jsscriptinlines.h b/js/src/jsscriptinlines.h index b3b7867da206..09c0c1684bf5 100644 --- a/js/src/jsscriptinlines.h +++ b/js/src/jsscriptinlines.h @@ -67,10 +67,18 @@ JSScript::functionDelazifying() const inline void JSScript::setFunction(JSFunction* fun) { + MOZ_ASSERT(!function_ && !module_); MOZ_ASSERT(fun->isTenured()); function_ = fun; } +inline void +JSScript::setModule(js::ModuleObject* module) +{ + MOZ_ASSERT(!function_ && !module_); + module_ = module; +} + inline void JSScript::ensureNonLazyCanonicalFunction(JSContext* cx) { diff --git a/js/src/moz.build b/js/src/moz.build index e319749f9fb6..f6660f5dda46 100644 --- a/js/src/moz.build +++ b/js/src/moz.build @@ -147,6 +147,7 @@ UNIFIED_SOURCES += [ 'builtin/Eval.cpp', 'builtin/Intl.cpp', 'builtin/MapObject.cpp', + 'builtin/ModuleObject.cpp', 'builtin/Object.cpp', 'builtin/Profilers.cpp', 'builtin/Reflect.cpp', diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index 8762f4018c65..55e2b1721eeb 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -56,6 +56,7 @@ #endif #include "jswrapper.h" +#include "builtin/ModuleObject.h" #include "builtin/TestingFunctions.h" #include "frontend/Parser.h" #include "gc/GCInternals.h" @@ -2067,7 +2068,12 @@ DisassembleToSprinter(JSContext* cx, unsigned argc, Value* vp, Sprinter* sprinte } else { for (unsigned i = 0; i < p.argc; i++) { RootedFunction fun(cx); - RootedScript script (cx, ValueToScript(cx, p.argv[i], fun.address())); + RootedScript script(cx); + RootedValue value(cx, p.argv[i]); + if (value.isObject() && value.toObject().is()) + script = value.toObject().as().script(); + else + script = ValueToScript(cx, value, fun.address()); if (!script) return false; if (!DisassembleScript(cx, script, fun, p.lines, p.recursive, sprinter)) @@ -3058,6 +3064,45 @@ Compile(JSContext* cx, unsigned argc, Value* vp) return ok; } +static bool +ParseModule(JSContext* cx, unsigned argc, Value* vp) +{ + CallArgs args = CallArgsFromVp(argc, vp); + if (args.length() < 1) { + JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, + JSMSG_MORE_ARGS_NEEDED, "parseModule", "0", "s"); + return false; + } + if (!args[0].isString()) { + JS_ReportError(cx, "expected string to compile, got %s", + JS_TypeOfValue(cx, args[0])); + return false; + } + + RootedObject global(cx, JS::CurrentGlobalOrNull(cx)); + JSFlatString* scriptContents = args[0].toString()->ensureFlat(cx); + if (!scriptContents) + return false; + + CompileOptions options(cx); + options.setFileAndLine("", 1); + + AutoStableStringChars stableChars(cx); + if (!stableChars.initTwoByte(cx, scriptContents)) + return false; + + const char16_t* chars = stableChars.twoByteRange().start().get(); + SourceBufferHolder srcBuf(chars, scriptContents->length(), + SourceBufferHolder::NoOwnership); + + RootedObject module(cx, frontend::CompileModule(cx, global, options, srcBuf)); + if (!module) + return false; + + args.rval().setObject(*module); + return true; +} + static bool Parse(JSContext* cx, unsigned argc, Value* vp) { @@ -4606,6 +4651,10 @@ static const JSFunctionSpecWithHelp shell_functions[] = { "compile(code)", " Compiles a string to bytecode, potentially throwing."), + JS_FN_HELP("parseModule", ParseModule, 1, 0, +"parseModule(code)", +" Parses source text as a module and returns a Module object."), + JS_FN_HELP("parse", Parse, 1, 0, "parse(code)", " Parses a string, potentially throwing."), diff --git a/js/src/vm/ScopeObject.h b/js/src/vm/ScopeObject.h index 12c83926ceab..5edb118903cc 100644 --- a/js/src/vm/ScopeObject.h +++ b/js/src/vm/ScopeObject.h @@ -21,6 +21,7 @@ namespace js { namespace frontend { struct Definition; class FunctionBox; +class ModuleBox; } class StaticWithObject; @@ -47,6 +48,9 @@ class StaticNonSyntacticScopeObjects; * JSFunction * Scope for function bodies. e.g., |function f() { var x; let y; }| * + * ModuleObject + * Scope for moddules. + * * StaticWithObject * Scope for |with|. e.g., |with ({}) { ... }| * @@ -63,8 +67,8 @@ class StaticNonSyntacticScopeObjects; * * (function f() { var x; function g() { } }) * - * All static scope objects are ScopeObjects with the exception of - * JSFunction. A JSFunction keeps its enclosing scope link on + * All static scope objects are ScopeObjects with the exception of JSFunction + * and ModuleObject, which keeps their enclosing scope link on * |JSScript::enclosingStaticScope()|. */ template @@ -78,7 +82,8 @@ class StaticScopeIter obj->is() || obj->is() || obj->is() || - obj->is(); + obj->is() || + obj->is(); } public: diff --git a/js/src/vm/TraceLogging.cpp b/js/src/vm/TraceLogging.cpp index 25a363d233be..a9af5e14469c 100644 --- a/js/src/vm/TraceLogging.cpp +++ b/js/src/vm/TraceLogging.cpp @@ -672,6 +672,7 @@ TraceLoggerThreadState::init() enabledTextIds[TraceLogger_ParserCompileFunction] = true; enabledTextIds[TraceLogger_ParserCompileLazy] = true; enabledTextIds[TraceLogger_ParserCompileScript] = true; + enabledTextIds[TraceLogger_ParserCompileModule] = true; enabledTextIds[TraceLogger_IrregexpCompile] = true; enabledTextIds[TraceLogger_IrregexpExecute] = true; enabledTextIds[TraceLogger_Scripts] = true; diff --git a/js/src/vm/TraceLoggingTypes.h b/js/src/vm/TraceLoggingTypes.h index eb70a1ebf86b..fb7b50a9af58 100644 --- a/js/src/vm/TraceLoggingTypes.h +++ b/js/src/vm/TraceLoggingTypes.h @@ -32,6 +32,7 @@ _(ParserCompileFunction) \ _(ParserCompileLazy) \ _(ParserCompileScript) \ + _(ParserCompileModule) \ _(Scripts) \ _(VM) \ \