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
356 changes: 217 additions & 139 deletions cranelift/codegen/src/alias_analysis.rs

Large diffs are not rendered by default.

182 changes: 182 additions & 0 deletions cranelift/filetests/filetests/alias/crossing-merges.clif
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
test optimize precise-output
set opt_level=speed
target aarch64

;; With the following CFG:
;;
;; block0
;; / \
;; block1 block2
;; | \ / |
;; | \ / |
;; | \ / |
;; | X |
;; | / \ |
;; | / \ |
;; | / \ |
;; block3 block4
;; \ /
;; block5
;;
;; Both `block1` and `block2` branch to both `block3` and `block4`, so a single
;; pass over the blocks, in whatever order our worklist happens to pop them, can
;; reach a merge block before it has seen all of that block's predecessors, and
;; can reach a store's observers only after it has already processed the block
;; that could otherwise have removed that store. Note that `block5`
;; post-dominates every other block, so its store is a dead-store-elimination
;; candidate for the stores in all of the blocks above it.
;;
;; The store in `block0` is observed by the load in `block3`, and so it is not
;; dead, despite being fully overwritten by the store in `block5`.
function %observed_after_crossing_merges(i64, i32, i32, i32) -> i32 {
region0 = 0 "R0"

block0(v0: i64, v1: i32, v2: i32, v3: i32):
store notrap aligned region0 v1, v0
brif v1, block1, block2

block1:
brif v2, block3, block4

block2:
brif v3, block3, block4

block3:
;; (Different static offset to avoid being store-to-load forwarded away.)
v4 = load.i32 notrap aligned region0 v0+8
jump block5(v4)

block4:
jump block5(v1)

block5(v5: i32):
store notrap aligned region0 v2, v0
return v5
}

; function %observed_after_crossing_merges(i64, i32, i32, i32) -> i32 fast {
; region0 = 0 "R0"
;
; block0(v0: i64, v1: i32, v2: i32, v3: i32):
; store notrap aligned region0 v1, v0
; brif v1, block1, block2
;
; block1:
; brif.i32 v2, block3, block4
;
; block2:
; brif.i32 v3, block3, block4
;
; block3:
; v4 = load.i32 notrap aligned region0 v0+8
; jump block5(v4)
;
; block4:
; jump block5(v1)
;
; block5(v5: i32):
; store.i32 notrap aligned region0 v2, v0
; return v5
; }

;; Same as above, but without the load in `block3`. Nothing observes the store in
;; `block0` now, so the store in `block5` does make it dead, even though the two
;; stores are separated by these control-flow merges.
function %dead_across_crossing_merges(i64, i32, i32, i32) {
region0 = 0 "R0"

block0(v0: i64, v1: i32, v2: i32, v3: i32):
store notrap aligned region0 v1, v0
brif v1, block1, block2

block1:
brif v2, block3, block4

block2:
brif v3, block3, block4

block3:
jump block5

block4:
jump block5

block5:
store notrap aligned region0 v2, v0
return
}

; function %dead_across_crossing_merges(i64, i32, i32, i32) fast {
; region0 = 0 "R0"
;
; block0(v0: i64, v1: i32, v2: i32, v3: i32):
; brif v1, block1, block2
;
; block1:
; brif.i32 v2, block3, block4
;
; block2:
; brif.i32 v3, block3, block4
;
; block3:
; jump block5
;
; block4:
; jump block5
;
; block5:
; store.i32 notrap aligned region0 v2, v0
; return
; }

;; The store is in `block2` this time, i.e. on only one of the two paths into
;; `block3`, where the load that observes it lives. It is still not dead.
function %store_in_one_predecessor(i64, i32, i32, i32) -> i32 {
region0 = 0 "R0"

block0(v0: i64, v1: i32, v2: i32, v3: i32):
brif v1, block1, block2

block1:
brif v2, block3, block4

block2:
store notrap aligned region0 v1, v0
brif v3, block3, block4

block3:
v4 = load.i32 notrap aligned region0 v0
jump block5(v4)

block4:
jump block5(v1)

block5(v5: i32):
store notrap aligned region0 v2, v0
return v5
}

; function %store_in_one_predecessor(i64, i32, i32, i32) -> i32 fast {
; region0 = 0 "R0"
;
; block0(v0: i64, v1: i32, v2: i32, v3: i32):
; brif v1, block1, block2
;
; block1:
; brif.i32 v2, block3, block4
;
; block2:
; store.i32 notrap aligned region0 v1, v0
; brif.i32 v3, block3, block4
;
; block3:
; v4 = load.i32 notrap aligned region0 v0
; jump block5(v4)
;
; block4:
; jump block5(v1)
;
; block5(v5: i32):
; store.i32 notrap aligned region0 v2, v0
; return v5
; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
test optimize precise-output
set opt_level=speed
target aarch64

;; The two stores below are to the same address, so the first one is
;; dead. However, `v2` and `v3` are still distinct values when
;; `AliasAnalysis::observed_stores` is computed, because that happens up front,
;; before the egraph has GVN'd anything. We can nonetheless eliminate the first
;; store because we never ask whether one store overwrites another while
;; computing the set of observed stores; we only ask that at the point where we
;; actually eliminate a dead store, by which time GVN has run.
function %pre_gvn_address(i64, i64) {
region0 = 0 "R0"

block0(v0: i64, v1: i64):
v2 = iconst.i64 0
v3 = iconst.i64 0
store notrap aligned region0 v1, v2
store notrap aligned region0 v0, v3
return
}

; function %pre_gvn_address(i64, i64) fast {
; region0 = 0 "R0"
;
; block0(v0: i64, v1: i64):
; v2 = iconst.i64 0
; store notrap aligned region0 v0, v2 ; v2 = 0
; return
; }
43 changes: 43 additions & 0 deletions cranelift/filetests/filetests/alias/not-dead-loop-carried.clif
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
test optimize precise-output
set opt_level=speed
target aarch64

;; Regression test for issue #13990.
;;
;; A store inside a loop is read on the next iteration by the load at the top of
;; the loop, across the back edge, so it is not dead.
function %loop_carried(i64, i32) -> i32 {
region0 = 0 "R0"
block0(v0: i64, v1: i32):
jump block1

block1:
v2 = load.i32 notrap region0 v0
v3 = iconst.i32 7
store notrap region0 v3, v0
brif v1, block1, block2

block2:
v4 = iconst.i32 0
store notrap region0 v4, v0
return v2
}

; function %loop_carried(i64, i32) -> i32 fast {
; region0 = 0 "R0"
;
; block0(v0: i64, v1: i32):
; v3 = iconst.i32 7
; jump block1
;
; block1:
; v2 = load.i32 notrap region0 v0
; v5 = iconst.i32 7
; store notrap region0 v5, v0 ; v5 = 7
; brif.i32 v1, block1, block2
;
; block2:
; v4 = iconst.i32 0
; store notrap region0 v4, v0 ; v4 = 0
; return v2
; }
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
test alias-analysis
set opt_level=speed
target aarch64

;; Removing a dead store must leave the last-store state pointing at the
;; overwriter, not at the store we just removed. Without that, the last store
;; for `region0` would still be the store we just removed when we reached the
;; next store, so we could only ever unwind the first link of a chain of dead
;; stores.
function %stale_after_dead_store(i64, i32, i32, i32) {
region0 = 0 "R0"

block0(v0: i64, v1: i32, v2: i32, v3: i32):
store notrap aligned region0 v1, v0
store notrap aligned region0 v2, v0
store notrap aligned region0 v3, v0
store notrap aligned region0 v3, v0+8
return
}

;; Every store to `v0` but the last is dead.
; not: store notrap aligned region0 v1, v0
; not: store notrap aligned region0 v2, v0
; check: store notrap aligned region0 v3, v0
; check: store notrap aligned region0 v3, v0+8
; not: store
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
test optimize precise-output
set opt_level=speed
target aarch64

;; Removing an idempotent store deliberately leaves the last store for its
;; region as whatever it was before that store, since that is what the
;; rewritten code's last store actually is.
function %stale_after_idempotent_store(i64, i32, i32) -> i32 {
region0 = 0 "R0"

block0(v0: i64, v1: i32, v2: i32):
store notrap aligned region0 v1, v0
brif v2, block1, block2

block1:
store notrap aligned region0 v1, v0
v3 = load.i32 notrap aligned region0 v0+8
return v3

block2:
store notrap aligned region0 v2, v0
return v2
}

; function %stale_after_idempotent_store(i64, i32, i32) -> i32 fast {
; region0 = 0 "R0"
;
; block0(v0: i64, v1: i32, v2: i32):
; store notrap aligned region0 v1, v0
; brif v2, block1, block2
;
; block1:
; v3 = load.i32 notrap aligned region0 v0+8
; return v3
;
; block2:
; store.i32 notrap aligned region0 v2, v0
; return v2
; }
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,21 @@ test optimize precise-output
set opt_level=speed
target aarch64

;; TODO: we cannot do both store-to-load forwarding and dead-store elimination
;; for the same store yet. This would require multiple iterations of the
;; analysis: whether a store is observed is computed up front, over the original
;; code, so the load below counts as observing the first store even though
;; store-to-load forwarding subsequently removes that load.
function %f(i64, i64) -> i64 {
region0 = 0 "R0"
block0(v0: i64, v1: i64):
v2 = iconst.i64 1
v3 = iadd v1, v2
store notrap aligned region0 v3, v0

;; `v4` should be rewritten into an alias of `v3` via store-to-load
;; forwarding, and should NOT mark `region0` as observed. Then, when we
;; process the second store below, we should be able to eliminate the first
;; store as dead.
;; `v4` is rewritten into an alias of `v3` via store-to-load forwarding, but
;; it has already marked the first store as observed, so we cannot eliminate
;; that store as dead when we process the second store below.
v4 = load.i64 notrap aligned region0 v0

v5 = iadd v3, v2
Expand All @@ -25,16 +29,16 @@ block0(v0: i64, v1: i64):
; region0 = 0 "R0"
;
; block0(v0: i64, v1: i64):
; v2 = iconst.i64 1
; v3 = iadd v1, v2 ; v2 = 1
; store notrap aligned region0 v3, v0
; v7 = iconst.i64 2
; v12 = iadd v1, v7 ; v7 = 2
; store notrap aligned region0 v12, v0
; v2 = iconst.i64 1
; v3 = iadd v1, v2 ; v2 = 1
; return v3
; }

;; TODO: we cannot do both store-to-load forwarding and dead-store elimination
;; across blocks yet. This would require multiple iterations of the analysis.
;; Same as above, but with the load and stores spread across blocks.
function %f(i64, i64) -> i64 {
region0 = 0 "R0"
block0(v0: i64, v1: i64):
Expand Down
Loading
Loading