Skip to content
Merged
2 changes: 2 additions & 0 deletions llvm/docs/SPIRVUsage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,8 @@ Below is a list of supported SPIR-V extensions, sorted alphabetically by their e
- Allows to allocate local arrays whose number of elements is unknown at compile time.
* - ``SPV_INTEL_joint_matrix``
- Adds few matrix capabilities on top of SPV_KHR_cooperative_matrix extension, such as matrix prefetch, get element coordinate and checked load/store/construct instructions, tensor float 32 and bfloat type interpretations for multiply-add instruction.
* - ``SPV_KHR_abort``
- Adds a function-termination instruction that signals an abnormal error to the client API. Currently supported in OpenCL environments only.
* - ``SPV_KHR_bit_instructions``
- Enables bit instructions to be used by SPIR-V modules without requiring the Shader capability.
* - ``SPV_KHR_expect_assume``
Expand Down
1 change: 1 addition & 0 deletions llvm/include/llvm/IR/IntrinsicsSPIRV.td
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ let TargetPrefix = "spv" in {
def int_spv_selection_merge : Intrinsic<[], [llvm_any_ty, llvm_i32_ty], [ImmArg<ArgIndex<1>>]>;
def int_spv_cmpxchg : Intrinsic<[llvm_i32_ty], [llvm_any_ty, llvm_vararg_ty]>;
def int_spv_unreachable : Intrinsic<[], []>;
def int_spv_abort : Intrinsic<[], [llvm_any_ty], [IntrNoReturn]>;
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], []>;
Expand Down
4 changes: 2 additions & 2 deletions llvm/lib/Target/SPIRV/SPIRVCommandLine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,8 +177,8 @@ static const StringMap<SPIRV::Extension::Extension> SPIRVExtensionMap = {
SPIRV::Extension::Extension::SPV_EXT_image_raw10_raw12},
{"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_AMD_weak_linkage", SPIRV::Extension::Extension::SPV_AMD_weak_linkage},
{"SPV_KHR_abort", SPIRV::Extension::Extension::SPV_KHR_abort}};

bool SPIRVExtensionsParser::parse(cl::Option &O, StringRef ArgName,
StringRef ArgValue, ExtensionSet &Vals) {
Expand Down
58 changes: 56 additions & 2 deletions llvm/lib/Target/SPIRV/SPIRVEmitIntrinsics.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1550,6 +1550,22 @@ void SPIRVEmitIntrinsics::replaceMemInstrUses(Instruction *Old,
} else if (isMemInstrToReplace(U) || isa<ReturnInst>(U) ||
isa<CallInst>(U)) {
U->replaceUsesOfWith(Old, New);
// For a `llvm.spv.abort` call whose composite message argument was
// rewritten to a value-id (i32), also retarget the call to a matching
// intrinsic declaration so the IR verifier is satisfied. The SPIR-V
// type of the value is tracked via the GlobalRegistry, so the selector
// still emits OpAbortKHR with the original composite type.
if (auto *CI = dyn_cast<CallInst>(U);
CI && CI->getIntrinsicID() == Intrinsic::spv_abort) {
Type *NewArgTy = New->getType();
Type *ExpectedArgTy = CI->getFunctionType()->getParamType(0);
if (NewArgTy != ExpectedArgTy) {
Module *M = CI->getModule();
Function *NewF = Intrinsic::getOrInsertDeclaration(
M, Intrinsic::spv_abort, {NewArgTy});
CI->setCalledFunction(NewF);
}
}
} else if (auto *Phi = dyn_cast<PHINode>(U)) {
if (Phi->getType() != New->getType()) {
Phi->mutateType(New->getType());
Expand Down Expand Up @@ -2360,9 +2376,47 @@ Instruction *SPIRVEmitIntrinsics::visitAtomicCmpXchgInst(AtomicCmpXchgInst &I) {
return NewI;
}

static bool isAbortCall(const Instruction &I, const SPIRVSubtarget &ST) {
auto *CI = dyn_cast<CallInst>(&I);
if (!CI)
return false;
switch (CI->getIntrinsicID()) {
case Intrinsic::spv_abort:
return true;
case Intrinsic::trap:
case Intrinsic::ubsantrap:
// When the extension is enabled, selection lowers these to OpAbortKHR.
return ST.canUseExtension(SPIRV::Extension::SPV_KHR_abort);
default:
return false;
}
}

// The OpAbortKHR instruction itself is a block terminator, so we don't need to
// emit an extra OpUnreachable instruction.
static bool precededByAbortIntrinsic(const UnreachableInst &I,
const SPIRVSubtarget &ST) {
// Find a previous non-debug instruction.
const Instruction *Prev = I.getPrevNode();
while (Prev && Prev->isDebugOrPseudoInst())
Prev = Prev->getPrevNode();

if (Prev && isAbortCall(*Prev, ST))
return true;

assert(llvm::none_of(
Comment thread
maarquitos14 marked this conversation as resolved.
*I.getParent(),
[&ST](const Instruction &II) { return isAbortCall(II, ST); }) &&
"abort-like call must be the last non-debug instruction before its "
"block's terminator");
return false;
}

Instruction *SPIRVEmitIntrinsics::visitUnreachableInst(UnreachableInst &I) {
IRBuilder<> B(I.getParent());
B.SetInsertPoint(&I);
const SPIRVSubtarget &ST = TM.getSubtarget<SPIRVSubtarget>(*I.getFunction());
if (precededByAbortIntrinsic(I, ST))
return &I;
IRBuilder<> B(&I);
B.CreateIntrinsic(Intrinsic::spv_unreachable, {});
return &I;
}
Expand Down
2 changes: 2 additions & 0 deletions llvm/lib/Target/SPIRV/SPIRVInstrInfo.td
Original file line number Diff line number Diff line change
Expand Up @@ -641,6 +641,8 @@ let isReturn = 1, hasDelaySlot = 0, isBarrier = 0, isTerminator = 1, isNotDuplic
def OpReturn: SimpleOp<"OpReturn", 253>;
def OpReturnValue: Op<254, (outs), (ins ID:$ret), "OpReturnValue $ret">;
def OpUnreachable: SimpleOp<"OpUnreachable", 255>;
def OpAbortKHR: Op<5121, (outs), (ins TYPE:$msg_type, ID:$msg),
"OpAbortKHR $msg_type $msg">;
}
def OpLifetimeStart: Op<256, (outs), (ins ID:$ptr, i32imm:$sz), "OpLifetimeStart $ptr $sz">;
def OpLifetimeStop: Op<257, (outs), (ins ID:$ptr, i32imm:$sz), "OpLifetimeStop $ptr $sz">;
Expand Down
96 changes: 91 additions & 5 deletions llvm/lib/Target/SPIRV/SPIRVInstructionSelector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,8 @@ class SPIRVInstructionSelector : public InstructionSelector {

bool diagnoseUnsupported(const MachineInstr &I, const Twine &Msg) const;

bool selectAbort(MachineInstr &I) const;
bool selectTrap(MachineInstr &I) const;
bool selectFrameIndex(Register ResVReg, SPIRVTypeInst ResType,
MachineInstr &I) const;
bool selectAllocaArray(Register ResVReg, SPIRVTypeInst ResType,
Expand Down Expand Up @@ -1375,11 +1377,13 @@ bool SPIRVInstructionSelector::spvSelect(Register ResVReg,
case TargetOpcode::G_UNMERGE_VALUES:
return selectUnmergeValues(I);

case TargetOpcode::G_TRAP:
case TargetOpcode::G_UBSANTRAP:
return selectTrap(I);

// Discard gen opcodes for intrinsics which we do not expect to actually
// represent code after lowering or intrinsics which are not implemented but
// should not crash when found in a customer's LLVM IR input.
case TargetOpcode::G_TRAP:
case TargetOpcode::G_UBSANTRAP:
case TargetOpcode::DBG_LABEL:
return true;
case TargetOpcode::G_DEBUGTRAP:
Expand Down Expand Up @@ -1739,9 +1743,9 @@ bool SPIRVInstructionSelector::selectPopCount(Register ResVReg,
SPIRVTypeInst ResType,
MachineInstr &I,
unsigned Opcode) const {
// Vulkan restricts OpBitCount to 32-bit integers or vectors of 32-bit
// integers unless VK_KHR_maintenance9 is enabled. Until VK_KHR_maintaince9
// is core we will not generate OpBitCount with any other types when
// Vulkan restricts OpBitCount to 32-bit integers or vectors of 32-bit
// integers unless VK_KHR_maintenance9 is enabled. Until VK_KHR_maintaince9
Comment thread
vmustya marked this conversation as resolved.
Outdated
// is core we will not generate OpBitCount with any other types when
// targeting Vulkan.
if (!STI.getTargetTriple().isVulkanOS())
return selectUnOp(ResVReg, ResType, I, Opcode);
Expand Down Expand Up @@ -4760,6 +4764,8 @@ bool SPIRVInstructionSelector::selectIntrinsic(Register ResVReg,
BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpUnreachable))
.constrainAllUses(TII, TRI, RBI);
return true;
case Intrinsic::spv_abort:
return selectAbort(I);
case Intrinsic::spv_alloca:
return selectFrameIndex(ResVReg, ResType, I);
case Intrinsic::spv_alloca_array:
Expand Down Expand Up @@ -6326,6 +6332,86 @@ bool SPIRVInstructionSelector::selectAllocaArray(Register ResVReg,
return true;
}

// Returns true iff `Ty` is a concrete SPIR-V type per the SPV_KHR_abort
// definition: a numerical scalar (int/float), a (physical) pointer, a vector,
// matrix or any aggregate (array/struct) recursively containing only such
// types. OpTypeBool, OpTypeVoid, opaque handles and similar abstract
// non-concrete types are rejected.
static bool isConcreteSPIRVType(SPIRVTypeInst Ty,
const SPIRVGlobalRegistry &GR) {
SmallVector<SPIRVTypeInst, 4> Worklist{Ty};
while (!Worklist.empty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think this deserves its own function.

SPIRVTypeInst T = Worklist.pop_back_val();
switch (T->getOpcode()) {
case SPIRV::OpTypeInt:
case SPIRV::OpTypeFloat:
case SPIRV::OpTypePointer:
break;
case SPIRV::OpTypeVector:
case SPIRV::OpTypeMatrix:
case SPIRV::OpTypeArray: {
Register OperandReg = T->getOperand(1).getReg();
SPIRVTypeInst ElementT = GR.getSPIRVTypeForVReg(OperandReg);
Worklist.push_back(ElementT);
} break;
case SPIRV::OpTypeStruct:
for (unsigned Idx = 1, E = T->getNumOperands(); Idx < E; ++Idx) {
Register OperandReg = T->getOperand(Idx).getReg();
SPIRVTypeInst ElementT = GR.getSPIRVTypeForVReg(OperandReg);
Worklist.push_back(ElementT);
}
break;
default:
Comment on lines +6343 to +6364

@michalpaszkowski michalpaszkowski Apr 21, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Could this also be a physical pointer (since it can be any concrete type)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Implemented: d0da520

return false;
}
}
return true;
}

bool SPIRVInstructionSelector::selectAbort(MachineInstr &I) const {
assert(I.getNumExplicitOperands() == 2);

Register MsgReg = I.getOperand(1).getReg();
SPIRVTypeInst MsgType = GR.getSPIRVTypeForVReg(MsgReg);
assert(MsgType && "Message argument of llvm.spv.abort has no SPIR-V type");

if (!isConcreteSPIRVType(MsgType, GR))
return diagnoseUnsupported(
I,
"llvm.spv.abort message type must be a concrete SPIR-V type (numerical "
"scalar, pointer, vector, matrix, or aggregate of such types)");

MachineBasicBlock &BB = *I.getParent();
BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpAbortKHR))
.addUse(GR.getSPIRVTypeID(MsgType))
.addUse(MsgReg)
.constrainAllUses(TII, TRI, RBI);
return true;
}

bool SPIRVInstructionSelector::selectTrap(MachineInstr &I) const {
// When the SPV_KHR_abort extension is disabled, drop the G_TRAP and
// G_UBSANTRAP silently.
if (!STI.canUseExtension(SPIRV::Extension::SPV_KHR_abort))
return true;

// Use the 32-bit integer constant for the abort "message" argument:
// - G_UBSANTRAP operand is zero-extended to 32 bits.
// - "All ones" constant is used for G_TRAP.
uint32_t MsgVal = ~0u;
if (I.getOpcode() == TargetOpcode::G_UBSANTRAP)
MsgVal = static_cast<uint32_t>(I.getOperand(0).getImm());

SPIRVTypeInst MsgType = GR.getOrCreateSPIRVIntegerType(32, I, TII);
Register MsgReg = buildI32Constant(MsgVal, I, MsgType);
MachineBasicBlock &BB = *I.getParent();
BuildMI(BB, I, I.getDebugLoc(), TII.get(SPIRV::OpAbortKHR))
.addUse(GR.getSPIRVTypeID(MsgType))
.addUse(MsgReg)
.constrainAllUses(TII, TRI, RBI);
return true;
}

bool SPIRVInstructionSelector::selectFrameIndex(Register ResVReg,
SPIRVTypeInst ResType,
MachineInstr &I) const {
Expand Down
8 changes: 8 additions & 0 deletions llvm/lib/Target/SPIRV/SPIRVModuleAnalysis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1967,6 +1967,14 @@ void addInstrRequirements(const MachineInstr &MI,
Reqs.addExtension(SPIRV::Extension::SPV_KHR_shader_clock);
Reqs.addCapability(SPIRV::Capability::ShaderClockKHR);
break;
case SPIRV::OpAbortKHR:
if (!ST.canUseExtension(SPIRV::Extension::SPV_KHR_abort))
report_fatal_error("OpAbortKHR instruction requires the "
"following SPIR-V extension: SPV_KHR_abort",
false);
Reqs.addExtension(SPIRV::Extension::SPV_KHR_abort);
Reqs.addCapability(SPIRV::Capability::AbortKHR);
break;
case SPIRV::OpFunctionPointerCallINTEL:
if (ST.canUseExtension(SPIRV::Extension::SPV_INTEL_function_pointers)) {
Reqs.addExtension(SPIRV::Extension::SPV_INTEL_function_pointers);
Expand Down
Loading
Loading