Bug 1317319: Factor out Export section decoding; r=luke
authorBenjamin Bouvier <benj@benj.me>
Tue, 15 Nov 2016 20:33:34 +0100
changeset 323051 5d3cab095f63084a4b86082bf2bbe55ce7283749
parent 323050 5b9544a7f1d8598f37cbe9f8b7302b84f775c82d
child 323052 bcaa68d34a94c6b7b2614e2e0eaaeb8cfa04d4a4
push id30967
push userphilringnalda@gmail.com
push dateFri, 18 Nov 2016 03:21:38 +0000
treeherdermozilla-central@8e476f8bd52d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersluke
bugs1317319
milestone53.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 1317319: Factor out Export section decoding; r=luke MozReview-Commit-ID: 34Fh7lPklN1
js/src/wasm/AsmJS.cpp
js/src/wasm/WasmBinaryFormat.cpp
js/src/wasm/WasmBinaryFormat.h
js/src/wasm/WasmBinaryToAST.cpp
js/src/wasm/WasmCompile.cpp
js/src/wasm/WasmGenerator.cpp
js/src/wasm/WasmGenerator.h
js/src/wasm/WasmModule.cpp
js/src/wasm/WasmModule.h
js/src/wasm/WasmTypes.cpp
js/src/wasm/WasmTypes.h
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -1653,16 +1653,17 @@ class MOZ_STACK_CLASS ModuleValidator
     SigMap                sigMap_;
     ImportMap             importMap_;
     ArrayViewVector       arrayViews_;
     bool                  atomicsPresent_;
     bool                  simdPresent_;
 
     // State used to build the AsmJSModule in finish():
     ModuleGenerator       mg_;
+    ExportVector          exports_;
     MutableAsmJSMetadata  asmJSMetadata_;
 
     // Error reporting:
     UniqueChars           errorString_;
     uint32_t              errorOffset_;
     bool                  errorOverRecursed_;
 
     // Helpers:
@@ -2135,18 +2136,18 @@ class MOZ_STACK_CLASS ModuleValidator
         if (maybeField)
             fieldChars = StringToNewUTF8CharsZ(cx_, *maybeField);
         else
             fieldChars = DuplicateString("");
         if (!fieldChars)
             return false;
 
         // Declare which function is exported which gives us an index into the
-        // module FuncExportVector.
-        if (!mg_.addFuncExport(Move(fieldChars), func.index()))
+        // module ExportVector.
+        if (!exports_.emplaceBack(Move(fieldChars), func.index(), DefinitionKind::Function))
             return false;
 
         // The exported function might have already been exported in which case
         // the index will refer into the range of AsmJSExports.
         return asmJSMetadata_->asmJSExports.emplaceBack(func.index(),
                                                         func.srcBegin() - asmJSMetadata_->srcStart,
                                                         func.srcEnd() - asmJSMetadata_->srcStart);
     }
@@ -2351,16 +2352,19 @@ class MOZ_STACK_CLASS ModuleValidator
     }
     bool finishFunctionBodies() {
         return mg_.finishFuncDefs();
     }
     SharedModule finish() {
         if (!arrayViews_.empty())
             mg_.initMemoryUsage(atomicsPresent_ ? MemoryUsage::Shared : MemoryUsage::Unshared);
 
+        if (!mg_.setExports(Move(exports_)))
+            return nullptr;
+
         asmJSMetadata_->usesSimd = simdPresent_;
 
         MOZ_ASSERT(asmJSMetadata_->asmJSFuncNames.empty());
         for (const Func* func : functions_) {
             CacheableChars funcName = StringToNewUTF8CharsZ(cx_, *func->name());
             if (!funcName || !asmJSMetadata_->asmJSFuncNames.emplaceBack(Move(funcName)))
                 return nullptr;
         }
--- a/js/src/wasm/WasmBinaryFormat.cpp
+++ b/js/src/wasm/WasmBinaryFormat.cpp
@@ -135,18 +135,18 @@ wasm::DecodeTypeSection(Decoder& d, SigW
     }
 
     if (!d.finishSection(sectionStart, sectionSize, "type"))
         return false;
 
     return true;
 }
 
-UniqueChars
-wasm::DecodeName(Decoder& d)
+static UniqueChars
+DecodeName(Decoder& d)
 {
     uint32_t numBytes;
     if (!d.readVarU32(&numBytes))
         return nullptr;
 
     const uint8_t* bytes;
     if (!d.readBytes(numBytes, &bytes))
         return nullptr;
@@ -221,18 +221,18 @@ DecodeTableLimits(Decoder& d, TableDescV
         return d.fail("too many table elements");
 
     if (tables->length())
         return d.fail("already have default table");
 
     return tables->emplaceBack(TableKind::AnyFunction, limits);
 }
 
-bool
-wasm::GlobalIsJSCompatible(Decoder& d, ValType type, bool isMutable)
+static bool
+GlobalIsJSCompatible(Decoder& d, ValType type, bool isMutable)
 {
     switch (type) {
       case ValType::I32:
       case ValType::F32:
       case ValType::F64:
         break;
       case ValType::I64:
         if (!jit::JitOptions.wasmTestMode)
@@ -499,16 +499,135 @@ wasm::DecodeGlobalSection(Decoder& d, Gl
     }
 
     if (!d.finishSection(sectionStart, sectionSize, "global"))
         return false;
 
     return true;
 }
 
+typedef HashSet<const char*, CStringHasher, SystemAllocPolicy> CStringSet;
+
+static UniqueChars
+DecodeExportName(Decoder& d, CStringSet* dupSet)
+{
+    UniqueChars exportName = DecodeName(d);
+    if (!exportName) {
+        d.fail("expected valid export name");
+        return nullptr;
+    }
+
+    CStringSet::AddPtr p = dupSet->lookupForAdd(exportName.get());
+    if (p) {
+        d.fail("duplicate export");
+        return nullptr;
+    }
+
+    if (!dupSet->add(p, exportName.get()))
+        return nullptr;
+
+    return Move(exportName);
+}
+
+static bool
+DecodeExport(Decoder& d, size_t numFuncs, size_t numTables, bool usesMemory,
+             const GlobalDescVector& globals, ExportVector* exports, CStringSet* dupSet)
+{
+    UniqueChars fieldName = DecodeExportName(d, dupSet);
+    if (!fieldName)
+        return false;
+
+    uint32_t exportKind;
+    if (!d.readVarU32(&exportKind))
+        return d.fail("failed to read export kind");
+
+    switch (DefinitionKind(exportKind)) {
+      case DefinitionKind::Function: {
+        uint32_t funcIndex;
+        if (!d.readVarU32(&funcIndex))
+            return d.fail("expected function index");
+
+        if (funcIndex >= numFuncs)
+            return d.fail("exported function index out of bounds");
+
+        return exports->emplaceBack(Move(fieldName), funcIndex, DefinitionKind::Function);
+      }
+      case DefinitionKind::Table: {
+        uint32_t tableIndex;
+        if (!d.readVarU32(&tableIndex))
+            return d.fail("expected table index");
+
+        if (tableIndex >= numTables)
+            return d.fail("exported table index out of bounds");
+
+        return exports->emplaceBack(Move(fieldName), DefinitionKind::Table);
+      }
+      case DefinitionKind::Memory: {
+        uint32_t memoryIndex;
+        if (!d.readVarU32(&memoryIndex))
+            return d.fail("expected memory index");
+
+        if (memoryIndex > 0 || !usesMemory)
+            return d.fail("exported memory index out of bounds");
+
+        return exports->emplaceBack(Move(fieldName), DefinitionKind::Memory);
+      }
+      case DefinitionKind::Global: {
+        uint32_t globalIndex;
+        if (!d.readVarU32(&globalIndex))
+            return d.fail("expected global index");
+
+        if (globalIndex >= globals.length())
+            return d.fail("exported global index out of bounds");
+
+        const GlobalDesc& global = globals[globalIndex];
+        if (!GlobalIsJSCompatible(d, global.type(), global.isMutable()))
+            return false;
+
+        return exports->emplaceBack(Move(fieldName), globalIndex, DefinitionKind::Global);
+      }
+      default:
+        return d.fail("unexpected export kind");
+    }
+
+    MOZ_CRASH("unreachable");
+}
+
+bool
+wasm::DecodeExportSection(Decoder& d, size_t numFuncs, size_t numTables, bool usesMemory,
+                          const GlobalDescVector& globals, ExportVector* exports)
+{
+    uint32_t sectionStart, sectionSize;
+    if (!d.startSection(SectionId::Export, &sectionStart, &sectionSize, "export"))
+        return false;
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    CStringSet dupSet;
+    if (!dupSet.init())
+        return false;
+
+    uint32_t numExports;
+    if (!d.readVarU32(&numExports))
+        return d.fail("failed to read number of exports");
+
+    if (numExports > MaxExports)
+        return d.fail("too many exports");
+
+    for (uint32_t i = 0; i < numExports; i++) {
+        if (!DecodeExport(d, numFuncs, numTables, usesMemory, globals, exports, &dupSet))
+            return false;
+    }
+
+    if (!d.finishSection(sectionStart, sectionSize, "export"))
+        return false;
+
+    return true;
+}
+
 bool
 wasm::EncodeLocalEntries(Encoder& e, const ValTypeVector& locals)
 {
     uint32_t numLocalEntries = 0;
     ValType prev = ValType(TypeCode::Limit);
     for (ValType t : locals) {
         if (t != prev) {
             numLocalEntries++;
--- a/js/src/wasm/WasmBinaryFormat.h
+++ b/js/src/wasm/WasmBinaryFormat.h
@@ -619,22 +619,16 @@ class Decoder
 };
 
 // Reusable macro encoding/decoding functions reused by both the two
 // encoders (AsmJS/WasmTextToBinary) and all the decoders
 // (WasmCompile/WasmIonCompile/WasmBaselineCompile/WasmBinaryToText).
 
 // Misc helpers.
 
-UniqueChars
-DecodeName(Decoder& d);
-
-MOZ_MUST_USE bool
-GlobalIsJSCompatible(Decoder& d, ValType type, bool isMutable);
-
 MOZ_MUST_USE bool
 EncodeLocalEntries(Encoder& d, const ValTypeVector& locals);
 
 MOZ_MUST_USE bool
 DecodeLocalEntries(Decoder& d, ModuleKind kind, ValTypeVector* locals);
 
 MOZ_MUST_USE bool
 DecodeInitializerExpression(Decoder& d, const GlobalDescVector& globals, ValType expected,
@@ -662,16 +656,20 @@ DecodeTableSection(Decoder& d, TableDesc
 
 MOZ_MUST_USE bool
 DecodeMemorySection(Decoder& d, bool hasMemory, Limits* memory, bool* present);
 
 MOZ_MUST_USE bool
 DecodeGlobalSection(Decoder& d, GlobalDescVector* globals);
 
 MOZ_MUST_USE bool
+DecodeExportSection(Decoder& d, size_t numFuncs, size_t numTables, bool usesMemory,
+                    const GlobalDescVector& globals, ExportVector* exports);
+
+MOZ_MUST_USE bool
 DecodeUnknownSections(Decoder& d);
 
 MOZ_MUST_USE bool
 DecodeDataSection(Decoder& d, bool usesMemory, uint32_t minMemoryByteLength,
                   const GlobalDescVector& globals, DataSegmentVector* segments);
 
 } // namespace wasm
 } // namespace js
--- a/js/src/wasm/WasmBinaryToAST.cpp
+++ b/js/src/wasm/WasmBinaryToAST.cpp
@@ -1434,28 +1434,29 @@ AstDecodeTypeSection(AstDecodeContext& c
         AstSig* astSig = new(c.lifo) AstSig(sigName, Move(sigNoName));
         if (!astSig || !c.module().append(astSig))
             return false;
     }
 
     return true;
 }
 
-static AstName
-ToAstName(AstDecodeContext& c, const UniqueChars& name)
+static bool
+ToAstName(AstDecodeContext& c, const char* name, AstName* out)
 {
-    size_t len = strlen(name.get());
+    size_t len = strlen(name);
     char16_t* buffer = static_cast<char16_t *>(c.lifo.alloc(len * sizeof(char16_t)));
     if (!buffer)
-        return AstName();
+        return false;
 
     for (size_t i = 0; i < len; i++)
-        buffer[i] = name.get()[i];
+        buffer[i] = name[i];
 
-    return AstName(buffer, len);
+    *out = AstName(buffer, len);
+    return true;
 }
 
 static bool
 AstDecodeImportSection(AstDecodeContext& c, const SigWithIdVector& sigs, TableDescVector* tables)
 {
     Uint32Vector funcSigIndices;
     Maybe<Limits> memory;
     ImportVector imports;
@@ -1466,18 +1467,22 @@ AstDecodeImportSection(AstDecodeContext&
     size_t lastFunc = 0;
     size_t lastGlobal = 0;
     size_t lastTable = 0;
     size_t lastMemory = 0;
 
     for (size_t importIndex = 0; importIndex < imports.length(); importIndex++) {
         const Import& import = imports[importIndex];
 
-        AstName moduleName = ToAstName(c, import.module);
-        AstName fieldName = ToAstName(c, import.field);
+        AstName moduleName;
+        if (!ToAstName(c, import.module.get(), &moduleName))
+            return false;
+        AstName fieldName;
+        if (!ToAstName(c, import.field.get(), &fieldName))
+            return false;
 
         AstImport* ast = nullptr;
         switch (import.kind) {
           case DefinitionKind::Function: {
             AstName importName;
             if (!GenerateName(c, AstName(u"import"), lastFunc, &importName))
                 return false;
 
@@ -1565,35 +1570,16 @@ AstDecodeTableSection(AstDecodeContext& 
         if (!c.module().addTable(name, (*tables)[i].limits))
             return false;
     }
 
     return true;
 }
 
 static bool
-AstDecodeName(AstDecodeContext& c, AstName* name)
-{
-    uint32_t length;
-    if (!c.d.readVarU32(&length))
-        return false;
-
-    const uint8_t* bytes;
-    if (!c.d.readBytes(length, &bytes))
-        return false;
-
-    char16_t* buffer = static_cast<char16_t *>(c.lifo.alloc(length * sizeof(char16_t)));
-    for (size_t i = 0; i < length; i++)
-        buffer[i] = bytes[i];
-
-    *name = AstName(buffer, length);
-    return true;
-}
-
-static bool
 AstDecodeMemorySection(AstDecodeContext& c)
 {
     bool present;
     Limits memory;
     if (!DecodeMemorySection(c.d, c.module().hasMemory(), &memory, &present))
         return false;
 
     if (present) {
@@ -1659,64 +1645,44 @@ AstDecodeGlobalSection(AstDecodeContext&
         if (!g || !c.module().append(g))
             return false;
     }
 
     return true;
 }
 
 static bool
-AstDecodeExport(AstDecodeContext& c, AstExport** export_)
-{
-    AstName fieldName;
-    if (!AstDecodeName(c, &fieldName))
-        return c.d.fail("expected export name");
-
-    uint32_t kindValue;
-    if (!c.d.readVarU32(&kindValue))
-        return c.d.fail("expected export kind");
-
-    uint32_t index;
-    if (!c.d.readVarU32(&index))
-        return c.d.fail("expected export internal index");
-
-    *export_ = new(c.lifo) AstExport(fieldName, DefinitionKind(kindValue), AstRef(index));
-    if (!*export_)
-        return false;
-
-    return true;
-}
-
-static bool
 AstDecodeExportSection(AstDecodeContext& c)
 {
-    uint32_t sectionStart, sectionSize;
-    if (!c.d.startSection(SectionId::Export, &sectionStart, &sectionSize, "export"))
+    const AstModule& m = c.module();
+    size_t numFuncs = m.numFuncImports() + c.funcDefSigs().length();
+
+    ExportVector exports;
+    if (!DecodeExportSection(c.d, numFuncs, m.tables().length(), m.hasMemory(), c.globalDescs(),
+                             &exports))
         return false;
-    if (sectionStart == Decoder::NotStarted)
-        return true;
 
-    uint32_t numExports;
-    if (!c.d.readVarU32(&numExports))
-        return c.d.fail("failed to read number of exports");
+    for (const Export& exp : exports) {
+        size_t index;
+        switch (exp.kind()) {
+          case DefinitionKind::Function: index = exp.funcIndex(); break;
+          case DefinitionKind::Global: index = exp.globalIndex(); break;
+          case DefinitionKind::Memory: index = 0; break;
+          case DefinitionKind::Table: index = 0; break;
+        }
 
-    if (numExports > MaxExports)
-        return c.d.fail("too many exports");
+        AstName name;
+        if (!ToAstName(c, exp.fieldName(), &name))
+            return false;
 
-    for (uint32_t i = 0; i < numExports; i++) {
-        AstExport* export_ = nullptr;
-        if (!AstDecodeExport(c, &export_))
-            return false;
-        if (!c.module().append(export_))
+        AstExport* e = new(c.lifo) AstExport(name, exp.kind(), AstRef(index));
+        if (!e || !c.module().append(e))
             return false;
     }
 
-    if (!c.d.finishSection(sectionStart, sectionSize, "export"))
-        return false;
-
     return true;
 }
 
 static bool
 AstDecodeFunctionBody(AstDecodeContext &c, uint32_t funcDefIndex, AstFunc** func)
 {
     uint32_t offset = c.d.currentOffset();
     uint32_t bodySize;
--- a/js/src/wasm/WasmCompile.cpp
+++ b/js/src/wasm/WasmCompile.cpp
@@ -465,131 +465,25 @@ DecodeMemorySection(Decoder& d, ModuleGe
         init->memoryUsage = MemoryUsage::Unshared;
         init->minMemoryLength = memory.initial;
         init->maxMemoryLength = memory.maximum;
     }
 
     return true;
 }
 
-typedef HashSet<const char*, CStringHasher, SystemAllocPolicy> CStringSet;
-
-static UniqueChars
-DecodeExportName(Decoder& d, CStringSet* dupSet)
-{
-    UniqueChars exportName = DecodeName(d);
-    if (!exportName) {
-        d.fail("expected valid export name");
-        return nullptr;
-    }
-
-    CStringSet::AddPtr p = dupSet->lookupForAdd(exportName.get());
-    if (p) {
-        d.fail("duplicate export");
-        return nullptr;
-    }
-
-    if (!dupSet->add(p, exportName.get()))
-        return nullptr;
-
-    return Move(exportName);
-}
-
-static bool
-DecodeExport(Decoder& d, ModuleGenerator& mg, CStringSet* dupSet)
-{
-    UniqueChars fieldName = DecodeExportName(d, dupSet);
-    if (!fieldName)
-        return false;
-
-    uint32_t exportKind;
-    if (!d.readVarU32(&exportKind))
-        return d.fail("failed to read export kind");
-
-    switch (DefinitionKind(exportKind)) {
-      case DefinitionKind::Function: {
-        uint32_t funcIndex;
-        if (!d.readVarU32(&funcIndex))
-            return d.fail("expected export internal index");
-
-        if (funcIndex >= mg.numFuncs())
-            return d.fail("exported function index out of bounds");
-
-        return mg.addFuncExport(Move(fieldName), funcIndex);
-      }
-      case DefinitionKind::Table: {
-        uint32_t tableIndex;
-        if (!d.readVarU32(&tableIndex))
-            return d.fail("expected table index");
-
-        if (tableIndex >= mg.tables().length())
-            return d.fail("exported table index out of bounds");
-
-        return mg.addTableExport(Move(fieldName));
-      }
-      case DefinitionKind::Memory: {
-        uint32_t memoryIndex;
-        if (!d.readVarU32(&memoryIndex))
-            return d.fail("expected memory index");
-
-        if (memoryIndex > 0 || !mg.usesMemory())
-            return d.fail("exported memory index out of bounds");
-
-        return mg.addMemoryExport(Move(fieldName));
-      }
-      case DefinitionKind::Global: {
-        uint32_t globalIndex;
-        if (!d.readVarU32(&globalIndex))
-            return d.fail("expected global index");
-
-        if (globalIndex >= mg.globals().length())
-            return d.fail("exported global index out of bounds");
-
-        const GlobalDesc& global = mg.globals()[globalIndex];
-        if (!GlobalIsJSCompatible(d, global.type(), global.isMutable()))
-            return false;
-
-        return mg.addGlobalExport(Move(fieldName), globalIndex);
-      }
-      default:
-        return d.fail("unexpected export kind");
-    }
-
-    MOZ_CRASH("unreachable");
-}
-
 static bool
 DecodeExportSection(Decoder& d, ModuleGenerator& mg)
 {
-    uint32_t sectionStart, sectionSize;
-    if (!d.startSection(SectionId::Export, &sectionStart, &sectionSize, "export"))
-        return false;
-    if (sectionStart == Decoder::NotStarted)
-        return true;
-
-    CStringSet dupSet;
-    if (!dupSet.init())
+    ExportVector exports;
+    if (!DecodeExportSection(d, mg.numFuncs(), mg.numTables(), mg.usesMemory(), mg.globals(),
+                             &exports))
         return false;
 
-    uint32_t numExports;
-    if (!d.readVarU32(&numExports))
-        return d.fail("failed to read number of exports");
-
-    if (numExports > MaxExports)
-        return d.fail("too many exports");
-
-    for (uint32_t i = 0; i < numExports; i++) {
-        if (!DecodeExport(d, mg, &dupSet))
-            return false;
-    }
-
-    if (!d.finishSection(sectionStart, sectionSize, "export"))
-        return false;
-
-    return true;
+    return mg.setExports(Move(exports));
 }
 
 static bool
 DecodeFunctionBody(Decoder& d, ModuleGenerator& mg, uint32_t funcIndex)
 {
     uint32_t bodySize;
     if (!d.readVarU32(&bodySize))
         return d.fail("expected number of function body bytes");
@@ -869,17 +763,17 @@ wasm::Compile(const ShareableBytes& byte
 
     if (!DecodeGlobalSection(d, &init->globals))
         return nullptr;
 
     ModuleGenerator mg(Move(imports));
     if (!mg.init(Move(init), args))
         return nullptr;
 
-    if (!DecodeExportSection(d, mg))
+    if (!::DecodeExportSection(d, mg))
         return nullptr;
 
     if (!DecodeStartSection(d, mg))
         return nullptr;
 
     if (!DecodeElemSection(d, mg))
         return nullptr;
 
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -796,41 +796,39 @@ ModuleGenerator::numFuncs() const
 const SigWithId&
 ModuleGenerator::funcSig(uint32_t funcIndex) const
 {
     MOZ_ASSERT(shared_->funcSigs[funcIndex]);
     return *shared_->funcSigs[funcIndex];
 }
 
 bool
-ModuleGenerator::addFuncExport(UniqueChars fieldName, uint32_t funcIndex)
-{
-    return exportedFuncs_.put(funcIndex) &&
-           exports_.emplaceBack(Move(fieldName), funcIndex, DefinitionKind::Function);
-}
-
-bool
-ModuleGenerator::addTableExport(UniqueChars fieldName)
+ModuleGenerator::setExports(ExportVector&& exports)
 {
-    MOZ_ASSERT(!startedFuncDefs_);
-    MOZ_ASSERT(shared_->tables.length() == 1);
-    shared_->tables[0].external = true;
-    return exports_.emplaceBack(Move(fieldName), DefinitionKind::Table);
-}
+    MOZ_ASSERT_IF(!isAsmJS(), !startedFuncDefs_);
+
+    exports_ = Move(exports);
 
-bool
-ModuleGenerator::addMemoryExport(UniqueChars fieldName)
-{
-    return exports_.emplaceBack(Move(fieldName), DefinitionKind::Memory);
-}
+    for (const Export& exp : exports_) {
+        switch (exp.kind()) {
+          case DefinitionKind::Function:
+            if (!exportedFuncs_.put(exp.funcIndex()))
+                return false;
+            break;
+          case DefinitionKind::Table:
+            MOZ_ASSERT(shared_->tables.length() == 1);
+            shared_->tables[0].external = true;
+            break;
+          case DefinitionKind::Memory:
+          case DefinitionKind::Global:
+            break;
+        }
+    }
 
-bool
-ModuleGenerator::addGlobalExport(UniqueChars fieldName, uint32_t globalIndex)
-{
-    return exports_.emplaceBack(Move(fieldName), globalIndex, DefinitionKind::Global);
+    return true;
 }
 
 bool
 ModuleGenerator::setStartFunction(uint32_t funcIndex)
 {
     metadata_->startFuncIndex.emplace(funcIndex);
     return exportedFuncs_.put(funcIndex);
 }
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -155,20 +155,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     const GlobalDescVector& globals() const { return shared_->globals; }
 
     // Functions declarations:
     uint32_t numFuncImports() const;
     uint32_t numFuncDefs() const;
     uint32_t numFuncs() const;
 
     // Exports:
-    MOZ_MUST_USE bool addFuncExport(UniqueChars fieldName, uint32_t funcIndex);
-    MOZ_MUST_USE bool addTableExport(UniqueChars fieldName);
-    MOZ_MUST_USE bool addMemoryExport(UniqueChars fieldName);
-    MOZ_MUST_USE bool addGlobalExport(UniqueChars fieldName, uint32_t globalIndex);
+    MOZ_MUST_USE bool setExports(ExportVector&& exports);
 
     // Function definitions:
     MOZ_MUST_USE bool startFuncDefs();
     MOZ_MUST_USE bool startFuncDef(uint32_t lineOrBytecode, FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDef(uint32_t funcIndex, FunctionGenerator* fg);
     MOZ_MUST_USE bool finishFuncDefs();
 
     // Start function:
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -162,73 +162,16 @@ Import::deserialize(const uint8_t* curso
 
 size_t
 Import::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return module.sizeOfExcludingThis(mallocSizeOf) +
            field.sizeOfExcludingThis(mallocSizeOf);
 }
 
-Export::Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind)
-  : fieldName_(Move(fieldName))
-{
-    pod.kind_ = kind;
-    pod.index_ = index;
-}
-
-Export::Export(UniqueChars fieldName, DefinitionKind kind)
-  : fieldName_(Move(fieldName))
-{
-    pod.kind_ = kind;
-    pod.index_ = 0;
-}
-
-uint32_t
-Export::funcIndex() const
-{
-    MOZ_ASSERT(pod.kind_ == DefinitionKind::Function);
-    return pod.index_;
-}
-
-uint32_t
-Export::globalIndex() const
-{
-    MOZ_ASSERT(pod.kind_ == DefinitionKind::Global);
-    return pod.index_;
-}
-
-size_t
-Export::serializedSize() const
-{
-    return fieldName_.serializedSize() +
-           sizeof(pod);
-}
-
-uint8_t*
-Export::serialize(uint8_t* cursor) const
-{
-    cursor = fieldName_.serialize(cursor);
-    cursor = WriteBytes(cursor, &pod, sizeof(pod));
-    return cursor;
-}
-
-const uint8_t*
-Export::deserialize(const uint8_t* cursor)
-{
-    (cursor = fieldName_.deserialize(cursor)) &&
-    (cursor = ReadBytes(cursor, &pod, sizeof(pod)));
-    return cursor;
-}
-
-size_t
-Export::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
-{
-    return fieldName_.sizeOfExcludingThis(mallocSizeOf);
-}
-
 size_t
 ElemSegment::serializedSize() const
 {
     return sizeof(tableIndex) +
            sizeof(offset) +
            SerializedPodVectorSize(elemFuncIndices) +
            SerializedPodVectorSize(elemCodeRangeIndices);
 }
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -72,49 +72,16 @@ struct LinkData : LinkDataCacheablePod
     SymbolicLinkArray   symbolicLinks;
 
     WASM_DECLARE_SERIALIZABLE(LinkData)
 };
 
 typedef UniquePtr<LinkData> UniqueLinkData;
 typedef UniquePtr<const LinkData> UniqueConstLinkData;
 
-// Export describes the export of a definition in a Module to a field in the
-// export object. For functions, Export stores an index into the
-// FuncExportVector in Metadata. For memory and table exports, there is
-// at most one (default) memory/table so no index is needed. Note: a single
-// definition can be exported by multiple Exports in the ExportVector.
-//
-// ExportVector is built incrementally by ModuleGenerator and then stored
-// immutably by Module.
-
-class Export
-{
-    CacheableChars fieldName_;
-    struct CacheablePod {
-        DefinitionKind kind_;
-        uint32_t index_;
-    } pod;
-
-  public:
-    Export() = default;
-    explicit Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind);
-    explicit Export(UniqueChars fieldName, DefinitionKind kind);
-
-    const char* fieldName() const { return fieldName_.get(); }
-
-    DefinitionKind kind() const { return pod.kind_; }
-    uint32_t funcIndex() const;
-    uint32_t globalIndex() const;
-
-    WASM_DECLARE_SERIALIZABLE(Export)
-};
-
-typedef Vector<Export, 0, SystemAllocPolicy> ExportVector;
-
 // ElemSegment represents an element segment in the module where each element
 // describes both its function index and its code range.
 
 struct ElemSegment
 {
     uint32_t tableIndex;
     InitExpr offset;
     Uint32Vector elemFuncIndices;
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -562,16 +562,73 @@ SigWithId::deserialize(const uint8_t* cu
 }
 
 size_t
 SigWithId::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return Sig::sizeOfExcludingThis(mallocSizeOf);
 }
 
+Export::Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind)
+  : fieldName_(Move(fieldName))
+{
+    pod.kind_ = kind;
+    pod.index_ = index;
+}
+
+Export::Export(UniqueChars fieldName, DefinitionKind kind)
+  : fieldName_(Move(fieldName))
+{
+    pod.kind_ = kind;
+    pod.index_ = 0;
+}
+
+uint32_t
+Export::funcIndex() const
+{
+    MOZ_ASSERT(pod.kind_ == DefinitionKind::Function);
+    return pod.index_;
+}
+
+uint32_t
+Export::globalIndex() const
+{
+    MOZ_ASSERT(pod.kind_ == DefinitionKind::Global);
+    return pod.index_;
+}
+
+size_t
+Export::serializedSize() const
+{
+    return fieldName_.serializedSize() +
+           sizeof(pod);
+}
+
+uint8_t*
+Export::serialize(uint8_t* cursor) const
+{
+    cursor = fieldName_.serialize(cursor);
+    cursor = WriteBytes(cursor, &pod, sizeof(pod));
+    return cursor;
+}
+
+const uint8_t*
+Export::deserialize(const uint8_t* cursor)
+{
+    (cursor = fieldName_.deserialize(cursor)) &&
+    (cursor = ReadBytes(cursor, &pod, sizeof(pod)));
+    return cursor;
+}
+
+size_t
+Export::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
+{
+    return fieldName_.sizeOfExcludingThis(mallocSizeOf);
+}
+
 Assumptions::Assumptions(JS::BuildIdCharVector&& buildId)
   : cpuId(GetCPUID()),
     buildId(Move(buildId))
 {}
 
 Assumptions::Assumptions()
   : cpuId(GetCPUID()),
     buildId()
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -580,16 +580,49 @@ struct Import
       : module(Move(module)), field(Move(field)), kind(kind)
     {}
 
     WASM_DECLARE_SERIALIZABLE(Import)
 };
 
 typedef Vector<Import, 0, SystemAllocPolicy> ImportVector;
 
+// Export describes the export of a definition in a Module to a field in the
+// export object. For functions, Export stores an index into the
+// FuncExportVector in Metadata. For memory and table exports, there is
+// at most one (default) memory/table so no index is needed. Note: a single
+// definition can be exported by multiple Exports in the ExportVector.
+//
+// ExportVector is built incrementally by ModuleGenerator and then stored
+// immutably by Module.
+
+class Export
+{
+    CacheableChars fieldName_;
+    struct CacheablePod {
+        DefinitionKind kind_;
+        uint32_t index_;
+    } pod;
+
+  public:
+    Export() = default;
+    explicit Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind);
+    explicit Export(UniqueChars fieldName, DefinitionKind kind);
+
+    const char* fieldName() const { return fieldName_.get(); }
+
+    DefinitionKind kind() const { return pod.kind_; }
+    uint32_t funcIndex() const;
+    uint32_t globalIndex() const;
+
+    WASM_DECLARE_SERIALIZABLE(Export)
+};
+
+typedef Vector<Export, 0, SystemAllocPolicy> ExportVector;
+
 // A GlobalDesc describes a single global variable. Currently, asm.js and wasm
 // exposes mutable and immutable private globals, but can't import nor export
 // mutable globals.
 
 enum class GlobalKind
 {
     Import,
     Constant,