Bug 1258972: Baldr: Implement wasm start section; r=luke
☠☠ backed out by 1bcaeba7dd14 ☠ ☠
authorBenjamin Bouvier <benj@benj.me>
Mon, 11 Jul 2016 16:02:14 +0200
changeset 387150 1b3a45aebcaffed2a9f67801f491b67f9a6106f1
parent 387149 df769eb7552e3485297e670803b0b33553c131a4
child 387151 8033414023e2b84b6f8608ce27c1ad3a9b0e321b
push id22898
push userCallek@gmail.com
push dateWed, 13 Jul 2016 13:20:13 +0000
reviewersluke
bugs1258972
milestone50.0a1
Bug 1258972: Baldr: Implement wasm start section; r=luke MozReview-Commit-ID: 6MUGChcJ040
js/src/asmjs/WasmAST.h
js/src/asmjs/WasmBinary.h
js/src/asmjs/WasmCompile.cpp
js/src/asmjs/WasmGenerator.cpp
js/src/asmjs/WasmGenerator.h
js/src/asmjs/WasmInstance.h
js/src/asmjs/WasmModule.cpp
js/src/asmjs/WasmModule.h
js/src/asmjs/WasmTextToBinary.cpp
js/src/jit-test/tests/wasm/binary.js
js/src/jit-test/tests/wasm/spec/start.wast.js
js/src/jit-test/tests/wasm/start.js
--- a/js/src/asmjs/WasmAST.h
+++ b/js/src/asmjs/WasmAST.h
@@ -585,16 +585,30 @@ class AstElemSegment : public AstNode
     {}
     uint32_t offset() const { return offset_; }
     AstRefVector& elems() { return elems_; }
     const AstRefVector& elems() const { return elems_; }
 };
 
 typedef AstVector<AstElemSegment*> AstElemSegmentVector;
 
+class AstStartFunc : public AstNode
+{
+    AstRef func_;
+
+  public:
+    explicit AstStartFunc(AstRef func)
+      : func_(func)
+    {}
+
+    AstRef& func() {
+        return func_;
+    }
+};
+
 class AstModule : public AstNode
 {
   public:
     typedef AstVector<AstFunc*> FuncVector;
     typedef AstVector<AstImport*> ImportVector;
     typedef AstVector<AstExport*> ExportVector;
     typedef AstVector<AstSig*> SigVector;
 
@@ -603,16 +617,17 @@ class AstModule : public AstNode
 
     LifoAlloc&           lifo_;
     SigVector            sigs_;
     SigMap               sigMap_;
     ImportVector         imports_;
     Maybe<AstResizable>  table_;
     Maybe<AstResizable>  memory_;
     ExportVector         exports_;
+    Maybe<AstStartFunc>  startFunc_;
     FuncVector           funcs_;
     AstDataSegmentVector dataSegments_;
     AstElemSegmentVector elemSegments_;
 
   public:
     explicit AstModule(LifoAlloc& lifo)
       : lifo_(lifo),
         sigs_(lifo),
@@ -657,16 +672,28 @@ class AstModule : public AstNode
         return dataSegments_;
     }
     bool append(AstElemSegment* seg) {
         return elemSegments_.append(seg);
     }
     const AstElemSegmentVector& elemSegments() const {
         return elemSegments_;
     }
+    bool hasStartFunc() const {
+        return !!startFunc_;
+    }
+    bool setStartFunc(AstStartFunc startFunc) {
+        if (startFunc_)
+            return false;
+        startFunc_.emplace(startFunc);
+        return true;
+    }
+    AstStartFunc& startFunc() {
+        return *startFunc_;
+    }
     bool declare(AstSig&& sig, uint32_t* sigIndex) {
         SigMap::AddPtr p = sigMap_.lookupForAdd(sig);
         if (p) {
             *sigIndex = p->value();
             return true;
         }
         *sigIndex = sigs_.length();
         auto* lifoSig = new (lifo_) AstSig(AstName(), Move(sig));
--- a/js/src/asmjs/WasmBinary.h
+++ b/js/src/asmjs/WasmBinary.h
@@ -28,16 +28,17 @@ static const uint32_t MagicNumber       
 static const uint32_t EncodingVersion    = 0x0b;
 
 static const char TypeSectionId[]        = "type";
 static const char ImportSectionId[]      = "import";
 static const char FunctionSectionId[]    = "function";
 static const char TableSectionId[]       = "table";
 static const char MemorySectionId[]      = "memory";
 static const char ExportSectionId[]      = "export";
+static const char StartSectionId[]       = "start";
 static const char CodeSectionId[]        = "code";
 static const char ElemSectionId[]        = "elem";
 static const char DataSectionId[]        = "data";
 static const char NameSectionId[]        = "name";
 
 enum class ValType
 {
     I32                                  = 0x01,
--- a/js/src/asmjs/WasmCompile.cpp
+++ b/js/src/asmjs/WasmCompile.cpp
@@ -1082,16 +1082,49 @@ DecodeFunctionBody(Decoder& d, ModuleGen
         return false;
 
     memcpy(fg.bytes().begin(), bodyBegin, bodySize);
 
     return mg.finishFuncDef(funcIndex, &fg);
 }
 
 static bool
+DecodeStartSection(Decoder& d, ModuleGenerator& mg)
+{
+
+    uint32_t sectionStart, sectionSize;
+    if (!d.startSection(StartSectionId, &sectionStart, &sectionSize))
+        return Fail(d, "failed to start section");
+    if (sectionStart == Decoder::NotStarted)
+        return true;
+
+    uint32_t startFuncIndex;
+    if (!d.readVarU32(&startFuncIndex))
+        return Fail(d, "failed to read start func index");
+
+    if (startFuncIndex >= mg.numFuncSigs())
+        return Fail(d, "unknown start function");
+
+    const DeclaredSig& sig = mg.funcSig(startFuncIndex);
+    if (sig.ret() != ExprType::Void)
+        return Fail(d, "start function must not return anything");
+
+    if (sig.args().length())
+        return Fail(d, "start function must be nullary");
+
+    if (!mg.setStartFunction(startFuncIndex))
+        return false;
+
+    if (!d.finishSection(sectionStart, sectionSize))
+        return Fail(d, "data section byte size mismatch");
+
+    return true;
+}
+
+static bool
 DecodeCodeSection(Decoder& d, ModuleGenerator& mg)
 {
     if (!mg.startFuncDefs())
         return false;
 
     uint32_t sectionStart, sectionSize;
     if (!d.startSection(CodeSectionId, &sectionStart, &sectionSize))
         return Fail(d, "failed to start section");
@@ -1386,16 +1419,19 @@ wasm::Compile(Bytes&& bytecode, CompileA
 
     ModuleGenerator mg;
     if (!mg.init(Move(init), Move(args)))
         return nullptr;
 
     if (!DecodeExportSection(d, newFormat, memoryExported, mg))
         return nullptr;
 
+    if (!DecodeStartSection(d, mg))
+        return nullptr;
+
     if (!DecodeCodeSection(d, mg))
         return nullptr;
 
     if (!DecodeElemSection(d, newFormat, Move(oldElems), mg))
         return nullptr;
 
     if (!DecodeDataSection(d, newFormat, mg))
         return nullptr;
--- a/js/src/asmjs/WasmGenerator.cpp
+++ b/js/src/asmjs/WasmGenerator.cpp
@@ -644,16 +644,18 @@ ModuleGenerator::funcImport(uint32_t fun
 {
     MOZ_ASSERT(shared_->funcImports[funcImportIndex].sig);
     return shared_->funcImports[funcImportIndex];
 }
 
 bool
 ModuleGenerator::declareExport(UniqueChars fieldName, uint32_t funcIndex, uint32_t* exportIndex)
 {
+    MOZ_ASSERT(!exportMap_.hasStartFunction());
+
     if (!exportMap_.fieldNames.append(Move(fieldName)))
         return false;
 
     FuncIndexMap::AddPtr p = funcIndexToExport_.lookupForAdd(funcIndex);
     if (p) {
         if (exportIndex)
             *exportIndex = p->value();
         return exportMap_.fieldsToExports.append(p->value());
@@ -683,16 +685,33 @@ ModuleGenerator::numExports() const
 bool
 ModuleGenerator::addMemoryExport(UniqueChars fieldName)
 {
     return exportMap_.fieldNames.append(Move(fieldName)) &&
            exportMap_.fieldsToExports.append(MemoryExport);
 }
 
 bool
+ModuleGenerator::setStartFunction(uint32_t funcIndex)
+{
+    FuncIndexMap::AddPtr p = funcIndexToExport_.lookupForAdd(funcIndex);
+    if (p) {
+        exportMap_.setStartFunction(p->value());
+        return true;
+    }
+
+    Sig copy;
+    if (!copy.clone(funcSig(funcIndex)))
+        return false;
+
+    exportMap_.setStartFunction(metadata_->exports.length());
+    return metadata_->exports.emplaceBack(Move(copy), funcIndex);
+}
+
+bool
 ModuleGenerator::startFuncDefs()
 {
     MOZ_ASSERT(!startedFuncDefs_);
     MOZ_ASSERT(!finishedFuncDefs_);
 
     // The wasmCompilationInProgress atomic ensures that there is only one
     // parallel compilation in progress at a time. In the special case of
     // asm.js, where the ModuleGenerator itself can be on a helper thread, this
--- a/js/src/asmjs/WasmGenerator.h
+++ b/js/src/asmjs/WasmGenerator.h
@@ -179,16 +179,19 @@ class MOZ_STACK_CLASS ModuleGenerator
     MOZ_MUST_USE bool addMemoryExport(UniqueChars fieldName);
 
     // 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:
+    bool setStartFunction(uint32_t funcIndex);
+
     // Segments:
     MOZ_MUST_USE bool addDataSegment(DataSegment s) { return dataSegments_.append(s); }
     MOZ_MUST_USE bool addElemSegment(ElemSegment&& s);
 
     // Function names:
     void setFuncNames(NameInBytecodeVector&& funcNames);
 
     // asm.js lazy initialization:
--- a/js/src/asmjs/WasmInstance.h
+++ b/js/src/asmjs/WasmInstance.h
@@ -25,17 +25,17 @@
 
 namespace js {
 
 class WasmActivation;
 class WasmInstanceObject;
 
 namespace wasm {
 
-struct ExportMap;
+class ExportMap;
 
 // Instance represents a wasm instance and provides all the support for runtime
 // execution of code in the instance. Instances share various immutable data
 // structures with the Module from which they were instantiated and other
 // instances instantiated from the same Module. However, an Instance has no
 // direct reference to its source Module which allows a Module to be destroyed
 // while it still has live Instances.
 
@@ -95,19 +95,19 @@ class Instance
     // activations of the instance live on the stack. Once in profiling mode,
     // ProfilingFrameIterator can be used to asynchronously walk the stack.
     // Otherwise, the ProfilingFrameIterator will skip any activations of this
     // instance.
 
     bool profilingEnabled() const { return profilingEnabled_; }
     const char* profilingLabel(uint32_t funcIndex) const { return funcLabels_[funcIndex].get(); }
 
-    // If the source binary was saved (by passing the bytecode to 'create'),
-    // this method will render the binary as text. Otherwise, a diagnostic
-    // string will be returned.
+    // If the source binary was saved (by passing the bytecode to the
+    // constructor), this method will render the binary as text. Otherwise, a
+    // diagnostic string will be returned.
 
     JSString* createText(JSContext* cx);
 
     // Return the name associated with a given function index, or generate one
     // if none was given by the module.
 
     bool getFuncName(JSContext* cx, uint32_t funcIndex, TwoByteName* name) const;
     JSAtom* getFuncAtom(JSContext* cx, uint32_t funcIndex) const;
--- a/js/src/asmjs/WasmModule.cpp
+++ b/js/src/asmjs/WasmModule.cpp
@@ -157,32 +157,35 @@ Import::sizeOfExcludingThis(MallocSizeOf
     return module.sizeOfExcludingThis(mallocSizeOf) +
            func.sizeOfExcludingThis(mallocSizeOf);
 }
 
 size_t
 ExportMap::serializedSize() const
 {
     return SerializedVectorSize(fieldNames) +
-           SerializedPodVectorSize(fieldsToExports);
+           SerializedPodVectorSize(fieldsToExports) +
+           sizeof(uint32_t);
 }
 
 uint8_t*
 ExportMap::serialize(uint8_t* cursor) const
 {
     cursor = SerializeVector(cursor, fieldNames);
     cursor = SerializePodVector(cursor, fieldsToExports);
+    cursor = WriteScalar(cursor, startExportIndex);
     return cursor;
 }
 
 const uint8_t*
 ExportMap::deserialize(const uint8_t* cursor)
 {
     (cursor = DeserializeVector(cursor, &fieldNames)) &&
-    (cursor = DeserializePodVector(cursor, &fieldsToExports));
+    (cursor = DeserializePodVector(cursor, &fieldsToExports)) &&
+    (cursor = ReadScalar(cursor, &startExportIndex));
     return cursor;
 }
 
 size_t
 ExportMap::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return SizeOfVectorExcludingThis(fieldNames, mallocSizeOf) &&
            fieldsToExports.sizeOfExcludingThis(mallocSizeOf);
@@ -559,16 +562,26 @@ Module::instantiate(JSContext* cx,
     RootedValue val(cx, ObjectValue(*exportObj));
     if (!JS_DefinePropertyById(cx, instanceObj, id, val, JSPROP_ENUMERATE))
         return false;
 
     // Done! Notify the Debugger of the new Instance.
 
     Debugger::onNewWasmInstance(cx, instanceObj);
 
+    // Call the start function, if there's one. By specification, it does not
+    // take any arguments nor does it return a value, so just create a dummy
+    // arguments object.
+
+    if (exportMap_.hasStartFunction()) {
+        FixedInvokeArgs<0> args(cx);
+        if (!instanceObj->instance().callExport(cx, exportMap_.startFunctionExportIndex(), args))
+            return false;
+    }
+
     return true;
 }
 
 bool
 wasm::IsExportedFunction(JSFunction* fun)
 {
     return fun->maybeNative() == WasmCall;
 }
--- a/js/src/asmjs/WasmModule.h
+++ b/js/src/asmjs/WasmModule.h
@@ -106,26 +106,48 @@ typedef Vector<Import, 0, SystemAllocPol
 // ExportMap describes all of a single module's exports. The ExportMap describes
 // how the Exports (stored in Metadata) are mapped to the fields of the export
 // object produced by instantiation. The 'fieldNames' vector provides the list
 // of names of the module's exports. For each field name, 'fieldsToExports'
 // provides either:
 //  - the sentinel value MemoryExport indicating an export of linear memory; or
 //  - the index of an export into the ExportVector in Metadata
 //
+// ExportMap also contains the start function's export index, which maps to the
+// export that is called at each instantiation of a given module.
+//
 // ExportMap is built incrementally by ModuleGenerator and then stored immutably
 // by Module.
 
 static const uint32_t MemoryExport = UINT32_MAX;
 
-struct ExportMap
+static const uint32_t NO_START_FUNCTION = UINT32_MAX;
+
+class ExportMap
 {
+    uint32_t startExportIndex;
+
+  public:
     CacheableCharsVector fieldNames;
     Uint32Vector fieldsToExports;
 
+    ExportMap() : startExportIndex(NO_START_FUNCTION) {}
+
+    bool hasStartFunction() const {
+        return startExportIndex != NO_START_FUNCTION;
+    }
+    void setStartFunction(uint32_t index) {
+        MOZ_ASSERT(!hasStartFunction());
+        startExportIndex = index;
+    }
+    uint32_t startFunctionExportIndex() const {
+        MOZ_ASSERT(hasStartFunction());
+        return startExportIndex;
+    }
+
     WASM_DECLARE_SERIALIZABLE(ExportMap)
 };
 
 // DataSegment describes the offset of a data segment in the bytecode that is
 // to be copied at a given offset into linear memory upon instantiation.
 
 struct DataSegment
 {
--- a/js/src/asmjs/WasmTextToBinary.cpp
+++ b/js/src/asmjs/WasmTextToBinary.cpp
@@ -85,42 +85,43 @@ class WasmToken
         Error,
         Export,
         Float,
         Func,
         GetLocal,
         If,
         Import,
         Index,
-        UnsignedInteger,
-        SignedInteger,
         Memory,
         NegativeZero,
         Load,
         Local,
         Loop,
         Module,
         Name,
         Nop,
         Offset,
         OpenParen,
         Param,
         Resizable,
         Result,
         Return,
         Segment,
         SetLocal,
+        SignedInteger,
+        Start,
         Store,
         Table,
         TernaryOpcode,
         Text,
         Then,
         Type,
         UnaryOpcode,
         Unreachable,
+        UnsignedInteger,
         ValueType
     };
   private:
     Kind kind_;
     const char16_t* begin_;
     const char16_t* end_;
     union {
         uint32_t index_;
@@ -1315,16 +1316,18 @@ WasmTokenStream::next()
 
       case 's':
         if (consume(MOZ_UTF16("select")))
             return WasmToken(WasmToken::TernaryOpcode, Expr::Select, begin, cur_);
         if (consume(MOZ_UTF16("set_local")))
             return WasmToken(WasmToken::SetLocal, begin, cur_);
         if (consume(MOZ_UTF16("segment")))
             return WasmToken(WasmToken::Segment, begin, cur_);
+        if (consume(MOZ_UTF16("start")))
+            return WasmToken(WasmToken::Start, begin, cur_);
         break;
 
       case 't':
         if (consume(MOZ_UTF16("table")))
             return WasmToken(WasmToken::Table, begin, cur_);
         if (consume(MOZ_UTF16("then")))
             return WasmToken(WasmToken::Then, begin, cur_);
         if (consume(MOZ_UTF16("type")))
@@ -2400,16 +2403,31 @@ ParseMemory(WasmParseContext& c, WasmTok
     if (!module->setMemory(memory)) {
         c.ts.generateError(token, c.error);
         return false;
     }
 
     return true;
 }
 
+static bool
+ParseStartFunc(WasmParseContext& c, WasmToken token, AstModule* module)
+{
+    AstRef func;
+    if (!c.ts.matchRef(&func, c.error))
+        return false;
+
+    if (!module->setStartFunc(AstStartFunc(func))) {
+        c.ts.generateError(token, c.error);
+        return false;
+    }
+
+    return true;
+}
+
 static AstImport*
 ParseImport(WasmParseContext& c, bool newFormat, AstModule* module)
 {
     AstName name = c.ts.getIfName();
 
     WasmToken moduleName;
     if (!c.ts.match(WasmToken::Text, &moduleName, c.error))
         return nullptr;
@@ -2550,16 +2568,21 @@ ParseModule(const char16_t* text, bool n
 
         switch (section.kind()) {
           case WasmToken::Type: {
             AstSig* sig = ParseTypeDef(c);
             if (!sig || !module->append(sig))
                 return nullptr;
             break;
           }
+          case WasmToken::Start: {
+            if (!ParseStartFunc(c, section, module))
+                return nullptr;
+            break;
+          }
           case WasmToken::Memory: {
             if (!ParseMemory(c, section, module))
                 return nullptr;
             break;
           }
           case WasmToken::Data: {
             AstDataSegment* segment = ParseDataSegment(c);
             if (!segment || !module->append(segment))
@@ -3063,16 +3086,21 @@ ResolveModule(LifoAlloc& lifo, AstModule
             return false;
     }
 
     for (AstFunc* func : module->funcs()) {
         if (!ResolveFunc(r, *func))
             return false;
     }
 
+    if (module->hasStartFunc()) {
+        if (!r.resolveFunction(module->startFunc().func()))
+            return false;
+    }
+
     return true;
 }
 
 /*****************************************************************************/
 // wasm function body serialization
 
 static bool
 EncodeExpr(Encoder& e, AstExpr& expr);
@@ -3718,16 +3746,33 @@ EncodeFunctionBody(Encoder& e, AstFunc& 
             return false;
     }
 
     e.patchVarU32(bodySizeAt, e.currentOffset() - beforeBody);
     return true;
 }
 
 static bool
+EncodeStartSection(Encoder& e, AstModule& module)
+{
+    if (!module.hasStartFunc())
+        return true;
+
+    size_t offset;
+    if (!e.startSection(StartSectionId, &offset))
+        return false;
+
+    if (!e.writeVarU32(module.startFunc().func().index()))
+        return false;
+
+    e.finishSection(offset);
+    return true;
+}
+
+static bool
 EncodeCodeSection(Encoder& e, AstModule& module)
 {
     if (module.funcs().empty())
         return true;
 
     size_t offset;
     if (!e.startSection(CodeSectionId, &offset))
         return false;
@@ -3868,16 +3913,19 @@ EncodeModule(AstModule& module, bool new
         return false;
 
     if (!EncodeMemorySection(e, newFormat, module))
         return false;
 
     if (!EncodeExportSection(e, newFormat, module))
         return false;
 
+    if (!EncodeStartSection(e, module))
+        return false;
+
     if (!EncodeCodeSection(e, module))
         return false;
 
     if (!EncodeDataSection(e, newFormat, module))
         return false;
 
     if (!EncodeElemSection(e, newFormat, module))
         return false;
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -257,17 +257,17 @@ wasmEval(moduleWithSections([sigSection(
 // Ignore errors in name section.
 var tooBigNameSection = {
     name: nameId,
     body: [...varU32(2**31)] // declare 2**31 functions.
 };
 wasmEval(moduleWithSections([tooBigNameSection]));
 
 // Checking stack trace.
-function runStartTraceTest(namesContent, expectedName) {
+function runStackTraceTest(namesContent, expectedName) {
     var sections = [
         sigSection([v2vSig]),
         importSection([{sigIndex:0, module:"env", func:"callback"}]),
         declSection([0]),
         exportSection([{funcIndex:0, name: "run"}]),
         bodySection([funcBody({locals: [], body: [CallImport, varU32(0), varU32(0)]})])
     ];
     if (namesContent)
@@ -276,20 +276,20 @@ function runStartTraceTest(namesContent,
     var callback = () => {
         var prevFrameEntry = new Error().stack.split('\n')[1];
         result = prevFrameEntry.split('@')[0];
     };
     wasmEval(moduleWithSections(sections), {"env": { callback }}).run();
     assertEq(result, expectedName);
 };
 
-runStartTraceTest(null, 'wasm-function[0]');
-runStartTraceTest([{name: 'test'}], 'test');
-runStartTraceTest([{name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
-runStartTraceTest([{name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
-runStartTraceTest([{name: 'test1'}, {name: 'test2'}], 'test1');
-runStartTraceTest([{name: 'test☃'}], 'test☃');
-runStartTraceTest([{name: 'te\xE0\xFF'}], 'te\xE0\xFF');
-runStartTraceTest([], 'wasm-function[0]');
+runStackTraceTest(null, 'wasm-function[0]');
+runStackTraceTest([{name: 'test'}], 'test');
+runStackTraceTest([{name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
+runStackTraceTest([{name: 'test', locals: [{name: 'var1'}, {name: 'var2'}]}], 'test');
+runStackTraceTest([{name: 'test1'}, {name: 'test2'}], 'test1');
+runStackTraceTest([{name: 'test☃'}], 'test☃');
+runStackTraceTest([{name: 'te\xE0\xFF'}], 'te\xE0\xFF');
+runStackTraceTest([], 'wasm-function[0]');
 // Notice that invalid names section content shall not fail the parsing
-runStartTraceTest([{nameLen: 100, name: 'test'}], 'wasm-function[0]'); // invalid name size
-runStartTraceTest([{name: 'test', locals: [{nameLen: 40, name: 'var1'}]}], 'wasm-function[0]'); // invalid variable name size
-runStartTraceTest([{name: ''}], 'wasm-function[0]'); // empty name
+runStackTraceTest([{nameLen: 100, name: 'test'}], 'wasm-function[0]'); // invalid name size
+runStackTraceTest([{name: 'test', locals: [{nameLen: 40, name: 'var1'}]}], 'wasm-function[0]'); // invalid variable name size
+runStackTraceTest([{name: ''}], 'wasm-function[0]'); // empty name
--- a/js/src/jit-test/tests/wasm/spec/start.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/start.wast.js
@@ -1,4 +1,2 @@
 // |jit-test| test-also-wasm-baseline
-// TODO start opcode
-quit();
 var importedArgs = ['start.wast']; load(scriptdir + '../spec.js');
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/start.js
@@ -0,0 +1,46 @@
+// |jit-test| test-also-wasm-baseline
+load(libdir + "wasm.js");
+
+assertErrorMessage(() => wasmEvalText('(module (func) (start 0) (start 0))'), SyntaxError, /wasm text error/);
+assertErrorMessage(() => wasmEvalText('(module (func) (start 1))'), TypeError, /unknown start function/);
+assertErrorMessage(() => wasmEvalText('(module (func) (start $unknown))'), SyntaxError, /label.*not found/);
+
+assertErrorMessage(() => wasmEvalText('(module (func (param i32)) (start 0))'), TypeError, /must be nullary/);
+assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param f32)) (start 0))'), TypeError, /must be nullary/);
+assertErrorMessage(() => wasmEvalText('(module (func (param i32) (param f32) (param f64)) (start 0))'), TypeError, /must be nullary/);
+
+assertErrorMessage(() => wasmEvalText('(module (func (result f32)) (start 0))'), TypeError, /must not return anything/);
+
+// Basic use case.
+var count = 0;
+function inc() { count++; }
+var exports = wasmEvalText(`(module (import "inc" "") (func (param i32)) (func (call_import 0)) (start 1))`, { inc });
+assertEq(count, 1);
+assertEq(Object.keys(exports).length, 0);
+
+count = 0;
+exports = wasmEvalText(`(module (import "inc" "") (func $start (call_import 0)) (start $start) (export "" 0))`, { inc });
+assertEq(count, 1);
+assertEq(typeof exports, 'function');
+assertEq(exports(), undefined);
+assertEq(count, 2);
+
+// New API.
+const Module = WebAssembly.Module;
+const Instance = WebAssembly.Instance;
+const textToBinary = str => wasmTextToBinary(str, 'new-format');
+
+count = 0;
+const m = new Module(textToBinary('(module (import "inc" "") (func) (func (call_import 0)) (start 1) (export "" 1))'));
+assertEq(count, 0);
+
+assertErrorMessage(() => new Instance(m), TypeError, /no import object given/);
+assertEq(count, 0);
+
+const i1 = new Instance(m, { inc });
+assertEq(count, 1);
+i1.exports[""]();
+assertEq(count, 2);
+
+const i2 = new Instance(m, { inc });
+assertEq(count, 3);