llvm / llvm/llvm-project

[RISC-V] Redundant VL1R reloads from same spill slot after InlineSpiller

Open
#212,267 4 comments 0 reactions 0 assignees View on GitHub
backend:RISC-V missed-optimization
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Problem

When RVV register pressure is high (e.g., e64m2 operations consuming all 32 vector registers), the InlineSpiller spills an e32m1 index vector and creates independent reload vregs for each use site. Since no CSE pass runs between register allocation and VirtRegRewriter, each reload becomes a separate vl1r instruction with its own address computation — loading the same value from the same stack location into independent vregs.

This was observed on SPEC CPU 2026 820.cloverleaf_s compiled with Flang 23 under the SPEC rvv_generic configuration:

/opt/clang-riscv64-unknown-linux-gnu/bin/flang -std=f2018 -O3 -g -fno-lto \
-march=rv64gcv -mabi=lp64d -mtune=generic -fopenmp \
-DSPEC_OPENMP -DSPEC_NO_TIMER

## Reproducer

This issue was observed on SPEC CPU 2026 820.cloverleaf_s. Due to SPEC licensing, the reduced IR file cannot be publicly attached, but it is available upon request to LLVM maintainers.

To reproduce:

1. Compile the SPEC benchmark with the rvv_generic configuration as shown above.
2. Extract the CloverLeaf build directory and find advec_cell_kernel.fppized.f90.
3. Generate LLVM MIR before VirtRegRewriter:
`
flang -S -emit-llvm -o advec_cell_kernel.ll ...
llc -O3 -stop-after=greedy -o advec_cell_kernel.mir advec_cell_kernel.ll
`
4. Search for multiple VL1RE stems from the same %stack. slot in the MIR output.

## Observation

The x-dir limiter loop’s vector body (advec_cell_kernel.fppized.f90:123) contains two consecutive blocks that both perform a "vl1r.v v14, (s0)" operation:

```asm
csrr s0, vlenb
slli s1, s0, 0x1
add s0, s0, s1
add s0, s0, sp
addi s0, s0, 352
vl1r.v v14, (s0) # reload 1
vmerge.vvm v11, v14, v11, v0 # use 1
...

csrr s0, vlenb # redundant addr calc
slli s1, s0, 0x1
add s0, s0, s1
add s0, s0, sp
addi s0, s0, 352
vl1r.v v14, (s0) # reload 2 (redundant)
vadd.vi v10, v14, -2, v0.t # use 2
```

The second reload is redundant for:

1. **Same source address:** Both instructions load from `(s0)`, which is computed with the same address arithmetic (`vlenb → sp + 352`).
2. **Same data flow:** There is no intervening store to this stack location between the two reloads, so the value in memory does not change.
3. **No necessity:** The destination register `v14` is not redefined between the two reloads, which means it's still available and its value from the first reload could be re-used directly.

In the MIR, all three reloads(the last one not shown here) come from the same spill slot (%stack.34 in Flang 23, %stack.35 in LLVM main).

## Possible Impact

Redundant vl1r instructions increase vector load count and introduce extra address computation cycles that are not needed. In practice:

- Each redundant vl1r and its address computation contribute a considerable amount of cycle overhead in perf annotate (sum of csrr/slli/add/add/addi/vl1r.v at around 2.7% in a OoO CPU).
- This overhead scales with the number of redundant reloads (millions of iterations in a typical SPEC run).

## Proposed Root Cause

It's suspected that the InlineSpiller creates a new vreg for each reload of the same spill slot, and with no CSE pass between greedy regalloc and VirtRegRewriter / after VirtRegRewriter , each becomes a separate vl1r instruction.

## Proposed Fix(WIP, uploading to PR)

### Proposed fix (safe): physreg-level CSE after VirtRegRewriter
- eliminates when same physreg is reloaded and not overwritten between
[flang23_to_v1.patch](https://github.com/user-attachments/files/30609449/flang23_to_v1.patch)
Verified:
- On Flang 23, the 1 redundant vl1r is correctly eliminated;
- latest LLVM currently shows no safe-to-eliminate pairs in this test case but the pattern remains possible.

## Discussion

- Currently RISCV-specific — should this be generalized to a target-independent pass in `llvm/lib/CodeGen/`?
- Would a broader approach (e.g., extending `MachineCSE` to run post-regalloc) be preferable?
- Looking for feedback!

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the MIR sequence with flang and llc using the SPEC CloverLeaf setup, then read the post-regalloc flow around VirtRegRewriter and MachineCSE in llvm/lib/CodeGen/. Compare the RISC-V-specific proposal with a target-independent approach. Done means the redundant VL1R reload pattern is addressed without changing correctness, with focused regression coverage.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.