Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/CodeGen/Passes.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstrTypes.h"
#include "llvm/IR/Instruction.h"
Expand Down Expand Up @@ -200,6 +201,8 @@ static bool isIntrinsicExpansion(Function &F) {
case Intrinsic::assume:
case Intrinsic::abs:
case Intrinsic::atan2:
case Intrinsic::fshl:
case Intrinsic::fshr:
case Intrinsic::exp:
case Intrinsic::is_fpclass:
case Intrinsic::log:
Expand Down Expand Up @@ -656,6 +659,32 @@ static Value *expandAtan2Intrinsic(CallInst *Orig) {
return Result;
}

template <bool LeftFunnel>
static Value *expandFunnelShiftIntrinsic(CallInst *Orig) {
Type *Ty = Orig->getType();
Value *A = Orig->getOperand(0);
Value *B = Orig->getOperand(1);
Value *Shift = Orig->getOperand(2);

IRBuilder<> Builder(Orig);

unsigned BitWidth = Ty->getScalarSizeInBits();
Constant *Mask = ConstantInt::get(Ty, BitWidth - 1);
Constant *Size = ConstantInt::get(Ty, BitWidth);

// The shift is not required to be masked as DXIL op will do so automatically
Value *Left =
LeftFunnel ? Builder.CreateShl(A, Shift) : Builder.CreateLShr(B, Shift);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From the docs (https://llvm.org/docs/LangRef.html#llvm-fshl-intrinsic) it looks like Shift should be mod the Size, ensuring its a valid shift amount.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Technically in this case the dxil shl/lshr would compute the modulo of the shift, as (poorly) commented above. However, since we will be required to compute the mask (for the other shift) and it makes the IR more readable, I have updated it use the masked shift.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah that seems safer (and no more expensive) than assuming the dxil op does so.


Value *MaskedShift = Builder.CreateAnd(Shift, Mask);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this is the modulo value you actually want to be shifting by?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be helpful to comment this line with what is happening.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments in the new implementation.

Value *InverseShift = Builder.CreateSub(Size, MaskedShift);
Value *Right = LeftFunnel ? Builder.CreateLShr(B, InverseShift)
: Builder.CreateShl(A, InverseShift);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't there be an edge-case for when the shift operand is 0 or N (where N is the number of bits)?
Consider 0 for example:

If Shift == 0 then InverseShift = N - (Shift mod N) = N, but shl and [l|a]shr in LLVM returns poison if the number of bits to shift by is equal to or greater than the number of bits in its operand (N).
In LLVM 3.7, the result is undefined instead of returning poison when the number of bits to shift by is >= N.

If the result is not undefined or poison in DXIL and I assume that DXIL will mask/modulo the shift amount for shl and [l|a]shr, then InverseShift = N mod N = 0 and then the result becomes (A << 0) | (B >> 0) == A | B or (A >> 0) | (B << 0) == A | B when the result should just be A.

Copy link
Contributor

@spall spall Dec 8, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the case where 'Shift == 0' B needs to be shifted to be all zeroes (shifted by the size), but as @lcohedron pointed out that isn't valid. Is it possible to always detect at compile time when Shift is zero?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, thanks!

We do need to handle the case when it is 0 with a dynamic or static variable, and as pointed out this was missing.

I have updated the implementation to follow the lowering as used in GlobalISel, here. Which accounts for this by creating two shifts of valid amounts.


Value *Result = Builder.CreateOr(Left, Right);
return Result;
}

static Value *expandPowIntrinsic(CallInst *Orig, Intrinsic::ID IntrinsicId) {

Value *X = Orig->getOperand(0);
Expand Down Expand Up @@ -995,6 +1024,12 @@ static bool expandIntrinsic(Function &F, CallInst *Orig) {
case Intrinsic::atan2:
Result = expandAtan2Intrinsic(Orig);
break;
case Intrinsic::fshl:
Result = expandFunnelShiftIntrinsic<true>(Orig);
break;
case Intrinsic::fshr:
Result = expandFunnelShiftIntrinsic<false>(Orig);
break;
case Intrinsic::exp:
Result = expandExpIntrinsic(Orig);
break;
Expand Down
90 changes: 90 additions & 0 deletions llvm/test/CodeGen/DirectX/fshl.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
; RUN: opt -S -dxil-intrinsic-expansion -scalarizer -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
; RUN: opt -S -dxil-intrinsic-expansion -scalarizer -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
;
; Make sure dxil operation function calls for funnel shifts left are generated.

; CHECK-LABEL: define{{.*}}@fshl_i16(
; CHECK-SAME: i16 %[[A:.*]], i16 %[[B:.*]], i16 %[[SHIFT:.*]])
define noundef i16 @fshl_i16(i16 %a, i16 %b, i16 %shift) {
entry:
; CHECK-NEXT: entry:
; CHECK-NEXT: %[[LEFT:.*]] = shl i16 %[[A]], %[[SHIFT]]
; CHECK-NEXT: %[[MASKED_SHIFT:.*]] = and i16 %[[SHIFT]], 15
; CHECK-NEXT: %[[INVERSE_SHIFT:.*]] = sub i16 16, %[[MASKED_SHIFT]]
; CHECK-NEXT: %[[RIGHT:.*]] = lshr i16 %[[B]], %[[INVERSE_SHIFT]]
; CHECK-NEXT: %[[RES:.*]] = or i16 %[[LEFT]], %[[RIGHT]]
; CHECK-NEXT: ret i16 %[[RES]]
%fsh = call i16 @llvm.fshl.i16(i16 %a, i16 %b, i16 %shift)
ret i16 %fsh
}

declare i16 @llvm.fshl.i16(i16, i16, i16)

; CHECK-LABEL: define{{.*}}@fshl_v1i32(
; CHECK-SAME: <1 x i32> %[[A_VEC:.*]], <1 x i32> %[[B_VEC:.*]], <1 x i32> %[[SHIFT_VEC:.*]])
define noundef <1 x i32> @fshl_v1i32(<1 x i32> %a, <1 x i32> %b, <1 x i32> %shift) {
entry:
; CHECK-NEXT: entry:
; CHECK-NEXT: %[[B:.*]] = extractelement <1 x i32> %[[B_VEC]], i64 0
; CHECK-NEXT: %[[A:.*]] = extractelement <1 x i32> %[[A_VEC]], i64 0
; CHECK-NEXT: %[[SHIFT:.*]] = extractelement <1 x i32> %[[SHIFT_VEC]], i64 0
; CHECK-NEXT: %[[LEFT:.*]] = shl i32 %[[A]], %[[SHIFT]]
; CHECK-NEXT: %[[MASKED_SHIFT:.*]] = and i32 %[[SHIFT]], 31
; CHECK-NEXT: %[[INVERSE_SHIFT:.*]] = sub i32 32, %[[MASKED_SHIFT]]
; CHECK-NEXT: %[[RIGHT:.*]] = lshr i32 %[[B]], %[[INVERSE_SHIFT]]
; CHECK-NEXT: %[[RES:.*]] = or i32 %[[LEFT]], %[[RIGHT]]
; CHECK-NEXT: %[[RES_VEC:.*]] = insertelement <1 x i32> poison, i32 %[[RES]], i64 0
; CHECK-NEXT: ret <1 x i32> %[[RES_VEC]]
%fsh = call <1 x i32> @llvm.fshl.v1i32(<1 x i32> %a, <1 x i32> %b, <1 x i32> %shift)
ret <1 x i32> %fsh
}

declare <1 x i32> @llvm.fshl.v1i32(<1 x i32>, <1 x i32>, <1 x i32>)

; CHECK-LABEL: define{{.*}}@fshl_v1i64(
; CHECK-SAME: <3 x i64> %[[A_VEC:.*]], <3 x i64> %[[B_VEC:.*]], <3 x i64> %[[SHIFT_VEC:.*]])
define noundef <3 x i64> @fshl_v1i64(<3 x i64> %a, <3 x i64> %b, <3 x i64> %shift) {
entry:
; CHECK-NEXT: entry:
; CHECK-NEXT: %[[B0:.*]] = extractelement <3 x i64> %[[B_VEC]], i64 0
; CHECK-NEXT: %[[B1:.*]] = extractelement <3 x i64> %[[B_VEC]], i64 1
; CHECK-NEXT: %[[B2:.*]] = extractelement <3 x i64> %[[B_VEC]], i64 2
;
; CHECK-NEXT: %[[A0:.*]] = extractelement <3 x i64> %[[A_VEC]], i64 0
; CHECK-NEXT: %[[SHIFT0:.*]] = extractelement <3 x i64> %[[SHIFT_VEC]], i64 0
; CHECK-NEXT: %[[LEFT0:.*]] = shl i64 %[[A0]], %[[SHIFT0]]
;
; CHECK-NEXT: %[[A1:.*]] = extractelement <3 x i64> %[[A_VEC]], i64 1
; CHECK-NEXT: %[[SHIFT1:.*]] = extractelement <3 x i64> %[[SHIFT_VEC]], i64 1
; CHECK-NEXT: %[[LEFT1:.*]] = shl i64 %[[A1]], %[[SHIFT1]]
;
; CHECK-NEXT: %[[A2:.*]] = extractelement <3 x i64> %[[A_VEC]], i64 2
; CHECK-NEXT: %[[SHIFT2:.*]] = extractelement <3 x i64> %[[SHIFT_VEC]], i64 2
; CHECK-NEXT: %[[LEFT2:.*]] = shl i64 %[[A2]], %[[SHIFT2]]
;
; CHECK-NEXT: %[[MASKED_SHIFT0:.*]] = and i64 %[[SHIFT0]], 63
; CHECK-NEXT: %[[MASKED_SHIFT1:.*]] = and i64 %[[SHIFT1]], 63
; CHECK-NEXT: %[[MASKED_SHIFT2:.*]] = and i64 %[[SHIFT2]], 63
;
; CHECK-NEXT: %[[INVERSE_SHIFT0:.*]] = sub i64 64, %[[MASKED_SHIFT0]]
; CHECK-NEXT: %[[INVERSE_SHIFT1:.*]] = sub i64 64, %[[MASKED_SHIFT1]]
; CHECK-NEXT: %[[INVERSE_SHIFT2:.*]] = sub i64 64, %[[MASKED_SHIFT2]]
;
; CHECK-NEXT: %[[RIGHT0:.*]] = lshr i64 %[[B0]], %[[INVERSE_SHIFT0]]
; CHECK-NEXT: %[[RIGHT1:.*]] = lshr i64 %[[B1]], %[[INVERSE_SHIFT1]]
; CHECK-NEXT: %[[RIGHT2:.*]] = lshr i64 %[[B2]], %[[INVERSE_SHIFT2]]
;
; CHECK-NEXT: %[[RES0:.*]] = or i64 %[[LEFT0]], %[[RIGHT0]]
; CHECK-NEXT: %[[RES1:.*]] = or i64 %[[LEFT1]], %[[RIGHT1]]
; CHECK-NEXT: %[[RES2:.*]] = or i64 %[[LEFT2]], %[[RIGHT2]]
;
; CHECK-NEXT: %[[INSERT0:.*]] = insertelement <3 x i64> poison, i64 %[[RES0]], i64 0
; CHECK-NEXT: %[[INSERT1:.*]] = insertelement <3 x i64> %[[INSERT0]], i64 %[[RES1]], i64 1
; CHECK-NEXT: %[[RES_VEC:.*]] = insertelement <3 x i64> %[[INSERT1]], i64 %[[RES2]], i64 2
;
; CHECK-NEXT: ret <3 x i64> %[[RES_VEC]]
%fsh = call <3 x i64> @llvm.fshl.v1i64(<3 x i64> %a, <3 x i64> %b, <3 x i64> %shift)
ret <3 x i64> %fsh
}

declare <3 x i64> @llvm.fshl.v1i64(<3 x i64>, <3 x i64>, <3 x i64>)
90 changes: 90 additions & 0 deletions llvm/test/CodeGen/DirectX/fshr.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
; RUN: opt -S -dxil-intrinsic-expansion -scalarizer -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
; RUN: opt -S -dxil-intrinsic-expansion -scalarizer -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
;
; Make sure dxil operation function calls for funnel shifts right are generated.

; CHECK-LABEL: define{{.*}}@fshr_i16(
; CHECK-SAME: i16 %[[A:.*]], i16 %[[B:.*]], i16 %[[SHIFT:.*]])
define noundef i16 @fshr_i16(i16 %a, i16 %b, i16 %shift) {
entry:
; CHECK-NEXT: entry:
; CHECK-NEXT: %[[LEFT:.*]] = lshr i16 %[[B]], %[[SHIFT]]
; CHECK-NEXT: %[[MASKED_SHIFT:.*]] = and i16 %[[SHIFT]], 15
; CHECK-NEXT: %[[INVERSE_SHIFT:.*]] = sub i16 16, %[[MASKED_SHIFT]]
; CHECK-NEXT: %[[RIGHT:.*]] = shl i16 %[[A]], %[[INVERSE_SHIFT]]
; CHECK-NEXT: %[[RES:.*]] = or i16 %[[LEFT]], %[[RIGHT]]
; CHECK-NEXT: ret i16 %[[RES]]
%fsh = call i16 @llvm.fshr.i16(i16 %a, i16 %b, i16 %shift)
ret i16 %fsh
}

declare i16 @llvm.fshr.i16(i16, i16, i16)

; CHECK-LABEL: define{{.*}}@fshr_v1i32(
; CHECK-SAME: <1 x i32> %[[A_VEC:.*]], <1 x i32> %[[B_VEC:.*]], <1 x i32> %[[SHIFT_VEC:.*]])
define noundef <1 x i32> @fshr_v1i32(<1 x i32> %a, <1 x i32> %b, <1 x i32> %shift) {
entry:
; CHECK-NEXT: entry:
; CHECK-NEXT: %[[A:.*]] = extractelement <1 x i32> %[[A_VEC]], i64 0
; CHECK-NEXT: %[[B:.*]] = extractelement <1 x i32> %[[B_VEC]], i64 0
; CHECK-NEXT: %[[SHIFT:.*]] = extractelement <1 x i32> %[[SHIFT_VEC]], i64 0
; CHECK-NEXT: %[[LEFT:.*]] = lshr i32 %[[B]], %[[SHIFT]]
; CHECK-NEXT: %[[MASKED_SHIFT:.*]] = and i32 %[[SHIFT]], 31
; CHECK-NEXT: %[[INVERSE_SHIFT:.*]] = sub i32 32, %[[MASKED_SHIFT]]
; CHECK-NEXT: %[[RIGHT:.*]] = shl i32 %[[A]], %[[INVERSE_SHIFT]]
; CHECK-NEXT: %[[RES:.*]] = or i32 %[[LEFT]], %[[RIGHT]]
; CHECK-NEXT: %[[RES_VEC:.*]] = insertelement <1 x i32> poison, i32 %[[RES]], i64 0
; CHECK-NEXT: ret <1 x i32> %[[RES_VEC]]
%fsh = call <1 x i32> @llvm.fshr.v1i32(<1 x i32> %a, <1 x i32> %b, <1 x i32> %shift)
ret <1 x i32> %fsh
}

declare <1 x i32> @llvm.fshr.v1i32(<1 x i32>, <1 x i32>, <1 x i32>)

; CHECK-LABEL: define{{.*}}@fshr_v1i64(
; CHECK-SAME: <3 x i64> %[[A_VEC:.*]], <3 x i64> %[[B_VEC:.*]], <3 x i64> %[[SHIFT_VEC:.*]])
define noundef <3 x i64> @fshr_v1i64(<3 x i64> %a, <3 x i64> %b, <3 x i64> %shift) {
entry:
; CHECK-NEXT: entry:
; CHECK-NEXT: %[[A0:.*]] = extractelement <3 x i64> %[[A_VEC]], i64 0
; CHECK-NEXT: %[[A1:.*]] = extractelement <3 x i64> %[[A_VEC]], i64 1
; CHECK-NEXT: %[[A2:.*]] = extractelement <3 x i64> %[[A_VEC]], i64 2
;
; CHECK-NEXT: %[[B0:.*]] = extractelement <3 x i64> %[[B_VEC]], i64 0
; CHECK-NEXT: %[[SHIFT0:.*]] = extractelement <3 x i64> %[[SHIFT_VEC]], i64 0
; CHECK-NEXT: %[[LEFT0:.*]] = lshr i64 %[[B0]], %[[SHIFT0]]
;
; CHECK-NEXT: %[[B1:.*]] = extractelement <3 x i64> %[[B_VEC]], i64 1
; CHECK-NEXT: %[[SHIFT1:.*]] = extractelement <3 x i64> %[[SHIFT_VEC]], i64 1
; CHECK-NEXT: %[[LEFT1:.*]] = lshr i64 %[[B1]], %[[SHIFT1]]
;
; CHECK-NEXT: %[[B2:.*]] = extractelement <3 x i64> %[[B_VEC]], i64 2
; CHECK-NEXT: %[[SHIFT2:.*]] = extractelement <3 x i64> %[[SHIFT_VEC]], i64 2
; CHECK-NEXT: %[[LEFT2:.*]] = lshr i64 %[[B2]], %[[SHIFT2]]
;
; CHECK-NEXT: %[[MASKED_SHIFT0:.*]] = and i64 %[[SHIFT0]], 63
; CHECK-NEXT: %[[MASKED_SHIFT1:.*]] = and i64 %[[SHIFT1]], 63
; CHECK-NEXT: %[[MASKED_SHIFT2:.*]] = and i64 %[[SHIFT2]], 63
;
; CHECK-NEXT: %[[INVERSE_SHIFT0:.*]] = sub i64 64, %[[MASKED_SHIFT0]]
; CHECK-NEXT: %[[INVERSE_SHIFT1:.*]] = sub i64 64, %[[MASKED_SHIFT1]]
; CHECK-NEXT: %[[INVERSE_SHIFT2:.*]] = sub i64 64, %[[MASKED_SHIFT2]]
;
; CHECK-NEXT: %[[RIGHT0:.*]] = shl i64 %[[A0]], %[[INVERSE_SHIFT0]]
; CHECK-NEXT: %[[RIGHT1:.*]] = shl i64 %[[A1]], %[[INVERSE_SHIFT1]]
; CHECK-NEXT: %[[RIGHT2:.*]] = shl i64 %[[A2]], %[[INVERSE_SHIFT2]]
;
; CHECK-NEXT: %[[RES0:.*]] = or i64 %[[LEFT0]], %[[RIGHT0]]
; CHECK-NEXT: %[[RES1:.*]] = or i64 %[[LEFT1]], %[[RIGHT1]]
; CHECK-NEXT: %[[RES2:.*]] = or i64 %[[LEFT2]], %[[RIGHT2]]
;
; CHECK-NEXT: %[[INSERT0:.*]] = insertelement <3 x i64> poison, i64 %[[RES0]], i64 0
; CHECK-NEXT: %[[INSERT1:.*]] = insertelement <3 x i64> %[[INSERT0]], i64 %[[RES1]], i64 1
; CHECK-NEXT: %[[RES_VEC:.*]] = insertelement <3 x i64> %[[INSERT1]], i64 %[[RES2]], i64 2
;
; CHECK-NEXT: ret <3 x i64> %[[RES_VEC]]
%fsh = call <3 x i64> @llvm.fshr.v1i64(<3 x i64> %a, <3 x i64> %b, <3 x i64> %shift)
ret <3 x i64> %fsh
}

declare <3 x i64> @llvm.fshr.v1i64(<3 x i64>, <3 x i64>, <3 x i64>)
Loading