Skip to content

Commit 31e1d5e

Browse files
authored
implement isnormal function (microsoft#7720)
Add an isnormal function to HLSL with 16 bit and 32 bit float overloads. Targeting DirectX: The 32 bit overload uses the IsNormal DXIL Op In SM6.8 and earlier the 16 bit overload is emulated using LLVM IR In SM6.9 and later the 16 bit overload uses the IsNormal DXIL Op Adds unit tests showing the correct IR/DXIL Op is generated for each float size and SM. Targeting SPIRV: OpIsNormal requires the kernel capability, so it cannot be used; therefore, the isnormal op is emulated. IsNormal = !(Nan || Inf || Zero || Subnormal) In the bit pattern of a 16 bit or 32 bit float; a normal number corresponds to one whose exponent bits are neither all zeros nor all ones. The emulation code checks that the exponent bits are neither all zeroes nor all ones. Adds unit tests showing isnormal is emulated. Closes microsoft#7648
1 parent 90449f6 commit 31e1d5e

File tree

18 files changed

+568
-22
lines changed

18 files changed

+568
-22
lines changed

docs/SPIR-V.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2477,6 +2477,8 @@ extended instruction mapping, so they are handled with additional steps:
24772477
- ``isfinite`` : Determines if the specified value is finite. Since ``OpIsFinite``
24782478
requires the ``Kernel`` capability, translation is done using ``OpIsNan`` and
24792479
``OpIsInf``. A given value is finite iff it is not NaN and not infinite.
2480+
- ``isnormal`` : Determines if the specified value is normal (not zero, INF, NAN or subnormal).
2481+
Since ``OpIsNormal`` requires the ``Kernel`` capability, the operation is emulated.
24802482
- ``clip``: Discards the current pixel if the specified value is less than zero.
24812483
Uses conditional control flow as well as SPIR-V ``OpKill``.
24822484
- ``rcp``: Calculates a fast, approximate, per-component reciprocal.

include/dxc/HlslIntrinsicOp.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ enum class IntrinsicOp {
164164
IOP_isfinite = 152,
165165
IOP_isinf = 153,
166166
IOP_isnan = 154,
167+
IOP_isnormal = 394,
167168
IOP_ldexp = 155,
168169
IOP_length = 156,
169170
IOP_lerp = 157,
@@ -400,7 +401,7 @@ enum class IntrinsicOp {
400401
IOP_usign = 355,
401402
MOP_InterlockedUMax = 356,
402403
MOP_InterlockedUMin = 357,
403-
Num_Intrinsics = 394,
404+
Num_Intrinsics = 395,
404405
};
405406
inline bool HasUnsignedIntrinsicOpcode(IntrinsicOp opcode) {
406407
switch (opcode) {

lib/HLSL/DxilPreparePasses.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,26 @@ class DxilFinalizeModule : public ModulePass {
694694
CI->eraseFromParent();
695695
}
696696

697+
// Check if exponent bits are neither all 0s nor all 1s
698+
static void emulateIsNormal(Module &M, CallInst *CI) {
699+
IRBuilder<> Builder(CI);
700+
Value *Val = CI->getOperand(1);
701+
DXASSERT(Val->getType()->isHalfTy(),
702+
"Only emulates Half overload of IsNormal");
703+
Type *IType = Type::getInt16Ty(M.getContext());
704+
705+
Constant *ExpBitMask = ConstantInt::get(IType, 0x7c00);
706+
Constant *Zero = ConstantInt::get(IType, 0);
707+
708+
Value *IVal = Builder.CreateBitCast(Val, IType);
709+
Value *Exp = Builder.CreateAnd(IVal, ExpBitMask);
710+
Value *NotAllZeroes = Builder.CreateICmpNE(Exp, Zero);
711+
Value *NotAllOnes = Builder.CreateICmpNE(Exp, ExpBitMask);
712+
Value *B1 = Builder.CreateAnd(NotAllZeroes, NotAllOnes);
713+
CI->replaceAllUsesWith(B1);
714+
CI->eraseFromParent();
715+
}
716+
697717
// Emulate IsSpecialFloat for Half pre sm6.9
698718
static void emulateIsSpecialFloat(Module &M, hlsl::OP *hlslOP) {
699719
// Finds the OpCodeClass that IsInf belongs to, IsSpecialFloat
@@ -719,6 +739,9 @@ class DxilFinalizeModule : public ModulePass {
719739
case (uint64_t)DXIL::OpCode::IsFinite:
720740
emulateIsFinite(M, CI);
721741
continue;
742+
case (uint64_t)DXIL::OpCode::IsNormal:
743+
emulateIsNormal(M, CI);
744+
continue;
722745
default:
723746
continue;
724747
}

lib/HLSL/HLOperationLower.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7450,6 +7450,7 @@ IntrinsicLower gLowerTable[] = {
74507450
TranslateOuterProductAccumulate, DXIL::OpCode::OuterProductAccumulate},
74517451
{IntrinsicOp::IOP___builtin_VectorAccumulate, TranslateVectorAccumulate,
74527452
DXIL::OpCode::VectorAccumulate},
7453+
{IntrinsicOp::IOP_isnormal, TrivialIsSpecialFloat, DXIL::OpCode::IsNormal},
74537454
};
74547455
} // namespace
74557456
static_assert(

tools/clang/include/clang/SPIRV/AstTypeProbe.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ bool isSintOrVecOfSintType(QualType type);
313313
/// integer type.
314314
bool isUintOrVecOfUintType(QualType type);
315315

316+
/// Returns true if the given type is a half or vector of half type.
317+
bool isHalfOrVecOfHalfType(QualType type);
318+
316319
/// Returns true if the given type is a float or vector of float type.
317320
bool isFloatOrVecOfFloatType(QualType type);
318321

tools/clang/lib/SPIRV/AstTypeProbe.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1280,6 +1280,13 @@ bool isUintOrVecOfUintType(QualType type) {
12801280
elemType->isUnsignedIntegerType();
12811281
}
12821282

1283+
/// Returns true if the given type is a half or vector of half type.
1284+
bool isHalfOrVecOfHalfType(QualType type) {
1285+
QualType elemType = {};
1286+
return (isScalarType(type, &elemType) || isVectorType(type, &elemType)) &&
1287+
elemType->isHalfType();
1288+
}
1289+
12831290
/// Returns true if the given type is a float or vector of float type.
12841291
bool isFloatOrVecOfFloatType(QualType type) {
12851292
QualType elemType = {};

tools/clang/lib/SPIRV/SpirvEmitter.cpp

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9658,6 +9658,9 @@ SpirvEmitter::processIntrinsicCallExpr(const CallExpr *callExpr) {
96589658
case hlsl::IntrinsicOp::IOP_isfinite:
96599659
retVal = processIntrinsicIsFinite(callExpr);
96609660
break;
9661+
case hlsl::IntrinsicOp::IOP_isnormal:
9662+
retVal = processIntrinsicIsNormal(callExpr);
9663+
break;
96619664
case hlsl::IntrinsicOp::IOP_EvaluateAttributeCentroid:
96629665
case hlsl::IntrinsicOp::IOP_EvaluateAttributeAtSample:
96639666
case hlsl::IntrinsicOp::IOP_EvaluateAttributeSnapped: {
@@ -12334,6 +12337,73 @@ SpirvEmitter::processIntrinsicIsFinite(const CallExpr *callExpr) {
1233412337
return actOnEachVec(/* index= */ 0, arg->getType(), returnType, argId);
1233512338
}
1233612339

12340+
SpirvInstruction *
12341+
SpirvEmitter::processIntrinsicIsNormal(const CallExpr *callExpr) {
12342+
// Since OpIsNormal needs the Kernel capability, translation is instead done
12343+
// IsNormal = !(zero || NaN || Inf || Subnormal)
12344+
// Check that the exponent is neither all 0s nor all 1s
12345+
const auto loc = callExpr->getExprLoc();
12346+
const auto range = callExpr->getSourceRange();
12347+
const QualType returnType = callExpr->getType();
12348+
const Expr *arg = callExpr->getArg(0);
12349+
auto *argId = doExpr(arg);
12350+
12351+
const auto actOnEachVec = [this, loc, range](
12352+
uint32_t /*index*/, QualType inType,
12353+
QualType outType, SpirvInstruction *curRow) {
12354+
QualType intTy;
12355+
SpirvConstant *expMask;
12356+
SpirvConstant *zero;
12357+
if (isHalfOrVecOfHalfType(inType)) {
12358+
intTy = astContext.UnsignedShortTy;
12359+
expMask = spvBuilder.getConstantInt(intTy, llvm::APInt(16, 0x7c00));
12360+
zero = spvBuilder.getConstantInt(intTy, llvm::APInt(16, 0));
12361+
} else {
12362+
assert(isFloatOrVecOfFloatType(inType));
12363+
intTy = astContext.UnsignedIntTy;
12364+
expMask = spvBuilder.getConstantInt(intTy, llvm::APInt(32, 0x7F800000));
12365+
zero = spvBuilder.getConstantInt(intTy, llvm::APInt(32, 0));
12366+
}
12367+
12368+
QualType boolTy = astContext.BoolTy;
12369+
uint32_t vecSize;
12370+
if (isVectorType(inType, nullptr, &vecSize)) {
12371+
intTy = astContext.getExtVectorType(intTy, vecSize);
12372+
boolTy = astContext.getExtVectorType(boolTy, vecSize);
12373+
expMask = spvBuilder.getConstantComposite(
12374+
intTy, llvm::SmallVector<SpirvConstant *, 4>(vecSize, expMask));
12375+
zero = spvBuilder.getConstantComposite(
12376+
intTy, llvm::SmallVector<SpirvConstant *, 4>(vecSize, zero));
12377+
}
12378+
12379+
auto *bitCast =
12380+
spvBuilder.createUnaryOp(spv::Op::OpBitcast, intTy, curRow, loc);
12381+
bitCast->setLayoutRule(SpirvLayoutRule::Void);
12382+
auto *masked = spvBuilder.createBinaryOp(spv::Op::OpBitwiseAnd, intTy,
12383+
bitCast, expMask, loc, range);
12384+
masked->setLayoutRule(SpirvLayoutRule::Void);
12385+
auto *notZero = spvBuilder.createBinaryOp(spv::Op::OpINotEqual, boolTy,
12386+
masked, zero, loc, range);
12387+
notZero->setLayoutRule(SpirvLayoutRule::Void);
12388+
auto *notOnes = spvBuilder.createBinaryOp(spv::Op::OpINotEqual, boolTy,
12389+
masked, expMask, loc, range);
12390+
notOnes->setLayoutRule(SpirvLayoutRule::Void);
12391+
auto *isNormal = spvBuilder.createBinaryOp(spv::Op::OpLogicalAnd, boolTy,
12392+
notZero, notOnes, loc, range);
12393+
isNormal->setLayoutRule(SpirvLayoutRule::Void);
12394+
return isNormal;
12395+
};
12396+
12397+
// If the instruction does not operate on matrices, we can perform the
12398+
// instruction on each vector of the matrix.
12399+
if (isMxNMatrix(arg->getType())) {
12400+
assert(isMxNMatrix(returnType));
12401+
return processEachVectorInMatrix(arg, returnType, argId, actOnEachVec, loc,
12402+
range);
12403+
}
12404+
return actOnEachVec(/* index= */ 0, arg->getType(), returnType, argId);
12405+
}
12406+
1233712407
SpirvInstruction *
1233812408
SpirvEmitter::processIntrinsicSinCos(const CallExpr *callExpr) {
1233912409
// Since there is no sincos equivalent in SPIR-V, we need to perform Sin

tools/clang/lib/SPIRV/SpirvEmitter.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -639,6 +639,9 @@ class SpirvEmitter : public ASTConsumer {
639639
/// Processes the 'isFinite' intrinsic function.
640640
SpirvInstruction *processIntrinsicIsFinite(const CallExpr *);
641641

642+
/// Processes the 'isNormal' intrinsic function.
643+
SpirvInstruction *processIntrinsicIsNormal(const CallExpr *);
644+
642645
/// Processes the 'rcp' intrinsic function.
643646
SpirvInstruction *processIntrinsicRcp(const CallExpr *);
644647

tools/clang/test/CodeGenDXIL/hlsl/intrinsics/IsSpecialFloat6.8.hlsl

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,38 @@ export bool test_isnan(half h) {
3535
export bool test_isfinite(half h) {
3636
return isfinite(h);
3737
}
38+
39+
// CHECK-LABEL: test_isnormal
40+
// CHECK: [[V1:%.*]] = bitcast half {{.*}} to i16
41+
// CHECK: [[V2:%.*]] = and i16 [[V1]], 31744
42+
// CHECK: [[V3:%.*]] = icmp ne i16 [[V2]], 0
43+
// CHECK: [[V4:%.*]] = icmp ne i16 [[V2]], 31744
44+
// CHECK: [[V5:%.*]] = and i1 [[V3]], [[V4]]
45+
// CHECK: ret i1 [[V5]]
46+
export bool test_isnormal(half h) {
47+
return isnormal(h);
48+
}
49+
50+
// CHECK-LABEL: test_isinf32
51+
// CHECK: call i1 @dx.op.isSpecialFloat.f32(i32 9, float
52+
export bool test_isinf32(float f) {
53+
return isinf(f);
54+
}
55+
56+
// CHECK-LABEL: test_isnan32
57+
// CHECK: call i1 @dx.op.isSpecialFloat.f32(i32 8, float
58+
export bool test_isnan32(float f) {
59+
return isnan(f);
60+
}
61+
62+
// CHECK-LABEL: test_isfinite32
63+
// CHECK: call i1 @dx.op.isSpecialFloat.f32(i32 10, float
64+
export bool test_isfinite32(float f) {
65+
return isfinite(f);
66+
}
67+
68+
// CHECK-LABEL: test_isnormal32
69+
// CHECK: call i1 @dx.op.isSpecialFloat.f32(i32 11, float
70+
export bool test_isnormal32(float f) {
71+
return isnormal(f);
72+
}

0 commit comments

Comments
 (0)