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

Reply via email to