llvm / llvm/llvm-project

[AMDGPU] VOPD pairing serializes independent buffer loads with descriptor reuse

Open
#222,620 1 comment 1 reaction 0 assignees View on GitHub
backend:AMDGPU
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Summary

On gfx1100, post-RA VOPD pairing can move a consumer of one buffer load ahead of a second, independent buffer load when the loads reuse the resource descriptor SGPRs. Wait insertion then has to wait for the first load before issuing the second. In this reduced case, the candidate pair does not ultimately form a VOPD instruction, so the serialization has no dual-issue benefit.

This is a scheduling / missed-optimization report, not a wrong-code report. It appears to be a remaining case of the load-overlap problem addressed by #201930 and #204854.

## Standalone reproducer

Save as `repro.mir`. It has seven machine instructions, including the final use marker and terminator. An AMDGPU-enabled `llc` is sufficient; no GPU, ROCm, or Triton installation is needed to reproduce the scheduling behavior.

```yaml
--- |
define amdgpu_kernel void @load_descriptor_reuse() #0 { ret void }
attributes #0 = { "target-features"="+wavefrontsize32" }
...
---
name: load_descriptor_reuse
tracksRegLiveness: true
body: |
bb.0:
liveins: $vgpr0, $vgpr1, $sgpr0, $sgpr8_sgpr9_sgpr10_sgpr11, $vcc

$vgpr0 = V_CNDMASK_B32_e32 -2147483648, $vgpr0, implicit $vcc, implicit $exec
$vgpr1 = BUFFER_LOAD_UBYTE_OFFEN $vgpr1, $sgpr8_sgpr9_sgpr10_sgpr11, 0, 0, 0, 0, implicit $exec :: (load (s8), addrspace 8)
$sgpr8 = S_MOV_B32 $sgpr0
$vgpr0 = BUFFER_LOAD_USHORT_OFFEN $vgpr0, $sgpr8_sgpr9_sgpr10_sgpr11, 0, 0, 0, 0, implicit $exec :: (load (s16), addrspace 8)
$vgpr3 = V_AND_B32_e32 15, $vgpr1, implicit $exec
S_NOP 0, implicit $vgpr0, implicit $vgpr3
S_ENDPGM 0
...
```

Run:

```sh
llc -mtriple=amdgpu11.00 -verify-machineinstrs \
-run-pass=postmisched repro.mir -o -
```

The relevant output order is shown below, with operands abbreviated:

```text
BUFFER_LOAD_UBYTE_OFFEN # first load, result in vgpr1
V_CNDMASK_B32_e32 # second load's address
S_MOV_B32 # overwrite descriptor sgpr8
V_AND_B32_e32 # consumes vgpr1 before the second load
BUFFER_LOAD_USHORT_OFFEN # second load
```

Adding `-amdgpu-enable-vopd=0` to the same command keeps `BUFFER_LOAD_USHORT_OFFEN` ahead of `V_AND_B32_e32`.

To observe the resulting wait:

```sh
llc -mtriple=amdgpu11.00 -verify-machineinstrs \
-run-pass=postmisched,si-insert-waitcnts repro.mir -o -
```

This inserts `S_WAITCNT .Vmcnt_0` immediately before the AND, hence before the second load. Separately, running `-run-pass=postmisched,gcn-create-vopd` leaves the CNDMASK and AND uncombined in this example.

## Expected behavior and dependency distinction

The first load must be **issued** before `sgpr8` is overwritten. Its result does not need to be **ready** before the overwrite or the second load. Keeping both loads ahead of the AND would preserve their overlap.

There is an anti-dependency from the first load to the descriptor overwrite, followed by a data dependency from that overwrite to the second load. This is an issue-order path, not a load-result dependency. The current `collectLoads(..., StopAtLoads=false)` traversal includes such a path when checking whether the loads already depend on each other. That appears to explain why the existing overlap guard permits this pairing.

## Version and verification boundary

- Reproduced with an assertions-enabled LLVM 24 build at `4f15934599778a52f5227005c47378631e2f40fa`, based on upstream `12b2a6767a17e6450a3e844359afa30440bdd9ba` (2026-09-08).
- That build has two local packed-load register-coalescing commits. Neither runs in the `-run-pass=postmisched` reproducer; the VOPD scheduling implementation is unmodified.
- On 2026-09-10, `GCNVOPDUtils.cpp` at upstream main `11cf968d0d79f5809b4f3fd6b526f0d16ec2a6d1` was byte-identical to this build's source. I have not rebuilt that entire main revision.
- All commands above were rerun on this reduced input with `-verify-machineinstrs` and completed successfully.

Contributor guide

Open the contributing guide

Research direction

Start in GCNVOPDUtils.cpp, focusing on collectLoads(..., StopAtLoads=false) and the VOPD overlap guard. Run the provided repro.mir commands with llc and compare postmisched output with VOPD enabled and disabled. Done means the independent loads remain overlapped and the unnecessary wait before V_AND_B32_e32 is no longer inserted.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
compilers, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.