Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/passes/Print.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Expand All @@ -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 << " ";
Expand Down
37 changes: 23 additions & 14 deletions src/wasm-interpreter.h
Original file line number Diff line number Diff line change
Expand Up @@ -2462,19 +2462,33 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
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)
auto refVal = ref.getSingleValue();
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) {
Expand Down Expand Up @@ -2540,14 +2554,9 @@ class ExpressionRunner : public OverriddenVisitor<SubType, Flow> {
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<float>(val.reinterpreti32());
Expand Down
145 changes: 125 additions & 20 deletions test/lit/array-multibyte.wast
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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))
)
)
63 changes: 63 additions & 0 deletions test/lit/ctor-eval/array-multibyte.wast
Original file line number Diff line number Diff line change
@@ -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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
;; An unaligned i32 store covers parts of two i16 elements.
;; An unaligned i32 store covers parts of three 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))
Loading
Loading