From 04df0718f735ded95ac4fdb14c0ba2958bd26f9e Mon Sep 17 00:00:00 2001 From: Brendan Dahl Date: Mon, 14 Sep 2026 22:52:29 +0000 Subject: [PATCH] [multibyte] Support all array element types in the interpreter Since primitive GC arrays are stored as raw byte buffers (#9051) the interpreter now only needs to be updated to handle calculating the effective address with the offset. Also fix a printing bug that only showed up once arrays of wider element types were executed: an unreachable access was printed as i32.load/i32.store regardless of its alignment, e.g. "i32.load align=8", which fails validation. Print a type that is valid for the alignment, as we do for loads from memory. Add execution tests for all the valid element types (i8, i16, i32, i64, f32, f64, v128): a new lit exec test that runs them in the interpreter before and after optimization, new spec test modules that perform the same accesses on arrays of each element type and cover the offset and align immediates, and a ctor-eval test that evaluates multibyte stores and serializes the results back as elements. Also expand the validation test with immutable non-i8 arrays, non-numeric element types, alignment and offset limits, and the feature requirement. --- src/passes/Print.cpp | 8 +- src/wasm-interpreter.h | 37 +- test/lit/array-multibyte.wast | 145 ++++- test/lit/ctor-eval/array-multibyte.wast | 63 ++ test/lit/exec/array-multibyte.wast | 581 ++++++++++++++++++ .../validation/array-multibyte-invalid.wast | 31 + test/spec/array-multibyte.wast | 400 +++++++++++- 7 files changed, 1224 insertions(+), 41 deletions(-) create mode 100644 test/lit/ctor-eval/array-multibyte.wast create mode 100644 test/lit/exec/array-multibyte.wast diff --git a/src/passes/Print.cpp b/src/passes/Print.cpp index bcc6b7884ff..e35e714d73b 100644 --- a/src/passes/Print.cpp +++ b/src/passes/Print.cpp @@ -2468,7 +2468,9 @@ struct PrintExpressionContents printHeapTypeName(curr->ref->type.getHeapType()); } void visitArrayLoad(ArrayLoad* curr) { - prepareColor(o) << forceConcrete(curr->type); + // Whatever type we print must be valid for the alignment, which matters + // when the type is unreachable and we must pick one. + prepareColor(o) << forceConcrete(curr->type, curr->align); o << ".load"; if (curr->type != Type::unreachable && curr->bytes < curr->type.getByteSize()) { @@ -2491,7 +2493,9 @@ struct PrintExpressionContents } void visitArrayStore(ArrayStore* curr) { - prepareColor(o) << forceConcrete(curr->value->type); + // Whatever type we print must be valid for the alignment, which matters + // when the value is unreachable and we must pick one. + prepareColor(o) << forceConcrete(curr->value->type, curr->align); o << ".store"; printStorePostfix(curr->bytes, curr->value->type); o << " "; diff --git a/src/wasm-interpreter.h b/src/wasm-interpreter.h index 6842005e8bb..b95c52cb7ec 100644 --- a/src/wasm-interpreter.h +++ b/src/wasm-interpreter.h @@ -2462,6 +2462,23 @@ class ExpressionRunner : public OverriddenVisitor { refVal.setElement(i, value.getSingleValue()); return Flow(); } + // Computes the effective byte address (index + offset) of a multibyte array + // access and traps if the access is out of bounds. Both index and offset are + // unsigned 32-bit values, so we compute in 64 bits to avoid overflow. + uint64_t getEffectiveArrayAddress(const Literal& refVal, + const Literal& index, + Address offset, + uint8_t bytes) { + uint64_t addr = index.getUnsigned() + uint64_t(offset); + uint64_t size = refVal.getRawBytes().size(); + // |addr| is at most 2^33 - 2 and |bytes| at most 16, so this cannot + // overflow. + if (addr + uint64_t(bytes) > size) { + trap("array oob"); + } + return addr; + } + Flow visitArrayLoad(ArrayLoad* curr) { VISIT(ref, curr->ref) VISIT(index, curr->index) @@ -2469,12 +2486,9 @@ class ExpressionRunner : public OverriddenVisitor { if (refVal.isNull()) { trap("null ref"); } - Index i = index.getSingleValue().geti32(); - size_t size = refVal.getRawBytes().size(); - if (i >= size || curr->bytes > (size - i)) { - trap("array oob"); - } - const uint8_t* p = &refVal.getRawBytes()[i]; + auto addr = getEffectiveArrayAddress( + refVal, index.getSingleValue(), curr->offset, curr->bytes); + const uint8_t* p = &refVal.getRawBytes()[addr]; switch (curr->type.getBasic()) { case Type::i32: { switch (curr->bytes) { @@ -2540,14 +2554,9 @@ class ExpressionRunner : public OverriddenVisitor { if (refVal.isNull()) { trap("null ref"); } - - Index i = index.getSingleValue().geti32(); - size_t size = refVal.getRawBytes().size(); - // Use subtraction to avoid overflow. - if (i >= size || curr->bytes > (size - i)) { - trap("array oob"); - } - uint8_t* p = &refVal.getRawBytes()[i]; + auto addr = getEffectiveArrayAddress( + refVal, index.getSingleValue(), curr->offset, curr->bytes); + uint8_t* p = &refVal.getRawBytes()[addr]; auto val = value.getSingleValue(); if (curr->value->type == Type::f32 && curr->bytes == 2) { float f32 = bit_cast(val.reinterpreti32()); diff --git a/test/lit/array-multibyte.wast b/test/lit/array-multibyte.wast index 5bb339f5e21..5fbbc525df9 100644 --- a/test/lit/array-multibyte.wast +++ b/test/lit/array-multibyte.wast @@ -10,58 +10,66 @@ ;; CHECK: (type $0 (func)) + ;; CHECK: (type $v128_array (array (mut v128))) + ;; CHECK: (type $i8_array (array (mut i8))) ;; RTRIP: (type $0 (func)) + ;; RTRIP: (type $v128_array (array (mut v128))) + ;; RTRIP: (type $i8_array (array (mut i8))) (type $i8_array (array (mut i8))) + ;; CHECK: (type $i64_array (array (mut i64))) + + ;; CHECK: (type $4 (func (param (ref $v128_array)))) + ;; CHECK: (type $i16_array (array (mut i16))) ;; CHECK: (type $i32_array (array (mut i32))) - ;; CHECK: (type $i64_array (array (mut i64))) - ;; CHECK: (type $f32_array (array (mut f32))) ;; CHECK: (type $f64_array (array (mut f64))) - ;; CHECK: (type $v128_array (array (mut v128))) - ;; CHECK: (type $imm_i8_array (array i8)) ;; CHECK: (type $imm_i32_array (array i32)) - ;; CHECK: (type $10 (func (param (ref $i16_array) (ref $i32_array) (ref $i64_array) (ref $f32_array) (ref $f64_array) (ref $v128_array)))) + ;; CHECK: (type $11 (func (param (ref $i16_array) (ref $i32_array) (ref $i64_array) (ref $f32_array) (ref $f64_array) (ref $v128_array)))) - ;; CHECK: (type $11 (func (param (ref $i8_array)))) + ;; CHECK: (type $12 (func (param (ref $i8_array)))) - ;; CHECK: (type $12 (func (param (ref $imm_i8_array) (ref $imm_i32_array)))) + ;; CHECK: (type $13 (func (param (ref $imm_i8_array) (ref $imm_i32_array)))) + + ;; CHECK: (type $14 (func (param (ref $v128_array) (ref $i64_array)))) ;; CHECK: (global $arr (ref $i8_array) (array.new_default $i8_array ;; CHECK-NEXT: (i32.const 4) ;; CHECK-NEXT: )) + ;; RTRIP: (type $i64_array (array (mut i64))) + + ;; RTRIP: (type $4 (func (param (ref $v128_array)))) + ;; RTRIP: (type $i16_array (array (mut i16))) ;; RTRIP: (type $i32_array (array (mut i32))) - ;; RTRIP: (type $i64_array (array (mut i64))) - ;; RTRIP: (type $f32_array (array (mut f32))) ;; RTRIP: (type $f64_array (array (mut f64))) - ;; RTRIP: (type $v128_array (array (mut v128))) - ;; RTRIP: (type $imm_i8_array (array i8)) ;; RTRIP: (type $imm_i32_array (array i32)) - ;; RTRIP: (type $10 (func (param (ref $i16_array) (ref $i32_array) (ref $i64_array) (ref $f32_array) (ref $f64_array) (ref $v128_array)))) + ;; RTRIP: (type $11 (func (param (ref $i16_array) (ref $i32_array) (ref $i64_array) (ref $f32_array) (ref $f64_array) (ref $v128_array)))) + + ;; RTRIP: (type $12 (func (param (ref $i8_array)))) - ;; RTRIP: (type $11 (func (param (ref $i8_array)))) + ;; RTRIP: (type $13 (func (param (ref $imm_i8_array) (ref $imm_i32_array)))) - ;; RTRIP: (type $12 (func (param (ref $imm_i8_array) (ref $imm_i32_array)))) + ;; RTRIP: (type $14 (func (param (ref $v128_array) (ref $i64_array)))) ;; RTRIP: (global $arr (ref $i8_array) (array.new_default $i8_array ;; RTRIP-NEXT: (i32.const 4) @@ -1044,7 +1052,7 @@ (type $imm_i8_array (array i8)) (type $imm_i32_array (array i32)) - ;; CHECK: (func $expanded_types (type $10) (param $a16 (ref $i16_array)) (param $a32 (ref $i32_array)) (param $a64 (ref $i64_array)) (param $af32 (ref $f32_array)) (param $af64 (ref $f64_array)) (param $av (ref $v128_array)) + ;; CHECK: (func $expanded_types (type $11) (param $a16 (ref $i16_array)) (param $a32 (ref $i32_array)) (param $a64 (ref $i64_array)) (param $af32 (ref $f32_array)) (param $af64 (ref $f64_array)) (param $av (ref $v128_array)) ;; CHECK-NEXT: (i32.store16 (type $i16_array) ;; CHECK-NEXT: (local.get $a16) ;; CHECK-NEXT: (i32.const 0) @@ -1112,7 +1120,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; RTRIP: (func $expanded_types (type $10) (param $a16 (ref $i16_array)) (param $a32 (ref $i32_array)) (param $a64 (ref $i64_array)) (param $af32 (ref $f32_array)) (param $af64 (ref $f64_array)) (param $av (ref $v128_array)) + ;; RTRIP: (func $expanded_types (type $11) (param $a16 (ref $i16_array)) (param $a32 (ref $i32_array)) (param $a64 (ref $i64_array)) (param $af32 (ref $f32_array)) (param $af64 (ref $f64_array)) (param $av (ref $v128_array)) ;; RTRIP-NEXT: (i32.store16 (type $i16_array) ;; RTRIP-NEXT: (local.get $a16) ;; RTRIP-NEXT: (i32.const 0) @@ -1195,7 +1203,7 @@ (drop (v128.load (type $v128_array) (local.get $av) (i32.const 0))) ) - ;; CHECK: (func $immediates (type $11) (param $arr (ref $i8_array)) + ;; CHECK: (func $immediates (type $12) (param $arr (ref $i8_array)) ;; CHECK-NEXT: (i32.store8 (type $i8_array) offset=4 ;; CHECK-NEXT: (local.get $arr) ;; CHECK-NEXT: (i32.const 0) @@ -1230,7 +1238,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; RTRIP: (func $immediates (type $11) (param $arr (ref $i8_array)) + ;; RTRIP: (func $immediates (type $12) (param $arr (ref $i8_array)) ;; RTRIP-NEXT: (i32.store8 (type $i8_array) offset=4 ;; RTRIP-NEXT: (local.get $arr) ;; RTRIP-NEXT: (i32.const 0) @@ -1274,7 +1282,7 @@ (drop (i32.load (type $i8_array) offset=12 align=2 (local.get $arr) (i32.const 0))) ) - ;; CHECK: (func $immutable_loads (type $12) (param $imm8 (ref $imm_i8_array)) (param $imm32 (ref $imm_i32_array)) + ;; CHECK: (func $immutable_loads (type $13) (param $imm8 (ref $imm_i8_array)) (param $imm32 (ref $imm_i32_array)) ;; CHECK-NEXT: (drop ;; CHECK-NEXT: (i32.load8_u (type $imm_i8_array) ;; CHECK-NEXT: (local.get $imm8) @@ -1288,7 +1296,7 @@ ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) ;; CHECK-NEXT: ) - ;; RTRIP: (func $immutable_loads (type $12) (param $imm8 (ref $imm_i8_array)) (param $imm32 (ref $imm_i32_array)) + ;; RTRIP: (func $immutable_loads (type $13) (param $imm8 (ref $imm_i8_array)) (param $imm32 (ref $imm_i32_array)) ;; RTRIP-NEXT: (drop ;; RTRIP-NEXT: (i32.load8_u (type $imm_i8_array) ;; RTRIP-NEXT: (local.get $imm8) @@ -1306,4 +1314,101 @@ (drop (i32.load8_u (type $imm_i8_array) (local.get $imm8) (i32.const 0))) (drop (i32.load (type $imm_i32_array) (local.get $imm32) (i32.const 0))) ) + + ;; Immediates on arrays with wider element types. The natural alignment is + ;; that of the access and not of the element, and the offset may be any u32. + ;; CHECK: (func $wide_immediates (type $14) (param $av (ref $v128_array)) (param $a64 (ref $i64_array)) + ;; CHECK-NEXT: (v128.store (type $v128_array) offset=16 + ;; CHECK-NEXT: (local.get $av) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000002 0x00000003 0x00000004) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (v128.load (type $v128_array) offset=16 align=1 + ;; CHECK-NEXT: (local.get $av) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (i64.store (type $i64_array) offset=4294967295 + ;; CHECK-NEXT: (local.get $a64) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i64.const 1) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load32_s (type $i64_array) offset=7 align=1 + ;; CHECK-NEXT: (local.get $a64) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $wide_immediates (type $14) (param $av (ref $v128_array)) (param $a64 (ref $i64_array)) + ;; RTRIP-NEXT: (v128.store (type $v128_array) offset=16 + ;; RTRIP-NEXT: (local.get $av) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (v128.const i32x4 0x00000001 0x00000002 0x00000003 0x00000004) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (v128.load (type $v128_array) offset=16 align=1 + ;; RTRIP-NEXT: (local.get $av) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (i64.store (type $i64_array) offset=4294967295 + ;; RTRIP-NEXT: (local.get $a64) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: (i64.const 1) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i64.load32_s (type $i64_array) offset=7 align=1 + ;; RTRIP-NEXT: (local.get $a64) + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: ) + (func $wide_immediates (param $av (ref $v128_array)) (param $a64 (ref $i64_array)) + (v128.store (type $v128_array) offset=16 align=16 (local.get $av) (i32.const 0) (v128.const i32x4 1 2 3 4)) + (drop (v128.load (type $v128_array) offset=16 align=1 (local.get $av) (i32.const 0))) + (i64.store (type $i64_array) offset=4294967295 align=8 (local.get $a64) (i32.const 0) (i64.const 1)) + (drop (i64.load32_s (type $i64_array) offset=7 align=1 (local.get $a64) (i32.const 0))) + ) + + ;; When the access is unreachable we have no type to print, and the type we + ;; pick must still be valid for the alignment. + ;; CHECK: (func $unreachable_load_align (type $4) (param $av (ref $v128_array)) + ;; CHECK-NEXT: (drop + ;; CHECK-NEXT: (i64.load (type $v128_array) align=8 + ;; CHECK-NEXT: (local.get $av) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $unreachable_load_align (type $4) (param $av (ref $v128_array)) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (local.get $av) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $unreachable_load_align (param $av (ref $v128_array)) + (drop (v128.load (type $v128_array) align=8 (local.get $av) (unreachable))) + ) + + ;; CHECK: (func $unreachable_store_align (type $4) (param $av (ref $v128_array)) + ;; CHECK-NEXT: (v128.store (type $v128_array) + ;; CHECK-NEXT: (local.get $av) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (unreachable) + ;; CHECK-NEXT: ) + ;; CHECK-NEXT: ) + ;; RTRIP: (func $unreachable_store_align (type $4) (param $av (ref $v128_array)) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (local.get $av) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (drop + ;; RTRIP-NEXT: (i32.const 0) + ;; RTRIP-NEXT: ) + ;; RTRIP-NEXT: (unreachable) + ;; RTRIP-NEXT: ) + (func $unreachable_store_align (param $av (ref $v128_array)) + (v128.store (type $v128_array) align=16 (local.get $av) (i32.const 0) (unreachable)) + ) ) diff --git a/test/lit/ctor-eval/array-multibyte.wast b/test/lit/ctor-eval/array-multibyte.wast new file mode 100644 index 00000000000..55dff590eef --- /dev/null +++ b/test/lit/ctor-eval/array-multibyte.wast @@ -0,0 +1,63 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --all-items and should not be edited. +;; RUN: wasm-ctor-eval %s --ctors=test --kept-exports= --quiet -all -S -o - | filecheck %s + +;; Multibyte stores are evaluated at compile time, and the resulting array +;; contents are serialized back into the module as elements of the array's +;; element type. + +(module + ;; CHECK: (type $i32_array (array (mut i32))) + (type $i32_array (array (mut i32))) + ;; CHECK: (type $i16_array (array (mut i16))) + (type $i16_array (array (mut i16))) + ;; CHECK: (type $v128_array (array (mut v128))) + (type $v128_array (array (mut v128))) + + ;; CHECK: (global $ctor-eval$global_12 (ref (exact $i32_array)) (array.new_fixed $i32_array 4 + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: (i32.const 1432778632) + ;; CHECK-NEXT: (i32.const 287454020) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_13 (ref (exact $i16_array)) (array.new_fixed $i16_array 4 + ;; CHECK-NEXT: (i32.const 56576) + ;; CHECK-NEXT: (i32.const 48076) + ;; CHECK-NEXT: (i32.const 170) + ;; CHECK-NEXT: (i32.const 0) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $ctor-eval$global_14 (ref (exact $v128_array)) (array.new_fixed $v128_array 1 + ;; CHECK-NEXT: (v128.const i32x4 0x00000001 0x00000002 0x00000003 0x00000004) + ;; CHECK-NEXT: )) + + ;; CHECK: (global $i32 (mut (ref null $i32_array)) (global.get $ctor-eval$global_12)) + (global $i32 (export "i32") (mut (ref null $i32_array)) (ref.null $i32_array)) + ;; CHECK: (global $i16 (mut (ref null $i16_array)) (global.get $ctor-eval$global_13)) + (global $i16 (export "i16") (mut (ref null $i16_array)) (ref.null $i16_array)) + ;; CHECK: (global $v128 (mut (ref null $v128_array)) (global.get $ctor-eval$global_14)) + (global $v128 (export "v128") (mut (ref null $v128_array)) (ref.null $v128_array)) + + (func $test (export "test") + ;; An i64 store covers two i32 elements. + (global.set $i32 (array.new_default $i32_array (i32.const 4))) + (i64.store (type $i32_array) (global.get $i32) (i32.const 4) + (i64.const 0x1122334455667788) + ) + ;; An unaligned i32 store covers parts of two i16 elements. + (global.set $i16 (array.new_default $i16_array (i32.const 4))) + (i32.store (type $i16_array) offset=1 (global.get $i16) (i32.const 0) + (i32.const 0xaabbccdd) + ) + ;; A v128 store covers an entire v128 element. + (global.set $v128 (array.new_default $v128_array (i32.const 1))) + (v128.store (type $v128_array) (global.get $v128) (i32.const 0) + (v128.const i32x4 1 2 3 4) + ) + ) +) +;; CHECK: (export "i32" (global $i32)) + +;; CHECK: (export "i16" (global $i16)) + +;; CHECK: (export "v128" (global $v128)) diff --git a/test/lit/exec/array-multibyte.wast b/test/lit/exec/array-multibyte.wast new file mode 100644 index 00000000000..14974c2a642 --- /dev/null +++ b/test/lit/exec/array-multibyte.wast @@ -0,0 +1,581 @@ +;; NOTE: Assertions have been generated by update_lit_checks.py --output=fuzz-exec and should not be edited. + +;; RUN: wasm-opt %s -all --fuzz-exec -q -o /dev/null 2>&1 | filecheck %s + +;; Multibyte array accesses execute on the payload bytes of an array of any +;; numeric element type, so the element type of the array is independent of the +;; type and size of the access. + +(module + (type $i8 (array (mut i8))) + (type $i16 (array (mut i16))) + (type $i32 (array (mut i32))) + (type $i64 (array (mut i64))) + (type $f32 (array (mut f32))) + (type $f64 (array (mut f64))) + (type $v128 (array (mut v128))) + (type $imm-i32 (array i32)) + + ;; An i8 array: the payload is 8 bytes long. + (func $new-i8 (result (ref $i8)) + (array.new_default $i8 (i32.const 8)) + ) + + ;; An i16 array: the payload is 8 bytes long. + (func $new-i16 (result (ref $i16)) + (array.new_default $i16 (i32.const 4)) + ) + + ;; An i32 array: the payload is 8 bytes long. + (func $new-i32 (result (ref $i32)) + (array.new_default $i32 (i32.const 2)) + ) + + ;; An i64 array: the payload is 16 bytes long. + (func $new-i64 (result (ref $i64)) + (array.new_default $i64 (i32.const 2)) + ) + + ;; An f32 array: the payload is 8 bytes long. + (func $new-f32 (result (ref $f32)) + (array.new_default $f32 (i32.const 2)) + ) + + ;; An f64 array: the payload is 16 bytes long. + (func $new-f64 (result (ref $f64)) + (array.new_default $f64 (i32.const 2)) + ) + + ;; A v128 array: the payload is 32 bytes long. + (func $new-v128 (result (ref $v128)) + (array.new_default $v128 (i32.const 2)) + ) + + ;; Storing and loading works the same on an array of any element type, as the + ;; accesses are on the raw payload bytes. + + ;; CHECK: [fuzz-exec] export i8-array + ;; CHECK-NEXT: [fuzz-exec] note result: i8-array => 305419896 + (func $i8-array (export "i8-array") (result i32) + (local $a (ref $i8)) + (local.set $a (call $new-i8)) + (i32.store (type $i8) (local.get $a) (i32.const 4) (i32.const 0x12345678)) + (i32.load (type $i8) (local.get $a) (i32.const 4)) + ) + + ;; CHECK: [fuzz-exec] export i16-array + ;; CHECK-NEXT: [fuzz-exec] note result: i16-array => 305419896 + (func $i16-array (export "i16-array") (result i32) + (local $a (ref $i16)) + (local.set $a (call $new-i16)) + (i32.store (type $i16) (local.get $a) (i32.const 4) (i32.const 0x12345678)) + (i32.load (type $i16) (local.get $a) (i32.const 4)) + ) + + ;; CHECK: [fuzz-exec] export i32-array + ;; CHECK-NEXT: [fuzz-exec] note result: i32-array => 305419896 + (func $i32-array (export "i32-array") (result i32) + (local $a (ref $i32)) + (local.set $a (call $new-i32)) + (i32.store (type $i32) (local.get $a) (i32.const 4) (i32.const 0x12345678)) + (i32.load (type $i32) (local.get $a) (i32.const 4)) + ) + + ;; CHECK: [fuzz-exec] export i64-array + ;; CHECK-NEXT: [fuzz-exec] note result: i64-array => 305419896 + (func $i64-array (export "i64-array") (result i32) + (local $a (ref $i64)) + (local.set $a (call $new-i64)) + (i32.store (type $i64) (local.get $a) (i32.const 4) (i32.const 0x12345678)) + (i32.load (type $i64) (local.get $a) (i32.const 4)) + ) + + ;; CHECK: [fuzz-exec] export f32-array + ;; CHECK-NEXT: [fuzz-exec] note result: f32-array => 305419896 + (func $f32-array (export "f32-array") (result i32) + (local $a (ref $f32)) + (local.set $a (call $new-f32)) + (i32.store (type $f32) (local.get $a) (i32.const 4) (i32.const 0x12345678)) + (i32.load (type $f32) (local.get $a) (i32.const 4)) + ) + + ;; CHECK: [fuzz-exec] export f64-array + ;; CHECK-NEXT: [fuzz-exec] note result: f64-array => 305419896 + (func $f64-array (export "f64-array") (result i32) + (local $a (ref $f64)) + (local.set $a (call $new-f64)) + (i32.store (type $f64) (local.get $a) (i32.const 4) (i32.const 0x12345678)) + (i32.load (type $f64) (local.get $a) (i32.const 4)) + ) + + ;; CHECK: [fuzz-exec] export v128-array + ;; CHECK-NEXT: [fuzz-exec] note result: v128-array => 305419896 + (func $v128-array (export "v128-array") (result i32) + (local $a (ref $v128)) + (local.set $a (call $new-v128)) + (i32.store (type $v128) (local.get $a) (i32.const 4) (i32.const 0x12345678)) + (i32.load (type $v128) (local.get $a) (i32.const 4)) + ) + + ;; Accesses do not need to be aligned with the array's elements: this writes + ;; an i32 in the middle of the elements of an i64 array, and reads it back in + ;; two pieces. + ;; CHECK: [fuzz-exec] export unaligned-with-elements + ;; CHECK-NEXT: [fuzz-exec] note result: unaligned-with-elements => 287454020 + (func $unaligned-with-elements (export "unaligned-with-elements") (result i32) + (local $a (ref $i64)) + (local.set $a (call $new-i64)) + (i32.store (type $i64) align=1 (local.get $a) (i32.const 6) (i32.const 0x11223344)) + (i32.or + (i32.load16_u (type $i64) align=1 (local.get $a) (i32.const 6)) + (i32.shl + (i32.load16_u (type $i64) align=1 (local.get $a) (i32.const 8)) + (i32.const 16) + ) + ) + ) + + ;; Multibyte stores are visible to array.get, and array.set is visible to + ;; multibyte loads. Here the second element of an i32 array is written as + ;; bytes and read as an element. + ;; CHECK: [fuzz-exec] export store-then-get + ;; CHECK-NEXT: [fuzz-exec] note result: store-then-get => 1337 + (func $store-then-get (export "store-then-get") (result i32) + (local $a (ref $i32)) + (local.set $a (call $new-i32)) + (i32.store (type $i32) (local.get $a) (i32.const 4) (i32.const 1337)) + (array.get $i32 (local.get $a) (i32.const 1)) + ) + + ;; CHECK: [fuzz-exec] export set-then-load + ;; CHECK-NEXT: [fuzz-exec] note result: set-then-load => 1337 + (func $set-then-load (export "set-then-load") (result i32) + (local $a (ref $i32)) + (local.set $a (call $new-i32)) + (array.set $i32 (local.get $a) (i32.const 1) (i32.const 1337)) + (i32.load (type $i32) (local.get $a) (i32.const 4)) + ) + + ;; The same, for the packed elements of an i16 array. The i32 write covers + ;; two elements. + ;; CHECK: [fuzz-exec] export store-then-get-packed + ;; CHECK-NEXT: [fuzz-exec] note result: store-then-get-packed => 4659 + (func $store-then-get-packed (export "store-then-get-packed") (result i32) + (local $a (ref $i16)) + (local.set $a (call $new-i16)) + (i32.store (type $i16) (local.get $a) (i32.const 0) (i32.const 0xffff1234)) + (i32.add + (array.get_u $i16 (local.get $a) (i32.const 0)) + (array.get_s $i16 (local.get $a) (i32.const 1)) + ) + ) + + ;; CHECK: [fuzz-exec] export set-then-load-packed + ;; CHECK-NEXT: [fuzz-exec] note result: set-then-load-packed => -60876 + (func $set-then-load-packed (export "set-then-load-packed") (result i32) + (local $a (ref $i16)) + (local.set $a (call $new-i16)) + (array.set $i16 (local.get $a) (i32.const 0) (i32.const 0x1234)) + (array.set $i16 (local.get $a) (i32.const 1) (i32.const 0xffff)) + (i32.load (type $i16) (local.get $a) (i32.const 0)) + ) + + ;; Loads of every width and sign, on an array whose elements are wider than + ;; the access. + ;; CHECK: [fuzz-exec] export load-widths-i32 + ;; CHECK-NEXT: [fuzz-exec] note result: load-widths-i32 => 65284 + (func $load-widths-i32 (export "load-widths-i32") (result i32) + (local $a (ref $i64)) + (local.set $a (call $new-i64)) + (i32.store (type $i64) (local.get $a) (i32.const 0) (i32.const 0x8000ff81)) + (i32.add + (i32.add + (i32.load8_s (type $i64) (local.get $a) (i32.const 0)) + (i32.load8_u (type $i64) (local.get $a) (i32.const 0)) + ) + (i32.add + (i32.load16_s (type $i64) (local.get $a) (i32.const 0)) + (i32.load16_u (type $i64) (local.get $a) (i32.const 0)) + ) + ) + ) + + ;; CHECK: [fuzz-exec] export load-widths-i64 + ;; CHECK-NEXT: [fuzz-exec] note result: load-widths-i64 => 33491462 + (func $load-widths-i64 (export "load-widths-i64") (result i64) + (local $a (ref $f64)) + (local.set $a (call $new-f64)) + (i64.store (type $f64) (local.get $a) (i32.const 0) (i64.const 0x8000000080ff8281)) + (i64.add + (i64.add + (i64.load8_s (type $f64) (local.get $a) (i32.const 0)) + (i64.load8_u (type $f64) (local.get $a) (i32.const 0)) + ) + (i64.add + (i64.add + (i64.load16_s (type $f64) (local.get $a) (i32.const 0)) + (i64.load16_u (type $f64) (local.get $a) (i32.const 0)) + ) + (i64.add + (i64.load32_s (type $f64) (local.get $a) (i32.const 0)) + (i64.load32_u (type $f64) (local.get $a) (i32.const 0)) + ) + ) + ) + ) + + ;; Stores of every width wrap the value, leaving the other bytes alone. + ;; CHECK: [fuzz-exec] export store-widths-i32 + ;; CHECK-NEXT: [fuzz-exec] note result: store-widths-i32 => 878116660 + (func $store-widths-i32 (export "store-widths-i32") (result i32) + (local $a (ref $i32)) + (local.set $a (call $new-i32)) + (i32.store (type $i32) (local.get $a) (i32.const 0) (i32.const -1)) + (i32.store8 (type $i32) (local.get $a) (i32.const 0) (i32.const 0x1234)) + (i32.store16 (type $i32) (local.get $a) (i32.const 2) (i32.const 0x123456)) + (i32.load (type $i32) (local.get $a) (i32.const 0)) + ) + + ;; CHECK: [fuzz-exec] export store-widths-i64 + ;; CHECK-NEXT: [fuzz-exec] note result: store-widths-i64 => 3771334298718109492 + (func $store-widths-i64 (export "store-widths-i64") (result i64) + (local $a (ref $i64)) + (local.set $a (call $new-i64)) + (i64.store (type $i64) (local.get $a) (i32.const 0) (i64.const -1)) + (i64.store8 (type $i64) (local.get $a) (i32.const 0) (i64.const 0x1234)) + (i64.store16 (type $i64) (local.get $a) (i32.const 2) (i64.const 0x123456)) + (i64.store32 (type $i64) (local.get $a) (i32.const 4) (i64.const 0x1234567890)) + (i64.load (type $i64) (local.get $a) (i32.const 0)) + ) + + ;; Floating point values are stored and loaded as their bits. + ;; CHECK: [fuzz-exec] export float-bits + ;; CHECK-NEXT: [fuzz-exec] note result: float-bits => -3.75 + (func $float-bits (export "float-bits") (result f64) + (local $a (ref $i8)) + (local.set $a (call $new-i8)) + (f32.store (type $i8) (local.get $a) (i32.const 0) (f32.const 1.5)) + (f64.store (type $i8) (local.get $a) (i32.const 0) (f64.const -3.75)) + (f64.load (type $i8) (local.get $a) (i32.const 0)) + ) + + ;; CHECK: [fuzz-exec] export float-reinterpret + ;; CHECK-NEXT: [fuzz-exec] note result: float-reinterpret => 1.5 + (func $float-reinterpret (export "float-reinterpret") (result f32) + (local $a (ref $f64)) + (local.set $a (call $new-f64)) + (i32.store (type $f64) (local.get $a) (i32.const 4) (i32.const 0x3fc00000)) + (f32.load (type $f64) (local.get $a) (i32.const 4)) + ) + + ;; v128 accesses read and write 16 bytes. + ;; CHECK: [fuzz-exec] export v128-access + ;; CHECK-NEXT: [fuzz-exec] note result: v128-access => i32x4 0x00000001 0x00000002 0x00000003 0x00000004 + (func $v128-access (export "v128-access") (result v128) + (local $a (ref $i32)) + (local.set $a (array.new_default $i32 (i32.const 8))) + (v128.store (type $i32) (local.get $a) (i32.const 8) + (v128.const i32x4 1 2 3 4) + ) + (v128.load (type $i32) (local.get $a) (i32.const 8)) + ) + + ;; A v128 store is visible to the individual elements it covers. + ;; CHECK: [fuzz-exec] export v128-store-elements + ;; CHECK-NEXT: [fuzz-exec] note result: v128-store-elements => 60 + (func $v128-store-elements (export "v128-store-elements") (result i32) + (local $a (ref $i32)) + (local.set $a (array.new_default $i32 (i32.const 4))) + (v128.store (type $i32) (local.get $a) (i32.const 0) + (v128.const i32x4 10 20 30 40) + ) + (i32.add + (array.get $i32 (local.get $a) (i32.const 1)) + (array.get $i32 (local.get $a) (i32.const 3)) + ) + ) + + ;; A v128 access can also span the elements of a v128 array. + ;; CHECK: [fuzz-exec] export v128-array-unaligned + ;; CHECK-NEXT: [fuzz-exec] note result: v128-array-unaligned => i32x4 0x00000002 0x00000000 0x00000003 0x00000000 + (func $v128-array-unaligned (export "v128-array-unaligned") (result v128) + (local $a (ref $v128)) + (local.set $a (call $new-v128)) + (v128.store (type $v128) (local.get $a) (i32.const 0) + (v128.const i64x2 1 2) + ) + (v128.store (type $v128) (local.get $a) (i32.const 16) + (v128.const i64x2 3 4) + ) + (v128.load (type $v128) align=1 (local.get $a) (i32.const 8)) + ) + + ;; The static offset immediate is added to the dynamic index. + ;; CHECK: [fuzz-exec] export offset + ;; CHECK-NEXT: [fuzz-exec] note result: offset => 305419896 + (func $offset (export "offset") (result i32) + (local $a (ref $i8)) + (local.set $a (call $new-i8)) + (i32.store (type $i8) offset=2 (local.get $a) (i32.const 2) (i32.const 0x12345678)) + (i32.load (type $i8) offset=3 (local.get $a) (i32.const 1)) + ) + + ;; The offset is enough on its own. + ;; CHECK: [fuzz-exec] export offset-only + ;; CHECK-NEXT: [fuzz-exec] note result: offset-only => 43981 + (func $offset-only (export "offset-only") (result i32) + (local $a (ref $i16)) + (local.set $a (call $new-i16)) + (i32.store16 (type $i16) offset=6 (local.get $a) (i32.const 0) (i32.const 0xabcd)) + (array.get_u $i16 (local.get $a) (i32.const 3)) + ) + + ;; Alignment is only a hint, and does not affect the result. + ;; CHECK: [fuzz-exec] export align + ;; CHECK-NEXT: [fuzz-exec] note result: align => 305419896 + (func $align (export "align") (result i32) + (local $a (ref $i32)) + (local.set $a (call $new-i32)) + (i32.store (type $i32) align=1 (local.get $a) (i32.const 0) (i32.const 0x12345678)) + (i32.load (type $i32) align=4 (local.get $a) (i32.const 0)) + ) + + ;; Loads from immutable arrays are allowed. + ;; CHECK: [fuzz-exec] export immutable + ;; CHECK-NEXT: [fuzz-exec] note result: immutable => 1432778632 + (func $immutable (export "immutable") (result i32) + (i32.load (type $imm-i32) + (array.new_fixed $imm-i32 2 (i32.const 0x11223344) (i32.const 0x55667788)) + (i32.const 4) + ) + ) + + ;; An access of the last bytes of the payload is in bounds. + ;; CHECK: [fuzz-exec] export in-bounds-end + ;; CHECK-NEXT: [fuzz-exec] note result: in-bounds-end => 305419896 + (func $in-bounds-end (export "in-bounds-end") (result i32) + (local $a (ref $i16)) + (local.set $a (call $new-i16)) + (i32.store (type $i16) (local.get $a) (i32.const 4) (i32.const 0x12345678)) + (i32.load (type $i16) (local.get $a) (i32.const 4)) + ) + + ;; An access one byte past that traps, even though the first byte is in + ;; bounds. + ;; CHECK: [fuzz-exec] export oob-end + ;; CHECK-NEXT: [trap array oob] + (func $oob-end (export "oob-end") (result i32) + (local $a (ref $i16)) + (local.set $a (call $new-i16)) + (i32.load (type $i16) (local.get $a) (i32.const 5)) + ) + + ;; The bounds check uses the payload size in bytes, not the number of + ;; elements, so an index that is a valid element index can still be out of + ;; bounds. + ;; CHECK: [fuzz-exec] export oob-past-payload + ;; CHECK-NEXT: [trap array oob] + (func $oob-past-payload (export "oob-past-payload") (result i32) + (local $a (ref $i32)) + (local.set $a (call $new-i32)) + (i32.load8_u (type $i32) (local.get $a) (i32.const 8)) + ) + + ;; The offset is included in the bounds check. + ;; CHECK: [fuzz-exec] export oob-offset + ;; CHECK-NEXT: [trap array oob] + (func $oob-offset (export "oob-offset") (result i32) + (local $a (ref $i8)) + (local.set $a (call $new-i8)) + (i32.load8_u (type $i8) offset=8 (local.get $a) (i32.const 0)) + ) + + ;; The index is unsigned, and the sum of the index and the offset does not + ;; wrap around. + ;; CHECK: [fuzz-exec] export oob-unsigned-index + ;; CHECK-NEXT: [trap array oob] + (func $oob-unsigned-index (export "oob-unsigned-index") (result i32) + (local $a (ref $i8)) + (local.set $a (call $new-i8)) + (i32.load8_u (type $i8) (local.get $a) (i32.const -1)) + ) + + ;; CHECK: [fuzz-exec] export oob-offset-no-wrap + ;; CHECK-NEXT: [trap array oob] + (func $oob-offset-no-wrap (export "oob-offset-no-wrap") (result i32) + (local $a (ref $i8)) + (local.set $a (call $new-i8)) + (i32.load8_u (type $i8) offset=1 (local.get $a) (i32.const -1)) + ) + + ;; Stores are bounds-checked in the same way as loads. + ;; CHECK: [fuzz-exec] export oob-store + ;; CHECK-NEXT: [trap array oob] + (func $oob-store (export "oob-store") + (local $a (ref $i64)) + (local.set $a (call $new-i64)) + (i64.store (type $i64) (local.get $a) (i32.const 9) (i64.const 1)) + ) + + ;; Any access on an empty array traps. + ;; CHECK: [fuzz-exec] export oob-empty + ;; CHECK-NEXT: [trap array oob] + (func $oob-empty (export "oob-empty") (result i32) + (i32.load8_u (type $v128) + (array.new_default $v128 (i32.const 0)) + (i32.const 0) + ) + ) + + ;; A null reference traps, before the bounds check. + ;; CHECK: [fuzz-exec] export null-load + ;; CHECK-NEXT: [trap null ref] + (func $null-load (export "null-load") (result i32) + (i32.load (type $i32) (ref.null $i32) (i32.const 999)) + ) + + ;; CHECK: [fuzz-exec] export null-store + ;; CHECK-NEXT: [trap null ref] + (func $null-store (export "null-store") + (i32.store (type $i32) (ref.null $i32) (i32.const 999) (i32.const 1)) + ) +) +;; CHECK: [fuzz-exec] export i8-array +;; CHECK-NEXT: [fuzz-exec] note result: i8-array => 305419896 + +;; CHECK: [fuzz-exec] export i16-array +;; CHECK-NEXT: [fuzz-exec] note result: i16-array => 305419896 + +;; CHECK: [fuzz-exec] export i32-array +;; CHECK-NEXT: [fuzz-exec] note result: i32-array => 305419896 + +;; CHECK: [fuzz-exec] export i64-array +;; CHECK-NEXT: [fuzz-exec] note result: i64-array => 305419896 + +;; CHECK: [fuzz-exec] export f32-array +;; CHECK-NEXT: [fuzz-exec] note result: f32-array => 305419896 + +;; CHECK: [fuzz-exec] export f64-array +;; CHECK-NEXT: [fuzz-exec] note result: f64-array => 305419896 + +;; CHECK: [fuzz-exec] export v128-array +;; CHECK-NEXT: [fuzz-exec] note result: v128-array => 305419896 + +;; CHECK: [fuzz-exec] export unaligned-with-elements +;; CHECK-NEXT: [fuzz-exec] note result: unaligned-with-elements => 287454020 + +;; CHECK: [fuzz-exec] export store-then-get +;; CHECK-NEXT: [fuzz-exec] note result: store-then-get => 1337 + +;; CHECK: [fuzz-exec] export set-then-load +;; CHECK-NEXT: [fuzz-exec] note result: set-then-load => 1337 + +;; CHECK: [fuzz-exec] export store-then-get-packed +;; CHECK-NEXT: [fuzz-exec] note result: store-then-get-packed => 4659 + +;; CHECK: [fuzz-exec] export set-then-load-packed +;; CHECK-NEXT: [fuzz-exec] note result: set-then-load-packed => -60876 + +;; CHECK: [fuzz-exec] export load-widths-i32 +;; CHECK-NEXT: [fuzz-exec] note result: load-widths-i32 => 65284 + +;; CHECK: [fuzz-exec] export load-widths-i64 +;; CHECK-NEXT: [fuzz-exec] note result: load-widths-i64 => 33491462 + +;; CHECK: [fuzz-exec] export store-widths-i32 +;; CHECK-NEXT: [fuzz-exec] note result: store-widths-i32 => 878116660 + +;; CHECK: [fuzz-exec] export store-widths-i64 +;; CHECK-NEXT: [fuzz-exec] note result: store-widths-i64 => 3771334298718109492 + +;; CHECK: [fuzz-exec] export float-bits +;; CHECK-NEXT: [fuzz-exec] note result: float-bits => -3.75 + +;; CHECK: [fuzz-exec] export float-reinterpret +;; CHECK-NEXT: [fuzz-exec] note result: float-reinterpret => 1.5 + +;; CHECK: [fuzz-exec] export v128-access +;; CHECK-NEXT: [fuzz-exec] note result: v128-access => i32x4 0x00000001 0x00000002 0x00000003 0x00000004 + +;; CHECK: [fuzz-exec] export v128-store-elements +;; CHECK-NEXT: [fuzz-exec] note result: v128-store-elements => 60 + +;; CHECK: [fuzz-exec] export v128-array-unaligned +;; CHECK-NEXT: [fuzz-exec] note result: v128-array-unaligned => i32x4 0x00000002 0x00000000 0x00000003 0x00000000 + +;; CHECK: [fuzz-exec] export offset +;; CHECK-NEXT: [fuzz-exec] note result: offset => 305419896 + +;; CHECK: [fuzz-exec] export offset-only +;; CHECK-NEXT: [fuzz-exec] note result: offset-only => 43981 + +;; CHECK: [fuzz-exec] export align +;; CHECK-NEXT: [fuzz-exec] note result: align => 305419896 + +;; CHECK: [fuzz-exec] export immutable +;; CHECK-NEXT: [fuzz-exec] note result: immutable => 1432778632 + +;; CHECK: [fuzz-exec] export in-bounds-end +;; CHECK-NEXT: [fuzz-exec] note result: in-bounds-end => 305419896 + +;; CHECK: [fuzz-exec] export oob-end +;; CHECK-NEXT: [trap array oob] + +;; CHECK: [fuzz-exec] export oob-past-payload +;; CHECK-NEXT: [trap array oob] + +;; CHECK: [fuzz-exec] export oob-offset +;; CHECK-NEXT: [trap array oob] + +;; CHECK: [fuzz-exec] export oob-unsigned-index +;; CHECK-NEXT: [trap array oob] + +;; CHECK: [fuzz-exec] export oob-offset-no-wrap +;; CHECK-NEXT: [trap array oob] + +;; CHECK: [fuzz-exec] export oob-store +;; CHECK-NEXT: [trap array oob] + +;; CHECK: [fuzz-exec] export oob-empty +;; CHECK-NEXT: [trap array oob] + +;; CHECK: [fuzz-exec] export null-load +;; CHECK-NEXT: [trap null ref] + +;; CHECK: [fuzz-exec] export null-store +;; CHECK-NEXT: [trap null ref] +;; CHECK-NEXT: [fuzz-exec] comparing align +;; CHECK-NEXT: [fuzz-exec] comparing f32-array +;; CHECK-NEXT: [fuzz-exec] comparing f64-array +;; CHECK-NEXT: [fuzz-exec] comparing float-bits +;; CHECK-NEXT: [fuzz-exec] comparing float-reinterpret +;; CHECK-NEXT: [fuzz-exec] comparing i16-array +;; CHECK-NEXT: [fuzz-exec] comparing i32-array +;; CHECK-NEXT: [fuzz-exec] comparing i64-array +;; CHECK-NEXT: [fuzz-exec] comparing i8-array +;; CHECK-NEXT: [fuzz-exec] comparing immutable +;; CHECK-NEXT: [fuzz-exec] comparing in-bounds-end +;; CHECK-NEXT: [fuzz-exec] comparing load-widths-i32 +;; CHECK-NEXT: [fuzz-exec] comparing load-widths-i64 +;; CHECK-NEXT: [fuzz-exec] comparing null-load +;; CHECK-NEXT: [fuzz-exec] comparing null-store +;; CHECK-NEXT: [fuzz-exec] comparing offset +;; CHECK-NEXT: [fuzz-exec] comparing offset-only +;; CHECK-NEXT: [fuzz-exec] comparing oob-empty +;; CHECK-NEXT: [fuzz-exec] comparing oob-end +;; CHECK-NEXT: [fuzz-exec] comparing oob-offset +;; CHECK-NEXT: [fuzz-exec] comparing oob-offset-no-wrap +;; CHECK-NEXT: [fuzz-exec] comparing oob-past-payload +;; CHECK-NEXT: [fuzz-exec] comparing oob-store +;; CHECK-NEXT: [fuzz-exec] comparing oob-unsigned-index +;; CHECK-NEXT: [fuzz-exec] comparing set-then-load +;; CHECK-NEXT: [fuzz-exec] comparing set-then-load-packed +;; CHECK-NEXT: [fuzz-exec] comparing store-then-get +;; CHECK-NEXT: [fuzz-exec] comparing store-then-get-packed +;; CHECK-NEXT: [fuzz-exec] comparing store-widths-i32 +;; CHECK-NEXT: [fuzz-exec] comparing store-widths-i64 +;; CHECK-NEXT: [fuzz-exec] comparing unaligned-with-elements +;; CHECK-NEXT: [fuzz-exec] comparing v128-access +;; CHECK-NEXT: [fuzz-exec] comparing v128-array +;; CHECK-NEXT: [fuzz-exec] comparing v128-array-unaligned +;; CHECK-NEXT: [fuzz-exec] comparing v128-store-elements diff --git a/test/lit/validation/array-multibyte-invalid.wast b/test/lit/validation/array-multibyte-invalid.wast index 263e0befc16..e5205849f31 100644 --- a/test/lit/validation/array-multibyte-invalid.wast +++ b/test/lit/validation/array-multibyte-invalid.wast @@ -1,14 +1,25 @@ ;; RUN: not wasm-opt -all %s 2>&1 | filecheck %s +;; RUN: not wasm-opt -all --disable-multibyte %s 2>&1 | filecheck %s --check-prefix=NO-FEATURE (module (type $imm (array i8)) + (type $imm_i32 (array i32)) (type $any_arr (array (mut anyref))) + (type $func_arr (array (mut funcref))) + (type $v128_arr (array (mut v128))) + ;; NO-FEATURE: unexpected false: array.store requires multibyte ;; CHECK: unexpected false: array store type must be mutable (func $store-immutable (param $a (ref $imm)) (i32.store8 (type $imm) (local.get $a) (i32.const 0) (i32.const 0)) ) + ;; The element type does not need to be i8 for the array to be immutable. + ;; CHECK: unexpected false: array store type must be mutable + (func $store-immutable-i32 (param $a (ref $imm_i32)) + (i32.store (type $imm_i32) (local.get $a) (i32.const 0) (i32.const 0)) + ) + ;; CHECK: unexpected false: array load type must be a numeric type (func $load-non-numeric (param $a (ref $any_arr)) (drop (i32.load8_u (type $any_arr) (local.get $a) (i32.const 0))) @@ -19,13 +30,33 @@ (i32.store8 (type $any_arr) (local.get $a) (i32.const 0) (i32.const 0)) ) + ;; Function references are not numeric either. + ;; CHECK: unexpected false: array load type must be a numeric type + (func $load-func (param $a (ref $func_arr)) + (drop (i32.load8_u (type $func_arr) (local.get $a) (i32.const 0))) + ) + ;; CHECK: unexpected false: alignment must not exceed natural (func $bad-align (param $a (ref $imm)) (drop (i32.load8_u (type $imm) align=2 (local.get $a) (i32.const 0))) ) + ;; The natural alignment is that of the access, not of the array's elements. + ;; CHECK: unexpected false: alignment must not exceed natural + (func $bad-align-v128 (param $a (ref $v128_arr)) + (drop (v128.load (type $v128_arr) align=32 (local.get $a) (i32.const 0))) + ) + ;; CHECK: unexpected false: offset must be u32 (func $bad-offset (param $a (ref $imm)) (drop (i32.load8_u (type $imm) offset=4294967296 (local.get $a) (i32.const 0))) ) + + ;; NO-FEATURE: unexpected false: array.load requires multibyte + ;; CHECK: unexpected false: offset must be u32 + (func $bad-offset-store (param $a (ref $v128_arr)) + (v128.store (type $v128_arr) offset=4294967296 (local.get $a) (i32.const 0) + (v128.const i32x4 0 0 0 0) + ) + ) ) diff --git a/test/spec/array-multibyte.wast b/test/spec/array-multibyte.wast index b38ed06be83..e1863db3cef 100644 --- a/test/spec/array-multibyte.wast +++ b/test/spec/array-multibyte.wast @@ -362,7 +362,10 @@ (assert_return (invoke "load_i32_16_u" (i32.const 0)) (i32.const 0x7FFF)) (assert_return (invoke "load_i32_16_s" (i32.const 0)) (i32.const 0x7FFF)) +(invoke "i32_set_i8" (i32.const 0) (i32.const 0xFF)) (invoke "i32_set_i8" (i32.const 1) (i32.const 0xFF)) +(invoke "i32_set_i8" (i32.const 2) (i32.const 0x56)) +(invoke "i32_set_i8" (i32.const 3) (i32.const 0x78)) (assert_return (invoke "load_i32_16_u" (i32.const 0)) (i32.const 0xFFFF)) (assert_return (invoke "load_i32_16_s" (i32.const 0)) (i32.const -1)) @@ -385,6 +388,7 @@ ;; i32_set_i16: Writes 2 bytes ;; Valid range: offset + 2 <= 4 -> Max offset 2 +(invoke "i32_set_i32" (i32.const 0) (i32.const 0)) (assert_trap (invoke "i32_set_i16" (i32.const -1) (i32.const 0)) "out of bounds") (assert_return (invoke "i32_set_i16" (i32.const 0) (i32.const 0))) (assert_return (invoke "i32_set_i16" (i32.const 1) (i32.const 0))) @@ -394,6 +398,7 @@ ;; i32_set_i32: Writes 4 bytes ;; Valid range: offset + 4 <= 4 -> Max offset 0 +(invoke "i32_set_i32" (i32.const 0) (i32.const 0)) (assert_trap (invoke "i32_set_i32" (i32.const -1) (i32.const 0)) "out of bounds") (assert_return (invoke "i32_set_i32" (i32.const 0) (i32.const 0))) (assert_trap (invoke "i32_set_i32" (i32.const 1) (i32.const 0xFFFFFFFF)) "out of bounds") @@ -401,6 +406,7 @@ ;; f32_set: Writes 4 bytes ;; Valid range: offset + 4 <= 4 -> Max offset 0 +(invoke "i32_set_i32" (i32.const 0) (i32.const 0)) (assert_trap (invoke "f32_set" (i32.const -1) (f32.const 0)) "out of bounds") (assert_return (invoke "f32_set" (i32.const 0) (f32.const 0))) (assert_trap (invoke "f32_set" (i32.const 1) (f32.const 1.0)) "out of bounds") @@ -423,6 +429,7 @@ ;; i64_set_i16: Writes 2 bytes ;; Valid range: offset + 2 <= 8 -> Max offset 6 +(invoke "i64_set_i64" (i32.const 0) (i64.const 0)) (assert_trap (invoke "i64_set_i16" (i32.const -1) (i64.const 0)) "out of bounds") (assert_return (invoke "i64_set_i16" (i32.const 0) (i64.const 0))) (assert_return (invoke "i64_set_i16" (i32.const 1) (i64.const 0))) @@ -433,6 +440,7 @@ ;; i64_set_i32: Writes 4 bytes ;; Valid range: offset + 4 <= 8 -> Max offset 4 +(invoke "i64_set_i64" (i32.const 0) (i64.const 0)) (assert_trap (invoke "i64_set_i32" (i32.const -1) (i64.const 0)) "out of bounds") (assert_return (invoke "i64_set_i32" (i32.const 0) (i64.const 0))) (assert_return (invoke "i64_set_i32" (i32.const 1) (i64.const 0))) @@ -443,6 +451,7 @@ ;; i64_set_i64: Writes 8 bytes ;; Valid range: offset + 8 <= 8 -> Max offset 0 +(invoke "i64_set_i64" (i32.const 0) (i64.const 0)) (assert_trap (invoke "i64_set_i64" (i32.const -1) (i64.const 0)) "out of bounds") (assert_return (invoke "i64_set_i64" (i32.const 0) (i64.const 0))) (assert_trap (invoke "i64_set_i64" (i32.const 1) (i64.const 0xFFFFFFFFFFFFFFFF)) "out of bounds") @@ -450,6 +459,7 @@ ;; f64_set: Writes 8 bytes ;; Valid range: offset + 8 <= 8 -> Max offset 0 +(invoke "i64_set_i64" (i32.const 0) (i64.const 0)) (assert_trap (invoke "f64_set" (i32.const -1) (f64.const 0)) "out of bounds") (assert_return (invoke "f64_set" (i32.const 0) (f64.const 0))) (assert_trap (invoke "f64_set" (i32.const 1) (f64.const 1.0)) "out of bounds") @@ -460,7 +470,7 @@ (module (type $a (array i8)) (func (export "i32_set_immutable") (param $a (ref $a)) - (i32.store (type $i8_array) (local.get $a) (i32.const 0) (i32.const 1)) + (i32.store (type $a) (local.get $a) (i32.const 0) (i32.const 1)) ) ) "array is immutable" @@ -468,12 +478,12 @@ (assert_invalid (module - (type $a (array (mut i16))) - (func (export "i32_set_mut_i16") (param $a (ref $a)) - (i32.store (type $i8_array) (local.get $a) (i32.const 0) (i32.const 1)) + (type $a (array (mut anyref))) + (func (export "i32_set_non_numeric") (param $a (ref $a)) + (i32.store (type $a) (local.get $a) (i32.const 0) (i32.const 1)) ) ) - "array element type must be i8" + "array element type must be numeric" ) ;; New OOB Load Tests @@ -507,3 +517,383 @@ ;; (assert_trap (invoke "array.get-null") "null array") (assert_trap (invoke "i32.store_array_null") "null array") + +;; ============================================================================ +;; Multibyte accesses on arrays of every numeric element type. +;; +;; The accesses operate on the array's payload bytes, so the element type of +;; the array is independent of the type and the size of the access. Each of the +;; arrays below has a 16-byte payload, so the same accesses and the same bounds +;; apply to all of them. +;; ============================================================================ + +(module + (type $i8_arr (array (mut i8))) + (type $i16_arr (array (mut i16))) + (type $i32_arr (array (mut i32))) + (type $i64_arr (array (mut i64))) + (type $f32_arr (array (mut f32))) + (type $f64_arr (array (mut f64))) + (type $v128_arr (array (mut v128))) + (type $imm_i32_arr (array i32)) + + (global $i8 (ref $i8_arr) (array.new_default $i8_arr (i32.const 16))) + (global $i16 (ref $i16_arr) (array.new_default $i16_arr (i32.const 8))) + (global $i32 (ref $i32_arr) (array.new_default $i32_arr (i32.const 4))) + (global $i64 (ref $i64_arr) (array.new_default $i64_arr (i32.const 2))) + (global $f32 (ref $f32_arr) (array.new_default $f32_arr (i32.const 4))) + (global $f64 (ref $f64_arr) (array.new_default $f64_arr (i32.const 2))) + (global $v128 (ref $v128_arr) (array.new_default $v128_arr (i32.const 1))) + (global $empty (ref $i32_arr) (array.new_default $i32_arr (i32.const 0))) + + (func (export "clear") + (v128.store (type $i8_arr) (global.get $i8) (i32.const 0) (v128.const i32x4 0 0 0 0)) + (v128.store (type $i16_arr) (global.get $i16) (i32.const 0) (v128.const i32x4 0 0 0 0)) + (v128.store (type $i32_arr) (global.get $i32) (i32.const 0) (v128.const i32x4 0 0 0 0)) + (v128.store (type $i64_arr) (global.get $i64) (i32.const 0) (v128.const i32x4 0 0 0 0)) + (v128.store (type $f32_arr) (global.get $f32) (i32.const 0) (v128.const i32x4 0 0 0 0)) + (v128.store (type $f64_arr) (global.get $f64) (i32.const 0) (v128.const i32x4 0 0 0 0)) + (v128.store (type $v128_arr) (global.get $v128) (i32.const 0) (v128.const i32x4 0 0 0 0)) + ) + + ;; Store an i32 and read it back, for an array of each element type. + + (func (export "i8_arr_i32") (param $i i32) (param $v i32) (result i32) + (i32.store (type $i8_arr) (global.get $i8) (local.get $i) (local.get $v)) + (i32.load (type $i8_arr) (global.get $i8) (local.get $i)) + ) + (func (export "i16_arr_i32") (param $i i32) (param $v i32) (result i32) + (i32.store (type $i16_arr) (global.get $i16) (local.get $i) (local.get $v)) + (i32.load (type $i16_arr) (global.get $i16) (local.get $i)) + ) + (func (export "i32_arr_i32") (param $i i32) (param $v i32) (result i32) + (i32.store (type $i32_arr) (global.get $i32) (local.get $i) (local.get $v)) + (i32.load (type $i32_arr) (global.get $i32) (local.get $i)) + ) + (func (export "i64_arr_i32") (param $i i32) (param $v i32) (result i32) + (i32.store (type $i64_arr) (global.get $i64) (local.get $i) (local.get $v)) + (i32.load (type $i64_arr) (global.get $i64) (local.get $i)) + ) + (func (export "f32_arr_i32") (param $i i32) (param $v i32) (result i32) + (i32.store (type $f32_arr) (global.get $f32) (local.get $i) (local.get $v)) + (i32.load (type $f32_arr) (global.get $f32) (local.get $i)) + ) + (func (export "f64_arr_i32") (param $i i32) (param $v i32) (result i32) + (i32.store (type $f64_arr) (global.get $f64) (local.get $i) (local.get $v)) + (i32.load (type $f64_arr) (global.get $f64) (local.get $i)) + ) + (func (export "v128_arr_i32") (param $i i32) (param $v i32) (result i32) + (i32.store (type $v128_arr) (global.get $v128) (local.get $i) (local.get $v)) + (i32.load (type $v128_arr) (global.get $v128) (local.get $i)) + ) + + ;; Store an i64 and read it back, for an array of each element type. + + (func (export "i8_arr_i64") (param $i i32) (param $v i64) (result i64) + (i64.store (type $i8_arr) (global.get $i8) (local.get $i) (local.get $v)) + (i64.load (type $i8_arr) (global.get $i8) (local.get $i)) + ) + (func (export "i16_arr_i64") (param $i i32) (param $v i64) (result i64) + (i64.store (type $i16_arr) (global.get $i16) (local.get $i) (local.get $v)) + (i64.load (type $i16_arr) (global.get $i16) (local.get $i)) + ) + (func (export "i32_arr_i64") (param $i i32) (param $v i64) (result i64) + (i64.store (type $i32_arr) (global.get $i32) (local.get $i) (local.get $v)) + (i64.load (type $i32_arr) (global.get $i32) (local.get $i)) + ) + (func (export "i64_arr_i64") (param $i i32) (param $v i64) (result i64) + (i64.store (type $i64_arr) (global.get $i64) (local.get $i) (local.get $v)) + (i64.load (type $i64_arr) (global.get $i64) (local.get $i)) + ) + (func (export "f32_arr_i64") (param $i i32) (param $v i64) (result i64) + (i64.store (type $f32_arr) (global.get $f32) (local.get $i) (local.get $v)) + (i64.load (type $f32_arr) (global.get $f32) (local.get $i)) + ) + (func (export "f64_arr_i64") (param $i i32) (param $v i64) (result i64) + (i64.store (type $f64_arr) (global.get $f64) (local.get $i) (local.get $v)) + (i64.load (type $f64_arr) (global.get $f64) (local.get $i)) + ) + (func (export "v128_arr_i64") (param $i i32) (param $v i64) (result i64) + (i64.store (type $v128_arr) (global.get $v128) (local.get $i) (local.get $v)) + (i64.load (type $v128_arr) (global.get $v128) (local.get $i)) + ) + + ;; Float accesses on arrays of integer elements, and integer accesses on + ;; arrays of float elements. + + (func (export "i32_arr_f32") (param $i i32) (param $v f32) (result f32) + (f32.store (type $i32_arr) (global.get $i32) (local.get $i) (local.get $v)) + (f32.load (type $i32_arr) (global.get $i32) (local.get $i)) + ) + (func (export "i16_arr_f64") (param $i i32) (param $v f64) (result f64) + (f64.store (type $i16_arr) (global.get $i16) (local.get $i) (local.get $v)) + (f64.load (type $i16_arr) (global.get $i16) (local.get $i)) + ) + (func (export "f64_arr_f32") (param $i i32) (param $v f32) (result f32) + (f32.store (type $f64_arr) (global.get $f64) (local.get $i) (local.get $v)) + (f32.load (type $f64_arr) (global.get $f64) (local.get $i)) + ) + (func (export "v128_arr_f64") (param $i i32) (param $v f64) (result f64) + (f64.store (type $v128_arr) (global.get $v128) (local.get $i) (local.get $v)) + (f64.load (type $v128_arr) (global.get $v128) (local.get $i)) + ) + + ;; v128 accesses, including on an array of v128 elements. + + (func (export "i8_arr_v128_lane0") (param $i i32) (param $v i64) (result i64) + (v128.store (type $i8_arr) (global.get $i8) (local.get $i) + (i64x2.splat (local.get $v)) + ) + (i64.load (type $i8_arr) (global.get $i8) (local.get $i)) + ) + (func (export "v128_arr_v128_lane1") (param $i i32) (param $v i64) (result i64) + (v128.store (type $v128_arr) (global.get $v128) (local.get $i) + (i64x2.splat (local.get $v)) + ) + (i64.load (type $v128_arr) (global.get $v128) (i32.add (local.get $i) (i32.const 8))) + ) + + ;; Sign extension when loading from an array of wider elements. + + (func (export "i64_arr_load8_s") (param $i i32) (param $v i32) (result i32) + (i32.store8 (type $i64_arr) (global.get $i64) (local.get $i) (local.get $v)) + (i32.load8_s (type $i64_arr) (global.get $i64) (local.get $i)) + ) + (func (export "i64_arr_load16_s") (param $i i32) (param $v i32) (result i32) + (i32.store16 (type $i64_arr) (global.get $i64) (local.get $i) (local.get $v)) + (i32.load16_s (type $i64_arr) (global.get $i64) (local.get $i)) + ) + (func (export "f64_arr_load32_s") (param $i i32) (param $v i64) (result i64) + (i64.store32 (type $f64_arr) (global.get $f64) (local.get $i) (local.get $v)) + (i64.load32_s (type $f64_arr) (global.get $f64) (local.get $i)) + ) + (func (export "f64_arr_load32_u") (param $i i32) (param $v i64) (result i64) + (i64.store32 (type $f64_arr) (global.get $f64) (local.get $i) (local.get $v)) + (i64.load32_u (type $f64_arr) (global.get $f64) (local.get $i)) + ) + + ;; Multibyte accesses and element accesses see the same payload bytes. + + (func (export "i32_arr_store_then_get") (param $v i32) (result i32) + (i32.store (type $i32_arr) (global.get $i32) (i32.const 8) (local.get $v)) + (array.get $i32_arr (global.get $i32) (i32.const 2)) + ) + (func (export "i32_arr_set_then_load") (param $v i32) (result i32) + (array.set $i32_arr (global.get $i32) (i32.const 2) (local.get $v)) + (i32.load (type $i32_arr) (global.get $i32) (i32.const 8)) + ) + (func (export "i16_arr_store_then_get_u") (param $v i32) (result i32) + (i32.store (type $i16_arr) (global.get $i16) (i32.const 4) (local.get $v)) + (array.get_u $i16_arr (global.get $i16) (i32.const 3)) + ) + (func (export "i16_arr_store_then_get_s") (param $v i32) (result i32) + (i32.store (type $i16_arr) (global.get $i16) (i32.const 4) (local.get $v)) + (array.get_s $i16_arr (global.get $i16) (i32.const 3)) + ) + (func (export "f64_arr_store_then_get") (param $v i64) (result f64) + (i64.store (type $f64_arr) (global.get $f64) (i32.const 8) (local.get $v)) + (array.get $f64_arr (global.get $f64) (i32.const 1)) + ) + + ;; Loads from an immutable array are allowed. + + (func (export "immutable_load") (param $i i32) (result i32) + (i32.load (type $imm_i32_arr) + (array.new_fixed $imm_i32_arr 2 (i32.const 0x11223344) (i32.const 0x55667788)) + (local.get $i) + ) + ) + + ;; Any access on an empty array is out of bounds. + + (func (export "empty_load") + (drop (i32.load8_u (type $i32_arr) (global.get $empty) (i32.const 0))) + ) + + ;; Null references trap. + + (func (export "null_load") (result i32) + (i32.load (type $v128_arr) (ref.null $v128_arr) (i32.const 0)) + ) + (func (export "null_store") + (f64.store (type $f64_arr) (ref.null $f64_arr) (i32.const 0) (f64.const 0)) + ) +) + +;; i32 accesses work the same on all element types. +(invoke "clear") +(assert_return (invoke "i8_arr_i32" (i32.const 3) (i32.const 0x12345678)) (i32.const 0x12345678)) +(assert_return (invoke "i16_arr_i32" (i32.const 3) (i32.const 0x12345678)) (i32.const 0x12345678)) +(assert_return (invoke "i32_arr_i32" (i32.const 3) (i32.const 0x12345678)) (i32.const 0x12345678)) +(assert_return (invoke "i64_arr_i32" (i32.const 3) (i32.const 0x12345678)) (i32.const 0x12345678)) +(assert_return (invoke "f32_arr_i32" (i32.const 3) (i32.const 0x12345678)) (i32.const 0x12345678)) +(assert_return (invoke "f64_arr_i32" (i32.const 3) (i32.const 0x12345678)) (i32.const 0x12345678)) +(assert_return (invoke "v128_arr_i32" (i32.const 3) (i32.const 0x12345678)) (i32.const 0x12345678)) + +;; The last in-bounds i32 access of a 16-byte payload is at index 12. +(invoke "clear") +(assert_return (invoke "i8_arr_i32" (i32.const 12) (i32.const -1)) (i32.const -1)) +(assert_return (invoke "i16_arr_i32" (i32.const 12) (i32.const -1)) (i32.const -1)) +(assert_return (invoke "i32_arr_i32" (i32.const 12) (i32.const -1)) (i32.const -1)) +(assert_return (invoke "i64_arr_i32" (i32.const 12) (i32.const -1)) (i32.const -1)) +(assert_return (invoke "f32_arr_i32" (i32.const 12) (i32.const -1)) (i32.const -1)) +(assert_return (invoke "f64_arr_i32" (i32.const 12) (i32.const -1)) (i32.const -1)) +(assert_return (invoke "v128_arr_i32" (i32.const 12) (i32.const -1)) (i32.const -1)) + +(assert_trap (invoke "i8_arr_i32" (i32.const 13) (i32.const 0)) "out of bounds") +(assert_trap (invoke "i16_arr_i32" (i32.const 13) (i32.const 0)) "out of bounds") +(assert_trap (invoke "i32_arr_i32" (i32.const 13) (i32.const 0)) "out of bounds") +(assert_trap (invoke "i64_arr_i32" (i32.const 13) (i32.const 0)) "out of bounds") +(assert_trap (invoke "f32_arr_i32" (i32.const 13) (i32.const 0)) "out of bounds") +(assert_trap (invoke "f64_arr_i32" (i32.const 13) (i32.const 0)) "out of bounds") +(assert_trap (invoke "v128_arr_i32" (i32.const 13) (i32.const 0)) "out of bounds") + +;; The index is unsigned, so a negative index is far out of bounds. +(assert_trap (invoke "i8_arr_i32" (i32.const -1) (i32.const 0)) "out of bounds") +(assert_trap (invoke "i32_arr_i32" (i32.const -4) (i32.const 0)) "out of bounds") +(assert_trap (invoke "v128_arr_i32" (i32.const -16) (i32.const 0)) "out of bounds") + +;; i64 accesses work the same on all element types. +(invoke "clear") +(assert_return (invoke "i8_arr_i64" (i32.const 5) (i64.const 0x1122334455667788)) (i64.const 0x1122334455667788)) +(assert_return (invoke "i16_arr_i64" (i32.const 5) (i64.const 0x1122334455667788)) (i64.const 0x1122334455667788)) +(assert_return (invoke "i32_arr_i64" (i32.const 5) (i64.const 0x1122334455667788)) (i64.const 0x1122334455667788)) +(assert_return (invoke "i64_arr_i64" (i32.const 5) (i64.const 0x1122334455667788)) (i64.const 0x1122334455667788)) +(assert_return (invoke "f32_arr_i64" (i32.const 5) (i64.const 0x1122334455667788)) (i64.const 0x1122334455667788)) +(assert_return (invoke "f64_arr_i64" (i32.const 5) (i64.const 0x1122334455667788)) (i64.const 0x1122334455667788)) +(assert_return (invoke "v128_arr_i64" (i32.const 5) (i64.const 0x1122334455667788)) (i64.const 0x1122334455667788)) + +;; The last in-bounds i64 access of a 16-byte payload is at index 8. +(invoke "clear") +(assert_return (invoke "i8_arr_i64" (i32.const 8) (i64.const -1)) (i64.const -1)) +(assert_return (invoke "v128_arr_i64" (i32.const 8) (i64.const -1)) (i64.const -1)) +(assert_trap (invoke "i8_arr_i64" (i32.const 9) (i64.const 0)) "out of bounds") +(assert_trap (invoke "i16_arr_i64" (i32.const 9) (i64.const 0)) "out of bounds") +(assert_trap (invoke "i32_arr_i64" (i32.const 9) (i64.const 0)) "out of bounds") +(assert_trap (invoke "i64_arr_i64" (i32.const 9) (i64.const 0)) "out of bounds") +(assert_trap (invoke "f32_arr_i64" (i32.const 9) (i64.const 0)) "out of bounds") +(assert_trap (invoke "f64_arr_i64" (i32.const 9) (i64.const 0)) "out of bounds") +(assert_trap (invoke "v128_arr_i64" (i32.const 9) (i64.const 0)) "out of bounds") + +;; Floats keep their bits, even when the element type is an integer type, and +;; even for NaNs with a payload. +(invoke "clear") +(assert_return (invoke "i32_arr_f32" (i32.const 2) (f32.const 3.3)) (f32.const 3.3)) +(assert_return (invoke "i32_arr_f32" (i32.const 2) (f32.const -nan:0x654321)) (f32.const -nan:0x654321)) +(assert_return (invoke "i16_arr_f64" (i32.const 3) (f64.const 3.3)) (f64.const 3.3)) +(assert_return (invoke "i16_arr_f64" (i32.const 3) (f64.const nan:0x123456789abcd)) (f64.const nan:0x123456789abcd)) +(assert_return (invoke "f64_arr_f32" (i32.const 6) (f32.const -1)) (f32.const -1)) +(assert_return (invoke "v128_arr_f64" (i32.const 4) (f64.const 1.5)) (f64.const 1.5)) + +;; v128 accesses read and write 16 bytes. +(invoke "clear") +(assert_return (invoke "i8_arr_v128_lane0" (i32.const 0) (i64.const 0x0102030405060708)) (i64.const 0x0102030405060708)) +(assert_return (invoke "v128_arr_v128_lane1" (i32.const 0) (i64.const 0x0102030405060708)) (i64.const 0x0102030405060708)) +(assert_trap (invoke "i8_arr_v128_lane0" (i32.const 1) (i64.const 0)) "out of bounds") + +;; Sign and zero extension. +(invoke "clear") +(assert_return (invoke "i64_arr_load8_s" (i32.const 1) (i32.const 0xff)) (i32.const -1)) +(assert_return (invoke "i64_arr_load8_s" (i32.const 1) (i32.const 0x7f)) (i32.const 127)) +(assert_return (invoke "i64_arr_load16_s" (i32.const 2) (i32.const 0xffff)) (i32.const -1)) +(assert_return (invoke "i64_arr_load16_s" (i32.const 2) (i32.const 0x8000)) (i32.const -32768)) +(assert_return (invoke "f64_arr_load32_s" (i32.const 4) (i64.const 0xffffffff)) (i64.const -1)) +(assert_return (invoke "f64_arr_load32_u" (i32.const 4) (i64.const 0xffffffff)) (i64.const 4294967295)) + +;; Multibyte accesses and element accesses agree. +(invoke "clear") +(assert_return (invoke "i32_arr_store_then_get" (i32.const 1337)) (i32.const 1337)) +(assert_return (invoke "i32_arr_set_then_load" (i32.const 1337)) (i32.const 1337)) +(assert_return (invoke "i16_arr_store_then_get_u" (i32.const 0xffff1234)) (i32.const 0xffff)) +(assert_return (invoke "i16_arr_store_then_get_s" (i32.const 0xffff1234)) (i32.const -1)) +(assert_return (invoke "f64_arr_store_then_get" (i64.const 4611686018427387904)) (f64.const 2)) + +;; Loads from immutable arrays. +(assert_return (invoke "immutable_load" (i32.const 0)) (i32.const 0x11223344)) +(assert_return (invoke "immutable_load" (i32.const 4)) (i32.const 0x55667788)) +(assert_return (invoke "immutable_load" (i32.const 2)) (i32.const 0x77881122)) +(assert_trap (invoke "immutable_load" (i32.const 5)) "out of bounds") + +;; Empty arrays and null references. +(assert_trap (invoke "empty_load") "out of bounds") +(assert_trap (invoke "null_load") "null array") +(assert_trap (invoke "null_store") "null array") + +;; ============================================================================ +;; Static offset and alignment immediates. +;; ============================================================================ + +(module + (type $i8_arr (array (mut i8))) + (type $i32_arr (array (mut i32))) + + (global $i8 (ref $i8_arr) (array.new_default $i8_arr (i32.const 16))) + (global $i32 (ref $i32_arr) (array.new_default $i32_arr (i32.const 4))) + + ;; The effective address is the sum of the dynamic index and the static + ;; offset, so these two functions access the same bytes. + (func (export "store_offset") (param $i i32) (param $v i32) + (i32.store (type $i8_arr) offset=4 (global.get $i8) (local.get $i) (local.get $v)) + ) + (func (export "load_index") (param $i i32) (result i32) + (i32.load (type $i8_arr) (global.get $i8) (local.get $i)) + ) + (func (export "load_offset") (param $i i32) (result i32) + (i32.load (type $i8_arr) offset=4 (global.get $i8) (local.get $i)) + ) + + ;; The offset alone can address the array. + (func (export "load_offset_only") (result i32) + (i32.load (type $i8_arr) offset=12 (global.get $i8) (i32.const 0)) + ) + + ;; The offset is included in the bounds check, and the sum of the index and + ;; the offset does not wrap around. + (func (export "load_offset_oob") (result i32) + (i32.load (type $i8_arr) offset=13 (global.get $i8) (i32.const 0)) + ) + (func (export "load_offset_no_wrap") (result i32) + (i32.load8_u (type $i8_arr) offset=1 (global.get $i8) (i32.const -1)) + ) + (func (export "load_offset_max") (result i32) + (i32.load8_u (type $i8_arr) offset=4294967295 (global.get $i8) (i32.const 1)) + ) + + ;; Alignment is only a hint: it does not affect the result, and it does not + ;; need to match the alignment of the array's elements. + (func (export "unaligned_store") (param $i i32) (param $v i32) + (i32.store (type $i32_arr) align=1 (global.get $i32) (local.get $i) (local.get $v)) + ) + (func (export "aligned_store") (param $i i32) (param $v i32) + (i32.store (type $i32_arr) align=4 (global.get $i32) (local.get $i) (local.get $v)) + ) + (func (export "unaligned_load") (param $i i32) (result i32) + (i32.load (type $i32_arr) align=1 (global.get $i32) (local.get $i)) + ) + (func (export "aligned_load") (param $i i32) (result i32) + (i32.load (type $i32_arr) align=4 (global.get $i32) (local.get $i)) + ) +) + +(invoke "store_offset" (i32.const 2) (i32.const 0x12345678)) +(assert_return (invoke "load_index" (i32.const 6)) (i32.const 0x12345678)) +(assert_return (invoke "load_offset" (i32.const 2)) (i32.const 0x12345678)) +(invoke "store_offset" (i32.const 0) (i32.const 0x11223344)) +(assert_return (invoke "load_index" (i32.const 4)) (i32.const 0x11223344)) +;; The second store overwrote the first two bytes of the first one. +(assert_return (invoke "load_index" (i32.const 6)) (i32.const 0x12341122)) + +(invoke "store_offset" (i32.const 8) (i32.const 0x55667788)) +(assert_return (invoke "load_offset_only") (i32.const 0x55667788)) + +(assert_trap (invoke "load_offset_oob") "out of bounds") +(assert_trap (invoke "load_offset_no_wrap") "out of bounds") +(assert_trap (invoke "load_offset_max") "out of bounds") + +;; An unaligned store of an i32 array crosses two elements. +(invoke "aligned_store" (i32.const 0) (i32.const 0)) +(invoke "aligned_store" (i32.const 4) (i32.const 0)) +(invoke "unaligned_store" (i32.const 2) (i32.const 0x12345678)) +(assert_return (invoke "unaligned_load" (i32.const 2)) (i32.const 0x12345678)) +(assert_return (invoke "aligned_load" (i32.const 0)) (i32.const 0x56780000)) +(assert_return (invoke "aligned_load" (i32.const 4)) (i32.const 0x00001234))