| Issue |
202670
|
| Summary |
Extend SROA PHI speculation to stores through PHI-selected alloca slices
|
| Labels |
new issue
|
| Assignees |
|
| Reporter |
ParkHanbum
|
LLVM can miss promotion/forwarding opportunities when a simple store is made
through a PHI-selected pointer to local alloca slices.
This looks like the store-side counterpart of an existing SROA idea. SROA
already has logic to speculate loads through PHI/select pointers so that values
loaded from the incoming pointers can be PHI'ed and then promoted. The missed
case here is a store through a PHI-selected local slot:
```llvm
%is_zero = icmp eq i64 %len, 0
br i1 %is_zero, label %join, label %nonzero
nonzero:
%obj = load ptr, ptr %obj.slot
store i64 1, ptr %slot_a
br label %join
join:
%obj.phi = phi ptr [ %obj, %nonzero ], [ undef, %zero ]
%store.slot = phi ptr [ %slot_b, %nonzero ], [ %slot_a, %zero ]
store i64 %len, ptr %store.slot
%reload_a = load i64, ptr %slot_a
%a_is_zero = icmp eq i64 %reload_a, 0
br i1 %a_is_zero, label %exit, label %check_b
check_b:
%reload_b = load i64, ptr %slot_b
%b_is_zero = icmp eq i64 %reload_b, 0
br i1 %b_is_zero, label %exit, label %call
call:
call void @dealloc(ptr %obj.phi, i64 %reload_b, i64 %reload_a)
```
The control-flow facts determine the stored values:
```text
%len == 0 path:
%store.slot == %slot_a
store %len to %slot_a
so %reload_a == 0 and the call is skipped
%len != 0 path:
store 1 to %slot_a
%store.slot == %slot_b
store %len to %slot_b
so %reload_a == 1 and %reload_b == %len != 0
```
The later reloads and zero checks can therefore be forwarded/simplified to the
original condition:
```llvm
%is_zero = icmp eq i64 %len, 0
br i1 %is_zero, label %exit, label %call
call:
%obj = load ptr, ptr %obj.slot
call void @dealloc(ptr %obj, i64 %len, i64 1)
```
A possible implementation direction is to extend SROA's existing PHI
speculation machinery to handle a narrow store-through-PHI form:
```llvm
join:
%p = phi ptr [ %a, %pred_a ], [ %b, %pred_b ]
store T %v, ptr %p
```
When all incoming pointers are known local non-escaping alloca slices and the
stored value is safely available on the incoming edge, this can be treated like
edge-specific stores:
```llvm
pred_a:
store T %v, ptr %a
br label %join
pred_b:
store T %v, ptr %b
br label %join
```
That should let the existing alloca promotion/SROA pipeline remove the later
reloads, repeated zero checks, and stack traffic.
Observed benchmark-backed evidence:
- Raw hits: 6,688.
- Stored hits: 5,000, capped by the scan output.
- Unique files in stored hits: 43.
- Project spread: all hits are in `uv-rs`.
- Representative hit: `uv-rs/optimized/e01wdsyx674u687gp4lij7kaw.ll:332`.
- Coarse signature: Rust `RawVecInner::current_memory` drop-glue/inline shapes.
This is therefore a real missed optimization, but the current corpus evidence is
clone-heavy rather than broad across projects.
A minimized final comparison keeps the source and target distinct after normal
cleanup. The optimized source still contains the local allocas, store through
the PHI-selected slot, reloads, and two zero checks, while the forwarded target
keeps only the original length check and the nonzero-path call. The final IR
line count changes from 45 to 25 in the minimized representative.
Backend smoke testing also favors the forwarded form:
| Target | Assembly lines | Instructions | Cycles | uOps | Block RThroughput |
| --- | ---: | ---: | ---: | ---: | ---: |
| x86_64 znver4 | 36 -> 23 | 1600 -> 700 | 506 -> 505 | 1600 -> 700 | 3.0 -> 1.5 |
| AArch64 neoverse-v2 | 40 -> 22 | 1800 -> 600 | 404 -> 404 | 2000 -> 600 | 3.3 -> 1.5 |
| ARMv7 cortex-a72 | 73 -> 56 | 2600 -> 1500 | 2718 -> 1624 | 3600 -> 2000 | 12.0 -> 6.7 |
| RISC-V u74 + Zbb | 45 -> 23 | 1800 -> 600 | 11303 -> 10004 | 1900 -> 700 | 9.5 -> 3.5 |
The transformation should stay narrow. Important legality constraints include:
- the PHI-selected pointers are local non-escaping alloca slices;
- the store is simple and type-compatible with the later loads;
- edge-specific insertion does not introduce an observable store on a path where
it was not originally executed;
- critical edges are handled conservatively;
- the stored value is available or safely materializable on the incoming edge;
- no intervening clobber can change the forwarded slots before the reloads;
- all relevant reload uses are covered, including branch conditions and call
arguments;
- nonnull, range, and poison-sensitive facts are preserved on the rewritten
call path.
This is not a request for broad memory CSE. The narrower goal is to unblock
SROA/mem2reg in cases where a store through a PHI-selected local alloca slice is
the only thing preventing later load forwarding and scalar promotion.
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs