[llvm] [AMDGPU] Run early-cse<memssa> at the end of the full-LTO pipeline (PR #208461)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jul 9 07:06:00 PDT 2026
michaelselehov wrote:
## Summary
Register `early-cse<memssa>` on the AMDGPU full-LTO (post-link) pipeline through the target's `FullLinkTimeOptimizationLast` extension point, so it runs once at the end of the LTO middle-end, right before code generation.
## Background
The regular (non-LTO) and ThinLTO function-simplification pipelines both run an EarlyCSE-with-MemorySSA pass near the start of the function pass sequence. The full-LTO post-link pipeline (`buildLTODefaultPipeline`) does not.
Because of that, in full-LTO builds a redundant load/store round-trip can survive all the way to code generation: a store immediately followed by a load of the same location whose value is then reused. The GVN/DSE passes that run later in the LTO pipeline do not catch this particular pattern. On AMDGPU the leftover redundancy makes the hazard recognizer insert extra `s_nop` padding in some kernels, which is a measurable throughput loss.
AMDGPU's codegen-prep pipeline already runs a plain `EarlyCSE` (without MemorySSA), but that is not strong enough to remove the memory round-trip; MemorySSA is what enables the load-after-store forwarding here.
## Approach
Rather than adding the pass to the target-independent LTO pipeline (which every target would pay for and which would need much broader justification), this registers it through the existing AMDGPU `registerFullLinkTimeOptimizationLastEPCallback`, gated to `-O1` and above. The change stays target-local and gives the AMDGPU full-LTO pipeline the same early-cse<memssa> cleanup the non-LTO and ThinLTO pipelines already have.
## Effect
Concrete IR example, taken from the updated PhaseOrdering test (`lto<O1>`):
before:
```llvm
store i32 %value, ptr addrspace(5) %val_field
store ptr %generic_input, ptr addrspace(5) %data
%retrieved_ptr = load ptr, ptr addrspace(5) %data
%retrieved_val = load i32, ptr addrspace(5) %val_field
call void @callee(ptr %retrieved_ptr, i32 %retrieved_val)
```
after:
```llvm
store i32 %value, ptr addrspace(5) %val_field
store ptr %generic_input, ptr addrspace(5) %data
call void @callee(ptr %generic_input, i32 %value)
```
The stored values are forwarded to the loads and the loads are removed. At the ISA level this eliminates the extra hazard `s_nop`s the redundancy caused (one hot kernel dropped from 12 to 10 `s_nop` per iteration), recovering a few percent of throughput on affected HIP kernels.
## Compile time
This adds a single function pass (early-cse<memssa>) at the very end of the full-LTO middle-end, only for AMDGPU and only at `-O1`+. MemorySSA is constructed for the pass and then discarded; impact on full-LTO compile time is expected to be negligible.
## Testing
- `llvm/test/CodeGen/AMDGPU/print-pipeline-passes.ll`: `early-cse<memssa>` is present in `lto<O1/O2/O3>` and absent at `lto<O0>`.
- `llvm/test/Transforms/PhaseOrdering/AMDGPU/infer-address-space.ll`: regenerated; now shows the load/store forwarding above.
- `ninja check-llvm`: no regressions.
https://github.com/llvm/llvm-project/pull/208461
More information about the llvm-commits
mailing list