[llvm] [AMDGPU] Re-fold masked hi16 pack into v_or_b32_sdwa (PR #210735)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 20 08:10:33 PDT 2026
michaelselehov wrote:
# [AMDGPU] Re-fold the masked high-half 16-bit pack into `v_or_b32_sdwa`
Follow-up to #206058, which added an isel pattern that selects the high-half
16-bit packing idiom `(hi << 16) | (z & 0xffff)` directly to the fused
`v_lshl_or_b32`. That pattern is a win when `z`'s upper 16 bits are already
known zero, but it left a small codegen-quality/perf hole in the common case
where they are not. This PR restores the lost fold without giving up the
#206058 win.
## The regression
When `z`'s high 16 bits are not known zero, isel first has to clear them:
```
v_and_b32 z, 0xffff, z
v_lshl_or_b32 d, hi, 16, z
```
Before #206058 this idiom stayed as a plain shift + `or`, and `SIPeepholeSDWA`
folded it into a single instruction:
```
v_or_b32_sdwa d, hi<<16, z src1_sel:WORD_0
```
The `WORD_0` source selector reads only the low 16 bits of `z`, so the `0xffff`
mask is implicit and free. `v_lshl_or_b32` has no SDWA form
(`getSDWAOp() == -1`), so once isel emits it the SDWA peephole can no longer
match the idiom: the fold is lost and the explicit `v_and` stays. In an
SDWA-heavy, VALU-bound kernel these extra ops are not hidden — a
`uavReadSpeed<char4>` microbenchmark on gfx942 regressed ~5%.
A coarse isel gate (suppressing the fused pattern) is not the right fix: for
the mask-free case `v_lshl_or_b32` is strictly one op and #206058's win is
real. The fix must recover SDWA only for the masked form.
## The fix
Add an additive peephole to `SIPeepholeSDWA` (`splitLshlOrForSDWA`), run
per-basic-block just before the existing SDWA matching. It recognizes
```
%m = V_AND_B32 0xffff, %z ; single use
%d = V_LSHL_OR_B32 %hi, 16, %m
```
and rewrites it to
```
%s = V_LSHLREV_B32 16, %hi
%d = V_OR_B32_sdwa %s, %z src1_sel:WORD_0
```
erasing the now-dead `V_AND`.
Properties:
- **Fires only on the masked form** (`V_AND 0xffff`, single use), so the
mask-free #206058 case is untouched — no regression there.
- **Never increases instruction count.** It is built atomically (2 ops -> 2 ops
in isolation). The subsequent `MachineCSE` shares the `%hi << 16` shift when
one already exists, and the generic SDWA matcher folds any source select of
`%z` into the new `V_OR_B32_sdwa`, recovering the pre-#206058 codegen. In
practice most sites shrink (the mask, and after CSE the shift, disappear).
- Done atomically rather than "split and hope the matcher re-folds": leaving the
`V_AND` in place lets a competing fold win and strand an extra `V_LSHLREV`,
which would grow the count.
Example (gfx9, from `amdgpu-codegenprepare-idiv.ll`):
```
; before
v_and_b32_e32 v1, 0xffff, v1
v_lshl_or_b32 v1, v4, 16, v1
; after
v_or_b32_sdwa v1, v3, v1 dst_sel:DWORD dst_unused:UNUSED_PAD src0_sel:DWORD src1_sel:WORD_0
```
## Testing
- New MIR test `sdwa-peephole-lshl-or-hi16pack.mir`: positive cases (mask on
either `V_AND` operand, `e32`/`e64`) and negative cases (multi-use `V_AND`,
non-`0xffff` mask, non-16 shift).
- `check-llvm` is clean. The AMDGPU CodeGen tests affected by the change were
regenerated; every delta is neutral or an improvement (fewer/equal VALU ops),
with no instruction-count regression. Since the pass returns early when the
subtarget has no SDWA, gfx11+/gfx12 codegen is unchanged.
https://github.com/llvm/llvm-project/pull/210735
More information about the llvm-commits
mailing list