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.js106
-rw-r--r--js/src/jit-test/tests/wasm/gc/arrays.js2027
-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/bug-1841119.js18
-rw-r--r--js/src/jit-test/tests/wasm/gc/bug-1843295.js10
-rw-r--r--js/src/jit-test/tests/wasm/gc/bug-1845436.js10
-rw-r--r--js/src/jit-test/tests/wasm/gc/bug-1845673.js14
-rw-r--r--js/src/jit-test/tests/wasm/gc/bug-1854007.js16
-rw-r--r--js/src/jit-test/tests/wasm/gc/call-indirect-subtyping.js137
-rw-r--r--js/src/jit-test/tests/wasm/gc/cast-abstract.js298
-rw-r--r--js/src/jit-test/tests/wasm/gc/cast-extern.js54
-rw-r--r--js/src/jit-test/tests/wasm/gc/casting.js116
-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/final_types.js48
-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/globals.js233
-rw-r--r--js/src/jit-test/tests/wasm/gc/i31ref.js164
-rw-r--r--js/src/jit-test/tests/wasm/gc/init-expr.js281
-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.js123
-rw-r--r--js/src/jit-test/tests/wasm/gc/limits.js69
-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.js333
-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.js12
-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.js180
-rw-r--r--js/src/jit-test/tests/wasm/gc/structs.js742
-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/tables.js167
-rw-r--r--js/src/jit-test/tests/wasm/gc/trailers-gc-stress.js166
-rw-r--r--js/src/jit-test/tests/wasm/gc/unreachable.js61
-rw-r--r--js/src/jit-test/tests/wasm/gc/value_subtyping.js311
48 files changed, 7500 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..ce27dc1089
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/TypedObject.js
@@ -0,0 +1,106 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// We can read the object fields from JS via a builtin
+{
+ 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(wasmGcReadField(p, 0), 1.5);
+ assertEq(wasmGcReadField(p, 1), 33);
+ assertErrorMessage(() => wasmGcReadField(p, 2), WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// Fields can't be modified from JS.
+{
+ 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, TypeError, /can't modify/);
+}
+
+// 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(wasmGcReadField(q, 0), 1.5);
+
+ let p = ins.mkp();
+ assertEq(typeof p, "object");
+ assertEq(wasmGcReadField(p, 0), null);
+
+ assertErrorMessage(() => { p[0] = q }, TypeError, /can't modify/);
+ assertErrorMessage(() => { p[1] = q }, TypeError, /can't modify/);
+}
+
+// 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(wasmGcReadField(p, 0), 0x1234567887654321n)
+
+ assertErrorMessage(() => { p[0] = 0 }, TypeError, /can't modify/);
+}
+
+// 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..b3f03151bb
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/arrays.js
@@ -0,0 +1,2027 @@
+// |jit-test| skip-if: !wasmGcEnabled(); test-also=--gc-zeal=2
+
+// 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(wasmGcArrayLength(array), length);
+
+ // Check init value
+ for (let i = 0; i < length; i++) {
+ assertEq(wasmGcReadField(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(wasmGcReadField(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(wasmGcReadField(a, 0), getS(a, 0));
+
+ // Check array.new truncates init value
+ assertEq(wasmGcReadField(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
+ )
+)
+`));
+
+// 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(wasmGcArrayLength(a), 4);
+ assertEq(wasmGcReadField(a, 0), 66);
+ assertEq(wasmGcReadField(a, 1), 77);
+ assertEq(wasmGcReadField(a, 2), 88);
+ assertEq(wasmGcReadField(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(wasmGcArrayLength(a), 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(wasmGcArrayLength(a), 30);
+ for (i = 0; i < 30; i++) {
+ assertEq(wasmGcReadField(a, i), i + 1);
+ }
+}
+
+// run: resulting 10_000-element array is as expected
+{
+ let initializers = '';
+ for (let i = 0; i < 10_000; i++) {
+ initializers += `i32.const ${i + 1}\n`;
+ }
+ let { newFixed } = wasmEvalText(`(module
+ (type $a (array i16))
+ (func (export "newFixed") (result eqref)
+ ${initializers}
+ array.new_fixed $a 10000
+ )
+ )`).exports;
+ let a = newFixed();
+ assertEq(wasmGcArrayLength(a), 10000);
+ for (i = 0; i < 10000; i++) {
+ assertEq(wasmGcReadField(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(wasmGcArrayLength(arr), 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(wasmGcArrayLength(arr), 4);
+ assertEq(wasmGcReadField(arr, 0), 48+1);
+ assertEq(wasmGcReadField(arr, 1), 48+3);
+ assertEq(wasmGcReadField(arr, 2), 48+3);
+ assertEq(wasmGcReadField(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 func $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 func $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 func $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 func $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 func $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)) func $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)) func $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)) func $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(wasmGcArrayLength(arr), 0);
+}
+
+// run: range to copy would require OOB read on elem segment
+{
+ let { newElem } = wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e func $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 func $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(wasmGcArrayLength(arr), 4);
+ assertEq(wasmGcReadField(arr, 0), f1);
+ assertEq(wasmGcReadField(arr, 1), f2);
+ assertEq(wasmGcReadField(arr, 2), f3);
+ assertEq(wasmGcReadField(arr, 3), f4);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.init_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
+ array-type imm-operand must be mutable
+ segment index must be "in range"
+ run:
+ array must not be null
+ 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
+ range to copy would require OOB write on array
+ 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 "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_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 "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_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 "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_data $a $d
+ )
+)`), WebAssembly.CompileError, /element type must be i8\/i16\/i32\/i64\/f32\/f64\/v128/);
+
+// validation: array-type imm-operand must be mutable
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i8))
+ (data $d "1337")
+ (func (export "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_data $a $d
+ )
+)`), WebAssembly.CompileError,
+ /destination array is not mutable/);
+
+// validation: segment index must be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array (mut i8)))
+ (data $d "1337")
+ (func (export "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_data $a 1 ;; 1 is the lowest invalid dseg index
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+)`), WebAssembly.CompileError, /segment index is out of range/);
+
+// run: array must not be null
+{
+ let { initData } = wasmEvalText(`(module
+ (type $a (array (mut i8)))
+ (data $d "1337")
+ (func (export "initData")
+ (; array to init ;) (ref.null $a)
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ assertErrorMessage(() => {
+ initData();
+ }, WebAssembly.RuntimeError, /dereferencing null pointer/);
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length init can be performed #1
+{
+ let { initData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array (mut i8)))
+ (data $d (offset (i32.const 0)) "1337")
+ (func (export "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ assertErrorMessage(() => {
+ initData();
+ }, 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 init can be performed #2
+{
+ let { initData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array (mut i8)))
+ (data $d (offset (i32.const 0)) "1337")
+ (func (export "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=4 into data ;) i32.const 4
+ (; size=0 elements ;) i32.const 0
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ assertErrorMessage(() => {
+ initData();
+ },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 init can be performed #3
+{
+ let { initData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array (mut i8)))
+ (data $d (offset (i32.const 0)) "1337")
+ (func (export "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=0 elements ;) i32.const 0
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ initData();
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length init can be performed #4
+{
+ let { initData } = wasmEvalText(`(module
+ (type $a (array (mut i8)))
+ (data $d "1337")
+ (func (export "initData")
+ data.drop $d
+
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ assertErrorMessage(() => {
+ initData();
+ },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 init can be performed #5
+{
+ let { initData } = wasmEvalText(`(module
+ (type $a (array (mut i8)))
+ (data $d "1337")
+ (func (export "initData")
+ data.drop $d
+
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=0 elements ;) i32.const 0
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ initData();
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length init can be performed #6
+{
+ let { initData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array (mut i8)))
+ (data $d "1337")
+ (func (export "initData")
+ data.drop $d
+
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=4 into array ;) i32.const 4
+ (; offset=0 into data ;) i32.const 0
+ (; size=0 elements ;) i32.const 0
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ initData();
+}
+
+// run: range to copy would require OOB read on data segment #1
+{
+ let { initData } = wasmEvalText(`(module
+ (type $a (array (mut i8)))
+ (data $d "1337")
+ (func (export "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=1 into data ;) i32.const 1
+ (; size=4 elements ;) i32.const 4
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ assertErrorMessage(() => {
+ initData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: range to copy would require OOB read on data segment #2
+{
+ let { initData } = wasmEvalText(`(module
+ (type $a (array (mut i16)))
+ (data $d "1337")
+ (func (export "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=1 into data ;) i32.const 1
+ (; size=2 elements ;) i32.const 2 ;; still 4 bytes
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ assertErrorMessage(() => {
+ initData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: range to copy would require OOB write on array #1
+{
+ let { initData } = wasmEvalText(`(module
+ (type $a (array (mut i8)))
+ (data $d "1337")
+ (func (export "initData")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=1 into array ;) i32.const 1
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ assertErrorMessage(() => {
+ initData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: range to copy would require OOB write on array #2
+{
+ let { initData } = wasmEvalText(`(module
+ (type $a (array (mut i16)))
+ (data $d "1337")
+ (func (export "initData")
+ (; array to init ;) (array.new_default $a (i32.const 2))
+ (; offset=1 into array ;) i32.const 1
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 2
+ array.init_data $a $d
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ assertErrorMessage(() => {
+ initData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: resulting array is as expected
+{
+ let { initData } = wasmEvalText(`(module
+ (type $a (array (mut i8)))
+ (data $other "\\\\9")
+ (data $d "1337")
+ (func (export "initData") (result eqref)
+ (local $arr (ref $a))
+ (local.set $arr (array.new_default $a (i32.const 4)))
+
+ (; array to init ;) local.get $arr
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 elements ;) i32.const 4
+ array.init_data $a $d
+
+ local.get $arr
+ )
+ (func data.drop 0) ;; force write of data count section, see https://github.com/bytecodealliance/wasm-tools/pull/1194
+ )`).exports;
+ let arr = initData();
+ assertEq(wasmGcArrayLength(arr), 4);
+ assertEq(wasmGcReadField(arr, 0), 48+1);
+ assertEq(wasmGcReadField(arr, 1), 48+3);
+ assertEq(wasmGcReadField(arr, 2), 48+3);
+ assertEq(wasmGcReadField(arr, 3), 48+7);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.init_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
+ array-type imm-operand must be mutable
+ destination elem type must be a supertype of src elem type
+ segment index must be "in range"
+ run:
+ array must not be null
+ 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 element segment
+ range to copy would require OOB write on array
+ resulting array is as expected
+*/
+
+// validation: array-type imm-operand needs to be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array (mut funcref)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem 4 $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 func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_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 (mut f32)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a $e
+ )
+)`), WebAssembly.CompileError, /element type is not a reftype/);
+
+// validation: array-type imm-operand must be mutable
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a $e
+ )
+)`), WebAssembly.CompileError, /destination array is not mutable/);
+
+// validation: destination elem type must be a supertype of src elem type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array (mut eqref)))
+ (elem $e func $f1)
+ (func $f1 (export "f1"))
+ ;; The 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 "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a $e
+ )
+)`), WebAssembly.CompileError, /incompatible element types/);
+
+// validation: segment index must be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array (mut funcref)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a 1 ;; 1 is the lowest invalid eseg index
+ )
+)`), WebAssembly.CompileError, /segment index is out of range/);
+
+// run: array must not be null
+{
+ let { initElem } = wasmEvalText(`(module
+ (type $a (array (mut funcref)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (ref.null $a)
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ initElem();
+ }, WebAssembly.RuntimeError, /dereferencing null pointer/);
+}
+
+// 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 { initElem } = wasmEvalText(`(module
+ (table 4 funcref)
+ (type $a (array (mut funcref)))
+ (elem $e (offset (i32.const 0)) func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ initElem();
+ }, 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 { initElem } = wasmEvalText(`(module
+ (table 4 funcref)
+ (type $a (array (mut funcref)))
+ (elem $e (offset (i32.const 0)) func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=4 into elem ;) i32.const 4
+ (; size=0 into elem ;) i32.const 0
+ array.init_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ initElem();
+ }, 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 { initElem } = wasmEvalText(`(module
+ (table 4 funcref)
+ (type $a (array (mut funcref)))
+ (elem $e (offset (i32.const 0)) func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=0 into elem ;) i32.const 0
+ array.init_elem $a $e
+ )
+ )`).exports;
+ initElem();
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length init can be performed #4
+{
+ let { initElem } = wasmEvalText(`(module
+ (type $a (array (mut funcref)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ elem.drop $e
+
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ initElem();
+ },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 init can be performed #5
+{
+ let { initElem } = wasmEvalText(`(module
+ (type $a (array (mut funcref)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ elem.drop $e
+
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 0
+ array.init_elem $a $e
+ )
+ )`).exports;
+ initElem();
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length init can be performed #6
+{
+ let { initElem } = wasmEvalText(`(module
+ (type $a (array (mut funcref)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ elem.drop $e
+
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 4
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 0
+ array.init_elem $a $e
+ )
+ )`).exports;
+ initElem();
+}
+
+// run: range to copy would require OOB read on elem segment
+{
+ let { initElem } = wasmEvalText(`(module
+ (type $a (array (mut funcref)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 1
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ initElem();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: range to copy would require OOB write on array
+{
+ let { initElem } = wasmEvalText(`(module
+ (type $a (array (mut funcref)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem")
+ (; array to init ;) (array.new_default $a (i32.const 4))
+ (; offset=0 into array ;) i32.const 1
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ initElem();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: resulting array is as expected
+{
+ let { initElem, f1, f2, f3, f4 } = wasmEvalText(`(module
+ (type $a (array (mut funcref)))
+ (elem $e func $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "initElem") (result eqref)
+ (local $arr (ref $a))
+ (local.set $arr (array.new_default $a (i32.const 4)))
+
+ (; array to init ;) local.get $arr
+ (; offset=0 into array ;) i32.const 0
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.init_elem $a $e
+
+ local.get $arr
+ )
+ )`).exports;
+ let arr = initElem();
+ assertEq(wasmGcArrayLength(arr), 4);
+ assertEq(wasmGcReadField(arr, 0), f1);
+ assertEq(wasmGcReadField(arr, 1), f2);
+ assertEq(wasmGcReadField(arr, 2), f3);
+ assertEq(wasmGcReadField(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) {
+ function len(arr) {
+ return Array.isArray(arr) ? arr.length : wasmGcArrayLength(arr);
+ }
+ function get(arr, i) {
+ return Array.isArray(arr) ? arr[i] : wasmGcReadField(arr, i);
+ }
+ assertEq(len(a1), 6);
+ assertEq(len(a2), 6);
+ for (i = 0; i < 6; i++) {
+ if (get(a1, i) !== get(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/);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.fill
+/*
+ validation:
+ array must be mutable
+ value must be compatible with array element type
+ run:
+ null array causes a trap
+ OOB conditions on array for non-zero length copies
+ OOB conditions on array for zero length copies
+ resulting arrays are as expected (all types)
+*/
+
+// validation: array must be mutable
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func
+ (array.new_default $a (i32.const 8))
+ i32.const 0
+ i32.const 123
+ i32.const 8
+ array.fill $a
+ )
+)
+`), WebAssembly.CompileError, /array is not mutable/);
+
+// validation: value must be compatible with array element type #1
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func
+ (array.new_default $a (i32.const 8))
+ i32.const 0
+ i64.const 123
+ i32.const 8
+ array.fill $a
+ )
+)
+`), WebAssembly.CompileError, /type mismatch/);
+
+// validation: value must be compatible with array element type #2
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array (mut eqref)))
+ (func
+ (array.new_default $a (i32.const 8))
+ i32.const 0
+ ref.null any
+ i32.const 8
+ array.fill $a
+ )
+)
+`), WebAssembly.CompileError, /type mismatch/);
+
+// run: null array causes a trap
+{
+ const { arrayFill } = wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func (export "arrayFill")
+ ref.null $a
+ i32.const 0
+ i32.const 123
+ i32.const 8
+ array.fill $a
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ arrayFill();
+ }, WebAssembly.RuntimeError, /dereferencing null pointer/);
+}
+
+// run: OOB conditions on array for non-zero length copies
+{
+ const { arrayFill } = wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func (export "arrayFill")
+ (array.new_default $a (i32.const 8))
+ i32.const 1
+ i32.const 123
+ i32.const 8
+ array.fill $a
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ arrayFill();
+ }, WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: OOB conditions on array for zero length copies
+{
+ const { arrayFill } = wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func (export "arrayFill")
+ (array.new_default $a (i32.const 8))
+ i32.const 8
+ i32.const 123
+ i32.const 0
+ array.fill $a
+ )
+ )`).exports;
+ arrayFill();
+}
+
+// run: arrays are as expected (all types)
+{
+ const TESTS = [
+ { type: 'i8', val: 'i32.const 123', get: 'array.get_u', test: 'i32.eq' },
+ { type: 'i16', val: 'i32.const 123', get: 'array.get_u', test: 'i32.eq' },
+ { type: 'i32', val: 'i32.const 123', test: 'i32.eq' },
+ { type: 'i64', val: 'i64.const 123', test: 'i64.eq' },
+ { type: 'f32', val: 'f32.const 3.14', test: 'f32.eq' },
+ { type: 'f64', val: 'f64.const 3.14', test: 'f64.eq' },
+ { type: 'eqref', val: 'global.get 0', test: 'ref.eq' },
+ ];
+ if (wasmSimdEnabled()) {
+ TESTS.push({ type: 'v128', val: 'v128.const i32x4 111 222 333 444', test: '(v128.xor) (i32.eq (v128.any_true) (i32.const 0))' });
+ }
+
+ for (const { type, val, get = 'array.get', test } of TESTS) {
+ const { arrayFill, isDefault, isFilled } = wasmEvalText(`(module
+ (type $a (array (mut ${type})))
+ (type $s (struct))
+ (global (ref $s) (struct.new_default $s))
+ (func (export "arrayFill") (result (ref $a))
+ (local $arr (ref $a))
+ (local.set $arr (array.new_default $a (i32.const 4)))
+
+ local.get $arr
+ i32.const 1
+ ${val}
+ i32.const 2
+ array.fill $a
+
+ local.get $arr
+ )
+ (func (export "isDefault") (param (ref $a) i32) (result i32)
+ (${get} $a (local.get 0) (local.get 1))
+ (${get} $a (array.new_default $a (i32.const 1)) (i32.const 0))
+ ${test}
+ )
+ (func (export "isFilled") (param (ref $a) i32) (result i32)
+ (${get} $a (local.get 0) (local.get 1))
+ ${val}
+ ${test}
+ )
+ )`).exports;
+ const arr = arrayFill();
+ assertEq(isDefault(arr, 0), 1, `expected default value for ${type} but got filled`);
+ assertEq(isFilled(arr, 1), 1, `expected filled value for ${type} but got default`);
+ assertEq(isFilled(arr, 2), 1, `expected filled value for ${type} but got default`);
+ assertEq(isDefault(arr, 3), 1, `expected default value for ${type} but got filled`);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// 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 (export "test") (result eqref)
+ ;; request exactly 2,000,000,000 bytes
+ (array.new $a (i32.const 0xABCD1234) (i32.const 500000000))
+ )
+ (func $f
+ call 0
+ drop
+ )
+ (start $f)
+)
+`), WebAssembly.RuntimeError, /too many array elements/);
+
+// array.new_default
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array f64))
+ (func (export "test") (result eqref)
+ ;; request exactly 2,000,000,000 bytes
+ (array.new_default $a (i32.const 250000000))
+ )
+ (func $f
+ call 0
+ drop
+ )
+ (start $f)
+)
+`), 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.
+
+// Test whether array data pointers are correctly tracked in stack maps.
+{
+ const { newArray, test } = wasmEvalText(`(module
+ (type $a (array i32))
+ (import "" "gc" (func $gc))
+ (func (export "newArray") (result (ref $a))
+ (array.new $a (i32.const 123) (i32.const 4))
+ )
+ (func (export "test") (param $arr (ref $a)) (result i32)
+ (local i32)
+ (local i32)
+
+ (array.get $a (local.get $arr) (i32.const 1))
+ local.set 1
+ call $gc
+ (array.get $a (local.get $arr) (i32.const 2))
+ local.set 2
+
+ (i32.add (local.get 1) (local.get 2))
+ )
+ )`, {"": {gc}}).exports;
+ const arr = newArray();
+ assertEq(isNurseryAllocated(arr), true);
+ const res = test(arr);
+ assertEq(res, 246);
+}
+
+// Test that zero-length arrays allocate correctly
+{
+ const { testNew, testNewDefault, testNewFixed } = wasmEvalText(`(module
+ (type $a (array f32))
+
+ (func (export "testNew") (result eqref eqref eqref eqref)
+ (array.new $a (f32.const 123) (i32.const 0))
+ (array.new $a (f32.const 123) (i32.const 0))
+ (array.new $a (f32.const 123) (i32.const 0))
+ (array.new $a (f32.const 123) (i32.const 0))
+ )
+ (func (export "testNewDefault") (result eqref eqref eqref eqref)
+ (array.new_default $a (i32.const 0))
+ (array.new_default $a (i32.const 0))
+ (array.new_default $a (i32.const 0))
+ (array.new_default $a (i32.const 0))
+ )
+ (func (export "testNewFixed") (result eqref eqref eqref eqref)
+ (array.new_fixed $a 0)
+ (array.new_fixed $a 0)
+ (array.new_fixed $a 0)
+ (array.new_fixed $a 0)
+ )
+ )`).exports;
+ testNew();
+ testNewDefault();
+ testNewFixed();
+}
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..c0c0df46b4
--- /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 (sub (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/bug-1841119.js b/js/src/jit-test/tests/wasm/gc/bug-1841119.js
new file mode 100644
index 0000000000..42c20c4e91
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/bug-1841119.js
@@ -0,0 +1,18 @@
+// |jit-test| --fuzzing-safe; --ion-offthread-compile=off; skip-if: !wasmGcEnabled()
+
+function f() {
+}
+
+gczeal(9,10);
+
+let t = wasmEvalText(`
+(module
+ (type (struct))
+ (table (export "table") (ref null 0)
+ (elem ( ref.null 0 ))
+ )
+ (global (export "global") (ref null 0) ref.null 0)
+ (tag (export "tag") (param (ref null 0)))
+)`).exports;
+
+f();
diff --git a/js/src/jit-test/tests/wasm/gc/bug-1843295.js b/js/src/jit-test/tests/wasm/gc/bug-1843295.js
new file mode 100644
index 0000000000..19a32263f6
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/bug-1843295.js
@@ -0,0 +1,10 @@
+// |jit-test| skip-if: !wasmGcEnabled(); --wasm-test-serialization
+
+wasmEvalText(`(module
+ (type (sub (array (mut i32))))
+ (type (sub 0 (array (mut i32))))
+ (rec
+ (type (sub (array (mut i32))))
+ (type (sub 2 (array (mut i32))))
+ )
+)`);
diff --git a/js/src/jit-test/tests/wasm/gc/bug-1845436.js b/js/src/jit-test/tests/wasm/gc/bug-1845436.js
new file mode 100644
index 0000000000..a79c22d9a1
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/bug-1845436.js
@@ -0,0 +1,10 @@
+// |jit-test| skip-if: !wasmGcEnabled(); --wasm-test-serialization
+
+// Test that serialization doesn't create a forward reference to the third
+// struct when serializing the reference to the first struct, which is
+// structurally identical to the first struct.
+wasmEvalText(`(module
+ (type (;0;) (struct))
+ (type (;2;) (struct (field (ref 0))))
+ (type (;3;) (struct))
+)`);
diff --git a/js/src/jit-test/tests/wasm/gc/bug-1845673.js b/js/src/jit-test/tests/wasm/gc/bug-1845673.js
new file mode 100644
index 0000000000..e4f48f0769
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/bug-1845673.js
@@ -0,0 +1,14 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+let {f} = wasmEvalText(`(module
+ (type (struct))
+ (func (export "f") (result anyref)
+ ref.null 0
+ br_on_cast_fail 0 (ref null 0) (ref null 0)
+ ref.null 0
+ br_on_cast_fail 0 (ref null 0) (ref 0)
+ br_on_cast_fail 0 (ref null 0) (ref null 0)
+ unreachable
+ )
+)`).exports;
+f();
diff --git a/js/src/jit-test/tests/wasm/gc/bug-1854007.js b/js/src/jit-test/tests/wasm/gc/bug-1854007.js
new file mode 100644
index 0000000000..c9d6b25369
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/bug-1854007.js
@@ -0,0 +1,16 @@
+// |jit-test| test-also=--wasm-test-serialization; skip-if: !wasmGcEnabled()
+
+let {run} = wasmEvalText(`(module
+ (rec (type $$t1 (func (result (ref null $$t1)))))
+ (rec (type $$t2 (func (result (ref null $$t2)))))
+
+ (func $$f1 (type $$t1) (ref.null $$t1))
+ (func $$f2 (type $$t2) (ref.null $$t2))
+ (table funcref (elem $$f1 $$f2))
+
+ (func (export "run")
+ (call_indirect (type $$t2) (i32.const 0))
+ drop
+ )
+)`).exports;
+run();
diff --git a/js/src/jit-test/tests/wasm/gc/call-indirect-subtyping.js b/js/src/jit-test/tests/wasm/gc/call-indirect-subtyping.js
new file mode 100644
index 0000000000..4301621a8c
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/call-indirect-subtyping.js
@@ -0,0 +1,137 @@
+// |jit-test| test-also=--wasm-tail-calls; skip-if: !wasmGcEnabled()
+
+// Test that call_indirect will respect subtyping by defining a bunch of types
+// and checking every combination of (expected, actual) type.
+//
+// NOTE: Several of these types are identical to each other to test
+// canonicalization as well, and this causes some bloat in the 'subtypeOf'
+// lists.
+const TESTS = [
+ {
+ // (type 0) (equivalent to 1)
+ type: `(type (sub (func)))`,
+ subtypeOf: [0, 1],
+ },
+ {
+ // (type 1) (equivalent to 0)
+ type: `(rec (type (sub (func))))`,
+ subtypeOf: [0, 1],
+ },
+ {
+ // (type 2)
+ type: `(rec (type (sub (func))) (type (sub (func))))`,
+ subtypeOf: [2],
+ },
+ {
+ // (type 3)
+ // Hack entry of previous to capture that it actually defines
+ // two types in the recursion group.
+ type: undefined,
+ subtypeOf: [3],
+ },
+ {
+ // (type 4) (equivalent to 7)
+ type: `(type (sub 0 (func)))`,
+ subtypeOf: [0, 1, 4, 7],
+ },
+ {
+ // (type 5) (equivalent to 8)
+ type: `(type (sub 4 (func)))`,
+ subtypeOf: [0, 1, 4, 5, 7, 8],
+ },
+ {
+ // (type 6)
+ type: `(type (sub 5 (func)))`,
+ subtypeOf: [0, 1, 4, 5, 6, 7, 8],
+ },
+ {
+ // (type 7) (equivalent to 4)
+ type: `(type (sub 0 (func)))`,
+ subtypeOf: [0, 1, 4, 7],
+ },
+ {
+ // (type 8) (equivalent to 5)
+ type: `(type (sub 7 (func)))`,
+ subtypeOf: [0, 1, 4, 5, 7, 8],
+ },
+ {
+ // (type 9) - a final type that has an immediate form
+ type: `(type (func))`,
+ subtypeOf: [9],
+ }
+];
+
+// Build a module with all the types, functions with those types, and functions
+// that call_indirect with those types, and a table with all the functions in
+// it.
+let typeSection = '';
+let importedFuncs = '';
+let definedFuncs = '';
+let callIndirectFuncs = '';
+let returnCallIndirectFuncs = '';
+let i = 0;
+for (let {type} of TESTS) {
+ if (type) {
+ typeSection += type + '\n';
+ }
+ importedFuncs += `(func \$import${i} (import "" "import${i}") (type ${i}))\n`;
+ definedFuncs += `(func \$define${i} (export "define${i}") (type ${i}))\n`;
+ callIndirectFuncs += `(func (export "call_indirect ${i}") (param i32)
+ (drop (ref.cast (ref ${i}) (table.get local.get 0)))
+ (call_indirect (type ${i}) local.get 0)
+ )\n`;
+ if (wasmTailCallsEnabled()) {
+ returnCallIndirectFuncs += `(func (export "return_call_indirect ${i}") (param i32)
+ (drop (ref.cast (ref ${i}) (table.get local.get 0)))
+ (return_call_indirect (type ${i}) local.get 0)
+ )\n`;
+ }
+ i++;
+}
+let moduleText = `(module
+ ${typeSection}
+ ${importedFuncs}
+ ${definedFuncs}
+ ${callIndirectFuncs}
+ ${returnCallIndirectFuncs}
+ (table
+ (export "table")
+ funcref
+ (elem ${TESTS.map((x, i) => `\$import${i} \$define${i}`).join(" ")})
+ )
+)`;
+
+// Now go over every combination of (actual, expected). In this case the caller
+// (which does the call_indirect) specifies expected and the callee will be the
+// actual.
+let imports = {
+ "": Object.fromEntries(TESTS.map((x, i) => [`import${i}`, () => {}])),
+};
+let exports = wasmEvalText(moduleText, imports).exports;
+for (let callerTypeIndex = 0; callerTypeIndex < TESTS.length; callerTypeIndex++) {
+ for (let calleeTypeIndex = 0; calleeTypeIndex < TESTS.length; calleeTypeIndex++) {
+ let calleeType = TESTS[calleeTypeIndex];
+
+ // If the callee (actual) is a subtype of caller (expected), then this
+ // should succeed.
+ let shouldPass = calleeType.subtypeOf.includes(callerTypeIndex);
+
+ let calleeImportFuncIndex = calleeTypeIndex * 2;
+ let calleeDefinedFuncIndex = calleeTypeIndex * 2 + 1;
+
+ // print(`expected (type ${callerTypeIndex}) (actual ${calleeTypeIndex})`);
+ let test = () => {
+ exports[`call_indirect ${callerTypeIndex}`](calleeImportFuncIndex)
+ exports[`call_indirect ${callerTypeIndex}`](calleeDefinedFuncIndex)
+ if (wasmTailCallsEnabled()) {
+ exports[`return_call_indirect ${callerTypeIndex}`](calleeImportFuncIndex)
+ exports[`return_call_indirect ${callerTypeIndex}`](calleeDefinedFuncIndex)
+ }
+ };
+ if (shouldPass) {
+ test();
+ } else {
+ assertErrorMessage(test, WebAssembly.RuntimeError, /mismatch|cast/);
+ }
+ }
+}
diff --git a/js/src/jit-test/tests/wasm/gc/cast-abstract.js b/js/src/jit-test/tests/wasm/gc/cast-abstract.js
new file mode 100644
index 0000000000..4dd7e73e49
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/cast-abstract.js
@@ -0,0 +1,298 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+load(libdir + "wasm-binary.js");
+
+// Replace this with a string like 'non-null (ref $s1) -> (ref any) -> (ref any)' to test exactly one specific case. Makes debugging easier.
+const specificTest = '';
+
+const preamble = `
+ (type $s1 (sub (struct)))
+ (type $s2 (sub $s1 (struct (field i32))))
+ (type $a1 (sub (array (ref null $s1))))
+ (type $a2 (sub $a1 (array (ref null $s2))))
+ (type $ft1 (sub (func (param (ref $s1)) (result (ref null $a1)))))
+ (type $ft2 (sub $ft1 (func (param (ref null $s1)) (result (ref null $a2)))))
+ (type $ft3 (sub (func (param (ref $a2)) (result i32))))
+ (type $ft4 (sub $ft3 (func (param (ref $a1)) (result i32))))
+
+ (func $f1 (type $ft1) ref.null $a1)
+ (func $f2 (type $ft2) ref.null $a2)
+ (func $f3 (type $ft3) i32.const 0)
+ (func $f4 (type $ft4) i32.const 0)
+ (elem declare func $f1 $f2 $f3 $f4) ;; allow funcs to be ref.func'd
+`;
+
+// All the concrete subtype relationships present in the preamble.
+// [x, y] means x <: y.
+const subtypes = [
+ ['$s2', '$s1'],
+ ['$a2', '$a1'],
+ ['$ft2', '$ft1'],
+ ['$ft4', '$ft3'],
+];
+
+const any = { name: 'any', top: 'anyref' };
+const eq = { name: 'eq', top: 'anyref' };
+const struct = { name: 'struct', top: 'anyref' };
+const array = { name: 'array', top: 'anyref' };
+const s1 = {
+ index: 0,
+ name: '$s1',
+ make: 'struct.new_default $s1',
+ top: 'anyref',
+};
+const s2 = {
+ index: 1,
+ name: '$s2',
+ make: 'struct.new_default $s2',
+ top: 'anyref',
+};
+const a1 = {
+ index: 2,
+ name: '$a1',
+ make: '(array.new_default $a1 (i32.const 10))',
+ top: 'anyref',
+};
+const a2 = {
+ index: 3,
+ name: '$a2',
+ make: '(array.new_default $a2 (i32.const 10))',
+ top: 'anyref',
+};
+const i31 = { name: 'i31', make: '(ref.i31 (i32.const 123))', top: 'anyref' };
+const none = { name: 'none', none: true, top: 'anyref' };
+
+const func = { name: 'func', top: 'funcref' };
+const ft1 = { index: 4, name: '$ft1', make: 'ref.func $f1', top: 'funcref' };
+const ft2 = { index: 5, name: '$ft2', make: 'ref.func $f2', top: 'funcref' };
+const ft3 = { index: 6, name: '$ft3', make: 'ref.func $f3', top: 'funcref' };
+const ft4 = { index: 7, name: '$ft4', make: 'ref.func $f4', top: 'funcref' };
+const nofunc = { name: 'nofunc', none: true, top: 'funcref' };
+
+const extern = { name: 'extern', make: '(extern.convert_any (struct.new_default $s1))', top: 'externref' }
+const noextern = { name: 'noextern', none: true, top: 'externref' }
+
+const typeSets = [
+ [any, eq, struct, s1, s2, none],
+ [any, eq, array, a1, a2, none],
+ [any, eq, s1, s2, a1, a2, none],
+ [any, eq, i31, none],
+ [any, eq, i31, s1, none],
+ [any, eq, i31, a1, none],
+
+ [func, ft1, ft2, nofunc],
+ [func, ft3, ft4, nofunc],
+ [func, ft1, ft2, ft3, ft4, nofunc],
+
+ [extern, noextern],
+];
+
+const nullables = [ // for example:
+ [true, true, true], // null $s1 -> null any -> null eq
+ [true, true, false], // null $s1 -> null any -> eq
+ [true, false, true], // null $s1 -> any -> null eq
+ [true, false, false], // null $s1 -> any -> eq
+ [false, true, true], // $s1 -> null any -> null eq
+ [false, true, false], // $s1 -> null any -> eq
+ [false, false, true], // $s1 -> any -> null eq
+ [false, false, false], // $s1 -> any -> eq
+]
+
+function isSubtype(src, dest) {
+ if (src.name === dest.name) {
+ return true;
+ }
+ for (const [src2, dest2] of subtypes) {
+ if (src.name === src2 && dest.name === dest2) {
+ return true;
+ }
+ }
+ return false;
+}
+
+let numCases = 0;
+
+// This will generate an enormous pile of test cases. All of these should be valid,
+// as in passing WebAssembly.validate, but some may not be good casts at runtime.
+for (const typeSet of typeSets) {
+ for (const start of typeSet) {
+ for (const middle of typeSet) {
+ for (const end of typeSet) {
+ for (const [nullable0, nullable1, nullable2] of nullables) {
+ for (const makeNull of [true, false]) {
+ const concrete0 = !!start.make;
+ const concrete1 = !!middle.make;
+ const concrete2 = !!end.make;
+
+ if (!concrete0 && !makeNull) {
+ // We can only start with null values for abstract types
+ continue;
+ }
+
+ if (!nullable0 && makeNull) {
+ // Can't use null as a valid value for a non-nullable type
+ continue;
+ }
+
+ numCases += 1;
+
+ let good1 = true;
+ let good2 = true;
+
+ // Null values will fail casts to non-nullable types
+ if (makeNull) {
+ if (!nullable1) {
+ good1 = false;
+ }
+ if (!nullable2) {
+ good2 = false;
+ }
+ }
+
+ // Bottom types can't represent non-null, so this will always fail
+ if (!makeNull) {
+ if (middle.none) {
+ good1 = false;
+ }
+ if (end.none) {
+ good2 = false;
+ }
+ }
+
+ // Concrete values are subject to subtyping relationships
+ if (!makeNull) {
+ if (concrete1 && !isSubtype(start, middle)) {
+ good1 = false;
+ }
+ if (concrete2 && !isSubtype(start, end)) {
+ good2 = false;
+ }
+ }
+
+ let emoji1 = good1 ? '✅' : '❌';
+ let emoji2 = good2 ? '✅' : '❌';
+
+ if (!good1) {
+ good2 = false;
+ emoji2 = '❓';
+ }
+
+ const name = `${makeNull ? 'null' : 'non-null'} (ref ${nullable0 ? 'null ' : ''}${start.name}) -> (ref ${nullable1 ? 'null ' : ''}${middle.name}) -> (ref ${nullable2 ? 'null ' : ''}${end.name})`;
+ if (specificTest && name != specificTest) {
+ continue;
+ }
+
+ print(`${emoji1}${emoji2} ${name}`);
+ const make = makeNull ? `ref.null ${start.name}` : start.make;
+ const type0 = `(ref ${nullable0 ? 'null ' : ''}${start.name})`;
+ const type1 = `(ref ${nullable1 ? 'null ' : ''}${middle.name})`;
+ const type2 = `(ref ${nullable2 ? 'null ' : ''}${end.name})`;
+ const moduleText = `(module
+ ${preamble}
+ (func (export "make") (result ${type0})
+ ${make}
+ )
+ (func (export "cast1") (param ${type0}) (result ${type1})
+ local.get 0
+ ref.cast ${type1}
+ )
+ (func (export "cast2") (param ${type1}) (result ${type2})
+ local.get 0
+ ref.cast ${type2}
+ )
+
+ (func (export "test1") (param ${type0}) (result i32)
+ local.get 0
+ ref.test ${type1}
+ )
+ (func (export "test2") (param ${type1}) (result i32)
+ local.get 0
+ ref.test ${type2}
+ )
+
+ ;; these are basically ref.test but with branches
+ (func (export "branch1") (param ${type0}) (result i32)
+ (block (result ${type1})
+ local.get 0
+ br_on_cast 0 ${start.top} ${type1}
+ drop
+ (return (i32.const 0))
+ )
+ drop
+ (return (i32.const 1))
+ )
+ (func (export "branch2") (param ${type1}) (result i32)
+ (block (result ${type2})
+ local.get 0
+ br_on_cast 0 ${middle.top} ${type2}
+ drop
+ (return (i32.const 0))
+ )
+ drop
+ (return (i32.const 1))
+ )
+ (func (export "branchfail1") (param ${type0}) (result i32)
+ (block (result ${middle.top})
+ local.get 0
+ br_on_cast_fail 0 ${start.top} ${type1}
+ drop
+ (return (i32.const 1))
+ )
+ drop
+ (return (i32.const 0))
+ )
+ (func (export "branchfail2") (param ${type1}) (result i32)
+ (block (result ${end.top})
+ local.get 0
+ br_on_cast_fail 0 ${middle.top} ${type2}
+ drop
+ (return (i32.const 1))
+ )
+ drop
+ (return (i32.const 0))
+ )
+ )`;
+
+ function assertCast(func, good) {
+ if (good) {
+ return [func(), true];
+ } else {
+ assertErrorMessage(func, WebAssembly.RuntimeError, /bad cast/);
+ return [null, false];
+ }
+ }
+
+ try {
+ // The casts are split up so the stack trace will show you which cast is failing.
+ const {
+ make,
+ cast1, cast2,
+ test1, test2,
+ branch1, branch2,
+ branchfail1, branchfail2,
+ } = wasmEvalText(moduleText).exports;
+
+ const val = make();
+ const [res1, ok1] = assertCast(() => cast1(val), good1);
+ assertEq(test1(val), good1 ? 1 : 0);
+ assertEq(branch1(val), good1 ? 1 : 0);
+ assertEq(branchfail1(val), good1 ? 1 : 0);
+ if (ok1) {
+ assertCast(() => cast2(res1), good2);
+ assertEq(test2(res1), good2 ? 1 : 0);
+ assertEq(branch2(res1), good2 ? 1 : 0);
+ assertEq(branchfail2(res1), good2 ? 1 : 0);
+ }
+ } catch (e) {
+ print("Failed! Module text was:");
+ print(moduleText);
+ throw e;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+print(`we hope you have enjoyed these ${numCases} test cases 😁`);
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..83a6d105b6
--- /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 (field i64 i32 i64 i32)))
+
+ (func (export "refTest") (param externref) (result i32)
+ local.get 0
+ any.convert_extern
+ ref.test (ref 0)
+ )
+ (func (export "refCast") (param externref) (result i32)
+ local.get 0
+ any.convert_extern
+ ref.cast (ref null 0)
+ drop
+ i32.const 0
+ )
+ (func (export "brOnCast") (param externref) (result i32)
+ (block (result (ref 0))
+ local.get 0
+ any.convert_extern
+ 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
+ any.convert_extern
+ 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..a71a589db8
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/casting.js
@@ -0,0 +1,116 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// 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} (sub (struct)))\n`;
+ } else {
+ typeSection += `(type \$${name} (sub \$${type.super} (struct)))\n`;
+ }
+ funcSection += `
+ (func (export "new${name}") (result externref)
+ struct.new_default \$${name}
+ extern.convert_any
+ )
+ (func (export "is${name}") (param externref) (result i32)
+ local.get 0
+ any.convert_extern
+ 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..e6d978cc44
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/directives.txt
@@ -0,0 +1 @@
+|jit-test| --wasm-gc; test-also=--wasm-compiler=optimizing; test-also=--wasm-compiler=baseline; 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..71114741fc
--- /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
+// - WasmGcObject 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(wasmGcReadField(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 wasmGcReadField(x, 0), "object");
+ assertEq(wasmGcReadField(x, 0).x, 0);
+ assertEq(typeof wasmGcReadField(x, 1), "object");
+ assertEq(wasmGcReadField(x, 1).x, 1);
+ assertEq(typeof wasmGcReadField(x, 2), "object");
+ assertEq(wasmGcReadField(x, 2).x, 2);
+ assertEq(typeof wasmGcReadField(x, 3), "object");
+ assertEq(wasmGcReadField(x, 3).x, 3);
+ assertEq(typeof wasmGcReadField(x, 4), "object");
+ assertEq(wasmGcReadField(x, 4).x, 4);
+ assertEq(typeof wasmGcReadField(x, 5), "object");
+ assertEq(wasmGcReadField(x, 5).x, 5);
+ assertEq(typeof wasmGcReadField(x, 6), "object");
+ assertEq(wasmGcReadField(x, 6).x, 6);
+ assertEq(typeof wasmGcReadField(x, 7), "object");
+ assertEq(wasmGcReadField(x, 7).x, 7);
+ assertEq(typeof wasmGcReadField(x, 8), "object");
+ assertEq(wasmGcReadField(x, 8).x, 8);
+ assertEq(typeof wasmGcReadField(x, 9), "object");
+ assertEq(wasmGcReadField(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..edfcbe9e4f
--- /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
+ any.convert_extern
+ )
+ (func (param anyref) (result externref)
+ local.get 0
+ extern.convert_any
+ )
+ (func (param (ref extern)) (result (ref any))
+ local.get 0
+ any.convert_extern
+ )
+ (func (param (ref any)) (result (ref extern))
+ local.get 0
+ extern.convert_any
+ )
+ (func (result (ref any))
+ unreachable
+ any.convert_extern
+ )
+ (func (result (ref extern))
+ unreachable
+ extern.convert_any
+ )
+)`);
+
+// Output type is nullable if the input type is nullable
+wasmFailValidateText(`(module
+ (func (param externref) (result (ref any))
+ local.get 0
+ any.convert_extern
+ )
+)`, /expected/);
+wasmFailValidateText(`(module
+ (func (param anyref) (result (ref extern))
+ local.get 0
+ extern.convert_any
+ )
+)`, /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
+ any.convert_extern
+ extern.convert_any
+ )
+)`).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)
+ (any.convert_extern
+ (extern.convert_any
+ 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)
+ (any.convert_extern
+ (extern.convert_any
+ local.get 0
+ )
+ )
+ )
+ )
+ )
+)`).exports;
+
+assertEq(testStruct(), 1);
+assertEq(testArray(), 1);
diff --git a/js/src/jit-test/tests/wasm/gc/final_types.js b/js/src/jit-test/tests/wasm/gc/final_types.js
new file mode 100644
index 0000000000..7787a1d3bb
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/final_types.js
@@ -0,0 +1,48 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Validate that incorrect subtyping examples are failing as expected
+const typeError = /incompatible super type/;
+
+var code =`
+(module
+ (type $A (sub (struct (field i32))))
+ (type $B (sub $A (struct (field i32) (field i32))))
+ (type $C (sub $B (struct (field i32) (field i32) (field i64))))
+
+ ;; Can't declare a super type with the final flag
+ (type $D (sub final $C (struct (field i32) (field i32) (field f32))))
+ (type $E (sub $D (struct (field i32) (field i32) (field f32))))
+)`;
+
+wasmFailValidateText(code, typeError);
+
+code =`
+(module
+ (type $A (sub (struct (field i32))))
+ (type $B (sub $A (struct (field i32) (field i32))))
+ (type $C (sub $B (struct (field i32) (field i32))))
+
+ ;; $D is final, without any supertype
+ (type $D (sub final (struct (field i32) (field i32) (field f32))))
+ (type $E (sub $D (struct (field i32) (field i32) (field f32))))
+)`;
+
+wasmFailValidateText(code, typeError);
+
+// Types are final by default.
+code =`
+(module
+ (type $A (struct (field i32)))
+ (type $B (sub $A (struct (field i32) (field i32))))
+)`;
+
+wasmFailValidateText(code, typeError);
+
+// A super type index must be stricly less than the current type.
+code =`
+(module
+ (type $A (sub (struct (field i32))))
+ (type $B (sub $B (struct (field i32) (field i32))))
+)`;
+
+wasmFailValidateText(code, /invalid super type index/);
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..3ef0d17dd3
--- /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 (sub (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 (sub (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 (sub (func (param (ref $A) (ref $A)) (result (ref $B)))))
+ (type $func4 (sub $func3 (func (param (ref $A) (ref $A)) (result (ref $C)))))
+ (type $func5 (sub (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 (sub (func (param (ref $A) (ref $C)) (result (ref $C)))))
+ (type $func8 (sub $func7 (func (param (ref $A) (ref $B)) (result (ref $C)))))
+ (type $func9 (sub (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 (sub (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 (sub (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 (sub (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 (sub (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 (sub (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/globals.js b/js/src/jit-test/tests/wasm/gc/globals.js
new file mode 100644
index 0000000000..48fe4023de
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/globals.js
@@ -0,0 +1,233 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Globals have identity now
+{
+ const { g, same } = wasmEvalText(`(module
+ (type (struct))
+ (global (export "g") eqref (struct.new 0))
+ (func $same (export "same") (param eqref) (result i32)
+ (ref.eq (local.get 0) (global.get 0))
+ )
+ (func $sanity
+ (if (call $same (global.get 0)) (then return))
+ unreachable
+ )
+ (start $sanity)
+ )`).exports;
+
+ assertEq(same(g.value), 1, "global had different identity when exported");
+}
+
+// Subtypes with func refs
+{
+ wasmEvalText(`(module
+ (type $s1 (struct))
+ (type $t1 (sub (func (param (ref $s1)))))
+ (type $t2 (sub $t1 (func (param (ref null $s1)))))
+ (func $a (type $t1))
+ (func $b (type $t2))
+
+ (global (ref $t1) ref.func $a)
+ (global (ref null $t1) ref.func $a)
+ (global (ref func) ref.func $a)
+ (global (ref null func) ref.func $a)
+
+ (global (ref $t2) ref.func $b)
+ (global (ref null $t2) ref.func $b)
+ (global (ref $t1) ref.func $b)
+ (global (ref null $t1) ref.func $b)
+ (global (ref func) ref.func $b)
+ (global (ref null func) ref.func $b)
+ )`);
+
+ assertErrorMessage(() => wasmEvalText(`(module
+ (type $s1 (struct))
+ (type $t1 (func (param (ref $s1))))
+ (type $t2 (func (param (ref null $s1)))) ;; not a subtype of t1
+ (func $a (type $t2))
+ (global (ref $t1) ref.func $a)
+ )`), WebAssembly.CompileError, /type mismatch/);
+}
+
+// Subtypes with struct refs
+{
+ wasmEvalText(`(module
+ (type $t1 (sub (struct)))
+ (type $t2 (sub $t1 (struct (field i32))))
+
+ (global (ref $t1) struct.new_default $t1)
+ (global (ref null $t1) struct.new_default $t1)
+ (global (ref struct) struct.new_default $t1)
+ (global (ref null struct) struct.new_default $t1)
+ (global (ref eq) struct.new_default $t1)
+ (global (ref null eq) struct.new_default $t1)
+ (global (ref any) struct.new_default $t1)
+ (global (ref null any) struct.new_default $t1)
+
+ (global (ref $t2) struct.new_default $t2)
+ (global (ref null $t2) struct.new_default $t2)
+ (global (ref $t1) struct.new_default $t2)
+ (global (ref null $t1) struct.new_default $t2)
+ (global (ref struct) struct.new_default $t2)
+ (global (ref null struct) struct.new_default $t2)
+ (global (ref eq) struct.new_default $t2)
+ (global (ref null eq) struct.new_default $t2)
+ (global (ref any) struct.new_default $t2)
+ (global (ref null any) struct.new_default $t2)
+ )`);
+
+ assertErrorMessage(() => wasmEvalText(`(module
+ (type $t1 (struct))
+ (type $t2 (struct (field i32))) ;; not a subtype of t1
+ (global (ref $t1) struct.new_default $t2)
+ )`), WebAssembly.CompileError, /type mismatch/);
+}
+
+// Subtypes with array refs
+{
+ wasmEvalText(`(module
+ (type $s (struct))
+ (type $t1 (sub (array (ref null $s))))
+ (type $t2 (sub $t1 (array (ref $s))))
+
+ (global (ref $t1) array.new_fixed $t1 0)
+ (global (ref null $t1) array.new_fixed $t1 0)
+ (global (ref array) array.new_fixed $t1 0)
+ (global (ref null array) array.new_fixed $t1 0)
+ (global (ref eq) array.new_fixed $t1 0)
+ (global (ref null eq) array.new_fixed $t1 0)
+ (global (ref any) array.new_fixed $t1 0)
+ (global (ref null any) array.new_fixed $t1 0)
+
+ (global (ref $t2) array.new_fixed $t2 0)
+ (global (ref null $t2) array.new_fixed $t2 0)
+ (global (ref $t1) array.new_fixed $t2 0)
+ (global (ref null $t1) array.new_fixed $t2 0)
+ (global (ref array) array.new_fixed $t2 0)
+ (global (ref null array) array.new_fixed $t2 0)
+ (global (ref eq) array.new_fixed $t2 0)
+ (global (ref null eq) array.new_fixed $t2 0)
+ (global (ref any) array.new_fixed $t2 0)
+ (global (ref null any) array.new_fixed $t2 0)
+ )`);
+
+ assertErrorMessage(() => wasmEvalText(`(module
+ (type $s (struct))
+ (type $t1 (array (ref null $s)))
+ (type $t2 (array (ref $s))) ;; not a subtype of t1
+ (global (ref $t1) array.new_fixed $t2 0)
+ )`), WebAssembly.CompileError, /type mismatch/);
+}
+
+// Subtypes should be respected on imports and exports
+{
+ const { struct, mut_struct, eq, mut_eq, any, mut_any } = wasmEvalText(`(module
+ (type (struct))
+ (global (export "struct") structref (struct.new 0))
+ (global (export "mut_struct") (mut structref) (struct.new 0))
+ (global (export "eq") eqref (struct.new 0))
+ (global (export "mut_eq") (mut eqref) (struct.new 0))
+ (global (export "any") anyref (struct.new 0))
+ (global (export "mut_any") (mut anyref) (struct.new 0))
+ )`).exports;
+
+ function importGlobalIntoType(g, t) {
+ wasmEvalText(`(module
+ (global (import "test" "g") ${t})
+ )`, { "test": { "g": g } });
+ }
+
+ importGlobalIntoType(struct, `structref`);
+ importGlobalIntoType(struct, `eqref`);
+ importGlobalIntoType(struct, `anyref`);
+
+ importGlobalIntoType(mut_struct, `(mut structref)`);
+ assertErrorMessage(() => importGlobalIntoType(mut_struct, `(mut eqref)`), WebAssembly.LinkError, /global type mismatch/);
+ assertErrorMessage(() => importGlobalIntoType(mut_struct, `(mut anyref)`), WebAssembly.LinkError, /global type mismatch/);
+
+ assertErrorMessage(() => importGlobalIntoType(eq, `structref`), WebAssembly.LinkError, /global type mismatch/);
+ importGlobalIntoType(eq, `eqref`);
+ importGlobalIntoType(eq, `anyref`);
+
+ assertErrorMessage(() => importGlobalIntoType(mut_eq, `(mut structref)`), WebAssembly.LinkError, /global type mismatch/);
+ importGlobalIntoType(mut_eq, `(mut eqref)`);
+ assertErrorMessage(() => importGlobalIntoType(mut_eq, `(mut anyref)`), WebAssembly.LinkError, /global type mismatch/);
+
+ assertErrorMessage(() => importGlobalIntoType(any, `structref`), WebAssembly.LinkError, /global type mismatch/);
+ assertErrorMessage(() => importGlobalIntoType(any, `eqref`), WebAssembly.LinkError, /global type mismatch/);
+ importGlobalIntoType(any, `anyref`);
+
+ assertErrorMessage(() => importGlobalIntoType(mut_any, `(mut structref)`), WebAssembly.LinkError, /global type mismatch/);
+ assertErrorMessage(() => importGlobalIntoType(mut_any, `(mut eqref)`), WebAssembly.LinkError, /global type mismatch/);
+ importGlobalIntoType(mut_any, `(mut anyref)`);
+}
+
+// Importing globals with ref types
+{
+ const { struct, array, func } = wasmEvalText(`(module
+ (type (struct))
+ (type (array i32))
+ (func $f)
+ (global (export "struct") structref (struct.new 0))
+ (global (export "array") arrayref (array.new_fixed 1 0))
+ (global (export "func") funcref (ref.func $f))
+ )`).exports;
+
+ function importValueIntoType(v, t) {
+ wasmEvalText(`(module
+ (global (import "test" "v") ${t})
+ )`, { "test": { "v": v } });
+ }
+
+ assertErrorMessage(() => importValueIntoType(struct.value, `(mut structref)`), WebAssembly.LinkError, /global mutability mismatch/);
+
+ importValueIntoType(null, `structref`);
+ assertErrorMessage(() => importValueIntoType(null, `(ref struct)`), TypeError, /cannot pass null/);
+ assertErrorMessage(() => importValueIntoType(0, `structref`), TypeError, /can only pass/);
+ importValueIntoType(struct.value, `structref`);
+ assertErrorMessage(() => importValueIntoType(array.value, `structref`), TypeError, /can only pass/);
+
+ importValueIntoType(null, `i31ref`);
+ assertErrorMessage(() => importValueIntoType(null, `(ref i31)`), TypeError, /cannot pass null/);
+ importValueIntoType(0, `i31ref`);
+ assertErrorMessage(() => importValueIntoType(0.1, `i31ref`), TypeError, /can only pass/);
+ assertErrorMessage(() => importValueIntoType("test", `i31ref`), TypeError, /can only pass/);
+ assertErrorMessage(() => importValueIntoType(struct.value, `i31ref`), TypeError, /can only pass/);
+
+ importValueIntoType(null, `eqref`);
+ assertErrorMessage(() => importValueIntoType(null, `(ref eq)`), TypeError, /cannot pass null/);
+ assertErrorMessage(() => importValueIntoType(undefined, `(ref eq)`), TypeError, /can only pass/);
+ importValueIntoType(0, `eqref`);
+ assertErrorMessage(() => importValueIntoType(0.1, `eqref`), TypeError, /can only pass/);
+ assertErrorMessage(() => importValueIntoType((x)=>x, `eqref`), TypeError, /can only pass/);
+ assertErrorMessage(() => importValueIntoType("test", `eqref`), TypeError, /can only pass/);
+ importValueIntoType(struct.value, `eqref`);
+ assertErrorMessage(() => importValueIntoType(func.value, `eqref`), TypeError, /can only pass/);
+
+ importValueIntoType(null, `anyref`);
+ assertErrorMessage(() => importValueIntoType(null, `(ref any)`), TypeError, /cannot pass null/);
+ importValueIntoType(undefined, `(ref any)`);
+ importValueIntoType(0, `anyref`);
+ importValueIntoType(0.1, `anyref`);
+ importValueIntoType((x)=>x, `anyref`)
+ importValueIntoType("test", `anyref`);
+ importValueIntoType(struct.value, `anyref`);
+ importValueIntoType(func.value, `anyref`);
+
+ importValueIntoType(null, `externref`);
+ assertErrorMessage(() => importValueIntoType(null, `(ref extern)`), TypeError, /cannot pass null/);
+ importValueIntoType(undefined, `(ref extern)`);
+ importValueIntoType(0, `externref`);
+ importValueIntoType(0.1, `externref`);
+ importValueIntoType((x)=>x, `externref`)
+ importValueIntoType("test", `externref`);
+ importValueIntoType(struct.value, `externref`);
+ importValueIntoType(func.value, `externref`);
+
+ importValueIntoType(null, `funcref`);
+ assertErrorMessage(() => importValueIntoType(null, `(ref func)`), TypeError, /cannot pass null/);
+ assertErrorMessage(() => importValueIntoType(0, `funcref`), TypeError, /can only pass/);
+ assertErrorMessage(() => importValueIntoType((x)=>x, `funcref`), TypeError, /can only pass/);
+ importValueIntoType(func.value, `funcref`)
+ importValueIntoType(func.value, `(ref func)`)
+}
diff --git a/js/src/jit-test/tests/wasm/gc/i31ref.js b/js/src/jit-test/tests/wasm/gc/i31ref.js
new file mode 100644
index 0000000000..65f2fccc3f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/i31ref.js
@@ -0,0 +1,164 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+let InvalidI31Values = [
+ null,
+ Number.EPSILON,
+ Number.MAX_SAFE_INTEGER,
+ Number.MIN_SAFE_INTEGER,
+ Number.MIN_VALUE,
+ Number.MAX_VALUE,
+ Number.NaN,
+ -0,
+ // Number objects are not coerced
+ ...WasmI31refValues.map(n => new Number(n)),
+ // Non-integers are not valid
+ ...WasmI31refValues.map(n => n + 0.1),
+ ...WasmI31refValues.map(n => n + 0.5),
+ ...WasmI31refValues.map(n => n + 0.9)
+];
+
+// Return an equivalent JS number for if a JS number is converted to i31ref
+// and then zero extended back to 32-bits.
+function valueAsI31GetU(value) {
+ // Zero extending will drop the sign bit, if any
+ return value & 0x7fffffff;
+}
+
+let identity = (n) => n;
+
+let {
+ castFromAnyref,
+ castFromExternref,
+ refI31,
+ refI31Identity,
+ i31GetU,
+ i31GetS,
+ i31EqualsI31,
+ i31EqualsEq
+} = wasmEvalText(`(module
+ (func $identity (import "" "identity") (param anyref) (result anyref))
+
+ (func (export "castFromAnyref") (param anyref) (result i32)
+ local.get 0
+ ref.test (ref i31)
+ )
+ (func (export "castFromExternref") (param externref) (result i32)
+ local.get 0
+ any.convert_extern
+ ref.test (ref i31)
+ )
+ (func (export "refI31") (param i32) (result anyref)
+ local.get 0
+ ref.i31
+ )
+ (func (export "refI31Identity") (param i32) (result anyref)
+ local.get 0
+ ref.i31
+ call $identity
+ )
+ (func (export "i31GetU") (param i32) (result i32)
+ local.get 0
+ ref.i31
+ i31.get_u
+ )
+ (func (export "i31GetS") (param i32) (result i32)
+ local.get 0
+ ref.i31
+ i31.get_s
+ )
+ (func (export "i31EqualsI31") (param i32) (param i32) (result i32)
+ (ref.eq
+ (ref.i31 local.get 0)
+ (ref.i31 local.get 1)
+ )
+ )
+ (func (export "i31EqualsEq") (param i32) (param eqref) (result i32)
+ (ref.eq
+ (ref.i31 local.get 0)
+ local.get 1
+ )
+ )
+)`, {"": {identity}}).exports;
+
+// Test that wasm will represent JS number values that are 31-bit integers as
+// an i31ref
+for (let i of WasmI31refValues) {
+ assertEq(castFromAnyref(i), 1);
+ assertEq(castFromExternref(i), 1);
+}
+
+// Test that wasm will not represent a JS value that is not a 31-bit number as
+// an i31ref
+for (let i of InvalidI31Values) {
+ assertEq(castFromAnyref(i), 0);
+ assertEq(castFromExternref(i), 0);
+}
+
+// Test that we can roundtrip 31-bit integers through the i31ref type
+// faithfully.
+for (let i of WasmI31refValues) {
+ assertEq(refI31(i), i);
+ assertEq(refI31Identity(i), i);
+ assertEq(i31GetU(i), valueAsI31GetU(i));
+ assertEq(i31GetS(i), i);
+}
+
+// Test that i31ref values are truncated when given a 32-bit value
+for (let i of WasmI31refValues) {
+ let adjusted = i | 0x80000000;
+ assertEq(refI31(adjusted), i);
+}
+
+// Test that comparing identical i31 values works
+for (let a of WasmI31refValues) {
+ for (let b of WasmI31refValues) {
+ assertEq(!!i31EqualsI31(a, b), a === b);
+ }
+}
+
+// Test that an i31ref is never mistaken for a different kind of reference
+for (let a of WasmI31refValues) {
+ for (let b of WasmEqrefValues) {
+ assertEq(!!i31EqualsEq(a, b), a === b);
+ }
+}
+
+// Test that i32 values get wrapped correctly
+const bigI32Tests = [
+ {
+ input: MaxI31refValue + 1,
+ expected: MinI31refValue,
+ },
+ {
+ input: MinI31refValue - 1,
+ expected: MaxI31refValue,
+ },
+]
+for (const {input, expected} of bigI32Tests) {
+ const { get, getElem } = wasmEvalText(`(module
+ (func (export "get") (param i32) (result i32)
+ (i31.get_s (ref.i31 (local.get 0)))
+ )
+
+ (table i31ref (elem (item (ref.i31 (i32.const ${input})))))
+ (func (export "getElem") (result i32)
+ (i31.get_s (table.get 0 (i32.const 0)))
+ )
+ )`).exports;
+ assertEq(get(input), expected);
+ assertEq(getElem(), expected);
+}
+
+const { i31GetU_null, i31GetS_null } = wasmEvalText(`(module
+ (func (export "i31GetU_null") (result i32)
+ ref.null i31
+ i31.get_u
+ )
+ (func (export "i31GetS_null") (result i32)
+ ref.null i31
+ i31.get_s
+ )
+)`).exports;
+
+assertErrorMessage(() => i31GetU_null(), WebAssembly.RuntimeError, /dereferencing null pointer/);
+assertErrorMessage(() => i31GetS_null(), WebAssembly.RuntimeError, /dereferencing null pointer/);
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..dff4b5c028
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/init-expr.js
@@ -0,0 +1,281 @@
+// |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(wasmGcReadField(result, 0), 2);
+assertEq(wasmGcReadField(result, 1), new Float32Array([3.140000104904175])[0]);
+result = structNewDefault();
+assertEq(wasmGcReadField(result, 0), 0);
+assertEq(wasmGcReadField(result, 1), 0);
+result = structLarge();
+assertEq(wasmGcReadField(result, 2), 3n);
+assertEq(wasmGcReadField(result, 19), 19);
+
+// array.new, array.new_default, array.new_fixed, and array.new_elem
+
+const { arrayNew, arrayNewDefault, arrayNewFixed, arrayNewElem } = 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)))
+
+ (elem $a3e (ref null $r)
+ (item (struct.new $r (i32.const 1) (f32.const 2.0)))
+ (item (struct.new $r (i32.const 3) (f32.const 4.0)))
+ (item (struct.new $r (i32.const 5) (f32.const 6.0)))
+ )
+
+ (func (export "arrayNew") (result eqref) global.get $g1)
+ (func (export "arrayNewDefault") (result eqref) global.get $g2)
+ (func (export "arrayNewFixed") (result eqref) global.get $g3)
+ (func (export "arrayNewElem") (result eqref)
+ (array.new_elem $a3 $a3e (i32.const 0) (i32.const 3))
+ )
+ )`).exports;
+
+result = arrayNew();
+assertEq(wasmGcArrayLength(result), 3);
+assertEq(wasmGcReadField(result, 0), 3.14);
+assertEq(wasmGcReadField(result, 2), 3.14);
+
+result = arrayNewDefault();
+assertEq(wasmGcArrayLength(result), 2);
+assertEq(wasmGcReadField(result, 1), 0);
+
+result = arrayNewFixed();
+assertEq(wasmGcArrayLength(result), 2);
+assertEq(wasmGcReadField(wasmGcReadField(result, 0), 0), 10);
+assertEq(wasmGcReadField(wasmGcReadField(result, 0), 1), 16);
+assertEq(wasmGcReadField(result, 1), null);
+
+result = arrayNewElem();
+assertEq(wasmGcArrayLength(result), 3);
+assertEq(wasmGcReadField(wasmGcReadField(result, 0), 0), 1);
+assertEq(wasmGcReadField(wasmGcReadField(result, 0), 1), 2);
+assertEq(wasmGcReadField(wasmGcReadField(result, 1), 0), 3);
+assertEq(wasmGcReadField(wasmGcReadField(result, 1), 1), 4);
+assertEq(wasmGcReadField(wasmGcReadField(result, 2), 0), 5);
+assertEq(wasmGcReadField(wasmGcReadField(result, 2), 1), 6);
+
+// any.convert_extern and extern.convert_any
+
+let {testString, testArray} = wasmEvalText(`(module
+ (type $array (array i32))
+ (import "env" "s" (global $s (ref extern)))
+ (global $s' (ref extern) (extern.convert_any (any.convert_extern (global.get $s))))
+ (func (export "testString") (result (ref extern))
+ (global.get $s'))
+ (global $a (ref $array) (array.new_fixed $array 1 (i32.const 0)))
+ (global $a' (ref any) (any.convert_extern (extern.convert_any (global.get $a))))
+ (func (export "testArray") (result i32)
+ (ref.eq (global.get $a) (ref.cast (ref eq) (global.get $a'))))
+)`, {env:{s:"abc"}}).exports;
+
+assertEq(testString(), 'abc');
+assertEq(testArray(), 1);
+
+wasmFailValidateText(`(module
+ (global $g (ref extern) (extern.convert_any (any.convert_extern (ref.null extern))))
+)`, /expected/);
+wasmFailValidateText(`(module
+ (global $g (ref extern) (any.convert_extern (extern.convert_any (ref.null any))))
+)`, /expected/);
+
+// Simple table initialization
+{
+ const { t1, t2, t1init } = wasmEvalText(`(module
+ (type $s (struct (field i32)))
+ (type $a1 (array f64))
+ (type $a2 (array (ref null $s)))
+
+ ;; passive segment
+ (elem $e1 anyref
+ (item (struct.new $s (i32.const 123)))
+ (item (array.new $a1 (f64.const 234) (i32.const 3)))
+ (item (array.new_default $a2 (i32.const 3)))
+ (item (ref.i31 (i32.const 345)))
+ )
+ (table $t1 (export "t1") 4 4 anyref)
+
+ ;; active segment
+ (table $t2 (export "t2") anyref (elem
+ (item (struct.new $s (i32.const 321)))
+ (item (array.new $a1 (f64.const 432) (i32.const 3)))
+ (item (array.new_default $a2 (i32.const 3)))
+ (item (ref.i31 (i32.const 543)))
+ ))
+
+ (func (export "t1init") (table.init $t1 $e1 (i32.const 0) (i32.const 0) (i32.const 4)))
+ )`).exports;
+
+ assertEq(t1.get(0), null);
+ assertEq(t1.get(1), null);
+ assertEq(t1.get(2), null);
+ assertEq(t1.get(3), null);
+
+ assertEq(wasmGcReadField(t2.get(0), 0), 321);
+ assertEq(wasmGcReadField(t2.get(1), 0), 432);
+ assertEq(wasmGcReadField(t2.get(1), 1), 432);
+ assertEq(wasmGcReadField(t2.get(1), 2), 432);
+ assertEq(wasmGcReadField(t2.get(2), 0), null);
+ assertEq(wasmGcReadField(t2.get(2), 1), null);
+ assertEq(wasmGcReadField(t2.get(2), 2), null);
+ assertEq(t2.get(3), 543);
+
+ t1init();
+ assertEq(wasmGcReadField(t1.get(0), 0), 123);
+ assertEq(wasmGcReadField(t1.get(1), 0), 234);
+ assertEq(wasmGcReadField(t1.get(1), 1), 234);
+ assertEq(wasmGcReadField(t1.get(1), 2), 234);
+ assertEq(wasmGcReadField(t1.get(2), 0), null);
+ assertEq(wasmGcReadField(t1.get(2), 1), null);
+ assertEq(wasmGcReadField(t1.get(2), 2), null);
+ assertEq(t1.get(3), 345);
+}
+
+// The contents of passive segments are unique per instance and evaluated at
+// instantiation time.
+{
+ const mod = new WebAssembly.Module(wasmTextToBinary(`(module
+ (type $s (struct (field (mut i32))))
+ (type $a (array (mut f64)))
+
+ (elem $e anyref
+ (item (struct.new $s (i32.const 123)))
+ (item (array.new $a (f64.const 234) (i32.const 1)))
+ )
+
+ (table $t1 (export "t1") 2 2 anyref)
+ (table $t2 (export "t2") 2 2 anyref)
+
+ (start $init1)
+
+ (func $init1
+ (table.init $t1 $e (i32.const 0) (i32.const 0) (i32.const 2))
+ )
+ (func (export "init2")
+ (table.init $t2 $e (i32.const 0) (i32.const 0) (i32.const 2))
+ )
+ (func (export "update1")
+ (ref.cast (ref $s) (table.get $t1 (i32.const 0)))
+ (struct.set $s 0 (i32.const 321))
+ (ref.cast (ref $a) (table.get $t1 (i32.const 1)))
+ (array.set $a (i32.const 0) (f64.const 432))
+ )
+ (func (export "update2")
+ (ref.cast (ref $s) (table.get $t1 (i32.const 0)))
+ (struct.set $s 0 (i32.const -321))
+ (ref.cast (ref $a) (table.get $t1 (i32.const 1)))
+ (array.set $a (i32.const 0) (f64.const -432))
+ )
+ )`));
+
+ const { t1: t1_1, t2: t2_1, init2: init2_1, update1: update1_1, update2: update2_1 } = new WebAssembly.Instance(mod, {}).exports;
+ const { t1: t1_2, t2: t2_2, init2: init2_2, update1: update1_2, update2: update2_2 } = new WebAssembly.Instance(mod, {}).exports;
+
+ assertEq(wasmGcReadField(t1_1.get(0), 0), 123);
+ assertEq(wasmGcReadField(t1_1.get(1), 0), 234);
+ assertEq(t2_1.get(0), null);
+ assertEq(t2_1.get(1), null);
+ assertEq(wasmGcReadField(t1_2.get(0), 0), 123);
+ assertEq(wasmGcReadField(t1_2.get(1), 0), 234);
+ assertEq(t2_2.get(0), null);
+ assertEq(t2_2.get(1), null);
+
+ update1_1();
+ assertEq(wasmGcReadField(t1_1.get(0), 0), 321);
+ assertEq(wasmGcReadField(t1_1.get(1), 0), 432);
+ assertEq(t2_1.get(0), null);
+ assertEq(t2_1.get(1), null);
+ assertEq(wasmGcReadField(t1_2.get(0), 0), 123);
+ assertEq(wasmGcReadField(t1_2.get(1), 0), 234);
+ assertEq(t2_2.get(0), null);
+ assertEq(t2_2.get(1), null);
+
+ init2_1();
+ assertEq(wasmGcReadField(t1_1.get(0), 0), 321);
+ assertEq(wasmGcReadField(t1_1.get(1), 0), 432);
+ assertEq(wasmGcReadField(t2_1.get(0), 0), 321);
+ assertEq(wasmGcReadField(t2_1.get(1), 0), 432);
+ assertEq(wasmGcReadField(t1_2.get(0), 0), 123);
+ assertEq(wasmGcReadField(t1_2.get(1), 0), 234);
+ assertEq(t2_2.get(0), null);
+ assertEq(t2_2.get(1), null);
+
+ init2_2();
+ assertEq(wasmGcReadField(t1_1.get(0), 0), 321);
+ assertEq(wasmGcReadField(t1_1.get(1), 0), 432);
+ assertEq(wasmGcReadField(t2_1.get(0), 0), 321);
+ assertEq(wasmGcReadField(t2_1.get(1), 0), 432);
+ assertEq(wasmGcReadField(t1_2.get(0), 0), 123);
+ assertEq(wasmGcReadField(t1_2.get(1), 0), 234);
+ assertEq(wasmGcReadField(t2_2.get(0), 0), 123);
+ assertEq(wasmGcReadField(t2_2.get(1), 0), 234);
+
+ update2_1();
+ assertEq(wasmGcReadField(t1_1.get(0), 0), -321);
+ assertEq(wasmGcReadField(t1_1.get(1), 0), -432);
+ assertEq(wasmGcReadField(t2_1.get(0), 0), -321);
+ assertEq(wasmGcReadField(t2_1.get(1), 0), -432);
+ assertEq(wasmGcReadField(t1_2.get(0), 0), 123);
+ assertEq(wasmGcReadField(t1_2.get(1), 0), 234);
+ assertEq(wasmGcReadField(t2_2.get(0), 0), 123);
+ assertEq(wasmGcReadField(t2_2.get(1), 0), 234);
+
+ update1_2();
+ assertEq(wasmGcReadField(t1_1.get(0), 0), -321);
+ assertEq(wasmGcReadField(t1_1.get(1), 0), -432);
+ assertEq(wasmGcReadField(t2_1.get(0), 0), -321);
+ assertEq(wasmGcReadField(t2_1.get(1), 0), -432);
+ assertEq(wasmGcReadField(t1_2.get(0), 0), 321);
+ assertEq(wasmGcReadField(t1_2.get(1), 0), 432);
+ assertEq(wasmGcReadField(t2_2.get(0), 0), 321);
+ assertEq(wasmGcReadField(t2_2.get(1), 0), 432);
+
+ update2_2();
+ assertEq(wasmGcReadField(t1_1.get(0), 0), -321);
+ assertEq(wasmGcReadField(t1_1.get(1), 0), -432);
+ assertEq(wasmGcReadField(t2_1.get(0), 0), -321);
+ assertEq(wasmGcReadField(t2_1.get(1), 0), -432);
+ assertEq(wasmGcReadField(t1_2.get(0), 0), -321);
+ assertEq(wasmGcReadField(t1_2.get(1), 0), -432);
+ assertEq(wasmGcReadField(t2_2.get(0), 0), -321);
+ assertEq(wasmGcReadField(t2_2.get(1), 0), -432);
+}
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..029bf576c2
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/js-boundary.js
@@ -0,0 +1,123 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Tests of dynamic type checks
+test('anyref', WasmAnyrefValues, []);
+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/;
+
+ // 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);
+ }
+}
+
+// Verify that GC objects are opaque
+for (const val of WasmGcObjectValues) {
+ assertEq(Reflect.getPrototypeOf(val), null);
+ assertEq(Reflect.setPrototypeOf(val, null), true);
+ assertEq(Reflect.setPrototypeOf(val, {}), false);
+ assertEq(Reflect.isExtensible(val), false);
+ assertEq(Reflect.preventExtensions(val), false);
+ assertEq(Reflect.getOwnPropertyDescriptor(val, "anything"), undefined);
+ assertEq(Reflect.defineProperty(val, "anything", { value: 42 }), false);
+ assertEq(Reflect.has(val, "anything"), false);
+ assertEq(Reflect.get(val, "anything"), undefined);
+ assertErrorMessage(() => { Reflect.set(val, "anything", 3); }, TypeError, /can't modify/);
+ assertErrorMessage(() => { Reflect.deleteProperty(val, "anything"); }, TypeError, /can't modify/);
+ assertEq(Reflect.ownKeys(val).length, 0, `gc objects should not have keys, but this one had: ${Reflect.ownKeys(val)}`);
+ for (const i in val) {
+ throw new Error(`GC objects should have no enumerable properties, but had ${i}`);
+ }
+ assertEq(val[Symbol.iterator], undefined, "GC objects should not be iterable");
+}
diff --git a/js/src/jit-test/tests/wasm/gc/limits.js b/js/src/jit-test/tests/wasm/gc/limits.js
new file mode 100644
index 0000000000..e6f21b5d6b
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/limits.js
@@ -0,0 +1,69 @@
+// |jit-test| skip-if: !wasmGcEnabled() || getBuildConfiguration("tsan")
+
+// This test has a timeout on TSAN configurations due to the large
+// allocations.
+
+// Limit of 1 million recursion groups
+wasmValidateText(`(module
+ ${`(rec (type (func)))`.repeat(1_000_000)}
+ )`);
+wasmFailValidateText(`(module
+ ${`(rec (type (func)))`.repeat(1_000_001)}
+ )`, /too many/);
+
+// Limit of 1 million types (across all recursion groups)
+wasmValidateText(`(module
+ (rec ${`(type (func))`.repeat(1_000_000)})
+ )`);
+wasmValidateText(`(module
+ (rec ${`(type (func))`.repeat(500_000)})
+ (rec ${`(type (func))`.repeat(500_000)})
+ )`);
+wasmFailValidateText(`(module
+ (rec ${`(type (func))`.repeat(1_000_001)})
+ )`, /too many/);
+wasmFailValidateText(`(module
+ (rec ${`(type (func))`.repeat(500_000)})
+ (rec ${`(type (func))`.repeat(500_001)})
+ )`, /too many/);
+
+// Limit of subtyping hierarchy 63 deep
+function testSubtypingModule(depth) {
+ let types = '(type (sub (func)))';
+ for (let i = 1; i <= depth; i++) {
+ types += `(type (sub ${i - 1} (func)))`;
+ }
+ return `(module
+ ${types}
+ )`;
+}
+wasmValidateText(testSubtypingModule(63));
+wasmFailValidateText(testSubtypingModule(64), /too deep/);
+
+// Limit of 10_000 struct fields
+wasmFailValidateText(`(module
+ (type (struct ${'(field i64)'.repeat(10_001)}))
+)`, /too many/);
+
+{
+ let {makeLargeStructDefault, makeLargeStruct} = wasmEvalText(`(module
+ (type $s (struct ${'(field i64)'.repeat(10_000)}))
+ (func (export "makeLargeStructDefault") (result anyref)
+ struct.new_default $s
+ )
+ (func (export "makeLargeStruct") (result anyref)
+ ${'i64.const 0 '.repeat(10_000)}
+ struct.new $s
+ )
+ )`).exports;
+ let largeStructDefault = makeLargeStructDefault();
+ let largeStruct = makeLargeStruct();
+}
+
+// array.new_fixed has limit of 10_000 operands
+wasmFailValidateText(`(module
+ (type $a (array i32))
+ (func
+ array.new_fixed $a 10001
+ )
+)`, /too many/);
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..99881ced42
--- /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))
+ (then (f64.const 5.0))
+ (else (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..49a772d26f
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/ref-struct.js
@@ -0,0 +1,333 @@
+// |jit-test| skip-if: !wasmGcEnabled(); test-also=--gc-zeal=2
+
+// 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))
+ (then (struct.new $wabbit (local.get $tmp) (ref.null $wabbit) (ref.null $wabbit)))
+ (else
+ (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))
+ (then (i32.const 0))
+ (else
+ (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)))
+ (then
+ (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)))
+ (then
+ (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 (sub (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 downcast succeeds we get the original pointer
+
+assertEq(wasmEvalText(
+ `(module
+ (type $node (sub (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);
+
+// 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();
+
+
+// And once more with mutable fields
+
+assertEq(wasmEvalText(
+ `(module
+ (type $node (sub (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(wasmGcReadField(b, 0), 0);
+ assertEq(wasmGcReadField(b, 1), 0);
+
+ let c = makeC();
+ assertEq(wasmGcReadField(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..a55b0c8f02
--- /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))
+ (then (unreachable))
+ (else (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..53bd663ce0
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/regress-1830975.js
@@ -0,0 +1,12 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+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);
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..9cd4d21143
--- /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 WasmGcObject, 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 (sub (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
+ (sub (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..a69b8dc600
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/signal-null-check.js
@@ -0,0 +1,180 @@
+// |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"]();
+ assertDereferenceNull(() => ins.exports["test_get_first"]());
+ assertDereferenceNull(() => ins.exports["test_get_mid"]());
+ assertDereferenceNull(() => ins.exports["test_get_last"]());
+ assertDereferenceNull(() => ins.exports["test_set_first"]());
+ assertDereferenceNull(() => ins.exports["test_set_mid"]());
+ assertDereferenceNull(() => 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"]();
+ assertDereferenceNull(() => ins.exports["test_get"](0));
+ assertDereferenceNull(() => ins.exports["test_get"](numItems >> 1));
+ assertDereferenceNull(() => ins.exports["test_get"](numItems - 1));
+ assertDereferenceNull(() => ins.exports["test_set"](0));
+ assertDereferenceNull(() => ins.exports["test_set"](numItems >> 1));
+ assertDereferenceNull(() => ins.exports["test_set"](numItems - 1));
+
+ ins.exports["init-non-null"]();
+ ins.exports["test_set"](3);
+ ins.exports["test_get"](0);
+}
+
+
+function assertDereferenceNull(fun) {
+ assertErrorMessage(fun, WebAssembly.RuntimeError, /dereferencing null pointer/);
+}
+
+// Linear memory loads/stores from small constant addresses also require
+// trapsites, it seems. So check that the following is compilable -- in
+// particular, that it doesn't produce any TrapSite placement validation errors.
+
+const ins = wasmEvalText(`
+ (module
+ (memory 1)
+ (func (result i32) (i32.load8_s (i32.const 17)))
+ (func (result i32) (i32.load8_u (i32.const 17)))
+ (func (result i32) (i32.load16_s (i32.const 17)))
+ (func (result i32) (i32.load16_u (i32.const 17)))
+
+ (func (result i64) (i64.load8_s (i32.const 17)))
+ (func (result i64) (i64.load8_u (i32.const 17)))
+ (func (result i64) (i64.load16_s (i32.const 17)))
+ (func (result i64) (i64.load16_u (i32.const 17)))
+
+ (func (result i64) (i64.load32_s (i32.const 17)))
+ (func (result i64) (i64.load32_u (i32.const 17)))
+
+ (func (param i32) (i32.store8 (i32.const 17) (local.get 0)))
+ (func (param i32) (i32.store16 (i32.const 17) (local.get 0)))
+
+ (func (param i64) (i64.store8 (i32.const 17) (local.get 0)))
+ (func (param i64) (i64.store16 (i32.const 17) (local.get 0)))
+ (func (param i64) (i64.store32 (i32.const 17) (local.get 0)))
+
+ (func (export "leet") (result i32) (i32.const 1337))
+ )`);
+
+assertEq(ins.exports["leet"](), 1337);
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..0ff0cbd4b4
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/structs.js
@@ -0,0 +1,742 @@
+// |jit-test| skip-if: !wasmGcEnabled(); test-also=--gc-zeal=2
+
+// This tests a bunch of wasm struct stuff, but not i8 or i16 fields.
+// See structs2.js for i8/i16 field tests.
+
+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 inline WasmGcObject.
+
+ (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(wasmGcReadField(point, 0), 37);
+assertEq(wasmGcReadField(point, 1), 42);
+
+var int_node = ins.mk_int_node(78, point);
+assertEq(wasmGcReadField(int_node, 0), 78);
+assertEq(wasmGcReadField(int_node, 1), point);
+
+var bigger = ins.mk_bigger();
+for ( let i=0; i < 52; i++ )
+ assertEq(wasmGcReadField(bigger, i), i);
+
+var withfloats = ins.mk_withfloats(1/3, Math.PI, bigger, 5/6, 0x1337);
+assertEq(wasmGcReadField(withfloats, 0), Math.fround(1/3));
+assertEq(wasmGcReadField(withfloats, 1), Math.PI);
+assertEq(wasmGcReadField(withfloats, 2), bigger);
+assertEq(wasmGcReadField(withfloats, 3), Math.fround(5/6));
+assertEq(wasmGcReadField(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 =
+ getBuildConfiguration("x64") && !getBuildConfiguration("tsan") &&
+ !getBuildConfiguration("asan") && !getBuildConfiguration("valgrind")
+ ? 100000
+ : 1000;
+var the_list = stressIns.iota1(stressLevel);
+for (let i=1; i <= stressLevel; i++) {
+ assertEq(wasmGcReadField(the_list, 0), i);
+ the_list = wasmGcReadField(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(wasmGcReadField(v, 0), 0x7aaaaaaa);
+ assertEq(wasmGcReadField(v, 1), 0x4201020337n);
+ assertEq(ins.low(v), 0x01020337);
+ assertEq(ins.high(v), 0x42);
+ assertEq(wasmGcReadField(v, 2), 0x6bbbbbbb);
+
+ ins.set(v);
+ assertEq(wasmGcReadField(v, 0), 0x7aaaaaaa);
+ assertEq(wasmGcReadField(v, 1), 0x3333333376544567n);
+ assertEq(wasmGcReadField(v, 2), 0x6bbbbbbb);
+
+ ins.set2(v);
+ assertEq(wasmGcReadField(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_i32_u (local.get $hi)) (i64.const 32))
+ (i64.extend_i32_u (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(wasmGcReadField(v, 0), 0x7aaaaaaa);
+ assertEq(wasmGcReadField(v, 1), 0x4201020337n);
+ assertEq(wasmGcReadField(v, 2), 0x6bbbbbbb);
+
+ ins.update0(0x45367101);
+ assertEq(wasmGcReadField(v, 0), 0x45367101);
+ assertEq(ins.get0(), 0x45367101);
+ assertEq(wasmGcReadField(v, 1), 0x4201020337n);
+ assertEq(wasmGcReadField(v, 2), 0x6bbbbbbb);
+
+ ins.update2(0x62345123);
+ assertEq(wasmGcReadField(v, 0), 0x45367101);
+ assertEq(wasmGcReadField(v, 1), 0x4201020337n);
+ assertEq(ins.get2(), 0x62345123);
+ assertEq(wasmGcReadField(v, 2), 0x62345123);
+
+ ins.update1(0x77777777, 0x22222222);
+ assertEq(wasmGcReadField(v, 0), 0x45367101);
+ assertEq(ins.get1_low(), 0x22222222);
+ assertEq(ins.get1_high(), 0x77777777);
+ assertEq(wasmGcReadField(v, 1), 0x7777777722222222n);
+ assertEq(wasmGcReadField(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);
+}
+
+// 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, TypeError, /can't modify/);
+ assertErrorMessage(() => v[1] = 12, TypeError, /can't modify/);
+}
+
+// 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 +
+ "))";
+}
+
+{
+ // 10_000 fields is allowable
+ let exports = wasmEvalText(structNewOfManyFields(10000)).exports;
+ let s = exports.create();
+ assertEq(s, s);
+}
+{
+ // but 10_001 is not
+ assertErrorMessage(() => wasmEvalText(structNewOfManyFields(10001)),
+ WebAssembly.CompileError,
+ /too many fields in struct/);
+}
+
+// FIXME: also check struct.new_default, once it is available in both compilers.
+
+// Exercise stack maps and GC
+{
+ // Zeal will cause us to allocate structs via instance call, requiring live registers
+ // to be spilled, and then GC values traced while on the stack.
+ gczeal(2, 1);
+
+ {
+ const { test } = wasmEvalText(`(module
+ (type $s (struct (field i32)))
+ (func (export "test") (param i32) (result i32)
+ local.get 0
+ (struct.new $s (i32.const 234))
+ (struct.new $s (i32.const 345))
+ drop
+ drop
+ )
+ )`).exports;
+ assertEq(test(123), 123);
+ }
+ {
+ const { test } = wasmEvalText(`(module
+ (type $s (struct (field f32)))
+ (func (export "test") (param f32) (result f32)
+ local.get 0
+ (struct.new $s (f32.const 234))
+ (struct.new $s (f32.const 345))
+ drop
+ drop
+ )
+ )`).exports;
+ assertEq(test(123), 123);
+ }
+
+ gczeal(0);
+}
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..25651d7175
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/structs2.js
@@ -0,0 +1,243 @@
+// |jit-test| skip-if: !wasmGcEnabled(); test-also=--gc-zeal=2
+
+// 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(wasmGcReadField(obj8, 0) + BigInt(wasmGcReadField(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(wasmGcReadField(obj16, 0) + BigInt(wasmGcReadField(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(wasmGcReadField(theObject, 0), 0x55);
+ assertEq(wasmGcReadField(theObject, 1), 0x55);
+ assertEq(wasmGcReadField(theObject, 2), 0x55);
+ assertEq(wasmGcReadField(theObject, 3), 0x77);
+ assertEq(wasmGcReadField(theObject, 4), 0x55);
+ assertEq(wasmGcReadField(theObject, 5), 0x55);
+ assertEq(wasmGcReadField(theObject, 6), 0x55);
+ assertEq(wasmGcReadField(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(wasmGcReadField(theObject, 0), 0x5555);
+ assertEq(wasmGcReadField(theObject, 1), 0x5555);
+ assertEq(wasmGcReadField(theObject, 2), 0x5555);
+ assertEq(wasmGcReadField(theObject, 3), 0x7766);
+ assertEq(wasmGcReadField(theObject, 4), 0x5555);
+ assertEq(wasmGcReadField(theObject, 5), 0x5555);
+ assertEq(wasmGcReadField(theObject, 6), 0x5555);
+ assertEq(wasmGcReadField(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..f461a4875d
--- /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 (sub (struct (field (ref $notParsedYet)))))
+ (type $b (sub $a (struct (field (ref $notParsedYet2)))))
+
+ (type $notParsedYet (sub (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/tables.js b/js/src/jit-test/tests/wasm/gc/tables.js
new file mode 100644
index 0000000000..e9c4e8f82e
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/tables.js
@@ -0,0 +1,167 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// implicit null funcref value
+{
+ const { t, get } = wasmEvalText(`(module
+ (table (export "t") 3 funcref)
+ (func (export "get") (param i32) (result funcref)
+ (table.get (local.get 0))
+ )
+ )`).exports;
+
+ assertEq(t.get(0), null);
+ assertEq(t.get(1), null);
+ assertEq(t.get(2), null);
+ assertEq(get(0), null);
+ assertEq(get(1), null);
+ assertEq(get(2), null);
+}
+
+// explicit null funcref value
+{
+ const { t, get } = wasmEvalText(`(module
+ (table (export "t") 3 funcref (ref.null func))
+ (func (export "get") (param i32) (result funcref)
+ (table.get (local.get 0))
+ )
+ )`).exports;
+
+ assertEq(t.get(0), null);
+ assertEq(t.get(1), null);
+ assertEq(t.get(2), null);
+ assertEq(get(0), null);
+ assertEq(get(1), null);
+ assertEq(get(2), null);
+}
+
+// actual funcref value
+{
+ const { t, get, foo } = wasmEvalText(`(module
+ (func $foo (export "foo"))
+
+ (table (export "t") 3 funcref (ref.func $foo))
+ (func (export "get") (param i32) (result funcref)
+ (table.get (local.get 0))
+ )
+ )`).exports;
+
+ assertEq(t.get(0), foo);
+ assertEq(t.get(1), foo);
+ assertEq(t.get(2), foo);
+ assertEq(get(0), foo);
+ assertEq(get(1), foo);
+ assertEq(get(2), foo);
+}
+
+// implicit null anyref value
+{
+ const { t, get } = wasmEvalText(`(module
+ (table (export "t") 3 anyref)
+ (func (export "get") (param i32) (result anyref)
+ (table.get (local.get 0))
+ )
+ )`).exports;
+
+ assertEq(t.get(0), null);
+ assertEq(t.get(1), null);
+ assertEq(t.get(2), null);
+ assertEq(get(0), null);
+ assertEq(get(1), null);
+ assertEq(get(2), null);
+}
+
+// explicit null anyref value
+{
+ const { t, get } = wasmEvalText(`(module
+ (table (export "t") 3 anyref (ref.null any))
+ (func (export "get") (param i32) (result anyref)
+ (table.get (local.get 0))
+ )
+ )`).exports;
+
+ assertEq(t.get(0), null);
+ assertEq(t.get(1), null);
+ assertEq(t.get(2), null);
+ assertEq(get(0), null);
+ assertEq(get(1), null);
+ assertEq(get(2), null);
+}
+
+// actual anyref value
+{
+ const { t, get } = wasmEvalText(`(module
+ (type $s (struct))
+
+ (table (export "t") 3 anyref (struct.new $s))
+ (func (export "get") (param i32) (result anyref)
+ (table.get (local.get 0))
+ )
+ )`).exports;
+
+ assertEq(!!t.get(0), true);
+ assertEq(!!t.get(1), true);
+ assertEq(!!t.get(2), true);
+ assertEq(!!get(0), true);
+ assertEq(!!get(1), true);
+ assertEq(!!get(2), true);
+}
+
+// implicit null externref value
+{
+ const { t, get } = wasmEvalText(`(module
+ (table (export "t") 3 externref)
+ (func (export "get") (param i32) (result externref)
+ (table.get (local.get 0))
+ )
+ )`).exports;
+
+ assertEq(t.get(0), null);
+ assertEq(t.get(1), null);
+ assertEq(t.get(2), null);
+ assertEq(get(0), null);
+ assertEq(get(1), null);
+ assertEq(get(2), null);
+}
+
+// explicit null externref value
+{
+ const { t, get } = wasmEvalText(`(module
+ (table (export "t") 3 externref (ref.null extern))
+ (func (export "get") (param i32) (result externref)
+ (table.get (local.get 0))
+ )
+ )`).exports;
+
+ assertEq(t.get(0), null);
+ assertEq(t.get(1), null);
+ assertEq(t.get(2), null);
+ assertEq(get(0), null);
+ assertEq(get(1), null);
+ assertEq(get(2), null);
+}
+
+// actual externref value (from an imported global, which is visible to tables)
+{
+ const foo = "wowzers";
+ const { t, get } = wasmEvalText(`(module
+ (global (import "" "foo") externref)
+
+ (table (export "t") 3 externref (global.get 0))
+ (func (export "get") (param i32) (result externref)
+ (table.get (local.get 0))
+ )
+ )`, { "": { "foo": foo } }).exports;
+
+ assertEq(t.get(0), foo);
+ assertEq(t.get(1), foo);
+ assertEq(t.get(2), foo);
+ assertEq(get(0), foo);
+ assertEq(get(1), foo);
+ assertEq(get(2), foo);
+}
+
+// non-imported globals come after tables and are therefore not visible
+assertErrorMessage(() => wasmEvalText(`(module
+ (global anyref (ref.null any))
+ (table 3 anyref (global.get 0))
+)`), WebAssembly.CompileError, /global.get index out of range/);
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..59410904a7
--- /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/unreachable.js b/js/src/jit-test/tests/wasm/gc/unreachable.js
new file mode 100644
index 0000000000..606aad1e3b
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/unreachable.js
@@ -0,0 +1,61 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+const INSTRUCTIONS = [
+ "struct.new $s",
+ "struct.new_default $s",
+ "struct.get $s 0",
+ "struct.get_s $s 1",
+ "struct.get_u $s 1",
+ "struct.set $s 0",
+ "array.new $a_unpacked",
+ "array.new_fixed $a_unpacked 2",
+ "array.new_default $a_unpacked",
+ "array.new_data $a_data 0",
+ "array.new_elem $a_elem 0",
+ "array.get $a_unpacked",
+ "array.get_s $a_packed",
+ "array.get_u $a_packed",
+ "array.set $a_unpacked",
+ "array.copy $a_unpacked $a_unpacked",
+ "array.len",
+ "ref.i31",
+ "i31.get_s",
+ "i31.get_u",
+ "ref.test structref",
+ "ref.test (ref $s)",
+ "ref.test nullref",
+ "ref.test (ref $f)",
+ "ref.test nullfuncref",
+ "ref.test externref",
+ "ref.test nullexternref",
+ "ref.cast structref",
+ "ref.cast (ref $s)",
+ "ref.cast nullref",
+ "ref.cast (ref $f)",
+ "ref.cast nullfuncref",
+ "ref.cast externref",
+ "ref.cast nullexternref",
+ "br_on_cast 0 anyref (ref $s)",
+ "br_on_cast_fail 0 anyref (ref $s)",
+ "any.convert_extern",
+ "extern.convert_any",
+];
+
+for (let instruction of INSTRUCTIONS) {
+ print(instruction);
+ wasmEvalText(`(module
+ (type $f (func))
+ (type $s (struct (field (mut i32)) (field (mut i8))))
+ (type $a_unpacked (array (mut i32)))
+ (type $a_packed (array (mut i8)))
+ (type $a_data (array (mut i32)))
+ (type $a_elem (array (mut anyref)))
+ (data "")
+ (elem anyref)
+ (func (result anyref)
+ unreachable
+ ${instruction}
+ unreachable
+ )
+ )`);
+}
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..1cb20f3047
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/value_subtyping.js
@@ -0,0 +1,311 @@
+// |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('i31ref', 'i31ref');
+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');
+
+// i31ref is a subtype of eqref
+assertSubtype('anyref', 'i31ref');
+assertSubtype('eqref', 'i31ref');
+
+// 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 (field (ref 0)))', '(struct (field (ref 1)))']));
+
+// Mutually referential structs
+assertSubtype(
+ '(ref 2)',
+ '(ref 0)',
+ `(rec
+ (type (struct (field (ref 1))))
+ (type (struct (field (ref 0))))
+ )
+ (rec
+ (type (struct (field (ref 3))))
+ (type (struct (field (ref 2))))
+ )`);
+
+// Struct subtypes can have extra fields
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ `(type (sub (struct)))
+ (type (sub 0 (struct (field i32))))`);
+assertSubtype(
+ '(ref 0)',
+ '(ref 1)',
+ `(type (sub (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 (sub (struct)))
+ (type (sub 0 (struct (field i32))))
+ (type (sub (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)',
+ simpleTypeSection([
+ '(sub (struct))',
+ '(sub 0 (struct (field i32)))',
+ '(sub (array (ref 0)))',
+ '(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)']));