diff --git a/lib/WasmReader/WasmSignature.cpp b/lib/WasmReader/WasmSignature.cpp index 772094b8293..5070720b044 100644 --- a/lib/WasmReader/WasmSignature.cpp +++ b/lib/WasmReader/WasmSignature.cpp @@ -79,11 +79,14 @@ size_t WasmSignature::GetShortSig() const return m_shortSig; } +template bool WasmSignature::IsEquivalent(const WasmSignature* sig) const { - if (m_shortSig != Js::Constants::InvalidSignature) + if (useShortSig && m_shortSig != Js::Constants::InvalidSignature) { - return sig->GetShortSig() == m_shortSig; + bool isEquivalent = sig->GetShortSig() == m_shortSig; + Assert(this->IsEquivalent(sig) == isEquivalent); + return isEquivalent; } if (GetResultType() == sig->GetResultType() && GetParamCount() == sig->GetParamCount() && @@ -93,6 +96,8 @@ bool WasmSignature::IsEquivalent(const WasmSignature* sig) const } return false; } +template bool WasmSignature::IsEquivalent(const WasmSignature*) const; +template bool WasmSignature::IsEquivalent(const WasmSignature*) const; Js::ArgSlot WasmSignature::GetParamSize(Js::ArgSlot index) const { @@ -141,24 +146,40 @@ void WasmSignature::FinalizeSignature() } } + // 3 bits for result type, 3 for each arg + const uint32 nBitsForResult = 3; +#ifdef ENABLE_WASM_SIMD + const uint32 nBitsForArgs = 3; +#else + // We can drop 1 bit by excluding void + const uint32 nBitsForArgs = 2; +#endif CompileAssert(Local::Void == 0); + // Make sure we can encode all types (including void) with the number of bits reserved + CompileAssert(Local::Limit <= (1 << nBitsForResult)); + // Make sure we can encode all types (excluding void) with the number of bits reserved + CompileAssert(Local::Limit - 1 <= (1 << nBitsForArgs)); + + ::Math::RecordOverflowPolicy sigOverflow; + const uint32 bitsRequiredForSig = UInt32Math::MulAdd((uint32)paramCount, sigOverflow); + if (sigOverflow.HasOverflowed()) + { + return; + } -#if 0 - CompileAssert(Local::Limit - 1 <= 4); - - // 3 bits for result type, 2 for each arg // we don't need to reserve a sentinel bit because there is no result type with value of 7 - uint32 sigSize = ((uint32)paramCount) * 3 + 3; - if (sigSize <= sizeof(m_shortSig) << 3) + CompileAssert(Local::Limit <= 0b111); + const uint32 nAvailableBits = sizeof(m_shortSig) * 8; + if (bitsRequiredForSig <= nAvailableBits) { - m_shortSig = (m_shortSig << 3) | m_resultType; + // Append the result type to the signature + m_shortSig = (m_shortSig << nBitsForResult) | m_resultType; for (Js::ArgSlot i = 0; i < paramCount; ++i) { - // we can use 2 bits per arg by dropping void - m_shortSig = (m_shortSig << 3) | (m_params[i] - 1); + // Append the param type to the signature, -1 to exclude Void + m_shortSig = (m_shortSig << nBitsForArgs) | (m_params[i] - 1); } } -#endif } Js::ArgSlot WasmSignature::GetParamsSize() const diff --git a/lib/WasmReader/WasmSignature.h b/lib/WasmReader/WasmSignature.h index 861e579cc5d..551d7dda1ab 100644 --- a/lib/WasmReader/WasmSignature.h +++ b/lib/WasmReader/WasmSignature.h @@ -26,6 +26,7 @@ class WasmSignature uint32 GetSignatureId() const; size_t GetShortSig() const; + template bool IsEquivalent(const WasmSignature* sig) const; static WasmSignature* FromIDL(WasmSignatureIDL* sig); diff --git a/test/wasm/rlexe.xml b/test/wasm/rlexe.xml index 892e10f7d0d..cd694048f25 100644 --- a/test/wasm/rlexe.xml +++ b/test/wasm/rlexe.xml @@ -121,6 +121,13 @@ -wasm -wasmi64 + + + table_signatures.js + exclude_jshost,exclude_drt,exclude_win7,exclude_mac + -wasm -args --no-verbose -endargs + + call.js diff --git a/test/wasm/table_signatures.js b/test/wasm/table_signatures.js new file mode 100644 index 00000000000..2631d7b0dd1 --- /dev/null +++ b/test/wasm/table_signatures.js @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------------------------------- +// Copyright (C) Microsoft Corporation and contributors. All rights reserved. +// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information. +//------------------------------------------------------------------------------------------------------- + +WScript.Flag("-wasmI64"); +WScript.LoadScriptFile("../UnitTestFramework/UnitTestFramework.js"); + +const types = { + i32: {param: "i32", result: "(result i32)", body: "i32.const 1"}, + i64: {param: "i64", result: "(result i64)", body: "i64.const 1"}, + f32: {param: "f32", result: "(result f32)", body: "f32.const 1"}, + f64: {param: "f64", result: "(result f64)", body: "f64.const 1"}, + void: {param: "", result: "", body: ""}, +}; + +function* paramTypes() { + while (true) { + yield types.i32; + yield types.i64; + yield types.i32; + yield types.f32; + yield types.i32; + yield types.f64; + } +} + +class Signature { + constructor(id, resType) { + this.id = id; + this.params = []; + this.result = types[resType]; + } + addParam(type) { + this.params.push(type); + } + toWat() { + // Types for the custom functions + return `(type $t${this.id} (func (param ${this.params.map(t => t.param).join(" ")}) ${this.result.result}))`; + } + makeCallwat() { + // Type of the call to the custom functions (first param is the index in the table) + return `(type $callt${this.id} (func (param i32 ${this.params.map(t => t.param).join(" ")}) ${this.result.result}))`; + } +} + +const signatures = []; +const nArgumentsToTest = [0, 1, 2, 3, 10, 25, 50, 200]; +for (const resType in types) { + for (const nArgs of nArgumentsToTest) { + const gen = paramTypes(); + const sigId = signatures.length; + const signature = new Signature(sigId, resType); + for (let j = 0; j < nArgs; ++j) { + signature.addParam(gen.next().value); + } + signatures.push(signature); + } +} + +const moduleText = ` +(module + ${signatures.map(sig => sig.toWat()).join("\n ")} + ${signatures.map(sig => sig.makeCallwat()).join("\n ")} + + (table ${signatures.length} anyfunc) + ;; Add custom functions to the table + (elem (i32.const 0) + ${signatures.map(sig => `$f${sig.id}`).join(" ")} + ) + + ;; Functions to put in the table + ${signatures.map(sig => ` + (func $f${sig.id} (type $t${sig.id}) ${sig.result.body})` + ).join("")} + + ;; Functions to call from the table + ${signatures.map(sig => ` + (func (export "call${sig.id}") (type $callt${sig.id}) + ${sig.params.map((_, iParam) => `(get_local ${iParam + 1})`).join(" ")} + (call_indirect $t${sig.id} (get_local 0)) + )` + ).join("")} +)`; + +const mod = new WebAssembly.Module(WebAssembly.wabt.convertWast2Wasm(moduleText)); +const ex = new WebAssembly.Instance(mod).exports; + +const tests = signatures.map(sig => ({ + name: `Signature $t${sig.id}`, + body() { + if (argv.verbose) { + console.log(`signature wat: ${sig.toWat()}`); + } + assert.doesNotThrow(() => ex[`call${sig.id}`](sig.id), `Calling signature ${sig.id} should not throw`); + for (let j = 0; j < signatures.length; ++j) { + if (j !== sig.id) { + assert.throws(() => ex[`call${j}`](sig.id), WebAssembly.RuntimeError, `Expected signature mismatch between\n${sig.toWat()}\n${signatures[j].toWat()}`, "Function called with invalid signature"); + } + } + } +})); + +WScript.LoadScriptFile("../UnitTestFramework/yargs.js"); +const argv = yargsParse(WScript.Arguments, { + boolean: ["verbose"], + number: ["start", "end"], + default: { + verbose: true, + start: 0, + end: tests.length + } +}).argv; + +const todoTests = tests + .slice(argv.start, argv.end); + +testRunner.run(todoTests, {verbose: argv.verbose});