-
Notifications
You must be signed in to change notification settings - Fork 13.4k
[DirectX] Implement llvm.is.fpclass
lowering for the fcNegZero FPClassTest and the IsNaN
, IsInf
, IsFinite
, IsNormal
DXIL ops
#138048
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
af4356c
4a4284d
0b19dcf
5a28041
0d8ea82
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm assuming because we can use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, both the dx_isinf intrinsic and the llvm.is.fpclass intrinsic can lower to the IsInf DXIL op. Their lowerings are independent of each other. Once the other dx_ intrinsics are defined, they can be mapped to their respective DXIL ops using |
||
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; | ||
|
@@ -799,6 +843,9 @@ class OpLowerer { | |
case Intrinsic::ctpop: | ||
HasErrors |= lowerCtpopToCountBits(F); | ||
break; | ||
case Intrinsic::is_fpclass: | ||
HasErrors |= lowerIsFPClass(F); | ||
break; | ||
} | ||
Updated = true; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If this switch is fully covered we don't need the default here, we can move it to after the switch like:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TCI->getZExtValue()
is a bitmask and there could be bits set that don't correspond to any test. So I think the default case should stay.