diff options
Diffstat (limited to 'js/src/jit-test/tests/wasm/gc/cast-abstract.js')
-rw-r--r-- | js/src/jit-test/tests/wasm/gc/cast-abstract.js | 264 |
1 files changed, 264 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/gc/cast-abstract.js b/js/src/jit-test/tests/wasm/gc/cast-abstract.js new file mode 100644 index 0000000000..ec2a44014c --- /dev/null +++ b/js/src/jit-test/tests/wasm/gc/cast-abstract.js @@ -0,0 +1,264 @@ +// |jit-test| skip-if: !wasmGcEnabled() + +const preamble = ` + (type $s1 (struct)) + (type $s2 (sub $s1 (struct (field i32)))) + (type $a1 (array (ref null $s1))) + (type $a2 (sub $a1 (array (ref null $s2)))) + (type $f1 (func)) + + (func $f (type $f1)) + (elem declare func $f) ;; allow $f to be ref.func'd +`; + +// All the concrete subtype relationships present in the preamble. +// [x, y] means x <: y. +const subtypes = [ + ['$s2', '$s1'], + ['$a2', '$a1'], +]; + +const typeSets = [ + [ + { name: 'any' }, + { name: 'eq' }, + { name: 'struct' }, + { name: '$s1', make: 'struct.new_default $s1' }, + { name: '$s2', make: 'struct.new_default $s2' }, + { name: 'none', none: true }, + ], + [ + { name: 'any' }, + { name: 'eq' }, + { name: 'array' }, + { name: '$a1', make: '(array.new_default $a1 (i32.const 10))' }, + { name: '$a2', make: '(array.new_default $a2 (i32.const 10))' }, + { name: 'none', none: true }, + ], + [ + { name: 'any' }, + { name: 'eq' }, + { name: '$s1', make: 'struct.new_default $s1' }, + { name: '$s2', make: 'struct.new_default $s2' }, + { name: '$a1', make: '(array.new_default $a1 (i32.const 10))' }, + { name: '$a2', make: '(array.new_default $a2 (i32.const 10))' }, + { name: 'none', none: true }, + ], + // i31 eventually + + // Apparently we don't support casting functions yet? That should be remedied... + // [ + // { name: 'func' }, + // { name: '$f1', make: 'ref.func $f' }, + // { name: 'nofunc', none: true }, + // ], + + // TODO: extern +]; + +const nullables = [ // for example: + [true, true, true], // null $s1 -> null any -> null eq + [true, true, false], // null $s1 -> null any -> eq + [true, false, true], // null $s1 -> any -> null eq + [true, false, false], // null $s1 -> any -> eq + [false, true, true], // $s1 -> null any -> null eq + [false, true, false], // $s1 -> null any -> eq + [false, false, true], // $s1 -> any -> null eq + [false, false, false], // $s1 -> any -> eq +] + +function isSubtype(src, dest) { + if (src.name === dest.name) { + return true; + } + for (const [src2, dest2] of subtypes) { + if (src.name === src2 && dest.name === dest2) { + return true; + } + } + return false; +} + +let numCases = 0; + +// Replace this with a string like 'non-null (ref $s1) -> (ref any) -> (ref any)' to test exactly one specific case. Makes debugging easier. +const specificTest = ''; + +// This will generate an enormous pile of test cases. All of these should be valid, +// as in passing WebAssembly.validate, but some may not be good casts at runtime. +for (const typeSet of typeSets) { + for (const start of typeSet) { + for (const middle of typeSet) { + for (const end of typeSet) { + for (const [nullable0, nullable1, nullable2] of nullables) { + for (const makeNull of [true, false]) { + const concrete0 = !!start.make; + const concrete1 = !!middle.make; + const concrete2 = !!end.make; + + if (!concrete0 && !makeNull) { + // We can only start with null values for abstract types + continue; + } + + if (!nullable0 && makeNull) { + // Can't use null as a valid value for a non-nullable type + continue; + } + + numCases += 1; + + let good1 = true; + let good2 = true; + + // Null values will fail casts to non-nullable types + if (makeNull) { + if (!nullable1) { + good1 = false; + } + if (!nullable2) { + good2 = false; + } + } + + // Bottom types can't represent non-null, so this will always fail + if (!makeNull) { + if (middle.none) { + good1 = false; + } + if (end.none) { + good2 = false; + } + } + + // Concrete values are subject to subtyping relationships + if (!makeNull) { + if (concrete1 && !isSubtype(start, middle)) { + good1 = false; + } + if (concrete2 && !isSubtype(start, end)) { + good2 = false; + } + } + + let emoji1 = good1 ? '✅' : '❌'; + let emoji2 = good2 ? '✅' : '❌'; + + if (!good1) { + good2 = false; + emoji2 = '❓'; + } + + const name = `${makeNull ? 'null' : 'non-null'} (ref ${nullable0 ? 'null ' : ''}${start.name}) -> (ref ${nullable1 ? 'null ' : ''}${middle.name}) -> (ref ${nullable2 ? 'null ' : ''}${end.name})`; + if (specificTest && name != specificTest) { + continue; + } + + print(`${emoji1}${emoji2} ${name}`); + const make = makeNull ? `ref.null ${start.name}` : start.make; + const type1 = `(ref ${nullable1 ? 'null ' : ''}${middle.name})`; + const type2 = `(ref ${nullable2 ? 'null ' : ''}${end.name})`; + const moduleText = `(module + ${preamble} + (func (export "cast1") (result ${type1}) + ${make} + ref.cast ${type1} + ) + (func (export "cast2") (param ${type1}) (result ${type2}) + local.get 0 + ref.cast ${type2} + ) + + (func (export "test1") (result i32) + ${make} + ref.test ${type1} + ) + (func (export "test2") (param ${type1}) (result i32) + local.get 0 + ref.test ${type2} + ) + + ;; these are basically ref.test but with branches + (func (export "branch1") (result i32) + (block (result ${type1}) + ${make} + br_on_cast 0 anyref ${type1} + drop + (return (i32.const 0)) + ) + drop + (return (i32.const 1)) + ) + (func (export "branch2") (param ${type1}) (result i32) + (block (result ${type2}) + local.get 0 + br_on_cast 0 anyref ${type2} + drop + (return (i32.const 0)) + ) + drop + (return (i32.const 1)) + ) + (func (export "branchfail1") (result i32) + (block (result anyref) + ${make} + br_on_cast_fail 0 anyref ${type1} + drop + (return (i32.const 1)) + ) + drop + (return (i32.const 0)) + ) + (func (export "branchfail2") (param ${type1}) (result i32) + (block (result anyref) + local.get 0 + br_on_cast_fail 0 anyref ${type2} + drop + (return (i32.const 1)) + ) + drop + (return (i32.const 0)) + ) + )`; + + try { + // The casts are split up so the stack trace will show you which cast is failing. + const { + cast1, cast2, + test1, test2, + branch1, branch2, + branchfail1, branchfail2, + } = wasmEvalText(moduleText).exports; + + function assertCast(func, good) { + if (good) { + return [func(), true]; + } else { + assertErrorMessage(func, WebAssembly.RuntimeError, /bad cast/); + return [null, false]; + } + } + + const [res1, ok1] = assertCast(cast1, good1); + assertEq(test1(), good1 ? 1 : 0); + assertEq(branch1(), good1 ? 1 : 0); + assertEq(branchfail1(), good1 ? 1 : 0); + if (ok1) { + assertCast(() => cast2(res1), good2); + assertEq(test2(res1), good2 ? 1 : 0); + assertEq(branch2(res1), good2 ? 1 : 0); + assertEq(branchfail2(res1), good2 ? 1 : 0); + } + } catch (e) { + print("Failed! Module text was:"); + print(moduleText); + throw e; + } + } + } + } + } + } +} + +print(`we hope you have enjoyed these ${numCases} test cases 😁`); |