| ; NOTE: Assertions have been autogenerated by utils/update_llc_test_checks.py UTC_ARGS: --version 5 | |
| ; RUN: llc -mtriple=amdgpu9.50-amd-amdhsa -O0 < %s | FileCheck %s | |
| ; Regression tests for scalar fshl/fshr with constant shift amounts that | |
| ; caused miscompilation via the scalar S_LSHR_B64 fast path: | |
| ; | |
| ; - fshl(scalar, const, 0): the FSHL->FSHR conversion computed ~0 = -1 as the | |
| ; inverted shift, which (being < 32 in a signed comparison) bypassed the | |
| ; S_AND_B32 masking and caused S_LSHR_B64 to shift by 63 instead of 0. | |
| ; | |
| ; - fshl(scalar, const, 1): same path computes -Z = -1 as the inverted shift; | |
| ; same buggy immediate pattern matched and skipped masking. | |
| ; | |
| ; - fshl(scalar, const, 16): same path computes -16 as the inverted shift; | |
| ; -16 also satisfies Imm < 32 signed and would bypass S_AND_B32 masking. | |
| ; | |
| ; - fshl(scalar, scalar, 8): same lowering applies when both inputs are uniform | |
| ; scalars; -8 would bypass S_AND_B32 masking. | |
| ; | |
| ; Correct behaviour: | |
| ; fshl(X, Y, 0) == X | |
| ; fshl(X, Y, 1) == (X << 1) | (Y >> 31) | |
| ; fshl(X, Y, 16) == (X << 16) | (Y >> 16) | |
| ; fshl(X, Y, 8) == (X << 8) | (Y >> 24) | |
| ; fshl(scalar, const, 0) must return scalar unchanged. | |
| define void @fshl_scalar_const_shift0(ptr addrspace(1) %out, i32 %x) { | |
| ; CHECK-LABEL: fshl_scalar_const_shift0: | |
| ; CHECK: ; %bb.0: | |
| ; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0) | |
| ; CHECK-NEXT: v_mov_b32_e32 v3, v1 | |
| ; CHECK-NEXT: ; kill: def $vgpr0 killed $vgpr0 def $vgpr0_vgpr1 killed $exec | |
| ; CHECK-NEXT: v_mov_b32_e32 v1, v3 | |
| ; CHECK-NEXT: v_readfirstlane_b32 s4, v2 | |
| ; CHECK-NEXT: s_mov_b32 s3, 1 | |
| ; CHECK-NEXT: s_lshr_b32 s2, s4, s3 | |
| ; CHECK-NEXT: s_mov_b32 s0, 0x5040305 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 def $sgpr0_sgpr1 | |
| ; CHECK-NEXT: s_mov_b32 s1, s4 | |
| ; CHECK-NEXT: s_lshr_b64 s[0:1], s[0:1], s3 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 killed $sgpr0_sgpr1 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 def $sgpr0_sgpr1 | |
| ; CHECK-NEXT: s_mov_b32 s1, s2 | |
| ; CHECK-NEXT: s_mov_b32 s2, -1 | |
| ; CHECK-NEXT: s_and_b32 s2, s2, 31 | |
| ; CHECK-NEXT: s_lshr_b64 s[0:1], s[0:1], s2 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 killed $sgpr0_sgpr1 | |
| ; CHECK-NEXT: v_mov_b32_e32 v2, s0 | |
| ; CHECK-NEXT: global_store_dword v[0:1], v2, off | |
| ; CHECK-NEXT: s_waitcnt vmcnt(0) | |
| ; CHECK-NEXT: s_setpc_b64 s[30:31] | |
| %scalar = call i32 @llvm.amdgcn.readfirstlane(i32 %x) | |
| %result = call i32 @llvm.fshl.i32(i32 %scalar, i32 84148997, i32 0) | |
| store i32 %result, ptr addrspace(1) %out, align 4 | |
| ret void | |
| } | |
| ; fshl(scalar, const, 1): inverted shift is 31 (= -1 masked to 31), which is | |
| ; correct. The bug was that -1 matched ShiftAmt32Imm (Imm < 32, signed) and | |
| ; skipped the S_AND_B32 masking, passing -1 raw to S_LSHR_B64. | |
| define void @fshl_scalar_const_shift1(ptr addrspace(1) %out, i32 %x) { | |
| ; CHECK-LABEL: fshl_scalar_const_shift1: | |
| ; CHECK: ; %bb.0: | |
| ; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0) | |
| ; CHECK-NEXT: v_mov_b32_e32 v3, v1 | |
| ; CHECK-NEXT: ; kill: def $vgpr0 killed $vgpr0 def $vgpr0_vgpr1 killed $exec | |
| ; CHECK-NEXT: v_mov_b32_e32 v1, v3 | |
| ; CHECK-NEXT: v_readfirstlane_b32 s2, v2 | |
| ; CHECK-NEXT: s_mov_b32 s0, 0xf2f2f2f2 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 def $sgpr0_sgpr1 | |
| ; CHECK-NEXT: s_mov_b32 s1, s2 | |
| ; CHECK-NEXT: s_mov_b32 s2, -1 | |
| ; CHECK-NEXT: s_and_b32 s2, s2, 31 | |
| ; CHECK-NEXT: s_lshr_b64 s[0:1], s[0:1], s2 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 killed $sgpr0_sgpr1 | |
| ; CHECK-NEXT: v_mov_b32_e32 v2, s0 | |
| ; CHECK-NEXT: global_store_dword v[0:1], v2, off | |
| ; CHECK-NEXT: s_waitcnt vmcnt(0) | |
| ; CHECK-NEXT: s_setpc_b64 s[30:31] | |
| %scalar = call i32 @llvm.amdgcn.readfirstlane(i32 %x) | |
| %result = call i32 @llvm.fshl.i32(i32 %scalar, i32 -218959118, i32 1) | |
| store i32 %result, ptr addrspace(1) %out, align 4 | |
| ret void | |
| } | |
| ; fshl(scalar, const, 16): inverted shift is -16, which also satisfied | |
| ; Imm < 32 signed and bypassed S_AND_B32 masking before the fix. | |
| define void @fshl_scalar_const_shift16(ptr addrspace(1) %out, i32 %x) { | |
| ; CHECK-LABEL: fshl_scalar_const_shift16: | |
| ; CHECK: ; %bb.0: | |
| ; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0) | |
| ; CHECK-NEXT: v_mov_b32_e32 v3, v1 | |
| ; CHECK-NEXT: ; kill: def $vgpr0 killed $vgpr0 def $vgpr0_vgpr1 killed $exec | |
| ; CHECK-NEXT: v_mov_b32_e32 v1, v3 | |
| ; CHECK-NEXT: v_readfirstlane_b32 s2, v2 | |
| ; CHECK-NEXT: s_mov_b32 s0, 0xf2f2f2f2 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 def $sgpr0_sgpr1 | |
| ; CHECK-NEXT: s_mov_b32 s1, s2 | |
| ; CHECK-NEXT: s_mov_b32 s2, -16 | |
| ; CHECK-NEXT: s_and_b32 s2, s2, 31 | |
| ; CHECK-NEXT: s_lshr_b64 s[0:1], s[0:1], s2 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 killed $sgpr0_sgpr1 | |
| ; CHECK-NEXT: v_mov_b32_e32 v2, s0 | |
| ; CHECK-NEXT: global_store_dword v[0:1], v2, off | |
| ; CHECK-NEXT: s_waitcnt vmcnt(0) | |
| ; CHECK-NEXT: s_setpc_b64 s[30:31] | |
| %scalar = call i32 @llvm.amdgcn.readfirstlane(i32 %x) | |
| %result = call i32 @llvm.fshl.i32(i32 %scalar, i32 -218959118, i32 16) | |
| store i32 %result, ptr addrspace(1) %out, align 4 | |
| ret void | |
| } | |
| ; fshl(scalar, scalar, 8): both inputs uniform; inverted shift is -8, which | |
| ; also satisfied Imm < 32 signed and bypassed S_AND_B32 masking before the fix. | |
| define void @fshl_scalar_scalar_shift8(ptr addrspace(1) %out, i32 %x, i32 %y) { | |
| ; CHECK-LABEL: fshl_scalar_scalar_shift8: | |
| ; CHECK: ; %bb.0: | |
| ; CHECK-NEXT: s_waitcnt vmcnt(0) expcnt(0) lgkmcnt(0) | |
| ; CHECK-NEXT: v_mov_b32_e32 v4, v1 | |
| ; CHECK-NEXT: ; kill: def $vgpr0 killed $vgpr0 def $vgpr0_vgpr1 killed $exec | |
| ; CHECK-NEXT: v_mov_b32_e32 v1, v4 | |
| ; CHECK-NEXT: v_readfirstlane_b32 s2, v2 | |
| ; CHECK-NEXT: v_readfirstlane_b32 s0, v3 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 def $sgpr0_sgpr1 | |
| ; CHECK-NEXT: s_mov_b32 s1, s2 | |
| ; CHECK-NEXT: s_mov_b32 s2, -8 | |
| ; CHECK-NEXT: s_and_b32 s2, s2, 31 | |
| ; CHECK-NEXT: s_lshr_b64 s[0:1], s[0:1], s2 | |
| ; CHECK-NEXT: ; kill: def $sgpr0 killed $sgpr0 killed $sgpr0_sgpr1 | |
| ; CHECK-NEXT: v_mov_b32_e32 v2, s0 | |
| ; CHECK-NEXT: global_store_dword v[0:1], v2, off | |
| ; CHECK-NEXT: s_waitcnt vmcnt(0) | |
| ; CHECK-NEXT: s_setpc_b64 s[30:31] | |
| %sx = call i32 @llvm.amdgcn.readfirstlane(i32 %x) | |
| %sy = call i32 @llvm.amdgcn.readfirstlane(i32 %y) | |
| %result = call i32 @llvm.fshl.i32(i32 %sx, i32 %sy, i32 8) | |
| store i32 %result, ptr addrspace(1) %out, align 4 | |
| ret void | |
| } |