Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
198 changes: 198 additions & 0 deletions lib/SPIRV/SPIRVWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4245,6 +4245,204 @@ SPIRVValue *LLVMToSPIRVBase::transIntrinsicInst(IntrinsicInst *II,
return BM->addInstTemplate(internal::OpMaskedScatterINTEL, Ops, BB,
nullptr);
}
case Intrinsic::is_fpclass: {
// There is no direct counterpart for the intrinsic in SPIR-V, hence
// we need to emulate its work by sequence of other instructions
SPIRVType *ResTy = transType(II->getType());
llvm::FPClassTest FPClass = static_cast<llvm::FPClassTest>(
cast<ConstantInt>(II->getArgOperand(1))->getZExtValue());
// if no tests are provided - return false
if (FPClass == 0)
return BM->addConstant(ResTy, false);
// if all tests are provided - return true
if (FPClass == fcAllFlags)
return BM->addConstant(ResTy, true);

Type *OpLLVMTy = II->getArgOperand(0)->getType();
SPIRVValue *InputFloat = transValue(II->getArgOperand(0), BB);
std::vector<SPIRVValue *> ResultVec;

// Adds test for Negative/Positive values
SPIRVValue *SignBitTest = nullptr;
SPIRVValue *NoSignTest = nullptr;
auto GetNegPosInstTest = [&](SPIRVValue *TestInst,
bool IsNegative) -> SPIRVValue * {
SignBitTest = (SignBitTest)
? SignBitTest
: BM->addInstTemplate(OpSignBitSet,
{InputFloat->getId()}, BB, ResTy);
if (IsNegative) {
return BM->addInstTemplate(
OpLogicalAnd, {SignBitTest->getId(), TestInst->getId()}, BB, ResTy);
}
NoSignTest = (NoSignTest)
? NoSignTest
: BM->addInstTemplate(OpLogicalNot,
{SignBitTest->getId()}, BB, ResTy);
return BM->addInstTemplate(
OpLogicalAnd, {NoSignTest->getId(), TestInst->getId()}, BB, ResTy);
};

// Get LLVM Op type converted to integer. It can be either scalar or vector.
const uint32_t BitSize = OpLLVMTy->getScalarSizeInBits();
Type *IntOpLLVMTy = IntegerType::getIntNTy(M->getContext(), BitSize);
if (OpLLVMTy->isVectorTy())
IntOpLLVMTy = FixedVectorType::get(
IntOpLLVMTy, cast<FixedVectorType>(OpLLVMTy)->getNumElements());
SPIRVType *OpSPIRVTy = transType(IntOpLLVMTy);
const llvm::fltSemantics &Semantics =
OpLLVMTy->getScalarType()->getFltSemantics();
const APInt Inf = APFloat::getInf(Semantics).bitcastToAPInt();
const APInt AllOneMantissa =
APFloat::getLargest(Semantics).bitcastToAPInt() & ~Inf;

// Some checks can be inverted tests for simple cases, for example
// simultaneous check for inf, normal, subnormal and zero is a check for
// non nan.
auto GetInvertedFPClassTest =
[](const llvm::FPClassTest Test) -> llvm::FPClassTest {
llvm::FPClassTest InvertedTest = ~Test & fcAllFlags;
switch (InvertedTest) {
default:
break;
case fcNan:
case fcSNan:
case fcQNan:
case fcInf:
case fcPosInf:
case fcNegInf:
case fcNormal:
case fcPosNormal:
case fcNegNormal:
case fcSubnormal:
case fcPosSubnormal:
case fcNegSubnormal:
case fcZero:
case fcPosZero:
case fcNegZero:
case fcFinite:
case fcPosFinite:
case fcNegFinite:
return InvertedTest;
}
return fcNone;
};
bool IsInverted = false;
if (llvm::FPClassTest InvertedCheck = GetInvertedFPClassTest(FPClass)) {
IsInverted = true;
FPClass = InvertedCheck;
}
auto GetInvertedTestIfNeeded = [&](SPIRVValue *TestInst) -> SPIRVValue * {
if (!IsInverted)
return TestInst;
return BM->addInstTemplate(OpLogicalNot, {TestInst->getId()}, BB, ResTy);
};

// Integer parameter of the intrinsic is combined from several bit masks
// referenced in FPClassTest enum from FloatingPointMode.h in LLVM.
// Since a single intrinsic can provide multiple tests - here we might end
// up adding several sequences of SPIR-V instructions
if (FPClass & fcNan) {
// Map on OpIsNan if we have both QNan and SNan test bits set
if (FPClass & fcSNan && FPClass & fcQNan) {
auto *TestIsNan =
BM->addInstTemplate(OpIsNan, {InputFloat->getId()}, BB, ResTy);
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsNan));
} else {
// isquiet(V) ==> abs(V) >= (unsigned(Inf) | quiet_bit)
APInt QNaNBitMask =
APInt::getOneBitSet(BitSize, AllOneMantissa.getActiveBits() - 1);
APInt InfWithQnanBit = Inf | QNaNBitMask;
auto *QNanBitConst = transValue(
Constant::getIntegerValue(IntOpLLVMTy, InfWithQnanBit), BB);
auto *BitCastToInt =
BM->addUnaryInst(OpBitcast, OpSPIRVTy, InputFloat, BB);
auto *IntAbs =
BM->addExtInst(OpSPIRVTy, BM->getExtInstSetId(SPIRVEIS_OpenCL),
OpenCLLIB::SAbs, {BitCastToInt}, BB);
auto *TestIsQNan = BM->addCmpInst(OpUGreaterThanEqual, ResTy, IntAbs,
QNanBitConst, BB);
if (FPClass & fcQNan) {
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsQNan));
} else {
// issignaling(V) ==> isnan(V) && !isquiet(V)
auto *TestIsNan =
BM->addInstTemplate(OpIsNan, {InputFloat->getId()}, BB, ResTy);
auto *NotQNan = BM->addInstTemplate(OpLogicalNot,
{TestIsQNan->getId()}, BB, ResTy);
auto *TestIsSNan = BM->addInstTemplate(
OpLogicalAnd, {TestIsNan->getId(), NotQNan->getId()}, BB, ResTy);
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsSNan));
}
}
}
if (FPClass & fcInf) {
auto *TestIsInf =
BM->addInstTemplate(OpIsInf, {InputFloat->getId()}, BB, ResTy);
if (FPClass & fcNegInf && FPClass & fcPosInf)
// Map on OpIsInf if we have both Inf test bits set
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsInf));
else
// Map on OpIsInf with following check for sign bit
ResultVec.emplace_back(GetInvertedTestIfNeeded(
GetNegPosInstTest(TestIsInf, FPClass & fcNegInf)));
}
if (FPClass & fcNormal) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a possibility that fcNormal bit is set and both fcPosNormal and fcNegNormal are not set? In such such, what is the expected behavior? Thanks

Copy link
Contributor Author

@MrSidims MrSidims Apr 10, 2023

Choose a reason for hiding this comment

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

fcNormal is (fcPosNormal | fcNegNormal) so it's impossible scenario
same for inf, subnormal etc

auto *TestIsNormal =
BM->addInstTemplate(OpIsNormal, {InputFloat->getId()}, BB, ResTy);
if (FPClass & fcNegNormal && FPClass & fcPosNormal)
// Map on OpIsNormal if we have both Normal test bits set
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsNormal));
else
// Map on OpIsNormal with following check for sign bit
ResultVec.emplace_back(GetInvertedTestIfNeeded(
GetNegPosInstTest(TestIsNormal, FPClass & fcNegNormal)));
}
if (FPClass & fcSubnormal) {
// issubnormal(V) ==> unsigned(abs(V) - 1) < (all mantissa bits set)
auto *BitCastToInt =
BM->addUnaryInst(OpBitcast, OpSPIRVTy, InputFloat, BB);
SPIRVValue *IntAbs =
BM->addExtInst(OpSPIRVTy, BM->getExtInstSetId(SPIRVEIS_OpenCL),
OpenCLLIB::SAbs, {BitCastToInt}, BB);
auto *MantissaConst = transValue(
Constant::getIntegerValue(IntOpLLVMTy, AllOneMantissa), BB);
auto *MinusOne =
BM->addBinaryInst(OpISub, OpSPIRVTy, IntAbs, MantissaConst, BB);
auto *TestIsSubnormal =
BM->addCmpInst(OpULessThan, ResTy, MinusOne, MantissaConst, BB);
if (FPClass & fcPosSubnormal && FPClass & fcNegSubnormal)
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsSubnormal));
else
ResultVec.emplace_back(GetInvertedTestIfNeeded(
GetNegPosInstTest(TestIsSubnormal, FPClass & fcNegSubnormal)));
}
if (FPClass & fcZero) {
// Create zero integer constant and check for equality with bitcasted to
// int float value
auto *BitCastToInt =
BM->addUnaryInst(OpBitcast, OpSPIRVTy, InputFloat, BB);
auto *ZeroConst = transValue(
Constant::getIntegerValue(IntOpLLVMTy, APInt::getZero(BitSize)), BB);
auto *TestIsZero =
BM->addCmpInst(OpIEqual, ResTy, BitCastToInt, ZeroConst, BB);
if (FPClass & fcPosZero && FPClass & fcNegZero)
ResultVec.emplace_back(GetInvertedTestIfNeeded(TestIsZero));
else
ResultVec.emplace_back(GetInvertedTestIfNeeded(
GetNegPosInstTest(TestIsZero, FPClass & fcNegZero)));
}
if (ResultVec.size() == 1)
return ResultVec.back();
SPIRVValue *Result = ResultVec.front();
for (size_t I = 1; I != ResultVec.size(); ++I) {
// Create a sequence of LogicalOr instructions from ResultVec to get
// the overall test result
std::vector<SPIRVId> LogicOps = {Result->getId(), ResultVec[I]->getId()};
Result = BM->addInstTemplate(OpLogicalOr, LogicOps, BB, ResTy);
}
return Result;
}

default:
if (BM->isUnknownIntrinsicAllowed(II))
Expand Down
Loading