summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/gc
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/wasm/gc')
-rw-r--r--js/src/jit-test/tests/wasm/gc/TypedObject.js121
-rw-r--r--js/src/jit-test/tests/wasm/gc/arrays.js1104
-rw-r--r--js/src/jit-test/tests/wasm/gc/binary.js29
-rw-r--r--js/src/jit-test/tests/wasm/gc/br-on-cast-fail.js199
-rw-r--r--js/src/jit-test/tests/wasm/gc/br-on-cast.js197
-rw-r--r--js/src/jit-test/tests/wasm/gc/cast-abstract.js264
-rw-r--r--js/src/jit-test/tests/wasm/gc/cast-extern.js54
-rw-r--r--js/src/jit-test/tests/wasm/gc/casting.js128
-rw-r--r--js/src/jit-test/tests/wasm/gc/debugger.js38
-rw-r--r--js/src/jit-test/tests/wasm/gc/directives.txt1
-rw-r--r--js/src/jit-test/tests/wasm/gc/disabled.js25
-rw-r--r--js/src/jit-test/tests/wasm/gc/externref-boxing-struct.js74
-rw-r--r--js/src/jit-test/tests/wasm/gc/externref-conversions.js94
-rw-r--r--js/src/jit-test/tests/wasm/gc/function_subtyping.js79
-rw-r--r--js/src/jit-test/tests/wasm/gc/global-get.js30
-rw-r--r--js/src/jit-test/tests/wasm/gc/init-expr.js75
-rw-r--r--js/src/jit-test/tests/wasm/gc/ion-and-baseline.js88
-rw-r--r--js/src/jit-test/tests/wasm/gc/js-boundary.js107
-rw-r--r--js/src/jit-test/tests/wasm/gc/linking.js72
-rw-r--r--js/src/jit-test/tests/wasm/gc/ref-eq.js27
-rw-r--r--js/src/jit-test/tests/wasm/gc/ref-global.js96
-rw-r--r--js/src/jit-test/tests/wasm/gc/ref-struct.js328
-rw-r--r--js/src/jit-test/tests/wasm/gc/ref.js202
-rw-r--r--js/src/jit-test/tests/wasm/gc/regress-1633355.js30
-rw-r--r--js/src/jit-test/tests/wasm/gc/regress-1739330.js23
-rw-r--r--js/src/jit-test/tests/wasm/gc/regress-1745391.js13
-rw-r--r--js/src/jit-test/tests/wasm/gc/regress-1754701.js30
-rw-r--r--js/src/jit-test/tests/wasm/gc/regress-1830975.js17
-rw-r--r--js/src/jit-test/tests/wasm/gc/regress-outline-repr.js151
-rw-r--r--js/src/jit-test/tests/wasm/gc/signal-null-check.js148
-rw-r--r--js/src/jit-test/tests/wasm/gc/structs.js724
-rw-r--r--js/src/jit-test/tests/wasm/gc/structs2.js243
-rw-r--r--js/src/jit-test/tests/wasm/gc/supertype_later_in_group.js15
-rw-r--r--js/src/jit-test/tests/wasm/gc/tables-generalized-struct.js48
-rw-r--r--js/src/jit-test/tests/wasm/gc/trailers-gc-stress.js166
-rw-r--r--js/src/jit-test/tests/wasm/gc/value_subtyping.js305
36 files changed, 5345 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/gc/TypedObject.js b/js/src/jit-test/tests/wasm/gc/TypedObject.js
new file mode 100644
index 0000000000..93eae428c4
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/TypedObject.js
@@ -0,0 +1,121 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// We can read the object fields from JS
+
+{
+ let ins = wasmEvalText(`(module
+ (type $p (struct (field f64) (field (mut i32))))
+
+ (func (export "mkp") (result eqref)
+ (struct.new $p (f64.const 1.5) (i32.const 33))))`).exports;
+
+ let p = ins.mkp();
+ assertEq(p[0], 1.5);
+ assertEq(p[1], 33);
+ assertEq(p[2], undefined);
+}
+
+// Writing an immutable field from JS throws.
+
+{
+ let ins = wasmEvalText(`(module
+ (type $p (struct (field f64)))
+
+ (func (export "mkp") (result eqref)
+ (struct.new $p (f64.const 1.5))))`).exports;
+
+ let p = ins.mkp();
+ assertErrorMessage(() => p[0] = 5.7,
+ Error,
+ /setting immutable field/);
+}
+
+// MVA v1 restriction: structs have no prototype
+
+{
+ let ins = wasmEvalText(`(module
+ (type $q (struct (field (mut f64))))
+ (type $p (struct (field (mut (ref null $q)))))
+
+ (type $r (struct (field (mut eqref))))
+
+ (func (export "mkp") (result eqref)
+ (struct.new $p (ref.null $q)))
+
+ (func (export "mkr") (result eqref)
+ (struct.new $r (ref.null eq))))`).exports;
+
+ assertEq(Object.getPrototypeOf(ins.mkp()), null);
+ assertEq(Object.getPrototypeOf(ins.mkr()), null);
+}
+
+// MVA v1 restriction: all fields are immutable
+
+{
+ let ins = wasmEvalText(`(module
+ (type $q (struct (field (mut f64))))
+ (type $p (struct (field (mut (ref null $q))) (field (mut eqref))))
+
+ (func (export "mkq") (result eqref)
+ (struct.new $q (f64.const 1.5)))
+
+ (func (export "mkp") (result eqref)
+ (struct.new $p (ref.null $q) (ref.null eq))))`).exports;
+ let q = ins.mkq();
+ assertEq(typeof q, "object");
+ assertEq(q[0], 1.5);
+
+ let p = ins.mkp();
+ assertEq(typeof p, "object");
+ assertEq(p[0], null);
+
+ assertErrorMessage(() => { p[0] = q },
+ Error,
+ /setting immutable field/);
+
+ assertErrorMessage(() => { p[1] = q },
+ Error,
+ /setting immutable field/);
+}
+
+// MVA v1 restriction: structs that expose i64 fields make those fields
+// immutable from JS, and the structs are not constructible from JS.
+
+{
+ let ins = wasmEvalText(`(module
+ (type $p (struct (field (mut i64))))
+ (func (export "mkp") (result eqref)
+ (struct.new $p (i64.const 0x1234567887654321))))`).exports;
+
+ let p = ins.mkp();
+ assertEq(typeof p, "object");
+ assertEq(p[0], 0x1234567887654321n)
+
+ assertErrorMessage(() => { p[0] = 0 },
+ Error,
+ /setting immutable field/);
+}
+
+// A consequence of the current mapping of i64 as two i32 fields is that we run
+// a risk of struct.narrow not recognizing the difference. So check this.
+
+{
+ let ins = wasmEvalText(
+ `(module
+ (type $p (struct (field i64)))
+ (type $q (struct (field i32) (field i32)))
+ (func $f (param eqref) (result i32)
+ local.get 0
+ ref.test (ref $q)
+ )
+ (func $g (param eqref) (result i32)
+ local.get 0
+ ref.test (ref $p)
+ )
+ (func (export "t1") (result i32)
+ (call $f (struct.new $p (i64.const 0))))
+ (func (export "t2") (result i32)
+ (call $g (struct.new $q (i32.const 0) (i32.const 0)))))`).exports;
+ assertEq(ins.t1(), 0);
+ assertEq(ins.t2(), 0);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/arrays.js b/js/src/jit-test/tests/wasm/gc/arrays.js
new file mode 100644
index 0000000000..44770b85ea
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/arrays.js
@@ -0,0 +1,1104 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Test array instructions on different valtypes
+
+const GENERAL_TESTS = [
+ [
+ 'i32',
+ 0,
+ 0xce,
+ ], [
+ 'i64',
+ 0n,
+ 0xabcdefn,
+ ], [
+ 'f32',
+ 0,
+ 13.5,
+ ], [
+ 'f64',
+ 0,
+ 13.5,
+ ], [
+ 'externref',
+ null,
+ 'hello',
+ ],
+];
+
+for (let [valtype, def, nondef] of GENERAL_TESTS) {
+ let {create, createDefault, get, set, len} = wasmEvalText(`(module
+ (type $a (array (mut ${valtype})))
+
+ (; new T[0] = { 1... } ;)
+ (func (export "create") (param i32 ${valtype}) (result eqref)
+ local.get 1
+ local.get 0
+ array.new $a
+ )
+
+ (; new T[0] ;)
+ (func (export "createDefault") (param i32) (result eqref)
+ local.get 0
+ array.new_default $a
+ )
+
+ (; 0[1] ;)
+ (func (export "get") (param eqref i32) (result ${valtype})
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ array.get $a
+ )
+
+ (; 0[1] = 2 ;)
+ (func (export "set") (param eqref i32 ${valtype})
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ local.get 2
+ array.set $a
+ )
+
+ (; len(a) ;)
+ (func (export "len") (param eqref) (result i32)
+ local.get 0
+ ref.cast (ref null $a)
+ array.len
+ )
+ )`).exports;
+
+ function checkArray(array, length, init, setval) {
+ // Check length
+ assertEq(len(array), length);
+ assertEq(array.length, length);
+
+ // Check init value
+ for (let i = 0; i < length; i++) {
+ assertEq(array[i], init);
+ assertEq(get(array, i), init);
+ }
+
+ // Set every element to setval
+ for (let i = 0; i < length; i++) {
+ set(array, i, setval);
+
+ // Check there is no overwrite
+ for (let j = i + 1; j < length; j++) {
+ assertEq(array[j], init);
+ assertEq(get(array, j), init);
+ }
+ }
+
+ // Check out of bounds conditions
+ for (let loc of [-1, length, length + 1]) {
+ assertErrorMessage(() => {
+ get(array, loc);
+ }, WebAssembly.RuntimeError, /out of bounds/);
+ assertErrorMessage(() => {
+ set(array, loc, setval);
+ }, WebAssembly.RuntimeError, /out of bounds/);
+ }
+ }
+
+ for (let arrayLength = 0; arrayLength < 5; arrayLength++) {
+ checkArray(createDefault(arrayLength), arrayLength, def, nondef);
+ checkArray(create(arrayLength, nondef), arrayLength, nondef, def);
+ }
+}
+
+// Check packed array reads and writes
+
+for (let [fieldtype, max] of [
+ [
+ 'i8',
+ 0xff,
+ ], [
+ 'i16',
+ 0xffff,
+ ]
+]) {
+ let {create, getS, getU, set} = wasmEvalText(`(module
+ (type $a (array (mut ${fieldtype})))
+
+ (; new T[0] = { 1... } ;)
+ (func (export "create") (param i32 i32) (result eqref)
+ local.get 1
+ local.get 0
+ array.new $a
+ )
+
+ (; 0[1] ;)
+ (func (export "getS") (param eqref i32) (result i32)
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ array.get_s $a
+ )
+
+ (; 0[1] ;)
+ (func (export "getU") (param eqref i32) (result i32)
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ array.get_u $a
+ )
+
+ (; 0[1] = 2 ;)
+ (func (export "set") (param eqref i32 i32)
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ local.get 2
+ array.set $a
+ )
+ )`).exports;
+
+ // Check zero and sign extension
+ let a = create(1, 0);
+ set(a, 0, max);
+ assertEq(getS(a, 0), -1);
+ assertEq(getU(a, 0), max);
+
+ // JS-API defaults to sign extension
+ assertEq(a[0], getS(a, 0));
+
+ // Check array.new truncates init value
+ assertEq(create(1, max + 1)[0], 0);
+
+ // Check array.set truncates
+ let b = create(1, 0);
+ set(b, 0, max + 1);
+ assertEq(getU(b, 0), 0);
+
+ // Check no overwrite on set
+ let c = create(2, 0);
+ set(c, 0, max << 4);
+ assertEq(getU(c, 0), (max << 4) & max);
+ assertEq(getU(c, 1), 0);
+}
+
+// Check arrays must be mutable to mutate
+
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func
+ (array.set $a
+ (array.new $a
+ i32.const 0xff
+ i32.const 10)
+ i32.const 0
+ i32.const 0
+ )
+ )
+)
+`), WebAssembly.CompileError, /array is not mutable/);
+
+// Check operations trap on null
+
+assertErrorMessage(() => {
+ wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func
+ ref.null $a
+ i32.const 0
+ array.get $a
+ drop
+ )
+ (start 0)
+ )`);
+}, WebAssembly.RuntimeError, /null/);
+
+assertErrorMessage(() => {
+ wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func
+ ref.null $a
+ i32.const 0
+ i32.const 0
+ array.set $a
+ )
+ (start 0)
+ )`);
+}, WebAssembly.RuntimeError, /null/);
+
+assertErrorMessage(() => {
+ wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func
+ ref.null $a
+ array.len
+ drop
+ )
+ (start 0)
+ )`);
+}, WebAssembly.RuntimeError, /null/);
+
+// Check an extension postfix is present iff the element is packed
+
+for ([fieldtype, packed] of [
+ ['i8', true],
+ ['i16', true],
+ ['i32', false],
+ ['i64', false],
+ ['f32', false],
+ ['f64', false],
+]) {
+ let extensionModule = `(module
+ (type $a (array ${fieldtype}))
+ (func
+ ref.null $a
+ i32.const 0
+ array.get_s $a
+ drop
+ )
+ (func
+ ref.null $a
+ i32.const 0
+ array.get_u $a
+ drop
+ )
+ )`;
+ let noExtensionModule = `(module
+ (type $a (array ${fieldtype}))
+ (func
+ ref.null $a
+ i32.const 0
+ array.get $a
+ drop
+ )
+ )`;
+
+ if (packed) {
+ wasmValidateText(extensionModule);
+ wasmFailValidateText(noExtensionModule, /must specify signedness/);
+ } else {
+ wasmFailValidateText(extensionModule, /must not specify signedness/);
+ wasmValidateText(noExtensionModule);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.new_fixed
+/*
+ validation:
+ array-type imm-operand needs to be "in range"
+ array-type imm-operand must refer to an array type
+ operands (on stack) must all match ("be compatible with") the array elem
+ type
+ number of operands (on stack) must not be less than the num-of-elems
+ imm-operand
+ zero elements doesn't fail compilation
+ reftypes elements doesn't fail compilation
+ trying to create a 1-billion-element array fails gracefully
+ run:
+ resulting 4-element array is as expected
+ resulting zero-element array is as expected
+ resulting 30-element array is as expected
+*/
+
+// validation: array-type imm-operand needs to be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i8))
+ (func (result eqref)
+ i32.const 66
+ i32.const 77
+ array.new_fixed 2 2 ;; type index 2 is the first invalid one
+ )
+)
+`), WebAssembly.CompileError, /type index out of range/);
+
+// validation: array-type imm-operand must refer to an array type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (func (param f64) (result f64)))
+ (func (result eqref)
+ i32.const 66
+ i32.const 77
+ array.new_fixed $a 2
+ )
+)
+`), WebAssembly.CompileError, /not an array type/);
+
+// validation: operands (on stack) must all match ("be compatible with")
+// the array elem type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func (result eqref)
+ f32.const 66.6
+ f64.const 77.7
+ array.new_fixed $a 2
+ )
+)
+`), WebAssembly.CompileError, /expression has type f64 but expected i32/);
+
+// validation: number of operands (on stack) must not be less than the
+// num-of-elems imm-operand
+assertNoWarning(() => wasmEvalText(`(module
+ (type $a (array f32))
+ (func
+ f64.const 66.6 ;; we won't put this in the array
+ f32.const 77.7
+ f32.const 88.8
+ array.new_fixed $a 2 ;; use up 88.8 and 77.7 and replace with array
+ drop ;; dump the array
+ f64.const 99.9
+ f64.mul ;; check the 66.6 value is still on the stack
+ drop ;; now should be empty
+ )
+)
+`));
+// (more)
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i64))
+ (func (param i64) (result eqref)
+ local.get 0
+ array.new_fixed $a 2
+ )
+)
+`), WebAssembly.CompileError, /popping value from empty stack/);
+
+// validation: zero elements doesn't fail compilation
+assertNoWarning(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func (result eqref)
+ array.new_fixed $a 0
+ )
+)
+`));
+
+// validation: reftyped elements doesn't fail compilation
+assertNoWarning(() => wasmEvalText(`(module
+ (type $a (array eqref))
+ (func (param eqref) (result eqref)
+ local.get 0
+ array.new_fixed $a 1
+ )
+)
+`));
+
+// validation: trying to create a 1-billion-element array fails gracefully
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array f32))
+ (func (export "newFixed") (result eqref)
+ f32.const 1337.0
+ f32.const 4771.0
+ array.new_fixed $a 1000000000
+ )
+)
+`), WebAssembly.CompileError, /popping value from empty stack/);
+
+// run: resulting 4-element array is as expected
+{
+ let { newFixed } = wasmEvalText(`(module
+ (type $a (array i8))
+ (func (export "newFixed") (result eqref)
+ i32.const 66
+ i32.const 77
+ i32.const 88
+ i32.const 99
+ array.new_fixed $a 4
+ )
+ )`).exports;
+ let a = newFixed();
+ assertEq(a.length, 4);
+ assertEq(a[0], 66);
+ assertEq(a[1], 77);
+ assertEq(a[2], 88);
+ assertEq(a[3], 99);
+}
+
+// run: resulting zero-element array is as expected
+{
+ let { newFixed } = wasmEvalText(`(module
+ (type $a (array i16))
+ (func (export "newFixed") (result eqref)
+ array.new_fixed $a 0
+ )
+ )`).exports;
+ let a = newFixed();
+ assertEq(a.length, 0);
+}
+
+// run: resulting 30-element array is as expected
+{
+ let { newFixed } = wasmEvalText(`(module
+ (type $a (array i16))
+ (func (export "newFixed") (result eqref)
+ i32.const 1
+ i32.const 2
+ i32.const 3
+ i32.const 4
+ i32.const 5
+ i32.const 6
+ i32.const 7
+ i32.const 8
+ i32.const 9
+ i32.const 10
+ i32.const 11
+ i32.const 12
+ i32.const 13
+ i32.const 14
+ i32.const 15
+ i32.const 16
+ i32.const 17
+ i32.const 18
+ i32.const 19
+ i32.const 20
+ i32.const 21
+ i32.const 22
+ i32.const 23
+ i32.const 24
+ i32.const 25
+ i32.const 26
+ i32.const 27
+ i32.const 28
+ i32.const 29
+ i32.const 30
+ array.new_fixed $a 30
+ )
+ )`).exports;
+ let a = newFixed();
+ assertEq(a.length, 30);
+ for (i = 0; i < 30; i++) {
+ assertEq(a[i], i + 1);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.new_data
+/*
+ validation:
+ array-type imm-operand needs to be "in range"
+ array-type imm-operand must refer to an array type
+ array-type imm-operand must refer to an array of numeric elements
+ segment index must be "in range"
+ run:
+ if segment is "already used" (active, or passive that has subsequently
+ been dropped), then only a zero length array can be created
+ range to copy would require OOB read on data segment
+ resulting array is as expected
+*/
+
+// validation: array-type imm-operand needs to be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i8))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data 2 $d
+ )
+)`), WebAssembly.CompileError, /type index out of range/);
+
+// validation: array-type imm-operand must refer to an array type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (func (param f32) (result f32)))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+)`), WebAssembly.CompileError, /not an array type/);
+
+// validation: array-type imm-operand must refer to an array of numeric elements
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array eqref))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+)`), WebAssembly.CompileError,
+ /element type must be i8\/i16\/i32\/i64\/f32\/f64\/v128/);
+
+// validation: segment index must be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i8))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a 1 ;; 1 is the lowest invalid dseg index
+ )
+)`), WebAssembly.CompileError, /segment index is out of range/);
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #1
+{
+ let { newData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array i8))
+ (data $d (offset (i32.const 0)) "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #2
+{
+ let { newData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array i8))
+ (data $d (offset (i32.const 0)) "1337")
+ (func (export "newData") (result eqref)
+ (; offset=4 into data ;) i32.const 4
+ (; size=0 into data ;) i32.const 0
+ array.new_data $a $d
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #3
+{
+ let { newData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array i8))
+ (data $d (offset (i32.const 0)) "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=0 into data ;) i32.const 0
+ array.new_data $a $d
+ )
+ )`).exports;
+ let arr = newData();
+ assertEq(arr.length, 0);
+}
+
+// run: range to copy would require OOB read on data segment
+{
+ let { newData } = wasmEvalText(`(module
+ (type $a (array i8))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 1
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: resulting array is as expected
+{
+ let { newData } = wasmEvalText(`(module
+ (type $a (array i8))
+ (data $other "\\\\9")
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+ )`).exports;
+ let arr = newData();
+ assertEq(arr.length, 4);
+ assertEq(arr[0], 48+1);
+ assertEq(arr[1], 48+3);
+ assertEq(arr[2], 48+3);
+ assertEq(arr[3], 48+7);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.new_elem
+/*
+ validation:
+ array-type imm-operand needs to be "in range"
+ array-type imm-operand must refer to an array type
+ array-type imm-operand must refer to an array of ref typed elements
+ destination elem type must be a supertype of src elem type
+ segment index must be "in range"
+ run:
+ if segment is "already used" (active, or passive that has subsequently
+ been dropped), then only a zero length array can be created
+ range to copy would require OOB read on elem segment
+ resulting array is as expected
+*/
+
+// validation: array-type imm-operand needs to be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem 3 $e
+ )
+)`), WebAssembly.CompileError, /type index out of range/);
+
+// validation: array-type imm-operand must refer to an array type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (func (param i64) (result f64)))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+)`), WebAssembly.CompileError, /not an array type/);
+
+// validation: array-type imm-operand must refer to an array of ref typed
+// elements
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array f32))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+)`), WebAssembly.CompileError, /element type is not a reftype/);
+
+// validation: destination elem type must be a supertype of src elem type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array eqref))
+ (elem $e funcref $f1)
+ (func $f1 (export "f1"))
+ ;; The implied copy here is from elem-seg-of-funcrefs to
+ ;; array-of-eqrefs, which must fail, because funcref isn't
+ ;; a subtype of eqref.
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=0 into elem ;) i32.const 0
+ array.new_elem $a $e
+ )
+)`), WebAssembly.CompileError, /incompatible element types/);
+
+// validation: segment index must be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a 1 ;; 1 is the lowest invalid eseg index
+ )
+)`), WebAssembly.CompileError, /segment index is out of range/);
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #1
+{
+ let { newElem } = wasmEvalText(`(module
+ (table 4 funcref)
+ (type $a (array funcref))
+ (elem $e (offset (i32.const 0)) funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newElem();
+ }, WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #2
+{
+ let { newElem } = wasmEvalText(`(module
+ (table 4 funcref)
+ (type $a (array funcref))
+ (elem $e (offset (i32.const 0)) funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=4 into elem ;) i32.const 4
+ (; size=0 into elem ;) i32.const 0
+ array.new_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newElem();
+ }, WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #3
+{
+ let { newElem } = wasmEvalText(`(module
+ (table 4 funcref)
+ (type $a (array funcref))
+ (elem $e (offset (i32.const 0)) funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=0 into elem ;) i32.const 0
+ array.new_elem $a $e
+ )
+ )`).exports;
+ let arr = newElem();
+ assertEq(arr.length, 0);
+}
+
+// run: range to copy would require OOB read on elem segment
+{
+ let { newElem } = wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 1
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newElem();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: resulting array is as expected
+{
+ let { newElem, f1, f2, f3, f4 } = wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+ )`).exports;
+ let arr = newElem();
+ assertEq(arr.length, 4);
+ assertEq(arr[0], f1);
+ assertEq(arr[1], f2);
+ assertEq(arr[2], f3);
+ assertEq(arr[3], f4);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.copy
+/*
+ validation:
+ dest array must be mutable
+ validation: src and dest arrays must have compatible element types
+ run:
+ check for OOB conditions on src/dest arrays for non-zero length copies
+ check for OOB conditions on src/dest arrays for zero length copies
+ check resulting arrays are as expected
+ check that null src or dest array causes a trap
+*/
+
+// validation: dest array must be mutable
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func (param eqref)
+ (array.copy $a $a (local.get 0) (i32.const 1)
+ (local.get 0) (i32.const 2) (i32.const 3))
+ )
+)
+`), WebAssembly.CompileError, /array is not mutable/);
+
+// validation: src and dest arrays must have compatible element types #1
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a32 (array (mut i32)))
+ (type $a8 (array i8))
+ (func (param eqref)
+ (array.copy $a32 $a8 (local.get 0) (i32.const 1)
+ (local.get 0) (i32.const 2) (i32.const 3))
+ )
+)
+`), WebAssembly.CompileError, /incompatible element types/);
+
+// validation: src and dest arrays must have compatible element types #2
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a64 (array (mut i64)))
+ (type $aER (array eqref))
+ (func (param eqref)
+ (array.copy $a64 $aER (local.get 0) (i32.const 1)
+ (local.get 0) (i32.const 2) (i32.const 3))
+ )
+)
+`), WebAssembly.CompileError, /incompatible element types/);
+
+// validation: src and dest arrays must have compatible element types #3
+//
+// We can only copy from a child (sub) reftype to a parent (super) reftype [or
+// to the same reftype.] Here, we have an array of eqref and an array of
+// arrays. An array is a child type of eqref, so it's invalid to try and copy
+// eqrefs into the array of arrays.
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $ty (array i32))
+ (type $child (array (mut (ref $ty))))
+ (type $parent (array (mut eqref)))
+ (func (param (ref null $child) (ref null $parent))
+ ;; implied copy from parent to child -> not allowed
+ (array.copy $child $parent (local.get 1) (i32.const 1)
+ (local.get 0) (i32.const 2) (i32.const 3))
+ )
+)
+`), WebAssembly.CompileError, /incompatible element types/);
+
+// run: check for OOB conditions on src/dest arrays for non-zero length copies
+// run: check for OOB conditions on src/dest arrays for zero length copies
+// run: check resulting arrays are as expected
+const ARRAY_COPY_TESTS = [
+ // Format is:
+ // array element type
+ // corresponding value type
+ // source array
+ // first expected result
+ // second expected result
+ //
+ // Value-type cases
+ [ 'i32', 'i32',
+ [22, 33, 44, 55, 66, 77],
+ [22, 55, 66, 77, 66, 77],
+ [22, 33, 22, 33, 44, 77]
+ ],
+ [ 'i64', 'i64',
+ [0x2022002220002n, 0x3033003330003n, 0x4044004440004n,
+ 0x5055005550005n, 0x6066006660006n, 0x7077007770007n],
+ [0x2022002220002n, 0x5055005550005n, 0x6066006660006n,
+ 0x7077007770007n, 0x6066006660006n, 0x7077007770007n],
+ [0x2022002220002n, 0x3033003330003n, 0x2022002220002n,
+ 0x3033003330003n, 0x4044004440004n, 0x7077007770007n]
+ ],
+ [ 'f32', 'f32',
+ [22.0, 33.0, 44.0, 55.0, 66.0, 77.0],
+ [22.0, 55.0, 66.0, 77.0, 66.0, 77.0],
+ [22.0, 33.0, 22.0, 33.0, 44.0, 77.0]
+ ],
+ [ 'f64', 'f64',
+ [22.0, 33.0, 44.0, 55.0, 66.0, 77.0],
+ [22.0, 55.0, 66.0, 77.0, 66.0, 77.0],
+ [22.0, 33.0, 22.0, 33.0, 44.0, 77.0]
+ ],
+ [ 'externref', 'externref',
+ ['two', 'three', 'four', 'five', 'six', 'seven'],
+ ['two', 'five', 'six', 'seven', 'six', 'seven'],
+ ['two', 'three', 'two', 'three', 'four', 'seven']
+ ],
+ // non-Value-type cases
+ [ 'i8', 'i32',
+ [22, 33, 44, 55, 66, 77],
+ [22, 55, 66, 77, 66, 77],
+ [22, 33, 22, 33, 44, 77]
+ ],
+ [ 'i16', 'i32',
+ [22, 33, 44, 55, 66, 77],
+ [22, 55, 66, 77, 66, 77],
+ [22, 33, 22, 33, 44, 77]
+ ]
+];
+
+for (let [elemTy, valueTy, src, exp1, exp2] of ARRAY_COPY_TESTS) {
+ let { arrayNew, arrayCopy } = wasmEvalText(
+ `(module
+ (type $arrTy (array (mut ${elemTy})))
+ (func (export "arrayNew")
+ (param ${valueTy} ${valueTy} ${valueTy}
+ ${valueTy} ${valueTy} ${valueTy})
+ (result eqref)
+ local.get 0
+ local.get 1
+ local.get 2
+ local.get 3
+ local.get 4
+ local.get 5
+ array.new_fixed $arrTy 6
+ )
+ (func (export "arrayCopy")
+ (param eqref i32 eqref i32 i32)
+ (array.copy $arrTy $arrTy
+ (ref.cast (ref null $arrTy) local.get 0) (local.get 1)
+ (ref.cast (ref null $arrTy) local.get 2) (local.get 3) (local.get 4)
+ )
+ )
+ )`
+ ).exports;
+
+ assertEq(src.length, 6);
+ assertEq(exp1.length, 6);
+ assertEq(exp2.length, 6);
+
+ function eqArrays(a1, a2) {
+ assertEq(a1.length, 6);
+ assertEq(a2.length, 6);
+ for (i = 0; i < 6; i++) {
+ if (a1[i] !== a2[i])
+ return false;
+ }
+ return true;
+ }
+ function show(who, arr) {
+ print(who + ": " + arr[0] + " " + arr[1] + " " + arr[2] + " "
+ + arr[3] + " " + arr[4] + " " + arr[5] + " ");
+ }
+
+ // Check that "normal" copying gives expected results.
+ let srcTO;
+ srcTO = arrayNew(src[0], src[1], src[2], src[3], src[4], src[5]);
+ arrayCopy(srcTO, 1, srcTO, 3, 3);
+ assertEq(eqArrays(srcTO, exp1), true);
+
+ srcTO = arrayNew(src[0], src[1], src[2], src[3], src[4], src[5]);
+ arrayCopy(srcTO, 2, srcTO, 0, 3);
+ assertEq(eqArrays(srcTO, exp2), true);
+
+ // Check out-of-bounds conditions
+ let exp1TO = arrayNew(exp1[0], exp1[1], exp1[2], exp1[3], exp1[4], exp1[5]);
+ let exp2TO = arrayNew(exp2[0], exp2[1], exp2[2], exp2[3], exp2[4], exp2[5]);
+
+ // dst overrun, wants to write [5, 6]
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 5, exp2TO, 1, 2);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // dst overrun, wants to write [7, 8]
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 7, exp2TO, 1, 2);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // dst zero-len overrun, wants to write no elements, but starting at 9
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 9, exp2TO, 1, 0);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // src overrun, wants to read [5, 6]
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 1, exp2TO, 5, 2);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // src overrun, wants to read [7, 8]
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 1, exp2TO, 7, 2);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // src zero-len overrun, wants to read no elements, but starting at 9
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 1, exp2TO, 9, 0);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: check that null src or dest array causes a trap #1
+{
+ let { shouldTrap } = wasmEvalText(`(module
+ (type $a (array (mut f32)))
+ (func (export "shouldTrap")
+ ref.null $a
+ i32.const 1
+ (array.new_fixed $a 3 (f32.const 1.23) (f32.const 4.56) (f32.const 7.89))
+ i32.const 1
+ i32.const 1
+ array.copy $a $a
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ shouldTrap();
+ }, WebAssembly.RuntimeError, /null/);
+}
+
+// run: check that null src or dest array causes a trap #2
+{
+ let { shouldTrap } = wasmEvalText(`(module
+ (type $a (array (mut f32)))
+ (func (export "shouldTrap")
+ (array.new_fixed $a 3 (f32.const 1.23) (f32.const 4.56) (f32.const 7.89))
+ i32.const 1
+ ref.null $a
+ i32.const 1
+ i32.const 1
+ array.copy $a $a
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ shouldTrap();
+ }, WebAssembly.RuntimeError, /null/);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Checks for requests for oversize arrays (more than MaxArrayPayloadBytes),
+// where MaxArrayPayloadBytes == 1,987,654,321.
+
+// array.new
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func
+ ;; request exactly 2,000,000,000 bytes
+ (array.new $a (i32.const 0xABCD1234) (i32.const 500000000))
+ drop
+ )
+ (start 0)
+)
+`), WebAssembly.RuntimeError, /too many array elements/);
+
+// array.new_default
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array f64))
+ (func
+ ;; request exactly 2,000,000,000 bytes
+ (array.new_default $a (i32.const 250000000))
+ drop
+ )
+ (start 0)
+)
+`), WebAssembly.RuntimeError, /too many array elements/);
+
+// array.new_fixed
+// This is impossible to test because it would require to create, at a
+// minimum, 1,987,654,321 (MaxArrayPayloadBytes) / 16 = 124.3 million
+// values, if each value is a v128. However, the max number of bytes per
+// function is 7,654,321 (MaxFunctionBytes). Even if it were possible to
+// create a value using just one insn byte, there wouldn't be enough.
+
+// array.new_data
+// Similarly, impossible to test because the max data segment length is 1GB
+// (1,073,741,824 bytes) (MaxDataSegmentLengthPages * PageSize), which is less
+// than MaxArrayPayloadBytes.
+
+// array.new_element
+// Similarly, impossible to test because an element segment can contain at
+// most 10,000,000 (MaxElemSegmentLength) entries.
diff --git a/js/src/jit-test/tests/wasm/gc/binary.js b/js/src/jit-test/tests/wasm/gc/binary.js
new file mode 100644
index 0000000000..1ef4a586a8
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/binary.js
@@ -0,0 +1,29 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+load(libdir + "wasm-binary.js");
+
+const v2vSig = {args:[], ret:VoidCode};
+const v2vSigSection = sigSection([v2vSig]);
+
+function checkInvalid(body, errorMessage) {
+ assertErrorMessage(() => new WebAssembly.Module(
+ moduleWithSections([v2vSigSection, declSection([0]), bodySection([body])])),
+ WebAssembly.CompileError,
+ errorMessage);
+}
+
+const invalidRefBlockType = funcBody({locals:[], body:[
+ BlockCode,
+ OptRefCode,
+ 0x42,
+ EndCode,
+]});
+checkInvalid(invalidRefBlockType, /heap type/);
+
+const invalidTooBigRefType = funcBody({locals:[], body:[
+ BlockCode,
+ OptRefCode,
+ varU32(1000000),
+ EndCode,
+]});
+checkInvalid(invalidTooBigRefType, /heap type/);
diff --git a/js/src/jit-test/tests/wasm/gc/br-on-cast-fail.js b/js/src/jit-test/tests/wasm/gc/br-on-cast-fail.js
new file mode 100644
index 0000000000..3482f7815a
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/br-on-cast-fail.js
@@ -0,0 +1,199 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+function typingModule(types, from, to, brParams, branchResults, fallthroughResults) {
+ return `(module
+ ${types}
+ (func
+ (param ${brParams.join(' ')})
+ (result ${branchResults.join(' ')})
+
+ (block (result ${fallthroughResults.join(' ')})
+ (; push params onto the stack in the same order as they appear, leaving
+ the last param at the top of the stack. ;)
+ ${brParams.map((_, i) => `local.get ${i}`).join('\n')}
+ br_on_cast_fail 1 ${from} ${to}
+ )
+ unreachable
+ )
+ )`;
+}
+
+function validTyping(types, from, to, brParams, branchResults, fallthroughResults) {
+ wasmValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults));
+}
+
+function invalidTyping(types, from, to, brParams, branchResults, fallthroughResults, error) {
+ wasmFailValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults), error);
+}
+
+// valid: eqref -> struct
+validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['eqref'], ['(ref $a)']);
+// valid: eqref -> struct (and looser types on results)
+validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['anyref'], ['(ref null $a)']);
+// valid: eqref -> nullable struct (note that branch type becomes non-nullable)
+validTyping('(type $a (struct))', 'eqref', '(ref null $a)', ['eqref'], ['(ref eq)'], ['(ref null $a)']);
+// valid: struct -> struct (from anyref)
+validTyping('(type $a (struct))', 'anyref', '(ref $a)', ['(ref $a)'], ['anyref'], ['(ref $a)']);
+// valid: struct -> struct (canonicalized)
+validTyping('(type $a (struct)) (type $b (struct))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $a)']);
+// valid: nullable struct -> non-nullable struct (canonicalized)
+validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref $b)', ['(ref null $a)'], ['(ref null $a)'], ['(ref $b)']);
+// valid: nullable struct -> nullable struct (canonicalized)
+validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref null $b)', ['(ref null $a)'], ['(ref $a)'], ['(ref null $a)']);
+// valid: eqref -> struct with extra arg
+validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'eqref'], ['i32', 'eqref'], ['i32', '(ref $a)']);
+// valid: eqref -> struct with two extra args
+validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)']);
+
+// invalid: block result type must have slot for casted-to type
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], [], ['(ref $a)'], /type mismatch/);
+// invalid: block result type must be supertype of casted-to type
+invalidTyping('(type $a (struct)) (type $b (struct (field i32)))', 'eqref', '(ref $a)', ['eqref'], ['(ref $b)'], ['(ref $a)'], /type mismatch/);
+// invalid: input is missing extra i32 from the branch target type
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['f32', 'eqref'], ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)'], /popping value/);
+// invalid: input has extra [i32, f32] swapped from the branch target type
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['f32', 'i32', 'eqref'], ['i32', 'f32', '(ref $a)'], /type mismatch/);
+// invalid: input has extra [i32, f32] swapped from the branch fallthrough type
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', 'eqref'], ['f32', 'i32', '(ref $a)'], /type mismatch/);
+// invalid: casting to non-nullable but fallthrough not nullable
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref $a)'], ['(ref eq)'], /type mismatch/);
+// invalid: struct -> struct (same recursion group)
+invalidTyping('(rec (type $a (struct)) (type $b (struct)))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $a)'], ['(ref $b)'], /type mismatch/);
+
+// Simple runtime test of cast-fail-ing
+{
+ let { makeA, makeB, isA, isB } = wasmEvalText(`(module
+ (type $a (struct))
+ (type $b (struct (field i32)))
+
+ (func (export "makeA") (result eqref)
+ struct.new_default $a
+ )
+
+ (func (export "makeB") (result eqref)
+ struct.new_default $b
+ )
+
+ (func (export "isA") (param eqref) (result i32)
+ (block (result eqref)
+ local.get 0
+ br_on_cast_fail 0 eqref (ref $a)
+
+ i32.const 1
+ br 1
+ )
+ drop
+ i32.const 0
+ )
+
+ (func (export "isB") (param eqref) (result i32)
+ (block (result eqref)
+ local.get 0
+ br_on_cast_fail 0 eqref (ref $b)
+
+ i32.const 1
+ br 1
+ )
+ drop
+ i32.const 0
+ )
+ )`).exports;
+
+ let a = makeA();
+ let b = makeB();
+
+ assertEq(isA(a), 1);
+ assertEq(isA(b), 0);
+ assertEq(isB(a), 0);
+ assertEq(isB(b), 1);
+}
+
+// Runtime test of cast-fail-ing with extra values
+{
+ function assertEqResults(a, b) {
+ if (!(a instanceof Array)) {
+ a = [a];
+ }
+ if (!(b instanceof Array)) {
+ b = [b];
+ }
+ if (a.length !== b.length) {
+ assertEq(a.length, b.length);
+ }
+ for (let i = 0; i < a.length; i++) {
+ let x = a[i];
+ let y = b[i];
+ // intentionally use loose equality to allow bigint to compare equally
+ // to number, as can happen with how we use the JS-API here.
+ assertEq(x == y, true);
+ }
+ }
+
+ function testExtra(values) {
+ let { makeT, makeF, select } = wasmEvalText(`(module
+ (type $t (struct))
+ (type $f (struct (field i32)))
+
+ (func (export "makeT") (result eqref)
+ struct.new_default $t
+ )
+ (func (export "makeF") (result eqref)
+ struct.new_default $f
+ )
+
+ (func (export "select")
+ (param eqref) (result ${values.map((type) => type).join(" ")})
+ (block (result eqref)
+ local.get 0
+ br_on_cast_fail 0 eqref (ref $t)
+
+ ${values.map((type, i) => `${type}.const ${values.length + i}`)
+ .join("\n")}
+ br 1
+ )
+ drop
+ ${values.map((type, i) => `${type}.const ${i}`).join("\n")}
+ )
+ )`).exports;
+
+ let t = makeT();
+ let f = makeF();
+
+ let trueValues = values.map((type, i) => i);
+ let falseValues = values.map((type, i) => values.length + i);
+
+ assertEqResults(select(t), falseValues);
+ assertEqResults(select(f), trueValues);
+ }
+
+ // multiples of primitive valtypes
+ for (let valtype of ['i32', 'i64', 'f32', 'f64']) {
+ testExtra([valtype]);
+ testExtra([valtype, valtype]);
+ testExtra([valtype, valtype, valtype]);
+ testExtra([valtype, valtype, valtype, valtype, valtype, valtype, valtype, valtype]);
+ }
+
+ // random sundry of valtypes
+ testExtra(['i32', 'f32', 'i64', 'f64']);
+ testExtra(['i32', 'f32', 'i64', 'f64', 'i32', 'f32', 'i64', 'f64']);
+}
+
+// This test causes the `values` vector returned by
+// `OpIter<Policy>::readBrOnCastFail` to contain three entries, the last of
+// which is the argument, hence is reftyped. This is used to verify an
+// assertion to that effect in FunctionCompiler::brOnCastCommon.
+{
+ let tOnCastFail =
+ `(module
+ (type $a (struct))
+ (func (export "onCastFail") (param f32 i32 eqref) (result f32 i32 eqref)
+ local.get 0
+ local.get 1
+ local.get 2
+ br_on_cast_fail 0 eqref (ref $a)
+ unreachable
+ )
+ )`;
+ let { onCastFail } = wasmEvalText(tOnCastFail).exports;
+}
diff --git a/js/src/jit-test/tests/wasm/gc/br-on-cast.js b/js/src/jit-test/tests/wasm/gc/br-on-cast.js
new file mode 100644
index 0000000000..cf83f1ba00
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/br-on-cast.js
@@ -0,0 +1,197 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+function typingModule(types, from, to, brParams, branchResults, fallthroughResults) {
+ return `(module
+ ${types}
+ (func
+ (param ${brParams.join(' ')})
+ (result ${branchResults.join(' ')})
+
+ (block (result ${fallthroughResults.join(' ')})
+ (; push params onto the stack in the same order as they appear, leaving
+ the last param at the top of the stack. ;)
+ ${brParams.map((_, i) => `local.get ${i}`).join('\n')}
+ br_on_cast 1 ${from} ${to}
+ )
+ unreachable
+ )
+ )`;
+}
+
+function validTyping(types, from, to, brParams, branchResults, fallthroughResults) {
+ wasmValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults));
+}
+
+function invalidTyping(types, from, to, brParams, branchResults, fallthroughResults, error) {
+ wasmFailValidateText(typingModule(types, from, to, brParams, branchResults, fallthroughResults), error);
+}
+
+// valid: eqref -> struct
+validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref $a)'], ['eqref']);
+// valid: eqref -> struct (and looser types on results)
+validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref null $a)'], ['anyref']);
+// valid: eqref -> nullable struct (note that fallthrough becomes non-nullable)
+validTyping('(type $a (struct))', 'eqref', '(ref null $a)', ['eqref'], ['(ref null $a)'], ['(ref eq)']);
+// valid: struct -> struct (from anyref)
+validTyping('(type $a (struct))', 'anyref', '(ref $a)', ['(ref $a)'], ['(ref $a)'], ['anyref']);
+// valid: struct -> struct (canonicalized)
+validTyping('(type $a (struct)) (type $b (struct))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $b)']);
+// valid: nullable struct -> non-nullable struct (canonicalized)
+validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref $b)', ['(ref null $a)'], ['(ref $b)'], ['(ref null $a)']);
+// valid: nullable struct -> nullable struct (canonicalized)
+validTyping('(type $a (struct)) (type $b (struct))', '(ref null $a)', '(ref null $b)', ['(ref null $a)'], ['(ref null $a)'], ['(ref $a)']);
+// valid: eqref -> struct with extra arg
+validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'eqref'], ['i32', '(ref $a)'], ['i32', 'eqref']);
+// valid: eqref -> struct with two extra args
+validTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['i32', 'f32', 'eqref']);
+
+// invalid: block result type must have slot for casted-to type
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], [], ['eqref'], /type mismatch/);
+// invalid: block result type must be supertype of casted-to type
+invalidTyping('(type $a (struct)) (type $b (struct (field i32)))', 'eqref', '(ref $a)', ['eqref'], ['(ref $b)'], ['(ref $a)'], /type mismatch/);
+// invalid: input is missing extra i32 from the branch target type
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['i32', 'f32', 'eqref'], /popping value/);
+// invalid: input has extra [i32, f32] swapped from the branch target type
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['f32', 'i32', '(ref $a)'], ['i32', 'f32', 'eqref'], /type mismatch/);
+// invalid: input has extra [i32, f32] swapped from the branch fallthrough type
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['i32', 'f32', 'eqref'], ['i32', 'f32', '(ref $a)'], ['f32', 'i32', 'eqref'], /type mismatch/);
+// invalid: casting to non-nullable but fallthrough not nullable
+invalidTyping('(type $a (struct))', 'eqref', '(ref $a)', ['eqref'], ['(ref $a)'], ['(ref eq)'], /type mismatch/);
+// invalid: struct -> struct (same recursion group)
+invalidTyping('(rec (type $a (struct)) (type $b (struct)))', '(ref $a)', '(ref $b)', ['(ref $a)'], ['(ref $b)'], ['(ref $a)'], /type mismatch/);
+
+// Simple runtime test of casting
+{
+ let { makeA, makeB, isA, isB } = wasmEvalText(`(module
+ (type $a (struct))
+ (type $b (sub $a (struct (field i32))))
+
+ (func (export "makeA") (result eqref)
+ struct.new_default $a
+ )
+
+ (func (export "makeB") (result eqref)
+ struct.new_default $b
+ )
+
+ (func (export "isA") (param eqref) (result i32)
+ (block (result (ref $a))
+ local.get 0
+ br_on_cast 0 anyref (ref $a)
+
+ i32.const 0
+ br 1
+ )
+ drop
+ i32.const 1
+ )
+
+ (func (export "isB") (param eqref) (result i32)
+ (block (result (ref $a))
+ local.get 0
+ br_on_cast 0 anyref (ref $b)
+
+ i32.const 0
+ br 1
+ )
+ drop
+ i32.const 1
+ )
+ )`).exports;
+
+ let a = makeA();
+ let b = makeB();
+
+ assertEq(isA(a), 1);
+ assertEq(isA(b), 1);
+ assertEq(isB(a), 0);
+ assertEq(isB(b), 1);
+}
+
+// Runtime test of casting with extra values
+{
+ function assertEqResults(a, b) {
+ if (!(a instanceof Array)) {
+ a = [a];
+ }
+ if (!(b instanceof Array)) {
+ b = [b];
+ }
+ if (a.length !== b.length) {
+ assertEq(a.length, b.length);
+ }
+ for (let i = 0; i < a.length; i++) {
+ let x = a[i];
+ let y = b[i];
+ // intentionally use loose equality to allow bigint to compare equally
+ // to number, as can happen with how we use the JS-API here.
+ assertEq(x == y, true);
+ }
+ }
+
+ function testExtra(values) {
+ let { makeT, makeF, select } = wasmEvalText(`(module
+ (type $t (struct))
+ (type $f (struct (field i32)))
+
+ (func (export "makeT") (result eqref)
+ struct.new_default $t
+ )
+ (func (export "makeF") (result eqref)
+ struct.new_default $f
+ )
+
+ (func (export "select") (param eqref) (result ${values.map((type) => type).join(" ")})
+ (block (result (ref $t))
+ local.get 0
+ br_on_cast 0 anyref (ref $t)
+
+ ${values.map((type, i) => `${type}.const ${values.length + i}`).join("\n")}
+ br 1
+ )
+ drop
+ ${values.map((type, i) => `${type}.const ${i}`).join("\n")}
+ )
+ )`).exports;
+
+ let t = makeT();
+ let f = makeF();
+
+ let trueValues = values.map((type, i) => i);
+ let falseValues = values.map((type, i) => values.length + i);
+
+ assertEqResults(select(t), trueValues);
+ assertEqResults(select(f), falseValues);
+ }
+
+ // multiples of primitive valtypes
+ for (let valtype of ['i32', 'i64', 'f32', 'f64']) {
+ testExtra([valtype]);
+ testExtra([valtype, valtype]);
+ testExtra([valtype, valtype, valtype]);
+ testExtra([valtype, valtype, valtype, valtype, valtype, valtype, valtype, valtype]);
+ }
+
+ // random sundry of valtypes
+ testExtra(['i32', 'f32', 'i64', 'f64']);
+ testExtra(['i32', 'f32', 'i64', 'f64', 'i32', 'f32', 'i64', 'f64']);
+}
+
+// This test causes the `values` vector returned by
+// `OpIter<Policy>::readBrOnCast` to contain three entries, the last of which
+// is the argument, hence is reftyped. This is used to verify an assertion to
+// that effect in FunctionCompiler::brOnCastCommon.
+{
+ let tOnCast =
+ `(module
+ (type $a (struct))
+ (func (export "onCast") (param f32 i32 eqref) (result f32 i32 (ref $a))
+ local.get 0
+ local.get 1
+ local.get 2
+ br_on_cast 0 anyref (ref $a)
+ unreachable
+ )
+ )`;
+ let { onCast } = wasmEvalText(tOnCast).exports;
+}
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 😁`);
diff --git a/js/src/jit-test/tests/wasm/gc/cast-extern.js b/js/src/jit-test/tests/wasm/gc/cast-extern.js
new file mode 100644
index 0000000000..9fd9ddf2c9
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/cast-extern.js
@@ -0,0 +1,54 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Casting an external value is an expected failure
+let { refCast, refTest, brOnCast, brOnCastFail } = wasmEvalText(`
+ (module
+ (; give this struct a unique identity to avoid conflict with
+ the struct type defined in wasm.js ;)
+ (type (struct i64 i32 i64 i32))
+
+ (func (export "refTest") (param externref) (result i32)
+ local.get 0
+ extern.internalize
+ ref.test (ref 0)
+ )
+ (func (export "refCast") (param externref) (result i32)
+ local.get 0
+ extern.internalize
+ ref.cast (ref null 0)
+ drop
+ i32.const 0
+ )
+ (func (export "brOnCast") (param externref) (result i32)
+ (block (result (ref 0))
+ local.get 0
+ extern.internalize
+ br_on_cast 0 anyref (ref 0)
+ drop
+ i32.const 0
+ br 1
+ )
+ drop
+ i32.const 1
+ )
+ (func (export "brOnCastFail") (param externref) (result i32)
+ (block (result anyref)
+ local.get 0
+ extern.internalize
+ br_on_cast_fail 0 anyref (ref 0)
+ drop
+ i32.const 1
+ br 1
+ )
+ drop
+ i32.const 0
+ )
+ )
+`).exports;
+
+for (let v of WasmNonNullExternrefValues) {
+ assertErrorMessage(() => refCast(v), WebAssembly.RuntimeError, /bad cast/);
+ assertEq(refTest(v), 0);
+ assertEq(brOnCast(v), 0);
+ assertEq(brOnCastFail(v), 0);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/casting.js b/js/src/jit-test/tests/wasm/gc/casting.js
new file mode 100644
index 0000000000..7c07bfcf69
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/casting.js
@@ -0,0 +1,128 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Test the maximum depth limit
+{
+ const MaxDepth = 31;
+ let types = `(type (struct))\n`;
+ for (let depth = 1; depth <= MaxDepth + 1; depth++) {
+ types += `(type (sub ${depth - 1} (struct)))\n`;
+ }
+ wasmFailValidateText(`(module
+ ${types}
+ )`, /too deep/);
+}
+
+// Test all possible casting combinations of the following graph:
+//
+// A1 A2
+// | |
+// B1 B2
+// | \ |
+// C1 C2 C3
+// | \ |
+// D1 D2 D3
+// | \ |
+// E1 E2 E3
+// | \ |
+// F1 F2 F3
+// | \ |
+// G1 G2 G3
+// | \ |
+// H1 H2 H3
+// | \ |
+// I1 I2 I3
+// | \ |
+// J1 J2 J3
+//
+// NOTE: this object will be mutated and needs to be ordered such that parent
+// definitions come before children. Note also, to be properly effective,
+// these trees need to have a depth of at least MinSuperTypeVectorLength as
+// defined in wasm/WasmCodegenConstants.h; keep it in sync with that.
+const TYPES = {
+ 'A1': { super: null },
+ 'A2': { super: null },
+ 'B1': { super: 'A1' },
+ 'B2': { super: 'A2' },
+ 'C1': { super: 'B1' },
+ 'C2': { super: 'B1' },
+ 'C3': { super: 'B2' },
+ 'D1': { super: 'C1' },
+ 'D2': { super: 'C1' },
+ 'D3': { super: 'C3' },
+ 'E1': { super: 'D1' },
+ 'E2': { super: 'D1' },
+ 'E3': { super: 'D3' },
+ 'F1': { super: 'E1' },
+ 'F2': { super: 'E1' },
+ 'F3': { super: 'E3' },
+ 'G1': { super: 'F1' },
+ 'G2': { super: 'F1' },
+ 'G3': { super: 'F3' },
+ 'H1': { super: 'G1' },
+ 'H2': { super: 'G1' },
+ 'H3': { super: 'G3' },
+ 'I1': { super: 'H1' },
+ 'I2': { super: 'H1' },
+ 'I3': { super: 'H3' },
+ 'J1': { super: 'I1' },
+ 'J2': { super: 'I1' },
+ 'J3': { super: 'I3' },
+};
+
+// The oracle method for testing the declared subtype relationship.
+function manualIsSubtype(types, subType, superType) {
+ while (subType !== superType && subType.super !== null) {
+ subType = types[subType.super];
+ }
+ return subType === superType;
+}
+
+function testAllCasts(types) {
+ let typeSection = ``;
+ let funcSection = ``;
+ for (let name in types) {
+ let type = types[name];
+ if (type.super === null) {
+ typeSection += `(type \$${name} (struct))\n`;
+ } else {
+ typeSection += `(type \$${name} (sub \$${type.super} (struct)))\n`;
+ }
+ funcSection += `
+ (func (export "new${name}") (result externref)
+ struct.new_default \$${name}
+ extern.externalize
+ )
+ (func (export "is${name}") (param externref) (result i32)
+ local.get 0
+ extern.internalize
+ ref.test (ref \$${name})
+ )`;
+ }
+ // NOTE: we place all types in a single recursion group to prevent
+ // canonicalization from
+ let moduleText = `(module
+ (rec ${typeSection})
+ ${funcSection}
+ )`;
+
+ // Instantiate the module and acquire the testing methods
+ let exports = wasmEvalText(moduleText).exports;
+ for (let name in types) {
+ let type = types[name];
+ type['new'] = exports[`new${name}`];
+ type['is'] = exports[`is${name}`];
+ }
+
+ // Test every combination of types, comparing the oracle method against the
+ // JIT'ed method.
+ for (let subTypeName in types) {
+ let subType = types[subTypeName];
+ for (let superTypeName in types) {
+ let superType = types[subTypeName];
+ assertEq(
+ manualIsSubtype(types, subType, superType) ? 1 : 0,
+ superType['is'](subType['new']()));
+ }
+ }
+}
+testAllCasts(TYPES);
diff --git a/js/src/jit-test/tests/wasm/gc/debugger.js b/js/src/jit-test/tests/wasm/gc/debugger.js
new file mode 100644
index 0000000000..dea6581eb8
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/debugger.js
@@ -0,0 +1,38 @@
+// |jit-test| skip-if: !wasmDebuggingEnabled()
+
+(function() {
+ let g = newGlobal({newCompartment: true});
+ let dbg = new Debugger(g);
+ g.eval(`o = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary('(module (func (param externref) (result externref) local.get 0) (export "" (func 0)))')));`);
+})();
+
+(function() {
+ var g = newGlobal({newCompartment: true});
+ g.parent = this;
+
+ let src = `
+ (module
+ (func (export "func") (param $ref externref) (result externref)
+ local.get $ref
+ )
+ )
+ `;
+
+ g.eval(`
+ var obj = { somekey: 'somevalue' };
+ Debugger(parent).onEnterFrame = function(frame) {
+ let v = frame.environment.getVariable('var0');
+ assertEq(typeof v, 'object');
+
+ let prop = v.unwrap().getOwnPropertyDescriptor('somekey');
+ assertEq(typeof prop, 'object');
+ assertEq(typeof prop.value, 'string');
+ assertEq(prop.value, 'somevalue');
+
+ // Disable onEnterFrame hook.
+ Debugger(parent).onEnterFrame = undefined;
+ };
+ `);
+
+ new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(`${src}`))).exports.func(g.obj);
+})();
diff --git a/js/src/jit-test/tests/wasm/gc/directives.txt b/js/src/jit-test/tests/wasm/gc/directives.txt
new file mode 100644
index 0000000000..49594ced70
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/directives.txt
@@ -0,0 +1 @@
+|jit-test| test-also=--wasm-compiler=optimizing; test-also=--wasm-function-references --wasm-gc; test-also=--wasm-compiler=baseline --wasm-function-references --wasm-gc; include:wasm.js
diff --git a/js/src/jit-test/tests/wasm/gc/disabled.js b/js/src/jit-test/tests/wasm/gc/disabled.js
new file mode 100644
index 0000000000..791c6ff25e
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/disabled.js
@@ -0,0 +1,25 @@
+// |jit-test| skip-if: wasmGcEnabled()
+
+const { CompileError, validate } = WebAssembly;
+
+const UNRECOGNIZED_OPCODE_OR_BAD_TYPE = /unrecognized opcode|(Structure|reference|gc) types not enabled|invalid heap type|invalid inline block type|bad type|\(ref T\) types not enabled|Invalid type|invalid function type/;
+
+let simpleTests = [
+ "(module (func (drop (ref.null eq))))",
+ "(module (func $test (local eqref)))",
+ "(module (func $test (param eqref)))",
+ "(module (func $test (result eqref) (ref.null eq)))",
+ "(module (func $test (block (result eqref) (unreachable)) unreachable))",
+ "(module (func $test (result i32) (local eqref) (ref.is_null (local.get 0))))",
+ `(module (import "a" "b" (func (param eqref))))`,
+ `(module (import "a" "b" (func (result eqref))))`,
+ `(module (type $s (struct)))`,
+];
+
+// Test that use of gc-types fails when gc is disabled.
+
+for (let src of simpleTests) {
+ let bin = wasmTextToBinary(src);
+ assertEq(validate(bin), false);
+ wasmCompilationShouldFail(bin, UNRECOGNIZED_OPCODE_OR_BAD_TYPE);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/externref-boxing-struct.js b/js/src/jit-test/tests/wasm/gc/externref-boxing-struct.js
new file mode 100644
index 0000000000..b9cfed080f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/externref-boxing-struct.js
@@ -0,0 +1,74 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Moving a JS value through a wasm externref is a pair of boxing/unboxing
+// conversions that leaves the value unchanged. There are many cases,
+// along these axes:
+//
+// - global variables, see externref-boxing.js
+// - tables, see externref-boxing.js
+// - function parameters and returns, see externref-boxing.js
+// - struct fields [for the gc feature], this file
+// - TypedObject fields when we construct with the externref type; this file
+
+// Struct fields of externref type can receive their value in these ways:
+//
+// - the struct.new and struct.set instructions
+// - storing into mutable fields from JS
+// - from a constructor called from JS
+//
+// Struct fields can be read in these ways:
+//
+// - the struct.get instruction
+// - reading the field from JS
+//
+// We're especially interested in two cases: where JS stores a non-object value
+// into a field, in this case there should be boxing; and where JS reads a
+// non-pointer value from a field, in this case there should be unboxing.
+
+// Write with struct.new, read with the JS getter
+
+for (let v of WasmExternrefValues)
+{
+ let ins = wasmEvalText(
+ `(module
+ (type $S (struct (field $S.x (mut externref))))
+ (func (export "make") (param $v externref) (result eqref)
+ (struct.new $S (local.get $v))))`);
+ let x = ins.exports.make(v);
+ assertEq(x[0], v);
+}
+
+// Try to make sure externrefs are properly traced
+
+{
+ let fields = iota(10).map(() => `(field externref)`).join(' ');
+ let params = iota(10).map((i) => `(param $${i} externref)`).join(' ');
+ let args = iota(10).map((i) => `(local.get $${i})`).join(' ');
+ let txt = `(module
+ (type $S (struct ${fields}))
+ (func (export "make") ${params} (result eqref)
+ (struct.new $S ${args})))`;
+ let ins = wasmEvalText(txt);
+ let x = ins.exports.make({x:0}, {x:1}, {x:2}, {x:3}, {x:4}, {x:5}, {x:6}, {x:7}, {x:8}, {x:9})
+ gc('shrinking');
+ assertEq(typeof x[0], "object");
+ assertEq(x[0].x, 0);
+ assertEq(typeof x[1], "object");
+ assertEq(x[1].x, 1);
+ assertEq(typeof x[2], "object");
+ assertEq(x[2].x, 2);
+ assertEq(typeof x[3], "object");
+ assertEq(x[3].x, 3);
+ assertEq(typeof x[4], "object");
+ assertEq(x[4].x, 4);
+ assertEq(typeof x[5], "object");
+ assertEq(x[5].x, 5);
+ assertEq(typeof x[6], "object");
+ assertEq(x[6].x, 6);
+ assertEq(typeof x[7], "object");
+ assertEq(x[7].x, 7);
+ assertEq(typeof x[8], "object");
+ assertEq(x[8].x, 8);
+ assertEq(typeof x[9], "object");
+ assertEq(x[9].x, 9);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/externref-conversions.js b/js/src/jit-test/tests/wasm/gc/externref-conversions.js
new file mode 100644
index 0000000000..12c5a2bbcc
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/externref-conversions.js
@@ -0,0 +1,94 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Output type is non-nullable if the input type is non-nullable
+wasmValidateText(`(module
+ (func (param externref) (result anyref)
+ local.get 0
+ extern.internalize
+ )
+ (func (param anyref) (result externref)
+ local.get 0
+ extern.externalize
+ )
+ (func (param (ref extern)) (result (ref any))
+ local.get 0
+ extern.internalize
+ )
+ (func (param (ref any)) (result (ref extern))
+ local.get 0
+ extern.externalize
+ )
+ (func (result (ref any))
+ unreachable
+ extern.internalize
+ )
+ (func (result (ref extern))
+ unreachable
+ extern.externalize
+ )
+)`);
+
+// Output type is nullable if the input type is nullable
+wasmFailValidateText(`(module
+ (func (param externref) (result (ref any))
+ local.get 0
+ extern.internalize
+ )
+)`, /expected/);
+wasmFailValidateText(`(module
+ (func (param anyref) (result (ref extern))
+ local.get 0
+ extern.externalize
+ )
+)`, /expected/);
+
+// Can round trip an externref through anyref and get the same thing back
+let {roundtripThroughAny} = wasmEvalText(`(module
+ (func (export "roundtripThroughAny") (param externref) (result externref)
+ local.get 0
+ extern.internalize
+ extern.externalize
+ )
+)`).exports;
+for (let value of WasmExternrefValues) {
+ assertEq(value, roundtripThroughAny(value));
+}
+
+// Can round trip GC objects through externref and get the same thing back
+let {testStruct, testArray} = wasmEvalText(`(module
+ (type $struct (struct))
+ (type $array (array i32))
+
+ (func (export "testStruct") (result i32)
+ (local (ref $struct))
+ (local.set 0 struct.new $struct)
+ local.get 0
+ (ref.eq
+ (ref.cast (ref null $struct)
+ (extern.internalize
+ (extern.externalize
+ local.get 0
+ )
+ )
+ )
+ )
+ )
+
+ (func (export "testArray") (result i32)
+ (local (ref $array))
+ (local.set 0 (array.new $array i32.const 0 i32.const 0))
+ local.get 0
+ (ref.eq
+ (ref.cast (ref null $array)
+ (extern.internalize
+ (extern.externalize
+ local.get 0
+ )
+ )
+ )
+ )
+ )
+)`).exports;
+
+assertEq(testStruct(), 1);
+assertEq(testArray(), 1);
diff --git a/js/src/jit-test/tests/wasm/gc/function_subtyping.js b/js/src/jit-test/tests/wasm/gc/function_subtyping.js
new file mode 100644
index 0000000000..6b4e719a74
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/function_subtyping.js
@@ -0,0 +1,79 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Validate rules for function subtyping:
+// - Same number of parameters and results
+// - Function return types are covariant
+// - Function parameter types are contravariant
+wasmValidateText(
+`(module
+ (type $A (struct (field i32)))
+ (type $B (sub $A (struct (field i32) (field i32))))
+ (type $C (sub $B (struct (field i32) (field i32) (field i64))))
+ (type $D (sub $B (struct (field i32) (field i32) (field f32))))
+
+ ;; Same types (invariant)
+ (type $func1 (func (param (ref $A) (ref $A)) (result (ref $C))))
+ (type $func2 (sub $func1 (func (param (ref $A) (ref $A)) (result (ref $C)))))
+
+ ;; Covariant return types are valid
+ (type $func3 (func (param (ref $A) (ref $A)) (result (ref $B))))
+ (type $func4 (sub $func3 (func (param (ref $A) (ref $A)) (result (ref $C)))))
+ (type $func5 (func (param (ref $A) (ref $A)) (result (ref $A) (ref $B))))
+ (type $func6 (sub $func5 (func (param (ref $A) (ref $A)) (result (ref $D) (ref $C)))))
+
+ ;; Contravariant parameter types are valid
+ (type $func7 (func (param (ref $A) (ref $C)) (result (ref $C))))
+ (type $func8 (sub $func7 (func (param (ref $A) (ref $B)) (result (ref $C)))))
+ (type $func9 (func (param (ref $D) (ref $C)) (result (ref $C))))
+ (type $func10 (sub $func9 (func (param (ref $A) (ref $B)) (result (ref $C)))))
+
+ ;; Mixing covariance and contravariance
+ (type $func11 (func (param (ref $D) (ref $C)) (result (ref $A))))
+ (type $func12 (sub $func11 (func (param (ref $A) (ref $B)) (result (ref $C)))))
+)
+`);
+
+// Validate that incorrect subtyping examples are failing as expected
+const typeError = /incompatible super type/;
+
+var code =`
+(module
+ (type $A (struct (field i32)))
+ (type $B (sub $A (struct (field i32) (field i32))))
+ (type $C (sub $B (struct (field i32) (field i32) (field i64))))
+ (type $D (sub $B (struct (field i32) (field i32) (field f32))))
+
+ ;; Not the same number of arguments/results
+ (type $func1 (func (param (ref $A) (ref $A)) (result (ref $C))))
+ (type $func2 (sub $func1 (func (param (ref $A) (ref $A)) (result (ref $C) (ref $A)))))
+)`;
+
+wasmFailValidateText(code, typeError);
+
+code =`
+(module
+ (type $A (struct (field i32)))
+ (type $B (sub $A (struct (field i32) (field i32))))
+ (type $C (sub $B (struct (field i32) (field i32) (field i64))))
+ (type $D (sub $B (struct (field i32) (field i32) (field f32))))
+
+ ;; Contravariant result types are invalid
+ (type $func3 (func (param (ref $A) (ref $A)) (result (ref $C))))
+ (type $func4 (sub $func3 (func (param (ref $A) (ref $A)) (result (ref $A)))))
+)`;
+
+wasmFailValidateText(code, typeError);
+
+code =`
+(module
+ (type $A (struct (field i32)))
+ (type $B (sub $A (struct (field i32) (field i32))))
+ (type $C (sub $B (struct (field i32) (field i32) (field i64))))
+ (type $D (sub $B (struct (field i32) (field i32) (field f32))))
+
+ ;; Covariant parameters are invalid
+ (type $func5 (func (param (ref $A) (ref $A)) (result (ref $C))))
+ (type $func6 (sub $func5 (func (param (ref $B) (ref $A)) (result (ref $C)))))
+)`;
+
+wasmFailValidateText(code, typeError);
diff --git a/js/src/jit-test/tests/wasm/gc/global-get.js b/js/src/jit-test/tests/wasm/gc/global-get.js
new file mode 100644
index 0000000000..1409fe39cd
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/global-get.js
@@ -0,0 +1,30 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// global.get cannot refer to self or after globals after self
+
+assertErrorMessage(() => wasmEvalText(`(module
+ (global i32 global.get 0)
+)`), WebAssembly.CompileError, /global/);
+
+assertErrorMessage(() => wasmEvalText(`(module
+ (global i32 global.get 1)
+ (global i32 i32.const 0)
+)`), WebAssembly.CompileError, /global/);
+
+// global.get works on previous globals
+
+{
+ let {func, b, c, e} = wasmEvalText(`(module
+ (func $func (export "func"))
+
+ (global $a i32 i32.const 1)
+ (global $b (export "b") i32 global.get $a)
+ (global $c (export "c") i32 global.get $b)
+
+ (global $d funcref ref.func $func)
+ (global $e (export "e") funcref global.get $d)
+ )`).exports;
+ assertEq(b.value, 1);
+ assertEq(c.value, 1);
+ assertEq(e.value, func);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/init-expr.js b/js/src/jit-test/tests/wasm/gc/init-expr.js
new file mode 100644
index 0000000000..b4d0b0950b
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/init-expr.js
@@ -0,0 +1,75 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Test wasm::InitExpr GC specific constant expressions
+
+// struct.new and struct.new_default
+
+const { structNew, structNewDefault, structLarge } = wasmEvalText(`(module
+ (type $r (struct (field i32) (field f32)))
+ (type $xxl (struct
+ (field i64) (field f64) (field i64) (field f64)
+ (field i64) (field f64) (field i64) (field f64)
+ (field i64) (field f64) (field i64) (field f64)
+ (field i64) (field f64) (field i64) (field f64)
+ (field i64) (field f64) (field i64) (field f64)))
+
+ (global $g1 (ref null $r) (struct.new_default $r))
+
+ (global $g2 (ref null $r) (struct.new $r
+ (i32.const 2)
+ (f32.const 3.14)))
+
+ (global $gx (ref null $xxl) (struct.new $xxl
+ (i64.const 1) (f64.const 2.) (i64.const 3) (f64.const 4.)
+ (i64.const 5) (f64.const 2.) (i64.const 3) (f64.const 4.)
+ (i64.const 1) (f64.const 8.) (i64.const 9) (f64.const 4.)
+ (i64.const 1) (f64.const 2.) (i64.const 12) (f64.const 3.14)
+ (i64.const 16) (f64.const 17.) (i64.const 18) (f64.const 19.)))
+
+ (func (export "structNewDefault") (result eqref) global.get $g1)
+ (func (export "structNew") (result eqref) global.get $g2)
+ (func (export "structLarge") (result eqref) global.get $gx)
+ )`).exports;
+
+let result;
+result = structNew();
+assertEq(result[0], 2);
+assertEq(result[1], new Float32Array([3.140000104904175])[0]);
+result = structNewDefault();
+assertEq(result[0], 0);
+assertEq(result[1], 0);
+result = structLarge();
+assertEq(result[2], 3n);
+assertEq(result[19], 19);
+
+// array.new, array.new_default, and array.new_fixed
+
+const { arrayNew, arrayNewDefault, arrayNewFixed } = wasmEvalText(`(module
+ (type $r (struct (field i32) (field f32)))
+ (type $a1 (array f64))
+ (type $a2 (array i32))
+ (type $a3 (array (ref null $r)))
+
+ (global $g1 (ref null $a1) (array.new $a1 (f64.const 3.14) (i32.const 3)))
+ (global $g2 (ref null $a2) (array.new_default $a2 (i32.const 2)))
+ (global $g3 (ref null $a3) (array.new_fixed $a3 2
+ (struct.new $r (i32.const 10) (f32.const 16.0))
+ (ref.null $r)))
+
+ (func (export "arrayNew") (result eqref) global.get $g1)
+ (func (export "arrayNewDefault") (result eqref) global.get $g2)
+ (func (export "arrayNewFixed") (result eqref) global.get $g3)
+ )`).exports;
+
+result = arrayNew();
+assertEq(result.length, 3);
+assertEq(result[0], 3.14);
+assertEq(result[2], 3.14);
+result = arrayNewDefault();
+assertEq(result.length, 2);
+assertEq(result[1], 0);
+result = arrayNewFixed();
+assertEq(result.length, 2);
+assertEq(result[0][0], 10);
+assertEq(result[0][1], 16);
+assertEq(result[1], null);
diff --git a/js/src/jit-test/tests/wasm/gc/ion-and-baseline.js b/js/src/jit-test/tests/wasm/gc/ion-and-baseline.js
new file mode 100644
index 0000000000..5a4951c585
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/ion-and-baseline.js
@@ -0,0 +1,88 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Attempt to test intercalls from ion to baseline and back.
+//
+// We get into this situation when the modules are compiled with different
+// tiering policies, or one tiers up before the other, or (for now) one opts
+// into gc and is baseline-compiled and the other does not and is ion-compiled.
+// There are lots of variables here. Generally, a small module will be
+// ion-compiled unless there's reason to baseline-compile it, so we're likely
+// actually testing something here.
+//
+// Some logging with printf confirms that refmod is baseline-compiled and
+// nonrefmod is ion-compiled at present, with --wasm-gc enabled.
+
+var refmod = new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (import "" "tbl" (table $tbl 4 funcref))
+ (import "" "print" (func $print (param i32)))
+
+ ;; Just a dummy
+ (type $s (struct (field i32)))
+
+ (type $htype (func (param externref)))
+ (type $itype (func (result externref)))
+
+ (elem (i32.const 0) $f $g)
+
+ (func $f (param externref)
+ (call $print (i32.const 1)))
+
+ (func $g (result externref)
+ (call $print (i32.const 2))
+ (ref.null extern))
+
+ (func (export "test_h")
+ (call_indirect (type $htype) (ref.null extern) (i32.const 2)))
+
+ (func (export "test_i")
+ (drop (call_indirect (type $itype) (i32.const 3))))
+
+ )`));
+
+var nonrefmod = new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (import "" "tbl" (table $tbl 4 funcref))
+ (import "" "print" (func $print (param i32)))
+
+ (type $ftype (func (param i32)))
+ (type $gtype (func (result i32)))
+
+ (elem (i32.const 2) $h $i)
+
+ ;; Should fail because of the signature mismatch: parameter
+ (func (export "test_f")
+ (call_indirect (type $ftype) (i32.const 37) (i32.const 0)))
+
+ ;; Should fail because of the signature mismatch: return value
+ (func (export "test_g")
+ (drop (call_indirect (type $gtype) (i32.const 1))))
+
+ (func $h (param i32)
+ (call $print (i32.const 2)))
+
+ (func $i (result i32)
+ (call $print (i32.const 3))
+ (i32.const 37))
+ )`));
+
+var tbl = new WebAssembly.Table({initial:4, element:"anyfunc"});
+var refins = new WebAssembly.Instance(refmod, {"":{print, tbl}}).exports;
+var nonrefins = new WebAssembly.Instance(nonrefmod, {"":{print, tbl}}).exports;
+
+assertErrorMessage(() => nonrefins.test_f(),
+ WebAssembly.RuntimeError,
+ /indirect call signature mismatch/);
+
+assertErrorMessage(() => nonrefins.test_g(),
+ WebAssembly.RuntimeError,
+ /indirect call signature mismatch/);
+
+assertErrorMessage(() => refins.test_h(),
+ WebAssembly.RuntimeError,
+ /indirect call signature mismatch/);
+
+assertErrorMessage(() => refins.test_i(),
+ WebAssembly.RuntimeError,
+ /indirect call signature mismatch/);
+
diff --git a/js/src/jit-test/tests/wasm/gc/js-boundary.js b/js/src/jit-test/tests/wasm/gc/js-boundary.js
new file mode 100644
index 0000000000..ebf5387704
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/js-boundary.js
@@ -0,0 +1,107 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Tests of dynamic type checks
+test('anyref', WasmAnyrefValues, WasmNonAnyrefValues);
+test('eqref', WasmEqrefValues, WasmNonAnyrefValues);
+test('structref', WasmStructrefValues, WasmNonAnyrefValues);
+test('arrayref', WasmArrayrefValues, WasmNonAnyrefValues);
+let { newStruct } = wasmEvalText(`
+ (module
+ (type $s (struct))
+ (func (export "newStruct") (result anyref)
+ struct.new $s)
+ )`).exports;
+test('(ref null 0)', [newStruct()], WasmNonAnyrefValues, '(type (struct))');
+test('nullref', [null], WasmNonAnyrefValues);
+
+function test(type, validValues, invalidValues, typeSection) {
+ const CheckError = /can only pass|bad type/;
+
+ if (!typeSection) {
+ typeSection = "";
+ }
+
+ // 1. Exported function params
+ let {a} = wasmEvalText(`(module
+ ${typeSection}
+ (func (export "a") (param ${type}))
+ )`).exports;
+ for (let val of invalidValues) {
+ assertErrorMessage(() => a(val), TypeError, CheckError);
+ }
+ for (let val of validValues) {
+ a(val);
+ }
+
+ // 2. Imported function results
+ for (let val of invalidValues) {
+ function returnVal() {
+ return val;
+ }
+ let {test} = wasmEvalText(`(module
+ ${typeSection}
+ (func $returnVal (import "" "returnVal") (result ${type}))
+ (func (export "test")
+ call $returnVal
+ drop
+ )
+ )`, {"": {returnVal}}).exports;
+ assertErrorMessage(() => test(), TypeError, CheckError);
+ }
+ for (let val of validValues) {
+ function returnVal() {
+ return val;
+ }
+ let {test} = wasmEvalText(`(module
+ ${typeSection}
+ (func $returnVal (import "" "returnVal") (result ${type}))
+ (func (export "test")
+ call $returnVal
+ drop
+ )
+ )`, {"": {returnVal}}).exports;
+ test(val);
+ }
+
+ // TODO: the rest of the tests cannot handle type sections yet.
+ if (typeSection !== "") {
+ return;
+ }
+
+ // 3. Global value setter
+ for (let val of validValues) {
+ // Construct global from JS-API with initial value
+ let a = new WebAssembly.Global({value: type}, val);
+ assertEq(a.value, val, 'roundtrip matches');
+
+ // Construct global from JS-API with null value, then set
+ let b = new WebAssembly.Global({value: type, mutable: true}, null);
+ b.value = val;
+ assertEq(b.value, val, 'roundtrip matches');
+ }
+ for (let val of invalidValues) {
+ // Construct global from JS-API with initial value
+ assertErrorMessage(() => new WebAssembly.Global({value: type}, val),
+ TypeError,
+ CheckError);
+
+ // Construct global from JS-API with null value, then set
+ let a = new WebAssembly.Global({value: type, mutable: true}, null);
+ assertErrorMessage(() => a.value = val,
+ TypeError,
+ CheckError);
+ }
+
+ // 4. Table set method
+ for (let val of validValues) {
+ let table = new WebAssembly.Table({element: type, initial: 1, maximum: 1});
+ table.set(0, val);
+ assertEq(table.get(0), val, 'roundtrip matches');
+ }
+ for (let val of invalidValues) {
+ let table = new WebAssembly.Table({element: type, initial: 1, maximum: 1});
+ assertErrorMessage(() => table.set(0, val),
+ TypeError,
+ CheckError);
+ }
+}
diff --git a/js/src/jit-test/tests/wasm/gc/linking.js b/js/src/jit-test/tests/wasm/gc/linking.js
new file mode 100644
index 0000000000..586011219a
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/linking.js
@@ -0,0 +1,72 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+function linkGlobals(typeSection, exportInit, linkType) {
+ let {global} = wasmEvalText(`(module
+ ${typeSection}
+ (global (export "global") ${linkType} ${exportInit})
+ )`).exports;
+
+ wasmEvalText(`(module
+ ${typeSection}
+ (import "" "global"
+ (global ${linkType})
+ )
+ )`, {"": {global}});
+}
+
+function linkTables(typeSection, exportInit, linkType) {
+ let {table} = wasmEvalText(`(module
+ ${typeSection}
+ (table (export "table") ${linkType} (elem (${exportInit})))
+ )`).exports;
+
+ wasmEvalText(`(module
+ ${typeSection}
+ (import "" "table"
+ (table 0 1 ${linkType})
+ )
+ )`, {"": {table}});
+}
+
+function linkFuncs(typeSection, exportInit, linkType) {
+ let {func} = wasmEvalText(`(module
+ ${typeSection}
+ (func
+ (export "func")
+ (param ${linkType})
+ (result ${linkType})
+ unreachable
+ )
+ )`).exports;
+
+ wasmEvalText(`(module
+ ${typeSection}
+ (import "" "func"
+ (func (param ${linkType}) (result ${linkType}))
+ )
+ )`, {"": {func}});
+}
+
+const TESTS = [
+ [
+ "(type (struct (field i32)))",
+ "ref.null 0",
+ "(ref null 0)",
+ ],
+ [
+ "(type (array i32))",
+ "ref.null 0",
+ "(ref null 0)",
+ ],
+ [
+ "(type (struct (field i32))) (type (struct (field (ref 0))))",
+ "ref.null 1",
+ "(ref null 1)",
+ ]
+];
+
+for (let test of TESTS) {
+ linkGlobals(...test);
+ linkTables(...test);
+ linkFuncs(...test);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/ref-eq.js b/js/src/jit-test/tests/wasm/gc/ref-eq.js
new file mode 100644
index 0000000000..e266916320
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/ref-eq.js
@@ -0,0 +1,27 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+//
+// ref.eq is part of the gc feature, not the reftypes feature.
+
+let { exports: { make, ref_eq, ref_eq_for_control } } = wasmEvalText(`(module
+ (type $s (struct))
+
+ (func (export "make") (result eqref) struct.new $s)
+
+ (func (export "ref_eq") (param $a eqref) (param $b eqref) (result i32)
+ (ref.eq (local.get $a) (local.get $b)))
+
+ (func (export "ref_eq_for_control") (param $a eqref) (param $b eqref) (result f64)
+ (if (result f64) (ref.eq (local.get $a) (local.get $b))
+ (f64.const 5.0)
+ (f64.const 3.0))))`);
+
+let a = make();
+let b = make();
+
+assertEq(ref_eq(null, null), 1);
+assertEq(ref_eq(null, a), 0);
+assertEq(ref_eq(b, b), 1);
+assertEq(ref_eq_for_control(null, null), 5);
+assertEq(ref_eq_for_control(null, a), 3);
+assertEq(ref_eq_for_control(b, b), 5);
+
diff --git a/js/src/jit-test/tests/wasm/gc/ref-global.js b/js/src/jit-test/tests/wasm/gc/ref-global.js
new file mode 100644
index 0000000000..1e2a87edc6
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/ref-global.js
@@ -0,0 +1,96 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Basic private-to-module functionality. At the moment all we have is null
+// pointers, not very exciting.
+
+{
+ let bin = wasmTextToBinary(
+ `(module
+ (type $point (struct
+ (field $x f64)
+ (field $y f64)))
+
+ (global $g1 (mut (ref null $point)) (ref.null $point))
+ (global $g2 (mut (ref null $point)) (ref.null $point))
+ (global $g3 (ref null $point) (ref.null $point))
+
+ ;; Restriction: cannot expose Refs outside the module, not even
+ ;; as a return value. See ref-restrict.js.
+
+ (func (export "get") (result eqref)
+ (global.get $g1))
+
+ (func (export "copy")
+ (global.set $g2 (global.get $g1)))
+
+ (func (export "clear")
+ (global.set $g1 (global.get $g3))
+ (global.set $g2 (ref.null $point))))`);
+
+ let mod = new WebAssembly.Module(bin);
+ let ins = new WebAssembly.Instance(mod).exports;
+
+ assertEq(ins.get(), null);
+ ins.copy(); // Should not crash
+ ins.clear(); // Should not crash
+}
+
+// Global with struct type
+
+{
+ let bin = wasmTextToBinary(
+ `(module
+ (type $point (struct
+ (field $x f64)
+ (field $y f64)))
+
+ (global $glob (mut (ref null $point)) (ref.null $point))
+
+ (func (export "init")
+ (global.set $glob (struct.new $point (f64.const 0.5) (f64.const 2.75))))
+
+ (func (export "change")
+ (global.set $glob (struct.new $point (f64.const 3.5) (f64.const 37.25))))
+
+ (func (export "clear")
+ (global.set $glob (ref.null $point)))
+
+ (func (export "x") (result f64)
+ (struct.get $point 0 (global.get $glob)))
+
+ (func (export "y") (result f64)
+ (struct.get $point 1 (global.get $glob))))`);
+
+ let mod = new WebAssembly.Module(bin);
+ let ins = new WebAssembly.Instance(mod).exports;
+
+ assertErrorMessage(() => ins.x(), WebAssembly.RuntimeError, /dereferencing null pointer/);
+
+ ins.init();
+ assertEq(ins.x(), 0.5);
+ assertEq(ins.y(), 2.75);
+
+ ins.change();
+ assertEq(ins.x(), 3.5);
+ assertEq(ins.y(), 37.25);
+
+ ins.clear();
+ assertErrorMessage(() => ins.x(), WebAssembly.RuntimeError, /dereferencing null pointer/);
+}
+
+// Global value of type externref for initializer from a WebAssembly.Global,
+// just check that it works.
+{
+ let bin = wasmTextToBinary(
+ `(module
+ (import "" "g" (global $g externref))
+ (global $glob externref (global.get $g))
+ (func (export "get") (result externref)
+ (global.get $glob)))`);
+
+ let mod = new WebAssembly.Module(bin);
+ let obj = {zappa:37};
+ let g = new WebAssembly.Global({value: "externref"}, obj);
+ let ins = new WebAssembly.Instance(mod, {"":{g}}).exports;
+ assertEq(ins.get(), obj);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/ref-struct.js b/js/src/jit-test/tests/wasm/gc/ref-struct.js
new file mode 100644
index 0000000000..59376ac1e7
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/ref-struct.js
@@ -0,0 +1,328 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// We'll be running some binary-format tests shortly.
+
+load(libdir + "wasm-binary.js");
+
+const v2vSigSection = sigSection([{args:[], ret:VoidCode}]);
+
+function checkInvalid(body, errorMessage) {
+ assertErrorMessage(() => new WebAssembly.Module(
+ moduleWithSections([v2vSigSection,
+ declSection([0]),
+ bodySection([body])])),
+ WebAssembly.CompileError,
+ errorMessage);
+}
+
+// General test case for struct.new, struct.get, and struct.set: binary tree
+// manipulation.
+
+{
+ let bin = wasmTextToBinary(
+ `(module
+ (import "" "print_lp" (func $print_lp))
+ (import "" "print_rp" (func $print_rp))
+ (import "" "print_int" (func $print_int (param i32)))
+
+ (type $wabbit (struct
+ (field $x (mut i32))
+ (field $left (mut (ref null $wabbit)))
+ (field $right (mut (ref null $wabbit)))))
+
+ (global $g (mut (ref null $wabbit)) (ref.null $wabbit))
+
+ (global $k (mut i32) (i32.const 0))
+
+ (func (export "init") (param $n i32)
+ (global.set $g (call $make (local.get $n))))
+
+ (func $make (param $n i32) (result (ref null $wabbit))
+ (local $tmp i32)
+ (local.set $tmp (global.get $k))
+ (global.set $k (i32.add (local.get $tmp) (i32.const 1)))
+ (if (result (ref null $wabbit)) (i32.le_s (local.get $n) (i32.const 2))
+ (struct.new $wabbit (local.get $tmp) (ref.null $wabbit) (ref.null $wabbit))
+ (block (result (ref null $wabbit))
+ (struct.new $wabbit
+ (local.get $tmp)
+ (call $make (i32.sub (local.get $n) (i32.const 1)))
+ (call $make (i32.sub (local.get $n) (i32.const 2)))))))
+
+ (func (export "accumulate") (result i32)
+ (call $accum (global.get $g)))
+
+ (func $accum (param $w (ref null $wabbit)) (result i32)
+ (if (result i32) (ref.is_null (local.get $w))
+ (i32.const 0)
+ (i32.add (struct.get $wabbit 0 (local.get $w))
+ (i32.sub (call $accum (struct.get $wabbit 1 (local.get $w)))
+ (call $accum (struct.get $wabbit 2 (local.get $w)))))))
+
+ (func (export "reverse")
+ (call $reverse (global.get $g)))
+
+ (func $reverse (param $w (ref null $wabbit))
+ (local $tmp (ref null $wabbit))
+ (if (i32.eqz (ref.is_null (local.get $w)))
+ (block
+ (struct.set $wabbit 0 (local.get $w) (i32.mul (i32.const 2) (struct.get $wabbit 0 (local.get $w))))
+ (local.set $tmp (struct.get $wabbit 1 (local.get $w)))
+ (struct.set $wabbit 1 (local.get $w) (struct.get $wabbit 2 (local.get $w)))
+ (struct.set $wabbit 2 (local.get $w) (local.get $tmp))
+ (call $reverse (struct.get $wabbit 1 (local.get $w)))
+ (call $reverse (struct.get $wabbit 2 (local.get $w))))))
+
+ (func (export "print")
+ (call $pr (global.get $g)))
+
+ (func $pr (param $w (ref null $wabbit))
+ (if (i32.eqz (ref.is_null (local.get $w)))
+ (block
+ (call $print_lp)
+ (call $print_int (struct.get $wabbit 0 (local.get $w)))
+ (call $pr (struct.get $wabbit 1 (local.get $w)))
+ (call $pr (struct.get $wabbit 2 (local.get $w)))
+ (call $print_rp))))
+ )`);
+
+ let s = "";
+ function pr_int(k) { s += k + " "; }
+ function pr_lp() { s += "(" };
+ function pr_rp() { s += ")" }
+
+ let mod = new WebAssembly.Module(bin);
+ let ins = new WebAssembly.Instance(mod, {"":{print_int:pr_int,print_lp:pr_lp,print_rp:pr_rp}}).exports;
+
+ ins.init(6);
+ s = ""; ins.print(); assertEq(s, "(0 (1 (2 (3 (4 )(5 ))(6 ))(7 (8 )(9 )))(10 (11 (12 )(13 ))(14 )))");
+ assertEq(ins.accumulate(), -13);
+
+ ins.reverse();
+ s = ""; ins.print(); assertEq(s, "(0 (20 (28 )(22 (26 )(24 )))(2 (14 (18 )(16 ))(4 (12 )(6 (10 )(8 )))))");
+ assertEq(ins.accumulate(), 14);
+
+ for (let i=10; i < 22; i++ ) {
+ ins.init(i);
+ ins.reverse();
+ gc();
+ ins.reverse();
+ }
+}
+
+// Sanity check for struct.set: we /can/ store a (ref null T) into a (ref null U) field
+// with struct.set if T <: U; this should fall out of normal coercion but good
+// to test.
+
+wasmEvalText(
+ `(module
+ (type $node (struct (field (mut (ref null $node)))))
+ (type $nix (sub $node (struct (field (mut (ref null $node))) (field i32))))
+ (func $f (param $p (ref null $node)) (param $q (ref null $nix))
+ (struct.set $node 0 (local.get $p) (local.get $q))))`);
+
+// ref.cast: if the pointer is null we trap
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (type $node (struct (field i32)))
+ (type $node2 (struct (field i32) (field f32)))
+ (func $f (param $p (ref null $node)) (result (ref null $node2))
+ (ref.cast (ref $node2) (local.get $p)))
+ (func (export "test") (result eqref)
+ (call $f (ref.null $node))))`).exports.test(),
+ WebAssembly.RuntimeError,
+ /bad cast/,
+);
+
+// ref.cast null: if the pointer is null we do not trap
+
+wasmEvalText(
+ `(module
+ (type $node (struct (field i32)))
+ (type $node2 (struct (field i32) (field f32)))
+ (func $f (param $p (ref null $node)) (result (ref null $node2))
+ (ref.cast (ref null $node2) (local.get $p)))
+ (func (export "test") (result eqref)
+ (call $f (ref.null $node))))`).exports.test();
+
+// ref.cast: if the downcast succeeds we get the original pointer
+
+assertEq(wasmEvalText(
+ `(module
+ (type $node (struct (field i32)))
+ (type $node2 (sub $node (struct (field i32) (field f32))))
+ (func $f (param $p (ref null $node)) (result (ref null $node2))
+ (ref.cast (ref null $node2) (local.get $p)))
+ (func (export "test") (result i32)
+ (local $n (ref null $node))
+ (local.set $n (struct.new $node2 (i32.const 0) (f32.const 12)))
+ (ref.eq (call $f (local.get $n)) (local.get $n))))`).exports.test(),
+ 1);
+
+// And once more with mutable fields
+
+assertEq(wasmEvalText(
+ `(module
+ (type $node (struct (field (mut i32))))
+ (type $node2 (sub $node (struct (field (mut i32)) (field f32))))
+ (func $f (param $p (ref null $node)) (result (ref null $node2))
+ (ref.cast (ref null $node2) (local.get $p)))
+ (func (export "test") (result i32)
+ (local $n (ref null $node))
+ (local.set $n (struct.new $node2 (i32.const 0) (f32.const 12)))
+ (ref.eq (call $f (local.get $n)) (local.get $n))))`).exports.test(),
+ 1);
+
+// ref.cast: eqref -> struct when the eqref is the right struct;
+// special case since eqref requires unboxing
+
+assertEq(wasmEvalText(
+ `(module
+ (type $node (struct (field i32)))
+ (func $f (param $p eqref) (result (ref null $node))
+ (ref.cast (ref null $node) (local.get $p)))
+ (func (export "test") (result i32)
+ (local $n (ref null $node))
+ (local.set $n (struct.new $node (i32.const 0)))
+ (ref.eq (call $f (local.get $n)) (local.get $n))))`).exports.test(),
+ 1);
+
+// Can default initialize a struct which zero initializes
+
+{
+ let {makeA, makeB, makeC} = wasmEvalText(`
+ (module
+ (type $a (struct))
+ (type $b (struct (field i32) (field f32)))
+ (type $c (struct (field eqref)))
+
+ (func (export "makeA") (result eqref)
+ struct.new_default $a
+ )
+ (func (export "makeB") (result eqref)
+ struct.new_default $b
+ )
+ (func (export "makeC") (result eqref)
+ struct.new_default $c
+ )
+ )`).exports;
+ let a = makeA();
+
+ let b = makeB();
+ assertEq(b[0], 0);
+ assertEq(b[1], 0);
+
+ let c = makeC();
+ assertEq(c[0], null);
+}
+
+// struct.new_default: valid if all struct fields are defaultable
+
+wasmFailValidateText(`(module
+ (type $a (struct (field (ref $a))))
+ (func
+ struct.new_default $a
+ )
+)`, /defaultable/);
+
+wasmFailValidateText(`(module
+ (type $a (struct (field i32) (field i32) (field (ref $a))))
+ (func
+ struct.new_default $a
+ )
+)`, /defaultable/);
+
+// Negative tests
+
+// Attempting to mutate immutable field with struct.set
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (type $node (struct (field i32)))
+ (func $f (param $p (ref null $node))
+ (struct.set $node 0 (local.get $p) (i32.const 37))))`),
+ WebAssembly.CompileError,
+ /field is not mutable/);
+
+// Attempting to store incompatible value in mutable field with struct.set
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (type $node (struct (field (mut i32))))
+ (func $f (param $p (ref null $node))
+ (struct.set $node 0 (local.get $p) (f32.const 37))))`),
+ WebAssembly.CompileError,
+ /expression has type f32 but expected i32/);
+
+// Out-of-bounds reference for struct.get
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (type $node (struct (field i32)))
+ (func $f (param $p (ref null $node)) (result i32)
+ (struct.get $node 1 (local.get $p))))`),
+ WebAssembly.CompileError,
+ /field index out of range/);
+
+// Out-of-bounds reference for struct.set
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (type $node (struct (field (mut i32))))
+ (func $f (param $p (ref null $node))
+ (struct.set $node 1 (local.get $p) (i32.const 37))))`),
+ WebAssembly.CompileError,
+ /field index out of range/);
+
+// Base pointer is of unrelated type to stated type in struct.get
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (type $node (struct (field i32)))
+ (type $snort (struct (field f64)))
+ (func $f (param $p (ref null $snort)) (result i32)
+ (struct.get $node 0 (local.get $p))))`),
+ WebAssembly.CompileError,
+ /expression has type.*but expected.*/);
+
+// Base pointer is of unrelated type to stated type in struct.set
+
+assertErrorMessage(() => wasmEvalText(
+ `(module
+ (type $node (struct (field (mut i32))))
+ (type $snort (struct (field f64)))
+ (func $f (param $p (ref null $snort)) (result i32)
+ (struct.set $node 0 (local.get $p) (i32.const 0))))`),
+ WebAssembly.CompileError,
+ /expression has type.*but expected.*/);
+
+// Null pointer dereference in struct.get
+
+assertErrorMessage(function() {
+ let ins = wasmEvalText(
+ `(module
+ (type $node (struct (field i32)))
+ (func (export "test")
+ (drop (call $f (ref.null $node))))
+ (func $f (param $p (ref null $node)) (result i32)
+ (struct.get $node 0 (local.get $p))))`);
+ ins.exports.test();
+},
+ WebAssembly.RuntimeError,
+ /dereferencing null pointer/);
+
+// Null pointer dereference in struct.set
+
+assertErrorMessage(function() {
+ let ins = wasmEvalText(
+ `(module
+ (type $node (struct (field (mut i32))))
+ (func (export "test")
+ (call $f (ref.null $node)))
+ (func $f (param $p (ref null $node))
+ (struct.set $node 0 (local.get $p) (i32.const 0))))`);
+ ins.exports.test();
+},
+ WebAssembly.RuntimeError,
+ /dereferencing null pointer/);
diff --git a/js/src/jit-test/tests/wasm/gc/ref.js b/js/src/jit-test/tests/wasm/gc/ref.js
new file mode 100644
index 0000000000..30f5413029
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/ref.js
@@ -0,0 +1,202 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Parsing and resolving.
+
+var text = `(module
+ (rec
+ (type $cons (struct
+ (field $car i32)
+ (field $cdr (ref null $cons))))
+
+ (type $odd (struct
+ (field $odd.x i32)
+ (field $to_even (ref null $even))))
+
+ (type $even (struct
+ (field $even.x i32)
+ (field $to_odd (ref null $odd))))
+ )
+
+ ;; Use eqref on the API since struct types cannot be exposed outside the module yet.
+
+ (import "m" "f" (func $imp (param eqref) (result eqref)))
+
+ ;; The bodies do nothing since we have no operations on structs yet.
+ ;; Note none of these functions are exported, as they use Ref types in their signatures.
+
+ (func (param (ref null $cons)) (result i32)
+ (i32.const 0))
+
+ (func $cdr (param $p (ref null $cons)) (result (ref null $cons))
+ (local $l (ref null $cons))
+ ;; store null value of correct type
+ (local.set $l (ref.null $cons))
+ ;; store local of correct type
+ (local.set $l (local.get $p))
+ ;; store call result of correct type
+ (local.set $l (call $cdr (local.get $p)))
+ ;; TODO: eventually also a test with global.get
+ ;; blocks and if with result type
+ (block (result (ref null $cons))
+ (if (result (ref null $cons)) (i32.eqz (i32.const 0))
+ (unreachable)
+ (ref.null $cons))))
+
+ (func (param (ref null $even)) (result (ref null $odd))
+ (ref.null $odd))
+
+ (func (param (ref null $odd)) (result (ref null $even))
+ (ref.null $even))
+
+ (func (param (ref null $cons))
+ (call $cdr (local.get 0))
+ drop
+ (call $imp (local.get 0))
+ drop)
+
+ (func (param (ref null $cons))
+ (drop (ref.eq (local.get 0) (ref.null $cons)))
+ (drop (ref.eq (ref.null $cons) (local.get 0)))
+ (drop (ref.eq (local.get 0) (ref.null $cons)))
+ (drop (ref.eq (ref.null $cons) (local.get 0))))
+ )`;
+
+// Validation
+
+wasmValidateText(text);
+
+// ref.is_null should work on any reference type
+
+new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (type $s (struct))
+ (func $null (param (ref null $s)) (result i32)
+ (ref.is_null (local.get 0))))
+`))
+
+// Automatic upcast to eqref
+
+new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (type $s (struct (field i32)))
+ (func $f (param (ref null $s)) (call $g (local.get 0)))
+ (func $g (param eqref) (unreachable)))
+`));
+
+// Misc failure modes
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (func (param (ref null $odd)) (unreachable)))
+`),
+SyntaxError, /failed to find name/);
+
+// Ref type mismatch in parameter is allowed through the prefix rule
+// but not if the structs are incompatible.
+
+wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (type $t (struct (field i32)))
+ (func $f (param (ref null $s)) (unreachable))
+ (func $g (param (ref null $t)) (call $f (local.get 0)))
+)`);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (type $t (struct (field f32))) ;; Incompatible type
+ (func $f (param (ref null $s)) (unreachable))
+ (func $g (param (ref null $t)) (call $f (local.get 0)))
+)`),
+WebAssembly.CompileError, /expression has type \(ref null.*\) but expected \(ref null.*\)/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (type $t (struct (field (mut i32)))) ;; Incompatible mutability
+ (func $f (param (ref null $s)) (unreachable))
+ (func $g (param (ref null $t)) (call $f (local.get 0)))
+)`),
+WebAssembly.CompileError, /expression has type \(ref null.*\) but expected \(ref null.*\)/);
+
+// Ref type mismatch in assignment to local but the prefix rule allows
+// the assignment to succeed if the structs are the same.
+
+wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (type $t (struct (field i32)))
+ (func $f (param (ref null $s)) (local (ref null $t)) (local.set 1 (local.get 0))))
+`)
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (type $t (struct (field f32)))
+ (func $f (param (ref null $s)) (local (ref null $t)) (local.set 1 (local.get 0))))
+`),
+WebAssembly.CompileError, /expression has type \(ref null.*\) but expected \(ref null.*\)/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (type $t (struct (field (mut i32))))
+ (func $f (param (ref null $s)) (unreachable))
+ (func $g (param (ref null $t)) (call $f (local.get 0)))
+)`),
+WebAssembly.CompileError, /expression has type \(ref null.*\) but expected \(ref null.*\)/);
+
+// Ref type mismatch in return but the prefix rule allows the return
+// to succeed if the structs are the same.
+
+wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (type $t (struct (field i32)))
+ (func $f (param (ref null $s)) (result (ref null $t)) (local.get 0)))
+`);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (type $t (struct (field f32)))
+ (func $f (param (ref null $s)) (result (ref null $t)) (local.get 0)))
+`),
+WebAssembly.CompileError, /expression has type \(ref null.*\) but expected \(ref null.*\)/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (type $t (struct (field (mut i32))))
+ (func $f (param (ref null $s)) (result (ref null $t)) (local.get 0)))
+`),
+WebAssembly.CompileError, /expression has type \(ref null.*\) but expected \(ref null.*\)/);
+
+if (!wasmFunctionReferencesEnabled()) {
+ // Ref type can't reference a function type
+
+ assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $x (func (param i32)))
+ (func $f (param (ref null $x)) (unreachable)))
+`),
+ WebAssembly.CompileError, /does not reference a gc type/);
+
+ assertErrorMessage(() => wasmEvalText(`
+(module
+ (type (func (param i32)))
+ (func $f (param (ref null 0)) (unreachable)))
+`),
+ WebAssembly.CompileError, /does not reference a gc type/);
+}
+
+// No automatic downcast from eqref
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field i32)))
+ (func $f (param eqref) (call $g (local.get 0)))
+ (func $g (param (ref null $s)) (unreachable)))
+`),
+WebAssembly.CompileError, /expression has type eqref but expected \(ref null.*\)/);
diff --git a/js/src/jit-test/tests/wasm/gc/regress-1633355.js b/js/src/jit-test/tests/wasm/gc/regress-1633355.js
new file mode 100644
index 0000000000..97c611efd8
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/regress-1633355.js
@@ -0,0 +1,30 @@
+// |jit-test| skip-if: !wasmGcEnabled() || wasmCompileMode().includes("ion")
+
+load(libdir + "asserts.js");
+
+var g23 = newGlobal({newCompartment: true});
+g23.parent = this;
+g23.eval(`
+ var dbg = new Debugger(parent);
+ dbg.onEnterFrame = function(frame) {}
+`);
+let bin = wasmTextToBinary(`
+ (type $wabbit (struct
+ (field $x (mut i32))
+ (field $left (mut (ref null $wabbit)))
+ (field $right (mut (ref null $wabbit)))
+ ))
+ (global $g (mut (ref null $wabbit)) (ref.null $wabbit))
+ (func (export "init") (param $n i32)
+ (global.set $g (call $make (local.get $n)))
+ )
+ (func $make (param $n i32) (result (ref null $wabbit))
+ (local $tmp i32)
+ (struct.new $wabbit (local.get $tmp) (ref.null $wabbit) (ref.null $wabbit))
+ )
+`);
+let mod = new WebAssembly.Module(bin);
+let ins = new WebAssembly.Instance(mod).exports;
+
+// Debugger can handle non-exposable fields, like (ref T).
+ins.init(6)
diff --git a/js/src/jit-test/tests/wasm/gc/regress-1739330.js b/js/src/jit-test/tests/wasm/gc/regress-1739330.js
new file mode 100644
index 0000000000..99e519d2d9
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/regress-1739330.js
@@ -0,0 +1,23 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+try {
+ gczeal(4);
+ function a(b) {
+ binary = wasmTextToBinary(b)
+ c = new WebAssembly.Module(binary)
+ return new WebAssembly.Instance(c)
+ }
+ d = [];
+ let { newStruct } = a(`
+ (type $e (struct))
+ (func (export "newStruct")
+ (result eqref)
+ struct.new $e
+ )
+ `).exports
+ d.push(newStruct());
+ gczeal(14, 7);
+ throw d;
+} catch (d) {
+ assertEq(d instanceof Array, true);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/regress-1745391.js b/js/src/jit-test/tests/wasm/gc/regress-1745391.js
new file mode 100644
index 0000000000..5ea8989d3e
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/regress-1745391.js
@@ -0,0 +1,13 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+let { createDefault } = wasmEvalText(`
+ (module (type $a (array (mut i32)))
+ (func (export "createDefault") (param i32) (result eqref)
+ local.get 0
+ array.new_default $a
+ )
+ )
+`).exports;
+for (len = -1; len > -100; len--) {
+ assertErrorMessage(() => createDefault(len), WebAssembly.RuntimeError, /too many array elements/);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/regress-1754701.js b/js/src/jit-test/tests/wasm/gc/regress-1754701.js
new file mode 100644
index 0000000000..656aa5d625
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/regress-1754701.js
@@ -0,0 +1,30 @@
+// |jit-test| skip-if: !wasmGcEnabled() || !('oomTest' in this)
+
+let { testArray, testStructInline, testStructOutline } = wasmEvalText(`
+ (module
+ (type $array (array i32))
+ (type $structInline (struct))
+ (type $structOutline
+ (struct
+ ${`(field i32)`.repeat(100)}
+ )
+ )
+ (func (export "testArray")
+ (param i32)
+ (result eqref)
+ local.get 0
+ array.new_default $array
+ )
+ (func (export "testStructInline")
+ (result eqref)
+ struct.new_default $structInline
+ )
+ (func (export "testStructOutline")
+ (result eqref)
+ struct.new_default $structOutline
+ )
+ )
+`).exports
+oomTest(() => testArray(1));
+oomTest(() => testStructInline());
+oomTest(() => testStructOutline());
diff --git a/js/src/jit-test/tests/wasm/gc/regress-1830975.js b/js/src/jit-test/tests/wasm/gc/regress-1830975.js
new file mode 100644
index 0000000000..ae6cba6bdd
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/regress-1830975.js
@@ -0,0 +1,17 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Hello, future wasm dev. This test will start failing when you add casting
+// for func and extern types. When that happens, remove the assertErrorMessage.
+
+assertErrorMessage(() => {
+ const { test } = wasmEvalText(`
+ (module
+ (type $f (func (result i32)))
+ (func (export "test") (type $f)
+ ref.null $f
+ ref.test (ref null $f)
+ )
+ )
+ `).exports;
+ assertEq(test(), 1);
+}, WebAssembly.CompileError, /ref.test only supports the any hierarchy/);
diff --git a/js/src/jit-test/tests/wasm/gc/regress-outline-repr.js b/js/src/jit-test/tests/wasm/gc/regress-outline-repr.js
new file mode 100644
index 0000000000..f49b6480df
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/regress-outline-repr.js
@@ -0,0 +1,151 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// White-box test for bug 1617908. The significance of this test is that the
+// type $S is too large to fit in an inline TypedObject, and the write barrier
+// logic must take this into account when storing the (ref $S2) into the last
+// field of the object.
+
+const wat = `
+(module
+ (type $S2 (struct))
+ (type $S (sub $S2
+ (struct
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut eqref)))))
+
+ (func $main
+ (struct.set $S 18
+ (struct.new $S
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (ref.null eq))
+ (struct.new $S2)))
+ (start $main))
+`
+wasmEvalText(wat);
+
+// Test subtyping across outline/inline representations works
+
+wasmEvalText(`
+(module
+ (type $inline
+ (struct
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ ))
+ (type $outline (sub $inline
+ (struct
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64))
+ (field (mut i64)))))
+
+ (func $main
+ (local $outline (ref null $outline))
+ (local $inline (ref null $inline))
+
+ (; create an outline object and acquire multiple views to it ;)
+ (struct.new $outline
+ (i64.const 0xFF)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0)
+ (i64.const 0))
+ local.tee $outline
+ local.set $inline
+
+ (; clobber the object header ;)
+ (struct.set $inline 0
+ local.get $inline
+ i64.const 0
+ )
+ (struct.set $inline 1
+ local.get $inline
+ i64.const 0
+ )
+ (struct.set $inline 2
+ local.get $inline
+ i64.const 0
+ )
+ (struct.set $inline 3
+ local.get $inline
+ i64.const 0
+ )
+ (struct.set $inline 4
+ local.get $inline
+ i64.const 0
+ )
+
+ (; try to read a field ;)
+ (struct.get $outline 0
+ local.get $outline
+ )
+ drop
+ )
+ (start $main))
+`);
diff --git a/js/src/jit-test/tests/wasm/gc/signal-null-check.js b/js/src/jit-test/tests/wasm/gc/signal-null-check.js
new file mode 100644
index 0000000000..a56526031d
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/signal-null-check.js
@@ -0,0 +1,148 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+//
+// Checks if null dereference works.
+
+for (let [fieldType, signedness, defaultValue] of [
+ ['i8', '_u', 'i32.const 42'],
+ ['i8', '_s', 'i32.const -42'],
+ ['i16', '_u', 'i32.const 1'],
+ ['i16', '_s', 'i32.const -1'],
+ ['i32', '', 'i32.const 3'],
+ ['i64', '', 'i64.const -77777777777'],
+ ['f32', '', 'f32.const 1.4'],
+ ['f64', '', 'f64.const 3.14'],
+ ['externref', '', 'ref.null extern'],
+].concat(
+ wasmSimdEnabled() ? [['v128', '', 'v128.const i32x4 1 2 -3 4']] : []
+)) {
+ // Check struct.get from null struct of a field of fieldType works
+ // Check struct.set similarly
+ testStructGetSet(fieldType, signedness, defaultValue, 4);
+ // - Also when the field is in the outline_ data area?
+ testStructGetSet(fieldType, signedness, defaultValue, 1000);
+ // Check array.get similarly
+ // Check array.set from null array with element of type fieldType
+ testArrayGetSet(fieldType, signedness, defaultValue, 100);
+}
+
+function testStructGetSet(fieldType, signedness, defaultValue, numFields) {
+ const ins = wasmEvalText(`
+(module
+ (type $t (struct ${
+ Array(numFields).fill("(field (mut " + fieldType + "))").join(' ')
+ }))
+ (global $g (mut (ref null $t)) (ref.null $t))
+ (func (export "init-null")
+ ref.null $t
+ global.set $g
+ )
+ (func (export "init-non-null")
+ ${ Array(numFields).fill(defaultValue).join('\n ') }
+ struct.new $t
+ global.set $g
+ )
+ (func (export "test_get_first")
+ global.get $g
+ struct.get${signedness} $t 0
+ drop
+ )
+ (func (export "test_set_first")
+ global.get $g
+ ${defaultValue}
+ struct.set $t 0
+ )
+ (func (export "test_get_mid")
+ global.get $g
+ struct.get${signedness} $t ${numFields >> 1}
+ drop
+ )
+ (func (export "test_set_mid")
+ global.get $g
+ ${defaultValue}
+ struct.set $t ${numFields >> 1}
+ )
+ (func (export "test_get_last")
+ global.get $g
+ struct.get${signedness} $t ${numFields - 1}
+ drop
+ )
+ (func (export "test_set_last")
+ global.get $g
+ ${defaultValue}
+ struct.set $t ${numFields - 1}
+ )
+)`);
+ ins.exports["init-non-null"]();
+ ins.exports["test_get_first"]();
+ ins.exports["test_get_mid"]();
+ ins.exports["test_get_last"]();
+ ins.exports["test_set_first"]();
+ ins.exports["test_set_mid"]();
+ ins.exports["test_set_last"]();
+
+ ins.exports["init-null"]();
+ assertDerefenceNull(() => ins.exports["test_get_first"]());
+ assertDerefenceNull(() => ins.exports["test_get_mid"]());
+ assertDerefenceNull(() => ins.exports["test_get_last"]());
+ assertDerefenceNull(() => ins.exports["test_set_first"]());
+ assertDerefenceNull(() => ins.exports["test_set_mid"]());
+ assertDerefenceNull(() => ins.exports["test_set_last"]());
+
+ ins.exports["init-non-null"]();
+ ins.exports["test_set_last"]();
+ ins.exports["test_get_first"]();
+}
+
+function testArrayGetSet(fieldType, signedness, defaultValue, numItems) {
+ const ins = wasmEvalText(`
+(module
+ (type $t (array (mut ${fieldType})))
+ (global $g (mut (ref null $t)) (ref.null $t))
+ (func (export "init-null")
+ ref.null $t
+ global.set $g
+ )
+ (func (export "init-non-null")
+ ${defaultValue}
+ i32.const ${numItems}
+ array.new $t
+ global.set $g
+ )
+ (func (export "test_get") (param i32)
+ global.get $g
+ local.get 0
+ array.get${signedness} $t
+ drop
+ )
+ (func (export "test_set") (param i32)
+ global.get $g
+ local.get 0
+ ${defaultValue}
+ array.set $t
+ )
+)`);
+ ins.exports["init-non-null"]();
+ ins.exports["test_get"](0);
+ ins.exports["test_get"](numItems >> 1);
+ ins.exports["test_get"](numItems - 1);
+ ins.exports["test_set"](0);
+ ins.exports["test_set"](numItems >> 1);
+ ins.exports["test_set"](numItems - 1);
+
+ ins.exports["init-null"]();
+ assertDerefenceNull(() => ins.exports["test_get"](0));
+ assertDerefenceNull(() => ins.exports["test_get"](numItems >> 1));
+ assertDerefenceNull(() => ins.exports["test_get"](numItems - 1));
+ assertDerefenceNull(() => ins.exports["test_set"](0));
+ assertDerefenceNull(() => ins.exports["test_set"](numItems >> 1));
+ assertDerefenceNull(() => ins.exports["test_set"](numItems - 1));
+
+ ins.exports["init-non-null"]();
+ ins.exports["test_set"](3);
+ ins.exports["test_get"](0);
+}
+
+
+function assertDerefenceNull(fun) {
+ assertErrorMessage(fun, WebAssembly.RuntimeError, /dereferencing null pointer/);
+}
diff --git a/js/src/jit-test/tests/wasm/gc/structs.js b/js/src/jit-test/tests/wasm/gc/structs.js
new file mode 100644
index 0000000000..388541c4ee
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/structs.js
@@ -0,0 +1,724 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// This tests a bunch of wasm struct stuff, but not i8 or i16 fields.
+// See structs2.js for i8/i16 field tests.
+
+var conf = getBuildConfiguration();
+
+var bin = wasmTextToBinary(
+ `(module
+ (func $x1 (import "m" "x1") (type $f1))
+ (func $x2 (import "m" "x2") (type $f2))
+
+ (table 2 funcref)
+ (elem (i32.const 0) $doit $doitagain)
+
+ ;; Type array has a mix of types
+
+ (type $f1 (func (param i32) (result i32)))
+
+ (type $point (struct
+ (field $point_x i32)
+ (field $point_y i32)))
+
+ (type $f2 (func (param f64) (result f64)))
+
+ (type $int_node (struct
+ (field $intbox_val (mut i32))
+ (field $intbox_next (mut externref))))
+
+ ;; Test all the types.
+
+ (type $omni (struct
+ (field $omni_i32 i32)
+ (field $omni_i32m (mut i32))
+ (field $omni_i64 i64)
+ (field $omni_i64m (mut i64))
+ (field $omni_f32 f32)
+ (field $omni_f32m (mut f32))
+ (field $omni_f64 f64)
+ (field $omni_f64m (mut f64))
+ (field $omni_externref externref)
+ (field $omni_externrefm (mut externref))))
+
+ ;; Various ways to reference a type in the middle of the
+ ;; type array, make sure we get the right one
+
+ (func (export "hello") (param f64) (param i32) (result f64)
+ (call_indirect (type $f2) (local.get 0) (local.get 1)))
+
+ (func $doit (param f64) (result f64)
+ (f64.sqrt (local.get 0)))
+
+ (func $doitagain (param f64) (result f64)
+ (f64.mul (local.get 0) (local.get 0)))
+
+ (func (export "x1") (param i32) (result i32)
+ (call $x1 (local.get 0)))
+
+ (func (export "x2") (param f64) (result f64)
+ (call $x2 (local.get 0)))
+
+ ;; Useful for testing to ensure that the type is not type #0 here.
+
+ (func (export "mk_point") (result eqref)
+ (struct.new $point (i32.const 37) (i32.const 42)))
+
+ (func (export "mk_int_node") (param i32) (param externref) (result eqref)
+ (struct.new $int_node (local.get 0) (local.get 1)))
+
+ ;; Too big to fit in an InlineTypedObject.
+
+ (type $bigger (struct
+ (field $a i32)
+ (field $b i32)
+ (field $c i32)
+ (field $d i32)
+ (field $e i32)
+ (field $f i32)
+ (field $g i32)
+ (field $h i32)
+ (field $i i32)
+ (field $j i32)
+ (field $k i32)
+ (field $l i32)
+ (field $m i32)
+ (field $n i32)
+ (field $o i32)
+ (field $p i32)
+ (field $q i32)
+ (field $r i32)
+ (field $s i32)
+ (field $t i32)
+ (field $u i32)
+ (field $v i32)
+ (field $w i32)
+ (field $x i32)
+ (field $y i32)
+ (field $z i32)
+ (field $aa i32)
+ (field $ab i32)
+ (field $ac i32)
+ (field $ad i32)
+ (field $ae i32)
+ (field $af i32)
+ (field $ag i32)
+ (field $ah i32)
+ (field $ai i32)
+ (field $aj i32)
+ (field $ak i32)
+ (field $al i32)
+ (field $am i32)
+ (field $an i32)
+ (field $ao i32)
+ (field $ap i32)
+ (field $aq i32)
+ (field $ar i32)
+ (field $as i32)
+ (field $at i32)
+ (field $au i32)
+ (field $av i32)
+ (field $aw i32)
+ (field $ax i32)
+ (field $ay i32)
+ (field $az i32)))
+
+ (func (export "mk_bigger") (result eqref)
+ (struct.new $bigger
+ (i32.const 0)
+ (i32.const 1)
+ (i32.const 2)
+ (i32.const 3)
+ (i32.const 4)
+ (i32.const 5)
+ (i32.const 6)
+ (i32.const 7)
+ (i32.const 8)
+ (i32.const 9)
+ (i32.const 10)
+ (i32.const 11)
+ (i32.const 12)
+ (i32.const 13)
+ (i32.const 14)
+ (i32.const 15)
+ (i32.const 16)
+ (i32.const 17)
+ (i32.const 18)
+ (i32.const 19)
+ (i32.const 20)
+ (i32.const 21)
+ (i32.const 22)
+ (i32.const 23)
+ (i32.const 24)
+ (i32.const 25)
+ (i32.const 26)
+ (i32.const 27)
+ (i32.const 28)
+ (i32.const 29)
+ (i32.const 30)
+ (i32.const 31)
+ (i32.const 32)
+ (i32.const 33)
+ (i32.const 34)
+ (i32.const 35)
+ (i32.const 36)
+ (i32.const 37)
+ (i32.const 38)
+ (i32.const 39)
+ (i32.const 40)
+ (i32.const 41)
+ (i32.const 42)
+ (i32.const 43)
+ (i32.const 44)
+ (i32.const 45)
+ (i32.const 46)
+ (i32.const 47)
+ (i32.const 48)
+ (i32.const 49)
+ (i32.const 50)
+ (i32.const 51)))
+
+ (type $withfloats (struct
+ (field $f1 f32)
+ (field $f2 f64)
+ (field $f3 externref)
+ (field $f4 f32)
+ (field $f5 i32)))
+
+ (func (export "mk_withfloats")
+ (param f32) (param f64) (param externref) (param f32) (param i32)
+ (result eqref)
+ (struct.new $withfloats (local.get 0) (local.get 1) (local.get 2) (local.get 3) (local.get 4)))
+
+ )`)
+
+var mod = new WebAssembly.Module(bin);
+var ins = new WebAssembly.Instance(mod, {m:{x1(x){ return x*3 }, x2(x){ return Math.PI }}}).exports;
+
+assertEq(ins.hello(4.0, 0), 2.0)
+assertEq(ins.hello(4.0, 1), 16.0)
+
+assertEq(ins.x1(12), 36)
+assertEq(ins.x2(8), Math.PI)
+
+var point = ins.mk_point();
+assertEq(0 in point, true);
+assertEq(1 in point, true);
+assertEq(2 in point, false);
+assertEq(point[0], 37);
+assertEq(point[1], 42);
+
+var int_node = ins.mk_int_node(78, point);
+assertEq(int_node[0], 78);
+assertEq(int_node[1], point);
+
+var bigger = ins.mk_bigger();
+for ( let i=0; i < 52; i++ )
+ assertEq(bigger[i], i);
+
+assertEq(bigger[-1], undefined);
+assertEq(bigger[52], undefined);
+
+var withfloats = ins.mk_withfloats(1/3, Math.PI, bigger, 5/6, 0x1337);
+assertEq(withfloats[0], Math.fround(1/3));
+assertEq(withfloats[1], Math.PI);
+assertEq(withfloats[2], bigger);
+assertEq(withfloats[3], Math.fround(5/6));
+assertEq(withfloats[4], 0x1337);
+
+// A simple stress test
+
+var stress = wasmTextToBinary(
+ `(module
+ (type $node (struct (field i32) (field (ref null $node))))
+ (func (export "iota1") (param $n i32) (result eqref)
+ (local $list (ref null $node))
+ (block $exit
+ (loop $loop
+ (br_if $exit (i32.eqz (local.get $n)))
+ (local.set $list (struct.new $node (local.get $n) (local.get $list)))
+ (local.set $n (i32.sub (local.get $n) (i32.const 1)))
+ (br $loop)))
+ (local.get $list)))`);
+var stressIns = new WebAssembly.Instance(new WebAssembly.Module(stress)).exports;
+var stressLevel = conf.x64 && !conf.tsan && !conf.asan && !conf.valgrind ? 100000 : 1000;
+var the_list = stressIns.iota1(stressLevel);
+for (let i=1; i <= stressLevel; i++) {
+ assertEq(the_list[0], i);
+ the_list = the_list[1];
+}
+assertEq(the_list, null);
+
+// Fields and their exposure in JS. We can't export types yet so hide them
+// inside the module with globals.
+
+// i64 fields.
+
+{
+ let txt =
+ `(module
+ (type $big (struct
+ (field (mut i32))
+ (field (mut i64))
+ (field (mut i32))))
+
+ (func (export "set") (param eqref)
+ (local (ref null $big))
+ (local.set 1 (ref.cast (ref null $big) (local.get 0)))
+ (struct.set $big 1 (local.get 1) (i64.const 0x3333333376544567)))
+
+ (func (export "set2") (param $p eqref)
+ (struct.set $big 1
+ (ref.cast (ref null $big) (local.get $p))
+ (i64.const 0x3141592653589793)))
+
+ (func (export "low") (param $p eqref) (result i32)
+ (i32.wrap/i64 (struct.get $big 1 (ref.cast (ref null $big) (local.get $p)))))
+
+ (func (export "high") (param $p eqref) (result i32)
+ (i32.wrap/i64 (i64.shr_u
+ (struct.get $big 1 (ref.cast (ref null $big) (local.get $p)))
+ (i64.const 32))))
+
+ (func (export "mk") (result eqref)
+ (struct.new $big (i32.const 0x7aaaaaaa) (i64.const 0x4201020337) (i32.const 0x6bbbbbbb)))
+
+ )`;
+
+ let ins = wasmEvalText(txt).exports;
+
+ let v = ins.mk();
+ assertEq(typeof v, "object");
+ assertEq(v[0], 0x7aaaaaaa);
+ assertEq(v[1], 0x4201020337n);
+ assertEq(ins.low(v), 0x01020337);
+ assertEq(ins.high(v), 0x42);
+ assertEq(v[2], 0x6bbbbbbb);
+
+ ins.set(v);
+ assertEq(v[0], 0x7aaaaaaa);
+ assertEq(v[1], 0x3333333376544567n);
+ assertEq(v[2], 0x6bbbbbbb);
+
+ ins.set2(v);
+ assertEq(v[1], 0x3141592653589793n);
+ assertEq(ins.low(v), 0x53589793);
+ assertEq(ins.high(v), 0x31415926)
+}
+
+{
+ let txt =
+ `(module
+ (type $big (struct
+ (field (mut i32))
+ (field (mut i64))
+ (field (mut i32))))
+
+ (global $g (mut (ref null $big)) (ref.null $big))
+
+ (func (export "make") (result eqref)
+ (global.set $g
+ (struct.new $big (i32.const 0x7aaaaaaa) (i64.const 0x4201020337) (i32.const 0x6bbbbbbb)))
+ (global.get $g))
+
+ (func (export "update0") (param $x i32)
+ (struct.set $big 0 (global.get $g) (local.get $x)))
+
+ (func (export "get0") (result i32)
+ (struct.get $big 0 (global.get $g)))
+
+ (func (export "update1") (param $hi i32) (param $lo i32)
+ (struct.set $big 1 (global.get $g)
+ (i64.or
+ (i64.shl (i64.extend_u/i32 (local.get $hi)) (i64.const 32))
+ (i64.extend_u/i32 (local.get $lo)))))
+
+ (func (export "get1_low") (result i32)
+ (i32.wrap/i64 (struct.get $big 1 (global.get $g))))
+
+ (func (export "get1_high") (result i32)
+ (i32.wrap/i64
+ (i64.shr_u (struct.get $big 1 (global.get $g)) (i64.const 32))))
+
+ (func (export "update2") (param $x i32)
+ (struct.set $big 2 (global.get $g) (local.get $x)))
+
+ (func (export "get2") (result i32)
+ (struct.get $big 2 (global.get $g)))
+
+ )`;
+
+ let ins = wasmEvalText(txt).exports;
+
+ let v = ins.make();
+ assertEq(v[0], 0x7aaaaaaa);
+ assertEq(v[1], 0x4201020337n);
+ assertEq(v[2], 0x6bbbbbbb);
+
+ ins.update0(0x45367101);
+ assertEq(v[0], 0x45367101);
+ assertEq(ins.get0(), 0x45367101);
+ assertEq(v[1], 0x4201020337n);
+ assertEq(v[2], 0x6bbbbbbb);
+
+ ins.update2(0x62345123);
+ assertEq(v[0], 0x45367101);
+ assertEq(v[1], 0x4201020337n);
+ assertEq(ins.get2(), 0x62345123);
+ assertEq(v[2], 0x62345123);
+
+ ins.update1(0x77777777, 0x22222222);
+ assertEq(v[0], 0x45367101);
+ assertEq(ins.get1_low(), 0x22222222);
+ assertEq(ins.get1_high(), 0x77777777);
+ assertEq(v[1], 0x7777777722222222n);
+ assertEq(v[2], 0x62345123);
+}
+
+
+var bin = wasmTextToBinary(
+ `(module
+ (type $cons (struct (field i32) (field (ref null $cons))))
+
+ (global $g (mut (ref null $cons)) (ref.null $cons))
+
+ (func (export "push") (param i32)
+ (global.set $g (struct.new $cons (local.get 0) (global.get $g))))
+
+ (func (export "top") (result i32)
+ (struct.get $cons 0 (global.get $g)))
+
+ (func (export "pop")
+ (global.set $g (struct.get $cons 1 (global.get $g))))
+
+ (func (export "is_empty") (result i32)
+ (ref.is_null (global.get $g)))
+
+ )`);
+
+var mod = new WebAssembly.Module(bin);
+var ins = new WebAssembly.Instance(mod).exports;
+ins.push(37);
+ins.push(42);
+ins.push(86);
+assertEq(ins.top(), 86);
+ins.pop();
+assertEq(ins.top(), 42);
+ins.pop();
+assertEq(ins.top(), 37);
+ins.pop();
+assertEq(ins.is_empty(), 1);
+assertErrorMessage(() => ins.pop(),
+ WebAssembly.RuntimeError,
+ /dereferencing null pointer/);
+
+// Check that a wrapped object cannot be passed as an eqref even if the wrapper
+// points to the right type. This is a temporary restriction, until we're able
+// to avoid dealing with wrappers inside the engine.
+
+{
+ var ins = wasmEvalText(
+ `(module
+ (type $Node (struct (field i32)))
+ (func (export "mk") (result eqref)
+ (struct.new $Node (i32.const 37)))
+ (func (export "f") (param $n eqref) (result eqref)
+ (ref.cast (ref null $Node) (local.get $n))))`).exports;
+ var n = ins.mk();
+ assertEq(ins.f(n), n);
+ assertErrorMessage(() => ins.f(wrapWithProto(n, {})), TypeError, /can only pass a WebAssembly GC object/);
+}
+
+// Field names.
+
+// Test that names map to the right fields.
+
+{
+ let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+ `(module
+ (type $s (struct
+ (field $x i32)
+ (field $y i32)))
+
+ (func $f (param $p (ref null $s)) (result i32)
+ (struct.get $s $x (local.get $p)))
+
+ (func $g (param $p (ref null $s)) (result i32)
+ (struct.get $s $y (local.get $p)))
+
+ (func (export "testf") (param $n i32) (result i32)
+ (call $f (struct.new $s (local.get $n) (i32.mul (local.get $n) (i32.const 2)))))
+
+ (func (export "testg") (param $n i32) (result i32)
+ (call $g (struct.new $s (local.get $n) (i32.mul (local.get $n) (i32.const 2)))))
+
+ )`))).exports;
+
+ assertEq(ins.testf(10), 10);
+ assertEq(ins.testg(10), 20);
+}
+
+// Test that field names must be unique in the module.
+
+assertErrorMessage(() => wasmTextToBinary(
+ `(module
+ (type $s (struct (field $x i32)))
+ (type $t (struct (field $x i32)))
+ )`),
+ SyntaxError,
+ /duplicate identifier for field/);
+
+// negative tests
+
+// Wrong type passed as initializer
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (type $r (struct (field i32)))
+ (func $f (param f64) (result eqref)
+ (struct.new $r (local.get 0)))
+)`)),
+WebAssembly.CompileError, /type mismatch/);
+
+// Too few values passed for initializer
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (type $r (struct (field i32) (field i32)))
+ (func $f (result eqref)
+ (struct.new $r (i32.const 0)))
+)`)),
+WebAssembly.CompileError, /popping value from empty stack/);
+
+// Too many values passed for initializer, sort of
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (type $r (struct (field i32) (field i32)))
+ (func $f (result eqref)
+ (i32.const 0)
+ (i32.const 1)
+ (i32.const 2)
+ struct.new $r)
+)`)),
+WebAssembly.CompileError, /unused values/);
+
+// Not referencing a structure type
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+ (type (func (param i32) (result i32)))
+ (func $f (result eqref)
+ (struct.new 0))
+)`)),
+WebAssembly.CompileError, /not a struct type/);
+
+// Nominal type equivalence for structs, but the prefix rule allows this
+// conversion to succeed.
+
+wasmEvalText(`
+ (module
+ (type $p (struct (field i32)))
+ (type $q (struct (field i32)))
+ (func $f (result (ref null $p))
+ (struct.new $q (i32.const 0))))
+`);
+
+// The field name is optional, so this should work.
+
+wasmEvalText(`
+(module
+ (type $s (struct (field i32))))
+`)
+
+// Empty structs are OK.
+
+wasmEvalText(`
+(module
+ (type $s (struct)))
+`)
+
+// Multiply defined structures.
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field $x i32)))
+ (type $s (struct (field $y i32))))
+`),
+SyntaxError, /duplicate type identifier/);
+
+// Bogus type definition syntax.
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s))
+`),
+SyntaxError, /wasm text error/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (field $x i32)))
+`),
+SyntaxError, /expected one of: `func`, `struct`, `array`/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (field $x i31))))
+`),
+SyntaxError, /wasm text error/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct (fjeld $x i32))))
+`),
+SyntaxError, /wasm text error/);
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct abracadabra)))
+`),
+SyntaxError, /wasm text error/);
+
+// Function should not reference struct type: syntactic test
+
+assertErrorMessage(() => wasmEvalText(`
+(module
+ (type $s (struct))
+ (type $f (func (param i32) (result i32)))
+ (func (type 0) (param i32) (result i32) (unreachable)))
+`),
+WebAssembly.CompileError, /signature index references non-signature/);
+
+// Can't set immutable fields from JS
+
+{
+ let ins = wasmEvalText(
+ `(module
+ (type $s (struct
+ (field i32)
+ (field (mut i64))))
+ (func (export "make") (result eqref)
+ (struct.new $s (i32.const 37) (i64.const 42))))`).exports;
+ let v = ins.make();
+ assertErrorMessage(() => v[0] = 12,
+ Error,
+ /setting immutable field/);
+ assertErrorMessage(() => v[1] = 12,
+ Error,
+ /setting immutable field/);
+}
+
+// Function should not reference struct type: binary test
+
+var bad = new Uint8Array([0x00, 0x61, 0x73, 0x6d,
+ 0x01, 0x00, 0x00, 0x00,
+
+ 0x01, // Type section
+ 0x03, // Section size
+ 0x01, // One type
+ 0x5f, // Struct
+ 0x00, // Zero fields
+
+ 0x03, // Function section
+ 0x02, // Section size
+ 0x01, // One function
+ 0x00, // Type of function
+
+ 0x0a, // Code section
+ 0x05, // Section size
+ 0x01, // One body
+ 0x03, // Body size
+ 0x00, // Zero locals
+ 0x00, // UNREACHABLE
+ 0x0b]); // END
+
+assertErrorMessage(() => new WebAssembly.Module(bad),
+ WebAssembly.CompileError, /signature index references non-signature/);
+
+// Exercise alias-analysis code for struct access
+{
+ let txt =
+ `(module
+ (type $meh (struct))
+ (type $hasOOL (struct
+ ;; In-line storage
+ (field i64) (field i64)
+ (field $ILnonref (mut i64)) (field $ILref (mut eqref))
+ (field i64) (field i64) (field i64) (field i64)
+ (field i64) (field i64) (field i64) (field i64)
+ (field i64) (field i64) (field i64) (field i64)
+ ;; Out-of-line storage (or maybe it starts earlier, but
+ ;; definitely not after this point).
+ (field $OOLnonref (mut i64)) (field $OOLref (mut eqref)))
+ )
+ (func (export "create") (result eqref)
+ (struct.new $hasOOL
+ (i64.const 1) (i64.const 2)
+ (i64.const 9876) (ref.null $meh)
+ (i64.const 3) (i64.const 4) (i64.const 5) (i64.const 6)
+ (i64.const 7) (i64.const 8) (i64.const 9) (i64.const 10)
+ (i64.const 11) (i64.const 12) (i64.const 13) (i64.const 14)
+ (i64.const 4321) (ref.null $meh))
+ )
+ ;; Write to an OOL field, then an IL field, then to an OOL field, so
+ ;; that we can at least check (from inspection of the optimised MIR)
+ ;; that the GVN+alias analysis causes the OOL block pointer not to be
+ ;; reloaded for the second OOL write. First for non-ref fields ..
+ (func (export "threeSetsNonReffy") (param eqref)
+ (local (ref $hasOOL))
+ (local.set 1 (ref.as_non_null (ref.cast (ref null $hasOOL) (local.get 0))))
+ (struct.set $hasOOL 16 (local.get 1) (i64.const 1337)) ;; set $OOLnonref
+ (struct.set $hasOOL 2 (local.get 1) (i64.const 7331)) ;; set $ILnonref
+ (struct.set $hasOOL 16 (local.get 1) (i64.const 9009)) ;; set $OOLnonref
+ )
+ ;; and the same for ref fields.
+ (func (export "threeSetsReffy") (param eqref)
+ (local (ref $hasOOL))
+ (local.set 1 (ref.as_non_null (ref.cast (ref null $hasOOL) (local.get 0))))
+ (struct.set $hasOOL 17 (local.get 1) (ref.null $meh)) ;; set $OOLref
+ (struct.set $hasOOL 3 (local.get 1) (ref.null $meh)) ;; set $ILref
+ (struct.set $hasOOL 17 (local.get 1) (ref.null $meh)) ;; set $OOLref
+ )
+ )`;
+ let exports = wasmEvalText(txt).exports;
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Checks for requests to create structs with more than MaxStructFields, where
+// MaxStructFields == 1000.
+
+function structNewOfManyFields(numFields) {
+ let defString = "(type $s (struct ";
+ for (i = 0; i < numFields; i++) {
+ defString += "(field i32) ";
+ }
+ defString += "))";
+
+ let insnString = "(struct.new $s ";
+ for (i = 0; i < numFields; i++) {
+ insnString += "(i32.const 1337) ";
+ }
+ insnString += ")";
+
+ return "(module " +
+ defString +
+ " (func (export \"create\") (result eqref) " +
+ insnString +
+ "))";
+}
+
+{
+ // 2000 fields is allowable
+ let exports = wasmEvalText(structNewOfManyFields(2000)).exports;
+ let s = exports.create();
+ assertEq(s, s);
+}
+{
+ // but 2001 is not
+ assertErrorMessage(() => wasmEvalText(structNewOfManyFields(2001)),
+ WebAssembly.CompileError,
+ /too many fields in struct/);
+}
+
+// FIXME: also check struct.new_default, once it is available in both compilers.
diff --git a/js/src/jit-test/tests/wasm/gc/structs2.js b/js/src/jit-test/tests/wasm/gc/structs2.js
new file mode 100644
index 0000000000..8c5b94db11
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/structs2.js
@@ -0,0 +1,243 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// This tests 8- and 16-bit field accesses for structs.
+
+// Check that struct.new writes for 8-bit fields do not overwrite subsequent
+// data. Because the writes happen forwards in the address space, the only
+// way I could think to do this is to force an 8-bit field to occupy the last
+// byte of the OOL malloc'd block, and then hope that ASan runs in automation
+// will pick up any overrun. I think it's impossible to test this from inside
+// the JS+wasm universe. Hence the test is pretty pointless from a purely
+// JS+wasm interpretation.
+{
+ let txt =
+ `(module
+ (type $hasOOL (struct
+ ;; In-line storage; 16 fields that preserve 16-alignment
+ (field i64) (field i64) (field i64) (field i64) ;; 32
+ (field i64) (field i64) (field i64) (field i64) ;; 64
+ (field i64) (field i64) (field i64) (field i64) ;; 96
+ (field i64) (field i64) (field i64) (field i64) ;; 128
+ ;; Out-of-line storage (or maybe it starts earlier, but
+ ;; definitely not after this point). 16 bytes on the
+ ;; basis that StructLayout::close will round the requested
+ ;; block size up to at max the next 16 byte boundary.
+ ;; The goal is that the last (field i8) is right at the
+ ;; end of the resulting malloc'd block, so that, if the
+ ;; struct.new initialisation code mistakenly initialises
+ ;; that field with a write larger than 8 bits, then we'll
+ ;; have a write off the end of the malloc'd block, which
+ ;; ASan automation runs should detect.
+ (field i8) (field i8) (field i8) (field i8)
+ (field i8) (field i8) (field i8) (field i8)
+ (field i8) (field i8) (field i8) (field i8)
+ (field i8) (field i8) (field i8) (field i8))
+ )
+ (func (export "build8")
+ (param $filler i64) (param $interesting i32) (result eqref)
+ (struct.new $hasOOL
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ )
+ )
+ )`;
+ let exports = wasmEvalText(txt).exports;
+ let obj8 = exports.build8(0x1234n, 0x5678);
+ // The above call should trigger OOB writes if the struct.new field
+ // writes are too large, but those will only be visible if we're running
+ // on ASan or Valgrind. In any case, add a fake data dependency below, so
+ // that the construction of the object can't (so easily) be optimised away.
+ assertEq(obj8[0] + BigInt(obj8[31]), 0x12ACn); // == 0x1234 + 0x78
+}
+
+// And exactly the same, except for 16 bit fields.
+{
+ let txt =
+ `(module
+ (type $hasOOL (struct
+ ;; in-line storage
+ (field i64) (field i64) (field i64) (field i64) ;; 32
+ (field i64) (field i64) (field i64) (field i64) ;; 64
+ (field i64) (field i64) (field i64) (field i64) ;; 96
+ (field i64) (field i64) (field i64) (field i64) ;; 128
+ (field i16) (field i16) (field i16) (field i16)
+ (field i16) (field i16) (field i16) (field i16))
+ )
+ (func (export "build16")
+ (param $filler i64) (param $interesting i32) (result eqref)
+ (struct.new $hasOOL
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $filler) (local.get $filler)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ (local.get $interesting) (local.get $interesting)
+ )
+ )
+ )`;
+ let exports = wasmEvalText(txt).exports;
+ let obj16 = exports.build16(0x4321n, 0x7865);
+ assertEq(obj16[0] + BigInt(obj16[23]), 0xBB86n); // == 0x4321 + 0x7865
+}
+
+// Test that 8-bit field writes do not overwrite adjacent fields.
+{
+ let txt =
+ `(module
+ (type $struct8x8
+ (struct (field i8) (field i8) (field i8) (field (mut i8))
+ (field i8) (field i8) (field i8) (field i8)
+ ))
+ (func (export "create") (result eqref)
+ (struct.new $struct8x8 (i32.const 0x55) (i32.const 0x55)
+ (i32.const 0x55) (i32.const 0x55)
+ (i32.const 0x55) (i32.const 0x55)
+ (i32.const 0x55) (i32.const 0x55)
+ ))
+ (func (export "writeField8x8_3") (param $p eqref) (param $v i32)
+ (struct.set $struct8x8 3 (ref.cast (ref null $struct8x8) (local.get $p))
+ (local.get $v))
+ )
+ )`;
+ let exports = wasmEvalText(txt).exports;
+ let theObject = exports.create();
+ exports.writeField8x8_3(theObject, 0x77);
+ assertEq(theObject[0], 0x55);
+ assertEq(theObject[1], 0x55);
+ assertEq(theObject[2], 0x55);
+ assertEq(theObject[3], 0x77);
+ assertEq(theObject[4], 0x55);
+ assertEq(theObject[5], 0x55);
+ assertEq(theObject[6], 0x55);
+ assertEq(theObject[7], 0x55);
+}
+
+// Test that 16-bit field writes do not overwrite adjacent fields.
+{
+ let txt =
+ `(module
+ (type $struct16x8
+ (struct (field i16) (field i16) (field i16) (field (mut i16))
+ (field i16) (field i16) (field i16) (field i16)
+ ))
+ (func (export "create") (result eqref)
+ (struct.new $struct16x8 (i32.const 0x5555) (i32.const 0x5555)
+ (i32.const 0x5555) (i32.const 0x5555)
+ (i32.const 0x5555) (i32.const 0x5555)
+ (i32.const 0x5555) (i32.const 0x5555)
+ ))
+ (func (export "writeField16x8_3") (param $p eqref) (param $v i32)
+ (struct.set $struct16x8 3 (ref.cast (ref null $struct16x8) (local.get $p))
+ (local.get $v))
+ )
+ )`;
+ let exports = wasmEvalText(txt).exports;
+ let theObject = exports.create();
+ exports.writeField16x8_3(theObject, 0x7766);
+ assertEq(theObject[0], 0x5555);
+ assertEq(theObject[1], 0x5555);
+ assertEq(theObject[2], 0x5555);
+ assertEq(theObject[3], 0x7766);
+ assertEq(theObject[4], 0x5555);
+ assertEq(theObject[5], 0x5555);
+ assertEq(theObject[6], 0x5555);
+ assertEq(theObject[7], 0x5555);
+}
+
+// Test that 8-bit field reads sign/zero extend correctly.
+{
+ let txt =
+ `(module
+ (type $struct8x8
+ (struct (field i8) (field i8) (field i8) (field i8)
+ (field i8) (field i8) (field i8) (field i8)
+ ))
+ (func (export "create") (result eqref)
+ (struct.new $struct8x8 (i32.const 0x11) (i32.const 0x82)
+ (i32.const 0x23) (i32.const 0x94)
+ (i32.const 0x35) (i32.const 0xA6)
+ (i32.const 0x47) (i32.const 0xB8)
+ ))
+ ;; read i8 from a field, unsigned extend, read value has top bit 0
+ (func (export "readU8hi0") (param $p eqref) (result i32)
+ (struct.get_u $struct8x8 2 (ref.cast (ref null $struct8x8) (local.get $p)))
+ )
+ ;; read i8 from a field, unsigned extend, read value has top bit 1
+ (func (export "readU8hi1") (param $p eqref) (result i32)
+ (struct.get_u $struct8x8 3 (ref.cast (ref null $struct8x8) (local.get $p)))
+ )
+ ;; read i8 from a field, signed extend, read value has top bit 0
+ (func (export "readS8hi0") (param $p eqref) (result i32)
+ (struct.get_s $struct8x8 4 (ref.cast (ref null $struct8x8) (local.get $p)))
+ )
+ ;; read i8 from a field, signed extend, read value has top bit 1
+ (func (export "readS8hi1") (param $p eqref) (result i32)
+ (struct.get_s $struct8x8 5 (ref.cast (ref null $struct8x8) (local.get $p)))
+ )
+ )`;
+ let exports = wasmEvalText(txt).exports;
+ let theObject = exports.create();
+ assertEq(exports.readU8hi0(theObject), 0x23); // zx of 0x23
+ assertEq(exports.readU8hi1(theObject), 0x94); // zx of 0x94
+ assertEq(exports.readS8hi0(theObject), 0x35); // sx of 0x35
+ assertEq(exports.readS8hi1(theObject), -0x5A); // sx of 0xA6
+}
+
+// Test that 16-bit field reads sign/zero extend correctly.
+{
+ let txt =
+ `(module
+ (type $struct16x8
+ (struct (field i16) (field i16) (field i16) (field i16)
+ (field i16) (field i16) (field i16) (field i16)
+ ))
+ (func (export "create") (result eqref)
+ (struct.new $struct16x8 (i32.const 0x11FF) (i32.const 0x82FE)
+ (i32.const 0x23FD) (i32.const 0x94FC)
+ (i32.const 0x35FB) (i32.const 0xA6FA)
+ (i32.const 0x47F9) (i32.const 0xB8F8)
+ ))
+ ;; read i16 from a field, unsigned extend, read value has top bit 0
+ (func (export "readU16hi0") (param $p eqref) (result i32)
+ (struct.get_u $struct16x8 2 (ref.cast (ref null $struct16x8) (local.get $p)))
+ )
+ ;; read i16 from a field, unsigned extend, read value has top bit 1
+ (func (export "readU16hi1") (param $p eqref) (result i32)
+ (struct.get_u $struct16x8 3 (ref.cast (ref null $struct16x8) (local.get $p)))
+ )
+ ;; read i16 from a field, signed extend, read value has top bit 0
+ (func (export "readS16hi0") (param $p eqref) (result i32)
+ (struct.get_s $struct16x8 4 (ref.cast (ref null $struct16x8) (local.get $p)))
+ )
+ ;; read i16 from a field, signed extend, read value has top bit 1
+ (func (export "readS16hi1") (param $p eqref) (result i32)
+ (struct.get_s $struct16x8 5 (ref.cast (ref null $struct16x8) (local.get $p)))
+ )
+ )`;
+ let exports = wasmEvalText(txt).exports;
+ let theObject = exports.create();
+ assertEq(exports.readU16hi0(theObject), 0x23FD); // zx of 0x23FD
+ assertEq(exports.readU16hi1(theObject), 0x94FC); // zx of 0x94FC
+ assertEq(exports.readS16hi0(theObject), 0x35FB); // sx of 0x35FB
+ assertEq(exports.readS16hi1(theObject), -0x5906); // sx of 0xA6FC
+}
diff --git a/js/src/jit-test/tests/wasm/gc/supertype_later_in_group.js b/js/src/jit-test/tests/wasm/gc/supertype_later_in_group.js
new file mode 100644
index 0000000000..6f5ce48282
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/supertype_later_in_group.js
@@ -0,0 +1,15 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Checking that we are correctly validating all subtyping rules.
+// In this example, $b should be a subtype of $a, even if their field types
+// will be loaded later.
+wasmValidateText(`
+(module
+ (rec
+ (type $a (struct (field (ref $notParsedYet))))
+ (type $b (sub $a (struct (field (ref $notParsedYet2)))))
+
+ (type $notParsedYet (struct))
+ (type $notParsedYet2 (sub $notParsedYet (struct (field i32))))
+ )
+)`);
diff --git a/js/src/jit-test/tests/wasm/gc/tables-generalized-struct.js b/js/src/jit-test/tests/wasm/gc/tables-generalized-struct.js
new file mode 100644
index 0000000000..b0bd964027
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/tables-generalized-struct.js
@@ -0,0 +1,48 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// table.set in bounds with i32 x eqref - works, no value generated
+// table.set with (ref null T) - works
+// table.set with null - works
+// table.set out of bounds - fails
+
+{
+ let ins = wasmEvalText(
+ `(module
+ (table (export "t") 10 eqref)
+ (type $dummy (struct (field i32)))
+ (func (export "set_eqref") (param i32) (param eqref)
+ (table.set (local.get 0) (local.get 1)))
+ (func (export "set_null") (param i32)
+ (table.set (local.get 0) (ref.null eq)))
+ (func (export "set_ref") (param i32) (param eqref)
+ (table.set (local.get 0) (ref.cast (ref null $dummy) (local.get 1))))
+ (func (export "make_struct") (result eqref)
+ (struct.new $dummy (i32.const 37))))`);
+ let a = ins.exports.make_struct();
+ ins.exports.set_eqref(3, a);
+ assertEq(ins.exports.t.get(3), a);
+ ins.exports.set_null(3);
+ assertEq(ins.exports.t.get(3), null);
+ let b = ins.exports.make_struct();
+ ins.exports.set_ref(5, b);
+ assertEq(ins.exports.t.get(5), b);
+
+ assertErrorMessage(() => ins.exports.set_eqref(10, a), WebAssembly.RuntimeError, /index out of bounds/);
+ assertErrorMessage(() => ins.exports.set_eqref(-1, a), WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// table.grow on table of eqref with non-null ref value
+
+{
+ let ins = wasmEvalText(
+ `(module
+ (type $S (struct (field i32) (field f64)))
+ (table (export "t") 2 eqref)
+ (func (export "f") (result i32)
+ (table.grow (struct.new $S (i32.const 0) (f64.const 3.14)) (i32.const 1))))`);
+ assertEq(ins.exports.t.length, 2);
+ assertEq(ins.exports.f(), 2);
+ assertEq(ins.exports.t.length, 3);
+ assertEq(typeof ins.exports.t.get(2), "object");
+}
+
diff --git a/js/src/jit-test/tests/wasm/gc/trailers-gc-stress.js b/js/src/jit-test/tests/wasm/gc/trailers-gc-stress.js
new file mode 100644
index 0000000000..d1975a9541
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/trailers-gc-stress.js
@@ -0,0 +1,166 @@
+// |jit-test| skip-if: !wasmGcEnabled() || getBuildConfiguration().simulator
+
+// This test is intended to test what was committed in
+//
+// Bug 1817385 - wasm-gc: reduce cost of allocation and GC paths
+// and
+// Bug 1820120 - Manage Wasm{Array,Struct}Object OOL-storage-blocks
+// using a thread-private cache
+//
+// and in particular the latter. The patches in these bugs reduce the cost of
+// wasm-gc struct/array allocation and collection, in part by better
+// integrating those objects with our generational GC facility.
+//
+// Existing tests do not cover all of those paths. In particular they do not
+// exercise both set-subtraction algorithms in Nursery::freeTrailerBlocks.
+// This test does, though.
+//
+// The test first creates an "primary" array of 1500 elements. Each element
+// is a reference to a secondary array of between 1 and 50 int32s. These
+// secondary arrays have size chosen randomly, and the elements are also
+// random.
+//
+// Then, elements of the primary array are replaced. An index in the range 0
+// .. N - 1 is randomly chosen, and the element there is replaced by a
+// randomly-created secondary array. This is repeated 500,000 times with
+// N = 800.
+//
+// Finally, all of the above is repeated, but with N = 1200.
+//
+// As a result just over a million arrays and their trailer blocks, of various
+// sizes, are allocated and deallocated. With N = 800, in
+// js::Nursery::freeTrailerBlocks, we end up with trailersRemovedUsed_ of
+// around 800, so one of the set-subtraction algorithms is exercised.
+// With N = 1200, the other is exercised. It's not entirely clear why changing
+// N causes trailersRemovedUsed_ to have more or less the same value during
+// nursery collection, but the correlation does seem fairly robust.
+//
+// The test is skipped on the simulator because it takes too long to run, and
+// triggers timeouts.
+
+let t =
+`(module
+
+ ;; A simple pseudo-random number generator.
+ ;; Produces numbers in the range 0 .. 2^16-1.
+ (global $rngState
+ (mut i32) (i32.const 1)
+ )
+ (func $rand (export "rand") (result i32)
+ (local $t i32)
+ ;; update $rngState
+ (local.set $t (global.get $rngState))
+ (local.set $t (i32.mul (local.get $t) (i32.const 1103515245)))
+ (local.set $t (i32.add (local.get $t) (i32.const 12345)))
+ (global.set $rngState (local.get $t))
+ ;; pull 16 random bits out of it
+ (local.set $t (i32.shr_u (local.get $t) (i32.const 15)))
+ (local.set $t (i32.and (local.get $t) (i32.const 0xFFFF)))
+ (local.get $t)
+ )
+
+ ;; Array types
+ (type $tArrayI32 (array (mut i32))) ;; "secondary array" above
+ (type $tArrayArrayI32 (array (mut (ref null $tArrayI32)))) ;; "primary array"
+
+ ;; Create an array ("secondary array") containing random numbers, with a
+ ;; size between 1 and 50, also randomly chosen.
+ (func $createSecondaryArray (export "createSecondaryArray")
+ (result (ref $tArrayI32))
+ (local $i i32)
+ (local $nElems i32)
+ (local $arr (ref $tArrayI32))
+ (local.set $nElems (call $rand))
+ (local.set $nElems (i32.rem_u (local.get $nElems) (i32.const 50)))
+ (local.set $nElems (i32.add (local.get $nElems) (i32.const 1)))
+ (local.set $arr (array.new $tArrayI32 (i32.const 0) (local.get $nElems)))
+ (loop $cont
+ (array.set $tArrayI32 (local.get $arr) (local.get $i) (call $rand))
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br_if $cont (i32.lt_u (local.get $i) (local.get $nElems)))
+ )
+ (local.get $arr)
+ )
+
+ ;; Create an array (the "primary array") of 1500 elements of
+ ;; type ref-of-tArrayI32.
+ (func $createPrimaryArray (export "createPrimaryArray")
+ (result (ref $tArrayArrayI32))
+ (local $i i32)
+ (local $arrarr (ref $tArrayArrayI32))
+ (local.set $arrarr (array.new $tArrayArrayI32 (ref.null $tArrayI32)
+ (i32.const 1500)))
+ (loop $cont
+ (array.set $tArrayArrayI32 (local.get $arrarr)
+ (local.get $i) (call $createSecondaryArray))
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br_if $cont (i32.lt_u (local.get $i) (i32.const 1500)))
+ )
+ (local.get $arrarr)
+ )
+
+ ;; Use $createPrimaryArray to create an initial array. Then randomly replace
+ ;; elements for a while.
+ (func $churn (export "churn") (param $thresh i32) (result i32)
+ (local $i i32)
+ (local $j i32)
+ (local $finalSum i32)
+ (local $arrarr (ref $tArrayArrayI32))
+ (local $arr (ref null $tArrayI32))
+ (local $arrLen i32)
+ (local.set $arrarr (call $createPrimaryArray))
+ ;; This loop iterates 500,000 times. Each iteration, it chooses
+ ;; a randomly element in $arrarr and replaces it with a new
+ ;; random array of 32-bit ints.
+ (loop $cont
+ ;; make $j be a random number in 0 .. $thresh-1.
+ ;; Then replace that index in $arrarr with a new random arrayI32.
+ (local.set $j (i32.rem_u (call $rand) (local.get $thresh)))
+ (array.set $tArrayArrayI32 (local.get $arrarr)
+ (local.get $j) (call $createSecondaryArray))
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br_if $cont (i32.lt_u (local.get $i) (i32.const 500000)))
+ )
+
+ ;; Finally, compute a checksum by summing all the numbers
+ ;; in all secondary arrays. This simply assumes that all of the refs to
+ ;; secondary arrays are non-null, which isn't per-se guaranteed by the
+ ;; previous loop, but it works in this case because the RNG
+ ;; produces each index value to overwrite at least once.
+ (local.set $finalSum (i32.const 0))
+ (local.set $i (i32.const 0)) ;; loop var for the outer loop
+ (loop $outer
+ ;; body of outer loop
+ ;; $arr = $arrarr[i]
+ (local.set $arr (array.get $tArrayArrayI32 (local.get $arrarr)
+ (local.get $i)))
+ ;; iterate over $arr
+ (local.set $arrLen (array.len (local.get $arr)))
+ (local.set $j (i32.const 0)) ;; loop var for the inner loop
+ (loop $inner
+ ;; body of inner loop
+ (local.set $finalSum
+ (i32.rotl (local.get $finalSum) (i32.const 1)))
+ (local.set $finalSum
+ (i32.xor (local.get $finalSum)
+ (array.get $tArrayI32 (local.get $arr)
+ (local.get $j))))
+ ;; loop control for the inner loop
+ (local.set $j (i32.add (local.get $j) (i32.const 1)))
+ (br_if $inner (i32.lt_u (local.get $j) (local.get $arrLen)))
+ )
+ ;; loop control for the outer loop
+ (local.set $i (i32.add (local.get $i) (i32.const 1)))
+ (br_if $outer (i32.lt_u (local.get $i) (i32.const 1500)))
+ )
+
+ ;; finally, roll in the final value of the RNG state
+ (i32.xor (local.get $finalSum) (global.get $rngState))
+ )
+)`;
+
+let i = wasmEvalText(t);
+let fns = i.exports;
+
+assertEq(fns.churn(800), -575895114);
+assertEq(fns.churn(1200), -1164697516);
diff --git a/js/src/jit-test/tests/wasm/gc/value_subtyping.js b/js/src/jit-test/tests/wasm/gc/value_subtyping.js
new file mode 100644
index 0000000000..bc0a3df678
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/value_subtyping.js
@@ -0,0 +1,305 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+function simpleTypeSection(types) {
+ return types.map((x, i) => `(type \$${i} ${x})`).join('\n');
+}
+
+function assertSubtype(superType, subType, types) {
+ types = types || [];
+ wasmEvalText(`(module
+ ${types}
+ (func
+ unreachable
+ (block (param ${subType})
+ (block (param ${superType})
+ drop
+ )
+ )
+ )
+ )`);
+}
+
+function assertNotSubtype(superType, subType, types) {
+ assertErrorMessage(() => {
+ assertSubtype(superType, subType, types);
+ }, WebAssembly.CompileError, /type mismatch/);
+}
+
+// Primitive trivial subtyping
+assertSubtype('i32', 'i32');
+assertSubtype('i64', 'i64');
+assertSubtype('f32', 'f32');
+assertSubtype('f64', 'f64');
+assertSubtype('eqref', 'eqref');
+assertSubtype('funcref', 'funcref');
+
+// No subtyping relation between funcref, anyref, externref. These are our top
+// types.
+assertNotSubtype('funcref', 'anyref');
+assertNotSubtype('anyref', 'funcref');
+assertNotSubtype('funcref', 'externref');
+assertNotSubtype('externref', 'funcref');
+assertNotSubtype('externref', 'anyref');
+assertNotSubtype('anyref', 'externref');
+
+// eqref is a subtype of anyref
+assertSubtype('anyref', 'eqref');
+
+// structref is a subtype of eqref and anyref
+assertSubtype('anyref', 'structref');
+assertSubtype('eqref', 'structref');
+
+// arrayref is a subtype of eqref and anyref
+assertSubtype('anyref', 'arrayref');
+assertSubtype('eqref', 'arrayref');
+
+// Structs are subtypes of anyref, eqref, and structref
+assertSubtype(
+ 'anyref',
+ '(ref 0)',
+ simpleTypeSection(['(struct)']));
+assertSubtype(
+ 'eqref',
+ '(ref 0)',
+ simpleTypeSection(['(struct)']));
+assertSubtype(
+ 'structref',
+ '(ref 0)',
+ simpleTypeSection(['(struct)']));
+
+// Struct identity
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection(['(struct)', '(struct)']));
+assertSubtype(
+ '(ref 1)',
+ '(ref 0)',
+ simpleTypeSection(['(struct)', '(struct)']));
+
+// Self referential struct
+assertSubtype(
+ '(ref 1)',
+ '(ref 0)',
+ simpleTypeSection(['(struct (ref 0))', '(struct (ref 1))']));
+
+// Mutually referential structs
+assertSubtype(
+ '(ref 2)',
+ '(ref 0)',
+ `(rec
+ (type (struct (ref 1)))
+ (type (struct (ref 0)))
+ )
+ (rec
+ (type (struct (ref 3)))
+ (type (struct (ref 2)))
+ )`);
+
+// Struct subtypes can have extra fields
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ `(type (struct))
+ (type (sub 0 (struct (field i32))))`);
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ `(type (struct))
+ (type (sub 0 (struct (field i32) (field i32))))`);
+
+// Struct supertypes cannot have extra fields
+assertNotSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection([
+ '(struct (field i32))',
+ '(struct)']));
+
+// Struct field mutability must match
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection([
+ '(struct (field (mut i32)))',
+ '(struct (field (mut i32)))']));
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection([
+ '(struct (field i32))',
+ '(struct (field i32))']));
+assertNotSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection([
+ '(struct (field (mut i32)))',
+ '(struct (field i32))']));
+assertNotSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection([
+ '(struct (field i32))',
+ '(struct (field (mut i32)))']));
+
+// Struct fields are invariant when mutable
+assertSubtype(
+ '(ref 2)',
+ '(ref 3)',
+ simpleTypeSection([
+ '(struct)',
+ '(struct)',
+ '(struct (field (mut (ref 0))))',
+ '(struct (field (mut (ref 1))))']));
+assertNotSubtype(
+ '(ref 2)',
+ '(ref 3)',
+ simpleTypeSection([
+ '(struct)',
+ '(struct (field i32))',
+ '(struct (field (mut (ref 0))))',
+ '(struct (field (mut (ref 1))))']));
+
+// Struct fields are covariant when immutable
+assertSubtype(
+ '(ref 2)',
+ '(ref 3)',
+ `(type (struct))
+ (type (sub 0 (struct (field i32))))
+ (type (struct (field (ref 0))))
+ (type (sub 2 (struct (field (ref 1)))))`);
+
+// Arrays are subtypes of anyref, eqref, and arrayref
+assertSubtype(
+ 'anyref',
+ '(ref 0)',
+ simpleTypeSection(['(array i32)']));
+assertSubtype(
+ 'eqref',
+ '(ref 0)',
+ simpleTypeSection(['(array i32)']));
+assertSubtype(
+ 'arrayref',
+ '(ref 0)',
+ simpleTypeSection(['(array i32)']));
+
+// Array identity
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection(['(array i32)', '(array i32)']));
+assertSubtype(
+ '(ref 1)',
+ '(ref 0)',
+ simpleTypeSection(['(array i32)', '(array i32)']));
+
+// Self referential array
+assertSubtype(
+ '(ref 1)',
+ '(ref 0)',
+ simpleTypeSection(['(array (ref 0))', '(array (ref 1))']));
+
+// Mutually referential arrays
+assertSubtype(
+ '(ref 2)',
+ '(ref 0)',
+ `(rec
+ (type (array (ref 1)))
+ (type (array (ref 0)))
+ )
+ (rec
+ (type (array (ref 3)))
+ (type (array (ref 2)))
+ )`);
+
+// Array mutability must match
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection([
+ '(array (mut i32))',
+ '(array (mut i32))']));
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection([
+ '(array i32)',
+ '(array i32)']));
+assertNotSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection([
+ '(array (mut i32))',
+ '(array i32)']));
+assertNotSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ simpleTypeSection([
+ '(array i32)',
+ '(array (mut i32))']));
+
+// Array elements are invariant when mutable
+assertSubtype(
+ '(ref 2)',
+ '(ref 3)',
+ simpleTypeSection([
+ '(struct)',
+ '(struct)',
+ '(array (mut (ref 0)))',
+ '(array (mut (ref 1)))']));
+assertNotSubtype(
+ '(ref 2)',
+ '(ref 3)',
+ simpleTypeSection([
+ '(struct)',
+ '(struct (field i32))',
+ '(array (mut (ref 0)))',
+ '(array (mut (ref 1)))']));
+
+// Array elements are covariant when immutable
+assertSubtype(
+ '(ref 2)',
+ '(ref 3)',
+ `(type (struct))
+ (type (sub 0 (struct (field i32))))
+ (type (array (ref 0)))
+ (type (sub 2 (array (ref 1))))`);
+
+// nullref is a subtype of everything in anyref hierarchy
+assertSubtype('anyref', 'nullref');
+assertSubtype('eqref', 'nullref');
+assertSubtype('structref', 'nullref');
+assertSubtype('arrayref', 'nullref');
+assertSubtype('(ref null 0)', 'nullref', simpleTypeSection(['(struct)']));
+assertSubtype('(ref null 0)', 'nullref', simpleTypeSection(['(array i32)']));
+
+// nullref is not a subtype of any other hierarchy
+assertNotSubtype('funcref', 'nullref');
+assertNotSubtype('(ref null 0)', 'nullref', simpleTypeSection(['(func)']));
+assertNotSubtype('externref', 'nullref');
+
+// nullfuncref is a subtype of everything in funcref hierarchy
+assertSubtype('funcref', 'nullfuncref');
+assertSubtype('(ref null 0)', 'nullfuncref', simpleTypeSection(['(func)']));
+
+// nullfuncref is not a subtype of any other hierarchy
+assertNotSubtype('anyref', 'nullfuncref');
+assertNotSubtype('eqref', 'nullfuncref');
+assertNotSubtype('structref', 'nullfuncref');
+assertNotSubtype('arrayref', 'nullfuncref');
+assertNotSubtype('externref', 'nullfuncref');
+assertNotSubtype('(ref null 0)', 'nullfuncref', simpleTypeSection(['(struct)']));
+assertNotSubtype('(ref null 0)', 'nullfuncref', simpleTypeSection(['(array i32)']));
+
+// nullexternref is a subtype of everything in externref hierarchy
+assertSubtype('externref', 'nullexternref');
+
+// nullexternref is not a subtype of any other hierarchy
+assertNotSubtype('anyref', 'nullexternref');
+assertNotSubtype('eqref', 'nullexternref');
+assertNotSubtype('structref', 'nullexternref');
+assertNotSubtype('arrayref', 'nullexternref');
+assertNotSubtype('funcref', 'nullexternref');
+assertNotSubtype('(ref null 0)', 'nullexternref', simpleTypeSection(['(struct)']));
+assertNotSubtype('(ref null 0)', 'nullexternref', simpleTypeSection(['(array i32)']));
+assertNotSubtype('(ref null 0)', 'nullexternref', simpleTypeSection(['(func)']));