summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/gc/cast-abstract.js
diff options
context:
space:
mode:
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.js264
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 😁`);