From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- js/src/jit-test/tests/wasm/gc/cast-abstract.js | 298 +++++++++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 js/src/jit-test/tests/wasm/gc/cast-abstract.js (limited to 'js/src/jit-test/tests/wasm/gc/cast-abstract.js') 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..4dd7e73e49 --- /dev/null +++ b/js/src/jit-test/tests/wasm/gc/cast-abstract.js @@ -0,0 +1,298 @@ +// |jit-test| skip-if: !wasmGcEnabled() + +load(libdir + "wasm-binary.js"); + +// 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 = ''; + +const preamble = ` + (type $s1 (sub (struct))) + (type $s2 (sub $s1 (struct (field i32)))) + (type $a1 (sub (array (ref null $s1)))) + (type $a2 (sub $a1 (array (ref null $s2)))) + (type $ft1 (sub (func (param (ref $s1)) (result (ref null $a1))))) + (type $ft2 (sub $ft1 (func (param (ref null $s1)) (result (ref null $a2))))) + (type $ft3 (sub (func (param (ref $a2)) (result i32)))) + (type $ft4 (sub $ft3 (func (param (ref $a1)) (result i32)))) + + (func $f1 (type $ft1) ref.null $a1) + (func $f2 (type $ft2) ref.null $a2) + (func $f3 (type $ft3) i32.const 0) + (func $f4 (type $ft4) i32.const 0) + (elem declare func $f1 $f2 $f3 $f4) ;; allow funcs 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'], + ['$ft2', '$ft1'], + ['$ft4', '$ft3'], +]; + +const any = { name: 'any', top: 'anyref' }; +const eq = { name: 'eq', top: 'anyref' }; +const struct = { name: 'struct', top: 'anyref' }; +const array = { name: 'array', top: 'anyref' }; +const s1 = { + index: 0, + name: '$s1', + make: 'struct.new_default $s1', + top: 'anyref', +}; +const s2 = { + index: 1, + name: '$s2', + make: 'struct.new_default $s2', + top: 'anyref', +}; +const a1 = { + index: 2, + name: '$a1', + make: '(array.new_default $a1 (i32.const 10))', + top: 'anyref', +}; +const a2 = { + index: 3, + name: '$a2', + make: '(array.new_default $a2 (i32.const 10))', + top: 'anyref', +}; +const i31 = { name: 'i31', make: '(ref.i31 (i32.const 123))', top: 'anyref' }; +const none = { name: 'none', none: true, top: 'anyref' }; + +const func = { name: 'func', top: 'funcref' }; +const ft1 = { index: 4, name: '$ft1', make: 'ref.func $f1', top: 'funcref' }; +const ft2 = { index: 5, name: '$ft2', make: 'ref.func $f2', top: 'funcref' }; +const ft3 = { index: 6, name: '$ft3', make: 'ref.func $f3', top: 'funcref' }; +const ft4 = { index: 7, name: '$ft4', make: 'ref.func $f4', top: 'funcref' }; +const nofunc = { name: 'nofunc', none: true, top: 'funcref' }; + +const extern = { name: 'extern', make: '(extern.convert_any (struct.new_default $s1))', top: 'externref' } +const noextern = { name: 'noextern', none: true, top: 'externref' } + +const typeSets = [ + [any, eq, struct, s1, s2, none], + [any, eq, array, a1, a2, none], + [any, eq, s1, s2, a1, a2, none], + [any, eq, i31, none], + [any, eq, i31, s1, none], + [any, eq, i31, a1, none], + + [func, ft1, ft2, nofunc], + [func, ft3, ft4, nofunc], + [func, ft1, ft2, ft3, ft4, nofunc], + + [extern, noextern], +]; + +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; + +// 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 type0 = `(ref ${nullable0 ? 'null ' : ''}${start.name})`; + const type1 = `(ref ${nullable1 ? 'null ' : ''}${middle.name})`; + const type2 = `(ref ${nullable2 ? 'null ' : ''}${end.name})`; + const moduleText = `(module + ${preamble} + (func (export "make") (result ${type0}) + ${make} + ) + (func (export "cast1") (param ${type0}) (result ${type1}) + local.get 0 + ref.cast ${type1} + ) + (func (export "cast2") (param ${type1}) (result ${type2}) + local.get 0 + ref.cast ${type2} + ) + + (func (export "test1") (param ${type0}) (result i32) + local.get 0 + 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") (param ${type0}) (result i32) + (block (result ${type1}) + local.get 0 + br_on_cast 0 ${start.top} ${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 ${middle.top} ${type2} + drop + (return (i32.const 0)) + ) + drop + (return (i32.const 1)) + ) + (func (export "branchfail1") (param ${type0}) (result i32) + (block (result ${middle.top}) + local.get 0 + br_on_cast_fail 0 ${start.top} ${type1} + drop + (return (i32.const 1)) + ) + drop + (return (i32.const 0)) + ) + (func (export "branchfail2") (param ${type1}) (result i32) + (block (result ${end.top}) + local.get 0 + br_on_cast_fail 0 ${middle.top} ${type2} + drop + (return (i32.const 1)) + ) + drop + (return (i32.const 0)) + ) + )`; + + function assertCast(func, good) { + if (good) { + return [func(), true]; + } else { + assertErrorMessage(func, WebAssembly.RuntimeError, /bad cast/); + return [null, false]; + } + } + + try { + // The casts are split up so the stack trace will show you which cast is failing. + const { + make, + cast1, cast2, + test1, test2, + branch1, branch2, + branchfail1, branchfail2, + } = wasmEvalText(moduleText).exports; + + const val = make(); + const [res1, ok1] = assertCast(() => cast1(val), good1); + assertEq(test1(val), good1 ? 1 : 0); + assertEq(branch1(val), good1 ? 1 : 0); + assertEq(branchfail1(val), 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 😁`); -- cgit v1.2.3