Implement SPV_KHR_poison_freeze extension (#198037)

Specification can be found here:

https://github.com/KhronosGroup/SPIRV-Registry/blob/main/extensions/KHR/SPV_KHR_poison_freeze.asciidoc
This commit is contained in:
Dmitry Sidorov 2026-05-28 16:09:54 +02:00 committed by GitHub
parent 34e7ecaf87
commit b43dcbeffe
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 447 additions and 54 deletions

View file

@ -281,6 +281,8 @@ Below is a list of supported SPIR-V extensions, sorted alphabetically by their e
- Adds cooperative matrix types and instructions for matrix multiply-add operations across invocations in a subgroup.
* - ``SPV_KHR_vulkan_memory_model``
- Adds the Vulkan memory model, which defines memory semantics for the Vulkan API.
* - ``SPV_KHR_poison_freeze``
- Adds instructions to represent a poison value and freeze. Also adds an execution mode to control poison behavior.
SPIR-V representation in LLVM IR

View file

@ -45,6 +45,8 @@ let TargetPrefix = "spv" in {
def int_spv_alloca : Intrinsic<[llvm_any_ty], [llvm_i32_ty], [ImmArg<ArgIndex<0>>]>;
def int_spv_alloca_array : Intrinsic<[llvm_any_ty], [llvm_anyint_ty, llvm_i32_ty], [ImmArg<ArgIndex<1>>]>;
def int_spv_undef : Intrinsic<[llvm_i32_ty], []>;
def int_spv_poison : Intrinsic<[llvm_any_ty], [], [IntrNoMem]>;
def int_spv_freeze : Intrinsic<[llvm_any_ty], [LLVMMatchType<0>]>;
def int_spv_inline_asm : Intrinsic<[], [llvm_metadata_ty, llvm_metadata_ty, llvm_vararg_ty]>;
def int_spv_named_boolean_spec_constant
: Intrinsic<[llvm_i1_ty], [llvm_i32_ty, llvm_i1_ty, llvm_metadata_ty],

View file

@ -543,15 +543,18 @@ void SPIRVAsmPrinter::outputExecutionMode(const Module &M) {
NamedMDNode *Node = M.getNamedMetadata("spirv.ExecutionMode");
if (Node) {
for (unsigned i = 0; i < Node->getNumOperands(); i++) {
const auto EM =
cast<ConstantInt>(
cast<ConstantAsMetadata>((Node->getOperand(i))->getOperand(1))
->getValue())
->getZExtValue();
// Skip ArithmeticPoisonKHR to avoid a duplicate.
if (EM == SPIRV::ExecutionMode::ArithmeticPoisonKHR)
continue;
// If SPV_KHR_float_controls2 is enabled and we find any of
// FPFastMathDefault, ContractionOff or SignedZeroInfNanPreserve execution
// modes, skip it, it'll be done somewhere else.
if (ST->canUseExtension(SPIRV::Extension::SPV_KHR_float_controls2)) {
const auto EM =
cast<ConstantInt>(
cast<ConstantAsMetadata>((Node->getOperand(i))->getOperand(1))
->getValue())
->getZExtValue();
if (EM == SPIRV::ExecutionMode::FPFastMathDefault ||
EM == SPIRV::ExecutionMode::ContractionOff ||
EM == SPIRV::ExecutionMode::SignedZeroInfNanPreserve)
@ -620,6 +623,18 @@ void SPIRVAsmPrinter::outputExecutionMode(const Module &M) {
Inst.addOperand(MCOperand::createImm(TypeCode));
outputMCInst(Inst);
}
// Per SPV_KHR_poison_freeze description of PoisonFreezeKHR "If declared,
// all entry points must use the ArithmeticPoisonKHR execution mode".
if (llvm::is_contained(MAI->Reqs.getMinimalCapabilities(),
SPIRV::Capability::PoisonFreezeKHR)) {
MCInst Inst;
Inst.setOpcode(SPIRV::OpExecutionMode);
Inst.addOperand(MCOperand::createReg(FReg));
unsigned EM =
static_cast<unsigned>(SPIRV::ExecutionMode::ArithmeticPoisonKHR);
Inst.addOperand(MCOperand::createImm(EM));
outputMCInst(Inst);
}
if (ST->isKernel() && !M.getNamedMetadata("spirv.ExecutionMode") &&
!M.getNamedMetadata("opencl.enable.FP_CONTRACT")) {
if (ST->canUseExtension(SPIRV::Extension::SPV_KHR_float_controls2)) {

View file

@ -178,7 +178,9 @@ static const StringMap<SPIRV::Extension::Extension> SPIRVExtensionMap = {
{"SPV_INTEL_unstructured_loop_controls",
SPIRV::Extension::Extension::SPV_INTEL_unstructured_loop_controls},
{"SPV_AMD_weak_linkage", SPIRV::Extension::Extension::SPV_AMD_weak_linkage},
{"SPV_KHR_abort", SPIRV::Extension::Extension::SPV_KHR_abort}};
{"SPV_KHR_abort", SPIRV::Extension::Extension::SPV_KHR_abort},
{"SPV_KHR_poison_freeze",
SPIRV::Extension::Extension::SPV_KHR_poison_freeze}};
bool SPIRVExtensionsParser::parse(cl::Option &O, StringRef ArgName,
StringRef ArgValue, ExtensionSet &Vals) {

View file

@ -29,6 +29,7 @@
#include "llvm/IR/TypedPointerType.h"
#include "llvm/IR/Value.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Transforms/Utils/Local.h"
#include <cassert>
@ -57,6 +58,8 @@
using namespace llvm;
using namespace llvm::PatternMatch;
#define DEBUG_TYPE "spirv-emit-intrinsics"
static cl::opt<bool>
SpirvEmitOpNames("spirv-emit-op-names",
cl::desc("Emit OpName for all instructions"),
@ -257,6 +260,7 @@ class SPIRVEmitIntrinsics
void preprocessCompositeConstants(IRBuilder<> &B);
void preprocessUndefs(IRBuilder<> &B);
void preprocessPoisons(IRBuilder<> &B);
void simplifyNullAddrSpaceCasts();
Type *reconstructType(Value *Op, bool UnknownElemTypeI8,
@ -447,6 +451,12 @@ static bool isAggrConstForceInt32(const Value *V) {
isa<ConstantDataArray>(V) || IsAggrZero || IsUndefAggregate;
}
static bool isSpvAggrPlaceholder(const Value *V) {
return match(
V,
m_AnyIntrinsic<Intrinsic::spv_undef, Intrinsic::spv_const_composite>());
}
static void setInsertPointSkippingPhis(IRBuilder<> &B, Instruction *I) {
if (isa<PHINode>(I))
B.SetInsertPoint(I->getParent()->getFirstNonPHIOrDbgOrAlloca());
@ -555,13 +565,8 @@ Type *SPIRVEmitIntrinsics::reconstructType(Value *Op, bool UnknownElemTypeI8,
Type *Ty = Op->getType();
if (auto *OpI = dyn_cast<Instruction>(Op)) {
Ty = restoreMutatedType(GR, OpI, Ty);
// Aggregate undef/constant values are lowered to spv_undef/
// spv_const_composite intrinsics whose return type is i32; recover the
// original aggregate type so downstream pointee deduction sees it.
if (match(OpI, m_CombineOr(m_Intrinsic<Intrinsic::spv_undef>(),
m_Intrinsic<Intrinsic::spv_const_composite>())))
if (auto It = AggrConstTypes.find(OpI); It != AggrConstTypes.end())
Ty = It->second;
if (auto It = AggrConstTypes.find(OpI); It != AggrConstTypes.end())
Ty = It->second;
}
if (!isUntypedPointerTy(Ty))
return Ty;
@ -1605,26 +1610,30 @@ void SPIRVEmitIntrinsics::replaceMemInstrUses(Instruction *Old,
}
void SPIRVEmitIntrinsics::preprocessUndefs(IRBuilder<> &B) {
std::queue<Instruction *> Worklist;
const SPIRVSubtarget *STI = TM.getSubtargetImpl(*CurrF);
bool HasPoisonExt =
STI->canUseExtension(SPIRV::Extension::SPV_KHR_poison_freeze);
SmallVector<Instruction *, 16> Insts;
for (auto &I : instructions(CurrF))
Worklist.push(&I);
Insts.push_back(&I);
while (!Worklist.empty()) {
Instruction *I = Worklist.front();
for (Instruction *I : Insts) {
bool BPrepared = false;
Worklist.pop();
for (auto &Op : I->operands()) {
auto *AggrUndef = dyn_cast<UndefValue>(Op);
if (!AggrUndef || !Op->getType()->isAggregateType())
continue;
if (HasPoisonExt && isa<PoisonValue>(AggrUndef))
continue;
if (isa<PoisonValue>(AggrUndef))
LLVM_DEBUG(dbgs() << "SPV_KHR_poison_freeze is not enabled. Poison is "
"lowered as undef\n");
if (!BPrepared) {
setInsertPointSkippingPhis(B, I);
BPrepared = true;
}
auto *IntrUndef = B.CreateIntrinsic(Intrinsic::spv_undef, {});
Worklist.push(IntrUndef);
I->replaceUsesOfWith(Op, IntrUndef);
AggrConsts[IntrUndef] = AggrUndef;
AggrConstTypes[IntrUndef] = AggrUndef->getType();
@ -1632,6 +1641,47 @@ void SPIRVEmitIntrinsics::preprocessUndefs(IRBuilder<> &B) {
}
}
void SPIRVEmitIntrinsics::preprocessPoisons(IRBuilder<> &B) {
const SPIRVSubtarget *STI = TM.getSubtargetImpl(*CurrF);
if (!STI->canUseExtension(SPIRV::Extension::SPV_KHR_poison_freeze))
return;
for (Instruction &I : instructions(CurrF)) {
bool BPrepared = false;
auto *Phi = dyn_cast<PHINode>(&I);
for (unsigned Idx = 0; Idx < I.getNumOperands(); ++Idx) {
Value *Op = I.getOperand(Idx);
auto *Poison = dyn_cast<PoisonValue>(Op);
if (!Poison || Op->getType()->isMetadataTy())
continue;
Type *OpTy = Op->getType();
Value *Replacement = nullptr;
if (OpTy->isAggregateType()) {
if (!BPrepared) {
setInsertPointSkippingPhis(B, &I);
BPrepared = true;
}
auto *Call =
B.CreateIntrinsic(Intrinsic::spv_poison, {B.getInt32Ty()}, {});
AggrConsts[Call] = Poison;
AggrConstTypes[Call] = OpTy;
Replacement = Call;
} else {
if (Phi)
B.SetInsertPoint(Phi->getIncomingBlock(Idx)->getTerminator());
else if (!BPrepared) {
setInsertPointSkippingPhis(B, &I);
BPrepared = true;
}
Replacement = B.CreateIntrinsic(Intrinsic::spv_poison, {OpTy}, {});
}
I.setOperand(Idx, Replacement);
}
}
}
// Simplify addrspacecast(null) instructions to ConstantPointerNull of the
// target type. Casting null always yields null, and this avoids SPIR-V
// lowering issues where the null gets typed as an integer instead of a
@ -1647,6 +1697,9 @@ void SPIRVEmitIntrinsics::simplifyNullAddrSpaceCasts() {
}
void SPIRVEmitIntrinsics::preprocessCompositeConstants(IRBuilder<> &B) {
const SPIRVSubtarget *STI = TM.getSubtargetImpl(*CurrF);
bool HasPoisonExt =
STI->canUseExtension(SPIRV::Extension::SPV_KHR_poison_freeze);
std::queue<Instruction *> Worklist;
for (auto &I : instructions(CurrF))
Worklist.push(&I);
@ -1676,6 +1729,13 @@ void SPIRVEmitIntrinsics::preprocessCompositeConstants(IRBuilder<> &B) {
ResTy = Op->getType()->isVectorTy() ? COp->getType() : B.getInt32Ty();
}
if (AggrConst) {
auto PrepareInsert = [&]() {
if (BPrepared)
return;
IsPhi ? B.SetInsertPointPastAllocas(I->getParent()->getParent())
: B.SetInsertPoint(I);
BPrepared = true;
};
SmallVector<Value *> Args;
if (auto *COp = dyn_cast<ConstantDataSequential>(Op))
for (unsigned i = 0; i < COp->getNumElements(); ++i)
@ -1688,13 +1748,22 @@ void SPIRVEmitIntrinsics::preprocessCompositeConstants(IRBuilder<> &B) {
CE && CE->getOpcode() == Instruction::AddrSpaceCast &&
isa<ConstantPointerNull>(CE->getOperand(0)))
Op = ConstantPointerNull::get(cast<PointerType>(CE->getType()));
if (HasPoisonExt && isa<PoisonValue>(Op)) {
PrepareInsert();
Type *PoisonTy = Op->getType();
if (PoisonTy->isAggregateType()) {
auto *Call = B.CreateIntrinsic(Intrinsic::spv_poison,
{B.getInt32Ty()}, {});
AggrConsts[Call] = cast<PoisonValue>(Op);
AggrConstTypes[Call] = PoisonTy;
Op = Call;
} else {
Op = B.CreateIntrinsic(Intrinsic::spv_poison, {PoisonTy}, {});
}
}
Args.push_back(Op);
}
if (!BPrepared) {
IsPhi ? B.SetInsertPointPastAllocas(I->getParent()->getParent())
: B.SetInsertPoint(I);
BPrepared = true;
}
PrepareInsert();
auto *CI =
B.CreateIntrinsic(Intrinsic::spv_const_composite, {ResTy}, {Args});
Worklist.push(CI);
@ -2068,17 +2137,11 @@ void SPIRVEmitIntrinsics::insertPtrCastOrAssignTypeInstr(Instruction *I,
Value *Op = SI->getValueOperand();
Value *Pointer = SI->getPointerOperand();
Type *OpTy = Op->getType();
if (auto *OpI = dyn_cast<Instruction>(Op))
if (auto *OpI = dyn_cast<Instruction>(Op)) {
OpTy = restoreMutatedType(GR, OpI, OpTy);
// Aggregate undef/constant values were lowered by preprocessUndefs/
// preprocessCompositeConstants to spv_undef/spv_const_composite calls
// returning i32; recover the original aggregate type so the pointee type
// assigned to the store's pointer operand reflects the value being stored.
if (match(Op, m_CombineOr(m_Intrinsic<Intrinsic::spv_undef>(),
m_Intrinsic<Intrinsic::spv_const_composite>())))
if (auto It = AggrConstTypes.find(cast<Instruction>(Op));
It != AggrConstTypes.end())
if (auto It = AggrConstTypes.find(OpI); It != AggrConstTypes.end())
OpTy = It->second;
}
if (OpTy == Op->getType())
OpTy = deduceElementTypeByValueDeep(OpTy, Op, false);
replacePointerOperandWithPtrCast(I, Pointer, OpTy, 1, B);
@ -2457,24 +2520,23 @@ shouldEmitIntrinsicsForGlobalValue(const GlobalVariableUsers &GVUsers,
Value *SPIRVEmitIntrinsics::buildSpvUndefComposite(Type *AggrTy,
IRBuilder<> &B) {
auto MakeLeaf = [&](Type *ElemTy) -> Instruction * {
auto *Leaf = B.CreateIntrinsic(Intrinsic::spv_undef, {});
AggrConsts[Leaf] = PoisonValue::get(ElemTy);
AggrConstTypes[Leaf] = ElemTy;
return Leaf;
};
SmallVector<Value *, 4> Elems;
if (auto *ArrTy = dyn_cast<ArrayType>(AggrTy)) {
Type *ElemTy = ArrTy->getElementType();
auto *UI = B.CreateIntrinsic(Intrinsic::spv_undef, {});
AggrConsts[UI] = PoisonValue::get(ElemTy);
AggrConstTypes[UI] = ElemTy;
Elems.assign(ArrTy->getNumElements(), UI);
Elems.assign(ArrTy->getNumElements(), MakeLeaf(ArrTy->getElementType()));
} else {
auto *StructTy = cast<StructType>(AggrTy);
DenseMap<Type *, Instruction *> UndefByType;
DenseMap<Type *, Instruction *> LeafByType;
for (unsigned I = 0; I < StructTy->getNumElements(); ++I) {
Type *ElemTy = StructTy->getContainedType(I);
auto &Entry = UndefByType[ElemTy];
if (!Entry) {
Entry = B.CreateIntrinsic(Intrinsic::spv_undef, {});
AggrConsts[Entry] = PoisonValue::get(ElemTy);
AggrConstTypes[Entry] = ElemTy;
}
auto &Entry = LeafByType[ElemTy];
if (!Entry)
Entry = MakeLeaf(ElemTy);
Elems.push_back(Entry);
}
}
@ -2499,8 +2561,21 @@ void SPIRVEmitIntrinsics::processGlobalValue(GlobalVariable &GV,
deduceElementTypeHelper(&GV, false);
Init = GV.getInitializer();
Value *InitOp = Init;
if (isa<UndefValue>(Init) && Init->getType()->isAggregateType())
InitOp = buildSpvUndefComposite(Init->getType(), B);
if (isa<UndefValue>(Init) && Init->getType()->isAggregateType()) {
const SPIRVSubtarget *STI = TM.getSubtargetImpl();
bool UsePoison =
isa<PoisonValue>(Init) &&
STI->canUseExtension(SPIRV::Extension::SPV_KHR_poison_freeze);
if (UsePoison) {
auto *Call =
B.CreateIntrinsic(Intrinsic::spv_poison, {B.getInt32Ty()}, {});
AggrConsts[Call] = cast<PoisonValue>(Init);
AggrConstTypes[Call] = Init->getType();
InitOp = Call;
} else {
InitOp = buildSpvUndefComposite(Init->getType(), B);
}
}
Type *Ty = isAggrConstForceInt32(Init) ? B.getInt32Ty() : Init->getType();
Constant *Const = isAggrConstForceInt32(Init) ? B.getInt32(1) : Init;
auto *InitInst = B.CreateIntrinsic(Intrinsic::spv_init_global,
@ -2593,12 +2668,14 @@ void SPIRVEmitIntrinsics::insertAssignTypeIntrs(Instruction *I,
setInsertPointAfterDef(B, I);
Type *TypeToAssign = Ty;
if (auto *II = dyn_cast<IntrinsicInst>(I)) {
if (II->getIntrinsicID() == Intrinsic::spv_const_composite ||
II->getIntrinsicID() == Intrinsic::spv_undef) {
if (isSpvAggrPlaceholder(II)) {
auto It = AggrConstTypes.find(II);
if (It == AggrConstTypes.end())
report_fatal_error("Unknown composite intrinsic type");
TypeToAssign = It->second;
} else if (II->getIntrinsicID() == Intrinsic::spv_poison) {
if (auto It = AggrConstTypes.find(II); It != AggrConstTypes.end())
TypeToAssign = It->second;
}
} else if (auto It = AggrConstTypes.find(I); It != AggrConstTypes.end())
TypeToAssign = It->second;
@ -3375,6 +3452,7 @@ bool SPIRVEmitIntrinsics::runOnFunction(Function &Func) {
processGlobalValue(GV, B);
preprocessUndefs(B);
preprocessPoisons(B);
simplifyNullAddrSpaceCasts();
preprocessCompositeConstants(B);

View file

@ -43,6 +43,7 @@ bool SPIRVInstrInfo::isConstantInstr(const MachineInstr &MI) const {
case SPIRV::OpSpecConstantCompositeContinuedINTEL:
case SPIRV::OpSpecConstantOp:
case SPIRV::OpUndef:
case SPIRV::OpPoisonKHR:
case SPIRV::OpConstantFunctionPointerINTEL:
return true;
default:

View file

@ -98,6 +98,10 @@ def OpNop: SimpleOp<"OpNop", 0>;
def OpUndef: Op<1, (outs ID:$res), (ins TYPE:$type), "$res = OpUndef $type">;
def OpSizeOf: Op<321, (outs ID:$res), (ins TYPE:$ty, ID:$ptr), "$res = OpSizeOf $ty $ptr">;
// - SPV_KHR_poison_freeze : Poison values and freeze
def OpPoisonKHR: Op<5158, (outs ID:$res), (ins TYPE:$type), "$res = OpPoisonKHR $type">;
def OpFreezeKHR: Op<5159, (outs ID:$res), (ins TYPE:$type, ID:$val), "$res = OpFreezeKHR $type $val">;
// - SPV_KHR_expect_assume : Expect assume instructions
def OpAssumeTrueKHR: Op<5630, (outs), (ins ID:$cond), "OpAssumeTrueKHR $cond">;
def OpExpectKHR: Op<5631, (outs ID:$res), (ins TYPE:$ty, ID:$val, ID:$expected), "$res = OpExpectKHR $ty $val $expected">;

View file

@ -623,7 +623,7 @@ static bool isConstReg(MachineRegisterInfo *MRI, MachineInstr *OpDef) {
GIntrinsic *GIntr = cast<GIntrinsic>(MI);
unsigned IntrID = GIntr->getIntrinsicID();
if (IntrID != Intrinsic::spv_const_composite &&
IntrID != Intrinsic::spv_undef)
IntrID != Intrinsic::spv_undef && IntrID != Intrinsic::spv_poison)
return false;
continue;
}
@ -650,6 +650,7 @@ static bool isConstReg(MachineRegisterInfo *MRI, MachineInstr *OpDef) {
case SPIRV::OpConstantSampler:
case SPIRV::OpConstantNull:
case SPIRV::OpUndef:
case SPIRV::OpPoisonKHR:
case SPIRV::OpConstantFunctionPointerINTEL:
continue;
default:
@ -3840,14 +3841,25 @@ bool SPIRVInstructionSelector::selectBitreverse(Register ResVReg,
bool SPIRVInstructionSelector::selectFreeze(Register ResVReg,
SPIRVTypeInst ResType,
MachineInstr &I) const {
assert(I.getOperand(0).isReg() && I.getOperand(1).isReg() &&
"G_FREEZE must define and use a register");
Register OpReg = I.getOperand(1).getReg();
// With SPV_KHR_poison_freeze, lower `freeze` to OpFreezeKHR.
if (STI.canUseExtension(SPIRV::Extension::SPV_KHR_poison_freeze)) {
BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(SPIRV::OpFreezeKHR))
.addDef(ResVReg)
.addUse(GR.getSPIRVTypeID(ResType))
.addUse(OpReg)
.constrainAllUses(TII, TRI, RBI);
return true;
}
// There is no way to implement `freeze` correctly without support on SPIR-V
// standard side, but we may at least address a simple (static) case when
// undef/poison value presence is obvious. The main benefit of even
// incomplete `freeze` support is preventing of translation from crashing due
// to lack of support on legalization and instruction selection steps.
if (!I.getOperand(0).isReg() || !I.getOperand(1).isReg())
return false;
Register OpReg = I.getOperand(1).getReg();
if (MachineInstr *Def = MRI->getVRegDef(OpReg)) {
if (Def->getOpcode() == TargetOpcode::COPY)
Def = MRI->getVRegDef(Def->getOperand(1).getReg());
@ -3867,9 +3879,13 @@ bool SPIRVInstructionSelector::selectFreeze(Register ResVReg,
unsigned DestOpCode;
if (Reg.isValid()) {
DestOpCode = SPIRV::OpConstantNull;
LLVM_DEBUG(dbgs() << "SPV_KHR_poison_freeze is not enabled. freeze of a "
"static undef/poison lowered to OpConstantNull\n");
} else {
DestOpCode = TargetOpcode::COPY;
Reg = OpReg;
LLVM_DEBUG(dbgs() << "SPV_KHR_poison_freeze is not enabled. freeze "
"skipped, lowered as a copy of the operand\n");
}
BuildMI(*I.getParent(), I, I.getDebugLoc(), TII.get(DestOpCode))
.addDef(I.getOperand(0).getReg())
@ -4718,6 +4734,19 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
MIB.constrainAllUses(TII, TRI, RBI);
return true;
}
case Intrinsic::spv_poison:
BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpPoisonKHR))
.addDef(ResVReg)
.addUse(GR.getSPIRVTypeID(ResType))
.constrainAllUses(TII, TRI, RBI);
return true;
case Intrinsic::spv_freeze:
BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpFreezeKHR))
.addDef(ResVReg)
.addUse(GR.getSPIRVTypeID(ResType))
.addUse(I.getOperand(2).getReg())
.constrainAllUses(TII, TRI, RBI);
return true;
case Intrinsic::spv_named_boolean_spec_constant: {
auto Opcode = I.getOperand(3).getImm() ? SPIRV::OpSpecConstantTrue
: SPIRV::OpSpecConstantFalse;

View file

@ -1981,6 +1981,15 @@ void addInstrRequirements(const MachineInstr &MI,
Reqs.addExtension(SPIRV::Extension::SPV_KHR_abort);
Reqs.addCapability(SPIRV::Capability::AbortKHR);
break;
case SPIRV::OpPoisonKHR:
case SPIRV::OpFreezeKHR:
if (!ST.canUseExtension(SPIRV::Extension::SPV_KHR_poison_freeze))
report_fatal_error("OpPoisonKHR/OpFreezeKHR instruction requires the "
"following SPIR-V extension: SPV_KHR_poison_freeze",
false);
Reqs.addExtension(SPIRV::Extension::SPV_KHR_poison_freeze);
Reqs.addCapability(SPIRV::Capability::PoisonFreezeKHR);
break;
case SPIRV::OpFunctionPointerCallINTEL:
if (ST.canUseExtension(SPIRV::Extension::SPV_INTEL_function_pointers)) {
Reqs.addExtension(SPIRV::Extension::SPV_INTEL_function_pointers);

View file

@ -403,6 +403,7 @@ defm SPV_AMD_weak_linkage : ExtensionOperand<137, [EnvOpenCL]>;
// Vulkan/Shader support requires emitting explicit layout decorations on the
// Message Type, which is not yet implemented.
defm SPV_KHR_abort : ExtensionOperand<138, [EnvOpenCL]>;
defm SPV_KHR_poison_freeze : ExtensionOperand<139, [EnvVulkan, EnvOpenCL]>;
//===----------------------------------------------------------------------===//
// Multiclass used to define Capabilities enum values and at the same time
@ -640,6 +641,7 @@ defm BlockingPipesALTERA : CapabilityOperand<5945, 0, 0, [SPV_ALTERA_blocking_pi
defm ArbitraryPrecisionFixedPointALTERA : CapabilityOperand<5922, 0, 0, [SPV_ALTERA_arbitrary_precision_fixed_point], []>;
defm ArbitraryPrecisionFloatingPointALTERA : CapabilityOperand<5845, 0, 0,[SPV_ALTERA_arbitrary_precision_floating_point], []>;
defm UnstructuredLoopControlsINTEL : CapabilityOperand<5886, 0, 0, [SPV_INTEL_unstructured_loop_controls], []>;
defm PoisonFreezeKHR : CapabilityOperand<5156, 0, 0, [SPV_KHR_poison_freeze], []>;
//===----------------------------------------------------------------------===//
// Multiclass used to define SourceLanguage enum values and at the same time
@ -844,6 +846,7 @@ defm RoundingModeRTNINTEL : ExecutionModeOperand<5621, [RoundToInfinityINTEL]>;
defm FloatingPointModeALTINTEL : ExecutionModeOperand<5622, [FloatingPointModeINTEL]>;
defm FloatingPointModeIEEEINTEL : ExecutionModeOperand<5623, [FloatingPointModeINTEL]>;
defm MaxWorkgroupSizeINTEL : ExecutionModeOperand<5893, [KernelAttributesINTEL]>;
defm ArithmeticPoisonKHR : ExecutionModeOperand<5157, [PoisonFreezeKHR]>;
// TODO-SPIRV: Add the following once they are used / tested.
// defm MaxWorkDimINTEL : ExecutionModeOperand<5894, [KernelAttributesINTEL]>;
// defm NoGlobalOffsetINTEL : ExecutionModeOperand<5895, [KernelAttributesINTEL]>;

View file

@ -0,0 +1,20 @@
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - -filetype=obj | spirv-val %}
; The entry point already requests ArithmeticPoisonKHR through
; !spirv.ExecutionMode. The capability-driven path must not add a second,
; duplicate execution mode for it.
; CHECK-DAG: OpCapability PoisonFreezeKHR
; CHECK-DAG: OpExtension "SPV_KHR_poison_freeze"
; CHECK: OpEntryPoint Kernel %[[#ENTRY:]] "kernel_with_poison"
; CHECK-COUNT-1: OpExecutionMode %[[#ENTRY]] ArithmeticPoisonKHR
; CHECK-NOT: OpExecutionMode %[[#ENTRY]] ArithmeticPoisonKHR
define spir_kernel void @kernel_with_poison(ptr %dst) {
store i32 poison, ptr %dst
ret void
}
!spirv.ExecutionMode = !{!0}
!0 = !{ptr @kernel_with_poison, i32 5157}

View file

@ -0,0 +1,11 @@
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - -filetype=obj | spirv-val %}
; CHECK-NOT: OpCapability PoisonFreezeKHR
; CHECK-NOT: OpExtension "SPV_KHR_poison_freeze"
; CHECK-NOT: ArithmeticPoisonKHR
define spir_kernel void @kernel_no_poison(ptr %dst, i32 %v) {
store i32 %v, ptr %dst
ret void
}

View file

@ -0,0 +1,31 @@
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - -filetype=obj | spirv-val %}
; Poison only appears in poison_helper, which is not an entry point and is not
; reachable from one. The PoisonFreezeKHR capability is nonetheless module-wide,
; so every entry point must carry the ArithmeticPoisonKHR execution mode, while
; the non-entry helper must not.
; CHECK-DAG: OpCapability PoisonFreezeKHR
; CHECK-DAG: OpExtension "SPV_KHR_poison_freeze"
; CHECK: OpEntryPoint Kernel %[[#ENTRY_A:]] "entry_a"
; CHECK: OpEntryPoint Kernel %[[#ENTRY_B:]] "entry_b"
; CHECK: OpExecutionMode %[[#ENTRY_A]] ArithmeticPoisonKHR
; CHECK: OpExecutionMode %[[#ENTRY_B]] ArithmeticPoisonKHR
; CHECK-NOT: OpExecutionMode %[[#]] ArithmeticPoisonKHR
define void @poison_helper(ptr %dst) {
store i32 poison, ptr %dst
ret void
}
define spir_kernel void @entry_a(ptr %dst) {
store i32 0, ptr %dst
ret void
}
define spir_kernel void @entry_b(ptr %dst) {
store i32 0, ptr %dst
ret void
}

View file

@ -0,0 +1,40 @@
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - | FileCheck %s
; RUN: llc -O0 -mtriple=spirv-vulkan-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - -filetype=obj | spirv-val %}
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - -filetype=obj | spirv-val %}
; CHECK-DAG: OpCapability PoisonFreezeKHR
; CHECK-DAG: OpExtension "SPV_KHR_poison_freeze"
; CHECK-DAG: %[[#I32:]] = OpTypeInt 32 0
; CHECK-DAG: %[[#V4:]] = OpTypeVector %[[#I32]] 4
; CHECK-DAG: %[[#PI:]] = OpPoisonKHR %[[#I32]]
; CHECK-DAG: %[[#PV:]] = OpPoisonKHR %[[#V4]]
; CHECK: %[[#FS:]] = OpFreezeKHR %[[#I32]] %[[#PI]]
; CHECK: OpStore %[[#]] %[[#FS]]
; CHECK: %[[#FVP:]] = OpFreezeKHR %[[#V4]] %[[#PV]]
; CHECK: OpStore %[[#]] %[[#FVP]]
; CHECK: %[[#FV:]] = OpFreezeKHR %[[#V4]] %[[#]]
; CHECK: OpStore %[[#]] %[[#FV]]
define void @freeze_scalar_poison(ptr %dst) {
%f = freeze i32 poison
store i32 %f, ptr %dst
ret void
}
define void @freeze_vector_poison(ptr %dst) {
%f = freeze <4 x i32> poison
store <4 x i32> %f, ptr %dst
ret void
}
define void @freeze_vector(ptr %dst, <4 x i32> %v) {
%f = freeze <4 x i32> %v
store <4 x i32> %f, ptr %dst
ret void
}

View file

@ -0,0 +1,43 @@
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - | FileCheck %s
; RUN: llc -O0 -mtriple=spirv-vulkan-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - | FileCheck %s
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - -filetype=obj | spirv-val %}
; CHECK-DAG: OpCapability PoisonFreezeKHR
; CHECK-DAG: OpExtension "SPV_KHR_poison_freeze"
; CHECK-DAG: %[[#I32:]] = OpTypeInt 32 0
; CHECK-DAG: %[[#ARR:]] = OpTypeArray %[[#I32]] %[[#]]
; CHECK-DAG: %[[#INNER:]] = OpTypeArray %[[#I32]] %[[#]]
; CHECK-DAG: %[[#NEST:]] = OpTypeArray %[[#INNER]] %[[#]]
; CHECK-DAG: %[[#PI:]] = OpPoisonKHR %[[#I32]]
; CHECK-DAG: %[[#C1:]] = OpConstant %[[#I32]] 1
; CHECK-DAG: %[[#C3:]] = OpConstant %[[#I32]] 3
; CHECK-DAG: %[[#C4:]] = OpConstant %[[#I32]] 4
; CHECK-DAG: %[[#MIX:]] = OpConstantComposite %[[#ARR]] %[[#C1]] %[[#PI]] %[[#C3]] %[[#C4]]
; CHECK-DAG: %[[#PINNER:]] = OpPoisonKHR %[[#INNER]]
; CHECK-DAG: %[[#PARTIAL:]] = OpConstantComposite %[[#INNER]] %[[#C1]] %[[#PI]]
; CHECK-DAG: %[[#NESTMIX:]] = OpConstantComposite %[[#NEST]] %[[#PARTIAL]] %[[#PINNER]]
; CHECK: OpFunction
; CHECK: OpStore %[[#]] %[[#MIX]]
; CHECK: OpFunction
; CHECK: OpStore %[[#]] %[[#NESTMIX]]
%arr = type [4 x i32]
%nest = type [2 x [2 x i32]]
@g_partial = global %arr [i32 1, i32 poison, i32 3, i32 4]
define void @poison_partial_composite(ptr %dst) {
store %arr [i32 1, i32 poison, i32 3, i32 4], ptr %dst
ret void
}
define void @poison_nested_composite(ptr %dst) {
store %nest [[2 x i32] [i32 1, i32 poison], [2 x i32] poison], ptr %dst
ret void
}

View file

@ -0,0 +1,103 @@
; RUN: llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - | FileCheck %s --check-prefixes=CHECK,OCL
; RUN: llc -O0 -mtriple=spirv-vulkan-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - | FileCheck %s --check-prefixes=CHECK
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv64-unknown-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - -filetype=obj | spirv-val %}
; RUN: %if spirv-tools %{ llc -O0 -mtriple=spirv-vulkan-unknown --spirv-ext=+SPV_KHR_poison_freeze %s -o - -filetype=obj | spirv-val %}
; CHECK-DAG: OpCapability PoisonFreezeKHR
; CHECK-DAG: OpExtension "SPV_KHR_poison_freeze"
; CHECK-DAG: %[[#I32:]] = OpTypeInt 32 0
; CHECK-DAG: %[[#V4:]] = OpTypeVector %[[#I32]] 4
; CHECK-DAG: %[[#ARR:]] = OpTypeArray %[[#I32]] %[[#]]
; CHECK-DAG: %[[#F32:]] = OpTypeFloat 32
; CHECK-DAG: %[[#V4F:]] = OpTypeVector %[[#F32]] 4
; CHECK-DAG: %[[#PI:]] = OpPoisonKHR %[[#I32]]
; CHECK-DAG: %[[#PV:]] = OpPoisonKHR %[[#V4]]
; CHECK-DAG: %[[#PVF:]] = OpPoisonKHR %[[#V4F]]
; CHECK-DAG: %[[#PF:]] = OpPoisonKHR %[[#F32]]
; CHECK-DAG: %[[#PA:]] = OpPoisonKHR %[[#ARR]]
; CHECK: OpFunction
; CHECK: OpStore %[[#]] %[[#PI]]
; CHECK: OpStore %[[#]] %[[#PI]]
; CHECK: OpFunction
; CHECK: OpStore %[[#]] %[[#PV]]
; CHECK: OpStore %[[#]] %[[#PV]]
; CHECK: OpFunction
; CHECK: OpStore %[[#]] %[[#PA]]
; CHECK: OpFunction
; CHECK: OpVectorShuffle %[[#V4F]] %[[#]] %[[#PVF]]
; CHECK: OpFunction
; CHECK: OpSelect %[[#V4F]] %[[#]] %[[#]] %[[#PVF]]
; CHECK: OpFunction
; CHECK: OpFAdd %[[#F32]] %[[#]] %[[#PF]]
; CHECK: OpFunction
; CHECK: OpFAdd %[[#F32]] %[[#]] %[[#PF]]
; OpPhi survives only on the OpenCL flow. The Vulkan path structurizes the
; CFG and folds phi(%v, poison) to %v before lowering.
; OCL: OpFunction
; OCL: OpPhi %[[#V4F]] %[[#]] %[[#]] %[[#PVF]] %[[#]]
%arr = type [4 x i32]
define void @poison_scalar(ptr %dst) {
store i32 poison, ptr %dst
store i32 poison, ptr %dst
ret void
}
define void @poison_vector(ptr %dst) {
store <4 x i32> poison, ptr %dst
store <4 x i32> poison, ptr %dst
ret void
}
define void @poison_aggregate(ptr %dst) {
store %arr poison, ptr %dst
ret void
}
define <4 x float> @poison_shuffle(float %val) {
%splat = insertelement <4 x float> poison, float %val, i32 0
%bcast = shufflevector <4 x float> %splat, <4 x float> poison, <4 x i32> zeroinitializer
ret <4 x float> %bcast
}
define <4 x float> @poison_select(i1 %cond, <4 x float> %v) {
%r = select i1 %cond, <4 x float> %v, <4 x float> poison
ret <4 x float> %r
}
define float @poison_fadd(float %x) {
%r = fadd float %x, poison
ret float %r
}
; A direct call to the type-overloaded intrinsic must lower the same way.
declare float @llvm.spv.poison.f32()
define float @poison_intrinsic_call(float %x) {
%p = call float @llvm.spv.poison.f32()
%r = fadd float %x, %p
ret float %r
}
define <4 x float> @poison_phi(i1 %c, <4 x float> %v) {
entry:
br i1 %c, label %a, label %b
a:
br label %j
b:
br label %j
j:
%p = phi <4 x float> [ %v, %a ], [ poison, %b ]
ret <4 x float> %p
}