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))