summaryrefslogtreecommitdiffstats
path: root/js/src/jit-test/tests/wasm/gc/arrays.js
diff options
context:
space:
mode:
Diffstat (limited to 'js/src/jit-test/tests/wasm/gc/arrays.js')
-rw-r--r--js/src/jit-test/tests/wasm/gc/arrays.js1104
1 files changed, 1104 insertions, 0 deletions
diff --git a/js/src/jit-test/tests/wasm/gc/arrays.js b/js/src/jit-test/tests/wasm/gc/arrays.js
new file mode 100644
index 0000000000..44770b85ea
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/arrays.js
@@ -0,0 +1,1104 @@
+// |jit-test| skip-if: !wasmGcEnabled()
+
+// Test array instructions on different valtypes
+
+const GENERAL_TESTS = [
+ [
+ 'i32',
+ 0,
+ 0xce,
+ ], [
+ 'i64',
+ 0n,
+ 0xabcdefn,
+ ], [
+ 'f32',
+ 0,
+ 13.5,
+ ], [
+ 'f64',
+ 0,
+ 13.5,
+ ], [
+ 'externref',
+ null,
+ 'hello',
+ ],
+];
+
+for (let [valtype, def, nondef] of GENERAL_TESTS) {
+ let {create, createDefault, get, set, len} = wasmEvalText(`(module
+ (type $a (array (mut ${valtype})))
+
+ (; new T[0] = { 1... } ;)
+ (func (export "create") (param i32 ${valtype}) (result eqref)
+ local.get 1
+ local.get 0
+ array.new $a
+ )
+
+ (; new T[0] ;)
+ (func (export "createDefault") (param i32) (result eqref)
+ local.get 0
+ array.new_default $a
+ )
+
+ (; 0[1] ;)
+ (func (export "get") (param eqref i32) (result ${valtype})
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ array.get $a
+ )
+
+ (; 0[1] = 2 ;)
+ (func (export "set") (param eqref i32 ${valtype})
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ local.get 2
+ array.set $a
+ )
+
+ (; len(a) ;)
+ (func (export "len") (param eqref) (result i32)
+ local.get 0
+ ref.cast (ref null $a)
+ array.len
+ )
+ )`).exports;
+
+ function checkArray(array, length, init, setval) {
+ // Check length
+ assertEq(len(array), length);
+ assertEq(array.length, length);
+
+ // Check init value
+ for (let i = 0; i < length; i++) {
+ assertEq(array[i], init);
+ assertEq(get(array, i), init);
+ }
+
+ // Set every element to setval
+ for (let i = 0; i < length; i++) {
+ set(array, i, setval);
+
+ // Check there is no overwrite
+ for (let j = i + 1; j < length; j++) {
+ assertEq(array[j], init);
+ assertEq(get(array, j), init);
+ }
+ }
+
+ // Check out of bounds conditions
+ for (let loc of [-1, length, length + 1]) {
+ assertErrorMessage(() => {
+ get(array, loc);
+ }, WebAssembly.RuntimeError, /out of bounds/);
+ assertErrorMessage(() => {
+ set(array, loc, setval);
+ }, WebAssembly.RuntimeError, /out of bounds/);
+ }
+ }
+
+ for (let arrayLength = 0; arrayLength < 5; arrayLength++) {
+ checkArray(createDefault(arrayLength), arrayLength, def, nondef);
+ checkArray(create(arrayLength, nondef), arrayLength, nondef, def);
+ }
+}
+
+// Check packed array reads and writes
+
+for (let [fieldtype, max] of [
+ [
+ 'i8',
+ 0xff,
+ ], [
+ 'i16',
+ 0xffff,
+ ]
+]) {
+ let {create, getS, getU, set} = wasmEvalText(`(module
+ (type $a (array (mut ${fieldtype})))
+
+ (; new T[0] = { 1... } ;)
+ (func (export "create") (param i32 i32) (result eqref)
+ local.get 1
+ local.get 0
+ array.new $a
+ )
+
+ (; 0[1] ;)
+ (func (export "getS") (param eqref i32) (result i32)
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ array.get_s $a
+ )
+
+ (; 0[1] ;)
+ (func (export "getU") (param eqref i32) (result i32)
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ array.get_u $a
+ )
+
+ (; 0[1] = 2 ;)
+ (func (export "set") (param eqref i32 i32)
+ local.get 0
+ ref.cast (ref null $a)
+ local.get 1
+ local.get 2
+ array.set $a
+ )
+ )`).exports;
+
+ // Check zero and sign extension
+ let a = create(1, 0);
+ set(a, 0, max);
+ assertEq(getS(a, 0), -1);
+ assertEq(getU(a, 0), max);
+
+ // JS-API defaults to sign extension
+ assertEq(a[0], getS(a, 0));
+
+ // Check array.new truncates init value
+ assertEq(create(1, max + 1)[0], 0);
+
+ // Check array.set truncates
+ let b = create(1, 0);
+ set(b, 0, max + 1);
+ assertEq(getU(b, 0), 0);
+
+ // Check no overwrite on set
+ let c = create(2, 0);
+ set(c, 0, max << 4);
+ assertEq(getU(c, 0), (max << 4) & max);
+ assertEq(getU(c, 1), 0);
+}
+
+// Check arrays must be mutable to mutate
+
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func
+ (array.set $a
+ (array.new $a
+ i32.const 0xff
+ i32.const 10)
+ i32.const 0
+ i32.const 0
+ )
+ )
+)
+`), WebAssembly.CompileError, /array is not mutable/);
+
+// Check operations trap on null
+
+assertErrorMessage(() => {
+ wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func
+ ref.null $a
+ i32.const 0
+ array.get $a
+ drop
+ )
+ (start 0)
+ )`);
+}, WebAssembly.RuntimeError, /null/);
+
+assertErrorMessage(() => {
+ wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func
+ ref.null $a
+ i32.const 0
+ i32.const 0
+ array.set $a
+ )
+ (start 0)
+ )`);
+}, WebAssembly.RuntimeError, /null/);
+
+assertErrorMessage(() => {
+ wasmEvalText(`(module
+ (type $a (array (mut i32)))
+ (func
+ ref.null $a
+ array.len
+ drop
+ )
+ (start 0)
+ )`);
+}, WebAssembly.RuntimeError, /null/);
+
+// Check an extension postfix is present iff the element is packed
+
+for ([fieldtype, packed] of [
+ ['i8', true],
+ ['i16', true],
+ ['i32', false],
+ ['i64', false],
+ ['f32', false],
+ ['f64', false],
+]) {
+ let extensionModule = `(module
+ (type $a (array ${fieldtype}))
+ (func
+ ref.null $a
+ i32.const 0
+ array.get_s $a
+ drop
+ )
+ (func
+ ref.null $a
+ i32.const 0
+ array.get_u $a
+ drop
+ )
+ )`;
+ let noExtensionModule = `(module
+ (type $a (array ${fieldtype}))
+ (func
+ ref.null $a
+ i32.const 0
+ array.get $a
+ drop
+ )
+ )`;
+
+ if (packed) {
+ wasmValidateText(extensionModule);
+ wasmFailValidateText(noExtensionModule, /must specify signedness/);
+ } else {
+ wasmFailValidateText(extensionModule, /must not specify signedness/);
+ wasmValidateText(noExtensionModule);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.new_fixed
+/*
+ validation:
+ array-type imm-operand needs to be "in range"
+ array-type imm-operand must refer to an array type
+ operands (on stack) must all match ("be compatible with") the array elem
+ type
+ number of operands (on stack) must not be less than the num-of-elems
+ imm-operand
+ zero elements doesn't fail compilation
+ reftypes elements doesn't fail compilation
+ trying to create a 1-billion-element array fails gracefully
+ run:
+ resulting 4-element array is as expected
+ resulting zero-element array is as expected
+ resulting 30-element array is as expected
+*/
+
+// validation: array-type imm-operand needs to be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i8))
+ (func (result eqref)
+ i32.const 66
+ i32.const 77
+ array.new_fixed 2 2 ;; type index 2 is the first invalid one
+ )
+)
+`), WebAssembly.CompileError, /type index out of range/);
+
+// validation: array-type imm-operand must refer to an array type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (func (param f64) (result f64)))
+ (func (result eqref)
+ i32.const 66
+ i32.const 77
+ array.new_fixed $a 2
+ )
+)
+`), WebAssembly.CompileError, /not an array type/);
+
+// validation: operands (on stack) must all match ("be compatible with")
+// the array elem type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func (result eqref)
+ f32.const 66.6
+ f64.const 77.7
+ array.new_fixed $a 2
+ )
+)
+`), WebAssembly.CompileError, /expression has type f64 but expected i32/);
+
+// validation: number of operands (on stack) must not be less than the
+// num-of-elems imm-operand
+assertNoWarning(() => wasmEvalText(`(module
+ (type $a (array f32))
+ (func
+ f64.const 66.6 ;; we won't put this in the array
+ f32.const 77.7
+ f32.const 88.8
+ array.new_fixed $a 2 ;; use up 88.8 and 77.7 and replace with array
+ drop ;; dump the array
+ f64.const 99.9
+ f64.mul ;; check the 66.6 value is still on the stack
+ drop ;; now should be empty
+ )
+)
+`));
+// (more)
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i64))
+ (func (param i64) (result eqref)
+ local.get 0
+ array.new_fixed $a 2
+ )
+)
+`), WebAssembly.CompileError, /popping value from empty stack/);
+
+// validation: zero elements doesn't fail compilation
+assertNoWarning(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func (result eqref)
+ array.new_fixed $a 0
+ )
+)
+`));
+
+// validation: reftyped elements doesn't fail compilation
+assertNoWarning(() => wasmEvalText(`(module
+ (type $a (array eqref))
+ (func (param eqref) (result eqref)
+ local.get 0
+ array.new_fixed $a 1
+ )
+)
+`));
+
+// validation: trying to create a 1-billion-element array fails gracefully
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array f32))
+ (func (export "newFixed") (result eqref)
+ f32.const 1337.0
+ f32.const 4771.0
+ array.new_fixed $a 1000000000
+ )
+)
+`), WebAssembly.CompileError, /popping value from empty stack/);
+
+// run: resulting 4-element array is as expected
+{
+ let { newFixed } = wasmEvalText(`(module
+ (type $a (array i8))
+ (func (export "newFixed") (result eqref)
+ i32.const 66
+ i32.const 77
+ i32.const 88
+ i32.const 99
+ array.new_fixed $a 4
+ )
+ )`).exports;
+ let a = newFixed();
+ assertEq(a.length, 4);
+ assertEq(a[0], 66);
+ assertEq(a[1], 77);
+ assertEq(a[2], 88);
+ assertEq(a[3], 99);
+}
+
+// run: resulting zero-element array is as expected
+{
+ let { newFixed } = wasmEvalText(`(module
+ (type $a (array i16))
+ (func (export "newFixed") (result eqref)
+ array.new_fixed $a 0
+ )
+ )`).exports;
+ let a = newFixed();
+ assertEq(a.length, 0);
+}
+
+// run: resulting 30-element array is as expected
+{
+ let { newFixed } = wasmEvalText(`(module
+ (type $a (array i16))
+ (func (export "newFixed") (result eqref)
+ i32.const 1
+ i32.const 2
+ i32.const 3
+ i32.const 4
+ i32.const 5
+ i32.const 6
+ i32.const 7
+ i32.const 8
+ i32.const 9
+ i32.const 10
+ i32.const 11
+ i32.const 12
+ i32.const 13
+ i32.const 14
+ i32.const 15
+ i32.const 16
+ i32.const 17
+ i32.const 18
+ i32.const 19
+ i32.const 20
+ i32.const 21
+ i32.const 22
+ i32.const 23
+ i32.const 24
+ i32.const 25
+ i32.const 26
+ i32.const 27
+ i32.const 28
+ i32.const 29
+ i32.const 30
+ array.new_fixed $a 30
+ )
+ )`).exports;
+ let a = newFixed();
+ assertEq(a.length, 30);
+ for (i = 0; i < 30; i++) {
+ assertEq(a[i], i + 1);
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.new_data
+/*
+ validation:
+ array-type imm-operand needs to be "in range"
+ array-type imm-operand must refer to an array type
+ array-type imm-operand must refer to an array of numeric elements
+ segment index must be "in range"
+ run:
+ if segment is "already used" (active, or passive that has subsequently
+ been dropped), then only a zero length array can be created
+ range to copy would require OOB read on data segment
+ resulting array is as expected
+*/
+
+// validation: array-type imm-operand needs to be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i8))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data 2 $d
+ )
+)`), WebAssembly.CompileError, /type index out of range/);
+
+// validation: array-type imm-operand must refer to an array type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (func (param f32) (result f32)))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+)`), WebAssembly.CompileError, /not an array type/);
+
+// validation: array-type imm-operand must refer to an array of numeric elements
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array eqref))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+)`), WebAssembly.CompileError,
+ /element type must be i8\/i16\/i32\/i64\/f32\/f64\/v128/);
+
+// validation: segment index must be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i8))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a 1 ;; 1 is the lowest invalid dseg index
+ )
+)`), WebAssembly.CompileError, /segment index is out of range/);
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #1
+{
+ let { newData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array i8))
+ (data $d (offset (i32.const 0)) "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #2
+{
+ let { newData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array i8))
+ (data $d (offset (i32.const 0)) "1337")
+ (func (export "newData") (result eqref)
+ (; offset=4 into data ;) i32.const 4
+ (; size=0 into data ;) i32.const 0
+ array.new_data $a $d
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #3
+{
+ let { newData } = wasmEvalText(`(module
+ (memory 1)
+ (type $a (array i8))
+ (data $d (offset (i32.const 0)) "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=0 into data ;) i32.const 0
+ array.new_data $a $d
+ )
+ )`).exports;
+ let arr = newData();
+ assertEq(arr.length, 0);
+}
+
+// run: range to copy would require OOB read on data segment
+{
+ let { newData } = wasmEvalText(`(module
+ (type $a (array i8))
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 1
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newData();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: resulting array is as expected
+{
+ let { newData } = wasmEvalText(`(module
+ (type $a (array i8))
+ (data $other "\\\\9")
+ (data $d "1337")
+ (func (export "newData") (result eqref)
+ (; offset=0 into data ;) i32.const 0
+ (; size=4 into data ;) i32.const 4
+ array.new_data $a $d
+ )
+ )`).exports;
+ let arr = newData();
+ assertEq(arr.length, 4);
+ assertEq(arr[0], 48+1);
+ assertEq(arr[1], 48+3);
+ assertEq(arr[2], 48+3);
+ assertEq(arr[3], 48+7);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.new_elem
+/*
+ validation:
+ array-type imm-operand needs to be "in range"
+ array-type imm-operand must refer to an array type
+ array-type imm-operand must refer to an array of ref typed elements
+ destination elem type must be a supertype of src elem type
+ segment index must be "in range"
+ run:
+ if segment is "already used" (active, or passive that has subsequently
+ been dropped), then only a zero length array can be created
+ range to copy would require OOB read on elem segment
+ resulting array is as expected
+*/
+
+// validation: array-type imm-operand needs to be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem 3 $e
+ )
+)`), WebAssembly.CompileError, /type index out of range/);
+
+// validation: array-type imm-operand must refer to an array type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (func (param i64) (result f64)))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+)`), WebAssembly.CompileError, /not an array type/);
+
+// validation: array-type imm-operand must refer to an array of ref typed
+// elements
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array f32))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+)`), WebAssembly.CompileError, /element type is not a reftype/);
+
+// validation: destination elem type must be a supertype of src elem type
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array eqref))
+ (elem $e funcref $f1)
+ (func $f1 (export "f1"))
+ ;; The implied copy here is from elem-seg-of-funcrefs to
+ ;; array-of-eqrefs, which must fail, because funcref isn't
+ ;; a subtype of eqref.
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=0 into elem ;) i32.const 0
+ array.new_elem $a $e
+ )
+)`), WebAssembly.CompileError, /incompatible element types/);
+
+// validation: segment index must be "in range"
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a 1 ;; 1 is the lowest invalid eseg index
+ )
+)`), WebAssembly.CompileError, /segment index is out of range/);
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #1
+{
+ let { newElem } = wasmEvalText(`(module
+ (table 4 funcref)
+ (type $a (array funcref))
+ (elem $e (offset (i32.const 0)) funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newElem();
+ }, WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #2
+{
+ let { newElem } = wasmEvalText(`(module
+ (table 4 funcref)
+ (type $a (array funcref))
+ (elem $e (offset (i32.const 0)) funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=4 into elem ;) i32.const 4
+ (; size=0 into elem ;) i32.const 0
+ array.new_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newElem();
+ }, WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: if segment is "already used" (active, or passive that has subsequently
+// been dropped), then only a zero length array can be created #3
+{
+ let { newElem } = wasmEvalText(`(module
+ (table 4 funcref)
+ (type $a (array funcref))
+ (elem $e (offset (i32.const 0)) funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=0 into elem ;) i32.const 0
+ array.new_elem $a $e
+ )
+ )`).exports;
+ let arr = newElem();
+ assertEq(arr.length, 0);
+}
+
+// run: range to copy would require OOB read on elem segment
+{
+ let { newElem } = wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 1
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ newElem();
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: resulting array is as expected
+{
+ let { newElem, f1, f2, f3, f4 } = wasmEvalText(`(module
+ (type $a (array funcref))
+ (elem $e funcref $f1 $f2 $f3 $f4)
+ (func $f1 (export "f1"))
+ (func $f2 (export "f2"))
+ (func $f3 (export "f3"))
+ (func $f4 (export "f4"))
+ (func (export "newElem") (result eqref)
+ (; offset=0 into elem ;) i32.const 0
+ (; size=4 into elem ;) i32.const 4
+ array.new_elem $a $e
+ )
+ )`).exports;
+ let arr = newElem();
+ assertEq(arr.length, 4);
+ assertEq(arr[0], f1);
+ assertEq(arr[1], f2);
+ assertEq(arr[2], f3);
+ assertEq(arr[3], f4);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// array.copy
+/*
+ validation:
+ dest array must be mutable
+ validation: src and dest arrays must have compatible element types
+ run:
+ check for OOB conditions on src/dest arrays for non-zero length copies
+ check for OOB conditions on src/dest arrays for zero length copies
+ check resulting arrays are as expected
+ check that null src or dest array causes a trap
+*/
+
+// validation: dest array must be mutable
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func (param eqref)
+ (array.copy $a $a (local.get 0) (i32.const 1)
+ (local.get 0) (i32.const 2) (i32.const 3))
+ )
+)
+`), WebAssembly.CompileError, /array is not mutable/);
+
+// validation: src and dest arrays must have compatible element types #1
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a32 (array (mut i32)))
+ (type $a8 (array i8))
+ (func (param eqref)
+ (array.copy $a32 $a8 (local.get 0) (i32.const 1)
+ (local.get 0) (i32.const 2) (i32.const 3))
+ )
+)
+`), WebAssembly.CompileError, /incompatible element types/);
+
+// validation: src and dest arrays must have compatible element types #2
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a64 (array (mut i64)))
+ (type $aER (array eqref))
+ (func (param eqref)
+ (array.copy $a64 $aER (local.get 0) (i32.const 1)
+ (local.get 0) (i32.const 2) (i32.const 3))
+ )
+)
+`), WebAssembly.CompileError, /incompatible element types/);
+
+// validation: src and dest arrays must have compatible element types #3
+//
+// We can only copy from a child (sub) reftype to a parent (super) reftype [or
+// to the same reftype.] Here, we have an array of eqref and an array of
+// arrays. An array is a child type of eqref, so it's invalid to try and copy
+// eqrefs into the array of arrays.
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $ty (array i32))
+ (type $child (array (mut (ref $ty))))
+ (type $parent (array (mut eqref)))
+ (func (param (ref null $child) (ref null $parent))
+ ;; implied copy from parent to child -> not allowed
+ (array.copy $child $parent (local.get 1) (i32.const 1)
+ (local.get 0) (i32.const 2) (i32.const 3))
+ )
+)
+`), WebAssembly.CompileError, /incompatible element types/);
+
+// run: check for OOB conditions on src/dest arrays for non-zero length copies
+// run: check for OOB conditions on src/dest arrays for zero length copies
+// run: check resulting arrays are as expected
+const ARRAY_COPY_TESTS = [
+ // Format is:
+ // array element type
+ // corresponding value type
+ // source array
+ // first expected result
+ // second expected result
+ //
+ // Value-type cases
+ [ 'i32', 'i32',
+ [22, 33, 44, 55, 66, 77],
+ [22, 55, 66, 77, 66, 77],
+ [22, 33, 22, 33, 44, 77]
+ ],
+ [ 'i64', 'i64',
+ [0x2022002220002n, 0x3033003330003n, 0x4044004440004n,
+ 0x5055005550005n, 0x6066006660006n, 0x7077007770007n],
+ [0x2022002220002n, 0x5055005550005n, 0x6066006660006n,
+ 0x7077007770007n, 0x6066006660006n, 0x7077007770007n],
+ [0x2022002220002n, 0x3033003330003n, 0x2022002220002n,
+ 0x3033003330003n, 0x4044004440004n, 0x7077007770007n]
+ ],
+ [ 'f32', 'f32',
+ [22.0, 33.0, 44.0, 55.0, 66.0, 77.0],
+ [22.0, 55.0, 66.0, 77.0, 66.0, 77.0],
+ [22.0, 33.0, 22.0, 33.0, 44.0, 77.0]
+ ],
+ [ 'f64', 'f64',
+ [22.0, 33.0, 44.0, 55.0, 66.0, 77.0],
+ [22.0, 55.0, 66.0, 77.0, 66.0, 77.0],
+ [22.0, 33.0, 22.0, 33.0, 44.0, 77.0]
+ ],
+ [ 'externref', 'externref',
+ ['two', 'three', 'four', 'five', 'six', 'seven'],
+ ['two', 'five', 'six', 'seven', 'six', 'seven'],
+ ['two', 'three', 'two', 'three', 'four', 'seven']
+ ],
+ // non-Value-type cases
+ [ 'i8', 'i32',
+ [22, 33, 44, 55, 66, 77],
+ [22, 55, 66, 77, 66, 77],
+ [22, 33, 22, 33, 44, 77]
+ ],
+ [ 'i16', 'i32',
+ [22, 33, 44, 55, 66, 77],
+ [22, 55, 66, 77, 66, 77],
+ [22, 33, 22, 33, 44, 77]
+ ]
+];
+
+for (let [elemTy, valueTy, src, exp1, exp2] of ARRAY_COPY_TESTS) {
+ let { arrayNew, arrayCopy } = wasmEvalText(
+ `(module
+ (type $arrTy (array (mut ${elemTy})))
+ (func (export "arrayNew")
+ (param ${valueTy} ${valueTy} ${valueTy}
+ ${valueTy} ${valueTy} ${valueTy})
+ (result eqref)
+ local.get 0
+ local.get 1
+ local.get 2
+ local.get 3
+ local.get 4
+ local.get 5
+ array.new_fixed $arrTy 6
+ )
+ (func (export "arrayCopy")
+ (param eqref i32 eqref i32 i32)
+ (array.copy $arrTy $arrTy
+ (ref.cast (ref null $arrTy) local.get 0) (local.get 1)
+ (ref.cast (ref null $arrTy) local.get 2) (local.get 3) (local.get 4)
+ )
+ )
+ )`
+ ).exports;
+
+ assertEq(src.length, 6);
+ assertEq(exp1.length, 6);
+ assertEq(exp2.length, 6);
+
+ function eqArrays(a1, a2) {
+ assertEq(a1.length, 6);
+ assertEq(a2.length, 6);
+ for (i = 0; i < 6; i++) {
+ if (a1[i] !== a2[i])
+ return false;
+ }
+ return true;
+ }
+ function show(who, arr) {
+ print(who + ": " + arr[0] + " " + arr[1] + " " + arr[2] + " "
+ + arr[3] + " " + arr[4] + " " + arr[5] + " ");
+ }
+
+ // Check that "normal" copying gives expected results.
+ let srcTO;
+ srcTO = arrayNew(src[0], src[1], src[2], src[3], src[4], src[5]);
+ arrayCopy(srcTO, 1, srcTO, 3, 3);
+ assertEq(eqArrays(srcTO, exp1), true);
+
+ srcTO = arrayNew(src[0], src[1], src[2], src[3], src[4], src[5]);
+ arrayCopy(srcTO, 2, srcTO, 0, 3);
+ assertEq(eqArrays(srcTO, exp2), true);
+
+ // Check out-of-bounds conditions
+ let exp1TO = arrayNew(exp1[0], exp1[1], exp1[2], exp1[3], exp1[4], exp1[5]);
+ let exp2TO = arrayNew(exp2[0], exp2[1], exp2[2], exp2[3], exp2[4], exp2[5]);
+
+ // dst overrun, wants to write [5, 6]
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 5, exp2TO, 1, 2);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // dst overrun, wants to write [7, 8]
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 7, exp2TO, 1, 2);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // dst zero-len overrun, wants to write no elements, but starting at 9
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 9, exp2TO, 1, 0);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // src overrun, wants to read [5, 6]
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 1, exp2TO, 5, 2);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // src overrun, wants to read [7, 8]
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 1, exp2TO, 7, 2);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+
+ // src zero-len overrun, wants to read no elements, but starting at 9
+ assertErrorMessage(() => {
+ arrayCopy(exp1TO, 1, exp2TO, 9, 0);
+ },WebAssembly.RuntimeError, /index out of bounds/);
+}
+
+// run: check that null src or dest array causes a trap #1
+{
+ let { shouldTrap } = wasmEvalText(`(module
+ (type $a (array (mut f32)))
+ (func (export "shouldTrap")
+ ref.null $a
+ i32.const 1
+ (array.new_fixed $a 3 (f32.const 1.23) (f32.const 4.56) (f32.const 7.89))
+ i32.const 1
+ i32.const 1
+ array.copy $a $a
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ shouldTrap();
+ }, WebAssembly.RuntimeError, /null/);
+}
+
+// run: check that null src or dest array causes a trap #2
+{
+ let { shouldTrap } = wasmEvalText(`(module
+ (type $a (array (mut f32)))
+ (func (export "shouldTrap")
+ (array.new_fixed $a 3 (f32.const 1.23) (f32.const 4.56) (f32.const 7.89))
+ i32.const 1
+ ref.null $a
+ i32.const 1
+ i32.const 1
+ array.copy $a $a
+ )
+ )`).exports;
+ assertErrorMessage(() => {
+ shouldTrap();
+ }, WebAssembly.RuntimeError, /null/);
+}
+
+//////////////////////////////////////////////////////////////////////////////
+//
+// Checks for requests for oversize arrays (more than MaxArrayPayloadBytes),
+// where MaxArrayPayloadBytes == 1,987,654,321.
+
+// array.new
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array i32))
+ (func
+ ;; request exactly 2,000,000,000 bytes
+ (array.new $a (i32.const 0xABCD1234) (i32.const 500000000))
+ drop
+ )
+ (start 0)
+)
+`), WebAssembly.RuntimeError, /too many array elements/);
+
+// array.new_default
+assertErrorMessage(() => wasmEvalText(`(module
+ (type $a (array f64))
+ (func
+ ;; request exactly 2,000,000,000 bytes
+ (array.new_default $a (i32.const 250000000))
+ drop
+ )
+ (start 0)
+)
+`), WebAssembly.RuntimeError, /too many array elements/);
+
+// array.new_fixed
+// This is impossible to test because it would require to create, at a
+// minimum, 1,987,654,321 (MaxArrayPayloadBytes) / 16 = 124.3 million
+// values, if each value is a v128. However, the max number of bytes per
+// function is 7,654,321 (MaxFunctionBytes). Even if it were possible to
+// create a value using just one insn byte, there wouldn't be enough.
+
+// array.new_data
+// Similarly, impossible to test because the max data segment length is 1GB
+// (1,073,741,824 bytes) (MaxDataSegmentLengthPages * PageSize), which is less
+// than MaxArrayPayloadBytes.
+
+// array.new_element
+// Similarly, impossible to test because an element segment can contain at
+// most 10,000,000 (MaxElemSegmentLength) entries.