llvm / llvm/llvm-project

[amdgpu] SIInsertWaitcnts repeats vmcnt(0) for same VMEM loaded VGPR base

Open
#205,945 5 comments 0 reactions 0 assignees View on GitHub
backend:AMDGPU
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

I came across the following in Triton (i can produce this if this is helpful), but please consider the following minimal repro:

```llvm
target triple = "amdgcn-amd-amdhsa"

define amdgpu_kernel void @waitcnt_pointer_table_loop_store_vgprbase_min(
ptr addrspace(1) %out,
ptr addrspace(1) %ptr_table,
i32 %n,
i32 %my_pe) #0 {
entry:
%tid = call i32 @llvm.amdgcn.workitem.id.x()
%lane = and i32 %tid, 127
%active = icmp ult i32 %lane, %n
%off0 = zext i32 %lane to i64
%lane_plus_1 = add nuw nsw i32 %lane, 1
%off1 = zext i32 %lane_plus_1 to i64
%peer_base = add i32 %my_pe, 1
br label %loop

loop:
%i = phi i32 [ 0, %entry ], [ %i.next, %join1 ]
%peer_raw = add i32 %peer_base, %i
%peer_lane = add i32 %peer_raw, %lane
%peer = and i32 %peer_lane, 7
%peer64 = zext i32 %peer to i64
%table_ptr = getelementptr i64, ptr addrspace(1) %ptr_table, i64 %peer64
%base_i64 = load i64, ptr addrspace(1) %table_ptr, align 8
%base = inttoptr i64 %base_i64 to ptr addrspace(1)
br i1 %active, label %then0, label %join0

then0:
%payload_ptr0 = getelementptr half, ptr addrspace(1) %base, i64 %off0
%payload0 = load half, ptr addrspace(1) %payload_ptr0, align 2
%out_ptr0 = getelementptr half, ptr addrspace(1) %out, i64 %off0
store half %payload0, ptr addrspace(1) %out_ptr0, align 2
br label %join0

join0:
br i1 %active, label %then1, label %join1

then1:
%payload_ptr1 = getelementptr half, ptr addrspace(1) %base, i64 %off1
%payload1 = load half, ptr addrspace(1) %payload_ptr1, align 2
%out_ptr1 = getelementptr half, ptr addrspace(1) %out, i64 %off1
store half %payload1, ptr addrspace(1) %out_ptr1, align 2
br label %join1

join1:
%i.next = add nuw nsw i32 %i, 1
%done = icmp eq i32 %i.next, 7
br i1 %done, label %exit, label %loop

exit:
ret void
}

declare i32 @llvm.amdgcn.workitem.id.x() #1

attributes #0 = { nounwind "amdgpu-flat-work-group-size"="1,256" }
attributes #1 = { nounwind readnone speculatable }
```

`%base` dominates both load address computations but when we insert `waitcnts`, the loads become serialized, roughly:

Before `SIInsertWaitcnts`:

```asm
vgpr4_vgpr5 = GLOBAL_LOAD_DWORDX2 ... ; lowered %base_i64 load

S_AND_SAVEEXEC_B64 ...
S_CBRANCH ...

vgpr8_vgpr9 = V_LSHL_ADD_U64 vgpr4_vgpr5, ...
vgpr7 = GLOBAL_LOAD_USHORT vgpr8_vgpr9
GLOBAL_STORE_SHORT ...

S_AND_SAVEEXEC_B64 ...
S_CBRANCH ...

vgpr4_vgpr5 = V_LSHL_ADD_U64 vgpr4_vgpr5, ...
vgpr4 = GLOBAL_LOAD_USHORT vgpr4_vgpr5
GLOBAL_STORE_SHORT ...
```

But after, we see the following:

```asm
vgpr4_vgpr5 = GLOBAL_LOAD_DWORDX2 ...

S_WAITCNT vmcnt(0)
V_LSHL_ADD_U64 ..., vgpr4_vgpr5
GLOBAL_LOAD_USHORT ...

S_WAITCNT vmcnt(0)
V_LSHL_ADD_U64 ..., vgpr4_vgpr5
GLOBAL_LOAD_USHORT ...
```

which kills performance. Can we instead place one dominating wait before the blocks, satisfying all dominated address calculation uses of the same base pointer?

Commands:

```
llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx942 -O3 repro.ll -o out.s
llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx942 -O3 -stop-before=si-insert-waitcnts repro.ll -o before.mir
llc -mtriple=amdgcn-amd-amdhsa -mcpu=gfx942 -O3 -stop-after=si-insert-waitcnts repro.ll -o after.mir
```

Contributor guide

Open the contributing guide

Research direction

Start by reproducing the issue with the provided llc commands and compare before.mir with after.mir around SIInsertWaitcnts. Trace how the repeated vmcnt(0) waits are inserted for the shared %base address computation. Done means the generated wait placement avoids serializing the two loads while preserving correct waitcnt behavior.

Written by the indexing model from the issue text.

Assessment

Domain
compilers, performance
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.