Skip to content
Merged
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: 4 additions & 4 deletions src/passes/HeapStoreOptimization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -181,7 +181,7 @@ struct HeapStoreOptimization
// effects.
auto firstEffects = effects(list[i]);
auto secondEffects = effects(list[j]);
if (secondEffects.invalidates(firstEffects)) {
if (firstEffects.orderedBefore(secondEffects)) {
return false;
}

Expand Down Expand Up @@ -241,7 +241,7 @@ struct HeapStoreOptimization
if (!new_->isWithDefault()) {
for (Index i = index + 1; i < operands.size(); i++) {
auto operandEffects = effects(operands[i]);
if (operandEffects.invalidates(setValueEffects)) {
if (operandEffects.orderedBefore(setValueEffects)) {
// TODO: we could use locals to reorder everything
return false;
}
Expand All @@ -252,7 +252,7 @@ struct HeapStoreOptimization
// if it exists.
if (new_->desc) {
auto descEffects = effects(new_->desc);
if (descEffects.invalidates(setValueEffects)) {
if (descEffects.orderedBefore(setValueEffects)) {
// TODO: we could use locals to reorder everything
return false;
}
Expand All @@ -264,7 +264,7 @@ struct HeapStoreOptimization
// the optimization X' would happen first.
ShallowEffectAnalyzer structNewEffects(
getPassOptions(), *getModule(), new_);
if (structNewEffects.invalidates(setValueEffects)) {
if (structNewEffects.orderedBefore(setValueEffects)) {
return false;
}

Expand Down
311 changes: 311 additions & 0 deletions test/lit/passes/heap-store-atomics.wast
Original file line number Diff line number Diff line change
@@ -0,0 +1,311 @@
;; NOTE: Assertions have been generated by update_lit_checks.py and should not be edited.
;; RUN: wasm-opt %s --heap-store-optimization -all -S -o - | filecheck %s

(module
;; CHECK: (type $struct (shared (struct (field (mut i32)) (field (mut i32)))))
(type $struct (shared (struct (field (mut i32)) (field (mut i32)))))

(rec
;; CHECK: (rec
;; CHECK-NEXT: (type $described (shared (descriptor $desc) (struct (field (mut i32)))))
(type $described (shared (descriptor $desc) (struct (field (mut i32)))))
;; CHECK: (type $desc (shared (describes $described) (struct)))
(type $desc (shared (describes $described) (struct)))
)

;; CHECK: (memory $mem 1 1 shared)
(memory $mem 1 1 shared)

;; Test 1: Disallowed reordering (GC read NOT moved before Wasm acquire load)
;; CHECK: (func $disallowed (type $3) (param $x (ref $struct)) (param $other (ref $struct))
;; CHECK-NEXT: (local $ref (ref $struct))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.atomic.load acqrel
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (struct.get $struct 1
;; CHECK-NEXT: (local.get $other)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $disallowed (param $x (ref $struct)) (param $other (ref $struct))
(local $ref (ref $struct))
(local.set $ref
(struct.new $struct
(i32.const 0)
;; Acquire load (returns i32 via block)
(block (result i32)
(drop (i32.atomic.load acqrel (i32.const 0)))
(i32.const 0)
)
)
)
;; struct.set with GC read value.
(struct.set $struct 0
(local.get $ref)
(struct.get $struct 1 (local.get $other))
)
)

;; Test 2: Allowed reordering (GC read moved before Wasm release store)
;; CHECK: (func $allowed (type $3) (param $x (ref $struct)) (param $other (ref $struct))
;; CHECK-NEXT: (local $ref (ref $struct))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (struct.get $struct 1
;; CHECK-NEXT: (local.get $other)
;; CHECK-NEXT: )
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (i32.atomic.store acqrel
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $allowed (param $x (ref $struct)) (param $other (ref $struct))
(local $ref (ref $struct))
(local.set $ref
(struct.new $struct
(i32.const 0)
;; Release store (returns i32 via block)
(block (result i32)
(i32.atomic.store acqrel (i32.const 0) (i32.const 42))
(i32.const 0)
)
)
)
;; struct.set with GC read value
(struct.set $struct 0
(local.get $ref)
(struct.get $struct 1 (local.get $other))
)
)
;; Test 3: Swap allowed (GC read in struct.new swapped with subsequent acquire load)
;; CHECK: (func $swap_allowed (type $4) (param $other (ref $struct))
;; CHECK-NEXT: (local $ref (ref $struct))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.atomic.load acqrel
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $swap_allowed (param $other (ref $struct))
(local $ref (ref $struct))
(local.set $ref
(struct.new $struct
(struct.get $struct 1 (local.get $other))
(i32.const 0)
)
)
(drop (i32.atomic.load acqrel (i32.const 0)))
(struct.set $struct 0
(local.get $ref)
(i32.const 42)
)
)

;; Test 4: Swap disallowed (struct.new with acquire load NOT swapped with subsequent GC read)
;; CHECK: (func $swap_disallowed (type $4) (param $other (ref $struct))
;; CHECK-NEXT: (local $ref (ref $struct))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (struct.new $struct
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.atomic.load acqrel
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (struct.get $struct 1
;; CHECK-NEXT: (local.get $other)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $struct 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $swap_disallowed (param $other (ref $struct))
(local $ref (ref $struct))
(local.set $ref
(struct.new $struct
(block (result i32)
(drop (i32.atomic.load acqrel (i32.const 0)))
(i32.const 0)
)
(i32.const 0)
)
)
(drop (struct.get $struct 1 (local.get $other)))
(struct.set $struct 0
(local.get $ref)
(i32.const 42)
)
)
;; Test 5: GC read in struct.set value CAN move before release store in descriptor
;; CHECK: (func $desc_allowed (type $5) (param $other (ref $struct)) (param $d (ref (exact $desc)))
;; CHECK-NEXT: (local $ref (ref $described))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (struct.new_desc $described
;; CHECK-NEXT: (struct.get $struct 1
;; CHECK-NEXT: (local.get $other)
;; CHECK-NEXT: )
;; CHECK-NEXT: (block (result (ref (exact $desc)))
;; CHECK-NEXT: (i32.atomic.store acqrel
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $d)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $desc_allowed (param $other (ref $struct)) (param $d (ref (exact $desc)))
(local $ref (ref $described))
(local.set $ref
(struct.new_desc $described
(i32.const 0)
(block (result (ref (exact $desc)))
(i32.atomic.store acqrel (i32.const 0) (i32.const 42))
(local.get $d)
)
)
)
(struct.set $described 0
(local.get $ref)
(struct.get $struct 1 (local.get $other))
)
)

;; Test 6: GC read in struct.set value CANNOT move before acquire load in descriptor
;; CHECK: (func $desc_disallowed (type $5) (param $other (ref $struct)) (param $d (ref (exact $desc)))
;; CHECK-NEXT: (local $ref (ref $described))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (struct.new_desc $described
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (block (result (ref (exact $desc)))
;; CHECK-NEXT: (drop
;; CHECK-NEXT: (i32.atomic.load acqrel
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (local.get $d)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $described 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (struct.get $struct 1
;; CHECK-NEXT: (local.get $other)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $desc_disallowed (param $other (ref $struct)) (param $d (ref (exact $desc)))
(local $ref (ref $described))
(local.set $ref
(struct.new_desc $described
(i32.const 0)
(block (result (ref (exact $desc)))
(drop (i32.atomic.load acqrel (i32.const 0)))
(local.get $d)
)
)
)
(struct.set $described 0
(local.get $ref)
(struct.get $struct 1 (local.get $other))
)
)

;; Test 7: Memory load in struct.set value CAN move before shallow trap (nullable desc) of struct.new
;; CHECK: (func $shallow_allowed (type $6) (param $d (ref (exact $desc)))
;; CHECK-NEXT: (local $ref (ref $described))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (struct.new_desc $described
;; CHECK-NEXT: (i32.load
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: )
;; CHECK-NEXT: (ref.null (shared none))
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (nop)
;; CHECK-NEXT: )
(func $shallow_allowed (param $d (ref (exact $desc)))
(local $ref (ref $described))
(local.set $ref
(struct.new_desc $described
(i32.const 0)
(ref.null $desc)
)
)
(struct.set $described 0
(local.get $ref)
(i32.load (i32.const 0))
)
)

;; Test 8: Memory store in struct.set value CANNOT move before shallow trap (nullable desc) of struct.new
;; CHECK: (func $shallow_disallowed (type $6) (param $d (ref (exact $desc)))
;; CHECK-NEXT: (local $ref (ref $described))
;; CHECK-NEXT: (local.set $ref
;; CHECK-NEXT: (struct.new_desc $described
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (ref.null (shared none))
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: (struct.set $described 0
;; CHECK-NEXT: (local.get $ref)
;; CHECK-NEXT: (block (result i32)
;; CHECK-NEXT: (i32.store
;; CHECK-NEXT: (i32.const 0)
;; CHECK-NEXT: (i32.const 42)
;; CHECK-NEXT: )
;; CHECK-NEXT: (i32.const 1337)
;; CHECK-NEXT: )
;; CHECK-NEXT: )
;; CHECK-NEXT: )
(func $shallow_disallowed (param $d (ref (exact $desc)))
(local $ref (ref $described))
(local.set $ref
(struct.new_desc $described
(i32.const 0)
(ref.null $desc)
)
)
(struct.set $described 0
(local.get $ref)
(block (result i32)
(i32.store (i32.const 0) (i32.const 42))
(i32.const 1337)
)
)
)
)
Loading