[llvm] 7c366b0 - [DirectX] Implement `llvm.is.fpclass` lowering for the fcNegZero FPClassTest and the `IsNaN`, `IsInf`, `IsFinite`, `IsNormal` DXIL ops (#138048)
via llvm-commits
llvm-commits at lists.llvm.org
Thu May 8 09:13:30 PDT 2025
Author: Deric C.
Date: 2025-05-08T09:13:26-07:00
New Revision: 7c366b041cd0effdcf0b7e1f3a7ad4eb39800349
URL: https://github.com/llvm/llvm-project/commit/7c366b041cd0effdcf0b7e1f3a7ad4eb39800349
DIFF: https://github.com/llvm/llvm-project/commit/7c366b041cd0effdcf0b7e1f3a7ad4eb39800349.diff
LOG: [DirectX] Implement `llvm.is.fpclass` lowering for the fcNegZero FPClassTest and the `IsNaN`, `IsInf`, `IsFinite`, `IsNormal` DXIL ops (#138048)
Fixes #137209
This PR:
- Adds a case to `expandIntrinsic()` in `DXILIntrinsicExpansion.cpp` to
expand the `Intrinsic::is_fpclass` in the case of
`FPClassTest::fcNegZero`
- Defines the `IsNaN`, `IsFinite`, `IsNormal` DXIL ops in `DXIL.td`
- Adds a case to `lowerIntrinsics()` in `DXILOpLowering.cpp` to handle
the lowering of `Intrinsic::is_fpclass` to the DXIL ops `IsNaN`,
`IsInf`, `IsFinite`, `IsNormal` when the FPClassTest is `fcNan`,
`fcInf`, `fcFinite`, and `fcNormal` respectively
- Creates a test `llvm/test/CodeGen/DirectX/is_fpclass.ll` to exercise
the intrinsic expansion and DXIL op lowering of `Intrinsic::is_fpclass`
~~A separate PR will be made to remove the now-redundant `dx_isinf`
intrinsic to address #87777.~~
A proper implementation for the lowering of the `llvm.is.fpclass`
intrinsic to handle all possible combinations of FPClassTest can be
implemented in a separate PR. This PR's implementation focuses primarily
on addressing the current use-cases for DirectML and HLSL intrinsics.
Added:
llvm/test/CodeGen/DirectX/is_fpclass.ll
Modified:
llvm/lib/Target/DirectX/DXIL.td
llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
llvm/lib/Target/DirectX/DXILOpLowering.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Target/DirectX/DXIL.td b/llvm/lib/Target/DirectX/DXIL.td
index 645105ade72b6..b3b3bcdba491a 100644
--- a/llvm/lib/Target/DirectX/DXIL.td
+++ b/llvm/lib/Target/DirectX/DXIL.td
@@ -422,6 +422,15 @@ def Saturate : DXILOp<7, unary> {
let attributes = [Attributes<DXIL1_0, [ReadNone]>];
}
+def IsNaN : DXILOp<8, isSpecialFloat> {
+ let Doc = "Determines if the specified value is NaN.";
+ let arguments = [OverloadTy];
+ let result = Int1Ty;
+ let overloads = [Overloads<DXIL1_0, [HalfTy, FloatTy]>];
+ let stages = [Stages<DXIL1_0, [all_stages]>];
+ let attributes = [Attributes<DXIL1_0, [ReadNone]>];
+}
+
def IsInf : DXILOp<9, isSpecialFloat> {
let Doc = "Determines if the specified value is infinite.";
let intrinsics = [IntrinSelect<int_dx_isinf>];
@@ -432,6 +441,24 @@ def IsInf : DXILOp<9, isSpecialFloat> {
let attributes = [Attributes<DXIL1_0, [ReadNone]>];
}
+def IsFinite : DXILOp<10, isSpecialFloat> {
+ let Doc = "Determines if the specified value is finite.";
+ let arguments = [OverloadTy];
+ let result = Int1Ty;
+ let overloads = [Overloads<DXIL1_0, [HalfTy, FloatTy]>];
+ let stages = [Stages<DXIL1_0, [all_stages]>];
+ let attributes = [Attributes<DXIL1_0, [ReadNone]>];
+}
+
+def IsNormal : DXILOp<11, isSpecialFloat> {
+ let Doc = "Determines if the specified value is normal.";
+ let arguments = [OverloadTy];
+ let result = Int1Ty;
+ let overloads = [Overloads<DXIL1_0, [HalfTy, FloatTy]>];
+ let stages = [Stages<DXIL1_0, [all_stages]>];
+ let attributes = [Attributes<DXIL1_0, [ReadNone]>];
+}
+
def Cos : DXILOp<12, unary> {
let Doc = "Returns cosine(theta) for theta in radians.";
let intrinsics = [IntrinSelect<int_cos>];
diff --git a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
index b9ac09d8ed166..db310f5a2bacb 100644
--- a/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
+++ b/llvm/lib/Target/DirectX/DXILIntrinsicExpansion.cpp
@@ -46,6 +46,7 @@ static bool isIntrinsicExpansion(Function &F) {
case Intrinsic::abs:
case Intrinsic::atan2:
case Intrinsic::exp:
+ case Intrinsic::is_fpclass:
case Intrinsic::log:
case Intrinsic::log10:
case Intrinsic::pow:
@@ -271,6 +272,59 @@ static Value *expandExpIntrinsic(CallInst *Orig) {
return Exp2Call;
}
+static Value *expandIsFPClass(CallInst *Orig) {
+ Value *T = Orig->getArgOperand(1);
+ auto *TCI = dyn_cast<ConstantInt>(T);
+
+ // These FPClassTest cases have DXIL opcodes, so they will be handled in
+ // DXIL Op Lowering instead.
+ switch (TCI->getZExtValue()) {
+ case FPClassTest::fcInf:
+ case FPClassTest::fcNan:
+ case FPClassTest::fcNormal:
+ case FPClassTest::fcFinite:
+ return nullptr;
+ }
+
+ IRBuilder<> Builder(Orig);
+
+ Value *F = Orig->getArgOperand(0);
+ Type *FTy = F->getType();
+ unsigned FNumElem = 0; // 0 => F is not a vector
+
+ unsigned BitWidth; // Bit width of F or the ElemTy of F
+ Type *BitCastTy; // An IntNTy of the same bitwidth as F or ElemTy of F
+
+ if (auto *FVecTy = dyn_cast<FixedVectorType>(FTy)) {
+ Type *ElemTy = FVecTy->getElementType();
+ FNumElem = FVecTy->getNumElements();
+ BitWidth = ElemTy->getPrimitiveSizeInBits();
+ BitCastTy = FixedVectorType::get(Builder.getIntNTy(BitWidth), FNumElem);
+ } else {
+ BitWidth = FTy->getPrimitiveSizeInBits();
+ BitCastTy = Builder.getIntNTy(BitWidth);
+ }
+
+ Value *FBitCast = Builder.CreateBitCast(F, BitCastTy);
+ switch (TCI->getZExtValue()) {
+ case FPClassTest::fcNegZero: {
+ Value *NegZero =
+ ConstantInt::get(Builder.getIntNTy(BitWidth), 1 << (BitWidth - 1));
+ Value *RetVal;
+ if (FNumElem) {
+ Value *NegZeroSplat = Builder.CreateVectorSplat(FNumElem, NegZero);
+ RetVal =
+ Builder.CreateICmpEQ(FBitCast, NegZeroSplat, "is.fpclass.negzero");
+ } else
+ RetVal = Builder.CreateICmpEQ(FBitCast, NegZero, "is.fpclass.negzero");
+ return RetVal;
+ }
+ default:
+ report_fatal_error(Twine("Unsupported FPClassTest"),
+ /* gen_crash_diag=*/false);
+ }
+}
+
static Value *expandAnyOrAllIntrinsic(CallInst *Orig,
Intrinsic::ID IntrinsicId) {
Value *X = Orig->getOperand(0);
@@ -557,6 +611,9 @@ static bool expandIntrinsic(Function &F, CallInst *Orig) {
case Intrinsic::exp:
Result = expandExpIntrinsic(Orig);
break;
+ case Intrinsic::is_fpclass:
+ Result = expandIsFPClass(Orig);
+ break;
case Intrinsic::log:
Result = expandLogIntrinsic(Orig);
break;
diff --git a/llvm/lib/Target/DirectX/DXILOpLowering.cpp b/llvm/lib/Target/DirectX/DXILOpLowering.cpp
index 59db18fd0df6a..cfdd5247149c2 100644
--- a/llvm/lib/Target/DirectX/DXILOpLowering.cpp
+++ b/llvm/lib/Target/DirectX/DXILOpLowering.cpp
@@ -739,6 +739,50 @@ class OpLowerer {
});
}
+ [[nodiscard]] bool lowerIsFPClass(Function &F) {
+ IRBuilder<> &IRB = OpBuilder.getIRB();
+ Type *RetTy = IRB.getInt1Ty();
+
+ return replaceFunction(F, [&](CallInst *CI) -> Error {
+ IRB.SetInsertPoint(CI);
+ SmallVector<Value *> Args;
+ Value *Fl = CI->getArgOperand(0);
+ Args.push_back(Fl);
+
+ dxil::OpCode OpCode;
+ Value *T = CI->getArgOperand(1);
+ auto *TCI = dyn_cast<ConstantInt>(T);
+ switch (TCI->getZExtValue()) {
+ case FPClassTest::fcInf:
+ OpCode = dxil::OpCode::IsInf;
+ break;
+ case FPClassTest::fcNan:
+ OpCode = dxil::OpCode::IsNaN;
+ break;
+ case FPClassTest::fcNormal:
+ OpCode = dxil::OpCode::IsNormal;
+ break;
+ case FPClassTest::fcFinite:
+ OpCode = dxil::OpCode::IsFinite;
+ break;
+ default:
+ SmallString<128> Msg =
+ formatv("Unsupported FPClassTest {0} for DXIL Op Lowering",
+ TCI->getZExtValue());
+ return make_error<StringError>(Msg, inconvertibleErrorCode());
+ }
+
+ Expected<CallInst *> OpCall =
+ OpBuilder.tryCreateOp(OpCode, Args, CI->getName(), RetTy);
+ if (Error E = OpCall.takeError())
+ return E;
+
+ CI->replaceAllUsesWith(*OpCall);
+ CI->eraseFromParent();
+ return Error::success();
+ });
+ }
+
bool lowerIntrinsics() {
bool Updated = false;
bool HasErrors = false;
@@ -805,6 +849,9 @@ class OpLowerer {
case Intrinsic::ctpop:
HasErrors |= lowerCtpopToCountBits(F);
break;
+ case Intrinsic::is_fpclass:
+ HasErrors |= lowerIsFPClass(F);
+ break;
}
Updated = true;
}
diff --git a/llvm/test/CodeGen/DirectX/is_fpclass.ll b/llvm/test/CodeGen/DirectX/is_fpclass.ll
new file mode 100644
index 0000000000000..a628096aacd7d
--- /dev/null
+++ b/llvm/test/CodeGen/DirectX/is_fpclass.ll
@@ -0,0 +1,154 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 5
+; RUN: opt -S -dxil-intrinsic-expansion -scalarizer -dxil-op-lower -mtriple=dxil-pc-shadermodel6.3-library %s | FileCheck %s
+
+
+define noundef i1 @isnegzero(float noundef %a) {
+; CHECK-LABEL: define noundef i1 @isnegzero(
+; CHECK-SAME: float noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = bitcast float [[A]] to i32
+; CHECK-NEXT: [[IS_FPCLASS_NEGZERO:%.*]] = icmp eq i32 [[TMP0]], -2147483648
+; CHECK-NEXT: ret i1 [[IS_FPCLASS_NEGZERO]]
+;
+entry:
+ %0 = call i1 @llvm.is.fpclass.f32(float %a, i32 32)
+ ret i1 %0
+}
+
+define noundef <2 x i1> @isnegzerov2(<2 x float> noundef %a) {
+; CHECK-LABEL: define noundef <2 x i1> @isnegzerov2(
+; CHECK-SAME: <2 x float> noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[A_I0:%.*]] = extractelement <2 x float> [[A]], i64 0
+; CHECK-NEXT: [[DOTI0:%.*]] = bitcast float [[A_I0]] to i32
+; CHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x float> [[A]], i64 1
+; CHECK-NEXT: [[DOTI1:%.*]] = bitcast float [[A_I1]] to i32
+; CHECK-NEXT: [[IS_FPCLASS_NEGZERO_I0:%.*]] = icmp eq i32 [[DOTI0]], -2147483648
+; CHECK-NEXT: [[IS_FPCLASS_NEGZERO_I1:%.*]] = icmp eq i32 [[DOTI1]], -2147483648
+; CHECK-NEXT: [[IS_FPCLASS_NEGZERO_UPTO0:%.*]] = insertelement <2 x i1> poison, i1 [[IS_FPCLASS_NEGZERO_I0]], i64 0
+; CHECK-NEXT: [[IS_FPCLASS_NEGZERO:%.*]] = insertelement <2 x i1> [[IS_FPCLASS_NEGZERO_UPTO0]], i1 [[IS_FPCLASS_NEGZERO_I1]], i64 1
+; CHECK-NEXT: ret <2 x i1> [[IS_FPCLASS_NEGZERO]]
+;
+entry:
+ %0 = call <2 x i1> @llvm.is.fpclass.v2f32(<2 x float> %a, i32 32)
+ ret <2 x i1> %0
+}
+
+define noundef i1 @isnan(float noundef %a) {
+; CHECK-LABEL: define noundef i1 @isnan(
+; CHECK-SAME: float noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 8, float [[A]]) #[[ATTR0:[0-9]+]]
+; CHECK-NEXT: ret i1 [[TMP0]]
+;
+entry:
+ %0 = call i1 @llvm.is.fpclass.f32(float %a, i32 3)
+ ret i1 %0
+}
+
+define noundef <2 x i1> @isnanv2(<2 x float> noundef %a) {
+; CHECK-LABEL: define noundef <2 x i1> @isnanv2(
+; CHECK-SAME: <2 x float> noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[A_I0:%.*]] = extractelement <2 x float> [[A]], i64 0
+; CHECK-NEXT: [[DOTI02:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 8, float [[A_I0]]) #[[ATTR0]]
+; CHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x float> [[A]], i64 1
+; CHECK-NEXT: [[DOTI11:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 8, float [[A_I1]]) #[[ATTR0]]
+; CHECK-NEXT: [[DOTUPTO0:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI02]], i64 0
+; CHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO0]], i1 [[DOTI11]], i64 1
+; CHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+entry:
+ %0 = call <2 x i1> @llvm.is.fpclass.v2f32(<2 x float> %a, i32 3)
+ ret <2 x i1> %0
+}
+
+define noundef i1 @isinf(float noundef %a) {
+; CHECK-LABEL: define noundef i1 @isinf(
+; CHECK-SAME: float noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 9, float [[A]]) #[[ATTR0]]
+; CHECK-NEXT: ret i1 [[TMP0]]
+;
+entry:
+ %0 = call i1 @llvm.is.fpclass.f32(float %a, i32 516)
+ ret i1 %0
+}
+
+define noundef <2 x i1> @isinfv2(<2 x float> noundef %a) {
+; CHECK-LABEL: define noundef <2 x i1> @isinfv2(
+; CHECK-SAME: <2 x float> noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[A_I0:%.*]] = extractelement <2 x float> [[A]], i64 0
+; CHECK-NEXT: [[DOTI02:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 9, float [[A_I0]]) #[[ATTR0]]
+; CHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x float> [[A]], i64 1
+; CHECK-NEXT: [[DOTI11:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 9, float [[A_I1]]) #[[ATTR0]]
+; CHECK-NEXT: [[DOTUPTO0:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI02]], i64 0
+; CHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO0]], i1 [[DOTI11]], i64 1
+; CHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+entry:
+ %0 = call <2 x i1> @llvm.is.fpclass.v2f32(<2 x float> %a, i32 516)
+ ret <2 x i1> %0
+}
+
+define noundef i1 @isfinite(float noundef %a) {
+; CHECK-LABEL: define noundef i1 @isfinite(
+; CHECK-SAME: float noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 10, float [[A]]) #[[ATTR0]]
+; CHECK-NEXT: ret i1 [[TMP0]]
+;
+entry:
+ %0 = call i1 @llvm.is.fpclass.f32(float %a, i32 504)
+ ret i1 %0
+}
+
+define noundef <2 x i1> @isfinitev2(<2 x float> noundef %a) {
+; CHECK-LABEL: define noundef <2 x i1> @isfinitev2(
+; CHECK-SAME: <2 x float> noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[A_I0:%.*]] = extractelement <2 x float> [[A]], i64 0
+; CHECK-NEXT: [[DOTI02:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 10, float [[A_I0]]) #[[ATTR0]]
+; CHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x float> [[A]], i64 1
+; CHECK-NEXT: [[DOTI11:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 10, float [[A_I1]]) #[[ATTR0]]
+; CHECK-NEXT: [[DOTUPTO0:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI02]], i64 0
+; CHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO0]], i1 [[DOTI11]], i64 1
+; CHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+entry:
+ %0 = call <2 x i1> @llvm.is.fpclass.v2f32(<2 x float> %a, i32 504)
+ ret <2 x i1> %0
+}
+
+define noundef i1 @isnormal(float noundef %a) {
+; CHECK-LABEL: define noundef i1 @isnormal(
+; CHECK-SAME: float noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[TMP0:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 11, float [[A]]) #[[ATTR0]]
+; CHECK-NEXT: ret i1 [[TMP0]]
+;
+entry:
+ %0 = call i1 @llvm.is.fpclass.f32(float %a, i32 264)
+ ret i1 %0
+}
+
+define noundef <2 x i1> @isnormalv2(<2 x float> noundef %a) {
+; CHECK-LABEL: define noundef <2 x i1> @isnormalv2(
+; CHECK-SAME: <2 x float> noundef [[A:%.*]]) {
+; CHECK-NEXT: [[ENTRY:.*:]]
+; CHECK-NEXT: [[A_I0:%.*]] = extractelement <2 x float> [[A]], i64 0
+; CHECK-NEXT: [[DOTI02:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 11, float [[A_I0]]) #[[ATTR0]]
+; CHECK-NEXT: [[A_I1:%.*]] = extractelement <2 x float> [[A]], i64 1
+; CHECK-NEXT: [[DOTI11:%.*]] = call i1 @dx.op.isSpecialFloat.f32(i32 11, float [[A_I1]]) #[[ATTR0]]
+; CHECK-NEXT: [[DOTUPTO0:%.*]] = insertelement <2 x i1> poison, i1 [[DOTI02]], i64 0
+; CHECK-NEXT: [[TMP0:%.*]] = insertelement <2 x i1> [[DOTUPTO0]], i1 [[DOTI11]], i64 1
+; CHECK-NEXT: ret <2 x i1> [[TMP0]]
+;
+entry:
+ %0 = call <2 x i1> @llvm.is.fpclass.v2f32(<2 x float> %a, i32 264)
+ ret <2 x i1> %0
+}
+
+declare i1 @llvm.is.fpclass.f32(float, i32 immarg)
+declare <2 x i1> @llvm.is.fpclass.v2f32(<2 x float>, i32 immarg)
More information about the llvm-commits
mailing list