Bug 930414 - Add module fields relating to exports r=shu
authorJon Coppeard <jcoppeard@mozilla.com>
Mon, 24 Aug 2015 15:58:36 +0100
changeset 259031 178d594d4934fa20df30f2883460ede1d15eb3ec
parent 259030 7861ef6582a9a013436d2c0f1c70150222775754
child 259032 9ac1f5052b91cdc341570cb2f8c03efc561faa54
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 fields relating to exports r=shu
js/src/builtin/ModuleObject.cpp
js/src/builtin/ModuleObject.h
js/src/gc/Marking.cpp
js/src/jit-test/tests/modules/export-entries.js
js/src/jsprototypes.h
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -8,16 +8,17 @@
 
 #include "gc/Tracer.h"
 
 #include "jsobjinlines.h"
 
 using namespace js;
 
 typedef JS::Rooted<ImportEntryObject*> RootedImportEntry;
+typedef JS::Rooted<ExportEntryObject*> RootedExportEntry;
 
 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;
 }
@@ -45,16 +46,26 @@ ModuleValueGetter(JSContext* cx, unsigne
 #define DEFINE_ATOM_ACCESSOR_METHOD(cls, name)                                \
     JSAtom*                                                                   \
     cls::name()                                                               \
     {                                                                         \
         Value value = cls##_##name##Value(this);                              \
         return &value.toString()->asAtom();                                   \
     }
 
+#define DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(cls, name)                        \
+    JSAtom*                                                                   \
+    cls::name()                                                               \
+    {                                                                         \
+        Value value = cls##_##name##Value(this);                              \
+        if (value.isNull())                                                   \
+            return nullptr;                                                   \
+        return &value.toString()->asAtom();                                   \
+    }
+
 ///////////////////////////////////////////////////////////////////////////
 // ImportEntryObject
 
 /* static */ const Class
 ImportEntryObject::class_ = {
     "ImportEntry",
     JSCLASS_HAS_RESERVED_SLOTS(ImportEntryObject::SlotCount) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_ImportEntry) |
@@ -115,16 +126,96 @@ ImportEntryObject::create(JSContext* cx,
         return nullptr;
     self->initReservedSlot(ModuleRequestSlot, StringValue(moduleRequest));
     self->initReservedSlot(ImportNameSlot, StringValue(importName));
     self->initReservedSlot(LocalNameSlot, StringValue(localName));
     return self;
 }
 
 ///////////////////////////////////////////////////////////////////////////
+// ExportEntryObject
+
+/* static */ const Class
+ExportEntryObject::class_ = {
+    "ExportEntry",
+    JSCLASS_HAS_RESERVED_SLOTS(ExportEntryObject::SlotCount) |
+    JSCLASS_HAS_CACHED_PROTO(JSProto_ExportEntry) |
+    JSCLASS_IS_ANONYMOUS |
+    JSCLASS_IMPLEMENTS_BARRIERS
+};
+
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, exportName, ExportNameSlot)
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, moduleRequest, ModuleRequestSlot)
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, importName, ImportNameSlot)
+DEFINE_GETTER_FUNCTIONS(ExportEntryObject, localName, LocalNameSlot)
+
+DEFINE_ATOM_ACCESSOR_METHOD(ExportEntryObject, exportName)
+DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, moduleRequest)
+DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, importName)
+DEFINE_ATOM_OR_NULL_ACCESSOR_METHOD(ExportEntryObject, localName)
+
+/* static */ bool
+ExportEntryObject::isInstance(HandleValue value)
+{
+    return value.isObject() && value.toObject().is<ExportEntryObject>();
+}
+
+/* static */ JSObject*
+ExportEntryObject::initClass(JSContext* cx, HandleObject obj)
+{
+    static const JSPropertySpec protoAccessors[] = {
+        JS_PSG("exportName", ExportEntryObject_exportNameGetter, 0),
+        JS_PSG("moduleRequest", ExportEntryObject_moduleRequestGetter, 0),
+        JS_PSG("importName", ExportEntryObject_importNameGetter, 0),
+        JS_PSG("localName", ExportEntryObject_localNameGetter, 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_ExportEntry, ObjectValue(*proto));
+    return proto;
+}
+
+JSObject*
+js::InitExportEntryClass(JSContext* cx, HandleObject obj)
+{
+    return ExportEntryObject::initClass(cx, obj);
+}
+
+static Value
+StringOrNullValue(JSString* maybeString)
+{
+    return maybeString ? StringValue(maybeString) : NullValue();
+}
+
+/* static */ ExportEntryObject*
+ExportEntryObject::create(JSContext* cx,
+                          HandleAtom maybeExportName,
+                          HandleAtom maybeModuleRequest,
+                          HandleAtom maybeImportName,
+                          HandleAtom maybeLocalName)
+{
+    RootedExportEntry self(cx, NewBuiltinClassInstance<ExportEntryObject>(cx));
+    if (!self)
+        return nullptr;
+    self->initReservedSlot(ExportNameSlot, StringOrNullValue(maybeExportName));
+    self->initReservedSlot(ModuleRequestSlot, StringOrNullValue(maybeModuleRequest));
+    self->initReservedSlot(ImportNameSlot, StringOrNullValue(maybeImportName));
+    self->initReservedSlot(LocalNameSlot, StringOrNullValue(maybeLocalName));
+    return self;
+}
+
+///////////////////////////////////////////////////////////////////////////
 // ModuleObject
 
 /* static */ const Class
 ModuleObject::class_ = {
     "Module",
     JSCLASS_HAS_RESERVED_SLOTS(ModuleObject::SlotCount) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_Module) |
     JSCLASS_IS_ANONYMOUS |
@@ -148,16 +239,19 @@ ModuleObject::class_ = {
     ArrayObject&                                                              \
     cls::name() const                                                         \
     {                                                                         \
         return getFixedSlot(cls::slot).toObject().as<ArrayObject>();          \
     }
 
 DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, requestedModules, RequestedModulesSlot)
 DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, importEntries, ImportEntriesSlot)
+DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, localExportEntries, LocalExportEntriesSlot)
+DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, indirectExportEntries, IndirectExportEntriesSlot)
+DEFINE_ARRAY_SLOT_ACCESSOR(ModuleObject, starExportEntries, StarExportEntriesSlot)
 
 /* static */ bool
 ModuleObject::isInstance(HandleValue value)
 {
     return value.isObject() && value.toObject().is<ModuleObject>();
 }
 
 /* static */ ModuleObject*
@@ -169,20 +263,26 @@ ModuleObject::create(ExclusiveContext* c
 void
 ModuleObject::init(HandleScript script)
 {
     initReservedSlot(ScriptSlot, PrivateValue(script));
 }
 
 void
 ModuleObject::initImportExportData(HandleArrayObject requestedModules,
-                                   HandleArrayObject importEntries)
+                                   HandleArrayObject importEntries,
+                                   HandleArrayObject localExportEntries,
+                                   HandleArrayObject indirectExportEntries,
+                                   HandleArrayObject starExportEntries)
 {
     initReservedSlot(RequestedModulesSlot, ObjectValue(*requestedModules));
     initReservedSlot(ImportEntriesSlot, ObjectValue(*importEntries));
+    initReservedSlot(LocalExportEntriesSlot, ObjectValue(*localExportEntries));
+    initReservedSlot(IndirectExportEntriesSlot, ObjectValue(*indirectExportEntries));
+    initReservedSlot(StarExportEntriesSlot, ObjectValue(*starExportEntries));
 }
 
 JSScript*
 ModuleObject::script() const
 {
     return static_cast<JSScript*>(getReservedSlot(ScriptSlot).toPrivate());
 }
 
@@ -192,23 +292,29 @@ ModuleObject::trace(JSTracer* trc, JSObj
     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)
 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("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
     };
 
     Rooted<GlobalObject*> global(cx, &obj->as<GlobalObject>());
 
     RootedObject proto(cx, global->createBlankPrototype<PlainObject>(cx));
     if (!proto)
         return nullptr;
@@ -226,17 +332,21 @@ js::InitModuleClass(JSContext* cx, Handl
 
 ///////////////////////////////////////////////////////////////////////////
 // ModuleBuilder
 
 ModuleBuilder::ModuleBuilder(JSContext* cx)
   : cx_(cx),
     requestedModules_(cx, AtomVector(cx)),
     importedBoundNames_(cx, AtomVector(cx)),
-    importEntries_(cx, ImportEntryVector(cx))
+    importEntries_(cx, ImportEntryVector(cx)),
+    exportEntries_(cx, ExportEntryVector(cx)),
+    localExportEntries_(cx, ExportEntryVector(cx)),
+    indirectExportEntries_(cx, ExportEntryVector(cx)),
+    starExportEntries_(cx, ExportEntryVector(cx))
 {}
 
 bool
 ModuleBuilder::buildAndInit(frontend::ParseNode* moduleNode, HandleModuleObject module)
 {
     MOZ_ASSERT(moduleNode->isKind(PNK_MODULE));
 
     ParseNode* stmtsNode = moduleNode->pn_expr;
@@ -246,41 +356,91 @@ ModuleBuilder::buildAndInit(frontend::Pa
     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:
+          case PNK_EXPORT_DEFAULT:
+            if (!processExport(pn))
+                return false;
             break;
 
           case PNK_EXPORT_FROM:
             if (!processExportFrom(pn))
                 return false;
             break;
 
-          case PNK_EXPORT_DEFAULT:
-            break;
-
           default:
             break;
         }
     }
 
+    for (const auto& e : exportEntries_) {
+        RootedExportEntry exp(cx_, e);
+        if (!exp->moduleRequest()) {
+            RootedImportEntry importEntry(cx_, importEntryFor(exp->localName()));
+            if (!importEntry) {
+                if (!localExportEntries_.append(exp))
+                    return false;
+            } else {
+                if (importEntry->importName() == cx_->names().star) {
+                    if (!localExportEntries_.append(exp))
+                        return false;
+                } else {
+                    RootedAtom exportName(cx_, exp->exportName());
+                    RootedAtom moduleRequest(cx_, importEntry->moduleRequest());
+                    RootedAtom importName(cx_, importEntry->importName());
+                    RootedExportEntry exportEntry(cx_);
+                    exportEntry = ExportEntryObject::create(cx_,
+                                                            exportName,
+                                                            moduleRequest,
+                                                            importName,
+                                                            nullptr);
+                    if (!exportEntry || !indirectExportEntries_.append(exportEntry))
+                        return false;
+                }
+            }
+        } else if (exp->importName() == cx_->names().star) {
+            if (!starExportEntries_.append(exp))
+                return false;
+        } else {
+            if (!indirectExportEntries_.append(exp))
+                return false;
+        }
+    }
+
     RootedArrayObject requestedModules(cx_, createArray<JSAtom*>(requestedModules_));
     if (!requestedModules)
         return false;
 
     RootedArrayObject importEntries(cx_, createArray<ImportEntryObject*>(importEntries_));
     if (!importEntries)
         return false;
 
+    RootedArrayObject localExportEntries(cx_, createArray<ExportEntryObject*>(localExportEntries_));
+    if (!localExportEntries)
+        return false;
+
+    RootedArrayObject indirectExportEntries(cx_);
+    indirectExportEntries = createArray<ExportEntryObject*>(indirectExportEntries_);
+    if (!indirectExportEntries)
+        return false;
+
+    RootedArrayObject starExportEntries(cx_, createArray<ExportEntryObject*>(starExportEntries_));
+    if (!starExportEntries)
+        return false;
+
     module->initImportExportData(requestedModules,
-                                 importEntries);
+                                 importEntries,
+                                 localExportEntries,
+                                 indirectExportEntries,
+                                 starExportEntries);
 
     return true;
 }
 
 bool
 ModuleBuilder::processImport(frontend::ParseNode* pn)
 {
     MOZ_ASSERT(pn->isArity(PN_BINARY));
@@ -299,41 +459,145 @@ ModuleBuilder::processImport(frontend::P
         RootedAtom importName(cx_, spec->pn_left->pn_atom);
         RootedAtom localName(cx_, spec->pn_right->pn_atom);
 
         if (!importedBoundNames_.append(localName))
             return false;
 
         RootedImportEntry importEntry(cx_);
         importEntry = ImportEntryObject::create(cx_, module, importName, localName);
-        if (!importEntry)
+        if (!importEntry || !importEntries_.append(importEntry))
             return false;
-
-        if (!importEntries_.append(importEntry)) {
-            ReportOutOfMemory(cx_);
-            return false;
-        }
     }
 
     return true;
 }
 
 bool
+ModuleBuilder::processExport(frontend::ParseNode* pn)
+{
+    MOZ_ASSERT(pn->isArity(PN_UNARY));
+
+    ParseNode* kid = pn->pn_kid;
+    bool isDefault = pn->getKind() == PNK_EXPORT_DEFAULT;
+
+    switch (kid->getKind()) {
+      case PNK_EXPORT_SPEC_LIST:
+        MOZ_ASSERT(!isDefault);
+        for (ParseNode* spec = kid->pn_head; spec; spec = spec->pn_next) {
+            MOZ_ASSERT(spec->isKind(PNK_EXPORT_SPEC));
+            RootedAtom localName(cx_, spec->pn_left->pn_atom);
+            RootedAtom exportName(cx_, spec->pn_right->pn_atom);
+            if (!appendLocalExportEntry(exportName, localName))
+                return false;
+        }
+        break;
+
+      case PNK_FUNCTION: {
+          RootedFunction func(cx_, kid->pn_funbox->function());
+          RootedAtom localName(cx_, func->atom());
+          RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
+          if (!appendLocalExportEntry(exportName, localName))
+              return false;
+          break;
+      }
+
+      case PNK_CLASS: {
+          const ClassNode& cls = kid->as<ClassNode>();
+          MOZ_ASSERT(cls.names());
+          RootedAtom localName(cx_, cls.names()->innerBinding()->pn_atom);
+          RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
+          if (!appendLocalExportEntry(exportName, localName))
+              return false;
+          break;
+      }
+
+      case PNK_VAR:
+      case PNK_CONST:
+      case PNK_GLOBALCONST:
+      case PNK_LET: {
+          MOZ_ASSERT(kid->isArity(PN_LIST));
+          for (ParseNode* var = kid->pn_head; var; var = var->pn_next) {
+              if (var->isKind(PNK_ASSIGN))
+                  var = var->pn_left;
+              MOZ_ASSERT(var->isKind(PNK_NAME));
+              RootedAtom localName(cx_, var->pn_atom);
+              RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
+              if (!appendLocalExportEntry(exportName, localName))
+                  return false;
+          }
+          break;
+      }
+
+      default:
+        MOZ_ASSERT(isDefault);
+        RootedAtom localName(cx_, cx_->names().starDefaultStar);
+        RootedAtom exportName(cx_, cx_->names().default_);
+        if (!appendLocalExportEntry(exportName, localName))
+            return false;
+        break;
+    }
+    return true;
+}
+
+bool
 ModuleBuilder::processExportFrom(frontend::ParseNode* pn)
 {
     MOZ_ASSERT(pn->isArity(PN_BINARY));
+    MOZ_ASSERT(pn->pn_left->isKind(PNK_EXPORT_SPEC_LIST));
     MOZ_ASSERT(pn->pn_right->isKind(PNK_STRING));
 
     RootedAtom module(cx_, pn->pn_right->pn_atom);
     if (!maybeAppendRequestedModule(module))
         return false;
 
+    for (ParseNode* spec = pn->pn_left->pn_head; spec; spec = spec->pn_next) {
+        if (spec->isKind(PNK_EXPORT_SPEC)) {
+            RootedAtom bindingName(cx_, spec->pn_left->pn_atom);
+            RootedAtom exportName(cx_, spec->pn_right->pn_atom);
+            if (!appendIndirectExportEntry(exportName, module, bindingName))
+                return false;
+        } else {
+            MOZ_ASSERT(spec->isKind(PNK_EXPORT_BATCH_SPEC));
+            RootedAtom importName(cx_, cx_->names().star);
+            if (!appendIndirectExportEntry(nullptr, module, importName))
+                return false;
+        }
+    }
+
     return true;
 }
 
+ImportEntryObject*
+ModuleBuilder::importEntryFor(JSAtom* localName)
+{
+    for (auto import : importEntries_) {
+        if (import->localName() == localName)
+            return import;
+    }
+    return nullptr;
+}
+
+bool
+ModuleBuilder::appendLocalExportEntry(HandleAtom exportName, HandleAtom localName)
+{
+    Rooted<ExportEntryObject*> exportEntry(cx_);
+    exportEntry = ExportEntryObject::create(cx_, exportName, nullptr, nullptr, localName);
+    return exportEntry && exportEntries_.append(exportEntry);
+}
+
+bool
+ModuleBuilder::appendIndirectExportEntry(HandleAtom exportName, HandleAtom moduleRequest,
+                                         HandleAtom importName)
+{
+    Rooted<ExportEntryObject*> exportEntry(cx_);
+    exportEntry = ExportEntryObject::create(cx_, exportName, moduleRequest, importName, nullptr);
+    return exportEntry && exportEntries_.append(exportEntry);
+}
+
 bool
 ModuleBuilder::maybeAppendRequestedModule(HandleAtom module)
 {
     for (auto m : requestedModules_) {
         if (m == module)
             return true;
     }
     return requestedModules_.append(module);
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -37,39 +37,74 @@ class ImportEntryObject : public NativeO
                                      HandleAtom moduleRequest,
                                      HandleAtom importName,
                                      HandleAtom localName);
     JSAtom* moduleRequest();
     JSAtom* importName();
     JSAtom* localName();
 };
 
+class ExportEntryObject : public NativeObject
+{
+  public:
+    enum
+    {
+        ExportNameSlot = 0,
+        ModuleRequestSlot,
+        ImportNameSlot,
+        LocalNameSlot,
+        SlotCount
+    };
+
+    static const Class class_;
+    static JSObject* initClass(JSContext* cx, HandleObject obj);
+    static bool isInstance(HandleValue value);
+    static ExportEntryObject* create(JSContext* cx,
+                                     HandleAtom maybeExportName,
+                                     HandleAtom maybeModuleRequest,
+                                     HandleAtom maybeImportName,
+                                     HandleAtom maybeLocalName);
+    JSAtom* exportName();
+    JSAtom* moduleRequest();
+    JSAtom* importName();
+    JSAtom* localName();
+};
+
 class ModuleObject : public NativeObject
 {
   public:
     enum
     {
         ScriptSlot = 0,
         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 initImportExportData(HandleArrayObject requestedModules,
-                              HandleArrayObject importEntries);
+                              HandleArrayObject importEntries,
+                              HandleArrayObject localExportEntries,
+                              HandleArrayObject indiretExportEntries,
+                              HandleArrayObject starExportEntries);
 
     JSScript* script() const;
     ArrayObject& requestedModules() const;
     ArrayObject& importEntries() const;
+    ArrayObject& localExportEntries() const;
+    ArrayObject& indirectExportEntries() const;
+    ArrayObject& starExportEntries() const;
 
   private:
     static void trace(JSTracer* trc, JSObject* obj);
 };
 
 typedef Rooted<ModuleObject*> RootedModuleObject;
 typedef Handle<ModuleObject*> HandleModuleObject;
 
@@ -82,29 +117,43 @@ class MOZ_STACK_CLASS ModuleBuilder
 
     bool buildAndInit(frontend::ParseNode* pn, HandleModuleObject module);
 
   private:
     using AtomVector = TraceableVector<JSAtom*>;
     using RootedAtomVector = JS::Rooted<AtomVector>;
     using ImportEntryVector = TraceableVector<ImportEntryObject*>;
     using RootedImportEntryVector = JS::Rooted<ImportEntryVector>;
+    using ExportEntryVector = TraceableVector<ExportEntryObject*> ;
+    using RootedExportEntryVector = JS::Rooted<ExportEntryVector> ;
 
     JSContext* cx_;
     RootedAtomVector requestedModules_;
     RootedAtomVector importedBoundNames_;
     RootedImportEntryVector importEntries_;
+    RootedExportEntryVector exportEntries_;
+    RootedExportEntryVector localExportEntries_;
+    RootedExportEntryVector indirectExportEntries_;
+    RootedExportEntryVector starExportEntries_;
 
     bool processImport(frontend::ParseNode* pn);
+    bool processExport(frontend::ParseNode* pn);
     bool processExportFrom(frontend::ParseNode* pn);
 
+    ImportEntryObject* importEntryFor(JSAtom* localName);
+
+    bool appendLocalExportEntry(HandleAtom exportName, HandleAtom localName);
+    bool appendIndirectExportEntry(HandleAtom exportName, HandleAtom moduleRequest,
+                                   HandleAtom importName);
+
     bool maybeAppendRequestedModule(HandleAtom module);
 
     template <typename T>
     ArrayObject* createArray(const TraceableVector<T>& vector);
 };
 
 JSObject* InitModuleClass(JSContext* cx, HandleObject obj);
 JSObject* InitImportEntryClass(JSContext* cx, HandleObject obj);
+JSObject* InitExportEntryClass(JSContext* cx, HandleObject obj);
 
 } // namespace js
 
 #endif /* builtin_ModuleObject_h */
--- a/js/src/gc/Marking.cpp
+++ b/js/src/gc/Marking.cpp
@@ -365,17 +365,18 @@ AssertRootMarkingPhase(JSTracer* trc)
     D(ModuleObject*)      \
     D(NestedScopeObject*) \
     D(PlainObject*) \
     D(SavedFrame*) \
     D(ScopeObject*) \
     D(ScriptSourceObject*) \
     D(SharedArrayBufferObject*) \
     D(SharedTypedArrayObject*) \
-    D(ImportEntryObject*)      \
+    D(ImportEntryObject*) \
+    D(ExportEntryObject*) \
     D(JSScript*) \
     D(LazyScript*) \
     D(Shape*) \
     D(JSAtom*) \
     D(JSString*) \
     D(JSFlatString*) \
     D(JSLinearString*) \
     D(PropertyName*) \
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/modules/export-entries.js
@@ -0,0 +1,121 @@
+// Test localExportEntries property
+
+function testArrayContents(actual, expected) {
+    assertEq(actual.length, expected.length);
+    for (var i = 0; i < actual.length; i++) {
+        for (var property in expected[i]) {
+            assertEq(actual[i][property], expected[i][property]);
+        }
+    }
+}
+
+function testLocalExportEntries(source, expected) {
+    var module = parseModule(source);
+    testArrayContents(module.localExportEntries, expected);
+}
+
+testLocalExportEntries(
+    'export var v;',
+    [{exportName: 'v', moduleRequest: null, importName: null, localName: 'v'}]);
+
+testLocalExportEntries(
+    'export var v = 0;',
+    [{exportName: 'v', moduleRequest: null, importName: null, localName: 'v'}]);
+
+testLocalExportEntries(
+    'export let x = 1;',
+    [{exportName: 'x', moduleRequest: null, importName: null, localName: 'x'}]);
+
+testLocalExportEntries(
+    'export const x = 1;',
+    [{exportName: 'x', moduleRequest: null, importName: null, localName: 'x'}]);
+
+testLocalExportEntries(
+    'export class foo { constructor() {} };',
+    [{exportName: 'foo', moduleRequest: null, importName: null, localName: 'foo'}]);
+
+testLocalExportEntries(
+    'export default function f() {};',
+    [{exportName: 'default', moduleRequest: null, importName: null, localName: 'f'}]);
+
+testLocalExportEntries(
+    'export default function() {};',
+    [{exportName: 'default', moduleRequest: null, importName: null, localName: '*default*'}]);
+
+testLocalExportEntries(
+    'export default 42;',
+    [{exportName: 'default', moduleRequest: null, importName: null, localName: '*default*'}]);
+
+testLocalExportEntries(
+    'let x = 1; export {x};',
+    [{exportName: 'x', moduleRequest: null, importName: null, localName: 'x'}]);
+
+testLocalExportEntries(
+    'let v = 1; export {v as x};',
+    [{exportName: 'x', moduleRequest: null, importName: null, localName: 'v'}]);
+
+testLocalExportEntries(
+    'export {x} from "mod";',
+    []);
+
+testLocalExportEntries(
+    'export {v as x} from "mod";',
+    []);
+
+testLocalExportEntries(
+    'export * from "mod";',
+    []);
+
+// Test indirectExportEntries property
+
+function testIndirectExportEntries(source, expected) {
+    var module = parseModule(source);
+    testArrayContents(module.indirectExportEntries, expected);
+}
+
+testIndirectExportEntries(
+    'export default function f() {};',
+    []);
+
+testIndirectExportEntries(
+    'let x = 1; export {x};',
+    []);
+
+testIndirectExportEntries(
+    'export {x} from "mod";',
+    [{exportName: 'x', moduleRequest: 'mod', importName: 'x', localName: null}]);
+
+testIndirectExportEntries(
+    'export {v as x} from "mod";',
+    [{exportName: 'x', moduleRequest: 'mod', importName: 'v', localName: null}]);
+
+testIndirectExportEntries(
+    'export * from "mod";',
+    []);
+
+testIndirectExportEntries(
+    'import {v as x} from "mod"; export {x as y};',
+    [{exportName: 'y', moduleRequest: 'mod', importName: 'v', localName: null}]);
+
+// Test starExportEntries property
+
+function testStarExportEntries(source, expected) {
+    var module = parseModule(source);
+    testArrayContents(module.starExportEntries, expected);
+}
+
+testStarExportEntries(
+    'export default function f() {};',
+    []);
+
+testStarExportEntries(
+    'let x = 1; export {x};',
+    []);
+
+testStarExportEntries(
+    'export {x} from "mod";',
+    []);
+
+testStarExportEntries(
+    'export * from "mod";',
+    [{exportName: null, moduleRequest: 'mod', importName: '*', localName: null}]);
--- a/js/src/jsprototypes.h
+++ b/js/src/jsprototypes.h
@@ -110,12 +110,13 @@ IF_SAB(real,imaginary)(SharedFloat32Arra
 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)) \
     real(ImportEntry,           57,      InitImportEntryClass,  OCLASP(ImportEntry)) \
+    real(ExportEntry,           58,      InitExportEntryClass,  OCLASP(ExportEntry)) \
 
 #define JS_FOR_EACH_PROTOTYPE(macro) JS_FOR_PROTOTYPES(macro,macro)
 
 #endif /* jsprototypes_h */