Bug 930414 - Add module requestedModules field r=shu
authorJon Coppeard <jcoppeard@mozilla.com>
Mon, 24 Aug 2015 15:58:35 +0100
changeset 259029 7fc1a5cd4b301a3f9507697090e71d9f7e975c9b
parent 259028 52f4c8ddfe07f83c3a36ff96118cc3db1ce6af94
child 259030 7861ef6582a9a013436d2c0f1c70150222775754
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 - Add module requestedModules field r=shu
js/src/builtin/ModuleObject.cpp
js/src/builtin/ModuleObject.h
js/src/frontend/BytecodeCompiler.cpp
js/src/jit-test/tests/modules/requested-modules.js
js/src/jsprototypes.h
js/src/vm/GlobalObject.cpp
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -5,22 +5,52 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "builtin/ModuleObject.h"
 
 #include "jsobjinlines.h"
 
 using namespace js;
 
+template<typename T, Value ValueGetter(T* obj)>
+static bool
+ModuleValueGetterImpl(JSContext* cx, CallArgs args)
+{
+    args.rval().set(ValueGetter(&args.thisv().toObject().as<T>()));
+    return true;
+}
+
+template<typename T, Value ValueGetter(T* obj)>
+static bool
+ModuleValueGetter(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    return CallNonGenericMethod<T::isInstance, ModuleValueGetterImpl<T, ValueGetter>>(cx, args);
+}
+
+#define DEFINE_GETTER_FUNCTIONS(cls, name, slot)                              \
+    static Value                                                              \
+    cls##_##name##Value(cls* obj) {                                           \
+        return obj->getFixedSlot(cls::slot);                                  \
+    }                                                                         \
+                                                                              \
+    static bool                                                               \
+    cls##_##name##Getter(JSContext* cx, unsigned argc, Value* vp)             \
+    {                                                                         \
+        return ModuleValueGetter<cls, cls##_##name##Value>(cx, argc, vp);     \
+    }
+
 ///////////////////////////////////////////////////////////////////////////
 // ModuleObject
 
-const Class ModuleObject::class_ = {
+/* static */ const Class
+ModuleObject::class_ = {
     "Module",
     JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_Module) |
     JSCLASS_IS_ANONYMOUS |
     JSCLASS_IMPLEMENTS_BARRIERS,
     nullptr,        /* addProperty */
     nullptr,        /* delProperty */
     nullptr,        /* getProperty */
     nullptr,        /* setProperty */
     nullptr,        /* enumerate   */
     nullptr,        /* resolve     */
@@ -28,34 +58,190 @@ const Class ModuleObject::class_ = {
     nullptr,        /* convert     */
     nullptr,        /* finalize    */
     nullptr,        /* call        */
     nullptr,        /* hasInstance */
     nullptr,        /* construct   */
     ModuleObject::trace
 };
 
+#define DEFINE_ARRAY_SLOT_ACCESSOR(cls, name, slot)                           \
+    ArrayObject&                                                              \
+    cls::name() const                                                         \
+    {                                                                         \
+        return getFixedSlot(cls::slot).toObject().as<ArrayObject>();          \
+    }
+
+DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, requestedModules, RequestedModulesSlot)
+
+#undef DEFINE_ARRAY_SLOT_ACCESSOR
+
+/* static */ bool
+ModuleObject::isInstance(HandleValue value)
+{
+    return value.isObject() && value.toObject().is<ModuleObject>();
+}
+
 /* static */ ModuleObject*
 ModuleObject::create(ExclusiveContext* cx)
 {
     return NewBuiltinClassInstance<ModuleObject>(cx, TenuredObject);
 }
 
 void
 ModuleObject::init(HandleScript script)
 {
     initReservedSlot(ScriptSlot, PrivateValue(script));
 }
 
+void
+ModuleObject::initImportExportData(HandleArrayObject requestedModules)
+{
+    initReservedSlot(RequestedModulesSlot, ObjectValue(*requestedModules));
+}
+
 JSScript*
 ModuleObject::script() const
 {
     return static_cast<JSScript*>(getReservedSlot(ScriptSlot).toPrivate());
 }
 
 /* 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, requestedModules, RequestedModulesSlot)
+
+#undef DEFINE_GETTER_FUNCTIONS
+
+JSObject*
+js::InitModuleClass(JSContext* cx, HandleObject obj)
+{
+    static const JSPropertySpec protoAccessors[] = {
+        JS_PSG("requestedModules", ModuleObject_requestedModulesGetter, 0),
+        JS_PS_END
+    };
+
+    Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
+
+    RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx));
+    if (!proto)
+        return nullptr;
+
+    if (!DefinePropertiesAndFunctions(cx, proto, protoAccessors, nullptr))
+        return nullptr;
+
+    global->setPrototype(JSProto_Module, ObjectValue(*proto));
+    return proto;
+}
+
+///////////////////////////////////////////////////////////////////////////
+// ModuleBuilder
+
+ModuleBuilder::ModuleBuilder(JSContext* cx)
+  : cx_(cx),
+    requestedModules_(cx, AtomVector(cx))
+{}
+
+bool
+ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject module)
+{
+    MOZ_ASSERT(moduleNode->isKind(PNK_MODULE));
+
+    ParseNode* stmtsNode = moduleNode->pn_expr;
+    MOZ_ASSERT(stmtsNode->isKind(PNK_STATEMENTLIST));
+    MOZ_ASSERT(stmtsNode->isArity(PN_LIST));
+
+    for (ParseNode* pn = stmtsNode->pn_head; pn; pn = pn->pn_next) {
+        switch (pn->getKind()) {
+          case PNK_IMPORT:
+            if (!processImport(pn))
+                return false;
+            break;
+
+          case PNK_EXPORT:
+            break;
+
+          case PNK_EXPORT_FROM:
+            if (!processExportFrom(pn))
+                return false;
+            break;
+
+          case PNK_EXPORT_DEFAULT:
+            break;
+
+          default:
+            break;
+        }
+    }
+
+    RootedArrayObject requestedModules(cx_, createArray<JSAtom*>(requestedModules_));
+    if (!requestedModules)
+        return false;
+
+    module->initImportExportData(requestedModules);
+    return true;
+}
+
+bool
+ModuleBuilder::processImport(frontend::ParseNode* pn)
+{
+    MOZ_ASSERT(pn->isArity(PN_BINARY));
+    MOZ_ASSERT(pn->pn_left->isKind(PNK_IMPORT_SPEC_LIST));
+    MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
+
+    RootedAtom module(cx_, pn->pn_right->pn_atom);
+    if (!maybeAppendRequestedModule(module))
+        return false;
+
+    return true;
+}
+
+bool
+ModuleBuilder::processExportFrom(frontend::ParseNode* pn)
+{
+    MOZ_ASSERT(pn->isArity(PN_BINARY));
+    MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
+
+    RootedAtom module(cx_, pn->pn_right->pn_atom);
+    if (!maybeAppendRequestedModule(module))
+        return false;
+
+    return true;
+}
+
+bool
+ModuleBuilder::maybeAppendRequestedModule(HandleAtom module)
+{
+    for (auto m : requestedModules_) {
+        if (m == module)
+            return true;
+    }
+    return requestedModules_.append(module);
+}
+
+static Value
+MakeElementValue(JSString *string)
+{
+    return StringValue(string);
+}
+
+template <typename T>
+ArrayObject* ModuleBuilder::createArray(const TraceableVector<T>& vector)
+{
+    uint32_t length = vector.length();
+    RootedArrayObject array(cx_, NewDenseFullyAllocatedArray(cx_, length));
+    if (!array)
+        return nullptr;
+
+    array->setDenseInitializedLength(length);
+    for (uint32_t i = 0; i < length; i++)
+        array->initDenseElement(i, MakeElementValue(vector[i]));
+    if (!JS_FreezeObject(cx_, array))
+        return nullptr;
+
+    return array;
+}
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -2,38 +2,78 @@
  * 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 "jsapi.h"
+
+#include "js/TraceableVector.h"
+
 #include "vm/NativeObject.h"
 
 namespace js {
 
+namespace frontend {
+class ParseNode;
+} /* namespace frontend */
+
 class ModuleObject : public NativeObject
 {
   public:
     enum
     {
         ScriptSlot = 0,
+        RequestedModulesSlot,
         SlotCount
     };
 
     static const Class class_;
 
+    static bool isInstance(HandleValue value);
+
     static ModuleObject* create(ExclusiveContext* cx);
     void init(HandleScript script);
+    void initImportExportData(HandleArrayObject requestedModules);
 
     JSScript* script() const;
+    ArrayObject& requestedModules() const;
 
   private:
     static void trace(JSTracer* trc, JSObject* obj);
 };
 
 typedef Rooted<ModuleObject*> RootedModuleObject;
 typedef Handle<ModuleObject*> HandleModuleObject;
 
+// Process a module's parse tree to collate the import and export data used when
+// creating a ModuleObject.
+class MOZ_STACK_CLASS ModuleBuilder
+{
+  public:
+    explicit ModuleBuilder(JSContext* cx);
+
+    bool buildAndInit(frontend::ParseNode* pn, HandleModuleObject module);
+
+  private:
+    using AtomVector = TraceableVector<JSAtom*>;
+    using RootedAtomVector = JS::Rooted<AtomVector>;
+
+    JSContext* cx_;
+    RootedAtomVector requestedModules_;
+
+    bool processImport(frontend::ParseNode* pn);
+    bool processExportFrom(frontend::ParseNode* pn);
+
+    bool maybeAppendRequestedModule(HandleAtom module);
+
+    template <typename T>
+    ArrayObject* createArray(const TraceableVector<T>& vector);
+};
+
+JSObject* InitModuleClass(JSContext* cx, HandleObject obj);
+
 } // namespace js
 
 #endif /* builtin_ModuleObject_h */
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -638,21 +638,25 @@ ModuleObject* BytecodeCompiler::compileM
     script->bindings = pn->pn_modulebox->bindings;
 
     if (!createEmitter(pn->pn_modulebox) ||
         !emitter->emitModuleScript(pn->pn_body))
     {
         return nullptr;
     }
 
-    if (!maybeCompleteCompressSource())
+    ModuleBuilder builder(cx->asJSContext());
+    if (!builder.buildAndInit(pn, module))
         return nullptr;
 
     parser->handler.freeTree(pn);
 
+    if (!maybeCompleteCompressSource())
+        return nullptr;
+
     MOZ_ASSERT_IF(cx->isJSContext(), !cx->asJSContext()->isExceptionPending());
     return module;
 }
 
 bool
 BytecodeCompiler::compileFunctionBody(MutableHandleFunction fun,
                                       Handle<PropertyNameVector> formals,
                                       GeneratorKind generatorKind)
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/requested-modules.js
@@ -0,0 +1,30 @@
+// Test requestedModules property
+
+function testRequestedModules(source, expected) {
+    var module = parseModule(source);
+    var actual = module.requestedModules;
+    assertEq(actual.length, expected.length);
+    for (var i = 0; i < actual.length; i++) {
+        assertEq(actual[i], expected[i]);
+    }
+}
+
+testRequestedModules("", []);
+
+testRequestedModules("import a from 'foo'",
+                     ['foo']);
+
+testRequestedModules("import a from 'foo'; import b from 'bar'",
+                     ['foo', 'bar']);
+
+testRequestedModules("import a from 'foo'; import b from 'bar'; import c from 'foo'",
+                     ['foo', 'bar']);
+
+testRequestedModules("export {} from 'foo'",
+                     ['foo']);
+
+testRequestedModules("export * from 'bar'",
+                     ['bar']);
+
+testRequestedModules("import a from 'foo'; export {} from 'bar'; export * from 'baz'",
+                     ['foo', 'bar', 'baz']);
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -108,12 +108,13 @@ IF_SAB(real,imaginary)(SharedInt32Array,
 IF_SAB(real,imaginary)(SharedUint32Array,       48,     InitViaClassSpec,       SHARED_TYPED_ARRAY_CLASP(Uint32)) \
 IF_SAB(real,imaginary)(SharedFloat32Array,      49,     InitViaClassSpec,       SHARED_TYPED_ARRAY_CLASP(Float32)) \
 IF_SAB(real,imaginary)(SharedFloat64Array,      50,     InitViaClassSpec,       SHARED_TYPED_ARRAY_CLASP(Float64)) \
 IF_SAB(real,imaginary)(SharedUint8ClampedArray, 51,     InitViaClassSpec,       SHARED_TYPED_ARRAY_CLASP(Uint8Clamped)) \
     real(TypedArray,            52,      InitViaClassSpec,      &js::TypedArrayObject::sharedTypedArrayPrototypeClass) \
 IF_SAB(real,imaginary)(Atomics,                 53,     InitAtomicsClass, OCLASP(Atomics)) \
     real(SavedFrame,            54,      InitViaClassSpec,      &js::SavedFrame::class_) \
     real(Reflect,               55,      InitReflect,           nullptr) \
+    real(Module,                56,      InitModuleClass,       OCLASP(Module)) \
 
 #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)
 
 #endif /* jsprototypes_h */
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -16,16 +16,17 @@
 #include "jsweakmap.h"
 
 #include "builtin/AtomicsObject.h"
 #include "builtin/Eval.h"
 #if EXPOSE_INTL_API
 # include "builtin/Intl.h"
 #endif
 #include "builtin/MapObject.h"
+#include "builtin/ModuleObject.h"
 #include "builtin/Object.h"
 #include "builtin/RegExp.h"
 #include "builtin/SIMD.h"
 #include "builtin/SymbolObject.h"
 #include "builtin/TypedObject.h"
 #include "builtin/WeakSetObject.h"
 #include "vm/HelperThreads.h"
 #include "vm/PIC.h"