WebAssembly JS API: implement more sections
authorjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 4 Nov 2016 22:12:12 +0000 (22:12 +0000)
committerjfbastien@apple.com <jfbastien@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 4 Nov 2016 22:12:12 +0000 (22:12 +0000)
https://bugs.webkit.org/show_bug.cgi?id=164023

Reviewed by Keith Miller.

On the JSC side:

 - Put in parser stubs for all WebAssembly sections.
 - Parse Import, Export sections.
 - Use tryReserveCapacity instead of reserve, and bail out of the parser if it fails. This prevents the parser from bringing everything down when faced with a malicious input.
 - Encapsulate all parsed module information into its own structure, making it easier to pass around (from parser to Plan to Module to Instance).
 - Create WasmFormat.cpp to hold parsed module information's dtor to avoid including WasmMemory.h needlessly.
JSTests:

 - parseCode: avoid overflow through function size.
 - Remove all remainders of polyfill-prototype-1, and update license.
 - Add missing WasmOps.h and WasmValidateInlines.h auto-generation for cmake build.

On the Builder.js testing side:

 - Implement Type, Import (function only), Export (function only) sections.
 - Check section order and uniqueness.
 - Optionally auto-generate the Type section from subsequent Export / Import / Code entries.
 - Allow re-exporting an import.

* wasm/Builder.js: build type, import, and export sections
(const._normalizeFunctionSignature):
* wasm/Builder_WebAssemblyBinary.js: Added. Forked from Builder.js
(const.emitters.Type):
(const.emitters.Import):
(const.emitters.Function):
(const.emitters.Table):
(const.emitters.Memory):
(const.emitters.Global):
(const.emitters.Export):
(const.emitters.Start):
(const.emitters.Element):
(const.emitters.Code):
(const.emitters.Data):
(export.const.Binary):
* wasm/LowLevelBinary.js: Add a few useful outputs
(export.default.LowLevelBinary.prototype.varuint1):
(export.default.LowLevelBinary.prototype.varint7):
* wasm/WASM.js: value type and external kind helpers
* wasm/assert.js: array element-wise equality comparison
(const._eq):
* wasm/js-api/test_Module.js:
(ModuleWithImports):
* wasm/self-test/test_BuilderJSON.js: many more tests for all the new Builder APIs, and update to some older tests which now require a Type section or rejiggered Function signature
(const.assertOpThrows):
(SectionsWithSameCustomName):
(TwoTypeSections):
(EmptyImportSection):
(ImportBeforeTypeSections):
* wasm/self-test/test_BuilderWebAssembly.js: remove a test which wasn't helpful and is now obsolete
(CustomSection):

Source/JavaScriptCore:

 - Remove all remainders of polyfill-prototype-1, and update license.
 - Add missing WasmOps.h and WasmValidateInlines.h auto-generation for cmake build.

On the Builder.js testing side:

 - Implement Type, Import (function only), Export (function only) sections.
 - Check section order and uniqueness.
 - Optionally auto-generate the Type section from subsequent Export / Import / Code entries.
 - Allow re-exporting an import.

* CMakeLists.txt: missing auto-genration
* JavaScriptCore.xcodeproj/project.pbxproj: merge conflict
* testWasm.cpp: update for API changes, no functional change
(checkPlan):
(runWasmTests):
* wasm/WasmFormat.cpp: add a dtor which requires extra headers which I'd rather not include in WasmFormat.h
(JSC::Wasm::ModuleInformation::~ModuleInformation):
* wasm/WasmFormat.h: Add External, Import, Functioninformation, Export, ModuleInformation, CompiledFunction, and remove obsolete stuff which was a holdover from the first implementation (all that code is now gone, so remove its license)
(JSC::Wasm::External::isValid):
* wasm/WasmModuleParser.cpp: simplify some, make names consistent with the WebAssembly section names, check memory allocations so they can fail early
(JSC::Wasm::ModuleParser::parse):
(JSC::Wasm::ModuleParser::parseType):
(JSC::Wasm::ModuleParser::parseImport):
(JSC::Wasm::ModuleParser::parseFunction):
(JSC::Wasm::ModuleParser::parseTable):
(JSC::Wasm::ModuleParser::parseMemory):
(JSC::Wasm::ModuleParser::parseGlobal):
(JSC::Wasm::ModuleParser::parseExport):
(JSC::Wasm::ModuleParser::parseStart):
(JSC::Wasm::ModuleParser::parseElement):
(JSC::Wasm::ModuleParser::parseCode): avoid overflow through function size.
(JSC::Wasm::ModuleParser::parseData):
* wasm/WasmModuleParser.h:
(JSC::Wasm::ModuleParser::moduleInformation):
* wasm/WasmParser.h:
(JSC::Wasm::Parser::consumeUTF8String): add as required by spec
(JSC::Wasm::Parser::parseExternalKind): add as per spec
* wasm/WasmPlan.cpp:
(JSC::Wasm::Plan::Plan): fix some ownership, improve some error messages
* wasm/WasmPlan.h: fix some ownership
(JSC::Wasm::Plan::getModuleInformation):
(JSC::Wasm::Plan::getMemory):
(JSC::Wasm::Plan::compiledFunctionCount):
(JSC::Wasm::Plan::compiledFunction):
(JSC::Wasm::Plan::getCompiledFunctions):
* wasm/WasmSections.h: macroize with description, so that error messages are super pretty. This could be auto-generated.
* wasm/js/JSWebAssemblyModule.cpp:
(JSC::JSWebAssemblyModule::create): take module information
(JSC::JSWebAssemblyModule::JSWebAssemblyModule): ditto
* wasm/js/JSWebAssemblyModule.h:
(JSC::JSWebAssemblyModule::moduleInformation):
* wasm/js/WebAssemblyInstanceConstructor.cpp:
(JSC::constructJSWebAssemblyInstance): check that modules with imports are instantiated with an import object, as per spec. This needs to be tested.
* wasm/js/WebAssemblyMemoryConstructor.cpp:
(JSC::constructJSWebAssemblyMemory):
* wasm/js/WebAssemblyModuleConstructor.cpp:
(JSC::constructJSWebAssemblyModule):
* wasm/js/WebAssemblyTableConstructor.cpp:
(JSC::constructJSWebAssemblyTable):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@208401 268f45cc-cd09-0410-ab3c-d52691b4dbfc

29 files changed:
JSTests/ChangeLog
JSTests/wasm/Builder.js
JSTests/wasm/Builder_WebAssemblyBinary.js [new file with mode: 0644]
JSTests/wasm/LowLevelBinary.js
JSTests/wasm/WASM.js
JSTests/wasm/assert.js
JSTests/wasm/js-api/test_Module.js
JSTests/wasm/js-api/test_basic_api.js
JSTests/wasm/self-test/test_BuilderJSON.js
JSTests/wasm/self-test/test_BuilderWebAssembly.js
JSTests/wasm/self-test/test_WASM.js
Source/JavaScriptCore/CMakeLists.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/JavaScriptCore.xcodeproj/project.pbxproj
Source/JavaScriptCore/testWasm.cpp
Source/JavaScriptCore/wasm/WasmFormat.cpp
Source/JavaScriptCore/wasm/WasmFormat.h
Source/JavaScriptCore/wasm/WasmModuleParser.cpp
Source/JavaScriptCore/wasm/WasmModuleParser.h
Source/JavaScriptCore/wasm/WasmParser.h
Source/JavaScriptCore/wasm/WasmPlan.cpp
Source/JavaScriptCore/wasm/WasmPlan.h
Source/JavaScriptCore/wasm/WasmSections.h
Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.cpp
Source/JavaScriptCore/wasm/js/JSWebAssemblyModule.h
Source/JavaScriptCore/wasm/js/WebAssemblyInstanceConstructor.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyMemoryConstructor.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyModuleConstructor.cpp
Source/JavaScriptCore/wasm/js/WebAssemblyTableConstructor.cpp

index 6f1812f..61c00b9 100644 (file)
@@ -1,3 +1,60 @@
+2016-11-04  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly JS API: implement more sections
+        https://bugs.webkit.org/show_bug.cgi?id=164023
+
+        Reviewed by Keith Miller.
+
+        On the JSC side:
+
+         - Put in parser stubs for all WebAssembly sections.
+         - Parse Import, Export sections.
+         - Use tryReserveCapacity instead of reserve, and bail out of the parser if it fails. This prevents the parser from bringing everything down when faced with a malicious input.
+         - Encapsulate all parsed module information into its own structure, making it easier to pass around (from parser to Plan to Module to Instance).
+         - Create WasmFormat.cpp to hold parsed module information's dtor to avoid including WasmMemory.h needlessly.
+         - parseCode: avoid overflow through function size.
+         - Remove all remainders of polyfill-prototype-1, and update license.
+         - Add missing WasmOps.h and WasmValidateInlines.h auto-generation for cmake build.
+
+        On the Builder.js testing side:
+
+         - Implement Type, Import (function only), Export (function only) sections.
+         - Check section order and uniqueness.
+         - Optionally auto-generate the Type section from subsequent Export / Import / Code entries.
+         - Allow re-exporting an import.
+
+        * wasm/Builder.js: build type, import, and export sections
+        (const._normalizeFunctionSignature):
+        * wasm/Builder_WebAssemblyBinary.js: Added. Forked from Builder.js
+        (const.emitters.Type):
+        (const.emitters.Import):
+        (const.emitters.Function):
+        (const.emitters.Table):
+        (const.emitters.Memory):
+        (const.emitters.Global):
+        (const.emitters.Export):
+        (const.emitters.Start):
+        (const.emitters.Element):
+        (const.emitters.Code):
+        (const.emitters.Data):
+        (export.const.Binary):
+        * wasm/LowLevelBinary.js: Add a few useful outputs
+        (export.default.LowLevelBinary.prototype.varuint1):
+        (export.default.LowLevelBinary.prototype.varint7):
+        * wasm/WASM.js: value type and external kind helpers
+        * wasm/assert.js: array element-wise equality comparison
+        (const._eq):
+        * wasm/js-api/test_Module.js:
+        (ModuleWithImports):
+        * wasm/self-test/test_BuilderJSON.js: many more tests for all the new Builder APIs, and update to some older tests which now require a Type section or rejiggered Function signature
+        (const.assertOpThrows):
+        (SectionsWithSameCustomName):
+        (TwoTypeSections):
+        (EmptyImportSection):
+        (ImportBeforeTypeSections):
+        * wasm/self-test/test_BuilderWebAssembly.js: remove a test which wasn't helpful and is now obsolete
+        (CustomSection):
+
 2016-11-03  Mark Lam  <mark.lam@apple.com>
 
         ClonedArguments need to also support haveABadTime mode.
index 056d72d..d1e4768 100644 (file)
@@ -23,7 +23,8 @@
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-import LowLevelBinary from 'LowLevelBinary.js';
+import * as assert from 'assert.js';
+import * as BuildWebAssembly from 'Builder_WebAssemblyBinary.js';
 import * as WASM from 'WASM.js';
 
 const _toJavaScriptName = name => {
@@ -43,51 +44,114 @@ const _isValidValue = (value, type) => {
 };
 const _unknownSectionId = 0;
 
-const _BuildWebAssemblyBinary = (preamble, sections) => {
-    let wasmBin = new LowLevelBinary();
-    const put = (bin, type, value) => bin[type](value);
-    for (const p of WASM.description.preamble)
-        put(wasmBin, p.type, preamble[p.name]);
-    for (const section of sections) {
-        put(wasmBin, WASM.sectionEncodingType, section.id);
-        let sectionBin = wasmBin.newPatchable("varuint");
-        switch (section.name) {
-        case "Type": throw new Error(`Unimplemented: section type "${section.name}"`);
-        case "Import": throw new Error(`Unimplemented: section type "${section.name}"`);
-        case "Function": throw new Error(`Unimplemented: section type "${section.name}"`);
-        case "Table": throw new Error(`Unimplemented: section type "${section.name}"`);
-        case "Memory": throw new Error(`Unimplemented: section type "${section.name}"`);
-        case "Global": throw new Error(`Unimplemented: section type "${section.name}"`);
-        case "Export": throw new Error(`Unimplemented: section type "${section.name}"`);
-        case "Start": throw new Error(`Unimplemented: section type "${section.name}"`);
-        case "Element": throw new Error(`Unimplemented: section type "${section.name}"`);
-        case "Code":
-            const numberOfFunctionBodies = section.data.length;
-            put(sectionBin, "varuint", numberOfFunctionBodies);
-            for (const func of section.data) {
-                let funcBin = sectionBin.newPatchable("varuint");
-                const localCount = func.locals.length;
-                put(funcBin, "varuint", localCount);
-                if (localCount !== 0) throw new Error(`Unimplemented: locals`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
-                for (const op of func.code) {
-                    put(funcBin, "uint8", op.value);
-                    if (op.arguments.length !== 0) throw new Error(`Unimplemented: arguments`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
-                    if (op.immediates.length !== 0) throw new Error(`Unimplemented: immediates`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
-                }
-                funcBin.apply();
+const _normalizeFunctionSignature = (params, ret) => {
+    assert.isArray(params);
+    for (const p of params)
+        assert.truthy(WASM.isValidValueType(p), `Type parameter ${p} needs a valid value type`);
+    if (typeof(ret) === "undefined")
+        ret = "void";
+    assert.isNotArray(ret, `Multiple return values not supported by WebAssembly yet`);
+    assert.falsy(ret !== "void" && !WASM.isValidValueType(ret), `Type return ${ret} must be valid value type`);
+    return [params, ret];
+};
+
+const _maybeRegisterType = (builder, type) => {
+    const typeSection = builder._getSection("Type");
+    if (typeof(type) === "number") {
+        // Type numbers already refer to the type section, no need to register them.
+        if (builder._checked) {
+            assert.isNotUndef(typeSection, `Can't use type ${type} if a type section isn't present`);
+            assert.isNotUndef(typeSection.data[type], `Type ${type} doesn't exist in type section`);
+        }
+        return type;
+    }
+    assert.hasObjectProperty(type, "params", `Expected type to be a number or object with 'params' and optionally 'ret' fields`);
+    const [params, ret] = _normalizeFunctionSignature(type.params, type.ret);
+    assert.isNotUndef(typeSection, `Can't add type if a type section isn't present`);
+    // Try reusing an equivalent type from the type section.
+    types:
+    for (let i = 0; i !== typeSection.data.length; ++i) {
+        const t = typeSection.data[i];
+        if (t.ret === ret && params.length === t.params.length) {
+            for (let j = 0; j !== t.params.length; ++j) {
+                if (params[j] !== t.params[j])
+                    continue types;
             }
-            break;
-        case "Data": throw new Error(`Unimplemented: section type "${section.name}"`);
-        default:
-            if (section.id !== _unknownSectionId) throw new Error(`Unknown section "${section.name}" with number ${section.id}`);
-            put(sectionBin, "string", section.name);
-            for (const byte of section.data)
-                put(sectionBin, "uint8", byte);
+            type = i;
             break;
         }
-        sectionBin.apply();
     }
-    return wasmBin;
+    if (typeof(type) !== "number") {
+        // Couldn't reuse a pre-existing type, register this type in the type section.
+        typeSection.data.push({ params: params, ret: ret });
+        type = typeSection.data.length - 1;
+    }
+    return type;
+};
+
+const _importFunctionContinuation = (builder, section, nextBuilder) => {
+    return (module, field, type) => {
+        assert.isString(module, `Import function module should be a string, got "${module}"`);
+        assert.isString(field, `Import function field should be a string, got "${field}"`);
+        const typeSection = builder._getSection("Type");
+        type = _maybeRegisterType(builder, type);
+        section.data.push({ field: field, type: type, kind: "Function", module: module });
+        // Imports also count in the function index space. Map them as objects to avoid clashing with Code functions' names.
+        builder._registerFunctionToIndexSpace({ module: module, field: field });
+        return nextBuilder;
+    };
+};
+
+const _exportFunctionContinuation = (builder, section, nextBuilder) => {
+    return (field, index, type) => {
+        assert.isString(field, `Export function field should be a string, got "${field}"`);
+        const typeSection = builder._getSection("Type");
+        if (typeof(type) !== "undefined") {
+            // Exports can leave the type unspecified, letting the Code builder patch them up later.
+            type = _maybeRegisterType(builder, type);
+        }
+        // We can't check much about "index" here because the Code section succeeds the Export section. More work is done at Code().End() time.
+        switch (typeof(index)) {
+        case "string": break; // Assume it's a function name which will be revealed in the Code section.
+        case "number": break; // Assume it's a number in the "function index space".
+        case "object":
+            // Re-exporting an import.
+            assert.hasObjectProperty(index, "module", `Re-exporting "${field}" from an import`);
+            assert.hasObjectProperty(index, "field", `Re-exporting "${field}" from an import`);
+            break;
+        case "undefined":
+            // Assume it's the same as the field (i.e. it's not being renamed).
+            index = field;
+            break;
+        default: throw new Error(`Export section's index must be a string or a number, got ${index}`);
+        }
+        const correspondingImport = builder._getFunctionFromIndexSpace(index);
+        const importSection = builder._getSection("Import");
+        if (typeof(index) === "object") {
+            // Re-exporting an import using its module+field name.
+            assert.isNotUndef(correspondingImport, `Re-exporting "${field}" couldn't find import from module "${index.module}" field "${index.field}"`);
+            index = correspondingImport;
+            if (typeof(type) === "undefined")
+                type = importSection.data[index].type;
+            if (builder._checked)
+                assert.eq(type, importSection.data[index].type, `Re-exporting import "${importSection.data[index].field}" as "${field}" has mismatching type`);
+        } else if (typeof(correspondingImport) !== "undefined") {
+            // Re-exporting an import using its index.
+            let exportedImport;
+            for (const i of importSection.data) {
+                if (i.module === correspondingImport.module && i.field === correspondingImport.field) {
+                    exportedImport = i;
+                    break;
+                }
+            }
+            if (typeof(type) === "undefined")
+                type = exportedImport.type;
+            if (builder._checked)
+                assert.eq(type, exportedImport.type, `Re-exporting import "${exportedImport.field}" as "${field}" has mismatching type`);
+        }
+        section.data.push({ field: field, type: type, kind: "Function", index: index });
+        return nextBuilder;
+    };
 };
 
 export default class Builder {
@@ -98,6 +162,8 @@ export default class Builder {
             preamble[p.name] = p.value;
         this.setPreamble(preamble);
         this._sections = [];
+        this._functionIndexSpace = {};
+        this._functionIndexSpaceCount = 0;
         this._registerSectionBuilders();
     }
     setChecked(checked) {
@@ -108,25 +174,119 @@ export default class Builder {
         this._preamble = Object.assign(this._preamble || {}, p);
         return this;
     }
+    _registerFunctionToIndexSpace(name) {
+        if (typeof(name) === "undefined") {
+            // Registering a nameless function still adds it to the function index space. Register it as something that can't normally be registered.
+            name = {};
+        }
+        // Collisions are fine: we'll simply count the function and forget the previous one.
+        this._functionIndexSpace[name] = this._functionIndexSpaceCount++;
+        // Map it both ways, the number space is distinct from the name space.
+        this._functionIndexSpace[this._functionIndexSpace[name]] = name;
+    }
+    _getFunctionFromIndexSpace(name) {
+        return this._functionIndexSpace[name];
+    }
     _registerSectionBuilders() {
         for (const section in WASM.description.section) {
             switch (section) {
+            case "Type":
+                this[section] = function() {
+                    const s = this._addSection(section);
+                    const builder = this;
+                    const typeBuilder = {
+                        End: () => builder,
+                        Func: (params, ret) => {
+                            [params, ret] = _normalizeFunctionSignature(params, ret);
+                            s.data.push({ params: params, ret: ret });
+                            return typeBuilder;
+                        },
+                    };
+                    return typeBuilder;
+                };
+                break;
+            case "Import":
+                this[section] = function() {
+                    const s = this._addSection(section);
+                    const importBuilder = {
+                        End: () => this,
+                        Table: () => { throw new Error(`Unimplemented: import table`); },
+                        Memory: () => { throw new Error(`Unimplemented: import memory`); },
+                        Global: () => { throw new Error(`Unimplemented: import global`); },
+                    };
+                    importBuilder.Function = _importFunctionContinuation(this, s, importBuilder);
+                    return importBuilder;
+                };
+                break;
+            case "Export":
+                this[section] = function() {
+                    const s = this._addSection(section);
+                    const exportBuilder = {
+                        End: () => this,
+                        Table: () => { throw new Error(`Unimplemented: export table`); },
+                        Memory: () => { throw new Error(`Unimplemented: export memory`); },
+                        Global: () => { throw new Error(`Unimplemented: export global`); },
+                    };
+                    exportBuilder.Function = _exportFunctionContinuation(this, s, exportBuilder);
+                    return exportBuilder;
+                };
+                break;
             case "Code":
                 this[section] = function() {
                     const s = this._addSection(section);
                     const builder = this;
                     const codeBuilder =  {
-                        End: () => builder,
-                        Function: parameters => {
-                            parameters = parameters || [];
-                            const invalidParameterTypes = parameters.filter(p => !WASM.isValidValueType(p));
-                            if (invalidParameterTypes.length !== 0) throw new Error(`Function declared with parameters [${parameters}], invalid: [${invalidParameterTypes}]`);
+                        End: () => {
+                            // We now have enough information to remap the export section's "type" and "index" according to the Code section we're currently ending.
+                            const typeSection = builder._getSection("Type");
+                            const importSection = builder._getSection("Import");
+                            const exportSection = builder._getSection("Export");
+                            const codeSection = s;
+                            if (exportSection) {
+                                for (const e of exportSection.data) {
+                                    switch (typeof(e.index)) {
+                                    default: throw new Error(`Unexpected export index "${e.index}"`);
+                                    case "string": {
+                                        const index = builder._getFunctionFromIndexSpace(e.index);
+                                        assert.isNumber(index, `Export section contains undefined function "${e.index}"`);
+                                        e.index = index;
+                                    } // Fallthrough.
+                                    case "number": {
+                                        const index = builder._getFunctionFromIndexSpace(e.index);
+                                        if (builder._checked)
+                                            assert.isNotUndef(index, `Export "${e.field}" doesn't correspond to a defined value in the function index space`);
+                                    } break;
+                                    case "undefined":
+                                        throw new Error(`Unimplemented: Function().End() with undefined export index`); // FIXME
+                                    }
+                                    if (typeof(e.type) === "undefined") {
+                                        // This must be a function export from the Code section (re-exports were handled earlier).
+                                        const functionIndexSpaceOffset = importSection ? importSection.data.length : 0;
+                                        const functionIndex = e.index - functionIndexSpaceOffset;
+                                        e.type = codeSection.data[functionIndex].type;
+                                    }
+                                }
+                            }
+                            return builder;
+                        },
+                        Function: (a0, a1) => {
+                            let signature = typeof(a0) === "string" ? a1 : a0;
+                            const functionName = typeof(a0) === "string" ? a0 : undefined;
+                            if (typeof(signature) === "undefined")
+                                signature = { params: [] };
+                            assert.hasObjectProperty(signature, "params", `Expect function signature to be an object with a "params" field, got "${signature}"`);
+                            const [params, ret] = _normalizeFunctionSignature(signature.params, signature.ret);
+                            signature = { params: params, ret: ret };
                             const func = {
-                                locals: parameters, // Parameters are the first locals.
-                                parameterCount: parameters.length,
+                                name: functionName,
+                                type: _maybeRegisterType(builder, signature),
+                                signature: signature,
+                                locals: params, // Parameters are the first locals.
+                                parameterCount: params.length,
                                 code: []
                             };
                             s.data.push(func);
+                            builder._registerFunctionToIndexSpace(functionName);
                             let functionBuilder = {};
                             for (const op in WASM.description.opcode) {
                                 const name = _toJavaScriptName(op);
@@ -174,19 +334,21 @@ export default class Builder {
                                     }
                                 } : () => {};
                                 const checkImms = builder._checked ? (op, imms) => {
-                                    if (imms.length != imm.length) throw new Error(`"${op}" expects ${imm.length} immediates, got ${imms.length}`);
+                                    assert.eq(imms.length, imm.length, `"${op}" expects ${imm.length} immediates, got ${imms.length}`);
                                     for (let idx = 0; idx !== imm.length; ++idx) {
                                         const got = imms[idx];
                                         const expect = imm[idx];
                                         switch (expect.name) {
                                         case "function_index":
-                                            if (!_isValidValue(got, "i32")) throw new Error(`Invalid value on ${op}: got "${got}", expected i32`);
+                                            assert.truthy(_isValidValue(got, "i32"), `Invalid value on ${op}: got "${got}", expected i32`);
                                             // FIXME check function indices. https://bugs.webkit.org/show_bug.cgi?id=163421
                                             break;
                                         case "local_index": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
                                         case "global_index": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
                                         case "type_index": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
-                                        case "value": if (!_isValidValue(got, ret[0])) throw new Error(`Invalid value on ${op}: got "${got}", expected ${ret[0]}`); break;
+                                        case "value":
+                                            assert.truthy(_isValidValue(got, ret[0]), `Invalid value on ${op}: got "${got}", expected ${ret[0]}`);
+                                            break;
                                         case "flags": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
                                         case "offset": throw new Error(`Unimplemented: "${expect.name}" on "${op}"`);
                                         // Control:
@@ -226,7 +388,11 @@ export default class Builder {
             const builder = this;
             const unknownBuilder =  {
                 End: () => builder,
-                Byte: b => { if ((b & 0xFF) !== b) throw new RangeError(`Unknown section expected byte, got: "${b}"`); s.data.push(b); return unknownBuilder; }
+                Byte: b => {
+                    assert.eq(b & 0xFF, b, `Unknown section expected byte, got: "${b}"`);
+                    s.data.push(b);
+                    return unknownBuilder;
+                }
             };
             return unknownBuilder;
         };
@@ -234,10 +400,39 @@ export default class Builder {
     _addSection(nameOrNumber, extraObject) {
         const name = typeof(nameOrNumber) === "string" ? nameOrNumber : "";
         const number = typeof(nameOrNumber) === "number" ? nameOrNumber : (WASM.description.section[name] ? WASM.description.section[name].value : _unknownSectionId);
+        if (this._checked) {
+            // Check uniqueness.
+            for (const s of this._sections)
+                assert.falsy(s.name === name && s.id === number, `Cannot have two sections with the same name "${name}" and ID ${number}`);
+            // Check ordering.
+            if ((number !== _unknownSectionId) && (this._sections.length !== 0)) {
+                for (let i = this._sections.length - 1; i >= 0; --i) {
+                    if (this._sections[i].id === _unknownSectionId)
+                        continue;
+                    assert.le(this._sections[i].id, number, `Bad section ordering: "${this._sections[i].name}" cannot precede "${name}"`);
+                    break;
+                }
+            }
+        }
         const s = Object.assign({ name: name, id: number, data: [] }, extraObject || {});
         this._sections.push(s);
         return s;
     }
+    _getSection(nameOrNumber) {
+        switch (typeof(nameOrNumber)) {
+        default: throw new Error(`Implementation problem: can't get section "${nameOrNumber}"`);
+        case "string":
+            for (const s of this._sections)
+                if (s.name === nameOrNumber)
+                    return s;
+            return undefined;
+        case "number":
+            for (const s of this._sections)
+                if (s.id === nameOrNumber)
+                    return s;
+            return undefined;
+        }
+    }
     optimize() {
         // FIXME Add more optimizations. https://bugs.webkit.org/show_bug.cgi?id=163424
         return this;
@@ -254,5 +449,5 @@ export default class Builder {
         // FIXME Create an asm.js equivalent string which can be eval'd. https://bugs.webkit.org/show_bug.cgi?id=163425
         throw new Error("asm.js not implemented yet");
     }
-    WebAssembly() { return _BuildWebAssemblyBinary(this._preamble, this._sections); }
+    WebAssembly() { return BuildWebAssembly.Binary(this._preamble, this._sections); }
 };
diff --git a/JSTests/wasm/Builder_WebAssemblyBinary.js b/JSTests/wasm/Builder_WebAssemblyBinary.js
new file mode 100644 (file)
index 0000000..f2b0a52
--- /dev/null
@@ -0,0 +1,107 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+import LowLevelBinary from 'LowLevelBinary.js';
+import * as WASM from 'WASM.js';
+
+const put = (bin, type, value) => bin[type](value);
+
+const emitters = {
+    Type: (section, bin) => {
+        put(bin, "varuint", section.data.length);
+        for (const entry of section.data) {
+            const funcTypeConstructor = -0x20; // FIXME Move this to wasm.json.
+            put(bin, "varint7", funcTypeConstructor);
+            put(bin, "varuint", entry.params.length);
+            for (const param of entry.params)
+                put(bin, "uint8", WASM.valueTypeValue[param]);
+            if (entry.ret === "void")
+                put(bin, "varuint1", 0);
+            else {
+                put(bin, "varuint1", 1);
+                put(bin, "uint8", WASM.valueTypeValue[entry.ret]);
+            }
+        }
+    },
+    Import: (section, bin) => {
+        put(bin, "varuint", section.data.length);
+        for (const entry of section.data) {
+            put(bin, "string", entry.module);
+            put(bin, "string", entry.field);
+            put(bin, "uint8", WASM.externalKindValue[entry.kind]);
+            switch (entry.kind) {
+            default: throw new Error(`Implementation problem: unexpected kind ${entry.kind}`);
+            case "Function": put(bin, "varuint", entry.type); break;
+            case "Table": throw new Error(`Not yet implemented`);
+            case "Memory": throw new Error(`Not yet implemented`);
+            case "Global": throw new Error(`Not yet implemented`);
+            }
+        }
+    },
+    Function: (section, bin) => { throw new Error(`Not yet implemented`); },
+    Table: (section, bin) => { throw new Error(`Not yet implemented`); },
+    Memory: (section, bin) => { throw new Error(`Not yet implemented`); },
+    Global: (section, bin) => { throw new Error(`Not yet implemented`); },
+    Export: (section, bin) => { throw new Error(`Not yet implemented`); },
+    Start: (section, bin) => { throw new Error(`Not yet implemented`); },
+    Element: (section, bin) => { throw new Error(`Not yet implemented`); },
+    Code: (section, bin) => {
+        put(bin, "varuint", section.data.length);
+        for (const func of section.data) {
+            let funcBin = bin.newPatchable("varuint");
+            const localCount = func.locals.length;
+            put(funcBin, "varuint", localCount);
+            if (localCount !== 0) throw new Error(`Unimplemented: locals`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
+            for (const op of func.code) {
+                put(funcBin, "uint8", op.value);
+                if (op.arguments.length !== 0) throw new Error(`Unimplemented: arguments`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
+                if (op.immediates.length !== 0) throw new Error(`Unimplemented: immediates`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706
+            }
+            funcBin.apply();
+        }
+    },
+    Data: (section, bin) => { throw new Error(`Not yet implemented`); },
+};
+
+export const Binary = (preamble, sections) => {
+    let wasmBin = new LowLevelBinary();
+    for (const p of WASM.description.preamble)
+        put(wasmBin, p.type, preamble[p.name]);
+    for (const section of sections) {
+        put(wasmBin, WASM.sectionEncodingType, section.id);
+        let sectionBin = wasmBin.newPatchable("varuint");
+        const emitter = emitters[section.name];
+        if (emitter)
+            emitter(section, sectionBin);
+        else {
+            // Unknown section.
+            put(sectionBin, "string", section.name);
+            for (const byte of section.data)
+                put(sectionBin, "uint8", byte);
+        }
+        sectionBin.apply();
+    }
+    return wasmBin;
+};
index 0a833c1..7200976 100644 (file)
@@ -27,6 +27,8 @@ const _initialAllocationSize = 1024;
 const _growAllocationSize = allocated => allocated * 2;
 
 export const varuintMin = 0;
+export const varint7Min = -0b1000000;
+export const varint7Max = 0b111111;
 export const varuint7Max = 0b1111111;
 export const varuintMax = ((((1 << 31) >>> 0) - 1) * 2) + 1;
 export const varintMin = -((1 << 31) >>> 0);
@@ -129,6 +131,16 @@ export default class LowLevelBinary {
             this.uint8(0x80 | b);
         } while (true);
     }
+    varuint1(v) {
+        if (v !== 0 && v !== 1)
+            throw new RangeError(`Invalid varuint1 ${v} range is [0, 1]`);
+        this.varuint(v);
+    }
+    varint7(v) {
+        if (v < varint7Min || varint7Max < v)
+            throw new RangeError(`Invalid varint7 ${v} range is [${varint7Min}, ${varint7Max}]`);
+        this.varint(v);
+    }
     varuint7(v) {
         if (v < varuintMin || varuint7Max < v)
             throw new RangeError(`Invalid varuint7 ${v} range is [${varuintMin}, ${varuint7Max}]`);
index 8e7b7ef..d8eaef6 100644 (file)
 
 import * as utilities from 'utilities.js';
 
+const _mapValues = from => {
+    let values = {};
+    for (const key in from)
+        values[key] = from[key].value;
+    return values;
+};
+
 export const description = utilities.json("wasm.json");
 export const valueType = Object.keys(description.value_type);
 const _valueTypeSet = new Set(valueType);
 export const isValidValueType = v => _valueTypeSet.has(v);
+export const valueTypeValue = _mapValues(description.value_type);
+export const externalKindValue = _mapValues(description.external_kind);
 export const sections = Object.keys(description.section);
 export const sectionEncodingType = description.section[sections[0]].type;
index 2ea5f70..02c5a7a 100644 (file)
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-const _notUndef = (v) => {
-    if (typeof v === "undefined")
-        throw new Error("Shouldn't be undefined");
+const _fail = (msg, extra) => {
+    throw new Error(msg + (extra ? ": " + extra : ""));
 };
 
-const _isUndef = (v) => {
-    if (typeof v !== "undefined")
-        throw new Error("Should be undefined");
+export const isNotA = (v, t, msg) => {
+    if (typeof v === t)
+        _fail(`Shouldn't be ${t}`, msg);
 };
 
-const _eq = (lhs, rhs) => {
-    if (lhs !== rhs)
-        throw new Error(`Not the same: "${lhs}" and "${rhs}"`);
+export const isA = (v, t, msg) => {
+    if (typeof v !== t)
+        _fail(`Should be ${t}, got ${typeof(v)}`, msg);
 };
 
-const _ge = (lhs, rhs) => {
-    _notUndef(lhs);
-    _notUndef(rhs);
+export const isNotUndef = (v, msg) => isNotA(v, "undefined", msg);
+export const isUndef = (v, msg) => isA(v, "undefined", msg);
+export const notObject = (v, msg) => isNotA(v, "object", msg);
+export const isObject = (v, msg) => isA(v, "object", msg);
+export const notString = (v, msg) => isNotA(v, "string", msg);
+export const isString = (v, msg) => isA(v, "string", msg);
+export const notNumber = (v, msg) => isNotA(v, "number", msg);
+export const isNumber = (v, msg) => isA(v, "number", msg);
+
+export const hasObjectProperty = (o, p, msg) => {
+    isObject(o, msg);
+    isNotUndef(o[p], msg, `expected object to have property ${p}`);
+};
+
+export const isArray = (v, msg) => {
+    if (!Array.isArray(v))
+        _fail(`Expected an array, got ${typeof(v)}`, msg);
+};
+
+export const isNotArray = (v, msg) => {
+    if (Array.isArray(v))
+        _fail(`Expected to not be an array`, msg);
+};
+
+export const truthy = (v, msg) => {
+    if (!v)
+        _fail(`Expected truthy`, msg);
+};
+
+export const falsy = (v, msg) => {
+    if (v)
+        _fail(`Expected falsy`, msg);
+};
+
+export const eq = (lhs, rhs, msg) => {
+    if (Array.isArray(lhs) && Array.isArray(rhs) && (lhs.length === rhs.length)) {
+        for (let i = 0; i !== lhs.length; ++i)
+            eq(lhs[i], rhs[i], msg);
+    } else if (lhs !== rhs)
+        _fail(`Not the same: "${lhs}" and "${rhs}"`, msg);
+};
+
+export const ge = (lhs, rhs, msg) => {
+    isNotUndef(lhs);
+    isNotUndef(rhs);
     if (!(lhs >= rhs))
-        throw new Error(`Expected: "${lhs}" < "${rhs}"`);
+        _fail(`Expected: "${lhs}" < "${rhs}"`, msg);
+};
+
+export const le = (lhs, rhs, msg) => {
+    isNotUndef(lhs);
+    isNotUndef(rhs);
+    if (!(lhs <= rhs))
+        _fail(`Expected: "${lhs}" > "${rhs}"`, msg);
 };
 
 const _throws = (func, type, message, ...args) => {
@@ -51,19 +99,18 @@ const _throws = (func, type, message, ...args) => {
     } catch (e) {
         if (e instanceof type && e.message === message)
             return;
-        throw new Error(`Expected to throw a ${type.name} with message "${message}", got ${e.name} with message "${e.message}"`);
+        _fail(`Expected to throw a ${type.name} with message "${message}", got ${e.name} with message "${e.message}"`);
     }
-    throw new Error(`Expected to throw a ${type.name} with message "${message}"`);
+    _fail(`Expected to throw a ${type.name} with message "${message}"`);
 };
 
-const _instanceof = (obj, type) => obj instanceof type;
+const _instanceof = (obj, type, msg) => {
+    if (!(obj instanceof type))
+        _fail(`Expected a ${typeof(type)}, got ${typeof obj}`);
+};
 
 // Use underscore names to avoid clashing with builtin names.
 export {
-    _notUndef as notUndef,
-    _isUndef as isUndef,
-    _eq as eq,
-    _ge as ge,
     _throws as throws,
     _instanceof as instanceof,
 };
index 821da84..94db1a4 100644 (file)
@@ -7,3 +7,13 @@ import Builder from '../Builder.js';
     const module = new WebAssembly.Module(bin);
     assert.instanceof(module, WebAssembly.Module);
 })();
+
+(function ModuleWithImports() {
+    const builder = (new Builder())
+        .Type().End()
+        .Import()
+            .Function("foo", "bar", { params: [] })
+        .End();
+    const bin = builder.WebAssembly().get();
+    const module = new WebAssembly.Module(bin);
+})();
index 054eaef..80ece29 100644 (file)
@@ -28,7 +28,7 @@ const constructorProperties = {
 };
 
 
-assert.notUndef(WebAssembly);
+assert.isNotUndef(WebAssembly);
 checkOwnPropertyDescriptor(utilities.global, "WebAssembly", { typeofvalue: "object", writable: true, configurable: true, enumerable: false });
 assert.eq(String(WebAssembly), "[object WebAssembly]");
 assert.isUndef(WebAssembly.length);
@@ -37,13 +37,13 @@ assert.throws(() => WebAssembly(), TypeError, `WebAssembly is not a function. (I
 assert.throws(() => new WebAssembly(), TypeError, `WebAssembly is not a constructor (evaluating 'new WebAssembly()')`);
 
 for (const f in functionProperties) {
-    assert.notUndef(WebAssembly[f]);
+    assert.isNotUndef(WebAssembly[f]);
     assert.eq(WebAssembly[f].name, f);
     assert.eq(WebAssembly[f].length, functionProperties[f].length);
 }
 
 for (const c in constructorProperties) {
-    assert.notUndef(WebAssembly[c]);
+    assert.isNotUndef(WebAssembly[c]);
     assert.eq(WebAssembly[c].name, c);
     assert.eq(WebAssembly[c].length, constructorProperties[c].length);
     checkOwnPropertyDescriptor(WebAssembly, c, constructorProperties[c]);
@@ -66,7 +66,7 @@ for (const c in constructorProperties) {
         assert.instanceof(instance, WebAssembly.Instance);
         for (const invalid of invalidInstanceImports)
             assert.throws(() => new WebAssembly[c](new WebAssembly.Module(emptyModuleArray), invalid), TypeError, `second argument to WebAssembly.Instance must be undefined or an Object (evaluating 'new WebAssembly[c](new WebAssembly.Module(emptyModuleArray), invalid)')`);
-        assert.notUndef(instance.exports);
+        assert.isNotUndef(instance.exports);
         checkOwnPropertyDescriptor(instance, "exports", { typeofvalue: "object", writable: true, configurable: true, enumerable: true });
         assert.isUndef(instance.exports.__proto__);
         assert.eq(Reflect.isExtensible(instance.exports), false);
index 0934231..eec3be9 100644 (file)
@@ -2,17 +2,17 @@ import * as assert from '../assert.js';
 import Builder from '../Builder.js';
 
 const assertOpThrows = (opFn, message) => {
-    let f = (new Builder()).Code().Function();
+    let f = (new Builder()).Type().End().Code().Function();
     assert.throws(opFn, Error, message, f);
 };
 
 (function EmptyModule() {
     const b = new Builder();
     const j = JSON.parse(b.json());
-    assert.notUndef(j.preamble);
-    assert.notUndef(j.preamble["magic number"]);
-    assert.notUndef(j.preamble.version);
-    assert.notUndef(j.section);
+    assert.isNotUndef(j.preamble);
+    assert.isNotUndef(j.preamble["magic number"]);
+    assert.isNotUndef(j.preamble.version);
+    assert.isNotUndef(j.section);
     assert.eq(j.section.length, 0);
 })();
 
@@ -20,14 +20,14 @@ const assertOpThrows = (opFn, message) => {
     const b = (new Builder()).setPreamble({ "magic number": 1337 });
     const j = JSON.parse(b.json());
     assert.eq(j.preamble["magic number"], 1337);
-    assert.notUndef(j.preamble.version);
+    assert.isNotUndef(j.preamble.version);
 })();
 
 (function CustomVersion() {
     const b = (new Builder()).setPreamble({ "version": 1337 });
     const j = JSON.parse(b.json());
     assert.eq(j.preamble["version"], 1337);
-    assert.notUndef(j.preamble.version);
+    assert.isNotUndef(j.preamble.version);
 })();
 
 (function CustomSection() {
@@ -57,15 +57,8 @@ const assertOpThrows = (opFn, message) => {
 })();
 
 (function CustomSectionInvalidByte() {
-    const b = new Builder();
-    let u = b.Unknown("custom section");
-    try {
-        u.Byte(0xFF + 1);
-    } catch (e) {
-        if (e instanceof RangeError) { return; }
-        throw new Error(`Expected a RangeError, got ${e}`);
-    }
-    throw new Error(`Expected to throw a RangeError`);
+    const u = (new Builder()).Unknown("custom section");
+    assert.throws(() => u.Byte(0xFF + 1), Error, `Not the same: "0" and "256": Unknown section expected byte, got: "256"`);
 })();
 
 (function TwoCustomSections() {
@@ -86,6 +79,168 @@ const assertOpThrows = (opFn, message) => {
     assert.eq(j.section[1].data[0], 0x11);
 })();
 
+(function SectionsWithSameCustomName() {
+    const b = (new Builder()).Unknown("foo").End();
+    assert.throws(() => b.Unknown("foo"), Error, `Expected falsy: Cannot have two sections with the same name "foo" and ID 0`);
+})();
+
+(function EmptyTypeSection() {
+    const b = (new Builder()).Type().End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section.length, 1);
+    assert.eq(j.section[0].name, "Type");
+    assert.eq(j.section[0].data.length, 0);
+})();
+
+(function TwoTypeSections() {
+    const b = (new Builder()).Type().End();
+    assert.throws(() => b.Type(), Error, `Expected falsy: Cannot have two sections with the same name "Type" and ID 1`);
+})();
+
+(function SimpleTypeSection() {
+    const b = (new Builder()).Type()
+            .Func([])
+            .Func([], "void")
+            .Func([], "i32")
+            .Func([], "i64")
+            .Func([], "f32")
+            .Func([], "f64")
+            .Func(["i32", "i64", "f32", "f64"])
+        .End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section[0].data.length, 7);
+    assert.eq(j.section[0].data[0].params, []);
+    assert.eq(j.section[0].data[0].ret, "void");
+    assert.eq(j.section[0].data[1].params, []);
+    assert.eq(j.section[0].data[1].ret, "void");
+    assert.eq(j.section[0].data[2].params, []);
+    assert.eq(j.section[0].data[2].ret, "i32");
+    assert.eq(j.section[0].data[3].params, []);
+    assert.eq(j.section[0].data[3].ret, "i64");
+    assert.eq(j.section[0].data[4].params, []);
+    assert.eq(j.section[0].data[4].ret, "f32");
+    assert.eq(j.section[0].data[5].params, []);
+    assert.eq(j.section[0].data[5].ret, "f64");
+    assert.eq(j.section[0].data[6].params, ["i32", "i64", "f32", "f64"]);
+    assert.eq(j.section[0].data[6].ret, "void");
+})();
+
+(function EmptyImportSection() {
+    const b = (new Builder()).Import().End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section.length, 1);
+    assert.eq(j.section[0].name, "Import");
+    assert.eq(j.section[0].data.length, 0);
+})();
+
+(function ImportBeforeTypeSections() {
+    const b = (new Builder()).Import().End();
+    assert.throws(() => b.Type(), Error, `Expected: "2" > "1": Bad section ordering: "Import" cannot precede "Type"`);
+})();
+
+(function ImportFunctionWithoutTypeSection() {
+    const i = (new Builder()).Import();
+    assert.throws(() => i.Function("foo", "bar", 0), Error, `Shouldn't be undefined: Can't use type 0 if a type section isn't present`);
+})();
+
+(function ImportFunctionWithInvalidType() {
+    const i = (new Builder()).Type().End().Import();
+    assert.throws(() => i.Function("foo", "bar", 0), Error, `Shouldn't be undefined: Type 0 doesn't exist in type section`);
+})();
+
+(function ImportFunction() {
+    const b = (new Builder())
+        .Type().Func([]).End()
+        .Import()
+            .Function("foo", "bar", 0)
+        .End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section[1].data.length, 1);
+    assert.eq(j.section[1].data[0].module, "foo");
+    assert.eq(j.section[1].data[0].field, "bar");
+    assert.eq(j.section[1].data[0].type, 0);
+    assert.eq(j.section[1].data[0].kind, "Function");
+})();
+
+(function ImportFunctionsWithExistingTypes() {
+    const b = (new Builder())
+        .Type()
+            .Func([])
+            .Func([], "i32")
+            .Func(["i64", "i32"])
+            .Func(["i64", "i64"])
+        .End()
+        .Import()
+            .Function("foo", "bar", { params: [] })
+            .Function("foo", "baz", { params: [], ret: "i32" })
+            .Function("foo", "boo", { params: ["i64", "i64"] })
+        .End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section[0].data.length, 4);
+    assert.eq(j.section[1].data.length, 3);
+    assert.eq(j.section[1].data[0].type, 0);
+    assert.eq(j.section[1].data[1].type, 1);
+    assert.eq(j.section[1].data[2].type, 3);
+})();
+
+(function ImportFunctionWithNewType() {
+    const b = (new Builder())
+        .Type().End()
+        .Import()
+            .Function("foo", "bar", { params: [] })
+            .Function("foo", "baz", { params: [], ret: "i32" })
+            .Function("foo", "boo", { params: ["i64", "i64"] })
+        .End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section[0].data.length, 3);
+    assert.eq(j.section[0].data[0].ret, "void");
+    assert.eq(j.section[0].data[0].params, []);
+    assert.eq(j.section[0].data[1].ret, "i32");
+    assert.eq(j.section[0].data[1].params, []);
+    assert.eq(j.section[0].data[2].ret, "void");
+    assert.eq(j.section[0].data[2].params, ["i64", "i64"]);
+})();
+
+(function EmptyExportSection() {
+    const b = (new Builder()).Export().End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section.length, 1);
+    assert.eq(j.section[0].name, "Export");
+    assert.eq(j.section[0].data.length, 0);
+})();
+
+(function ExportFunctionWithoutTypeSection() {
+    const e = (new Builder()).Export();
+    assert.throws(() => e.Function("foo", 0, 0), Error, `Shouldn't be undefined: Can't use type 0 if a type section isn't present`);
+})();
+
+(function ExportFunctionWithInvalidType() {
+    const e = (new Builder()).Type().End().Export();
+    assert.throws(() => e.Function("foo", 0, 0), Error, `Shouldn't be undefined: Type 0 doesn't exist in type section`);
+})();
+
+(function ExportAnImport() {
+    const b = (new Builder())
+        .Type().End()
+        .Import().Function("foo", "bar", { params: [] }).End()
+        .Export().Function("ExportAnImport", { module: "foo", field: "bar" }).End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section[2].name, "Export");
+    assert.eq(j.section[2].data.length, 1);
+    assert.eq(j.section[2].data[0].field, "ExportAnImport");
+    assert.eq(j.section[2].data[0].type, 0);
+    assert.eq(j.section[2].data[0].index, 0);
+    assert.eq(j.section[2].data[0].kind, "Function");
+})();
+
+(function ExportMismatchedImport() {
+    const e = (new Builder())
+        .Type().End()
+        .Import().Function("foo", "bar", { params: [] }).End()
+        .Export();
+    assert.throws(() => e.Function("foo", 0, { params: ["i32"] }), Error, `Not the same: "1" and "0": Re-exporting import "bar" as "foo" has mismatching type`);
+})();
+
 (function EmptyCodeSection() {
     const b = new Builder();
     b.Code();
@@ -97,199 +252,254 @@ const assertOpThrows = (opFn, message) => {
 
 (function CodeSectionWithEmptyFunction() {
     const b = new Builder();
-    b.Code()
-        .Function();
+    b.Type().End()
+        .Code()
+            .Function();
     const j = JSON.parse(b.json());
-    assert.eq(j.section.length, 1);
-    assert.eq(j.section[0].name, "Code");
+    assert.eq(j.section.length, 2);
+    assert.eq(j.section[0].name, "Type");
     assert.eq(j.section[0].data.length, 1);
-    assert.eq(j.section[0].data[0].parameterCount, 0);
-    assert.eq(j.section[0].data[0].locals.length, 0);
-    assert.eq(j.section[0].data[0].code.length, 0);
+    assert.eq(j.section[0].data[0].params, []);
+    assert.eq(j.section[0].data[0].ret, "void");
+    assert.eq(j.section[1].name, "Code");
+    assert.eq(j.section[1].data.length, 1);
+    assert.eq(j.section[1].data[0].name, undefined);
+    assert.eq(j.section[1].data[0].type, 0);
+    assert.eq(j.section[1].data[0].parameterCount, 0);
+    assert.eq(j.section[1].data[0].locals.length, 0);
+    assert.eq(j.section[1].data[0].code.length, 0);
 })();
 
 (function CodeSectionWithEmptyFunctionWithParameters() {
     const b = new Builder();
-    b.Code()
-        .Function(["i32", "i64", "f32", "f64"]);
+    b.Type().End()
+        .Code()
+            .Function({ params: ["i32", "i64", "f32", "f64"] });
     const j = JSON.parse(b.json());
-    assert.eq(j.section.length, 1);
-    assert.eq(j.section[0].name, "Code");
+    assert.eq(j.section.length, 2);
     assert.eq(j.section[0].data.length, 1);
-    assert.eq(j.section[0].data[0].parameterCount, 4);
-    assert.eq(j.section[0].data[0].locals[0], "i32");
-    assert.eq(j.section[0].data[0].locals[1], "i64");
-    assert.eq(j.section[0].data[0].locals[2], "f32");
-    assert.eq(j.section[0].data[0].locals[3], "f64");
-    assert.eq(j.section[0].data[0].code.length, 0);
+    assert.eq(j.section[0].data[0].params, ["i32", "i64", "f32", "f64"]);
+    assert.eq(j.section[0].data[0].ret, "void");
+    assert.eq(j.section[1].data.length, 1);
+    assert.eq(j.section[1].data[0].type, 0);
+    assert.eq(j.section[1].data[0].parameterCount, 4);
+    assert.eq(j.section[1].data[0].locals[0], "i32");
+    assert.eq(j.section[1].data[0].locals[1], "i64");
+    assert.eq(j.section[1].data[0].locals[2], "f32");
+    assert.eq(j.section[1].data[0].locals[3], "f64");
+    assert.eq(j.section[1].data[0].code.length, 0);
 })();
 
 (function InvalidFunctionParameters() {
-    for (let invalid in ["", "void", "bool", "any", "struct"]) {
+    for (let invalid of ["", "void", "bool", "any", "struct", 0, 3.14, undefined, [], {}]) {
         const c = (new Builder()).Code();
-        try {
-            c.Function([invalid]);
-        } catch (e) {
-            if (e instanceof Error) { continue; }
-            throw new Error(`Expected an Error, got ${e}`);
-        }
-        throw new Error(`Expected to throw an Error for ${invalid}`);
+        assert.throws(() => c.Function({ params: [invalid] }), Error, `Expected truthy: Type parameter ${invalid} needs a valid value type`);
     }
 })();
 
 (function SimpleFunction() {
     const b = new Builder();
-    b.Code()
-        .Function()
-            .Nop()
-            .Nop()
-        .End();
+    b.Type().End()
+        .Code()
+            .Function()
+                .Nop()
+                .Nop()
+            .End();
     const j = JSON.parse(b.json());
-    assert.eq(j.section[0].data.length, 1);
-    assert.eq(j.section[0].data[0].locals.length, 0);
-    assert.eq(j.section[0].data[0].code.length, 3);
-    assert.eq(j.section[0].data[0].code[0].name, "nop");
-    assert.eq(j.section[0].data[0].code[1].name, "nop");
-    assert.eq(j.section[0].data[0].code[2].name, "end");
+    assert.eq(j.section[1].data.length, 1);
+    assert.eq(j.section[1].data[0].locals.length, 0);
+    assert.eq(j.section[1].data[0].code.length, 3);
+    assert.eq(j.section[1].data[0].code[0].name, "nop");
+    assert.eq(j.section[1].data[0].code[1].name, "nop");
+    assert.eq(j.section[1].data[0].code[2].name, "end");
 })();
 
 (function TwoSimpleFunctions() {
     const b = new Builder();
-    b.Code()
-        .Function()
-            .Nop()
-            .Nop()
+    b.Type().End()
+        .Code()
+            .Function()
+                .Nop()
+                .Nop()
+            .End()
+            .Function()
+                .Return()
+            .End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section[1].data.length, 2);
+    assert.eq(j.section[1].data[0].locals.length, 0);
+    assert.eq(j.section[1].data[0].code.length, 3);
+    assert.eq(j.section[1].data[0].code[0].name, "nop");
+    assert.eq(j.section[1].data[0].code[1].name, "nop");
+    assert.eq(j.section[1].data[0].code[2].name, "end");
+    assert.eq(j.section[1].data[1].locals.length, 0);
+    assert.eq(j.section[1].data[1].code.length, 2);
+    assert.eq(j.section[1].data[1].code[0].name, "return");
+    assert.eq(j.section[1].data[1].code[1].name, "end");
+})();
+
+(function NamedFunctions() {
+    const b = new Builder().Type().End().Code()
+        .Function("hello").End()
+        .Function("world", { params: ["i32"] }).End()
+    .End();
+    const j = JSON.parse(b.json());
+    assert.eq(j.section[1].data[0].name, "hello");
+    assert.eq(j.section[1].data[0].parameterCount, 0);
+    assert.eq(j.section[1].data[1].name, "world");
+    assert.eq(j.section[1].data[1].parameterCount, 1);
+})();
+
+(function ExportSimpleFunctions() {
+    const b = (new Builder())
+        .Type().End()
+        .Export()
+            .Function("foo", 0, { params: [] })
+            .Function("bar")
+            .Function("betterNameForBar", "bar")
         .End()
-        .Function()
-            .Return()
+        .Code()
+            .Function({ params: [] }).Nop().End()
+            .Function("bar", { params: [] }).Nop().End()
         .End();
     const j = JSON.parse(b.json());
-    assert.eq(j.section[0].data.length, 2);
-    assert.eq(j.section[0].data[0].locals.length, 0);
-    assert.eq(j.section[0].data[0].code.length, 3);
-    assert.eq(j.section[0].data[0].code[0].name, "nop");
-    assert.eq(j.section[0].data[0].code[1].name, "nop");
-    assert.eq(j.section[0].data[0].code[2].name, "end");
-    assert.eq(j.section[0].data[1].locals.length, 0);
-    assert.eq(j.section[0].data[1].code.length, 2);
-    assert.eq(j.section[0].data[1].code[0].name, "return");
-    assert.eq(j.section[0].data[1].code[1].name, "end");
+    assert.eq(j.section[0].data.length, 1);
+    assert.eq(j.section[0].data[0].ret, "void");
+    assert.eq(j.section[0].data[0].params, []);
+    assert.eq(j.section[1].data.length, 3);
+    assert.eq(j.section[1].data[0].field, "foo");
+    assert.eq(j.section[1].data[0].type, 0);
+    assert.eq(j.section[1].data[0].index, 0);
+    assert.eq(j.section[1].data[0].kind, "Function");
+    assert.eq(j.section[1].data[1].field, "bar");
+    assert.eq(j.section[1].data[1].type, 0);
+    assert.eq(j.section[1].data[1].index, 1);
+    assert.eq(j.section[1].data[1].kind, "Function");
+    assert.eq(j.section[1].data[2].field, "betterNameForBar");
+    assert.eq(j.section[1].data[2].type, 0);
+    assert.eq(j.section[1].data[2].index, 1);
+    assert.eq(j.section[1].data[2].kind, "Function");
+})();
+
+(function ExportUndefinedFunction() {
+    const c = (new Builder()).Type().End().Export().Function("foo").End().Code();
+    assert.throws(() => c.End(), Error, `Should be number, got undefined: Export section contains undefined function "foo"`);
 })();
 
 (function TwoBuildersAtTheSameTime() {
     const b = [new Builder(), new Builder()];
-    const f = b.map(builder => builder.Code().Function());
+    const f = b.map(builder => builder.Type().End().Code().Function());
     f[0].Nop();
     f[1].Return().End().End();
     f[0].Nop().End().End();
     const j = b.map(builder => JSON.parse(builder.json()));
-    assert.eq(j[0].section[0].data[0].code.length, 3);
-    assert.eq(j[0].section[0].data[0].code[0].name, "nop");
-    assert.eq(j[0].section[0].data[0].code[1].name, "nop");
-    assert.eq(j[0].section[0].data[0].code[2].name, "end");
-    assert.eq(j[1].section[0].data[0].code.length, 2);
-    assert.eq(j[1].section[0].data[0].code[0].name, "return");
-    assert.eq(j[1].section[0].data[0].code[1].name, "end");
+    assert.eq(j[0].section[1].data[0].code.length, 3);
+    assert.eq(j[0].section[1].data[0].code[0].name, "nop");
+    assert.eq(j[0].section[1].data[0].code[1].name, "nop");
+    assert.eq(j[0].section[1].data[0].code[2].name, "end");
+    assert.eq(j[1].section[1].data[0].code.length, 2);
+    assert.eq(j[1].section[1].data[0].code[0].name, "return");
+    assert.eq(j[1].section[1].data[0].code[1].name, "end");
 })();
 
 (function CheckedOpcodeArgumentsTooMany() {
-    assertOpThrows(f => f.Nop("uh-oh!"), `"nop" expects 0 immediates, got 1`);
+    assertOpThrows(f => f.Nop("uh-oh!"), `Not the same: "1" and "0": "nop" expects 0 immediates, got 1`);
 })();
 
 (function UncheckedOpcodeArgumentsTooMany() {
-    (new Builder()).setChecked(false).Code().Function().Nop("This is fine.", "I'm OK with the events that are unfolding currently.");
+    (new Builder()).setChecked(false).Type().End().Code().Function().Nop("This is fine.", "I'm OK with the events that are unfolding currently.");
 })();
 
 (function CheckedOpcodeArgumentsNotEnough() {
-    assertOpThrows(f => f.I32Const(), `"i32.const" expects 1 immediates, got 0`);
+    assertOpThrows(f => f.I32Const(), `Not the same: "0" and "1": "i32.const" expects 1 immediates, got 0`);
 })();
 
 (function UncheckedOpcodeArgumentsNotEnough() {
-    (new Builder()).setChecked(false).Code().Function().I32Const();
+    (new Builder()).setChecked(false).Type().End().Code().Function().I32Const();
 })();
 
 (function CallNoArguments() {
-    const b = (new Builder()).Code().Function().Call(0).End().End();
+    const b = (new Builder()).Type().End().Code().Function().Call(0).End().End();
     const j = JSON.parse(b.json());
-    assert.eq(j.section[0].data[0].code.length, 2);
-    assert.eq(j.section[0].data[0].code[0].name, "call");
-    assert.eq(j.section[0].data[0].code[0].arguments.length, 0);
-    assert.eq(j.section[0].data[0].code[0].immediates.length, 1);
-    assert.eq(j.section[0].data[0].code[0].immediates[0], 0);
-    assert.eq(j.section[0].data[0].code[1].name, "end");
+    assert.eq(j.section[1].data[0].code.length, 2);
+    assert.eq(j.section[1].data[0].code[0].name, "call");
+    assert.eq(j.section[1].data[0].code[0].arguments.length, 0);
+    assert.eq(j.section[1].data[0].code[0].immediates.length, 1);
+    assert.eq(j.section[1].data[0].code[0].immediates[0], 0);
+    assert.eq(j.section[1].data[0].code[1].name, "end");
 })();
 
 (function CallInvalid() {
     for (let c of [-1, 0x100000000, "0", {}, Infinity, -Infinity, NaN, -NaN, null])
-        assertOpThrows(f => f.Call(c), `Invalid value on call: got "${c}", expected i32`);
+        assertOpThrows(f => f.Call(c), `Expected truthy: Invalid value on call: got "${c}", expected i32`);
 })();
 
 (function I32ConstValid() {
     for (let c of [0, 1, 2, 42, 1337, 0xFF, 0xFFFF, 0x7FFFFFFF, 0xFFFFFFFE, 0xFFFFFFFF]) {
-        const b = (new Builder()).Code().Function().I32Const(c).Return().End().End();
+        const b = (new Builder()).Type().End().Code().Function().I32Const(c).Return().End().End();
         const j = JSON.parse(b.json());
-        assert.eq(j.section[0].data[0].code[0].name, "i32.const");
-        assert.eq(j.section[0].data[0].code[0].arguments.length, 0);
-        assert.eq(j.section[0].data[0].code[0].immediates.length, 1);
-        assert.eq(j.section[0].data[0].code[0].immediates[0], c);
+        assert.eq(j.section[1].data[0].code[0].name, "i32.const");
+        assert.eq(j.section[1].data[0].code[0].arguments.length, 0);
+        assert.eq(j.section[1].data[0].code[0].immediates.length, 1);
+        assert.eq(j.section[1].data[0].code[0].immediates[0], c);
     }
 })();
 
 (function I32ConstInvalid() {
     for (let c of [-1, 0x100000000, 0.1, -0.1, "0", {}, Infinity, null])
-        assertOpThrows(f => f.I32Const(c), `Invalid value on i32.const: got "${c}", expected i32`);
+        assertOpThrows(f => f.I32Const(c), `Expected truthy: Invalid value on i32.const: got "${c}", expected i32`);
 })();
 
 // FIXME: test i64. https://bugs.webkit.org/show_bug.cgi?id=163420
 
 (function F32ConstValid() {
     for (let c of [0, -0., 0.2, Math.PI, 0x100000000]) {
-        const b = (new Builder()).Code().Function().F32Const(c).Return().End().End();
+        const b = (new Builder()).Type().End().Code().Function().F32Const(c).Return().End().End();
         const j = JSON.parse(b.json());
-        assert.eq(j.section[0].data[0].code[0].name, "f32.const");
-        assert.eq(j.section[0].data[0].code[0].arguments.length, 0);
-        assert.eq(j.section[0].data[0].code[0].immediates.length, 1);
-        assert.eq(j.section[0].data[0].code[0].immediates[0], c);
+        assert.eq(j.section[1].data[0].code[0].name, "f32.const");
+        assert.eq(j.section[1].data[0].code[0].arguments.length, 0);
+        assert.eq(j.section[1].data[0].code[0].immediates.length, 1);
+        assert.eq(j.section[1].data[0].code[0].immediates[0], c);
     }
 })();
 
 (function F32ConstInvalid() {
     for (let c of ["0", {}, Infinity, -Infinity, NaN, -NaN, null])
-        assertOpThrows(f => f.F32Const(c), `Invalid value on f32.const: got "${c}", expected f32`);
+        assertOpThrows(f => f.F32Const(c), `Expected truthy: Invalid value on f32.const: got "${c}", expected f32`);
 })();
 
 (function F64ConstValid() {
     for (let c of [0, -0., 0.2, Math.PI, 0x100000000]) {
-        const b = (new Builder()).Code().Function().F64Const(c).Return().End().End();
+        const b = (new Builder()).Type().End().Code().Function().F64Const(c).Return().End().End();
         const j = JSON.parse(b.json());
-        assert.eq(j.section[0].data[0].code[0].name, "f64.const");
-        assert.eq(j.section[0].data[0].code[0].arguments.length, 0);
-        assert.eq(j.section[0].data[0].code[0].immediates.length, 1);
-        assert.eq(j.section[0].data[0].code[0].immediates[0], c);
+        assert.eq(j.section[1].data[0].code[0].name, "f64.const");
+        assert.eq(j.section[1].data[0].code[0].arguments.length, 0);
+        assert.eq(j.section[1].data[0].code[0].immediates.length, 1);
+        assert.eq(j.section[1].data[0].code[0].immediates[0], c);
     }
 })();
 
 (function F64ConstInvalid() {
     for (let c of ["0", {}, Infinity, -Infinity, NaN, -NaN, null])
-        assertOpThrows(f => f.F64Const(c), `Invalid value on f64.const: got "${c}", expected f64`);
+        assertOpThrows(f => f.F64Const(c), `Expected truthy: Invalid value on f64.const: got "${c}", expected f64`);
 })();
 
 (function CallOneFromStack() {
-    const b = (new Builder()).Code()
-        .Function(["i32"])
+    const b = (new Builder()).Type().End().Code()
+        .Function({ params: ["i32"] })
             .I32Const(42)
             .Call(0)
         .End()
     .End();
     const j = JSON.parse(b.json());
-    assert.eq(j.section[0].data[0].code.length, 3);
-    assert.eq(j.section[0].data[0].code[0].name, "i32.const");
-    assert.eq(j.section[0].data[0].code[0].immediates[0], 42);
-    assert.eq(j.section[0].data[0].code[1].name, "call");
-    // FIXME: assert.eq(j.section[0].data[0].code[1].arguments.length, 1); https://bugs.webkit.org/show_bug.cgi?id=163267
-    assert.eq(j.section[0].data[0].code[1].immediates.length, 1);
-    assert.eq(j.section[0].data[0].code[1].immediates[0], 0);
-    assert.eq(j.section[0].data[0].code[2].name, "end");
+    assert.eq(j.section[1].data[0].code.length, 3);
+    assert.eq(j.section[1].data[0].code[0].name, "i32.const");
+    assert.eq(j.section[1].data[0].code[0].immediates[0], 42);
+    assert.eq(j.section[1].data[0].code[1].name, "call");
+    // FIXME: assert.eq(j.section[1].data[0].code[1].arguments.length, 1); https://bugs.webkit.org/show_bug.cgi?id=163267
+    assert.eq(j.section[1].data[0].code[1].immediates.length, 1);
+    assert.eq(j.section[1].data[0].code[1].immediates[0], 0);
+    assert.eq(j.section[1].data[0].code[2].name, "end");
 })();
 
 // FIXME https://bugs.webkit.org/show_bug.cgi?id=163267 all of these:
@@ -301,22 +511,22 @@ const assertOpThrows = (opFn, message) => {
 //  test function names (both setting and calling them).
 
 (function CallManyFromStack() {
-    const b = (new Builder()).Code()
-          .Function(["i32", "i32", "i32", "i32"])
+    const b = (new Builder()).Type().End().Code()
+        .Function({ params: ["i32", "i32", "i32", "i32"] })
               .I32Const(42).I32Const(1337).I32Const(0xBEEF).I32Const(0xFFFF)
               .Call(0)
         .End()
     .End();
     const j = JSON.parse(b.json());
-    assert.eq(j.section[0].data[0].code.length, 6);
-    assert.eq(j.section[0].data[0].code[4].name, "call");
-    // FIXME: assert.eq(j.section[0].data[0].code[4].arguments.length, 4); https://bugs.webkit.org/show_bug.cgi?id=163267
-    assert.eq(j.section[0].data[0].code[4].immediates.length, 1);
-    assert.eq(j.section[0].data[0].code[4].immediates[0], 0);
+    assert.eq(j.section[1].data[0].code.length, 6);
+    assert.eq(j.section[1].data[0].code[4].name, "call");
+    // FIXME: assert.eq(j.section[1].data[0].code[4].arguments.length, 4); https://bugs.webkit.org/show_bug.cgi?id=163267
+    assert.eq(j.section[1].data[0].code[4].immediates.length, 1);
+    assert.eq(j.section[1].data[0].code[4].immediates[0], 0);
 })();
 
 (function OpcodeAdd() {
-    const b = (new Builder()).Code()
+    const b = (new Builder()).Type().End().Code()
           .Function()
               .I32Const(42).I32Const(1337)
               .I32Add()
@@ -324,39 +534,39 @@ const assertOpThrows = (opFn, message) => {
         .End()
     .End();
     const j = JSON.parse(b.json());
-    assert.eq(j.section[0].data[0].code.length, 5);
-    assert.eq(j.section[0].data[0].code[2].name, "i32.add");
-    // FIXME: assert.eq(j.section[0].data[0].code[2].arguments.length, 2); https://bugs.webkit.org/show_bug.cgi?id=163267
-    assert.eq(j.section[0].data[0].code[3].name, "return");
+    assert.eq(j.section[1].data[0].code.length, 5);
+    assert.eq(j.section[1].data[0].code[2].name, "i32.add");
+    // FIXME: assert.eq(j.section[1].data[0].code[2].arguments.length, 2); https://bugs.webkit.org/show_bug.cgi?id=163267
+    assert.eq(j.section[1].data[0].code[3].name, "return");
     // FIXME check return. https://bugs.webkit.org/show_bug.cgi?id=163267
 })();
 
 (function OpcodeUnreachable() {
-    const b = (new Builder()).Code().Function().Unreachable().End().End();
+    const b = (new Builder()).Type().End().Code().Function().Unreachable().End().End();
     const j = JSON.parse(b.json());
-    assert.eq(j.section[0].data[0].code.length, 2);
-    assert.eq(j.section[0].data[0].code[0].name, "unreachable");
+    assert.eq(j.section[1].data[0].code.length, 2);
+    assert.eq(j.section[1].data[0].code[0].name, "unreachable");
 })();
 
 (function OpcodeUnreachableCombinations() {
-    (new Builder()).Code().Function().Nop().Unreachable().End().End();
-    (new Builder()).Code().Function().Unreachable().Nop().End().End();
-    (new Builder()).Code().Function().Return().Unreachable().End().End();
-    (new Builder()).Code().Function().Unreachable().Return().End().End();
-    (new Builder()).Code().Function().Call(0).Unreachable().End().End();
-    (new Builder()).Code().Function().Unreachable().Call(0).End().End();
+    (new Builder()).Type().End().Code().Function().Nop().Unreachable().End().End().json();
+    (new Builder()).Type().End().Code().Function().Unreachable().Nop().End().End().json();
+    (new Builder()).Type().End().Code().Function().Return().Unreachable().End().End().json();
+    (new Builder()).Type().End().Code().Function().Unreachable().Return().End().End().json();
+    (new Builder()).Type().End().Code().Function().Call(0).Unreachable().End().End().json();
+    (new Builder()).Type().End().Code().Function().Unreachable().Call(0).End().End().json();
 })();
 
 (function OpcodeSelect() {
-    const b = (new Builder()).Code().Function()
+    const b = (new Builder()).Type().End().Code().Function()
         .I32Const(1).I32Const(2).I32Const(0)
         .Select()
         .Return()
       .End()
     .End();
     const j = JSON.parse(b.json());
-    assert.eq(j.section[0].data[0].code.length, 6);
-    assert.eq(j.section[0].data[0].code[3].name, "select");
+    assert.eq(j.section[1].data[0].code.length, 6);
+    assert.eq(j.section[1].data[0].code[3].name, "select");
 })();
 
 // FIXME test type mismatch with select. https://bugs.webkit.org/show_bug.cgi?id=163267
index a251c43..ac57d37 100644 (file)
@@ -30,16 +30,3 @@ import Builder from '../Builder.js';
               ["00000000 00 61 73 6d 0c 00 00 00 00 0f 0a 4f 00 48 00 48  |·asm·······O·H·H|",
                "00000010 00 41 00 49 00 de ad c0 fe                       |·A·I·····       |"].join("\n"));
 })();
-
-(function Basic() {
-    const bin = (new Builder())
-        .Code()
-            .Function()
-                .Nop()
-                .Nop()
-            .End()
-        .End()
-        .WebAssembly();
-    assert.eq(bin.hexdump().trim(),
-              "00000000 00 61 73 6d 0c 00 00 00 0a 06 01 04 00 0a 0a 0f  |·asm············|");
-})();
index 39a9569..997797f 100644 (file)
@@ -1,8 +1,8 @@
 import * as assert from '../assert.js';
 import * as WASM from '../WASM.js';
 
-assert.notUndef(WASM.description);
-assert.notUndef(WASM.valueType);
+assert.isNotUndef(WASM.description);
+assert.isNotUndef(WASM.valueType);
 assert.ge(WASM.valueType.length, 4);
 
 for (const v of WASM.valueType)
@@ -18,7 +18,7 @@ const expectedFields = [
     "opcode",
 ];
 for (const e of expectedFields) {
-    assert.notUndef(WASM.description[e]);
+    assert.isNotUndef(WASM.description[e]);
     if (typeof(WASM.description[e]) !== "object")
         throw new Error(`Expected description to contain field "${e}"`);
 }
@@ -32,11 +32,11 @@ const expectedOpFields = [
 ];
 for (const op in WASM.description.opcode)
     for (const e of expectedOpFields)
-        assert.notUndef(WASM.description.opcode[op][e]);
+        assert.isNotUndef(WASM.description.opcode[op][e]);
 
 // FIXME: test for field "b3op" when all arithmetic/ comparison ops have them. https://bugs.webkit.org/show_bug.cgi?id=146064
 
-assert.notUndef(WASM.sections);
-assert.notUndef(WASM.sectionEncodingType);
+assert.isNotUndef(WASM.sections);
+assert.isNotUndef(WASM.sectionEncodingType);
 for (const section of WASM.sections)
     assert.eq(WASM.sectionEncodingType, WASM.description.section[section].type);
index bbf6fe4..72dcc02 100644 (file)
@@ -1139,6 +1139,23 @@ else ()
     )
 endif ()
 
+# WebAssembly generator
+
+macro(GENERATE_PYTHON _generator _input _output)
+    add_custom_command(
+        OUTPUT ${_output}
+        MAIN_DEPENDENCY ${_generator}
+        DEPENDS ${_input}
+        COMMAND ${PYTHON_EXECUTABLE} ${_generator} ${_input} ${_output}
+        VERBATIM)
+    list(APPEND JavaScriptCore_HEADERS ${_output})
+    ADD_SOURCE_DEPENDENCIES(${_input} ${_output})
+endmacro()
+GENERATE_PYTHON(${CMAKE_CURRENT_SOURCE_DIR}/wasm/generateWasmOpsHeader.py ${CMAKE_CURRENT_SOURCE_DIR}/wasm/wasm.json ${DERIVED_SOURCES_JAVASCRIPTCORE_DIR}/WasmOps.h)
+GENERATE_PYTHON(${CMAKE_CURRENT_SOURCE_DIR}/wasm/generateWasmValidateInlinesHeader.py ${CMAKE_CURRENT_SOURCE_DIR}/wasm/wasm.json ${DERIVED_SOURCES_JAVASCRIPTCORE_DIR}/WasmValidateInlines.h)
+
+# LUT generator
+
 set(HASH_LUT_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/create_hash_table)
 macro(GENERATE_HASH_LUT _input _output)
     add_custom_command(
index b357359..0b92805 100644 (file)
@@ -1,3 +1,77 @@
+2016-11-04  JF Bastien  <jfbastien@apple.com>
+
+        WebAssembly JS API: implement more sections
+        https://bugs.webkit.org/show_bug.cgi?id=164023
+
+        Reviewed by Keith Miller.
+
+        On the JSC side:
+
+         - Put in parser stubs for all WebAssembly sections.
+         - Parse Import, Export sections.
+         - Use tryReserveCapacity instead of reserve, and bail out of the parser if it fails. This prevents the parser from bringing everything down when faced with a malicious input.
+         - Encapsulate all parsed module information into its own structure, making it easier to pass around (from parser to Plan to Module to Instance).
+         - Create WasmFormat.cpp to hold parsed module information's dtor to avoid including WasmMemory.h needlessly.
+         - Remove all remainders of polyfill-prototype-1, and update license.
+         - Add missing WasmOps.h and WasmValidateInlines.h auto-generation for cmake build.
+
+        On the Builder.js testing side:
+
+         - Implement Type, Import (function only), Export (function only) sections.
+         - Check section order and uniqueness.
+         - Optionally auto-generate the Type section from subsequent Export / Import / Code entries.
+         - Allow re-exporting an import.
+
+        * CMakeLists.txt: missing auto-genration
+        * JavaScriptCore.xcodeproj/project.pbxproj: merge conflict
+        * testWasm.cpp: update for API changes, no functional change
+        (checkPlan):
+        (runWasmTests):
+        * wasm/WasmFormat.cpp: add a dtor which requires extra headers which I'd rather not include in WasmFormat.h
+        (JSC::Wasm::ModuleInformation::~ModuleInformation):
+        * wasm/WasmFormat.h: Add External, Import, Functioninformation, Export, ModuleInformation, CompiledFunction, and remove obsolete stuff which was a holdover from the first implementation (all that code is now gone, so remove its license)
+        (JSC::Wasm::External::isValid):
+        * wasm/WasmModuleParser.cpp: simplify some, make names consistent with the WebAssembly section names, check memory allocations so they can fail early
+        (JSC::Wasm::ModuleParser::parse):
+        (JSC::Wasm::ModuleParser::parseType):
+        (JSC::Wasm::ModuleParser::parseImport):
+        (JSC::Wasm::ModuleParser::parseFunction):
+        (JSC::Wasm::ModuleParser::parseTable):
+        (JSC::Wasm::ModuleParser::parseMemory):
+        (JSC::Wasm::ModuleParser::parseGlobal):
+        (JSC::Wasm::ModuleParser::parseExport):
+        (JSC::Wasm::ModuleParser::parseStart):
+        (JSC::Wasm::ModuleParser::parseElement):
+        (JSC::Wasm::ModuleParser::parseCode): avoid overflow through function size.
+        (JSC::Wasm::ModuleParser::parseData):
+        * wasm/WasmModuleParser.h:
+        (JSC::Wasm::ModuleParser::moduleInformation):
+        * wasm/WasmParser.h:
+        (JSC::Wasm::Parser::consumeUTF8String): add as required by spec
+        (JSC::Wasm::Parser::parseExternalKind): add as per spec
+        * wasm/WasmPlan.cpp:
+        (JSC::Wasm::Plan::Plan): fix some ownership, improve some error messages
+        * wasm/WasmPlan.h: fix some ownership
+        (JSC::Wasm::Plan::getModuleInformation):
+        (JSC::Wasm::Plan::getMemory):
+        (JSC::Wasm::Plan::compiledFunctionCount):
+        (JSC::Wasm::Plan::compiledFunction):
+        (JSC::Wasm::Plan::getCompiledFunctions):
+        * wasm/WasmSections.h: macroize with description, so that error messages are super pretty. This could be auto-generated.
+        * wasm/js/JSWebAssemblyModule.cpp:
+        (JSC::JSWebAssemblyModule::create): take module information
+        (JSC::JSWebAssemblyModule::JSWebAssemblyModule): ditto
+        * wasm/js/JSWebAssemblyModule.h:
+        (JSC::JSWebAssemblyModule::moduleInformation):
+        * wasm/js/WebAssemblyInstanceConstructor.cpp:
+        (JSC::constructJSWebAssemblyInstance): check that modules with imports are instantiated with an import object, as per spec. This needs to be tested.
+        * wasm/js/WebAssemblyMemoryConstructor.cpp:
+        (JSC::constructJSWebAssemblyMemory):
+        * wasm/js/WebAssemblyModuleConstructor.cpp:
+        (JSC::constructJSWebAssemblyModule):
+        * wasm/js/WebAssemblyTableConstructor.cpp:
+        (JSC::constructJSWebAssemblyTable):
+
 2016-11-03  Mark Lam  <mark.lam@apple.com>
 
         ClonedArguments need to also support haveABadTime mode.
index 1997340..600ca34 100644 (file)
                53FD04D41D7AB291003287D3 /* WasmCallingConvention.h in Headers */ = {isa = PBXBuildFile; fileRef = 53FD04D21D7AB187003287D3 /* WasmCallingConvention.h */; };
                53FF7F991DBFCD9000A26CCC /* WasmValidate.h in Headers */ = {isa = PBXBuildFile; fileRef = 53FF7F981DBFCD9000A26CCC /* WasmValidate.h */; };
                53FF7F9B1DBFD2B900A26CCC /* WasmValidate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F9A1DBFD2B900A26CCC /* WasmValidate.cpp */; };
-               53FF7F9D1DC00DB100A26CCC /* WasmFormat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 53FF7F9C1DC00DB100A26CCC /* WasmFormat.cpp */; };
                5B70CFDE1DB69E6600EC23F9 /* JSAsyncFunction.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B70CFD81DB69E5C00EC23F9 /* JSAsyncFunction.h */; };
                5B70CFDF1DB69E6600EC23F9 /* JSAsyncFunction.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5B70CFD91DB69E5C00EC23F9 /* JSAsyncFunction.cpp */; };
                5B70CFE01DB69E6600EC23F9 /* AsyncFunctionPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = 5B70CFDA1DB69E5C00EC23F9 /* AsyncFunctionPrototype.h */; };
                AD2FCC2D1DB838FD00B3E736 /* WebAssemblyPrototype.h in Headers */ = {isa = PBXBuildFile; fileRef = AD2FCC271DB838C400B3E736 /* WebAssemblyPrototype.h */; };
                AD2FCC301DB83D4900B3E736 /* JSWebAssembly.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AD2FCC2E1DB839F700B3E736 /* JSWebAssembly.cpp */; };
                AD2FCC311DB83D4900B3E736 /* JSWebAssembly.h in Headers */ = {isa = PBXBuildFile; fileRef = AD2FCC2F1DB839F700B3E736 /* JSWebAssembly.h */; };
+               AD2FCC331DC4045400B3E736 /* WasmFormat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AD2FCC321DC4045300B3E736 /* WasmFormat.cpp */; };
                AD86A93E1AA4D88D002FE77F /* WeakGCMapInlines.h in Headers */ = {isa = PBXBuildFile; fileRef = AD86A93D1AA4D87C002FE77F /* WeakGCMapInlines.h */; settings = {ATTRIBUTES = (Private, ); }; };
                ADDB1F6318D77DBE009B58A8 /* OpaqueRootSet.h in Headers */ = {isa = PBXBuildFile; fileRef = ADDB1F6218D77DB7009B58A8 /* OpaqueRootSet.h */; settings = {ATTRIBUTES = (Private, ); }; };
                ADE39FFF16DD144B0003CD4A /* PropertyTable.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AD1CF06816DCAB2D00B97123 /* PropertyTable.cpp */; };
                53FD04D21D7AB187003287D3 /* WasmCallingConvention.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WasmCallingConvention.h; sourceTree = "<group>"; };
                53FF7F981DBFCD9000A26CCC /* WasmValidate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WasmValidate.h; sourceTree = "<group>"; };
                53FF7F9A1DBFD2B900A26CCC /* WasmValidate.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WasmValidate.cpp; sourceTree = "<group>"; };
-               53FF7F9C1DC00DB100A26CCC /* WasmFormat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WasmFormat.cpp; sourceTree = "<group>"; };
                5B70CFD81DB69E5C00EC23F9 /* JSAsyncFunction.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSAsyncFunction.h; sourceTree = "<group>"; };
                5B70CFD91DB69E5C00EC23F9 /* JSAsyncFunction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSAsyncFunction.cpp; sourceTree = "<group>"; };
                5B70CFDA1DB69E5C00EC23F9 /* AsyncFunctionPrototype.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AsyncFunctionPrototype.h; sourceTree = "<group>"; };
                AD2FCC271DB838C400B3E736 /* WebAssemblyPrototype.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = WebAssemblyPrototype.h; path = js/WebAssemblyPrototype.h; sourceTree = "<group>"; };
                AD2FCC2E1DB839F700B3E736 /* JSWebAssembly.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSWebAssembly.cpp; sourceTree = "<group>"; };
                AD2FCC2F1DB839F700B3E736 /* JSWebAssembly.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSWebAssembly.h; sourceTree = "<group>"; };
+               AD2FCC321DC4045300B3E736 /* WasmFormat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WasmFormat.cpp; sourceTree = "<group>"; };
                AD86A93D1AA4D87C002FE77F /* WeakGCMapInlines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WeakGCMapInlines.h; sourceTree = "<group>"; };
                ADDB1F6218D77DB7009B58A8 /* OpaqueRootSet.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpaqueRootSet.h; sourceTree = "<group>"; };
                B59F89371891AD3300D5CCDC /* UnlinkedInstructionStream.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UnlinkedInstructionStream.h; sourceTree = "<group>"; };
                                53F40E921D5A4AB30099A1B6 /* WasmB3IRGenerator.h */,
                                53FD04D11D7AB187003287D3 /* WasmCallingConvention.cpp */,
                                53FD04D21D7AB187003287D3 /* WasmCallingConvention.h */,
-                               53FF7F9C1DC00DB100A26CCC /* WasmFormat.cpp */,
+                               AD2FCC321DC4045300B3E736 /* WasmFormat.cpp */,
                                7BC547D21B69599B00959B58 /* WasmFormat.h */,
                                53F40E8A1D5901BB0099A1B6 /* WasmFunctionParser.h */,
                                535557151D9DFA32006D583B /* WasmMemory.cpp */,
                                0F0A75221B94BFA900110660 /* InferredType.cpp in Sources */,
                                0FFC92111B94D4DF0071DD66 /* InferredTypeTable.cpp in Sources */,
                                0FF8BDEA1AD4CF7100DFE884 /* InferredValue.cpp in Sources */,
+                               AD2FCC331DC4045400B3E736 /* WasmFormat.cpp in Sources */,
                                9E729407190F01A5001A91B5 /* InitializeThreading.cpp in Sources */,
                                A513E5B7185B8BD3007E95AD /* InjectedScript.cpp in Sources */,
                                A514B2C2185A684400F3C7CB /* InjectedScriptBase.cpp in Sources */,
index 6e3f5d2..9263b9a 100644 (file)
@@ -276,13 +276,13 @@ static void checkPlan(Plan& plan, unsigned expectedNumberOfFunctions)
         CRASH();
     }
 
-    if (plan.resultSize() != expectedNumberOfFunctions) {
+    if (plan.compiledFunctionCount() != expectedNumberOfFunctions) {
         dataLogLn("Incorrect number of functions");
         CRASH();
     }
 
     for (unsigned i = 0; i < expectedNumberOfFunctions; ++i) {
-        if (!plan.result(i)) {
+        if (!plan.compiledFunction(i)) {
             dataLogLn("Function at index, " , i, " failed to compile correctly");
             CRASH();
         }
@@ -320,8 +320,8 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK(isIdentical(invoke<int>(*plan.result(0)->jsEntryPoint, { boxf(0), boxf(32) }), 1));
-        CHECK(isIdentical(invoke<int>(*plan.result(0)->jsEntryPoint, { boxf(1), boxf(32) }), 2));
+        CHECK(isIdentical(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { boxf(0), boxf(32) }), 1));
+        CHECK(isIdentical(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { boxf(1), boxf(32) }), 2));
     }
 
     {
@@ -347,8 +347,8 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK(isIdentical(invoke<int>(*plan.result(0)->jsEntryPoint, { boxf(0), boxf(32) }), 1));
-        CHECK(isIdentical(invoke<int>(*plan.result(0)->jsEntryPoint, { boxf(1), boxf(32) }), 2));
+        CHECK(isIdentical(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { boxf(0), boxf(32) }), 1));
+        CHECK(isIdentical(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { boxf(1), boxf(32) }), 2));
     }
 
     {
@@ -385,14 +385,14 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(1) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(1) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(2) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(2) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(1) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(6) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(6) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(1) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(2) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(2) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(1) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(6) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(6) }), 0);
     }
 
     {
@@ -415,10 +415,10 @@ static void runWasmTests()
         checkPlan(plan, 2);
 
         // Test this doesn't crash.
-        CHECK(isIdentical(invoke<float>(*plan.result(1)->jsEntryPoint, { boxf(0.0), boxf(1.5) }), -1.5f));
-        CHECK(isIdentical(invoke<float>(*plan.result(1)->jsEntryPoint, { boxf(100.1234), boxf(12.5) }), 87.6234f));
-        CHECK(isIdentical(invoke<float>(*plan.result(0)->jsEntryPoint, { boxf(0.0), boxf(1.5) }), -1.5f));
-        CHECK(isIdentical(invoke<float>(*plan.result(0)->jsEntryPoint, { boxf(100.1234), boxf(12.5) }), 87.6234f));
+        CHECK(isIdentical(invoke<float>(*plan.compiledFunction(1)->jsEntryPoint, { boxf(0.0), boxf(1.5) }), -1.5f));
+        CHECK(isIdentical(invoke<float>(*plan.compiledFunction(1)->jsEntryPoint, { boxf(100.1234), boxf(12.5) }), 87.6234f));
+        CHECK(isIdentical(invoke<float>(*plan.compiledFunction(0)->jsEntryPoint, { boxf(0.0), boxf(1.5) }), -1.5f));
+        CHECK(isIdentical(invoke<float>(*plan.compiledFunction(0)->jsEntryPoint, { boxf(100.1234), boxf(12.5) }), 87.6234f));
     }
 
     {
@@ -441,10 +441,10 @@ static void runWasmTests()
         checkPlan(plan, 2);
 
         // Test this doesn't crash.
-        CHECK(isIdentical(invoke<float>(*plan.result(1)->jsEntryPoint, { boxf(0.0), boxf(1.5) }), 1.5f));
-        CHECK(isIdentical(invoke<float>(*plan.result(1)->jsEntryPoint, { boxf(100.1234), boxf(12.5) }), 112.6234f));
-        CHECK(isIdentical(invoke<float>(*plan.result(0)->jsEntryPoint, { boxf(0.0), boxf(1.5) }), 1.5f));
-        CHECK(isIdentical(invoke<float>(*plan.result(0)->jsEntryPoint, { boxf(100.1234), boxf(12.5) }), 112.6234f));
+        CHECK(isIdentical(invoke<float>(*plan.compiledFunction(1)->jsEntryPoint, { boxf(0.0), boxf(1.5) }), 1.5f));
+        CHECK(isIdentical(invoke<float>(*plan.compiledFunction(1)->jsEntryPoint, { boxf(100.1234), boxf(12.5) }), 112.6234f));
+        CHECK(isIdentical(invoke<float>(*plan.compiledFunction(0)->jsEntryPoint, { boxf(0.0), boxf(1.5) }), 1.5f));
+        CHECK(isIdentical(invoke<float>(*plan.compiledFunction(0)->jsEntryPoint, { boxf(100.1234), boxf(12.5) }), 112.6234f));
     }
 
     {
@@ -472,11 +472,11 @@ static void runWasmTests()
         checkPlan(plan, 2);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(1)->jsEntryPoint, { box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(1)->jsEntryPoint, { box(100) }), 1200);
-        CHECK_EQ(invoke<int>(*plan.result(1)->jsEntryPoint, { box(1) }), 12);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(2), box(3), box(4), box(5), box(6), box(7), box(8), box(9), box(10), box(11), box(12) }), 78);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(2), box(3), box(4), box(5), box(6), box(7), box(8), box(9), box(10), box(11), box(100) }), 166);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(1)->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(1)->jsEntryPoint, { box(100) }), 1200);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(1)->jsEntryPoint, { box(1) }), 12);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(2), box(3), box(4), box(5), box(6), box(7), box(8), box(9), box(10), box(11), box(12) }), 78);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(2), box(3), box(4), box(5), box(6), box(7), box(8), box(9), box(10), box(11), box(100) }), 166);
     }
 
     {
@@ -503,10 +503,10 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2) }), 2);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(4) }), 24);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2) }), 2);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(4) }), 24);
     }
 
     {
@@ -526,18 +526,18 @@ static void runWasmTests()
         };
 
         Plan plan(*vm, vector);
-        if (plan.resultSize() != 2 || !plan.result(0) || !plan.result(1)) {
+        if (plan.compiledFunctionCount() != 2 || !plan.compiledFunction(0) || !plan.compiledFunction(1)) {
             dataLogLn("Module failed to compile correctly.");
             CRASH();
         }
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(1)->jsEntryPoint, { box(0), box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(1)->jsEntryPoint, { box(100), box(0) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(1)->jsEntryPoint, { box(1), box(15) }), 16);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100) }), 200);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1) }), 2);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(1)->jsEntryPoint, { box(0), box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(1)->jsEntryPoint, { box(100), box(0) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(1)->jsEntryPoint, { box(1), box(15) }), 16);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100) }), 200);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1) }), 2);
     }
 
     {
@@ -560,12 +560,12 @@ static void runWasmTests()
         checkPlan(plan, 2);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(1)->jsEntryPoint, { box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(1)->jsEntryPoint, { box(100) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(1)->jsEntryPoint, { box(1) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(1)->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(1)->jsEntryPoint, { box(100) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(1)->jsEntryPoint, { box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1) }), 1);
     }
 
     {
@@ -589,9 +589,9 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(10) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(2) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(100) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(10) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(2) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(100) }), 1);
     }
 
     {
@@ -616,9 +616,9 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(10) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(2) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(100) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(10) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(2) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(100) }), 1);
     }
 
     {
@@ -657,7 +657,7 @@ static void runWasmTests()
         unsigned length = 5;
         unsigned offset = sizeof(uint32_t);
         uint32_t* memory = static_cast<uint32_t*>(plan.memory()->memory());
-        invoke<void>(*plan.result(0)->jsEntryPoint, { box(100), box(offset), box(length) });
+        invoke<void>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(offset), box(length) });
         offset /= sizeof(uint32_t);
         CHECK_EQ(memory[offset - 1], 0u);
         CHECK_EQ(memory[offset + length], 0u);
@@ -666,7 +666,7 @@ static void runWasmTests()
 
         length = 10;
         offset = 5 * sizeof(uint32_t);
-        invoke<void>(*plan.result(0)->jsEntryPoint, { box(5), box(offset), box(length) });
+        invoke<void>(*plan.compiledFunction(0)->jsEntryPoint, { box(5), box(offset), box(length) });
         offset /= sizeof(uint32_t);
         CHECK_EQ(memory[offset - 1], 100u);
         CHECK_EQ(memory[offset + length], 0u);
@@ -709,7 +709,7 @@ static void runWasmTests()
         unsigned length = 5;
         unsigned offset = 1;
         uint8_t* memory = static_cast<uint8_t*>(plan.memory()->memory());
-        invoke<void>(*plan.result(0)->jsEntryPoint, { box(100), box(offset), box(length) });
+        invoke<void>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(offset), box(length) });
         CHECK_EQ(memory[offset - 1], 0u);
         CHECK_EQ(memory[offset + length], 0u);
         for (unsigned i = 0; i < length; ++i)
@@ -717,7 +717,7 @@ static void runWasmTests()
 
         length = 10;
         offset = 5;
-        invoke<void>(*plan.result(0)->jsEntryPoint, { box(5), box(offset), box(length) });
+        invoke<void>(*plan.compiledFunction(0)->jsEntryPoint, { box(5), box(offset), box(length) });
         CHECK_EQ(memory[offset - 1], 100u);
         CHECK_EQ(memory[offset + length], 0u);
         for (unsigned i = 0; i < length; ++i)
@@ -747,9 +747,9 @@ static void runWasmTests()
         ASSERT(plan.memory()->size());
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(10) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(2) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(100) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(10) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(2) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(100) }), 1);
     }
 
     {
@@ -774,9 +774,9 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1) }), 1);
     }
 
     {
@@ -801,10 +801,10 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(10) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(2) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(100) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(-12), box(plan.memory()->size() - sizeof(uint64_t)) }), -12);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(10) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(2) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(100) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(-12), box(plan.memory()->size() - sizeof(uint64_t)) }), -12);
     }
 
     {
@@ -829,9 +829,9 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(10) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(2) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(100) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(10) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(2) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(100) }), 1);
     }
 
     {
@@ -870,7 +870,7 @@ static void runWasmTests()
         unsigned length = 5;
         unsigned offset = sizeof(uint32_t);
         uint32_t* memory = static_cast<uint32_t*>(plan.memory()->memory());
-        invoke<void>(*plan.result(0)->jsEntryPoint, { box(100), box(offset), box(length) });
+        invoke<void>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(offset), box(length) });
         offset /= sizeof(uint32_t);
         CHECK_EQ(memory[offset - 1], 0u);
         CHECK_EQ(memory[offset + length], 0u);
@@ -879,7 +879,7 @@ static void runWasmTests()
 
         length = 10;
         offset = 5 * sizeof(uint32_t);
-        invoke<void>(*plan.result(0)->jsEntryPoint, { box(5), box(offset), box(length) });
+        invoke<void>(*plan.compiledFunction(0)->jsEntryPoint, { box(5), box(offset), box(length) });
         offset /= sizeof(uint32_t);
         CHECK_EQ(memory[offset - 1], 100u);
         CHECK_EQ(memory[offset + length], 0u);
@@ -922,7 +922,7 @@ static void runWasmTests()
         unsigned length = 5;
         unsigned offset = 1;
         uint8_t* memory = static_cast<uint8_t*>(plan.memory()->memory());
-        invoke<void>(*plan.result(0)->jsEntryPoint, { box(100), box(offset), box(length) });
+        invoke<void>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(offset), box(length) });
         CHECK_EQ(memory[offset - 1], 0u);
         CHECK_EQ(memory[offset + length], 0u);
         for (unsigned i = 0; i < length; ++i)
@@ -930,7 +930,7 @@ static void runWasmTests()
 
         length = 10;
         offset = 5;
-        invoke<void>(*plan.result(0)->jsEntryPoint, { box(5), box(offset), box(length) });
+        invoke<void>(*plan.compiledFunction(0)->jsEntryPoint, { box(5), box(offset), box(length) });
         CHECK_EQ(memory[offset - 1], 100u);
         CHECK_EQ(memory[offset + length], 0u);
         for (unsigned i = 0; i < length; ++i)
@@ -960,9 +960,9 @@ static void runWasmTests()
         ASSERT(plan.memory()->size());
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(10) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(2) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(100) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(10) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(2) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(100) }), 1);
     }
 
     {
@@ -987,9 +987,9 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100) }), 100);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100) }), 100);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1) }), 1);
     }
 
     {
@@ -1014,14 +1014,14 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(1) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(0) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(1) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(2) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(2) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(1) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(6) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(6) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(0) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(2) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(2) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(1) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(6) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(6) }), 1);
     }
 
     {
@@ -1052,14 +1052,14 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(1) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(1) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(2) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(2) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(1) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(6) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(6) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(1) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(2) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(2) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(1) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(6) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(6) }), 0);
     }
 
 
@@ -1076,7 +1076,7 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { }), 5);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { }), 5);
     }
 
 
@@ -1094,7 +1094,7 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { }), 11);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { }), 11);
     }
 
     {
@@ -1111,7 +1111,7 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { }), 11);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { }), 11);
     }
 
     {
@@ -1128,7 +1128,7 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { }), 11);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { }), 11);
     }
 
     {
@@ -1144,10 +1144,10 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(1) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(1) }), 101);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(-1), box(1)}), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(std::numeric_limits<int>::max()), box(1) }), std::numeric_limits<int>::min());
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(1) }), 101);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(-1), box(1)}), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(std::numeric_limits<int>::max()), box(1) }), std::numeric_limits<int>::min());
     }
 
     {
@@ -1170,8 +1170,8 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(10) }), 10);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(10) }), 10);
     }
 
     {
@@ -1203,10 +1203,10 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2)}), 3);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100) }), 5050);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2)}), 3);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100) }), 5050);
     }
 
     {
@@ -1244,14 +1244,14 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(1) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(1) }), 2);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(2) }), 2);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(2) }), 4);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(6) }), 12);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(6) }), 600);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(100) }), 10000);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(1) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(1) }), 2);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(2) }), 2);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(2) }), 4);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(6) }), 12);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(6) }), 600);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(100) }), 10000);
     }
 
     {
@@ -1294,14 +1294,14 @@ static void runWasmTests()
         checkPlan(plan, 1);
 
         // Test this doesn't crash.
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(0), box(1) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(0) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(1) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(2) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(2) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(1), box(1) }), 0);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(2), box(6) }), 1);
-        CHECK_EQ(invoke<int>(*plan.result(0)->jsEntryPoint, { box(100), box(6) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(0), box(1) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(0) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(1) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(2) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(2) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(1), box(1) }), 0);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(2), box(6) }), 1);
+        CHECK_EQ(invoke<int>(*plan.compiledFunction(0)->jsEntryPoint, { box(100), box(6) }), 0);
     }
 
 }
index 2bdc800..bf8e356 100644 (file)
  */
 
 #include "config.h"
+
 #include "WasmFormat.h"
 
 #if ENABLE(WEBASSEMBLY)
 
+#include "WasmMemory.h"
+
 namespace JSC { namespace Wasm {
 
 const char* toString(Type type)
@@ -46,6 +49,8 @@ const char* toString(Type type)
     }
 }
 
+ModuleInformation::~ModuleInformation() { }
+
 } } // namespace JSC::Wasm
 
-#endif // ENABLE(B3_JIT)
+#endif // ENABLE(WEBASSEMBLY)
index 8286aef..499644c 100644 (file)
  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- *
- * =========================================================================
- *
- * Copyright (c) 2015 by the repository authors of
- * WebAssembly/polyfill-prototype-1.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
  */
 
 #pragma once
@@ -98,37 +81,83 @@ inline bool isValueType(Type type)
 }
 
 const char* toString(Type);
+    
+struct External {
+    enum Kind : uint8_t {
+        Function = 0,
+        Table = 1,
+        Memory = 2,
+        Global = 3,
+    };
+    template<typename Int>
+    static bool isValid(Int val)
+    {
+        switch (val) {
+        case Function:
+        case Table:
+        case Memory:
+        case Global:
+            return true;
+        default:
+            return false;
+        }
+    }
+    
+    static_assert(Function == 0, "Wasm needs Function to have the value 0");
+    static_assert(Table    == 1, "Wasm needs Table to have the value 1");
+    static_assert(Memory   == 2, "Wasm needs Memory to have the value 2");
+    static_assert(Global   == 3, "Wasm needs Global to have the value 3");
+};
 
 struct Signature {
     Type returnType;
     Vector<Type> arguments;
 };
-
-struct FunctionImport {
-    String functionName;
+    
+struct Import {
+    String module;
+    String field;
+    External::Kind kind;
+    union {
+        Signature* functionSignature;
+        // FIXME implement Table https://bugs.webkit.org/show_bug.cgi?id=164135
+        // FIXME implement Memory https://bugs.webkit.org/show_bug.cgi?id=164134
+        // FIXME implement Global https://bugs.webkit.org/show_bug.cgi?id=164133
+    };
 };
 
-struct FunctionImportSignature {
-    uint32_t signatureIndex;
-    uint32_t functionImportIndex;
+struct FunctionInformation {
+    Signature* signature;
+    size_t start;
+    size_t end;
 };
 
-struct FunctionDeclaration {
-    uint32_t signatureIndex;
+class Memory;
+
+struct Export {
+    String field;
+    External::Kind kind;
+    union {
+        Signature* functionSignature;
+        // FIXME implement Table https://bugs.webkit.org/show_bug.cgi?id=164135
+        // FIXME implement Memory https://bugs.webkit.org/show_bug.cgi?id=164134
+        // FIXME implement Global https://bugs.webkit.org/show_bug.cgi?id=164133
+    };
 };
 
-struct FunctionPointerTable {
-    uint32_t signatureIndex;
-    Vector<uint32_t> functionIndices;
-    Vector<JSFunction*> functions;
-};
+struct ModuleInformation {
+    Vector<Signature> signatures;
+    Vector<Import> imports;
+    Vector<FunctionInformation> functions;
+    std::unique_ptr<Memory> memory;
+    Vector<Export> exports;
 
-struct FunctionInformation {
-    Signature* signature;
-    size_t start;
-    size_t end;
+    ~ModuleInformation();
 };
 
+struct FunctionCompilation;
+typedef Vector<std::unique_ptr<FunctionCompilation>> CompiledFunctions;
+
 struct UnlinkedCall {
     CodeLocationCall callLocation;
     size_t functionIndex;
index 55c212e..b035b45 100644 (file)
@@ -29,6 +29,7 @@
 #if ENABLE(WEBASSEMBLY)
 
 #include "WasmFormat.h"
+#include "WasmMemory.h"
 #include "WasmOps.h"
 #include "WasmSections.h"
 
@@ -40,6 +41,8 @@ static const bool verbose = false;
 
 bool ModuleParser::parse()
 {
+    m_module = std::make_unique<ModuleInformation>();
+
     const size_t minSize = 8;
     if (length() < minSize) {
         m_errorMessage = "Module is " + String::number(length()) + " bytes, expected at least " + String::number(minSize) + " bytes";
@@ -111,54 +114,20 @@ bool ModuleParser::parse()
         auto end = m_offset + sectionLength;
 
         switch (section) {
-
-        case Sections::Memory: {
-            if (verbose)
-                dataLogLn("Parsing Memory.");
-            if (!parseMemory()) {
-                // FIXME improve error message https://bugs.webkit.org/show_bug.cgi?id=163919
-                m_errorMessage = "couldn't parse memory";
-                return false;
-            }
-            break;
-        }
-
-        case Sections::FunctionTypes: {
-            if (verbose)
-                dataLogLn("Parsing types.");
-            if (!parseFunctionTypes()) {
-                // FIXME improve error message https://bugs.webkit.org/show_bug.cgi?id=163919
-                m_errorMessage = "couldn't parse types";
-                return false;
-            }
-            break;
-        }
-
-        case Sections::Signatures: {
-            if (verbose)
-                dataLogLn("Parsing function signatures.");
-            if (!parseFunctionSignatures()) {
-                // FIXME improve error message https://bugs.webkit.org/show_bug.cgi?id=163919
-                m_errorMessage = "couldn't parse function signatures";
-                return false;
-            }
-            break;
-        }
-
-        case Sections::Definitions: {
-            if (verbose)
-                dataLogLn("Parsing function definitions.");
-            if (!parseFunctionDefinitions()) {
-                // FIXME improve error message https://bugs.webkit.org/show_bug.cgi?id=163919
-                m_errorMessage = "couldn't parse function definitions";
-                return false;
-            }
-            break;
-        }
-
-        case Sections::Unknown:
-        // FIXME: Delete this when we support all the sections.
-        default: {
+        // FIXME improve error message in macro below https://bugs.webkit.org/show_bug.cgi?id=163919
+#define WASM_SECTION_PARSE(NAME, ID, DESCRIPTION) \
+        case Sections::NAME: { \
+            if (verbose) \
+                dataLogLn("Parsing " DESCRIPTION); \
+            if (!parse ## NAME()) { \
+                m_errorMessage = "couldn't parse section " #NAME ": " DESCRIPTION; \
+                return false; \
+            } \
+        } break;
+        FOR_EACH_WASM_SECTION(WASM_SECTION_PARSE)
+#undef WASM_SECTION_PARSE
+
+        case Sections::Unknown: {
             if (verbose)
                 dataLogLn("Unknown section, skipping.");
             // Ignore section's name LEB and bytes: they're already included in sectionLength.
@@ -184,50 +153,21 @@ bool ModuleParser::parse()
     return true;
 }
 
-bool ModuleParser::parseMemory()
-{
-    uint8_t flags;
-    if (!parseVarUInt1(flags))
-        return false;
-
-    uint32_t size;
-    if (!parseVarUInt32(size))
-        return false;
-    if (size > maxPageCount)
-        return false;
-
-    uint32_t capacity = maxPageCount;
-    if (flags) {
-        if (!parseVarUInt32(capacity))
-            return false;
-        if (size > capacity || capacity > maxPageCount)
-            return false;
-    }
-
-    capacity *= pageSize;
-    size *= pageSize;
-
-    Vector<unsigned> pinnedSizes = { 0 };
-    m_memory = std::make_unique<Memory>(size, capacity, pinnedSizes);
-    return m_memory->memory();
-}
-
-bool ModuleParser::parseFunctionTypes()
+bool ModuleParser::parseType()
 {
     uint32_t count;
     if (!parseVarUInt32(count))
         return false;
-
     if (verbose)
         dataLogLn("count: ", count);
-
-    m_signatures.resize(count);
+    if (!m_module->signatures.tryReserveCapacity(count))
+        return false;
 
     for (uint32_t i = 0; i < count; ++i) {
-        uint8_t type;
-        if (!parseUInt7(type))
+        int8_t type;
+        if (!parseInt7(type))
             return false;
-        if (type != 0x40) // Function type constant.
+        if (type != -0x20) // Function type constant.
             return false;
 
         if (verbose)
@@ -241,22 +181,25 @@ bool ModuleParser::parseFunctionTypes()
             dataLogLn("argumentCount: ", argumentCount);
 
         Vector<Type> argumentTypes;
-        argumentTypes.resize(argumentCount);
+        if (!argumentTypes.tryReserveCapacity(argumentCount))
+            return false;
 
-        for (unsigned i = 0; i < argumentCount; ++i) {
-            if (!parseUInt7(type) || !isValueType(static_cast<Type>(type)))
+        for (unsigned i = 0; i != argumentCount; ++i) {
+            uint8_t argumentType;
+            if (!parseUInt7(argumentType) || !isValueType(static_cast<Type>(argumentType)))
                 return false;
-            argumentTypes[i] = static_cast<Type>(type);
+            argumentTypes.uncheckedAppend(static_cast<Type>(argumentType));
         }
 
-        if (!parseVarUInt1(type))
+        uint8_t returnCount;
+        if (!parseVarUInt1(returnCount))
             return false;
         Type returnType;
 
         if (verbose)
-            dataLogLn(type);
+            dataLogLn(returnCount);
 
-        if (type) {
+        if (returnCount) {
             Type value;
             if (!parseValueType(value))
                 return false;
@@ -264,48 +207,195 @@ bool ModuleParser::parseFunctionTypes()
         } else
             returnType = Type::Void;
 
-        m_signatures[i] = { returnType, WTFMove(argumentTypes) };
+        m_module->signatures.uncheckedAppend({ returnType, WTFMove(argumentTypes) });
     }
     return true;
 }
 
-bool ModuleParser::parseFunctionSignatures()
+bool ModuleParser::parseImport()
+{
+    uint32_t importCount;
+    if (!parseVarUInt32(importCount))
+        return false;
+    if (!m_module->imports.tryReserveCapacity(importCount))
+        return false;
+
+    for (uint32_t importNumber = 0; importNumber != importCount; ++importNumber) {
+        Import i;
+        uint32_t moduleLen;
+        uint32_t fieldLen;
+        if (!parseVarUInt32(moduleLen))
+            return false;
+        if (!consumeUTF8String(i.module, moduleLen))
+            return false;
+        if (!parseVarUInt32(fieldLen))
+            return false;
+        if (!consumeUTF8String(i.field, fieldLen))
+            return false;
+        if (!parseExternalKind(i.kind))
+            return false;
+        switch (i.kind) {
+        case External::Function: {
+            uint32_t functionSignatureIndex;
+            if (!parseVarUInt32(functionSignatureIndex))
+                return false;
+            if (functionSignatureIndex > m_module->signatures.size())
+                return false;
+            i.functionSignature = &m_module->signatures[functionSignatureIndex];
+        } break;
+        case External::Table:
+            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164135
+            break;
+        case External::Memory:
+            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164134
+            break;
+        case External::Global:
+            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164133
+            // In the MVP, only immutable global variables can be imported.
+            break;
+        }
+
+        m_module->imports.uncheckedAppend(i);
+    }
+
+    return true;
+}
+
+bool ModuleParser::parseFunction()
 {
     uint32_t count;
     if (!parseVarUInt32(count))
         return false;
+    if (!m_module->functions.tryReserveCapacity(count))
+        return false;
 
-    m_functions.resize(count);
-
-    for (uint32_t i = 0; i < count; ++i) {
+    for (uint32_t i = 0; i != count; ++i) {
         uint32_t typeNumber;
         if (!parseVarUInt32(typeNumber))
             return false;
 
-        if (typeNumber >= m_signatures.size())
+        if (typeNumber >= m_module->signatures.size())
             return false;
 
-        m_functions[i].signature = &m_signatures[typeNumber];
+        m_module->functions.uncheckedAppend({ &m_module->signatures[typeNumber], 0, 0 });
     }
 
     return true;
 }
 
-bool ModuleParser::parseFunctionDefinitions()
+bool ModuleParser::parseTable()
+{
+    // FIXME
+    return true;
+}
+
+bool ModuleParser::parseMemory()
+{
+    uint8_t flags;
+    if (!parseVarUInt1(flags))
+        return false;
+
+    uint32_t size;
+    if (!parseVarUInt32(size))
+        return false;
+    if (size > maxPageCount)
+        return false;
+
+    uint32_t capacity = maxPageCount;
+    if (flags) {
+        if (!parseVarUInt32(capacity))
+            return false;
+        if (size > capacity || capacity > maxPageCount)
+            return false;
+    }
+
+    capacity *= pageSize;
+    size *= pageSize;
+
+    Vector<unsigned> pinnedSizes = { 0 };
+    m_module->memory = std::make_unique<Memory>(size, capacity, pinnedSizes);
+    return m_module->memory->memory();
+}
+
+bool ModuleParser::parseGlobal()
+{
+    // FIXME https://bugs.webkit.org/show_bug.cgi?id=164133
+    return true;
+}
+
+bool ModuleParser::parseExport()
+{
+    uint32_t exportCount;
+    if (!parseVarUInt32(exportCount))
+        return false;
+    if (!m_module->exports.tryReserveCapacity(exportCount))
+        return false;
+
+    for (uint32_t exportNumber = 0; exportNumber != exportCount; ++exportNumber) {
+        Export e;
+        uint32_t fieldLen;
+        if (!parseVarUInt32(fieldLen))
+            return false;
+        if (!consumeUTF8String(e.field, fieldLen))
+            return false;
+        if (!parseExternalKind(e.kind))
+            return false;
+        switch (e.kind) {
+        case External::Function: {
+            uint32_t functionSignatureIndex;
+            if (!parseVarUInt32(functionSignatureIndex))
+                return false;
+            if (functionSignatureIndex > m_module->signatures.size())
+                return false;
+            e.functionSignature = &m_module->signatures[functionSignatureIndex];
+        } break;
+        case External::Table:
+            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164135
+            break;
+        case External::Memory:
+            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164134
+            break;
+        case External::Global:
+            // FIXME https://bugs.webkit.org/show_bug.cgi?id=164133
+            // In the MVP, only immutable global variables can be exported.
+            break;
+        }
+
+        m_module->exports.uncheckedAppend(e);
+    }
+
+    return true;
+}
+
+bool ModuleParser::parseStart()
+{
+    // FIXME https://bugs.webkit.org/show_bug.cgi?id=161709
+    return true;
+}
+
+bool ModuleParser::parseElement()
+{
+    // FIXME https://bugs.webkit.org/show_bug.cgi?id=161709
+    return true;
+}
+
+bool ModuleParser::parseCode()
 {
     uint32_t count;
     if (!parseVarUInt32(count))
         return false;
 
-    if (count != m_functions.size())
+    if (count != m_module->functions.size())
         return false;
 
-    for (uint32_t i = 0; i < count; ++i) {
+    for (uint32_t i = 0; i != count; ++i) {
         uint32_t functionSize;
         if (!parseVarUInt32(functionSize))
             return false;
+        if (functionSize > length() || functionSize > length() - m_offset)
+            return false;
 
-        FunctionInformation& info = m_functions[i];
+        FunctionInformation& info = m_module->functions[i];
         info.start = m_offset;
         info.end = m_offset + functionSize;
         m_offset = info.end;
@@ -314,6 +404,12 @@ bool ModuleParser::parseFunctionDefinitions()
     return true;
 }
 
+bool ModuleParser::parseData()
+{
+    // FIXME https://bugs.webkit.org/show_bug.cgi?id=161709
+    return true;
+}
+
 } } // namespace JSC::Wasm
 
 #endif // ENABLE(WEBASSEMBLY)
index 38293ac..899bf21 100644 (file)
@@ -27,7 +27,7 @@
 
 #if ENABLE(WEBASSEMBLY)
 
-#include "WasmMemory.h"
+#include "WasmFormat.h"
 #include "WasmOps.h"
 #include "WasmParser.h"
 #include <wtf/Vector.h>
@@ -56,27 +56,18 @@ public:
         return m_errorMessage;
     }
 
-    const Vector<FunctionInformation>& functionInformation() const
+    std::unique_ptr<ModuleInformation>& moduleInformation()
     {
         RELEASE_ASSERT(!failed());
-        return m_functions;
-    }
-    std::unique_ptr<Memory>& memory()
-    {
-        RELEASE_ASSERT(!failed());
-        return m_memory;
+        return m_module;
     }
 
 private:
-    bool WARN_UNUSED_RETURN parseMemory();
-    bool WARN_UNUSED_RETURN parseFunctionTypes();
-    bool WARN_UNUSED_RETURN parseFunctionSignatures();
-    bool WARN_UNUSED_RETURN parseFunctionDefinitions();
-    bool WARN_UNUSED_RETURN parseFunctionDefinition(uint32_t number);
+#define WASM_SECTION_DECLARE_PARSER(NAME, ID, DESCRIPTION) bool WARN_UNUSED_RETURN parse ## NAME();
+    FOR_EACH_WASM_SECTION(WASM_SECTION_DECLARE_PARSER)
+#undef WASM_SECTION_DECLARE_PARSER
 
-    Vector<FunctionInformation> m_functions;
-    Vector<Signature> m_signatures;
-    std::unique_ptr<Memory> m_memory;
+    std::unique_ptr<ModuleInformation> m_module;
     bool m_failed { true };
     String m_errorMessage;
 };
index 46a7b39..5908777 100644 (file)
@@ -33,6 +33,8 @@
 #include "WasmOps.h"
 #include "WasmSections.h"
 #include <wtf/LEBDecoder.h>
+#include <wtf/StdLibExtras.h>
+#include <wtf/text/WTFString.h>
 
 namespace JSC { namespace Wasm {
 
@@ -42,14 +44,17 @@ protected:
 
     bool WARN_UNUSED_RETURN consumeCharacter(char);
     bool WARN_UNUSED_RETURN consumeString(const char*);
+    bool WARN_UNUSED_RETURN consumeUTF8String(String&, size_t);
 
     bool WARN_UNUSED_RETURN parseVarUInt1(uint8_t& result);
+    bool WARN_UNUSED_RETURN parseInt7(int8_t& result);
     bool WARN_UNUSED_RETURN parseUInt7(uint8_t& result);
     bool WARN_UNUSED_RETURN parseUInt32(uint32_t& result);
     bool WARN_UNUSED_RETURN parseVarUInt32(uint32_t& result) { return WTF::LEBDecoder::decodeUInt32(m_source, m_sourceLength, m_offset, result); }
     bool WARN_UNUSED_RETURN parseVarUInt64(uint64_t& result) { return WTF::LEBDecoder::decodeUInt64(m_source, m_sourceLength, m_offset, result); }
 
     bool WARN_UNUSED_RETURN parseValueType(Type& result);
+    bool WARN_UNUSED_RETURN parseExternalKind(External::Kind& result);
 
     const uint8_t* source() const { return m_source; }
     size_t length() const { return m_sourceLength; }
@@ -92,6 +97,21 @@ ALWAYS_INLINE bool Parser::consumeString(const char* str)
     return true;
 }
 
+ALWAYS_INLINE bool Parser::consumeUTF8String(String& result, size_t stringLength)
+{
+    if (stringLength == 0) {
+        result = String();
+        return true;
+    }
+    if (length() < stringLength || m_offset > length() - stringLength)
+        return false;
+    result = String::fromUTF8(static_cast<const LChar*>(&source()[m_offset]), stringLength);
+    m_offset += stringLength;
+    if (result.isEmpty())
+        return false;
+    return true;
+}
+
 ALWAYS_INLINE bool Parser::parseUInt32(uint32_t& result)
 {
     if (length() < 4 || m_offset > length() - 4)
@@ -101,6 +121,15 @@ ALWAYS_INLINE bool Parser::parseUInt32(uint32_t& result)
     return true;
 }
 
+ALWAYS_INLINE bool Parser::parseInt7(int8_t& result)
+{
+    if (m_offset >= length())
+        return false;
+    uint8_t v = source()[m_offset++];
+    result = (v & 0x40) ? WTF::bitwise_cast<int8_t>(uint8_t(v | 0x80)) : v;
+    return (v & 0x80) == 0;
+}
+
 ALWAYS_INLINE bool Parser::parseUInt7(uint8_t& result)
 {
     if (m_offset >= length())
@@ -128,6 +157,17 @@ ALWAYS_INLINE bool Parser::parseValueType(Type& result)
     result = static_cast<Type>(value);
     return true;
 }
+    
+ALWAYS_INLINE bool Parser::parseExternalKind(External::Kind& result)
+{
+    uint8_t value;
+    if (!parseUInt7(value))
+        return false;
+    if (!External::isValid(value))
+        return false;
+    result = static_cast<External::Kind>(value);
+    return true;
+}
 
 } } // namespace JSC::Wasm
 
index 739be42..2e028d7 100644 (file)
 #include "B3Compilation.h"
 #include "WasmB3IRGenerator.h"
 #include "WasmCallingConvention.h"
+#include "WasmMemory.h"
 #include "WasmModuleParser.h"
 #include "WasmValidate.h"
 #include <wtf/DataLog.h>
+#include <wtf/text/StringBuilder.h>
 
 namespace JSC { namespace Wasm {
 
@@ -48,40 +50,50 @@ Plan::Plan(VM& vm, const uint8_t* source, size_t sourceLength)
 {
     if (verbose)
         dataLogLn("Starting plan.");
-    ModuleParser moduleParser(source, sourceLength);
-    if (!moduleParser.parse()) {
-        dataLogLn("Parsing module failed: ", moduleParser.errorMessage());
-        m_errorMessage = moduleParser.errorMessage();
-        return;
+    {
+        ModuleParser moduleParser(source, sourceLength);
+        if (!moduleParser.parse()) {
+            dataLogLn("Parsing module failed: ", moduleParser.errorMessage());
+            m_errorMessage = moduleParser.errorMessage();
+            return;
+        }
+        m_moduleInformation = WTFMove(moduleParser.moduleInformation());
     }
-
     if (verbose)
         dataLogLn("Parsed module.");
 
-    for (const FunctionInformation& info : moduleParser.functionInformation()) {
+    if (!m_compiledFunctions.tryReserveCapacity(m_moduleInformation->functions.size())) {
+        StringBuilder builder;
+        builder.appendLiteral("Failed allocating enough space for ");
+        builder.appendNumber(m_moduleInformation->functions.size());
+        builder.appendLiteral(" compiled functions");
+        m_errorMessage = builder.toString();
+        return;
+    }
+
+    for (const FunctionInformation& info : m_moduleInformation->functions) {
         if (verbose)
-            dataLogLn("Processing funcion starting at: ", info.start, " and ending at: ", info.end);
+            dataLogLn("Processing function starting at: ", info.start, " and ending at: ", info.end);
         const uint8_t* functionStart = source + info.start;
         size_t functionLength = info.end - info.start;
         ASSERT(functionLength <= sourceLength);
 
-        String error = validateFunction(functionStart, functionLength, info.signature, moduleParser.functionInformation());
+        String error = validateFunction(functionStart, functionLength, info.signature, m_moduleInformation->functions);
         if (!error.isNull()) {
             m_errorMessage = error;
             return;
         }
 
-        m_result.append(parseAndCompile(vm, functionStart, functionLength, moduleParser.memory().get(), info.signature, moduleParser.functionInformation()));
+        m_compiledFunctions.uncheckedAppend(parseAndCompile(vm, functionStart, functionLength, m_moduleInformation->memory.get(), info.signature, m_moduleInformation->functions));
     }
 
     // Patch the call sites for each function.
-    for (std::unique_ptr<FunctionCompilation>& functionPtr : m_result) {
+    for (std::unique_ptr<FunctionCompilation>& functionPtr : m_compiledFunctions) {
         FunctionCompilation* function = functionPtr.get();
         for (auto& call : function->unlinkedCalls)
-            MacroAssembler::repatchCall(call.callLocation, CodeLocationLabel(m_result[call.functionIndex]->code->code()));
+            MacroAssembler::repatchCall(call.callLocation, CodeLocationLabel(m_compiledFunctions[call.functionIndex]->code->code()));
     }
 
-    m_memory = WTFMove(moduleParser.memory());
     m_failed = false;
 }
 
index b84f9d7..d7c03ab 100644 (file)
@@ -38,8 +38,6 @@ class Memory;
 
 class Plan {
 public:
-    typedef Vector<std::unique_ptr<FunctionCompilation>> CompiledFunctions;
-
     JS_EXPORT_PRIVATE Plan(VM&, Vector<uint8_t>);
     JS_EXPORT_PRIVATE Plan(VM&, const uint8_t*, size_t);
     JS_EXPORT_PRIVATE ~Plan();
@@ -50,36 +48,37 @@ public:
         RELEASE_ASSERT(failed());
         return m_errorMessage;
     }
-    size_t resultSize() const
+    
+    std::unique_ptr<ModuleInformation>& getModuleInformation()
     {
         RELEASE_ASSERT(!failed());
-        return m_result.size();
+        return m_moduleInformation;
     }
-    const FunctionCompilation* result(size_t n) const
+    const Memory* memory() const
     {
         RELEASE_ASSERT(!failed());
-        return m_result.at(n).get();
+        return m_moduleInformation->memory.get();
     }
-    const Memory* memory() const
+    size_t compiledFunctionCount() const
     {
         RELEASE_ASSERT(!failed());
-        return m_memory.get();
+        return m_compiledFunctions.size();
     }
-    
-    CompiledFunctions* getFunctions()
+    const FunctionCompilation* compiledFunction(size_t i) const
     {
         RELEASE_ASSERT(!failed());
-        return &m_result;
+        return m_compiledFunctions.at(i).get();
     }
-    std::unique_ptr<Memory>* getMemory()
+    CompiledFunctions& getCompiledFunctions()
     {
         RELEASE_ASSERT(!failed());
-        return &m_memory;
+        return m_compiledFunctions;
     }
 
 private:
-    CompiledFunctions m_result;
-    std::unique_ptr<Memory> m_memory;
+    std::unique_ptr<ModuleInformation> m_moduleInformation;
+    CompiledFunctions m_compiledFunctions;
+
     bool m_failed { true };
     String m_errorMessage;
 };
index 7257867..9960524 100644 (file)
 
 namespace JSC { namespace Wasm {
 
+#define FOR_EACH_WASM_SECTION(macro) \
+    macro(Type,     1, "Function signature declarations") \
+    macro(Import,   2, "Import declarations") \
+    macro(Function, 3, "Function declarations") \
+    macro(Table,    4, "Indirect function table and other tables") \
+    macro(Memory,   5, "Memory attributes") \
+    macro(Global,   6, "Global declarations") \
+    macro(Export,   7, "Exports") \
+    macro(Start,    8, "Start function declaration") \
+    macro(Element,  9, "Elements section") \
+    macro(Code,    10, "Function bodies (code)") \
+    macro(Data,    11, "Data segments")
+
 struct Sections {
     enum Section : uint8_t {
-        FunctionTypes = 1,
-        Signatures = 3,
-        Memory = 5,
-        Definitions = 10,
+#define DEFINE_WASM_SECTION_ENUM(NAME, ID, DESCRIPTION) NAME = ID,
+        FOR_EACH_WASM_SECTION(DEFINE_WASM_SECTION_ENUM)
+#undef DEFINE_WASM_SECTION_ENUM
         Unknown
     };
     static bool validateOrder(Section previous, Section next)
index 65cca95..d6ba381 100644 (file)
@@ -35,9 +35,9 @@
 
 namespace JSC {
 
-JSWebAssemblyModule* JSWebAssemblyModule::create(VM& vm, Structure* structure, Vector<std::unique_ptr<Wasm::FunctionCompilation>>* compiledFunctions, std::unique_ptr<Wasm::Memory>* memory)
+JSWebAssemblyModule* JSWebAssemblyModule::create(VM& vm, Structure* structure, std::unique_ptr<Wasm::ModuleInformation>& moduleInformation, Wasm::CompiledFunctions& compiledFunctions)
 {
-    auto* instance = new (NotNull, allocateCell<JSWebAssemblyModule>(vm.heap)) JSWebAssemblyModule(vm, structure, compiledFunctions, memory);
+    auto* instance = new (NotNull, allocateCell<JSWebAssemblyModule>(vm.heap)) JSWebAssemblyModule(vm, structure, moduleInformation, compiledFunctions);
     instance->finishCreation(vm);
     return instance;
 }
@@ -47,10 +47,10 @@ Structure* JSWebAssemblyModule::createStructure(VM& vm, JSGlobalObject* globalOb
     return Structure::create(vm, globalObject, prototype, TypeInfo(ObjectType, StructureFlags), info());
 }
 
-JSWebAssemblyModule::JSWebAssemblyModule(VM& vm, Structure* structure, Vector<std::unique_ptr<Wasm::FunctionCompilation>>* compiledFunctions, std::unique_ptr<Wasm::Memory>* memory)
+JSWebAssemblyModule::JSWebAssemblyModule(VM& vm, Structure* structure, std::unique_ptr<Wasm::ModuleInformation>& moduleInformation, Wasm::CompiledFunctions& compiledFunctions)
     : Base(vm, structure)
-    , m_compiledFunctions(WTFMove(*compiledFunctions))
-    , m_memory(WTFMove(*memory))
+    , m_moduleInformation(WTFMove(moduleInformation))
+    , m_compiledFunctions(WTFMove(compiledFunctions))
 {
 }
 
index a438a61..9da2122 100644 (file)
 
 #include "JSDestructibleObject.h"
 #include "JSObject.h"
+#include "WasmFormat.h"
 
 namespace JSC {
 
-namespace Wasm {
-struct FunctionCompilation;
-class Memory;
-}
-
 class JSWebAssemblyModule : public JSDestructibleObject {
 public:
     typedef JSDestructibleObject Base;
 
-    static JSWebAssemblyModule* create(VM&, Structure*, Vector<std::unique_ptr<Wasm::FunctionCompilation>>*, std::unique_ptr<Wasm::Memory>*);
+    static JSWebAssemblyModule* create(VM&, Structure*, std::unique_ptr<Wasm::ModuleInformation>&, Wasm::CompiledFunctions&);
     static Structure* createStructure(VM&, JSGlobalObject*, JSValue);
 
     DECLARE_INFO;
 
+    const Wasm::ModuleInformation* moduleInformation() const
+    {
+        return m_moduleInformation.get();
+    }
+
 protected:
-    JSWebAssemblyModule(VM&, Structure*, Vector<std::unique_ptr<Wasm::FunctionCompilation>>*, std::unique_ptr<Wasm::Memory>*);
+    JSWebAssemblyModule(VM&, Structure*, std::unique_ptr<Wasm::ModuleInformation>&, Wasm::CompiledFunctions&);
     void finishCreation(VM&);
     static void destroy(JSCell*);
     static void visitChildren(JSCell*, SlotVisitor&);
-
 private:
-    Vector<std::unique_ptr<Wasm::FunctionCompilation>> m_compiledFunctions;
-    std::unique_ptr<Wasm::Memory> m_memory;
+    std::unique_ptr<Wasm::ModuleInformation> m_moduleInformation;
+    Wasm::CompiledFunctions m_compiledFunctions;
 };
 
 } // namespace JSC
index a9ff11d..c8aea2f 100644 (file)
@@ -64,8 +64,9 @@ static EncodedJSValue JSC_HOST_CALL constructJSWebAssemblyInstance(ExecState* st
     if (!importArgument.isUndefined() && !importObject)
         return JSValue::encode(throwException(state, scope, createTypeError(state, ASCIILiteral("second argument to WebAssembly.Instance must be undefined or an Object"), defaultSourceAppender, runtimeTypeForValue(importArgument))));
 
-    // FIXME use the importObject. https://bugs.webkit.org/show_bug.cgi?id=164039
     // If the list of module.imports is not empty and Type(importObject) is not Object, a TypeError is thrown.
+    if (module->moduleInformation()->imports.size() && !importObject)
+        return JSValue::encode(throwException(state, scope, createTypeError(state, ASCIILiteral("second argument to WebAssembly.Instance must be Object because the WebAssembly.Module has imports"), defaultSourceAppender, runtimeTypeForValue(importArgument))));
 
     // FIXME String things from https://bugs.webkit.org/show_bug.cgi?id=164023
     // Let exports be a list of (string, JS value) pairs that is mapped from each external value e in instance.exports as follows:
index 1c582b4..cfcac60 100644 (file)
@@ -47,6 +47,7 @@ static EncodedJSValue JSC_HOST_CALL constructJSWebAssemblyMemory(ExecState* stat
 {
     VM& vm = state->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
+    // FIXME https://bugs.webkit.org/show_bug.cgi?id=164134
     return JSValue::encode(throwException(state, scope, createError(state, ASCIILiteral("WebAssembly doesn't yet implement the Memory constructor property"))));
 }
 
index 0b055a0..bb684e1 100644 (file)
@@ -73,13 +73,11 @@ static EncodedJSValue JSC_HOST_CALL constructJSWebAssemblyModule(ExecState* stat
     if (plan.failed())
         return JSValue::encode(throwException(state, scope, createWebAssemblyCompileError(state, plan.errorMessage())));
 
-    // The spec string values inside Ast.module are decoded as UTF8 as described in Web.md. FIXME https://bugs.webkit.org/show_bug.cgi?id=164023
-
     // On success, a new WebAssembly.Module object is returned with [[Module]] set to the validated Ast.module.
     auto* structure = InternalFunction::createSubclassStructure(state, state->newTarget(), asInternalFunction(state->callee())->globalObject()->WebAssemblyModuleStructure());
     RETURN_IF_EXCEPTION(scope, encodedJSValue());
 
-    return JSValue::encode(JSWebAssemblyModule::create(vm, structure, plan.getFunctions(), plan.getMemory()));
+    return JSValue::encode(JSWebAssemblyModule::create(vm, structure, plan.getModuleInformation(), plan.getCompiledFunctions()));
 }
 
 static EncodedJSValue JSC_HOST_CALL callJSWebAssemblyModule(ExecState* state)
index caa7e9e..c9580ba 100644 (file)
@@ -47,6 +47,7 @@ static EncodedJSValue JSC_HOST_CALL constructJSWebAssemblyTable(ExecState* state
 {
     VM& vm = state->vm();
     auto scope = DECLARE_THROW_SCOPE(vm);
+    // FIXME https://bugs.webkit.org/show_bug.cgi?id=164135
     return JSValue::encode(throwException(state, scope, createError(state, ASCIILiteral("WebAssembly doesn't yet implement the Table constructor property"))));
 }