Source code

Revision control

Copy as Markdown

Other Tools

// |jit-test| test-also=--setpref=wasm_tail_calls=true
// Test that call_indirect will respect subtyping by defining a bunch of types
// and checking every combination of (expected, actual) type.
//
// NOTE: Several of these types are identical to each other to test
// canonicalization as well, and this causes some bloat in the 'subtypeOf'
// lists.
const TESTS = [
{
// (type 0) (equivalent to 1)
type: `(type (sub (func)))`,
subtypeOf: [0, 1],
},
{
// (type 1) (equivalent to 0)
type: `(rec (type (sub (func))))`,
subtypeOf: [0, 1],
},
{
// (type 2)
type: `(rec (type (sub (func))) (type (sub (func))))`,
subtypeOf: [2],
},
{
// (type 3)
// Hack entry of previous to capture that it actually defines
// two types in the recursion group.
type: undefined,
subtypeOf: [3],
},
{
// (type 4) (equivalent to 7)
type: `(type (sub 0 (func)))`,
subtypeOf: [0, 1, 4, 7],
},
{
// (type 5) (equivalent to 8)
type: `(type (sub 4 (func)))`,
subtypeOf: [0, 1, 4, 5, 7, 8],
},
{
// (type 6)
type: `(type (sub 5 (func)))`,
subtypeOf: [0, 1, 4, 5, 6, 7, 8],
},
{
// (type 7) (equivalent to 4)
type: `(type (sub 0 (func)))`,
subtypeOf: [0, 1, 4, 7],
},
{
// (type 8) (equivalent to 5)
type: `(type (sub 7 (func)))`,
subtypeOf: [0, 1, 4, 5, 7, 8],
},
{
// (type 9) - a final type that has an immediate form
type: `(type (func))`,
subtypeOf: [9],
}
];
// Build a module with all the types, functions with those types, and functions
// that call_indirect with those types, and a table with all the functions in
// it.
let typeSection = '';
let importedFuncs = '';
let definedFuncs = '';
let callIndirectFuncs = '';
let returnCallIndirectFuncs = '';
let i = 0;
for (let {type} of TESTS) {
if (type) {
typeSection += type + '\n';
}
importedFuncs += `(func \$import${i} (import "" "import${i}") (type ${i}))\n`;
definedFuncs += `(func \$define${i} (export "define${i}") (type ${i}))\n`;
callIndirectFuncs += `(func (export "call_indirect ${i}") (param i32)
(drop (ref.cast (ref ${i}) (table.get local.get 0)))
(call_indirect (type ${i}) local.get 0)
)\n`;
returnCallIndirectFuncs += `(func (export "return_call_indirect ${i}") (param i32)
(drop (ref.cast (ref ${i}) (table.get local.get 0)))
(return_call_indirect (type ${i}) local.get 0)
)\n`;
i++;
}
let moduleText = `(module
${typeSection}
${importedFuncs}
${definedFuncs}
${callIndirectFuncs}
${returnCallIndirectFuncs}
(table
(export "table")
funcref
(elem ${TESTS.map((x, i) => `\$import${i} \$define${i}`).join(" ")})
)
)`;
// Now go over every combination of (actual, expected). In this case the caller
// (which does the call_indirect) specifies expected and the callee will be the
// actual.
let imports = {
"": Object.fromEntries(TESTS.map((x, i) => [`import${i}`, () => {}])),
};
let exports = wasmEvalText(moduleText, imports).exports;
for (let callerTypeIndex = 0; callerTypeIndex < TESTS.length; callerTypeIndex++) {
for (let calleeTypeIndex = 0; calleeTypeIndex < TESTS.length; calleeTypeIndex++) {
let calleeType = TESTS[calleeTypeIndex];
// If the callee (actual) is a subtype of caller (expected), then this
// should succeed.
let shouldPass = calleeType.subtypeOf.includes(callerTypeIndex);
let calleeImportFuncIndex = calleeTypeIndex * 2;
let calleeDefinedFuncIndex = calleeTypeIndex * 2 + 1;
// print(`expected (type ${callerTypeIndex}) (actual ${calleeTypeIndex})`);
let test = () => {
exports[`call_indirect ${callerTypeIndex}`](calleeImportFuncIndex)
exports[`call_indirect ${callerTypeIndex}`](calleeDefinedFuncIndex)
exports[`return_call_indirect ${callerTypeIndex}`](calleeImportFuncIndex)
exports[`return_call_indirect ${callerTypeIndex}`](calleeDefinedFuncIndex)
};
if (shouldPass) {
test();
} else {
assertErrorMessage(test, WebAssembly.RuntimeError, /mismatch|cast/);
}
}
}