WebKit Bugzilla
Attachment 372518 Details for
Bug 199044
: [WASM-References] Add extra tests for Wasm references + fix element parsing and subtyping bugs
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
Patch
bug-199044-20190619193111.patch (text/plain), 149.14 KB, created by
Justin Michaud
on 2019-06-19 19:31:12 PDT
(
hide
)
Description:
Patch
Filename:
MIME Type:
Creator:
Justin Michaud
Created:
2019-06-19 19:31:12 PDT
Size:
149.14 KB
patch
obsolete
>Subversion Revision: 246589 >diff --git a/Source/JavaScriptCore/ChangeLog b/Source/JavaScriptCore/ChangeLog >index 885669589b230939c80c69e9d1bc86f593fd78e1..4dbd315a38a25f96c3cc41dbfc4462aca5d0a870 100644 >--- a/Source/JavaScriptCore/ChangeLog >+++ b/Source/JavaScriptCore/ChangeLog >@@ -1,3 +1,20 @@ >+2019-06-19 Justin Michaud <justin_michaud@apple.com> >+ >+ [WASM-References] Add extra tests for Wasm references + fix element parsing and subtyping bugs >+ https://bugs.webkit.org/show_bug.cgi?id=199044 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Fix parsing table indices from the element section. The byte that we previously read as the table index actually tells us how to parse the table index. >+ Fix some areas where we got the isSubtype check wrong, causing funcrefs to not be considred anyrefs. >+ >+ * wasm/WasmAirIRGenerator.cpp: >+ (JSC::Wasm::AirIRGenerator::unify): >+ * wasm/WasmSectionParser.cpp: >+ (JSC::Wasm::SectionParser::parseElement): >+ * wasm/WasmValidate.cpp: >+ (JSC::Wasm::Validate::unify): >+ > 2019-06-19 Justin Michaud <justin_michaud@apple.com> > > [WASM-References] Rename anyfunc to funcref >diff --git a/Source/JavaScriptCore/wasm/WasmAirIRGenerator.cpp b/Source/JavaScriptCore/wasm/WasmAirIRGenerator.cpp >index d908d82ae45534ce1fa814c5467c184911f07079..91e8e594836d9632ce67e4cf8babad8b3637e778 100644 >--- a/Source/JavaScriptCore/wasm/WasmAirIRGenerator.cpp >+++ b/Source/JavaScriptCore/wasm/WasmAirIRGenerator.cpp >@@ -2115,7 +2115,7 @@ auto AirIRGenerator::addCallIndirect(unsigned tableIndex, const Signature& signa > > void AirIRGenerator::unify(const ExpressionType& dst, const ExpressionType& source) > { >- ASSERT(dst.type() == source.type()); >+ ASSERT(isSubtype(source.type(), dst.type())); > append(moveOpForValueType(dst.type()), source, dst); > } > >diff --git a/Source/JavaScriptCore/wasm/WasmSectionParser.cpp b/Source/JavaScriptCore/wasm/WasmSectionParser.cpp >index 14cf280c87484ea7e35a8fd2ab5a8af6d6be877d..57adeb3412cf4ca6bc2219d1375df11e22ce5415 100644 >--- a/Source/JavaScriptCore/wasm/WasmSectionParser.cpp >+++ b/Source/JavaScriptCore/wasm/WasmSectionParser.cpp >@@ -376,7 +376,14 @@ auto SectionParser::parseElement() -> PartialResult > uint8_t initOpcode; > uint32_t indexCount; > >- WASM_PARSER_FAIL_IF(!parseVarUInt32(tableIndex), "can't get ", elementNum, "th Element table index"); >+ uint8_t magic; >+ WASM_PARSER_FAIL_IF(!parseUInt8(magic) || (!magic && magic != 2), "can't get ", elementNum, "th Element reserved byte, which should be either 0x00 or 0x02 followed by a table index"); >+ >+ if (magic == 2) >+ WASM_PARSER_FAIL_IF(!parseVarUInt32(tableIndex), "can't get ", elementNum, "th Element table index"); >+ else >+ tableIndex = 0; >+ > WASM_PARSER_FAIL_IF(tableIndex >= m_info->tableCount(), "Element section for Table ", tableIndex, " exceeds available Table ", m_info->tableCount()); > WASM_PARSER_FAIL_IF(m_info->tables[tableIndex].type() != TableElementType::Funcref, "Table ", tableIndex, " must have type 'funcref' to have an element section"); > Type initExprType; >diff --git a/Source/JavaScriptCore/wasm/WasmValidate.cpp b/Source/JavaScriptCore/wasm/WasmValidate.cpp >index bee09ea825f4cd7b4986bd960579ea10e5f6eee2..25c72b64b382720038c3b6d4b5ae834ee017447c 100644 >--- a/Source/JavaScriptCore/wasm/WasmValidate.cpp >+++ b/Source/JavaScriptCore/wasm/WasmValidate.cpp >@@ -434,7 +434,7 @@ auto Validate::unify(const ExpressionList& values, const ControlType& block) -> > } > > WASM_VALIDATOR_FAIL_IF(values.size() != 1, "block with type: ", block.signature(), " ends with a stack containing more than one value"); >- WASM_VALIDATOR_FAIL_IF(values[0] != block.signature(), "control flow returns with unexpected type"); >+ WASM_VALIDATOR_FAIL_IF(!isSubtype(values[0], block.signature()), "control flow returns with unexpected type"); > return { }; > } > >diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog >index 46d15ac6d5f0590f8f9fc336c108cd84a56b0bbf..26ed1b53f906713ed4597251d9ff18dd774574c6 100644 >--- a/LayoutTests/ChangeLog >+++ b/LayoutTests/ChangeLog >@@ -1,3 +1,35 @@ >+2019-06-19 Justin Michaud <justin_michaud@apple.com> >+ >+ [WASM-References] Add extra tests for Wasm references + fix element parsing and subtyping bugs >+ https://bugs.webkit.org/show_bug.cgi?id=199044 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Add wasm references spec tests as well as a worker test. >+ >+ * workers/wasm-references.html: Added. >+ * workers/wasm-references/test.js: Added. >+ (const._fail): >+ (const.isNotA.assert.isNotA): >+ (const): >+ (switch.typeof): >+ (Builder): >+ (Builder.prototype.setChecked): >+ (Builder.prototype.setPreamble): >+ (Builder.prototype._functionIndexSpaceKeyHash): >+ (Builder.prototype._registerFunctionToIndexSpace): >+ (Builder.prototype._getFunctionFromIndexSpace): >+ (Builder.prototype._registerSectionBuilders.const.section.in.WASM.description.section.switch.section.case.string_appeared_here.this.section): >+ (Builder.prototype._registerSectionBuilders.const.section.in.WASM.description.section.switch.section.const.codeBuilder.End.switch.case.string_appeared_here.e): >+ (Builder.prototype._registerSectionBuilders.this.Unknown): >+ (done): >+ (runTest.worker.onmessage): >+ (runTest): >+ (doGC): >+ * workers/wasm-references/worker.js: Added. >+ (const._fail): >+ (const.isNotA.assert.isNotA): >+ > 2019-06-19 Cathie Chen <cathiechen@igalia.com> > > resize-observer/element-leak.html is a flaky failure >diff --git a/LayoutTests/workers/wasm-references-expected.txt b/LayoutTests/workers/wasm-references-expected.txt >new file mode 100644 >index 0000000000000000000000000000000000000000..9fd5cc5c247a2de74cdc7088ec68de8b4bb9c1cd >--- /dev/null >+++ b/LayoutTests/workers/wasm-references-expected.txt >@@ -0,0 +1,5 @@ >+CONSOLE MESSAGE: line 1836: Finished test >+PASS successfullyParsed is true >+ >+TEST COMPLETE >+ >diff --git a/LayoutTests/workers/wasm-references.html b/LayoutTests/workers/wasm-references.html >new file mode 100644 >index 0000000000000000000000000000000000000000..348241b879b5597f9d005db62308b7280c68b574 >--- /dev/null >+++ b/LayoutTests/workers/wasm-references.html >@@ -0,0 +1,13 @@ >+<html> >+<head> >+<script src="../resources/js-test-pre.js"></script> >+</head> >+<body> >+<script> >+if (window.testRunner) >+ testRunner.waitUntilDone(); >+</script> >+<script src="./wasm-references/test.js"></script> >+<script src="../resources/js-test-post.js"></script> >+</body> >+</html> >diff --git a/LayoutTests/workers/wasm-references/test.js b/LayoutTests/workers/wasm-references/test.js >new file mode 100644 >index 0000000000000000000000000000000000000000..6c405b63a3efbfe86d23a31898c563934753bcb0 >--- /dev/null >+++ b/LayoutTests/workers/wasm-references/test.js >@@ -0,0 +1,1905 @@ >+/* >+ * Copyright (C) 2019 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. >+ */ >+ >+const _fail = (msg, extra) => { >+ throw new Error(msg + (extra ? ": " + extra : "")); >+}; >+ >+const assert = {}; >+ >+const isNotA = assert.isNotA = (v, t, msg) => { >+ if (typeof v === t) >+ _fail(`Shouldn't be ${t}`, msg); >+}; >+ >+const isA = assert.isA = (v, t, msg) => { >+ if (typeof v !== t) >+ _fail(`Should be ${t}, got ${typeof(v)}`, msg); >+}; >+ >+const isNotUndef = assert.isNotUndef = (v, msg) => isNotA(v, "undefined", msg); >+assert.isUndef = (v, msg) => isA(v, "undefined", msg); >+assert.notObject = (v, msg) => isNotA(v, "object", msg); >+const isObject = assert.isObject = (v, msg) => isA(v, "object", msg); >+assert.notString = (v, msg) => isNotA(v, "string", msg); >+assert.isString = (v, msg) => isA(v, "string", msg); >+assert.notNumber = (v, msg) => isNotA(v, "number", msg); >+assert.isNumber = (v, msg) => isA(v, "number", msg); >+assert.notFunction = (v, msg) => isNotA(v, "function", msg); >+assert.isFunction = (v, msg) => isA(v, "function", msg); >+ >+assert.hasObjectProperty = (o, p, msg) => { >+ isObject(o, msg); >+ isNotUndef(o[p], msg, `expected object to have property ${p}`); >+}; >+ >+assert.isArray = (v, msg) => { >+ if (!Array.isArray(v)) >+ _fail(`Expected an array, got ${typeof(v)}`, msg); >+}; >+ >+assert.isNotArray = (v, msg) => { >+ if (Array.isArray(v)) >+ _fail(`Expected to not be an array`, msg); >+}; >+ >+assert.truthy = (v, msg) => { >+ if (!v) >+ _fail(`Expected truthy`, msg); >+}; >+ >+assert.falsy = (v, msg) => { >+ if (v) >+ _fail(`Expected falsy`, msg); >+}; >+ >+assert.eq = (lhs, rhs, msg) => { >+ if (typeof lhs !== typeof rhs) >+ _fail(`Not the same: "${lhs}" and "${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) { >+ if (typeof lhs === "number" && isNaN(lhs) && isNaN(rhs)) >+ return; >+ _fail(`Not the same: "${lhs}" and "${rhs}"`, msg); >+ } else { >+ if (typeof lhs === "number" && (1.0 / lhs !== 1.0 / rhs)) // Distinguish -0.0 from 0.0. >+ _fail(`Not the same: "${lhs}" and "${rhs}"`, msg); >+ } >+}; >+ >+const canonicalizeI32 = (number) => { >+ if (Math.round(number) === number && number >= 2 ** 31) >+ number = number - 2 ** 32; >+ return number; >+} >+ >+assert.eqI32 = (lhs, rhs, msg) => { >+ return eq(canonicalizeI32(lhs), canonicalizeI32(rhs), msg); >+}; >+ >+assert.ge = (lhs, rhs, msg) => { >+ isNotUndef(lhs); >+ isNotUndef(rhs); >+ if (!(lhs >= rhs)) >+ _fail(`Expected: "${lhs}" < "${rhs}"`, msg); >+}; >+ >+assert.le = (lhs, rhs, msg) => { >+ isNotUndef(lhs); >+ isNotUndef(rhs); >+ if (!(lhs <= rhs)) >+ _fail(`Expected: "${lhs}" > "${rhs}"`, msg); >+}; >+ >+const _throws = (func, type, message, ...args) => { >+ try { >+ func(...args); >+ } catch (e) { >+ if (e instanceof type) { >+ if (e.message === message) >+ return e; >+ // Ignore source information at the end of the error message if the >+ // expected message didn't specify that information. Sometimes it >+ // changes, or it's tricky to get just right. >+ const evaluatingIndex = e.message.indexOf(" (evaluating '"); >+ if (evaluatingIndex !== -1) { >+ const cleanMessage = e.message.substring(0, evaluatingIndex); >+ if (cleanMessage === message) >+ return e; >+ } >+ } >+ _fail(`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}"`); >+}; >+ >+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. >+assert.throws = _throws; >+assert.instanceof = _instanceof; >+ >+const asyncTestImpl = (promise, thenFunc, catchFunc) => { >+ asyncTestStart(1); >+ promise.then(thenFunc).catch(catchFunc); >+}; >+ >+const printExn = (e) => { >+ print("Failed: ", e); >+ print(e.stack); >+}; >+ >+assert.asyncTest = (promise) => asyncTestImpl(promise, asyncTestPassed, printExn); >+assert.asyncTestEq = (promise, expected) => { >+ const thenCheck = (value) => { >+ if (value === expected) >+ return asyncTestPassed(); >+ print("Failed: got ", value, " but expected ", expected); >+ >+ } >+ asyncTestImpl(promise, thenCheck, printExn); >+}; >+ >+const WASM_JSON = ` >+{ >+ "comments": ["This file describes the WebAssembly ISA.", >+ "Scripts in this folder auto-generate C++ code for JavaScriptCore as well as the testing DSL which WebKit's WebAssembly tests use." >+ ], >+ "preamble": [ >+ { "name": "magic number", "type": "uint32", "value": 1836278016, "description": "NULL character followed by 'asm'" }, >+ { "name": "version", "type": "uint32", "value": 1, "description": "Version number" } >+ ], >+ "type" : { >+ "i32": { "type": "varint7", "value": -1, "b3type": "B3::Int32" }, >+ "i64": { "type": "varint7", "value": -2, "b3type": "B3::Int64" }, >+ "f32": { "type": "varint7", "value": -3, "b3type": "B3::Float" }, >+ "f64": { "type": "varint7", "value": -4, "b3type": "B3::Double" }, >+ "funcref": { "type": "varint7", "value": -16, "b3type": "B3::Int64" }, >+ "anyref": { "type": "varint7", "value": -17, "b3type": "B3::Int64" }, >+ "func": { "type": "varint7", "value": -32, "b3type": "B3::Void" }, >+ "void": { "type": "varint7", "value": -64, "b3type": "B3::Void" } >+ }, >+ "value_type": ["i32", "i64", "f32", "f64", "anyref", "funcref"], >+ "block_type": ["i32", "i64", "f32", "f64", "void", "anyref", "funcref"], >+ "elem_type": ["funcref","anyref"], >+ "external_kind": { >+ "Function": { "type": "uint8", "value": 0 }, >+ "Table": { "type": "uint8", "value": 1 }, >+ "Memory": { "type": "uint8", "value": 2 }, >+ "Global": { "type": "uint8", "value": 3 } >+ }, >+ "section" : { >+ "Type": { "type": "varuint7", "value": 1, "description": "Function signature declarations" }, >+ "Import": { "type": "varuint7", "value": 2, "description": "Import declarations" }, >+ "Function": { "type": "varuint7", "value": 3, "description": "Function declarations" }, >+ "Table": { "type": "varuint7", "value": 4, "description": "Indirect function table and other tables" }, >+ "Memory": { "type": "varuint7", "value": 5, "description": "Memory attributes" }, >+ "Global": { "type": "varuint7", "value": 6, "description": "Global declarations" }, >+ "Export": { "type": "varuint7", "value": 7, "description": "Exports" }, >+ "Start": { "type": "varuint7", "value": 8, "description": "Start function declaration" }, >+ "Element": { "type": "varuint7", "value": 9, "description": "Elements section" }, >+ "Code": { "type": "varuint7", "value": 10, "description": "Function bodies (code)" }, >+ "Data": { "type": "varuint7", "value": 11, "description": "Data segments" } >+ }, >+ "opcode": { >+ "unreachable": { "category": "control", "value": 0, "return": [], "parameter": [], "immediate": [], "description": "trap immediately" }, >+ "block": { "category": "control", "value": 2, "return": ["control"], "parameter": [], "immediate": [{"name": "sig", "type": "block_type"}], "description": "begin a sequence of expressions, yielding 0 or 1 values" }, >+ "loop": { "category": "control", "value": 3, "return": ["control"], "parameter": [], "immediate": [{"name": "sig", "type": "block_type"}], "description": "begin a block which can also form control flow loops" }, >+ "if": { "category": "control", "value": 4, "return": ["control"], "parameter": ["bool"], "immediate": [{"name": "sig", "type": "block_type"}], "description": "begin if expression" }, >+ "else": { "category": "control", "value": 5, "return": ["control"], "parameter": [], "immediate": [], "description": "begin else expression of if" }, >+ "select": { "category": "control", "value": 27, "return": ["prev"], "parameter": ["any", "prev", "bool"], "immediate": [], "description": "select one of two values based on condition" }, >+ "br": { "category": "control", "value": 12, "return": [], "parameter": [], "immediate": [{"name": "relative_depth", "type": "varuint32"}], "description": "break that targets an outer nested block" }, >+ "br_if": { "category": "control", "value": 13, "return": [], "parameter": [], "immediate": [{"name": "relative_depth", "type": "varuint32"}], "description": "conditional break that targets an outer nested block" }, >+ "br_table": { "category": "control", "value": 14, "return": [], "parameter": [], "immediate": [{"name": "target_count", "type": "varuint32", "description": "number of entries in the target_table"}, >+ {"name": "target_table", "type": "varuint32*", "description": "target entries that indicate an outer block or loop to which to break"}, >+ {"name": "default_target", "type": "varuint32", "description": "an outer block or loop to which to break in the default case"}], >+ "description": "branch table control flow construct" }, >+ "return": { "category": "control", "value": 15, "return": [], "parameter": [], "immediate": [], "description": "return zero or one value from this function" }, >+ "drop": { "category": "control", "value": 26, "return": [], "parameter": ["any"], "immediate": [], "description": "ignore value" }, >+ "nop": { "category": "control", "value": 1, "return": [], "parameter": [], "immediate": [], "description": "no operation" }, >+ "end": { "category": "control", "value": 11, "return": [], "parameter": [], "immediate": [], "description": "end a block, loop, or if" }, >+ "i32.const": { "category": "special", "value": 65, "return": ["i32"], "parameter": [], "immediate": [{"name": "value", "type": "varint32"}], "description": "a constant value interpreted as i32" }, >+ "i64.const": { "category": "special", "value": 66, "return": ["i64"], "parameter": [], "immediate": [{"name": "value", "type": "varint64"}], "description": "a constant value interpreted as i64" }, >+ "f64.const": { "category": "special", "value": 68, "return": ["f64"], "parameter": [], "immediate": [{"name": "value", "type": "double"}], "description": "a constant value interpreted as f64" }, >+ "f32.const": { "category": "special", "value": 67, "return": ["f32"], "parameter": [], "immediate": [{"name": "value", "type": "float"}], "description": "a constant value interpreted as f32" }, >+ "ref.null": { "category": "special", "value": 208, "return": ["funcref"], "parameter": [], "immediate": [], "description": "a constant null reference" }, >+ "ref.is_null": { "category": "special", "value": 209, "return": ["i32"], "parameter": ["anyref"], "immediate": [], "description": "determine if a reference is null" }, >+ "ref.func": { "category": "special", "value": 210, "return": ["funcref"], "parameter": [], "immediate": [{"name": "function_index", "type": "varuint32"}], "description": "return a reference to the function at the given index" }, >+ "get_local": { "category": "special", "value": 32, "return": ["any"], "parameter": [], "immediate": [{"name": "local_index", "type": "varuint32"}], "description": "read a local variable or parameter" }, >+ "set_local": { "category": "special", "value": 33, "return": [], "parameter": ["any"], "immediate": [{"name": "local_index", "type": "varuint32"}], "description": "write a local variable or parameter" }, >+ "tee_local": { "category": "special", "value": 34, "return": ["any"], "parameter": ["any"], "immediate": [{"name": "local_index", "type": "varuint32"}], "description": "write a local variable or parameter and return the same value" }, >+ "get_global": { "category": "special", "value": 35, "return": ["any"], "parameter": [], "immediate": [{"name": "global_index", "type": "varuint32"}], "description": "read a global variable" }, >+ "set_global": { "category": "special", "value": 36, "return": [], "parameter": ["any"], "immediate": [{"name": "global_index", "type": "varuint32"}], "description": "write a global variable" }, >+ "table.get": { "category": "special", "value": 37, "return": ["anyref"], "parameter": ["i32"], "immediate": [{"name": "table_index", "type": "varuint32"}], "description": "get a table value" }, >+ "table.set": { "category": "special", "value": 38, "return": [], "parameter": ["i32", "anyref"], "immediate": [{"name": "table_index", "type": "varuint32"}], "description": "set a table value" }, >+ "table.size": { "category": "exttable", "value": 252, "return": ["i32"], "parameter": [], "immediate": [{"name": "global_index", "type": "varuint32"}], "description": "get the size of a table", "extendedOp": 15 }, >+ "table.grow": { "category": "exttable", "value": 252, "return": ["i32"], "parameter": ["anyref", "i32"], "immediate": [{"name": "global_index", "type": "varuint32"}], "description": "grow a table by the given delta and return the previous size, or -1 if enough space cannot be allocated", "extendedOp": 16 }, >+ "table.fill": { "category": "exttable", "value": 252, "return": ["i32"], "parameter": ["i32", "anyref", "i32"], "immediate": [{"name": "global_index", "type": "varuint32"}], "description": "fill entries [i,i+n) with the given value", "extendedOp": 17 }, >+ "call": { "category": "call", "value": 16, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "function_index", "type": "varuint32"}], "description": "call a function by its index" }, >+ "call_indirect": { "category": "call", "value": 17, "return": ["call"], "parameter": ["call"], "immediate": [{"name": "type_index", "type": "varuint32"}, {"name": "table_index","type": "varuint32"}],"description": "call a function indirect with an expected signature" }, >+ "i32.load8_s": { "category": "memory", "value": 44, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i32.load8_u": { "category": "memory", "value": 45, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i32.load16_s": { "category": "memory", "value": 46, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i32.load16_u": { "category": "memory", "value": 47, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i64.load8_s": { "category": "memory", "value": 48, "return": ["i64"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i64.load8_u": { "category": "memory", "value": 49, "return": ["i64"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i64.load16_s": { "category": "memory", "value": 50, "return": ["i64"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i64.load16_u": { "category": "memory", "value": 51, "return": ["i64"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i64.load32_s": { "category": "memory", "value": 52, "return": ["i64"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i64.load32_u": { "category": "memory", "value": 53, "return": ["i64"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i32.load": { "category": "memory", "value": 40, "return": ["i32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i64.load": { "category": "memory", "value": 41, "return": ["i64"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "f32.load": { "category": "memory", "value": 42, "return": ["f32"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "f64.load": { "category": "memory", "value": 43, "return": ["f64"], "parameter": ["addr"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "load from memory" }, >+ "i32.store8": { "category": "memory", "value": 58, "return": [], "parameter": ["addr", "i32"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "store to memory" }, >+ "i32.store16": { "category": "memory", "value": 59, "return": [], "parameter": ["addr", "i32"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "store to memory" }, >+ "i64.store8": { "category": "memory", "value": 60, "return": [], "parameter": ["addr", "i64"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "store to memory" }, >+ "i64.store16": { "category": "memory", "value": 61, "return": [], "parameter": ["addr", "i64"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "store to memory" }, >+ "i64.store32": { "category": "memory", "value": 62, "return": [], "parameter": ["addr", "i64"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "store to memory" }, >+ "i32.store": { "category": "memory", "value": 54, "return": [], "parameter": ["addr", "i32"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "store to memory" }, >+ "i64.store": { "category": "memory", "value": 55, "return": [], "parameter": ["addr", "i64"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "store to memory" }, >+ "f32.store": { "category": "memory", "value": 56, "return": [], "parameter": ["addr", "f32"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "store to memory" }, >+ "f64.store": { "category": "memory", "value": 57, "return": [], "parameter": ["addr", "f64"], "immediate": [{"name": "flags", "type": "varuint32"}, {"name": "offset", "type": "varuint32"}], "description": "store to memory" }, >+ "current_memory": { "category": "operation", "value": 63, "return": ["size"], "parameter": [], "immediate": [{"name": "flags", "type": "varuint32"}], "description": "query the size of memory" }, >+ "grow_memory": { "category": "operation", "value": 64, "return": ["size"], "parameter": ["size"], "immediate": [{"name": "flags", "type": "varuint32"}], "description": "grow the size of memory" }, >+ "i32.add": { "category": "arithmetic", "value": 106, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "Add" }, >+ "i32.sub": { "category": "arithmetic", "value": 107, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "Sub" }, >+ "i32.mul": { "category": "arithmetic", "value": 108, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "Mul" }, >+ "i32.div_s": { "category": "arithmetic", "value": 109, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [] }, >+ "i32.div_u": { "category": "arithmetic", "value": 110, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [] }, >+ "i32.rem_s": { "category": "arithmetic", "value": 111, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [] }, >+ "i32.rem_u": { "category": "arithmetic", "value": 112, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [] }, >+ "i32.and": { "category": "arithmetic", "value": 113, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "BitAnd" }, >+ "i32.or": { "category": "arithmetic", "value": 114, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "BitOr" }, >+ "i32.xor": { "category": "arithmetic", "value": 115, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "BitXor" }, >+ "i32.shl": { "category": "arithmetic", "value": 116, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "Shl" }, >+ "i32.shr_u": { "category": "arithmetic", "value": 118, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "ZShr" }, >+ "i32.shr_s": { "category": "arithmetic", "value": 117, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "SShr" }, >+ "i32.rotr": { "category": "arithmetic", "value": 120, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "RotR" }, >+ "i32.rotl": { "category": "arithmetic", "value": 119, "return": ["i32"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "RotL" }, >+ "i32.eq": { "category": "comparison", "value": 70, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "Equal" }, >+ "i32.ne": { "category": "comparison", "value": 71, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "NotEqual" }, >+ "i32.lt_s": { "category": "comparison", "value": 72, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "LessThan" }, >+ "i32.le_s": { "category": "comparison", "value": 76, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "LessEqual" }, >+ "i32.lt_u": { "category": "comparison", "value": 73, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "Below" }, >+ "i32.le_u": { "category": "comparison", "value": 77, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "BelowEqual" }, >+ "i32.gt_s": { "category": "comparison", "value": 74, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "GreaterThan" }, >+ "i32.ge_s": { "category": "comparison", "value": 78, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "GreaterEqual" }, >+ "i32.gt_u": { "category": "comparison", "value": 75, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "Above" }, >+ "i32.ge_u": { "category": "comparison", "value": 79, "return": ["bool"], "parameter": ["i32", "i32"], "immediate": [], "b3op": "AboveEqual" }, >+ "i32.clz": { "category": "arithmetic", "value": 103, "return": ["i32"], "parameter": ["i32"], "immediate": [], "b3op": "Clz" }, >+ "i32.ctz": { "category": "arithmetic", "value": 104, "return": ["i32"], "parameter": ["i32"], "immediate": [] }, >+ "i32.popcnt": { "category": "arithmetic", "value": 105, "return": ["i32"], "parameter": ["i32"], "immediate": [] }, >+ "i32.eqz": { "category": "comparison", "value": 69, "return": ["bool"], "parameter": ["i32"], "immediate": [], "b3op": "Equal(i32(0), @0)" }, >+ "i64.add": { "category": "arithmetic", "value": 124, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "Add" }, >+ "i64.sub": { "category": "arithmetic", "value": 125, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "Sub" }, >+ "i64.mul": { "category": "arithmetic", "value": 126, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "Mul" }, >+ "i64.div_s": { "category": "arithmetic", "value": 127, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [] }, >+ "i64.div_u": { "category": "arithmetic", "value": 128, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [] }, >+ "i64.rem_s": { "category": "arithmetic", "value": 129, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [] }, >+ "i64.rem_u": { "category": "arithmetic", "value": 130, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [] }, >+ "i64.and": { "category": "arithmetic", "value": 131, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "BitAnd" }, >+ "i64.or": { "category": "arithmetic", "value": 132, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "BitOr" }, >+ "i64.xor": { "category": "arithmetic", "value": 133, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "BitXor" }, >+ "i64.shl": { "category": "arithmetic", "value": 134, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "Shl(@0, Trunc(@1))" }, >+ "i64.shr_u": { "category": "arithmetic", "value": 136, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "ZShr(@0, Trunc(@1))" }, >+ "i64.shr_s": { "category": "arithmetic", "value": 135, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "SShr(@0, Trunc(@1))" }, >+ "i64.rotr": { "category": "arithmetic", "value": 138, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "RotR(@0, Trunc(@1))" }, >+ "i64.rotl": { "category": "arithmetic", "value": 137, "return": ["i64"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "RotL(@0, Trunc(@1))" }, >+ "i64.eq": { "category": "comparison", "value": 81, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "Equal" }, >+ "i64.ne": { "category": "comparison", "value": 82, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "NotEqual" }, >+ "i64.lt_s": { "category": "comparison", "value": 83, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "LessThan" }, >+ "i64.le_s": { "category": "comparison", "value": 87, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "LessEqual" }, >+ "i64.lt_u": { "category": "comparison", "value": 84, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "Below" }, >+ "i64.le_u": { "category": "comparison", "value": 88, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "BelowEqual" }, >+ "i64.gt_s": { "category": "comparison", "value": 85, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "GreaterThan" }, >+ "i64.ge_s": { "category": "comparison", "value": 89, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "GreaterEqual" }, >+ "i64.gt_u": { "category": "comparison", "value": 86, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "Above" }, >+ "i64.ge_u": { "category": "comparison", "value": 90, "return": ["bool"], "parameter": ["i64", "i64"], "immediate": [], "b3op": "AboveEqual" }, >+ "i64.clz": { "category": "arithmetic", "value": 121, "return": ["i64"], "parameter": ["i64"], "immediate": [], "b3op": "Clz" }, >+ "i64.ctz": { "category": "arithmetic", "value": 122, "return": ["i64"], "parameter": ["i64"], "immediate": [] }, >+ "i64.popcnt": { "category": "arithmetic", "value": 123, "return": ["i64"], "parameter": ["i64"], "immediate": [] }, >+ "i64.eqz": { "category": "comparison", "value": 80, "return": ["bool"], "parameter": ["i64"], "immediate": [], "b3op": "Equal(i64(0), @0)" }, >+ "f32.add": { "category": "arithmetic", "value": 146, "return": ["f32"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "Add" }, >+ "f32.sub": { "category": "arithmetic", "value": 147, "return": ["f32"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "Sub" }, >+ "f32.mul": { "category": "arithmetic", "value": 148, "return": ["f32"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "Mul" }, >+ "f32.div": { "category": "arithmetic", "value": 149, "return": ["f32"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "Div" }, >+ "f32.min": { "category": "arithmetic", "value": 150, "return": ["f32"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "Select(Equal(@0, @1), BitOr(@0, @1), Select(LessThan(@0, @1), @0, Select(GreaterThan(@0, @1), @1, Add(@0, @1))))" }, >+ "f32.max": { "category": "arithmetic", "value": 151, "return": ["f32"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "Select(Equal(@0, @1), BitAnd(@0, @1), Select(LessThan(@0, @1), @1, Select(GreaterThan(@0, @1), @0, Add(@0, @1))))" }, >+ "f32.abs": { "category": "arithmetic", "value": 139, "return": ["f32"], "parameter": ["f32"], "immediate": [], "b3op": "Abs" }, >+ "f32.neg": { "category": "arithmetic", "value": 140, "return": ["f32"], "parameter": ["f32"], "immediate": [], "b3op": "Neg" }, >+ "f32.copysign": { "category": "arithmetic", "value": 152, "return": ["f32"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "BitwiseCast(BitOr(BitAnd(BitwiseCast(@1), i32(0x80000000)), BitAnd(BitwiseCast(@0), i32(0x7fffffff))))" }, >+ "f32.ceil": { "category": "arithmetic", "value": 141, "return": ["f32"], "parameter": ["f32"], "immediate": [], "b3op": "Ceil" }, >+ "f32.floor": { "category": "arithmetic", "value": 142, "return": ["f32"], "parameter": ["f32"], "immediate": [], "b3op": "Floor" }, >+ "f32.trunc": { "category": "arithmetic", "value": 143, "return": ["f32"], "parameter": ["f32"], "immediate": [] }, >+ "f32.nearest": { "category": "arithmetic", "value": 144, "return": ["f32"], "parameter": ["f32"], "immediate": [] }, >+ "f32.sqrt": { "category": "arithmetic", "value": 145, "return": ["f32"], "parameter": ["f32"], "immediate": [], "b3op": "Sqrt" }, >+ "f32.eq": { "category": "comparison", "value": 91, "return": ["bool"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "Equal" }, >+ "f32.ne": { "category": "comparison", "value": 92, "return": ["bool"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "NotEqual" }, >+ "f32.lt": { "category": "comparison", "value": 93, "return": ["bool"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "LessThan" }, >+ "f32.le": { "category": "comparison", "value": 95, "return": ["bool"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "LessEqual" }, >+ "f32.gt": { "category": "comparison", "value": 94, "return": ["bool"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "GreaterThan" }, >+ "f32.ge": { "category": "comparison", "value": 96, "return": ["bool"], "parameter": ["f32", "f32"], "immediate": [], "b3op": "GreaterEqual" }, >+ "f64.add": { "category": "arithmetic", "value": 160, "return": ["f64"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "Add" }, >+ "f64.sub": { "category": "arithmetic", "value": 161, "return": ["f64"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "Sub" }, >+ "f64.mul": { "category": "arithmetic", "value": 162, "return": ["f64"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "Mul" }, >+ "f64.div": { "category": "arithmetic", "value": 163, "return": ["f64"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "Div" }, >+ "f64.min": { "category": "arithmetic", "value": 164, "return": ["f64"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "Select(Equal(@0, @1), BitOr(@0, @1), Select(LessThan(@0, @1), @0, Select(GreaterThan(@0, @1), @1, Add(@0, @1))))" }, >+ "f64.max": { "category": "arithmetic", "value": 165, "return": ["f64"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "Select(Equal(@0, @1), BitAnd(@0, @1), Select(LessThan(@0, @1), @1, Select(GreaterThan(@0, @1), @0, Add(@0, @1))))" }, >+ "f64.abs": { "category": "arithmetic", "value": 153, "return": ["f64"], "parameter": ["f64"], "immediate": [], "b3op": "Abs" }, >+ "f64.neg": { "category": "arithmetic", "value": 154, "return": ["f64"], "parameter": ["f64"], "immediate": [], "b3op": "Neg" }, >+ "f64.copysign": { "category": "arithmetic", "value": 166, "return": ["f64"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "BitwiseCast(BitOr(BitAnd(BitwiseCast(@1), i64(0x8000000000000000)), BitAnd(BitwiseCast(@0), i64(0x7fffffffffffffff))))" }, >+ "f64.ceil": { "category": "arithmetic", "value": 155, "return": ["f64"], "parameter": ["f64"], "immediate": [], "b3op": "Ceil" }, >+ "f64.floor": { "category": "arithmetic", "value": 156, "return": ["f64"], "parameter": ["f64"], "immediate": [], "b3op": "Floor" }, >+ "f64.trunc": { "category": "arithmetic", "value": 157, "return": ["f64"], "parameter": ["f64"], "immediate": [] }, >+ "f64.nearest": { "category": "arithmetic", "value": 158, "return": ["f64"], "parameter": ["f64"], "immediate": [] }, >+ "f64.sqrt": { "category": "arithmetic", "value": 159, "return": ["f64"], "parameter": ["f64"], "immediate": [], "b3op": "Sqrt" }, >+ "f64.eq": { "category": "comparison", "value": 97, "return": ["bool"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "Equal" }, >+ "f64.ne": { "category": "comparison", "value": 98, "return": ["bool"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "NotEqual" }, >+ "f64.lt": { "category": "comparison", "value": 99, "return": ["bool"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "LessThan" }, >+ "f64.le": { "category": "comparison", "value": 101, "return": ["bool"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "LessEqual" }, >+ "f64.gt": { "category": "comparison", "value": 100, "return": ["bool"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "GreaterThan" }, >+ "f64.ge": { "category": "comparison", "value": 102, "return": ["bool"], "parameter": ["f64", "f64"], "immediate": [], "b3op": "GreaterEqual" }, >+ "i32.trunc_s/f32": { "category": "conversion", "value": 168, "return": ["i32"], "parameter": ["f32"], "immediate": [] }, >+ "i32.trunc_s/f64": { "category": "conversion", "value": 170, "return": ["i32"], "parameter": ["f64"], "immediate": [] }, >+ "i32.trunc_u/f32": { "category": "conversion", "value": 169, "return": ["i32"], "parameter": ["f32"], "immediate": [] }, >+ "i32.trunc_u/f64": { "category": "conversion", "value": 171, "return": ["i32"], "parameter": ["f64"], "immediate": [] }, >+ "i32.wrap/i64": { "category": "conversion", "value": 167, "return": ["i32"], "parameter": ["i64"], "immediate": [], "b3op": "Trunc" }, >+ "i64.trunc_s/f32": { "category": "conversion", "value": 174, "return": ["i64"], "parameter": ["f32"], "immediate": [] }, >+ "i64.trunc_s/f64": { "category": "conversion", "value": 176, "return": ["i64"], "parameter": ["f64"], "immediate": [] }, >+ "i64.trunc_u/f32": { "category": "conversion", "value": 175, "return": ["i64"], "parameter": ["f32"], "immediate": [] }, >+ "i64.trunc_u/f64": { "category": "conversion", "value": 177, "return": ["i64"], "parameter": ["f64"], "immediate": [] }, >+ "i64.extend_s/i32": { "category": "conversion", "value": 172, "return": ["i64"], "parameter": ["i32"], "immediate": [], "b3op": "SExt32" }, >+ "i64.extend_u/i32": { "category": "conversion", "value": 173, "return": ["i64"], "parameter": ["i32"], "immediate": [], "b3op": "ZExt32" }, >+ "f32.convert_s/i32": { "category": "conversion", "value": 178, "return": ["f32"], "parameter": ["i32"], "immediate": [], "b3op": "IToF" }, >+ "f32.convert_u/i32": { "category": "conversion", "value": 179, "return": ["f32"], "parameter": ["i32"], "immediate": [], "b3op": "IToF(ZExt32(@0))" }, >+ "f32.convert_s/i64": { "category": "conversion", "value": 180, "return": ["f32"], "parameter": ["i64"], "immediate": [], "b3op": "IToF" }, >+ "f32.convert_u/i64": { "category": "conversion", "value": 181, "return": ["f32"], "parameter": ["i64"], "immediate": [] }, >+ "f32.demote/f64": { "category": "conversion", "value": 182, "return": ["f32"], "parameter": ["f64"], "immediate": [], "b3op": "DoubleToFloat"}, >+ "f32.reinterpret/i32": { "category": "conversion", "value": 190, "return": ["f32"], "parameter": ["i32"], "immediate": [], "b3op": "BitwiseCast" }, >+ "f64.convert_s/i32": { "category": "conversion", "value": 183, "return": ["f64"], "parameter": ["i32"], "immediate": [], "b3op": "IToD" }, >+ "f64.convert_u/i32": { "category": "conversion", "value": 184, "return": ["f64"], "parameter": ["i32"], "immediate": [], "b3op": "IToD(ZExt32(@0))" }, >+ "f64.convert_s/i64": { "category": "conversion", "value": 185, "return": ["f64"], "parameter": ["i64"], "immediate": [], "b3op": "IToD" }, >+ "f64.convert_u/i64": { "category": "conversion", "value": 186, "return": ["f64"], "parameter": ["i64"], "immediate": [] }, >+ "f64.promote/f32": { "category": "conversion", "value": 187, "return": ["f64"], "parameter": ["f32"], "immediate": [], "b3op": "FloatToDouble"}, >+ "f64.reinterpret/i64": { "category": "conversion", "value": 191, "return": ["f64"], "parameter": ["i64"], "immediate": [], "b3op": "BitwiseCast" }, >+ "i32.reinterpret/f32": { "category": "conversion", "value": 188, "return": ["i32"], "parameter": ["f32"], "immediate": [], "b3op": "BitwiseCast" }, >+ "i64.reinterpret/f64": { "category": "conversion", "value": 189, "return": ["i64"], "parameter": ["f64"], "immediate": [], "b3op": "BitwiseCast" } >+ } >+} >+`; >+ >+ >+const _environment = >+ ((typeof process === "object" && typeof require === "function") ? "node" >+ : (typeof window === "object" ? "web" >+ : (typeof importScripts === "function" ? "worker" >+ : "shell"))); >+ >+let _global = (typeof global !== 'object' || !global || global.Math !== Math || global.Array !== Array) >+ ? ((typeof self !== 'undefined') ? self >+ : (typeof window !== 'undefined') ? window >+ : (typeof global !== 'undefined') ? global >+ : Function('return this')()) >+ : global; >+ >+const _eval = x => eval.call(null, x); >+ >+const _read = filename => { >+ switch (_environment) { >+ case "node": return read(filename); >+ case "web": // fallthrough >+ case "worker": let xhr = new XMLHttpRequest(); xhr.open("GET", filename, /*async=*/false); return xhr.responseText; >+ case "shell": return read(filename); >+ } >+} >+ >+const _load = filename => { >+ switch (_environment) { >+ case "node": // fallthrough >+ case "web": // fallthrough >+ case "shell": return _eval(_read(filename)); >+ case "worker": return importScripts(filename); >+ } >+} >+ >+const _json = filename => { >+ assert.eq(filename, "wasm.json"); >+ return JSON.parse(WASM_JSON); >+ >+ switch (_environment) { >+ case "node": // fallthrough >+ case "shell": return JSON.parse(_read(filename)); >+ case "web": // fallthrough >+ case "worker": let xhr = new XMLHttpRequest(); xhr.overrideMimeType("application/json"); xhr.open("GET", filename, /*async=*/false); return xhr.response; >+ } >+} >+ >+const _dump = (what, name, pad = ' ') => { >+ const value = v => { >+ try { return `"${v}"`; } >+ catch (e) { return `Error: "${e.message}"`; } >+ }; >+ let s = `${pad}${name} ${typeof what}: ${value(what)}`; >+ for (let p in what) { >+ s += `\n${pad}${pad}${p}: ${value(what[p])} ${typeof v}`; >+ s += '\n' + _dump(what[p], p, pad + pad); >+ } >+ return s; >+}; >+ >+const _toJavaScriptName = name => { >+ const camelCase = name.replace(/([^a-z0-9].)/g, c => c[1].toUpperCase()); >+ const CamelCase = camelCase.charAt(0).toUpperCase() + camelCase.slice(1); >+ return CamelCase; >+}; >+ >+// Use underscore names to avoid clashing with builtin names. >+const utilities = { >+ dump : _dump, >+ eval : _eval, >+ read : _read, >+ load : _load, >+ json : _json, >+ global : _global, >+ toJavaScriptName: _toJavaScriptName >+}; >+const util = utilities; >+ >+ >+const _mapValues = from => { >+ let values = {}; >+ for (const key in from) >+ values[key] = from[key].value; >+ return values; >+}; >+ >+ >+const WASM = {}; >+{ >+ const description = WASM.description = utilities.json("wasm.json"); >+ const type = WASM.type = Object.keys(description.type); >+ const _typeSet = new Set(type); >+ WASM.isValidType = v => _typeSet.has(v); >+ WASM.typeValue = _mapValues(description.type); >+ const _valueTypeSet = new Set(description.value_type); >+ WASM.isValidValueType = v => _valueTypeSet.has(v); >+ const _blockTypeSet = new Set(description.block_type); >+ WASM.isValidBlockType = v => _blockTypeSet.has(v); >+ WASM.externalKindValue = _mapValues(description.external_kind); >+ const sections = WASM.sections = Object.keys(description.section); >+ WASM.sectionEncodingType = description.section[sections[0]].type; >+} >+ >+ >+// LowLevelBinary.js >+const _initialAllocationSize = 1024; >+const _growAllocationSize = allocated => allocated * 2; >+ >+const varuint32Min = 0; >+const varint7Min = -0b1000000; >+const varint7Max = 0b111111; >+const varuint7Max = 0b1111111; >+const varuint32Max = ((((1 << 31) >>> 0) - 1) * 2) + 1; >+const varint32Min = -((1 << 31) >>> 0); >+const varint32Max = ((1 << 31) - 1) >>> 0; >+const varBitsMax = 5; >+ >+const _getterRangeCheck = (llb, at, size) => { >+ if (0 > at || at + size > llb._used) >+ throw new RangeError(`[${at}, ${at + size}) is out of buffer range [0, ${llb._used})`); >+}; >+ >+const _hexdump = (buf, size) => { >+ let s = ""; >+ const width = 16; >+ const base = 16; >+ for (let row = 0; row * width < size; ++row) { >+ const address = (row * width).toString(base); >+ s += "0".repeat(8 - address.length) + address; >+ let chars = ""; >+ for (let col = 0; col !== width; ++col) { >+ const idx = row * width + col; >+ if (idx < size) { >+ const byte = buf[idx]; >+ const bytestr = byte.toString(base); >+ s += " " + (bytestr.length === 1 ? "0" + bytestr : bytestr); >+ chars += 0x20 <= byte && byte < 0x7F ? String.fromCharCode(byte) : "·"; >+ } else { >+ s += " "; >+ chars += " "; >+ } >+ } >+ s+= " |" + chars + "|\n"; >+ } >+ return s; >+}; >+ >+class LowLevelBinary { >+ constructor() { >+ this._buf = new Uint8Array(_initialAllocationSize); >+ this._used = 0; >+ } >+ >+ newPatchable(type) { return new PatchableLowLevelBinary(type, this); } >+ >+ // Utilities. >+ get() { return this._buf; } >+ trim() { this._buf = this._buf.slice(0, this._used); } >+ >+ hexdump() { return _hexdump(this._buf, this._used); } >+ _maybeGrow(bytes) { >+ const allocated = this._buf.length; >+ if (allocated - this._used < bytes) { >+ let buf = new Uint8Array(_growAllocationSize(allocated)); >+ buf.set(this._buf); >+ this._buf = buf; >+ } >+ } >+ _push8(v) { this._buf[this._used] = v & 0xFF; this._used += 1; } >+ >+ // Data types. >+ uint8(v) { >+ if ((v & 0xFF) >>> 0 !== v) >+ throw new RangeError(`Invalid uint8 ${v}`); >+ this._maybeGrow(1); >+ this._push8(v); >+ } >+ uint16(v) { >+ if ((v & 0xFFFF) >>> 0 !== v) >+ throw new RangeError(`Invalid uint16 ${v}`); >+ this._maybeGrow(2); >+ this._push8(v); >+ this._push8(v >>> 8); >+ } >+ uint24(v) { >+ if ((v & 0xFFFFFF) >>> 0 !== v) >+ throw new RangeError(`Invalid uint24 ${v}`); >+ this._maybeGrow(3); >+ this._push8(v); >+ this._push8(v >>> 8); >+ this._push8(v >>> 16); >+ } >+ uint32(v) { >+ if ((v & 0xFFFFFFFF) >>> 0 !== v) >+ throw new RangeError(`Invalid uint32 ${v}`); >+ this._maybeGrow(4); >+ this._push8(v); >+ this._push8(v >>> 8); >+ this._push8(v >>> 16); >+ this._push8(v >>> 24); >+ } >+ float(v) { >+ if (isNaN(v)) >+ throw new RangeError("unimplemented, NaNs"); >+ // Unfortunately, we cannot just view the actual buffer as a Float32Array since it needs to be 4 byte aligned >+ let buffer = new ArrayBuffer(4); >+ let floatView = new Float32Array(buffer); >+ let int8View = new Uint8Array(buffer); >+ floatView[0] = v; >+ for (let byte of int8View) >+ this._push8(byte); >+ } >+ >+ double(v) { >+ if (isNaN(v)) >+ throw new RangeError("unimplemented, NaNs"); >+ // Unfortunately, we cannot just view the actual buffer as a Float64Array since it needs to be 4 byte aligned >+ let buffer = new ArrayBuffer(8); >+ let floatView = new Float64Array(buffer); >+ let int8View = new Uint8Array(buffer); >+ floatView[0] = v; >+ for (let byte of int8View) >+ this._push8(byte); >+ } >+ >+ varuint32(v) { >+ assert.isNumber(v); >+ if (v < varuint32Min || varuint32Max < v) >+ throw new RangeError(`Invalid varuint32 ${v} range is [${varuint32Min}, ${varuint32Max}]`); >+ while (v >= 0x80) { >+ this.uint8(0x80 | (v & 0x7F)); >+ v >>>= 7; >+ } >+ this.uint8(v); >+ } >+ varint32(v) { >+ assert.isNumber(v); >+ if (v < varint32Min || varint32Max < v) >+ throw new RangeError(`Invalid varint32 ${v} range is [${varint32Min}, ${varint32Max}]`); >+ do { >+ const b = v & 0x7F; >+ v >>= 7; >+ if ((v === 0 && ((b & 0x40) === 0)) || (v === -1 && ((b & 0x40) === 0x40))) { >+ this.uint8(b & 0x7F); >+ break; >+ } >+ this.uint8(0x80 | b); >+ } while (true); >+ } >+ varuint64(v) { >+ assert.isNumber(v); >+ if (v < varuint32Min || varuint32Max < v) >+ throw new RangeError(`unimplemented: varuint64 larger than 32-bit`); >+ this.varuint32(v); // FIXME implement 64-bit var{u}int https://bugs.webkit.org/show_bug.cgi?id=163420 >+ } >+ varint64(v) { >+ assert.isNumber(v); >+ if (v < varint32Min || varint32Max < v) >+ throw new RangeError(`unimplemented: varint64 larger than 32-bit`); >+ this.varint32(v); // FIXME implement 64-bit var{u}int https://bugs.webkit.org/show_bug.cgi?id=163420 >+ } >+ varuint1(v) { >+ if (v !== 0 && v !== 1) >+ throw new RangeError(`Invalid varuint1 ${v} range is [0, 1]`); >+ this.varuint32(v); >+ } >+ varint7(v) { >+ if (v < varint7Min || varint7Max < v) >+ throw new RangeError(`Invalid varint7 ${v} range is [${varint7Min}, ${varint7Max}]`); >+ this.varint32(v); >+ } >+ varuint7(v) { >+ if (v < varuint32Min || varuint7Max < v) >+ throw new RangeError(`Invalid varuint7 ${v} range is [${varuint32Min}, ${varuint7Max}]`); >+ this.varuint32(v); >+ } >+ block_type(v) { >+ if (!WASM.isValidBlockType(v)) >+ throw new Error(`Invalid block type ${v}`); >+ this.varint7(WASM.typeValue[v]); >+ } >+ string(str) { >+ let patch = this.newPatchable("varuint32"); >+ for (const char of str) { >+ // Encode UTF-8 2003 code points. >+ const code = char.codePointAt(); >+ if (code <= 0x007F) { >+ const utf8 = code; >+ patch.uint8(utf8); >+ } else if (code <= 0x07FF) { >+ const utf8 = 0x80C0 | ((code & 0x7C0) >> 6) | ((code & 0x3F) << 8); >+ patch.uint16(utf8); >+ } else if (code <= 0xFFFF) { >+ const utf8 = 0x8080E0 | ((code & 0xF000) >> 12) | ((code & 0xFC0) << 2) | ((code & 0x3F) << 16); >+ patch.uint24(utf8); >+ } else if (code <= 0x10FFFF) { >+ const utf8 = (0x808080F0 | ((code & 0x1C0000) >> 18) | ((code & 0x3F000) >> 4) | ((code & 0xFC0) << 10) | ((code & 0x3F) << 24)) >>> 0; >+ patch.uint32(utf8); >+ } else >+ throw new Error(`Unexpectedly large UTF-8 character code point '${char}' 0x${code.toString(16)}`); >+ } >+ patch.apply(); >+ } >+ >+ // Getters. >+ getSize() { return this._used; } >+ getUint8(at) { >+ _getterRangeCheck(this, at, 1); >+ return this._buf[at]; >+ } >+ getUint16(at) { >+ _getterRangeCheck(this, at, 2); >+ return this._buf[at] | (this._buf[at + 1] << 8); >+ } >+ getUint24(at) { >+ _getterRangeCheck(this, at, 3); >+ return this._buf[at] | (this._buf[at + 1] << 8) | (this._buf[at + 2] << 16); >+ } >+ getUint32(at) { >+ _getterRangeCheck(this, at, 4); >+ return (this._buf[at] | (this._buf[at + 1] << 8) | (this._buf[at + 2] << 16) | (this._buf[at + 3] << 24)) >>> 0; >+ } >+ getVaruint32(at) { >+ let v = 0; >+ let shift = 0; >+ let byte = 0; >+ do { >+ byte = this.getUint8(at++); >+ ++read; >+ v = (v | ((byte & 0x7F) << shift) >>> 0) >>> 0; >+ shift += 7; >+ } while ((byte & 0x80) !== 0); >+ if (shift - 7 > 32) throw new RangeError(`Shifting too much at ${at}`); >+ if ((shift == 35) && ((byte & 0xF0) != 0)) throw new Error(`Unexpected non-significant varuint32 bits in last byte 0x${byte.toString(16)}`); >+ return { value: v, next: at }; >+ } >+ getVarint32(at) { >+ let v = 0; >+ let shift = 0; >+ let byte = 0; >+ do { >+ byte = this.getUint8(at++); >+ v = (v | ((byte & 0x7F) << shift) >>> 0) >>> 0; >+ shift += 7; >+ } while ((byte & 0x80) !== 0); >+ if (shift - 7 > 32) throw new RangeError(`Shifting too much at ${at}`); >+ if ((shift == 35) && (((byte << 26) >> 30) != ((byte << 25) >> 31))) throw new Error(`Unexpected non-significant varint32 bits in last byte 0x${byte.toString(16)}`); >+ if ((byte & 0x40) === 0x40) { >+ const sext = shift < 32 ? 32 - shift : 0; >+ v = (v << sext) >> sext; >+ } >+ return { value: v, next: at }; >+ } >+ getVaruint1(at) { >+ const res = this.getVaruint32(at); >+ if (res.value !== 0 && res.value !== 1) throw new Error(`Expected a varuint1, got value ${res.value}`); >+ return res; >+ } >+ getVaruint7(at) { >+ const res = this.getVaruint32(at); >+ if (res.value > varuint7Max) throw new Error(`Expected a varuint7, got value ${res.value}`); >+ return res; >+ } >+ getString(at) { >+ const size = this.getVaruint32(at); >+ const last = size.next + size.value; >+ let i = size.next; >+ let str = ""; >+ while (i < last) { >+ // Decode UTF-8 2003 code points. >+ const peek = this.getUint8(i); >+ let code; >+ if ((peek & 0x80) === 0x0) { >+ const utf8 = this.getUint8(i); >+ assert.eq(utf8 & 0x80, 0x00); >+ i += 1; >+ code = utf8; >+ } else if ((peek & 0xE0) === 0xC0) { >+ const utf8 = this.getUint16(i); >+ assert.eq(utf8 & 0xC0E0, 0x80C0); >+ i += 2; >+ code = ((utf8 & 0x1F) << 6) | ((utf8 & 0x3F00) >> 8); >+ } else if ((peek & 0xF0) === 0xE0) { >+ const utf8 = this.getUint24(i); >+ assert.eq(utf8 & 0xC0C0F0, 0x8080E0); >+ i += 3; >+ code = ((utf8 & 0xF) << 12) | ((utf8 & 0x3F00) >> 2) | ((utf8 & 0x3F0000) >> 16); >+ } else if ((peek & 0xF8) === 0xF0) { >+ const utf8 = this.getUint32(i); >+ assert.eq((utf8 & 0xC0C0C0F8) | 0, 0x808080F0 | 0); >+ i += 4; >+ code = ((utf8 & 0x7) << 18) | ((utf8 & 0x3F00) << 4) | ((utf8 & 0x3F0000) >> 10) | ((utf8 & 0x3F000000) >> 24); >+ } else >+ throw new Error(`Unexpectedly large UTF-8 initial byte 0x${peek.toString(16)}`); >+ str += String.fromCodePoint(code); >+ } >+ if (i !== last) >+ throw new Error(`String decoding read up to ${i}, expected ${last}, UTF-8 decoding was too greedy`); >+ return str; >+ } >+}; >+ >+class PatchableLowLevelBinary extends LowLevelBinary { >+ constructor(type, lowLevelBinary) { >+ super(); >+ this.type = type; >+ this.target = lowLevelBinary; >+ this._buffered_bytes = 0; >+ } >+ _push8(v) { ++this._buffered_bytes; super._push8(v); } >+ apply() { >+ this.target[this.type](this._buffered_bytes); >+ for (let i = 0; i < this._buffered_bytes; ++i) >+ this.target.uint8(this._buf[i]); >+ } >+}; >+ >+LowLevelBinary.varuint32Min = 0; >+LowLevelBinary.varint7Min = -0b1000000; >+LowLevelBinary.varint7Max = 0b111111; >+LowLevelBinary.varuint7Max = 0b1111111; >+LowLevelBinary.varuint32Max = ((((1 << 31) >>> 0) - 1) * 2) + 1; >+LowLevelBinary.varint32Min = -((1 << 31) >>> 0); >+LowLevelBinary.varint32Max = ((1 << 31) - 1) >>> 0; >+LowLevelBinary.varBitsMax = 5; >+ >+ >+// Builder_WebAssemblyBinary.js >+const BuildWebAssembly = {}; >+{ >+ const put = (bin, type, value) => bin[type](value); >+ >+ const putResizableLimits = (bin, initial, maximum) => { >+ assert.truthy(typeof initial === "number", "We expect 'initial' to be an integer"); >+ let hasMaximum = 0; >+ if (typeof maximum === "number") { >+ hasMaximum = 1; >+ } else { >+ assert.truthy(typeof maximum === "undefined", "We expect 'maximum' to be an integer if it's defined"); >+ } >+ >+ put(bin, "varuint1", hasMaximum); >+ put(bin, "varuint32", initial); >+ if (hasMaximum) >+ put(bin, "varuint32", maximum); >+ }; >+ >+ const putTable = (bin, {initial, maximum, element}) => { >+ assert.truthy(WASM.isValidType(element), "We expect 'element' to be a valid type. It was: " + element); >+ put(bin, "varint7", WASM.typeValue[element]); >+ >+ putResizableLimits(bin, initial, maximum); >+ }; >+ >+ const valueType = WASM.description.type.i32.type >+ >+ const putGlobalType = (bin, global) => { >+ put(bin, valueType, WASM.typeValue[global.type]); >+ put(bin, "varuint1", global.mutability); >+ }; >+ >+ const putOp = (bin, op) => { >+ put(bin, "uint8", op.value); >+ if (WASM.description.opcode[op.name].extendedOp) >+ put(bin, "uint8", WASM.description.opcode[op.name].extendedOp); >+ if (op.arguments.length !== 0) >+ throw new Error(`Unimplemented: arguments`); // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706 >+ >+ switch (op.name) { >+ default: >+ for (let i = 0; i < op.immediates.length; ++i) { >+ const type = WASM.description.opcode[op.name].immediate[i].type >+ if (!bin[type]) >+ throw new TypeError(`Unknown type: ${type} in op: ${op.name}`); >+ put(bin, type, op.immediates[i]); >+ } >+ break; >+ case "i32.const": { >+ assert.eq(op.immediates.length, 1); >+ let imm = op.immediates[0]; >+ // Do a static cast to make large int32s signed. >+ if (imm >= 2 ** 31) >+ imm = imm - (2 ** 32); >+ put(bin, "varint32", imm); >+ break; >+ } >+ case "br_table": >+ put(bin, "varuint32", op.immediates.length - 1); >+ for (let imm of op.immediates) >+ put(bin, "varuint32", imm); >+ break; >+ } >+ }; >+ >+ const putInitExpr = (bin, expr) => { >+ putOp(bin, { value: WASM.description.opcode[expr.op].value, name: expr.op, immediates: [expr.initValue], arguments: [] }); >+ putOp(bin, { value: WASM.description.opcode.end.value, name: "end", immediates: [], arguments: [] }); >+ }; >+ >+ const emitters = { >+ Type: (section, bin) => { >+ put(bin, "varuint32", section.data.length); >+ for (const entry of section.data) { >+ put(bin, "varint7", WASM.typeValue["func"]); >+ put(bin, "varuint32", entry.params.length); >+ for (const param of entry.params) >+ put(bin, "varint7", WASM.typeValue[param]); >+ if (entry.ret === "void") >+ put(bin, "varuint1", 0); >+ else { >+ put(bin, "varuint1", 1); >+ put(bin, "varint7", WASM.typeValue[entry.ret]); >+ } >+ } >+ }, >+ Import: (section, bin) => { >+ put(bin, "varuint32", 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, "varuint32", entry.type); >+ break; >+ } >+ case "Table": { >+ putTable(bin, entry.tableDescription); >+ break; >+ } >+ case "Memory": { >+ let {initial, maximum} = entry.memoryDescription; >+ putResizableLimits(bin, initial, maximum); >+ break; >+ }; >+ case "Global": >+ putGlobalType(bin, entry.globalDescription); >+ break; >+ } >+ } >+ }, >+ >+ Function: (section, bin) => { >+ put(bin, "varuint32", section.data.length); >+ for (const signature of section.data) >+ put(bin, "varuint32", signature); >+ }, >+ >+ Table: (section, bin) => { >+ put(bin, "varuint32", section.data.length); >+ for (const {tableDescription} of section.data) { >+ putTable(bin, tableDescription); >+ } >+ }, >+ >+ Memory: (section, bin) => { >+ // Flags, currently can only be [0,1] >+ put(bin, "varuint1", section.data.length); >+ for (const memory of section.data) { >+ put(bin, "varuint32", memory.max ? 1 : 0); >+ put(bin, "varuint32", memory.initial); >+ if (memory.max) >+ put(bin, "varuint32", memory.max); >+ } >+ }, >+ >+ Global: (section, bin) => { >+ put(bin, "varuint32", section.data.length); >+ for (const global of section.data) { >+ putGlobalType(bin, global); >+ putInitExpr(bin, global) >+ } >+ }, >+ >+ Export: (section, bin) => { >+ put(bin, "varuint32", section.data.length); >+ for (const entry of section.data) { >+ put(bin, "string", entry.field); >+ put(bin, "uint8", WASM.externalKindValue[entry.kind]); >+ switch (entry.kind) { >+ case "Global": >+ case "Function": >+ case "Memory": >+ case "Table": >+ put(bin, "varuint32", entry.index); >+ break; >+ default: throw new Error(`Implementation problem: unexpected kind ${entry.kind}`); >+ } >+ } >+ }, >+ Start: (section, bin) => { >+ put(bin, "varuint32", section.data[0]); >+ }, >+ Element: (section, bin) => { >+ const data = section.data; >+ put(bin, "varuint32", data.length); >+ for (const {tableIndex, offset, functionIndices} of data) { >+ put(bin, "varuint32", tableIndex); >+ >+ let initExpr; >+ if (typeof offset === "number") >+ initExpr = {op: "i32.const", initValue: offset}; >+ else >+ initExpr = offset; >+ putInitExpr(bin, initExpr); >+ >+ put(bin, "varuint32", functionIndices.length); >+ for (const functionIndex of functionIndices) >+ put(bin, "varuint32", functionIndex); >+ } >+ }, >+ >+ Code: (section, bin) => { >+ put(bin, "varuint32", section.data.length); >+ for (const func of section.data) { >+ let funcBin = bin.newPatchable("varuint32"); >+ const localCount = func.locals.length - func.parameterCount; >+ put(funcBin, "varuint32", localCount); >+ for (let i = func.parameterCount; i < func.locals.length; ++i) { >+ put(funcBin, "varuint32", 1); >+ put(funcBin, "varint7", WASM.typeValue[func.locals[i]]); >+ } >+ >+ for (const op of func.code) >+ putOp(funcBin, op); >+ >+ funcBin.apply(); >+ } >+ }, >+ >+ Data: (section, bin) => { >+ put(bin, "varuint32", section.data.length); >+ for (const datum of section.data) { >+ put(bin, "varuint32", datum.index); >+ // FIXME allow complex init_expr here. https://bugs.webkit.org/show_bug.cgi?id=165700 >+ // For now we only handle i32.const as offset. >+ put(bin, "uint8", WASM.description.opcode["i32.const"].value); >+ put(bin, WASM.description.opcode["i32.const"].immediate[0].type, datum.offset); >+ put(bin, "uint8", WASM.description.opcode["end"].value); >+ put(bin, "varuint32", datum.data.length); >+ for (const byte of datum.data) >+ put(bin, "uint8", byte); >+ } >+ }, >+ }; >+ >+ BuildWebAssembly.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("varuint32"); >+ 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(); >+ } >+ wasmBin.trim(); >+ return wasmBin; >+ }; >+} >+ >+ >+const LLB = LowLevelBinary; >+ >+const _isValidValue = (value, type) => { >+ switch (type) { >+ // We allow both signed and unsigned numbers. >+ case "i32": return Math.round(value) === value && LLB.varint32Min <= value && value <= LLB.varuint32Max; >+ case "anyref": return true; >+ case "i64": return true; // FIXME https://bugs.webkit.org/show_bug.cgi?id=163420 64-bit values >+ case "f32": return typeof(value) === "number" && isFinite(value); >+ case "f64": return typeof(value) === "number" && isFinite(value); >+ default: throw new Error(`Implementation problem: unknown type ${type}`); >+ } >+}; >+const _unknownSectionId = 0; >+ >+const _normalizeFunctionSignature = (params, ret) => { >+ assert.isArray(params); >+ for (const p of params) >+ assert.truthy(WASM.isValidValueType(p) || p === "void", `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.truthy(WASM.isValidBlockType(ret), `Type return ${ret} must be valid block type`); >+ return [params, ret]; >+}; >+ >+const _errorHandlingProxyFor = builder => builder["__isProxy"] ? builder : new Proxy(builder, { >+ get: (target, property, receiver) => { >+ if (property === "__isProxy") >+ return true; >+ if (target[property] === undefined) >+ throw new Error(`WebAssembly builder received unknown property '${property}'`); >+ return target[property]; >+ } >+}); >+ >+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 not use type ${type} if a type section is not present`); >+ assert.isNotUndef(typeSection.data[type], `Type ${type} does not 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 not add type if a type section is not 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; >+ } >+ type = i; >+ break; >+ } >+ } >+ 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 _errorHandlingProxyFor(nextBuilder); >+ }; >+}; >+ >+const _importMemoryContinuation = (builder, section, nextBuilder) => { >+ return (module, field, {initial, maximum}) => { >+ assert.isString(module, `Import Memory module should be a string, got "${module}"`); >+ assert.isString(field, `Import Memory field should be a string, got "${field}"`); >+ section.data.push({module, field, kind: "Memory", memoryDescription: {initial, maximum}}); >+ return _errorHandlingProxyFor(nextBuilder); >+ }; >+}; >+ >+const _importTableContinuation = (builder, section, nextBuilder) => { >+ return (module, field, {initial, maximum, element}) => { >+ assert.isString(module, `Import Table module should be a string, got "${module}"`); >+ assert.isString(field, `Import Table field should be a string, got "${field}"`); >+ section.data.push({module, field, kind: "Table", tableDescription: {initial, maximum, element}}); >+ return _errorHandlingProxyFor(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 _errorHandlingProxyFor(nextBuilder); >+ }; >+}; >+ >+const _normalizeMutability = (mutability) => { >+ if (mutability === "mutable") >+ return 1; >+ else if (mutability === "immutable") >+ return 0; >+ else >+ throw new Error(`mutability should be either "mutable" or "immutable", but got ${mutability}`); >+}; >+ >+const _exportGlobalContinuation = (builder, section, nextBuilder) => { >+ return (field, index) => { >+ assert.isNumber(index, `Global exports only support number indices right now`); >+ section.data.push({ field, kind: "Global", index }); >+ return _errorHandlingProxyFor(nextBuilder); >+ } >+}; >+ >+const _exportMemoryContinuation = (builder, section, nextBuilder) => { >+ return (field, index) => { >+ assert.isNumber(index, `Memory exports only support number indices`); >+ section.data.push({field, kind: "Memory", index}); >+ return _errorHandlingProxyFor(nextBuilder); >+ } >+}; >+ >+const _exportTableContinuation = (builder, section, nextBuilder) => { >+ return (field, index) => { >+ assert.isNumber(index, `Table exports only support number indices`); >+ section.data.push({field, kind: "Table", index}); >+ return _errorHandlingProxyFor(nextBuilder); >+ } >+}; >+ >+const _importGlobalContinuation = (builder, section, nextBuilder) => { >+ return () => { >+ const globalBuilder = { >+ End: () => nextBuilder >+ }; >+ for (let op of WASM.description.value_type) { >+ globalBuilder[util.toJavaScriptName(op)] = (module, field, mutability) => { >+ assert.isString(module, `Import global module should be a string, got "${module}"`); >+ assert.isString(field, `Import global field should be a string, got "${field}"`); >+ assert.isString(mutability, `Import global mutability should be a string, got "${mutability}"`); >+ section.data.push({ globalDescription: { type: op, mutability: _normalizeMutability(mutability) }, module, field, kind: "Global" }); >+ return _errorHandlingProxyFor(globalBuilder); >+ }; >+ } >+ return _errorHandlingProxyFor(globalBuilder); >+ }; >+}; >+ >+const _checkStackArgs = (op, param) => { >+ for (let expect of param) { >+ if (WASM.isValidType(expect)) { >+ // FIXME implement stack checks for arguments. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ } else { >+ // Handle our own meta-types. >+ switch (expect) { >+ case "addr": break; // FIXME implement addr. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "any": break; // FIXME implement any. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "bool": break; // FIXME implement bool. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "call": break; // FIXME implement call stack argument checks based on function signature. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "global": break; // FIXME implement global. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "local": break; // FIXME implement local. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "prev": break; // FIXME implement prev, checking for whetever the previous value was. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "size": break; // FIXME implement size. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ default: throw new Error(`Implementation problem: unhandled meta-type "${expect}" on "${op}"`); >+ } >+ } >+ } >+} >+ >+const _checkStackReturn = (op, ret) => { >+ for (let expect of ret) { >+ if (WASM.isValidType(expect)) { >+ // FIXME implement stack checks for return. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ } else { >+ // Handle our own meta-types. >+ switch (expect) { >+ case "any": break; >+ case "bool": break; // FIXME implement bool. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "call": break; // FIXME implement call stack return check based on function signature. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "control": break; // FIXME implement control. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "global": break; // FIXME implement global. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "local": break; // FIXME implement local. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "prev": break; // FIXME implement prev, checking for whetever the parameter type was. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "size": break; // FIXME implement size. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ default: throw new Error(`Implementation problem: unhandled meta-type "${expect}" on "${op}"`); >+ } >+ } >+ } >+}; >+ >+const _checkImms = (op, imms, expectedImms, ret) => { >+ const minExpectedImms = expectedImms.filter(i => i.type.slice(-1) !== '*').length; >+ if (minExpectedImms !== expectedImms.length) >+ assert.ge(imms.length, minExpectedImms, `"${op}" expects at least ${minExpectedImms} immediate${minExpectedImms !== 1 ? 's' : ''}, got ${imms.length}`); >+ else >+ assert.eq(imms.length, minExpectedImms, `"${op}" expects exactly ${minExpectedImms} immediate${minExpectedImms !== 1 ? 's' : ''}, got ${imms.length}`); >+ for (let idx = 0; idx !== expectedImms.length; ++idx) { >+ const got = imms[idx]; >+ const expect = expectedImms[idx]; >+ switch (expect.name) { >+ case "function_index": >+ assert.truthy(_isValidValue(got, "i32") && got >= 0, `Invalid value on ${op}: got "${got}", expected non-negative i32`); >+ // FIXME check function indices. https://bugs.webkit.org/show_bug.cgi?id=163421 >+ break; >+ case "local_index": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "global_index": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "type_index": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "value": >+ assert.truthy(_isValidValue(got, ret[0]), `Invalid value on ${op}: got "${got}", expected ${ret[0]}`); >+ break; >+ case "flags": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "offset": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ // Control: >+ case "default_target": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "relative_depth": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "sig": >+ assert.truthy(WASM.isValidBlockType(imms[idx]), `Invalid block type on ${op}: "${imms[idx]}"`); >+ break; >+ case "target_count": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "target_table": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "reserved": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ case "table_index": break; // improve checking https://bugs.webkit.org/show_bug.cgi?id=163421 >+ default: throw new Error(`Implementation problem: unhandled immediate "${expect.name}" on "${op}"`); >+ } >+ } >+}; >+ >+const _createFunctionBuilder = (func, builder, previousBuilder) => { >+ let functionBuilder = {}; >+ for (const op in WASM.description.opcode) { >+ const name = util.toJavaScriptName(op); >+ const value = WASM.description.opcode[op].value; >+ const ret = WASM.description.opcode[op]["return"]; >+ const param = WASM.description.opcode[op].parameter; >+ const imm = WASM.description.opcode[op].immediate; >+ >+ const checkStackArgs = builder._checked ? _checkStackArgs : () => {}; >+ const checkStackReturn = builder._checked ? _checkStackReturn : () => {}; >+ const checkImms = builder._checked ? _checkImms : () => {}; >+ >+ functionBuilder[name] = (...args) => { >+ let nextBuilder; >+ switch (name) { >+ default: >+ nextBuilder = functionBuilder; >+ break; >+ case "End": >+ nextBuilder = previousBuilder; >+ break; >+ case "Block": >+ case "Loop": >+ case "If": >+ nextBuilder = _createFunctionBuilder(func, builder, functionBuilder); >+ break; >+ } >+ >+ // Passing a function as the argument is a way to nest blocks lexically. >+ const continuation = args[args.length - 1]; >+ const hasContinuation = typeof(continuation) === "function"; >+ const imms = hasContinuation ? args.slice(0, -1) : args; // FIXME: allow passing in stack values, as-if code were a stack machine. Just check for a builder to this function, and drop. https://bugs.webkit.org/show_bug.cgi?id=163422 >+ checkImms(op, imms, imm, ret); >+ checkStackArgs(op, param); >+ checkStackReturn(op, ret); >+ const stackArgs = []; // FIXME https://bugs.webkit.org/show_bug.cgi?id=162706 >+ func.code.push({ name: op, value: value, arguments: stackArgs, immediates: imms }); >+ if (hasContinuation) >+ return _errorHandlingProxyFor(continuation(nextBuilder).End()); >+ return _errorHandlingProxyFor(nextBuilder); >+ }; >+ }; >+ >+ return _errorHandlingProxyFor(functionBuilder); >+} >+ >+const _createFunction = (section, builder, previousBuilder) => { >+ return (...args) => { >+ const nameOffset = (typeof(args[0]) === "string") >>> 0; >+ const functionName = nameOffset ? args[0] : undefined; >+ let signature = args[0 + nameOffset]; >+ const locals = args[1 + nameOffset] === undefined ? [] : args[1 + nameOffset]; >+ for (const local of locals) >+ assert.truthy(WASM.isValidValueType(local), `Type of local: ${local} needs to be a valid value type`); >+ >+ if (typeof(signature) === "undefined") >+ signature = { params: [] }; >+ >+ let type; >+ let params; >+ if (typeof signature === "object") { >+ assert.hasObjectProperty(signature, "params", `Expect function signature to be an object with a "params" field, got "${signature}"`); >+ let ret; >+ ([params, ret] = _normalizeFunctionSignature(signature.params, signature.ret)); >+ signature = {params, ret}; >+ type = _maybeRegisterType(builder, signature); >+ } else { >+ assert.truthy(typeof signature === "number"); >+ const typeSection = builder._getSection("Type"); >+ assert.truthy(!!typeSection); >+ // FIXME: we should allow indices that exceed this to be able to >+ // test JSCs validator is correct. https://bugs.webkit.org/show_bug.cgi?id=165786 >+ assert.truthy(signature < typeSection.data.length); >+ type = signature; >+ signature = typeSection.data[signature]; >+ assert.hasObjectProperty(signature, "params", `Expect function signature to be an object with a "params" field, got "${signature}"`); >+ params = signature.params; >+ } >+ >+ const func = { >+ name: functionName, >+ type, >+ signature: signature, >+ locals: params.concat(locals), // Parameters are the first locals. >+ parameterCount: params.length, >+ code: [] >+ }; >+ >+ const functionSection = builder._getSection("Function"); >+ if (functionSection) >+ functionSection.data.push(func.type); >+ >+ section.data.push(func); >+ builder._registerFunctionToIndexSpace(functionName); >+ >+ return _createFunctionBuilder(func, builder, previousBuilder); >+ } >+}; >+ >+class Builder { >+ constructor() { >+ this.setChecked(true); >+ let preamble = {}; >+ for (const p of WASM.description.preamble) >+ preamble[p.name] = p.value; >+ this.setPreamble(preamble); >+ this._sections = []; >+ this._functionIndexSpace = new Map(); >+ this._functionIndexSpaceCount = 0; >+ this._registerSectionBuilders(); >+ } >+ setChecked(checked) { >+ this._checked = checked; >+ return this; >+ } >+ setPreamble(p) { >+ this._preamble = Object.assign(this._preamble || {}, p); >+ return this; >+ } >+ _functionIndexSpaceKeyHash(obj) { >+ // We don't need a full hash, just something that has a defined order for Map. Objects we insert aren't nested. >+ if (typeof(obj) !== 'object') >+ return obj; >+ const keys = Object.keys(obj).sort(); >+ let entries = []; >+ for (let k in keys) >+ entries.push([k, obj[k]]); >+ return JSON.stringify(entries); >+ } >+ _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 = {}; >+ } >+ const value = this._functionIndexSpaceCount++; >+ // Collisions are fine: we'll simply count the function and forget the previous one. >+ this._functionIndexSpace.set(this._functionIndexSpaceKeyHash(name), value); >+ // Map it both ways, the number space is distinct from the name space. >+ this._functionIndexSpace.set(this._functionIndexSpaceKeyHash(value), name); >+ } >+ _getFunctionFromIndexSpace(name) { >+ return this._functionIndexSpace.get(this._functionIndexSpaceKeyHash(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 _errorHandlingProxyFor(typeBuilder); >+ }, >+ }; >+ return _errorHandlingProxyFor(typeBuilder); >+ }; >+ break; >+ >+ case "Import": >+ this[section] = function() { >+ const s = this._addSection(section); >+ const importBuilder = { >+ End: () => this, >+ }; >+ importBuilder.Global = _importGlobalContinuation(this, s, importBuilder); >+ importBuilder.Function = _importFunctionContinuation(this, s, importBuilder); >+ importBuilder.Memory = _importMemoryContinuation(this, s, importBuilder); >+ importBuilder.Table = _importTableContinuation(this, s, importBuilder); >+ return _errorHandlingProxyFor(importBuilder); >+ }; >+ break; >+ >+ case "Function": >+ this[section] = function() { >+ const s = this._addSection(section); >+ const functionBuilder = { >+ End: () => this >+ // FIXME: add ability to add this with whatever. >+ }; >+ return _errorHandlingProxyFor(functionBuilder); >+ }; >+ break; >+ >+ case "Table": >+ this[section] = function() { >+ const s = this._addSection(section); >+ const tableBuilder = { >+ End: () => this, >+ Table: ({initial, maximum, element}) => { >+ s.data.push({tableDescription: {initial, maximum, element}}); >+ return _errorHandlingProxyFor(tableBuilder); >+ } >+ }; >+ return _errorHandlingProxyFor(tableBuilder); >+ }; >+ break; >+ >+ case "Memory": >+ this[section] = function() { >+ const s = this._addSection(section); >+ const memoryBuilder = { >+ End: () => this, >+ InitialMaxPages: (initial, maximum) => { >+ s.data.push({ initial, maximum }); >+ return _errorHandlingProxyFor(memoryBuilder); >+ } >+ }; >+ return _errorHandlingProxyFor(memoryBuilder); >+ }; >+ break; >+ >+ case "Global": >+ this[section] = function() { >+ const s = this._addSection(section); >+ const globalBuilder = { >+ End: () => this, >+ GetGlobal: (type, initValue, mutability) => { >+ s.data.push({ type, op: "get_global", mutability: _normalizeMutability(mutability), initValue }); >+ return _errorHandlingProxyFor(globalBuilder); >+ }, >+ RefFunc: (type, initValue, mutability) => { >+ s.data.push({ type, op: "ref.func", mutability: _normalizeMutability(mutability), initValue }); >+ return _errorHandlingProxyFor(globalBuilder); >+ }, >+ RefNull: (type, mutability) => { >+ s.data.push({ type, op: "ref.null", mutability: _normalizeMutability(mutability) }); >+ return _errorHandlingProxyFor(globalBuilder); >+ } >+ }; >+ for (let op of WASM.description.value_type) { >+ globalBuilder[util.toJavaScriptName(op)] = (initValue, mutability) => { >+ s.data.push({ type: op, op: op + ".const", mutability: _normalizeMutability(mutability), initValue }); >+ return _errorHandlingProxyFor(globalBuilder); >+ }; >+ } >+ return _errorHandlingProxyFor(globalBuilder); >+ }; >+ break; >+ >+ case "Export": >+ this[section] = function() { >+ const s = this._addSection(section); >+ const exportBuilder = { >+ End: () => this, >+ }; >+ exportBuilder.Global = _exportGlobalContinuation(this, s, exportBuilder); >+ exportBuilder.Function = _exportFunctionContinuation(this, s, exportBuilder); >+ exportBuilder.Memory = _exportMemoryContinuation(this, s, exportBuilder); >+ exportBuilder.Table = _exportTableContinuation(this, s, exportBuilder); >+ return _errorHandlingProxyFor(exportBuilder); >+ }; >+ break; >+ >+ case "Start": >+ this[section] = function(functionIndexOrName) { >+ const s = this._addSection(section); >+ const startBuilder = { >+ End: () => this, >+ }; >+ if (typeof(functionIndexOrName) !== "number" && typeof(functionIndexOrName) !== "string") >+ throw new Error(`Start section's function index must either be a number or a string`); >+ s.data.push(functionIndexOrName); >+ return _errorHandlingProxyFor(startBuilder); >+ }; >+ break; >+ >+ case "Element": >+ this[section] = function(...args) { >+ if (args.length !== 0 && this._checked) >+ throw new Error("You're doing it wrong. This element does not take arguments. You must chain the call with another Element()"); >+ >+ const s = this._addSection(section); >+ const elementBuilder = { >+ End: () => this, >+ Element: ({tableIndex = 0, offset, functionIndices}) => { >+ s.data.push({tableIndex, offset, functionIndices}); >+ return _errorHandlingProxyFor(elementBuilder); >+ } >+ }; >+ >+ return _errorHandlingProxyFor(elementBuilder); >+ }; >+ break; >+ >+ case "Code": >+ this[section] = function() { >+ const s = this._addSection(section); >+ const builder = this; >+ const codeBuilder = { >+ End: () => { >+ // We now have enough information to remap the export section's "type" and "index" according to the Code section we are currently ending. >+ const typeSection = builder._getSection("Type"); >+ const importSection = builder._getSection("Import"); >+ const exportSection = builder._getSection("Export"); >+ const startSection = builder._getSection("Start"); >+ const codeSection = s; >+ if (exportSection) { >+ for (const e of exportSection.data) { >+ if (e.kind !== "Function" || typeof(e.type) !== "undefined") >+ continue; >+ 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}" does not 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). >+ let functionIndexSpaceOffset = 0; >+ if (importSection) { >+ for (const {kind} of importSection.data) { >+ if (kind === "Function") >+ ++functionIndexSpaceOffset; >+ } >+ } >+ const functionIndex = e.index - functionIndexSpaceOffset; >+ e.type = codeSection.data[functionIndex].type; >+ } >+ } >+ } >+ if (startSection) { >+ const start = startSection.data[0]; >+ let mapped = builder._getFunctionFromIndexSpace(start); >+ if (!builder._checked) { >+ if (typeof(mapped) === "undefined") >+ mapped = start; // In unchecked mode, simply use what was provided if it's nonsensical. >+ assert.isA(start, "number"); // It can't be too nonsensical, otherwise we can't create a binary. >+ startSection.data[0] = start; >+ } else { >+ if (typeof(mapped) === "undefined") >+ throw new Error(`Start section refers to non-existant function '${start}'`); >+ if (typeof(start) === "string" || typeof(start) === "object") >+ startSection.data[0] = mapped; >+ // FIXME in checked mode, test that the type is acceptable for start function. We probably want _registerFunctionToIndexSpace to also register types per index. https://bugs.webkit.org/show_bug.cgi?id=165658 >+ } >+ } >+ return _errorHandlingProxyFor(builder); >+ }, >+ >+ }; >+ codeBuilder.Function = _createFunction(s, builder, codeBuilder); >+ return _errorHandlingProxyFor(codeBuilder); >+ }; >+ break; >+ >+ case "Data": >+ this[section] = function() { >+ const s = this._addSection(section); >+ const dataBuilder = { >+ End: () => this, >+ Segment: data => { >+ assert.isArray(data); >+ for (const datum of data) { >+ assert.isNumber(datum); >+ assert.ge(datum, 0); >+ assert.le(datum, 0xff); >+ } >+ s.data.push({ data: data, index: 0, offset: 0 }); >+ let thisSegment = s.data[s.data.length - 1]; >+ const segmentBuilder = { >+ End: () => dataBuilder, >+ Index: index => { >+ assert.eq(index, 0); // Linear memory index must be zero in MVP. >+ thisSegment.index = index; >+ return _errorHandlingProxyFor(segmentBuilder); >+ }, >+ Offset: offset => { >+ // FIXME allow complex init_expr here. https://bugs.webkit.org/show_bug.cgi?id=165700 >+ assert.isNumber(offset); >+ thisSegment.offset = offset; >+ return _errorHandlingProxyFor(segmentBuilder); >+ }, >+ }; >+ return _errorHandlingProxyFor(segmentBuilder); >+ }, >+ }; >+ return _errorHandlingProxyFor(dataBuilder); >+ }; >+ break; >+ >+ default: >+ this[section] = () => { throw new Error(`Unknown section type "${section}"`); }; >+ break; >+ } >+ } >+ >+ this.Unknown = function(name) { >+ const s = this._addSection(name); >+ const builder = this; >+ const unknownBuilder = { >+ End: () => builder, >+ Byte: b => { >+ assert.eq(b & 0xFF, b, `Unknown section expected byte, got: "${b}"`); >+ s.data.push(b); >+ return _errorHandlingProxyFor(unknownBuilder); >+ } >+ }; >+ return _errorHandlingProxyFor(unknownBuilder); >+ }; >+ } >+ _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) >+ if (number !== _unknownSectionId) >+ 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 not 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; >+ } >+ json() { >+ const obj = { >+ preamble: this._preamble, >+ section: this._sections >+ }; >+ // JSON.stringify serializes -0.0 as 0.0. >+ const replacer = (key, value) => { >+ if (value === 0.0 && 1.0 / value === -Infinity) >+ return "NEGATIVE_ZERO"; >+ return value; >+ }; >+ return JSON.stringify(obj, replacer); >+ } >+ AsmJS() { >+ "use asm"; // For speed. >+ // 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 BuildWebAssembly.Binary(this._preamble, this._sections); } >+}; >+ >+// End Builder >+ >+const WORKER_COUNT = 5; >+let count = 0; >+function done() { >+ if (++count === WORKER_COUNT) { >+ console.log("Finished test"); >+ if (window.testRunner) >+ testRunner.notifyDone(); >+ } >+} >+ >+function runTest() { >+ const $1 = new WebAssembly.Module((new Builder()) >+ .Type().End() >+ .Function().End() >+ .Table() >+ .Table({initial: 0, maximum: 0, element: "anyref"}) >+ .Table({initial: 20, maximum: 30, element: "anyref"}) >+ .End() >+ .Export() >+ .Function("tbl_size") >+ .Function("tbl_grow") >+ .Function("tbl_fill") >+ .Function("make_ref") >+ .Table("tbl", 1) >+ .End() >+ .Code() >+ .Function("tbl_size", { params: [], ret: "i32" }) >+ .TableSize(1) >+ .End() >+ .Function("tbl_grow", { params: ["i32"], ret: "i32" }) >+ .RefFunc(0) >+ .GetLocal(0) >+ .TableGrow(1) >+ .End() >+ .Function("tbl_fill", { params: ["i32", "anyref", "i32"], ret: "void" }) >+ .GetLocal(0) >+ .GetLocal(1) >+ .GetLocal(2) >+ .TableFill(1) >+ .End() >+ .Function("make_ref", { params: [], ret: "funcref" }) >+ .RefFunc(1) >+ .End() >+ .End().WebAssembly().get()); >+ >+ for (let i=0; i<WORKER_COUNT; ++i) { >+ let worker = new Worker("./wasm-references/worker.js"); >+ worker.onmessage = function(e) { >+ if (e.data === "done") { >+ done(); >+ return; >+ } >+ >+ console.log("Bad result: " + e.data); >+ if (window.testRunner) >+ testRunner.notifyDone(); >+ } >+ worker.postMessage($1); >+ } >+} >+ >+function doGC() { >+ let garbage = { val: "hi", val2: 5, arr: [] } >+ for (let i=0; i<100; ++i) garbage.arr += ({ field: i }) >+ >+ for (let j=0; j<50; ++j) { >+ if(window.testRunner) >+ GCController.collect(); >+ } >+} >+ >+runTest() >+doGC() >+done() >diff --git a/LayoutTests/workers/wasm-references/worker.js b/LayoutTests/workers/wasm-references/worker.js >new file mode 100644 >index 0000000000000000000000000000000000000000..96d1f60d518b27826a199f1b8c0b5bdc660a7969 >--- /dev/null >+++ b/LayoutTests/workers/wasm-references/worker.js >@@ -0,0 +1,200 @@ >+/* >+ * Copyright (C) 2019 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. >+ */ >+ >+const _fail = (msg, extra) => { >+ throw new Error(msg + (extra ? ": " + extra : "")); >+}; >+ >+const assert = {}; >+ >+const isNotA = assert.isNotA = (v, t, msg) => { >+ if (typeof v === t) >+ _fail(`Shouldn't be ${t}`, msg); >+}; >+ >+const isA = assert.isA = (v, t, msg) => { >+ if (typeof v !== t) >+ _fail(`Should be ${t}, got ${typeof(v)}`, msg); >+}; >+ >+const isNotUndef = assert.isNotUndef = (v, msg) => isNotA(v, "undefined", msg); >+assert.isUndef = (v, msg) => isA(v, "undefined", msg); >+assert.notObject = (v, msg) => isNotA(v, "object", msg); >+const isObject = assert.isObject = (v, msg) => isA(v, "object", msg); >+assert.notString = (v, msg) => isNotA(v, "string", msg); >+assert.isString = (v, msg) => isA(v, "string", msg); >+assert.notNumber = (v, msg) => isNotA(v, "number", msg); >+assert.isNumber = (v, msg) => isA(v, "number", msg); >+assert.notFunction = (v, msg) => isNotA(v, "function", msg); >+assert.isFunction = (v, msg) => isA(v, "function", msg); >+ >+assert.hasObjectProperty = (o, p, msg) => { >+ isObject(o, msg); >+ isNotUndef(o[p], msg, `expected object to have property ${p}`); >+}; >+ >+assert.isArray = (v, msg) => { >+ if (!Array.isArray(v)) >+ _fail(`Expected an array, got ${typeof(v)}`, msg); >+}; >+ >+assert.isNotArray = (v, msg) => { >+ if (Array.isArray(v)) >+ _fail(`Expected to not be an array`, msg); >+}; >+ >+assert.truthy = (v, msg) => { >+ if (!v) >+ _fail(`Expected truthy`, msg); >+}; >+ >+assert.falsy = (v, msg) => { >+ if (v) >+ _fail(`Expected falsy`, msg); >+}; >+ >+assert.eq = (lhs, rhs, msg) => { >+ if (typeof lhs !== typeof rhs) >+ _fail(`Not the same: "${lhs}" and "${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) { >+ if (typeof lhs === "number" && isNaN(lhs) && isNaN(rhs)) >+ return; >+ _fail(`Not the same: "${lhs}" and "${rhs}"`, msg); >+ } else { >+ if (typeof lhs === "number" && (1.0 / lhs !== 1.0 / rhs)) // Distinguish -0.0 from 0.0. >+ _fail(`Not the same: "${lhs}" and "${rhs}"`, msg); >+ } >+}; >+ >+const canonicalizeI32 = (number) => { >+ if (Math.round(number) === number && number >= 2 ** 31) >+ number = number - 2 ** 32; >+ return number; >+} >+ >+assert.eqI32 = (lhs, rhs, msg) => { >+ return eq(canonicalizeI32(lhs), canonicalizeI32(rhs), msg); >+}; >+ >+assert.ge = (lhs, rhs, msg) => { >+ isNotUndef(lhs); >+ isNotUndef(rhs); >+ if (!(lhs >= rhs)) >+ _fail(`Expected: "${lhs}" < "${rhs}"`, msg); >+}; >+ >+assert.le = (lhs, rhs, msg) => { >+ isNotUndef(lhs); >+ isNotUndef(rhs); >+ if (!(lhs <= rhs)) >+ _fail(`Expected: "${lhs}" > "${rhs}"`, msg); >+}; >+ >+const _throws = (func, type, message, ...args) => { >+ try { >+ func(...args); >+ } catch (e) { >+ if (e instanceof type) { >+ if (e.message === message) >+ return e; >+ // Ignore source information at the end of the error message if the >+ // expected message didn't specify that information. Sometimes it >+ // changes, or it's tricky to get just right. >+ const evaluatingIndex = e.message.indexOf(" (evaluating '"); >+ if (evaluatingIndex !== -1) { >+ const cleanMessage = e.message.substring(0, evaluatingIndex); >+ if (cleanMessage === message) >+ return e; >+ } >+ } >+ _fail(`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}"`); >+}; >+ >+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. >+assert.throws = _throws; >+assert.instanceof = _instanceof; >+ >+const asyncTestImpl = (promise, thenFunc, catchFunc) => { >+ asyncTestStart(1); >+ promise.then(thenFunc).catch(catchFunc); >+}; >+ >+const printExn = (e) => { >+ print("Failed: ", e); >+ print(e.stack); >+}; >+ >+assert.asyncTest = (promise) => asyncTestImpl(promise, asyncTestPassed, printExn); >+assert.asyncTestEq = (promise, expected) => { >+ const thenCheck = (value) => { >+ if (value === expected) >+ return asyncTestPassed(); >+ print("Failed: got ", value, " but expected ", expected); >+ >+ } >+ asyncTestImpl(promise, thenCheck, printExn); >+}; >+ >+// ------ end assert >+ >+onmessage = function({ data: module }) { >+ WebAssembly.instantiate(module).then(($1) => { >+ try { >+ assert.eq($1.exports.tbl_size(), 20) >+ assert.eq($1.exports.tbl_grow(5), 20) >+ assert.eq($1.exports.tbl_size(), 25) >+ assert.eq($1.exports.tbl.get(0), null) >+ assert.eq($1.exports.tbl.get(24), $1.exports.tbl_size) >+ >+ for (let i=0; i<1000; ++i) { >+ $1.exports.tbl_fill(1,"hi",3) >+ assert.eq($1.exports.tbl.get(0), null) >+ assert.eq($1.exports.tbl.get(1), "hi") >+ assert.eq($1.exports.tbl.get(2), "hi") >+ assert.eq($1.exports.tbl.get(3), "hi") >+ assert.eq($1.exports.tbl.get(4), null) >+ >+ assert.eq($1.exports.make_ref(), $1.exports.tbl_grow) >+ } >+ } catch (e) { >+ console.log(e) >+ postMessage("" + e) >+ return >+ } >+ >+ console.log("Good result"); >+ postMessage("done"); >+ }, (e) => postMessage("" + e)) >+} >diff --git a/JSTests/ChangeLog b/JSTests/ChangeLog >index 05d012357bfe2194ac8f78369def88e12e128c85..907736c37bb2f05ed4a8f857ef05196c72e61d35 100644 >--- a/JSTests/ChangeLog >+++ b/JSTests/ChangeLog >@@ -1,3 +1,78 @@ >+2019-06-19 Justin Michaud <justin_michaud@apple.com> >+ >+ [WASM-References] Add extra tests for Wasm references + fix element parsing and subtyping bugs >+ https://bugs.webkit.org/show_bug.cgi?id=199044 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ Add wasm references spec tests as well as a worker test. >+ >+ * wasm.yaml: >+ * wasm/Builder_WebAssemblyBinary.js: >+ (const.emitters.Element): >+ * wasm/js-api/element.js: >+ (assert.throws.new.WebAssembly.Module.builder.WebAssembly): >+ * wasm/references-spec-tests/ref_is_null.js: Added. >+ (hostref): >+ (is_hostref): >+ (is_funcref): >+ (eq_ref): >+ (let.handler.get target): >+ (register): >+ (module): >+ (instance): >+ (call): >+ (get instance): >+ (exports): >+ (run): >+ (assert_malformed): >+ (assert_invalid): >+ (assert_unlinkable): >+ (assert_uninstantiable): >+ (assert_trap): >+ (try.f): >+ (catch): >+ (assert_exhaustion): >+ (assert_return): >+ (assert_return_canonical_nan): >+ (assert_return_arithmetic_nan): >+ (assert_return_ref): >+ (assert_return_func): >+ * wasm/references-spec-tests/ref_null.js: Added. >+ (hostref): >+ (is_hostref): >+ (is_funcref): >+ (eq_ref): >+ (let.handler.get target): >+ (register): >+ (module): >+ (instance): >+ (call): >+ (get instance): >+ (exports): >+ (run): >+ (assert_malformed): >+ (assert_invalid): >+ (assert_unlinkable): >+ (assert_uninstantiable): >+ (assert_trap): >+ (try.f): >+ (catch): >+ (assert_exhaustion): >+ (assert_return): >+ (assert_return_canonical_nan): >+ (assert_return_arithmetic_nan): >+ (assert_return_ref): >+ (assert_return_func): >+ * wasm/references/element_parsing.js: Added. >+ (module): >+ * wasm/references/func_ref.js: >+ * wasm/references/multitable.js: >+ * wasm/references/table_misc.js: >+ (TableSize.0.End.End.WebAssembly): >+ * wasm/references/validation.js: >+ (assert.throws): >+ > 2019-06-19 Justin Michaud <justin_michaud@apple.com> > > [WASM-References] Rename anyfunc to funcref >diff --git a/JSTests/wasm.yaml b/JSTests/wasm.yaml >index 45dbd1acb7093df26704129c6b42f4cac39d63e5..f8711f74baeed43c7f2338151d71a16dad65e96b 100644 >--- a/JSTests/wasm.yaml >+++ b/JSTests/wasm.yaml >@@ -31,6 +31,8 @@ > cmd: runWebAssemblySuite unless parseRunCommands > - path: wasm/references > cmd: runWebAssemblySuite unless parseRunCommands >+- path: wasm/references-spec-tests >+ cmd: runWebAssemblySuite unless parseRunCommands > - path: wasm/fuzz > cmd: runWebAssemblySuite unless parseRunCommands > - path: wasm/stress >diff --git a/JSTests/wasm/Builder_WebAssemblyBinary.js b/JSTests/wasm/Builder_WebAssemblyBinary.js >index 4f3a23f74ec3dc43ea452a44f3baec2ddd731ea5..86338d345dc8996d13a40012b35d541729ae65da 100644 >--- a/JSTests/wasm/Builder_WebAssemblyBinary.js >+++ b/JSTests/wasm/Builder_WebAssemblyBinary.js >@@ -194,6 +194,8 @@ const emitters = { > const data = section.data; > put(bin, "varuint32", data.length); > for (const {tableIndex, offset, functionIndices} of data) { >+ if (tableIndex != 0) >+ put(bin, "uint8", 2); > put(bin, "varuint32", tableIndex); > > let initExpr; >diff --git a/JSTests/wasm/js-api/element.js b/JSTests/wasm/js-api/element.js >index acf99f8c3e226826df5c60114cb37ba9cb244846..7b80d509afb26bad2c7ccb6e38e52ecb252330a8 100644 >--- a/JSTests/wasm/js-api/element.js >+++ b/JSTests/wasm/js-api/element.js >@@ -41,7 +41,7 @@ import * as assert from '../assert.js'; > .End() > .End(); > >- assert.throws(() => new WebAssembly.Module(builder.WebAssembly().get()), WebAssembly.CompileError, "WebAssembly.Module doesn't parse at byte 30: Element section for Table 1 exceeds available Table 1"); >+ assert.throws(() => new WebAssembly.Module(builder.WebAssembly().get()), WebAssembly.CompileError, "WebAssembly.Module doesn't parse at byte 31: Element section for Table 1 exceeds available Table 1"); > } > > { >diff --git a/JSTests/wasm/references-spec-tests/ref_is_null.js b/JSTests/wasm/references-spec-tests/ref_is_null.js >new file mode 100644 >index 0000000000000000000000000000000000000000..83ab049faf5d76d0b965810c42beaf2005ded6fa >--- /dev/null >+++ b/JSTests/wasm/references-spec-tests/ref_is_null.js >@@ -0,0 +1,214 @@ >+ >+'use strict'; >+let console = { log: print } >+let hostrefs = {}; >+let hostsym = Symbol("hostref"); >+function hostref(s) { >+ if (! (s in hostrefs)) hostrefs[s] = {[hostsym]: s}; >+ return hostrefs[s]; >+} >+function is_hostref(x) { >+ return (x !== null && hostsym in x) ? 1 : 0; >+} >+function is_funcref(x) { >+ return typeof x === "function" ? 1 : 0; >+} >+function eq_ref(x, y) { >+ return x === y ? 1 : 0; >+} >+ >+let spectest = { >+ hostref: hostref, >+ is_hostref: is_hostref, >+ is_funcref: is_funcref, >+ eq_ref: eq_ref, >+ print: console.log.bind(console), >+ print_i32: console.log.bind(console), >+ print_i32_f32: console.log.bind(console), >+ print_f64_f64: console.log.bind(console), >+ print_f32: console.log.bind(console), >+ print_f64: console.log.bind(console), >+ global_i32: 666, >+ global_f32: 666, >+ global_f64: 666, >+ table: new WebAssembly.Table({initial: 10, maximum: 20, element: 'anyfunc'}), >+ memory: new WebAssembly.Memory({initial: 1, maximum: 2}) >+}; >+ >+let handler = { >+ get(target, prop) { >+ return (prop in target) ? target[prop] : {}; >+ } >+}; >+let registry = new Proxy({spectest}, handler); >+ >+function register(name, instance) { >+ registry[name] = instance.exports; >+} >+ >+function module(bytes, valid = true) { >+ let buffer = new ArrayBuffer(bytes.length); >+ let view = new Uint8Array(buffer); >+ for (let i = 0; i < bytes.length; ++i) { >+ view[i] = bytes.charCodeAt(i); >+ } >+ let validated; >+ try { >+ validated = WebAssembly.validate(buffer); >+ } catch (e) { >+ throw new Error("Wasm validate throws"); >+ } >+ if (validated !== valid) { >+ throw new Error("Wasm validate failure" + (valid ? "" : " expected")); >+ } >+ return new WebAssembly.Module(buffer); >+} >+ >+function instance(bytes, imports = registry) { >+ return new WebAssembly.Instance(module(bytes), imports); >+} >+ >+function call(instance, name, args) { >+ return instance.exports[name](...args); >+} >+ >+function get(instance, name) { >+ let v = instance.exports[name]; >+ return (v instanceof WebAssembly.Global) ? v.value : v; >+} >+ >+function exports(instance) { >+ return {module: instance.exports, spectest: spectest}; >+} >+ >+function run(action) { >+ action(); >+} >+ >+function assert_malformed(bytes) { >+ try { module(bytes, false) } catch (e) { >+ if (e instanceof WebAssembly.CompileError) return; >+ } >+ throw new Error("Wasm decoding failure expected"); >+} >+ >+function assert_invalid(bytes) { >+ try { module(bytes, false) } catch (e) { >+ if (e instanceof WebAssembly.CompileError) return; >+ } >+ throw new Error("Wasm validation failure expected"); >+} >+ >+function assert_unlinkable(bytes) { >+ let mod = module(bytes); >+ try { new WebAssembly.Instance(mod, registry) } catch (e) { >+ if (e instanceof WebAssembly.LinkError) return; >+ } >+ throw new Error("Wasm linking failure expected"); >+} >+ >+function assert_uninstantiable(bytes) { >+ let mod = module(bytes); >+ try { new WebAssembly.Instance(mod, registry) } catch (e) { >+ if (e instanceof WebAssembly.RuntimeError) return; >+ } >+ throw new Error("Wasm trap expected"); >+} >+ >+function assert_trap(action) { >+ try { action() } catch (e) { >+ if (e instanceof WebAssembly.RuntimeError) return; >+ } >+ throw new Error("Wasm trap expected"); >+} >+ >+let StackOverflow; >+try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor } >+ >+function assert_exhaustion(action) { >+ try { action() } catch (e) { >+ if (e instanceof StackOverflow) return; >+ } >+ throw new Error("Wasm resource exhaustion expected"); >+} >+ >+function assert_return(action, expected) { >+ let actual = action(); >+ if (!Object.is(actual, expected)) { >+ throw new Error("Wasm return value " + expected + " expected, got " + actual); >+ }; >+} >+ >+function assert_return_canonical_nan(action) { >+ let actual = action(); >+ // Note that JS can't reliably distinguish different NaN values, >+ // so there's no good way to test that it's a canonical NaN. >+ if (!Number.isNaN(actual)) { >+ throw new Error("Wasm return value NaN expected, got " + actual); >+ }; >+} >+ >+function assert_return_arithmetic_nan(action) { >+ // Note that JS can't reliably distinguish different NaN values, >+ // so there's no good way to test for specific bitpatterns here. >+ let actual = action(); >+ if (!Number.isNaN(actual)) { >+ throw new Error("Wasm return value NaN expected, got " + actual); >+ }; >+} >+ >+function assert_return_ref(action) { >+ let actual = action(); >+ if (actual === null || typeof actual !== "object" && typeof actual !== "function") { >+ throw new Error("Wasm reference return value expected, got " + actual); >+ }; >+} >+ >+function assert_return_func(action) { >+ let actual = action(); >+ if (typeof actual !== "function") { >+ throw new Error("Wasm function return value expected, got " + actual); >+ }; >+} >+ >+// ref_is_null.wast:1 >+let $1 = instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x97\x80\x80\x80\x00\x05\x60\x01\x6f\x01\x7f\x60\x01\x70\x01\x7f\x60\x00\x00\x60\x01\x6f\x00\x60\x01\x7f\x01\x7f\x03\x88\x80\x80\x80\x00\x07\x00\x01\x02\x03\x02\x04\x04\x04\x87\x80\x80\x80\x00\x02\x6f\x00\x02\x70\x00\x02\x07\xc1\x80\x80\x80\x00\x06\x06\x61\x6e\x79\x72\x65\x66\x00\x00\x07\x66\x75\x6e\x63\x72\x65\x66\x00\x01\x04\x69\x6e\x69\x74\x00\x03\x06\x64\x65\x69\x6e\x69\x74\x00\x04\x0b\x61\x6e\x79\x72\x65\x66\x2d\x65\x6c\x65\x6d\x00\x05\x0c\x66\x75\x6e\x63\x72\x65\x66\x2d\x65\x6c\x65\x6d\x00\x06\x09\x88\x80\x80\x80\x00\x01\x02\x01\x41\x01\x0b\x01\x02\x0a\xd4\x80\x80\x80\x00\x07\x85\x80\x80\x80\x00\x00\x20\x00\xd1\x0b\x85\x80\x80\x80\x00\x00\x20\x00\xd1\x0b\x82\x80\x80\x80\x00\x00\x0b\x88\x80\x80\x80\x00\x00\x41\x01\x20\x00\x26\x00\x0b\x8c\x80\x80\x80\x00\x00\x41\x01\xd0\x26\x00\x41\x01\xd0\x26\x01\x0b\x88\x80\x80\x80\x00\x00\x20\x00\x25\x00\x10\x00\x0b\x88\x80\x80\x80\x00\x00\x20\x00\x25\x01\x10\x01\x0b"); >+ >+// ref_is_null.wast:29 >+assert_return(() => call($1, "anyref", [null]), 1); >+ >+// ref_is_null.wast:30 >+assert_return(() => call($1, "funcref", [null]), 1); >+ >+// ref_is_null.wast:32 >+assert_return(() => call($1, "anyref", [hostref(1)]), 0); >+ >+// ref_is_null.wast:34 >+run(() => call($1, "init", [hostref(0)])); >+ >+// ref_is_null.wast:36 >+assert_return(() => call($1, "anyref-elem", [0]), 1); >+ >+// ref_is_null.wast:37 >+assert_return(() => call($1, "funcref-elem", [0]), 1); >+ >+// ref_is_null.wast:39 >+assert_return(() => call($1, "anyref-elem", [1]), 0); >+ >+// ref_is_null.wast:40 >+assert_return(() => call($1, "funcref-elem", [1]), 0); >+ >+// ref_is_null.wast:42 >+run(() => call($1, "deinit", [])); >+ >+// ref_is_null.wast:44 >+assert_return(() => call($1, "anyref-elem", [0]), 1); >+ >+// ref_is_null.wast:45 >+assert_return(() => call($1, "funcref-elem", [0]), 1); >+ >+// ref_is_null.wast:47 >+assert_return(() => call($1, "anyref-elem", [1]), 1); >+ >+// ref_is_null.wast:48 >+assert_return(() => call($1, "funcref-elem", [1]), 1); >diff --git a/JSTests/wasm/references-spec-tests/ref_null.js b/JSTests/wasm/references-spec-tests/ref_null.js >new file mode 100644 >index 0000000000000000000000000000000000000000..d02590436592c9c8dc6bc152f7c58f15f538a788 >--- /dev/null >+++ b/JSTests/wasm/references-spec-tests/ref_null.js >@@ -0,0 +1,183 @@ >+'use strict'; >+ >+let console = { log: print } >+ >+let hostrefs = {}; >+let hostsym = Symbol("hostref"); >+function hostref(s) { >+ if (! (s in hostrefs)) hostrefs[s] = {[hostsym]: s}; >+ return hostrefs[s]; >+} >+function is_hostref(x) { >+ return (x !== null && hostsym in x) ? 1 : 0; >+} >+function is_funcref(x) { >+ return typeof x === "function" ? 1 : 0; >+} >+function eq_ref(x, y) { >+ return x === y ? 1 : 0; >+} >+ >+let spectest = { >+ hostref: hostref, >+ is_hostref: is_hostref, >+ is_funcref: is_funcref, >+ eq_ref: eq_ref, >+ print: console.log.bind(console), >+ print_i32: console.log.bind(console), >+ print_i32_f32: console.log.bind(console), >+ print_f64_f64: console.log.bind(console), >+ print_f32: console.log.bind(console), >+ print_f64: console.log.bind(console), >+ global_i32: 666, >+ global_f32: 666, >+ global_f64: 666, >+ table: new WebAssembly.Table({initial: 10, maximum: 20, element: 'anyfunc'}), >+ memory: new WebAssembly.Memory({initial: 1, maximum: 2}) >+}; >+ >+let handler = { >+ get(target, prop) { >+ return (prop in target) ? target[prop] : {}; >+ } >+}; >+let registry = new Proxy({spectest}, handler); >+ >+function register(name, instance) { >+ registry[name] = instance.exports; >+} >+ >+function module(bytes, valid = true) { >+ let buffer = new ArrayBuffer(bytes.length); >+ let view = new Uint8Array(buffer); >+ for (let i = 0; i < bytes.length; ++i) { >+ view[i] = bytes.charCodeAt(i); >+ } >+ let validated; >+ try { >+ validated = WebAssembly.validate(buffer); >+ } catch (e) { >+ console.log(e) >+ throw new Error("Wasm validate throws"); >+ } >+ if (validated !== valid) { >+ //throw new Error("Wasm validate failure" + (valid ? "" : " expected")); >+ } >+ return new WebAssembly.Module(buffer); >+} >+ >+function instance(bytes, imports = registry) { >+ return new WebAssembly.Instance(module(bytes), imports); >+} >+ >+function call(instance, name, args) { >+ return instance.exports[name](...args); >+} >+ >+function get(instance, name) { >+ let v = instance.exports[name]; >+ return (v instanceof WebAssembly.Global) ? v.value : v; >+} >+ >+function exports(instance) { >+ return {module: instance.exports, spectest: spectest}; >+} >+ >+function run(action) { >+ action(); >+} >+ >+function assert_malformed(bytes) { >+ try { module(bytes, false) } catch (e) { >+ if (e instanceof WebAssembly.CompileError) return; >+ } >+ throw new Error("Wasm decoding failure expected"); >+} >+ >+function assert_invalid(bytes) { >+ try { module(bytes, false) } catch (e) { >+ if (e instanceof WebAssembly.CompileError) return; >+ } >+ throw new Error("Wasm validation failure expected"); >+} >+ >+function assert_unlinkable(bytes) { >+ let mod = module(bytes); >+ try { new WebAssembly.Instance(mod, registry) } catch (e) { >+ if (e instanceof WebAssembly.LinkError) return; >+ } >+ throw new Error("Wasm linking failure expected"); >+} >+ >+function assert_uninstantiable(bytes) { >+ let mod = module(bytes); >+ try { new WebAssembly.Instance(mod, registry) } catch (e) { >+ if (e instanceof WebAssembly.RuntimeError) return; >+ } >+ throw new Error("Wasm trap expected"); >+} >+ >+function assert_trap(action) { >+ try { action() } catch (e) { >+ if (e instanceof WebAssembly.RuntimeError) return; >+ } >+ throw new Error("Wasm trap expected"); >+} >+ >+let StackOverflow; >+try { (function f() { 1 + f() })() } catch (e) { StackOverflow = e.constructor } >+ >+function assert_exhaustion(action) { >+ try { action() } catch (e) { >+ if (e instanceof StackOverflow) return; >+ } >+ throw new Error("Wasm resource exhaustion expected"); >+} >+ >+function assert_return(action, expected) { >+ let actual = action(); >+ if (!Object.is(actual, expected)) { >+ throw new Error("Wasm return value " + expected + " expected, got " + actual); >+ }; >+} >+ >+function assert_return_canonical_nan(action) { >+ let actual = action(); >+ // Note that JS can't reliably distinguish different NaN values, >+ // so there's no good way to test that it's a canonical NaN. >+ if (!Number.isNaN(actual)) { >+ throw new Error("Wasm return value NaN expected, got " + actual); >+ }; >+} >+ >+function assert_return_arithmetic_nan(action) { >+ // Note that JS can't reliably distinguish different NaN values, >+ // so there's no good way to test for specific bitpatterns here. >+ let actual = action(); >+ if (!Number.isNaN(actual)) { >+ throw new Error("Wasm return value NaN expected, got " + actual); >+ }; >+} >+ >+function assert_return_ref(action) { >+ let actual = action(); >+ if (actual === null || typeof actual !== "object" && typeof actual !== "function") { >+ throw new Error("Wasm reference return value expected, got " + actual); >+ }; >+} >+ >+function assert_return_func(action) { >+ let actual = action(); >+ if (typeof actual !== "function") { >+ throw new Error("Wasm function return value expected, got " + actual); >+ }; >+} >+ >+// ref_null.wast:1 >+let $1 = instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x89\x80\x80\x80\x00\x02\x60\x00\x01\x6f\x60\x00\x01\x70\x03\x83\x80\x80\x80\x00\x02\x00\x01\x06\x89\x80\x80\x80\x00\x02\x6f\x00\xd0\x0b\x70\x00\xd0\x0b\x07\x94\x80\x80\x80\x00\x02\x06\x61\x6e\x79\x72\x65\x66\x00\x00\x07\x66\x75\x6e\x63\x72\x65\x66\x00\x01\x0a\x91\x80\x80\x80\x00\x02\x83\x80\x80\x80\x00\x00\xd0\x0b\x83\x80\x80\x80\x00\x00\xd0\x0b"); >+ >+// ref_null.wast:9 >+assert_return(() => call($1, "anyref", []), null); >+ >+// ref_null.wast:10 >+assert_return(() => call($1, "funcref", []), null); >diff --git a/JSTests/wasm/references/element_parsing.js b/JSTests/wasm/references/element_parsing.js >new file mode 100644 >index 0000000000000000000000000000000000000000..bc27eac6c31099f6002edbb93e709bb4491a3722 >--- /dev/null >+++ b/JSTests/wasm/references/element_parsing.js >@@ -0,0 +1,25 @@ >+import * as assert from '../assert.js'; >+ >+function module(bytes, valid = true) { >+ let buffer = new ArrayBuffer(bytes.length); >+ let view = new Uint8Array(buffer); >+ for (let i = 0; i < bytes.length; ++i) { >+ view[i] = bytes.charCodeAt(i); >+ } >+ let validated; >+ try { >+ validated = WebAssembly.validate(buffer); >+ } catch (e) { >+ console.log(e) >+ throw new Error("Wasm validate throws"); >+ } >+ return new WebAssembly.Module(buffer); >+} >+ >+assert.throws(() => module("\x00\x61\x73\x6d\x01\x00\x00\x00\x01\x97\x80\x80\x80\x00\x05\x60\x01\x6f\x01\x7f\x60\x01\x70\x01\x7f\x60\x00\x00\x60\x01\x6f\x00\x60\x01\x7f\x01\x7f\x03\x88\x80\x80\x80\x00\x07\x00\x01\x02\x03\x02\x04\x04\x04\x87\x80\x80\x80\x00\x02\x6f\x00\x02\x70\x00\x02\x07\xc1\x80\x80\x80\x00\x06\x06\x61\x6e\x79\x72\x65\x66\x00\x00\x07\x66\x75\x6e\x63\x72\x65\x66\x00\x01\x04\x69\x6e\x69\x74\x00\x03\x06\x64\x65\x69\x6e\x69\x74\x00\x04\x0b\x61\x6e\x79\x72\x65\x66\x2d\x65\x6c\x65\x6d\x00\x05\x0c\x66\x75\x6e\x63\x72\x65\x66\x2d\x65\x6c\x65\x6d\x00\x06\x09\x88\x80\x80\x80\x00\x01" >+// Table elem section: >+// 0x02 x:tableidx e:expr y*:vec(funcidx) >+// + "\x02" >+ + "\x01" >+ + "\x41\x01\x0b\x01\x02\x0a\xd4\x80\x80\x80\x00\x07\x85\x80\x80\x80\x00\x00\x20\x00\xd1\x0b\x85\x80\x80\x80\x00\x00\x20\x00\xd1\x0b\x82\x80\x80\x80\x00\x00\x0b\x88\x80\x80\x80\x00\x00\x41\x01\x20\x00\x26\x00\x0b\x8c\x80\x80\x80\x00\x00\x41\x01\xd0\x26\x00\x41\x01\xd0\x26\x01\x0b\x88\x80\x80\x80\x00\x00\x20\x00\x25\x00\x10\x00\x0b\x88\x80\x80\x80\x00\x00\x20\x00\x25\x01\x10\x01\x0b"), >+ Error, "WebAssembly.Module doesn't parse at byte 143: can't get 0th Element reserved byte, which should be either 0x00 or 0x02 followed by a table index (evaluating 'new WebAssembly.Module(buffer)')"); >diff --git a/JSTests/wasm/references/func_ref.js b/JSTests/wasm/references/func_ref.js >index 685adfd6cbba3cbe7840875077610504842d5214..ee5187ef3e6aefced6fd930a96298195e0046f7d 100644 >--- a/JSTests/wasm/references/func_ref.js >+++ b/JSTests/wasm/references/func_ref.js >@@ -242,7 +242,7 @@ assert.throws(() => new WebAssembly.Module((new Builder()) > .Type().End() > .Function().End() > .Code() >- .Function("h", { params: ["funcref"], ret: "anyref" }) >+ .Function("h", { params: ["anyref"], ret: "funcref" }) > .GetLocal(0) > .End() > .End().WebAssembly().get()), Error, "WebAssembly.Module doesn't validate: control flow returns with unexpected type, in function at index 0 (evaluating 'new WebAssembly.Module')"); >diff --git a/JSTests/wasm/references/multitable.js b/JSTests/wasm/references/multitable.js >index 9161589f19f4f99893bb02e00f3e7a9d70474447..784ea5d71313095cff2c45941ae2d516d611ff2b 100644 >--- a/JSTests/wasm/references/multitable.js >+++ b/JSTests/wasm/references/multitable.js >@@ -302,7 +302,7 @@ assert.throws(() => new WebAssembly.Instance(new WebAssembly.Module((new Builder > .Function("ret42", { params: [], ret: "i32" }) > .I32Const(42) > .End() >- .End().WebAssembly().get())), Error, "WebAssembly.Module doesn't parse at byte 40: Table 0 must have type 'funcref' to have an element section (evaluating 'new WebAssembly.Module')") >+ .End().WebAssembly().get())), Error, "WebAssembly.Module doesn't parse at byte 41: Table 0 must have type 'funcref' to have an element section (evaluating 'new WebAssembly.Module')") > > assert.throws(() => new WebAssembly.Instance(new WebAssembly.Module((new Builder()) > .Type().End() >diff --git a/JSTests/wasm/references/table_misc.js b/JSTests/wasm/references/table_misc.js >index c3b599f3ee5b0403a7f81adccfcbc7e211e22f6e..964c62b54199c268d18ef3ae1f0cb963c8273254 100644 >--- a/JSTests/wasm/references/table_misc.js >+++ b/JSTests/wasm/references/table_misc.js >@@ -50,6 +50,7 @@ assert.throws(() => new WebAssembly.Module((new Builder()) > .Export() > .Function("tbl_size") > .Function("tbl_grow") >+ .Function("ident") > .Table("tbl", 1) > .End() > .Code() >@@ -61,9 +62,14 @@ assert.throws(() => new WebAssembly.Module((new Builder()) > .GetLocal(1) > .TableGrow(1) > .End() >+ .Function("ident", { params: ["i32"], ret: "i32" }) >+ .GetLocal(0) >+ .End() > .End().WebAssembly().get())); > fullGC() > >+ assert.eq($1.exports.ident("hi"), 0) >+ > assert.eq($1.exports.tbl_size(), 20) > assert.eq($1.exports.tbl_grow("hi", 5), 20) > assert.eq($1.exports.tbl_size(), 25) >diff --git a/JSTests/wasm/references/validation.js b/JSTests/wasm/references/validation.js >index b7ba61b3c64a3ccb9666a7ef49fc2c06037b4e42..a288c8b4998cbe42ee4ec9def6792ae8c90de449 100644 >--- a/JSTests/wasm/references/validation.js >+++ b/JSTests/wasm/references/validation.js >@@ -73,14 +73,14 @@ import Builder from '../Builder.js'; > const builder = (new Builder()) > .Type().End() > .Import() >- .Table("imp", "tbl", {initial: 2, element: "funcref"}) >+ .Table("imp", "tbl", {initial: 2, element: "anyref"}) > .End() > .Function().End() > .Export() > .Function("j") > .End() > .Code() >- .Function("j", { params: [], ret: "anyref" }) >+ .Function("j", { params: [], ret: "funcref" }) > .I32Const(0) > .TableGet(0) > .End()
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 199044
:
372512
|
372518
|
372523
|
372525
|
372527
|
372532
|
372538
|
372542
|
372570