Skip to content

Commit

Permalink
[MERGE #4479 @Cellule] WASM: re-enable short signature
Browse files Browse the repository at this point in the history
Merge pull request #4479 from Cellule:wasm/signature

Follow-up after #4200.
Re-enable Wasm short signature check.
Use more constant variables with names to better document the intend of WasmSignature::FinalizeSignature.
Add test to check various types of signatures
  • Loading branch information
Cellule committed Jan 4, 2018
2 parents 3232600 + dfd9306 commit bc49f89
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 12 deletions.
45 changes: 33 additions & 12 deletions lib/WasmReader/WasmSignature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,11 +79,14 @@ size_t WasmSignature::GetShortSig() const
return m_shortSig;
}

template<bool useShortSig>
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<false>(sig) == isEquivalent);
return isEquivalent;
}
if (GetResultType() == sig->GetResultType() &&
GetParamCount() == sig->GetParamCount() &&
Expand All @@ -93,6 +96,8 @@ bool WasmSignature::IsEquivalent(const WasmSignature* sig) const
}
return false;
}
template bool WasmSignature::IsEquivalent<true>(const WasmSignature*) const;
template bool WasmSignature::IsEquivalent<false>(const WasmSignature*) const;

Js::ArgSlot WasmSignature::GetParamSize(Js::ArgSlot index) const
{
Expand Down Expand Up @@ -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<nBitsForArgs, nBitsForResult>((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
Expand Down
1 change: 1 addition & 0 deletions lib/WasmReader/WasmSignature.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class WasmSignature
uint32 GetSignatureId() const;
size_t GetShortSig() const;

template<bool useShortSig = true>
bool IsEquivalent(const WasmSignature* sig) const;
static WasmSignature* FromIDL(WasmSignatureIDL* sig);

Expand Down
7 changes: 7 additions & 0 deletions test/wasm/rlexe.xml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,13 @@
<compile-flags>-wasm -wasmi64</compile-flags>
</default>
</test>
<test>
<default>
<files>table_signatures.js</files>
<tags>exclude_jshost,exclude_drt,exclude_win7,exclude_mac</tags>
<compile-flags>-wasm -args --no-verbose -endargs</compile-flags>
</default>
</test>
<test>
<default>
<files>call.js</files>
Expand Down
118 changes: 118 additions & 0 deletions test/wasm/table_signatures.js
Original file line number Diff line number Diff line change
@@ -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});

0 comments on commit bc49f89

Please sign in to comment.