call-indirect-subtyping.js (3847B)
1 // Test that call_indirect will respect subtyping by defining a bunch of types 2 // and checking every combination of (expected, actual) type. 3 // 4 // NOTE: Several of these types are identical to each other to test 5 // canonicalization as well, and this causes some bloat in the 'subtypeOf' 6 // lists. 7 const TESTS = [ 8 { 9 // (type 0) (equivalent to 1) 10 type: `(type (sub (func)))`, 11 subtypeOf: [0, 1], 12 }, 13 { 14 // (type 1) (equivalent to 0) 15 type: `(rec (type (sub (func))))`, 16 subtypeOf: [0, 1], 17 }, 18 { 19 // (type 2) 20 type: `(rec (type (sub (func))) (type (sub (func))))`, 21 subtypeOf: [2], 22 }, 23 { 24 // (type 3) 25 // Hack entry of previous to capture that it actually defines 26 // two types in the recursion group. 27 type: undefined, 28 subtypeOf: [3], 29 }, 30 { 31 // (type 4) (equivalent to 7) 32 type: `(type (sub 0 (func)))`, 33 subtypeOf: [0, 1, 4, 7], 34 }, 35 { 36 // (type 5) (equivalent to 8) 37 type: `(type (sub 4 (func)))`, 38 subtypeOf: [0, 1, 4, 5, 7, 8], 39 }, 40 { 41 // (type 6) 42 type: `(type (sub 5 (func)))`, 43 subtypeOf: [0, 1, 4, 5, 6, 7, 8], 44 }, 45 { 46 // (type 7) (equivalent to 4) 47 type: `(type (sub 0 (func)))`, 48 subtypeOf: [0, 1, 4, 7], 49 }, 50 { 51 // (type 8) (equivalent to 5) 52 type: `(type (sub 7 (func)))`, 53 subtypeOf: [0, 1, 4, 5, 7, 8], 54 }, 55 { 56 // (type 9) - a final type that has an immediate form 57 type: `(type (func))`, 58 subtypeOf: [9], 59 } 60 ]; 61 62 // Build a module with all the types, functions with those types, and functions 63 // that call_indirect with those types, and a table with all the functions in 64 // it. 65 let typeSection = ''; 66 let importedFuncs = ''; 67 let definedFuncs = ''; 68 let callIndirectFuncs = ''; 69 let returnCallIndirectFuncs = ''; 70 let i = 0; 71 for (let {type} of TESTS) { 72 if (type) { 73 typeSection += type + '\n'; 74 } 75 importedFuncs += `(func \$import${i} (import "" "import${i}") (type ${i}))\n`; 76 definedFuncs += `(func \$define${i} (export "define${i}") (type ${i}))\n`; 77 callIndirectFuncs += `(func (export "call_indirect ${i}") (param i32) 78 (drop (ref.cast (ref ${i}) (table.get local.get 0))) 79 (call_indirect (type ${i}) local.get 0) 80 )\n`; 81 returnCallIndirectFuncs += `(func (export "return_call_indirect ${i}") (param i32) 82 (drop (ref.cast (ref ${i}) (table.get local.get 0))) 83 (return_call_indirect (type ${i}) local.get 0) 84 )\n`; 85 i++; 86 } 87 let moduleText = `(module 88 ${typeSection} 89 ${importedFuncs} 90 ${definedFuncs} 91 ${callIndirectFuncs} 92 ${returnCallIndirectFuncs} 93 (table 94 (export "table") 95 funcref 96 (elem ${TESTS.map((x, i) => `\$import${i} \$define${i}`).join(" ")}) 97 ) 98 )`; 99 100 // Now go over every combination of (actual, expected). In this case the caller 101 // (which does the call_indirect) specifies expected and the callee will be the 102 // actual. 103 let imports = { 104 "": Object.fromEntries(TESTS.map((x, i) => [`import${i}`, () => {}])), 105 }; 106 let exports = wasmEvalText(moduleText, imports).exports; 107 for (let callerTypeIndex = 0; callerTypeIndex < TESTS.length; callerTypeIndex++) { 108 for (let calleeTypeIndex = 0; calleeTypeIndex < TESTS.length; calleeTypeIndex++) { 109 let calleeType = TESTS[calleeTypeIndex]; 110 111 // If the callee (actual) is a subtype of caller (expected), then this 112 // should succeed. 113 let shouldPass = calleeType.subtypeOf.includes(callerTypeIndex); 114 115 let calleeImportFuncIndex = calleeTypeIndex * 2; 116 let calleeDefinedFuncIndex = calleeTypeIndex * 2 + 1; 117 118 // print(`expected (type ${callerTypeIndex}) (actual ${calleeTypeIndex})`); 119 let test = () => { 120 exports[`call_indirect ${callerTypeIndex}`](calleeImportFuncIndex) 121 exports[`call_indirect ${callerTypeIndex}`](calleeDefinedFuncIndex) 122 exports[`return_call_indirect ${callerTypeIndex}`](calleeImportFuncIndex) 123 exports[`return_call_indirect ${callerTypeIndex}`](calleeDefinedFuncIndex) 124 }; 125 if (shouldPass) { 126 test(); 127 } else { 128 assertErrorMessage(test, WebAssembly.RuntimeError, /mismatch|cast/); 129 } 130 } 131 }