From d2096811294598cd5b2f7ce8f17e5701fb167d13 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Mon, 20 Apr 2026 10:14:42 -0700 Subject: [PATCH 1/7] Implement SPV_KHR_abort extension The SPV_KHR_abort extension introduces the `OpAbortKHR` instruction, allowing shaders to terminate execution early. Assisted-by: Claude Opus 4.7 --- include/LLVMSPIRVExtensions.inc | 1 + lib/SPIRV/SPIRVReader.cpp | 15 +++++++ lib/SPIRV/SPIRVWriter.cpp | 23 ++++++++++- lib/SPIRV/libSPIRV/SPIRVEntry.cpp | 1 + lib/SPIRV/libSPIRV/SPIRVInstruction.h | 48 ++++++++++++++++++++++ lib/SPIRV/libSPIRV/SPIRVModule.cpp | 7 ++++ lib/SPIRV/libSPIRV/SPIRVModule.h | 2 + lib/SPIRV/libSPIRV/SPIRVNameMapEnum.h | 1 + lib/SPIRV/libSPIRV/SPIRVOpCodeEnum.h | 1 + spirv-headers-tag.conf | 2 +- test/extensions/KHR/SPV_KHR_abort/abort.ll | 38 +++++++++++++++++ 11 files changed, 136 insertions(+), 3 deletions(-) create mode 100644 test/extensions/KHR/SPV_KHR_abort/abort.ll diff --git a/include/LLVMSPIRVExtensions.inc b/include/LLVMSPIRVExtensions.inc index caa9bcbef7..187c72ec6b 100644 --- a/include/LLVMSPIRVExtensions.inc +++ b/include/LLVMSPIRVExtensions.inc @@ -6,6 +6,7 @@ EXT(SPV_EXT_shader_atomic_float16_add) EXT(SPV_EXT_shader_atomic_float_add) EXT(SPV_EXT_shader_atomic_float_min_max) EXT(SPV_EXT_image_raw10_raw12) +EXT(SPV_KHR_abort) EXT(SPV_KHR_no_integer_wrap_decoration) EXT(SPV_KHR_float_controls) EXT(SPV_KHR_linkonce_odr) diff --git a/lib/SPIRV/SPIRVReader.cpp b/lib/SPIRV/SPIRVReader.cpp index e0b20be3ce..cc065e34e3 100644 --- a/lib/SPIRV/SPIRVReader.cpp +++ b/lib/SPIRV/SPIRVReader.cpp @@ -1939,6 +1939,21 @@ Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F, transValue(RV->getReturnValue(), F, BB), BB)); } + case OpAbortKHR: { + // OpAbortKHR is a SPIR-V block terminator. In LLVM IR, model it as a call + // to the SPIR-V friendly builtin __spirv_AbortKHR followed by an + // 'unreachable' terminator. + auto *AbortInst = + transSPIRVBuiltinFromInst(static_cast(BV), BB); + if (auto *Call = dyn_cast(AbortInst)) { + Call->setDoesNotReturn(); + if (auto *Callee = Call->getCalledFunction()) + Callee->setDoesNotReturn(); + } + new UnreachableInst(*Context, BB); + return mapValue(BV, AbortInst); + } + case OpLifetimeStart: { SPIRVLifetimeStart *LTStart = static_cast(BV); IRBuilder<> Builder(BB); diff --git a/lib/SPIRV/SPIRVWriter.cpp b/lib/SPIRV/SPIRVWriter.cpp index da7c8aef5e..257e8b33f4 100644 --- a/lib/SPIRV/SPIRVWriter.cpp +++ b/lib/SPIRV/SPIRVWriter.cpp @@ -2333,10 +2333,21 @@ LLVMToSPIRVBase::transValueWithoutDecoration(Value *V, SPIRVBasicBlock *BB, return mapValue(V, BI); } - if (dyn_cast(V)) + if (dyn_cast(V)) { + // SPV_KHR_abort: OpAbortKHR is itself a block terminator. If the LLVM IR + // appends a trailing 'unreachable' after the abort call (the canonical + // pattern for noreturn calls), do not emit a second SPIR-V terminator. + if (auto *Last = BB->getTerminateInstr()) + if (Last->getOpCode() == OpAbortKHR) + return mapValue(V, const_cast(Last)); return mapValue(V, BM->addUnreachableInst(BB)); + } if (auto *RI = dyn_cast(V)) { + // SPV_KHR_abort: same suppression as for UnreachableInst above. + if (auto *Last = BB->getTerminateInstr()) + if (Last->getOpCode() == OpAbortKHR) + return mapValue(V, const_cast(Last)); if (auto *RV = RI->getReturnValue()) { if (auto *II = dyn_cast(RV)) { if (II->getIntrinsicID() == Intrinsic::frexp) { @@ -6902,6 +6913,13 @@ LLVMToSPIRVBase::transBuiltinToInstWithoutDecoration(Op OC, CallInst *CI, auto BArgs = transValue(getArguments(CI), BB); return BM->addControlBarrierInst(BArgs[0], BArgs[1], BArgs[2], BB); } break; + case OpAbortKHR: { + BM->getErrorLog().checkError( + BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_abort), + SPIRVEC_RequiresExtension, "SPV_KHR_abort\n"); + auto *Msg = transValue(CI->getArgOperand(0), BB); + return BM->addAbortKHRInst(Msg, BB); + } break; case OpGroupAsyncCopy: { auto BArgs = transValue(getArguments(CI), BB); return BM->addAsyncGroupCopy(BArgs[0], BArgs[1], BArgs[2], BArgs[3], @@ -7592,7 +7610,8 @@ bool runSpirvBackend(Module *M, std::string &Result, std::string &ErrMsg, SPIRV::ExtensionID::SPV_INTEL_function_pointers, SPIRV::ExtensionID::SPV_KHR_shader_clock, SPIRV::ExtensionID::SPV_KHR_cooperative_matrix, - SPIRV::ExtensionID::SPV_KHR_non_semantic_info}; + SPIRV::ExtensionID::SPV_KHR_non_semantic_info, + SPIRV::ExtensionID::SPV_KHR_abort}; // The fallback for the Triple value. static const std::string DefaultTriple = "spirv64-unknown-unknown"; diff --git a/lib/SPIRV/libSPIRV/SPIRVEntry.cpp b/lib/SPIRV/libSPIRV/SPIRVEntry.cpp index 3cb2600cb3..3ab9914745 100644 --- a/lib/SPIRV/libSPIRV/SPIRVEntry.cpp +++ b/lib/SPIRV/libSPIRV/SPIRVEntry.cpp @@ -171,6 +171,7 @@ bool SPIRVEntry::isEndOfBlock() const { case OpReturn: case OpReturnValue: case OpUnreachable: + case OpAbortKHR: return true; default: return false; diff --git a/lib/SPIRV/libSPIRV/SPIRVInstruction.h b/lib/SPIRV/libSPIRV/SPIRVInstruction.h index 6bd3e896db..5ff20a961b 100644 --- a/lib/SPIRV/libSPIRV/SPIRVInstruction.h +++ b/lib/SPIRV/libSPIRV/SPIRVInstruction.h @@ -858,6 +858,54 @@ template class SPIRVInstNoOperand : public SPIRVInstruction { typedef SPIRVInstNoOperand SPIRVReturn; typedef SPIRVInstNoOperand SPIRVUnreachable; +class SPIRVAbortKHR : public SPIRVInstruction { +public: + static const Op OC = OpAbortKHR; + static const SPIRVWord FixedWordCount = 3; + // Complete constructor + SPIRVAbortKHR(SPIRVValue *TheMessage, SPIRVBasicBlock *TheBB) + : SPIRVInstruction(FixedWordCount, OC, TheBB), + MessageTypeId(TheMessage->getType()->getId()), + MessageId(TheMessage->getId()) { + setAttr(); + validate(); + assert(TheBB && "Invalid BB"); + } + // Incomplete constructor + SPIRVAbortKHR() + : SPIRVInstruction(OC), MessageTypeId(SPIRVID_INVALID), + MessageId(SPIRVID_INVALID) { + setAttr(); + } + + SPIRVCapVec getRequiredCapability() const override { + return getVec(CapabilityAbortKHR); + } + + std::optional getRequiredExtension() const override { + return ExtensionID::SPV_KHR_abort; + } + + SPIRVValue *getMessage() const { return getValue(MessageId); } + SPIRVType *getMessageType() const { + return reinterpret_cast(getEntry(MessageTypeId)); + } + + std::vector getOperands() override { + return std::vector(1, getMessage()); + } + +protected: + void setAttr() { + setHasNoId(); + setHasNoType(); + } + _SPIRV_DEF_ENCDEC2(MessageTypeId, MessageId) + void validate() const override { SPIRVInstruction::validate(); } + SPIRVId MessageTypeId; + SPIRVId MessageId; +}; + class SPIRVReturnValue : public SPIRVInstruction { public: static const Op OC = OpReturnValue; diff --git a/lib/SPIRV/libSPIRV/SPIRVModule.cpp b/lib/SPIRV/libSPIRV/SPIRVModule.cpp index f82fbe8d47..53e094d480 100644 --- a/lib/SPIRV/libSPIRV/SPIRVModule.cpp +++ b/lib/SPIRV/libSPIRV/SPIRVModule.cpp @@ -549,6 +549,8 @@ class SPIRVModuleImpl : public SPIRVModule { llvm::MDNode *MD) override; SPIRVInstruction *addAssumeTrueKHRInst(SPIRVValue *Condition, SPIRVBasicBlock *BB) override; + SPIRVInstruction *addAbortKHRInst(SPIRVValue *Message, + SPIRVBasicBlock *BB) override; SPIRVInstruction *addExpectKHRInst(SPIRVType *ResultTy, SPIRVValue *Value, SPIRVValue *ExpectedValue, SPIRVBasicBlock *BB) override; @@ -2031,6 +2033,11 @@ SPIRVInstruction *SPIRVModuleImpl::addAssumeTrueKHRInst(SPIRVValue *Condition, return addInstruction(new SPIRVAssumeTrueKHR(Condition->getId(), BB), BB); } +SPIRVInstruction *SPIRVModuleImpl::addAbortKHRInst(SPIRVValue *Message, + SPIRVBasicBlock *BB) { + return addInstruction(new SPIRVAbortKHR(Message, BB), BB); +} + SPIRVInstruction *SPIRVModuleImpl::addExpectKHRInst(SPIRVType *ResultTy, SPIRVValue *Value, SPIRVValue *ExpectedValue, diff --git a/lib/SPIRV/libSPIRV/SPIRVModule.h b/lib/SPIRV/libSPIRV/SPIRVModule.h index 3960922674..a78f63b858 100644 --- a/lib/SPIRV/libSPIRV/SPIRVModule.h +++ b/lib/SPIRV/libSPIRV/SPIRVModule.h @@ -529,6 +529,8 @@ class SPIRVModule { llvm::MDNode *MD) = 0; virtual SPIRVInstruction *addAssumeTrueKHRInst(SPIRVValue *Condition, SPIRVBasicBlock *BB) = 0; + virtual SPIRVInstruction *addAbortKHRInst(SPIRVValue *Message, + SPIRVBasicBlock *BB) = 0; virtual SPIRVInstruction *addExpectKHRInst(SPIRVType *ResultTy, SPIRVValue *Value, SPIRVValue *ExpectedValue, diff --git a/lib/SPIRV/libSPIRV/SPIRVNameMapEnum.h b/lib/SPIRV/libSPIRV/SPIRVNameMapEnum.h index 61fd5f7c34..f0ade9912d 100644 --- a/lib/SPIRV/libSPIRV/SPIRVNameMapEnum.h +++ b/lib/SPIRV/libSPIRV/SPIRVNameMapEnum.h @@ -658,6 +658,7 @@ template <> inline void SPIRVMap::init() { "SubgroupMatrixMultiplyAccumulateINTEL"); add(CapabilityTernaryBitwiseFunctionINTEL, "TernaryBitwiseFunctionINTEL"); add(CapabilityFMAKHR, "FMAKHR"); + add(CapabilityAbortKHR, "AbortKHR"); // From spirv_internal.hpp add(internal::CapabilityTokenTypeINTEL, "TokenTypeINTEL"); add(internal::CapabilityHWThreadQueryINTEL, "HWThreadQueryINTEL"); diff --git a/lib/SPIRV/libSPIRV/SPIRVOpCodeEnum.h b/lib/SPIRV/libSPIRV/SPIRVOpCodeEnum.h index a0d9ff94aa..436a3ad8d4 100644 --- a/lib/SPIRV/libSPIRV/SPIRVOpCodeEnum.h +++ b/lib/SPIRV/libSPIRV/SPIRVOpCodeEnum.h @@ -355,6 +355,7 @@ _SPIRV_OP(CooperativeMatrixStoreKHR, 4458) _SPIRV_OP(CooperativeMatrixMulAddKHR, 4459) _SPIRV_OP(CooperativeMatrixLengthKHR, 4460) _SPIRV_OP(ReadClockKHR, 5056) +_SPIRV_OP(AbortKHR, 5121) _SPIRV_OP(SubgroupShuffleINTEL, 5571) _SPIRV_OP(SubgroupShuffleDownINTEL, 5572) _SPIRV_OP(SubgroupShuffleUpINTEL, 5573) diff --git a/spirv-headers-tag.conf b/spirv-headers-tag.conf index 41f264189d..e274757960 100644 --- a/spirv-headers-tag.conf +++ b/spirv-headers-tag.conf @@ -1 +1 @@ -9268f3057354a2cb65991ba5f38b16d81e803692 +ad9184e76a66b1001c29db9b0a3e87f646c64de0 diff --git a/test/extensions/KHR/SPV_KHR_abort/abort.ll b/test/extensions/KHR/SPV_KHR_abort/abort.ll new file mode 100644 index 0000000000..d52f316f7b --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort.ll @@ -0,0 +1,38 @@ +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc -o %t.spv --spirv-ext=+SPV_KHR_abort +; RUN: llvm-spirv %t.spv -o %t.spt --to-text +; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV +; RUN: llvm-spirv %t.spv -o %t.rev.bc -r --spirv-target-env=SPV-IR +; RUN: llvm-dis %t.rev.bc -o %t.rev.ll +; RUN: FileCheck < %t.rev.ll %s --check-prefix=CHECK-LLVM + +; RUN: not llvm-spirv %t.bc 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR +; CHECK-ERROR: RequiresExtension: Feature requires the following SPIR-V extension: +; CHECK-ERROR-NEXT: SPV_KHR_abort + +target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64" +target triple = "spir64-unknown-unknown" + +; CHECK-SPIRV: Capability AbortKHR +; CHECK-SPIRV: Extension "SPV_KHR_abort" +; CHECK-SPIRV: TypeInt [[#I32Ty:]] 32 +; CHECK-SPIRV: FunctionParameter [[#I32Ty]] [[#MsgId:]] +; CHECK-SPIRV: AbortKHR [[#I32Ty]] [[#MsgId]] + +; CHECK-LLVM: call spir_func void @{{.*__spirv_AbortKHR.*}}(i32 %{{.*}}){{.*}}#[[#ATTR:]] +; CHECK-LLVM-NEXT: unreachable +; CHECK-LLVM: attributes #[[#ATTR]] = {{{.*}}noreturn{{.*}}} + +define spir_func void @test_abort(i32 %msg) { +entry: + call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %msg) + ret void +} + +declare spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32) + +!opencl.spir.version = !{!0} +!spirv.Source = !{!1} + +!0 = !{i32 1, i32 2} +!1 = !{i32 4, i32 100000} From 9493cdf841fde52314eef80efb1bed98e61c719a Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Mon, 20 Apr 2026 10:21:02 -0700 Subject: [PATCH 2/7] Fix dyn_cast -> isa --- lib/SPIRV/SPIRVWriter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/SPIRV/SPIRVWriter.cpp b/lib/SPIRV/SPIRVWriter.cpp index 257e8b33f4..fbc595631e 100644 --- a/lib/SPIRV/SPIRVWriter.cpp +++ b/lib/SPIRV/SPIRVWriter.cpp @@ -2333,7 +2333,7 @@ LLVMToSPIRVBase::transValueWithoutDecoration(Value *V, SPIRVBasicBlock *BB, return mapValue(V, BI); } - if (dyn_cast(V)) { + if (isa(V)) { // SPV_KHR_abort: OpAbortKHR is itself a block terminator. If the LLVM IR // appends a trailing 'unreachable' after the abort call (the canonical // pattern for noreturn calls), do not emit a second SPIR-V terminator. From 0b02b539d9a8c30b489de02a2ebc5d6302ec5348 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Wed, 22 Apr 2026 09:34:38 -0700 Subject: [PATCH 3/7] Address review comments --- lib/SPIRV/SPIRVWriter.cpp | 24 ++++++++---- lib/SPIRV/libSPIRV/SPIRVInstruction.h | 9 ----- .../SPV_KHR_abort/abort-invalid-arg-count.ll | 37 +++++++++++++++++++ test/extensions/KHR/SPV_KHR_abort/abort.ll | 10 +++++ 4 files changed, 64 insertions(+), 16 deletions(-) create mode 100644 test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count.ll diff --git a/lib/SPIRV/SPIRVWriter.cpp b/lib/SPIRV/SPIRVWriter.cpp index fbc595631e..ae3c0a06c4 100644 --- a/lib/SPIRV/SPIRVWriter.cpp +++ b/lib/SPIRV/SPIRVWriter.cpp @@ -2344,10 +2344,16 @@ LLVMToSPIRVBase::transValueWithoutDecoration(Value *V, SPIRVBasicBlock *BB, } if (auto *RI = dyn_cast(V)) { - // SPV_KHR_abort: same suppression as for UnreachableInst above. - if (auto *Last = BB->getTerminateInstr()) - if (Last->getOpCode() == OpAbortKHR) - return mapValue(V, const_cast(Last)); + // SPV_KHR_abort: OpAbortKHR is itself a SPIR-V block terminator. If the + // LLVM IR appends a trailing `ret void` after the abort call, do not emit + // a second SPIR-V terminator. We deliberately limit this suppression to + // `ret void`: if a non-void function ends with `ret ` after + // OpAbortKHR, fall through so that the value is still translated and the + // user sees a normal SPIR-V validation error rather than silently + // dropping the return value. + if (auto *Last = BB->getTerminateInstr(); + !RI->getReturnValue() && Last && Last->getOpCode() == OpAbortKHR) + return mapValue(V, const_cast(Last)); if (auto *RV = RI->getReturnValue()) { if (auto *II = dyn_cast(RV)) { if (II->getIntrinsicID() == Intrinsic::frexp) { @@ -5142,9 +5148,10 @@ SPIRVValue *LLVMToSPIRVBase::transIntrinsicInst(IntrinsicInst *II, case Intrinsic::invariant_start: case Intrinsic::invariant_end: case Intrinsic::dbg_label: - // llvm.trap and llvm.debugtrap intrinsics are not implemented. But for now - // don't crash. This change is pending the trap/abort intrinsic - // implementation. + // TODO: lower llvm.trap / llvm.ubsantrap / llvm.debugtrap to OpAbortKHR + // (with a null/undefined Message) when the SPV_KHR_abort extension is + // enabled. For now we silently drop these intrinsics so that the translator + // doesn't crash on input that contains them. case Intrinsic::trap: case Intrinsic::ubsantrap: case Intrinsic::debugtrap: @@ -6917,6 +6924,9 @@ LLVMToSPIRVBase::transBuiltinToInstWithoutDecoration(Op OC, CallInst *CI, BM->getErrorLog().checkError( BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_abort), SPIRVEC_RequiresExtension, "SPV_KHR_abort\n"); + BM->getErrorLog().checkError( + CI->arg_size() == 1, SPIRVEC_InvalidInstruction, + "__spirv_AbortKHR must be called with exactly one Message argument\n"); auto *Msg = transValue(CI->getArgOperand(0), BB); return BM->addAbortKHRInst(Msg, BB); } break; diff --git a/lib/SPIRV/libSPIRV/SPIRVInstruction.h b/lib/SPIRV/libSPIRV/SPIRVInstruction.h index 5ff20a961b..bd624dc119 100644 --- a/lib/SPIRV/libSPIRV/SPIRVInstruction.h +++ b/lib/SPIRV/libSPIRV/SPIRVInstruction.h @@ -886,15 +886,6 @@ class SPIRVAbortKHR : public SPIRVInstruction { return ExtensionID::SPV_KHR_abort; } - SPIRVValue *getMessage() const { return getValue(MessageId); } - SPIRVType *getMessageType() const { - return reinterpret_cast(getEntry(MessageTypeId)); - } - - std::vector getOperands() override { - return std::vector(1, getMessage()); - } - protected: void setAttr() { setHasNoId(); diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count.ll b/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count.ll new file mode 100644 index 0000000000..fb0f292d19 --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count.ll @@ -0,0 +1,37 @@ +; Negative tests for the SPIR-V translator's handling of `__spirv_AbortKHR`. +; OpAbortKHR takes exactly one Message operand; calls with any other arity must +; be rejected by the translator with a clear diagnostic rather than producing +; ill-formed SPIR-V. + +; RUN: split-file %s %t + +; RUN: llvm-as %t/no-args.ll -o %t/no-args.bc +; RUN: not llvm-spirv %t/no-args.bc --spirv-ext=+SPV_KHR_abort -o /dev/null 2>&1 | FileCheck %s --check-prefix=CHECK-ARG-COUNT + +; RUN: llvm-as %t/two-args.ll -o %t/two-args.bc +; RUN: not llvm-spirv %t/two-args.bc --spirv-ext=+SPV_KHR_abort -o /dev/null 2>&1 | FileCheck %s --check-prefix=CHECK-ARG-COUNT + +; CHECK-ARG-COUNT: InvalidInstruction +; CHECK-ARG-COUNT: __spirv_AbortKHR must be called with exactly one Message argument + +;--- no-args.ll +target triple = "spir64-unknown-unknown" + +define spir_func void @abort_no_args() { +entry: + call spir_func void @_Z16__spirv_AbortKHRv() + ret void +} + +declare spir_func void @_Z16__spirv_AbortKHRv() + +;--- two-args.ll +target triple = "spir64-unknown-unknown" + +define spir_func void @abort_two_args(i32 %a, i32 %b) { +entry: + call spir_func void @_Z16__spirv_AbortKHRii(i32 %a, i32 %b) + ret void +} + +declare spir_func void @_Z16__spirv_AbortKHRii(i32, i32) diff --git a/test/extensions/KHR/SPV_KHR_abort/abort.ll b/test/extensions/KHR/SPV_KHR_abort/abort.ll index d52f316f7b..dea159fc47 100644 --- a/test/extensions/KHR/SPV_KHR_abort/abort.ll +++ b/test/extensions/KHR/SPV_KHR_abort/abort.ll @@ -5,6 +5,7 @@ ; RUN: llvm-spirv %t.spv -o %t.rev.bc -r --spirv-target-env=SPV-IR ; RUN: llvm-dis %t.rev.bc -o %t.rev.ll ; RUN: FileCheck < %t.rev.ll %s --check-prefix=CHECK-LLVM +; RUN: spirv-val %t.spv ; RUN: not llvm-spirv %t.bc 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR ; CHECK-ERROR: RequiresExtension: Feature requires the following SPIR-V extension: @@ -29,6 +30,15 @@ entry: ret void } +; Same as @test_abort, but with an explicit `unreachable` terminator instead of +; `ret void`. Both forms must lower to a single OpAbortKHR with no trailing +; OpUnreachable / OpReturn. +define spir_func void @test_abort_unreachable(i32 %msg) { +entry: + call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %msg) + unreachable +} + declare spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32) !opencl.spir.version = !{!0} From f5410093c3db79ed40860dc8b9f811758700c543 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Wed, 22 Apr 2026 10:30:06 -0700 Subject: [PATCH 4/7] Restore the getOperands() method --- lib/SPIRV/libSPIRV/SPIRVInstruction.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/SPIRV/libSPIRV/SPIRVInstruction.h b/lib/SPIRV/libSPIRV/SPIRVInstruction.h index bd624dc119..e6fed82563 100644 --- a/lib/SPIRV/libSPIRV/SPIRVInstruction.h +++ b/lib/SPIRV/libSPIRV/SPIRVInstruction.h @@ -886,6 +886,10 @@ class SPIRVAbortKHR : public SPIRVInstruction { return ExtensionID::SPV_KHR_abort; } + std::vector getOperands() override { + return std::vector(1, getValue(MessageId)); + } + protected: void setAttr() { setHasNoId(); From c5de6a4c89a2cdd485ef5744c540f0a4a5173dd3 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Tue, 28 Apr 2026 14:36:51 -0700 Subject: [PATCH 5/7] Add more tests; Ignore LLVM IR instructions after the __spirv_AbortKHR call --- lib/SPIRV/SPIRVWriter.cpp | 34 +++---- lib/SPIRV/libSPIRV/SPIRVInstruction.h | 7 +- .../KHR/SPV_KHR_abort/abort-conditional.ll | 67 +++++++++++++ .../KHR/SPV_KHR_abort/abort-in-kernel.ll | 93 ++++++++++++++++++ .../SPV_KHR_abort/abort-multiple-blocks.ll | 85 ++++++++++++++++ .../abort-post-terminator-suppression.ll | 96 +++++++++++++++++++ test/extensions/KHR/SPV_KHR_abort/abort.ll | 6 +- .../KHR/SPV_KHR_abort/no-abort-unaffected.ll | 88 +++++++++++++++++ 8 files changed, 451 insertions(+), 25 deletions(-) create mode 100644 test/extensions/KHR/SPV_KHR_abort/abort-conditional.ll create mode 100644 test/extensions/KHR/SPV_KHR_abort/abort-in-kernel.ll create mode 100644 test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll create mode 100644 test/extensions/KHR/SPV_KHR_abort/abort-post-terminator-suppression.ll create mode 100644 test/extensions/KHR/SPV_KHR_abort/no-abort-unaffected.ll diff --git a/lib/SPIRV/SPIRVWriter.cpp b/lib/SPIRV/SPIRVWriter.cpp index ae3c0a06c4..896af72ab0 100644 --- a/lib/SPIRV/SPIRVWriter.cpp +++ b/lib/SPIRV/SPIRVWriter.cpp @@ -2333,27 +2333,10 @@ LLVMToSPIRVBase::transValueWithoutDecoration(Value *V, SPIRVBasicBlock *BB, return mapValue(V, BI); } - if (isa(V)) { - // SPV_KHR_abort: OpAbortKHR is itself a block terminator. If the LLVM IR - // appends a trailing 'unreachable' after the abort call (the canonical - // pattern for noreturn calls), do not emit a second SPIR-V terminator. - if (auto *Last = BB->getTerminateInstr()) - if (Last->getOpCode() == OpAbortKHR) - return mapValue(V, const_cast(Last)); + if (isa(V)) return mapValue(V, BM->addUnreachableInst(BB)); - } if (auto *RI = dyn_cast(V)) { - // SPV_KHR_abort: OpAbortKHR is itself a SPIR-V block terminator. If the - // LLVM IR appends a trailing `ret void` after the abort call, do not emit - // a second SPIR-V terminator. We deliberately limit this suppression to - // `ret void`: if a non-void function ends with `ret ` after - // OpAbortKHR, fall through so that the value is still translated and the - // user sees a normal SPIR-V validation error rather than silently - // dropping the return value. - if (auto *Last = BB->getTerminateInstr(); - !RI->getReturnValue() && Last && Last->getOpCode() == OpAbortKHR) - return mapValue(V, const_cast(Last)); if (auto *RV = RI->getReturnValue()) { if (auto *II = dyn_cast(RV)) { if (II->getIntrinsicID() == Intrinsic::frexp) { @@ -6347,6 +6330,15 @@ void LLVMToSPIRVBase::transFunction(Function *I) { SPIRVBasicBlock *BB = static_cast(transValue(&FI, nullptr)); for (auto &BI : FI) { + // SPV_KHR_abort: OpAbortKHR is itself a SPIR-V block terminator. Once a + // basic block has been terminated by OpAbortKHR, any subsequent LLVM IR + // instructions in the same block (typically lifetime intrinsics, a + // trailing `unreachable`, or a `ret`) must not be emitted, otherwise + // the resulting SPIR-V would have instructions after a block terminator + // and fail validation. + if (auto *Last = BB->getTerminateInstr(); + Last && Last->getOpCode() == OpAbortKHR) + break; transValue(&BI, BB, false); } } @@ -6921,9 +6913,9 @@ LLVMToSPIRVBase::transBuiltinToInstWithoutDecoration(Op OC, CallInst *CI, return BM->addControlBarrierInst(BArgs[0], BArgs[1], BArgs[2], BB); } break; case OpAbortKHR: { - BM->getErrorLog().checkError( - BM->isAllowedToUseExtension(ExtensionID::SPV_KHR_abort), - SPIRVEC_RequiresExtension, "SPV_KHR_abort\n"); + if (!BM->checkExtension(ExtensionID::SPV_KHR_abort, + SPIRVEC_RequiresExtension, "SPV_KHR_abort\n")) + return nullptr; BM->getErrorLog().checkError( CI->arg_size() == 1, SPIRVEC_InvalidInstruction, "__spirv_AbortKHR must be called with exactly one Message argument\n"); diff --git a/lib/SPIRV/libSPIRV/SPIRVInstruction.h b/lib/SPIRV/libSPIRV/SPIRVInstruction.h index e6fed82563..a48cf749c5 100644 --- a/lib/SPIRV/libSPIRV/SPIRVInstruction.h +++ b/lib/SPIRV/libSPIRV/SPIRVInstruction.h @@ -862,7 +862,7 @@ class SPIRVAbortKHR : public SPIRVInstruction { public: static const Op OC = OpAbortKHR; static const SPIRVWord FixedWordCount = 3; - // Complete constructor + // Complete constructor. SPIRVAbortKHR(SPIRVValue *TheMessage, SPIRVBasicBlock *TheBB) : SPIRVInstruction(FixedWordCount, OC, TheBB), MessageTypeId(TheMessage->getType()->getId()), @@ -871,7 +871,7 @@ class SPIRVAbortKHR : public SPIRVInstruction { validate(); assert(TheBB && "Invalid BB"); } - // Incomplete constructor + // Incomplete constructor. SPIRVAbortKHR() : SPIRVInstruction(OC), MessageTypeId(SPIRVID_INVALID), MessageId(SPIRVID_INVALID) { @@ -890,12 +890,13 @@ class SPIRVAbortKHR : public SPIRVInstruction { return std::vector(1, getValue(MessageId)); } + _SPIRV_DEF_ENCDEC2(MessageTypeId, MessageId) + protected: void setAttr() { setHasNoId(); setHasNoType(); } - _SPIRV_DEF_ENCDEC2(MessageTypeId, MessageId) void validate() const override { SPIRVInstruction::validate(); } SPIRVId MessageTypeId; SPIRVId MessageId; diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-conditional.ll b/test/extensions/KHR/SPV_KHR_abort/abort-conditional.ll new file mode 100644 index 0000000000..91c1c76728 --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort-conditional.ll @@ -0,0 +1,67 @@ +; Conditional abort — the assert() pattern where only some paths abort. +; Verifies that: +; 1. The abort BB gets OpAbortKHR (not OpUnreachable) +; 2. The non-abort BB is unaffected (still has OpReturn) +; 3. No double terminators in the abort block. + +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc --spirv-ext=+SPV_KHR_abort -o %t.spv +; RUN: llvm-spirv %t.spv -to-text -o %t.spt +; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV + +; Round-trip +; RUN: llvm-spirv -r %t.spv -o %t.rev.bc +; RUN: llvm-dis < %t.rev.bc | FileCheck %s --check-prefix=CHECK-LLVM + +; FIXME: enable the following run when the translator CI is updated to a new +; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; extension. +; RUN: not spirv-val %t.spv + +; ---- SPIR-V with extension ---- +; CHECK-SPIRV-DAG: Extension "SPV_KHR_abort" +; CHECK-SPIRV-DAG: Capability AbortKHR +; CHECK-SPIRV: Function +; +; Entry block: branch conditional +; CHECK-SPIRV: BranchConditional +; +; OK block: normal return +; CHECK-SPIRV: Return +; +; Trap block: abort only, no Unreachable / Return after it +; CHECK-SPIRV: AbortKHR +; CHECK-SPIRV-EMPTY: +; CHECK-SPIRV-NEXT: FunctionEnd + +; ---- Round-trip LLVM IR ---- +; CHECK-LLVM: define spir_func void @assert_like +; CHECK-LLVM: br i1 +; CHECK-LLVM: ret void +; CHECK-LLVM: call spir_func void @{{.*__spirv_AbortKHR.*}}(i32 %{{.*}}){{.*}}#[[#ATTR:]] +; CHECK-LLVM-NEXT: unreachable +; CHECK-LLVM: attributes #[[#ATTR]] = {{{.*}}noreturn{{.*}}} + +target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64" +target triple = "spir64-unknown-unknown" + +define spir_func void @assert_like(i32 %gid, i32 %N, i32 %msg) { +entry: + %cmp = icmp slt i32 %gid, %N + br i1 %cmp, label %ok, label %trap + +ok: + ret void + +trap: + call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %msg) + unreachable +} + +declare spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32) + +!opencl.spir.version = !{!0} +!spirv.Source = !{!1} + +!0 = !{i32 1, i32 2} +!1 = !{i32 4, i32 100000} diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-in-kernel.ll b/test/extensions/KHR/SPV_KHR_abort/abort-in-kernel.ll new file mode 100644 index 0000000000..38ed8c3bcf --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort-in-kernel.ll @@ -0,0 +1,93 @@ +; Kernel function with assert-like abort pattern. Models the real-world HIP +; assert() use case: kernel calls a helper which calls __spirv_AbortKHR. +; +; Verifies that OpAbortKHR works correctly inside callee functions invoked +; from kernel entry points. +; +; Note: the kernel's assert.fail block has FunctionCall + Unreachable +; (the unreachable is after the call, not after an abort — this is correct +; because the abort is inside the callee, not the caller). + +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc --spirv-ext=+SPV_KHR_abort -o %t.spv +; RUN: llvm-spirv %t.spv -to-text -o %t.spt +; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV + +; FIXME: enable the following run when the translator CI is updated to a new +; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; extension. +; RUN: not spirv-val %t.spv + +; Round-trip +; RUN: llvm-spirv -r %t.spv -o %t.rev.bc +; RUN: llvm-dis < %t.rev.bc | FileCheck %s --check-prefix=CHECK-LLVM + +; ---- SPIR-V ---- +; CHECK-SPIRV-DAG: Capability AbortKHR +; CHECK-SPIRV-DAG: Extension "SPV_KHR_abort" +; CHECK-SPIRV-DAG: EntryPoint 6 {{[0-9]+}} "test_kernel" + +; __assert_fail_internal: contains the abort -> OpAbortKHR +; CHECK-SPIRV: Function +; CHECK-SPIRV: AbortKHR +; CHECK-SPIRV: FunctionEnd + +; test_kernel: calls __assert_fail_internal, then unreachable (in caller) +; CHECK-SPIRV: Function +; CHECK-SPIRV: BranchConditional +; CHECK-SPIRV: Return +; CHECK-SPIRV: FunctionCall +; CHECK-SPIRV: Unreachable +; CHECK-SPIRV: FunctionEnd + +; ---- Round-trip ---- +; CHECK-LLVM: define spir_func void @__assert_fail_internal +; CHECK-LLVM: call spir_func void @{{.*__spirv_AbortKHR.*}}(i32 {{.*}}) +; CHECK-LLVM-NEXT: unreachable +; +; CHECK-LLVM: define spir_kernel void @test_kernel + +target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64" +target triple = "spir64-unknown-unknown" + +; Models __assert_fail from device libraries +define spir_func void @__assert_fail_internal(i32 %msg) #0 { +entry: + call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %msg) + unreachable +} + +; Kernel entry point with conditional assert +define spir_kernel void @test_kernel(ptr addrspace(1) %in, i32 %N) #1 + !kernel_arg_addr_space !1 !kernel_arg_access_qual !2 + !kernel_arg_type !3 !kernel_arg_base_type !3 !kernel_arg_type_qual !4 { +entry: + %gid = call spir_func i64 @_Z13get_global_idj(i32 0) + %gid32 = trunc i64 %gid to i32 + %cmp = icmp slt i32 %gid32, %N + br i1 %cmp, label %ok, label %assert.fail + +ok: + ret void + +assert.fail: + call spir_func void @__assert_fail_internal(i32 42) + unreachable +} + +declare spir_func i64 @_Z13get_global_idj(i32) #2 +declare spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32) + +attributes #0 = { noinline noreturn nounwind } +attributes #1 = { nounwind } +attributes #2 = { nounwind } + +!opencl.spir.version = !{!0} +!spirv.Source = !{!5} + +!0 = !{i32 1, i32 2} +!1 = !{i32 1, i32 0} +!2 = !{!"none", !"none"} +!3 = !{!"int*", !"int"} +!4 = !{!"", !""} +!5 = !{i32 4, i32 100000} diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll b/test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll new file mode 100644 index 0000000000..046e70ac3e --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll @@ -0,0 +1,85 @@ +; Multiple basic blocks: some abort, some don't. +; Verifies that: +; 1. Non-abort blocks are completely unaffected (normal terminators preserved) +; 2. Multiple abort blocks in the same function each get their own OpAbortKHR +; 3. No cross-contamination between blocks. + +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc --spirv-ext=+SPV_KHR_abort -o %t.spv +; RUN: llvm-spirv %t.spv -to-text -o %t.spt +; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV + +; FIXME: enable the following run when the translator CI is updated to a new +; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; extension. +; RUN: not spirv-val %t.spv + +; Round-trip +; RUN: llvm-spirv -r %t.spv -o %t.rev.bc +; RUN: llvm-dis < %t.rev.bc | FileCheck %s --check-prefix=CHECK-LLVM + +; ---- SPIR-V checks ---- +; CHECK-SPIRV-DAG: Capability AbortKHR +; CHECK-SPIRV-DAG: Extension "SPV_KHR_abort" + +; Function with multiple paths +; CHECK-SPIRV: Function +; +; Entry: conditional branch +; CHECK-SPIRV: BranchConditional +; +; Work block: second conditional branch +; CHECK-SPIRV: BranchConditional +; +; Return block: normal return +; CHECK-SPIRV: ReturnValue +; +; First abort block +; CHECK-SPIRV: AbortKHR +; +; Second abort block +; CHECK-SPIRV: AbortKHR +; CHECK-SPIRV: FunctionEnd + +; ---- Round-trip ---- +; CHECK-LLVM: define spir_func i32 @multi_abort +; CHECK-LLVM: br i1 +; CHECK-LLVM: br i1 +; CHECK-LLVM: ret i32 +; CHECK-LLVM: call spir_func void @{{.*__spirv_AbortKHR.*}}(i32 {{.*}}) +; CHECK-LLVM-NEXT: unreachable +; CHECK-LLVM: call spir_func void @{{.*__spirv_AbortKHR.*}}(i32 {{.*}}) +; CHECK-LLVM-NEXT: unreachable + +target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64" +target triple = "spir64-unknown-unknown" + +define spir_func i32 @multi_abort(i32 %x, i32 %m1, i32 %m2) { +entry: + %cmp1 = icmp sgt i32 %x, 0 + br i1 %cmp1, label %work, label %err1 + +work: + %result = mul i32 %x, 42 + %cmp2 = icmp slt i32 %result, 1000 + br i1 %cmp2, label %ret, label %err2 + +ret: + ret i32 %result + +err1: + call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %m1) + unreachable + +err2: + call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %m2) + unreachable +} + +declare spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32) + +!opencl.spir.version = !{!0} +!spirv.Source = !{!1} + +!0 = !{i32 1, i32 2} +!1 = !{i32 4, i32 100000} diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-post-terminator-suppression.ll b/test/extensions/KHR/SPV_KHR_abort/abort-post-terminator-suppression.ll new file mode 100644 index 0000000000..cf6f56f27d --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort-post-terminator-suppression.ll @@ -0,0 +1,96 @@ +; Edge case: instructions after the __spirv_AbortKHR call in the same basic +; block. +; +; In real code (e.g. device libraries' __assert_fail), the pattern is: +; call void @__spirv_AbortKHR(i32 %msg) +; ; ... possibly lifetime.end intrinsics ... +; ret void ; or unreachable +; +; OpAbortKHR is itself a SPIR-V block terminator, so all subsequent +; instructions in the same BB must be suppressed to produce valid SPIR-V. +; This test verifies that no instructions appear after OpAbortKHR in any +; function. + +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc --spirv-ext=+SPV_KHR_abort -o %t.spv +; RUN: llvm-spirv %t.spv -to-text -o %t.spt +; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV + +; FIXME: enable the following run when the translator CI is updated to a new +; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; extension. +; RUN: not spirv-val %t.spv + +; Round-trip +; RUN: llvm-spirv -r %t.spv -o %t.rev.bc +; RUN: llvm-dis < %t.rev.bc | FileCheck %s --check-prefix=CHECK-LLVM + +; ---- abort followed by unreachable (common pattern) ---- +; CHECK-SPIRV: Function +; CHECK-SPIRV: AbortKHR +; CHECK-SPIRV-EMPTY: +; CHECK-SPIRV-NEXT: FunctionEnd + +; ---- abort followed by ret void (device library pattern) ---- +; CHECK-SPIRV: Function +; CHECK-SPIRV: AbortKHR +; CHECK-SPIRV-EMPTY: +; CHECK-SPIRV-NEXT: FunctionEnd + +; ---- abort followed by lifetime.end + ret void (full device library pattern) ---- +; CHECK-SPIRV: Function +; CHECK-SPIRV: AbortKHR +; CHECK-SPIRV-EMPTY: +; CHECK-SPIRV-NEXT: FunctionEnd + +; ---- Round-trip: all variants recover __spirv_AbortKHR + unreachable ---- +; CHECK-LLVM: define spir_func void @abort_then_unreachable +; CHECK-LLVM: call spir_func void @{{.*__spirv_AbortKHR.*}}(i32 {{.*}}) +; CHECK-LLVM-NEXT: unreachable +; +; CHECK-LLVM: define spir_func void @abort_then_ret +; CHECK-LLVM: call spir_func void @{{.*__spirv_AbortKHR.*}}(i32 {{.*}}) +; CHECK-LLVM-NEXT: unreachable +; +; CHECK-LLVM: define spir_func void @abort_then_lifetime_ret +; CHECK-LLVM: call spir_func void @{{.*__spirv_AbortKHR.*}}(i32 {{.*}}) +; CHECK-LLVM-NEXT: unreachable + +target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64" +target triple = "spir64-unknown-unknown" + +; Pattern 1: abort + unreachable (standard LLVM pattern after noreturn call) +define spir_func void @abort_then_unreachable(i32 %msg) { +entry: + call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %msg) + unreachable +} + +; Pattern 2: abort + ret void (seen in device library __assert_fail) +define spir_func void @abort_then_ret(i32 %msg) { +entry: + call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %msg) + ret void +} + +; Pattern 3: abort + lifetime.end + ret void (full device library pattern) +define spir_func void @abort_then_lifetime_ret(i32 %msg) { +entry: + %buf = alloca i8, align 1 + call void @llvm.lifetime.start.p0(i64 1, ptr %buf) + call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %msg) + call void @llvm.lifetime.end.p0(i64 1, ptr %buf) + ret void +} + +declare spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32) +declare void @llvm.lifetime.start.p0(i64 immarg, ptr captures(none)) #0 +declare void @llvm.lifetime.end.p0(i64 immarg, ptr captures(none)) #0 + +attributes #0 = { argmemonly nounwind willreturn } + +!opencl.spir.version = !{!0} +!spirv.Source = !{!1} + +!0 = !{i32 1, i32 2} +!1 = !{i32 4, i32 100000} diff --git a/test/extensions/KHR/SPV_KHR_abort/abort.ll b/test/extensions/KHR/SPV_KHR_abort/abort.ll index dea159fc47..8ab6723475 100644 --- a/test/extensions/KHR/SPV_KHR_abort/abort.ll +++ b/test/extensions/KHR/SPV_KHR_abort/abort.ll @@ -5,7 +5,11 @@ ; RUN: llvm-spirv %t.spv -o %t.rev.bc -r --spirv-target-env=SPV-IR ; RUN: llvm-dis %t.rev.bc -o %t.rev.ll ; RUN: FileCheck < %t.rev.ll %s --check-prefix=CHECK-LLVM -; RUN: spirv-val %t.spv + +; FIXME: enable the following run when the translator CI is updated to a new +; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; extension. +; RUN: not spirv-val %t.spv ; RUN: not llvm-spirv %t.bc 2>&1 | FileCheck %s --check-prefix=CHECK-ERROR ; CHECK-ERROR: RequiresExtension: Feature requires the following SPIR-V extension: diff --git a/test/extensions/KHR/SPV_KHR_abort/no-abort-unaffected.ll b/test/extensions/KHR/SPV_KHR_abort/no-abort-unaffected.ll new file mode 100644 index 0000000000..6763f043ab --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/no-abort-unaffected.ll @@ -0,0 +1,88 @@ +; Sanity check: enabling SPV_KHR_abort does not affect functions that don't +; call __spirv_AbortKHR. Normal terminators (OpReturn, OpReturnValue, +; OpBranch, OpUnreachable) must be preserved. + +; RUN: llvm-as %s -o %t.bc +; RUN: llvm-spirv %t.bc --spirv-ext=+SPV_KHR_abort -o %t.spv +; RUN: llvm-spirv %t.spv -to-text -o %t.spt +; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV + +; Validate SPIR-V +; RUN: spirv-val %t.spv + +; Round-trip must be lossless for non-abort code +; RUN: llvm-spirv -r %t.spv -o %t.rev.bc +; RUN: llvm-dis < %t.rev.bc | FileCheck %s --check-prefix=CHECK-LLVM + +; No AbortKHR anywhere — only non-abort functions +; CHECK-SPIRV-NOT: AbortKHR + +; Normal void return preserved +; CHECK-SPIRV: Function +; CHECK-SPIRV: Return +; CHECK-SPIRV: FunctionEnd + +; Normal value return preserved +; CHECK-SPIRV: Function +; CHECK-SPIRV: ReturnValue +; CHECK-SPIRV: FunctionEnd + +; Unreachable preserved (not suppressed when no abort precedes it) +; CHECK-SPIRV: Function +; CHECK-SPIRV: Unreachable +; CHECK-SPIRV: FunctionEnd + +; Branch preserved +; CHECK-SPIRV: Function +; CHECK-SPIRV: BranchConditional +; CHECK-SPIRV: ReturnValue +; CHECK-SPIRV: ReturnValue +; CHECK-SPIRV: FunctionEnd + +; ---- Round-trip ---- +; CHECK-LLVM: define spir_func void @void_return +; CHECK-LLVM: ret void +; +; CHECK-LLVM: define spir_func i32 @value_return +; CHECK-LLVM: ret i32 +; +; CHECK-LLVM: define spir_func void @plain_unreachable +; CHECK-LLVM: unreachable +; +; CHECK-LLVM: define spir_func i32 @branched + +target datalayout = "e-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-n8:16:32:64" +target triple = "spir64-unknown-unknown" + +define spir_func void @void_return() { +entry: + ret void +} + +define spir_func i32 @value_return(i32 %x) { +entry: + %r = add i32 %x, 1 + ret i32 %r +} + +define spir_func void @plain_unreachable() { +entry: + unreachable +} + +define spir_func i32 @branched(i1 %cond, i32 %a, i32 %b) { +entry: + br i1 %cond, label %t, label %f + +t: + ret i32 %a + +f: + ret i32 %b +} + +!opencl.spir.version = !{!0} +!spirv.Source = !{!1} + +!0 = !{i32 1, i32 2} +!1 = !{i32 4, i32 100000} From b80d64e26ea267728643db13f315cdc335666d27 Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Wed, 29 Apr 2026 07:41:45 -0700 Subject: [PATCH 6/7] Split invalid-arg-count test to avoid split-file dependency The CI environment does not provide the split-file utility, causing the abort-invalid-arg-count.ll test to fail. Split it into two standalone tests that exercise the no-args and two-args negative cases independently, removing the split-file requirement. --- .../abort-invalid-arg-count-no-args.ll | 18 +++++++++ .../abort-invalid-arg-count-two-args.ll | 18 +++++++++ .../SPV_KHR_abort/abort-invalid-arg-count.ll | 37 ------------------- 3 files changed, 36 insertions(+), 37 deletions(-) create mode 100644 test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count-no-args.ll create mode 100644 test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count-two-args.ll delete mode 100644 test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count.ll diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count-no-args.ll b/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count-no-args.ll new file mode 100644 index 0000000000..e14b84cf69 --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count-no-args.ll @@ -0,0 +1,18 @@ +; Negative test: OpAbortKHR takes exactly one Message operand; a call to +; `__spirv_AbortKHR` with no arguments must be rejected by the translator. + +; RUN: llvm-as %s -o %t.bc +; RUN: not llvm-spirv %t.bc --spirv-ext=+SPV_KHR_abort -o /dev/null 2>&1 | FileCheck %s + +; CHECK: InvalidInstruction +; CHECK: __spirv_AbortKHR must be called with exactly one Message argument + +target triple = "spir64-unknown-unknown" + +define spir_func void @abort_no_args() { +entry: + call spir_func void @_Z16__spirv_AbortKHRv() + ret void +} + +declare spir_func void @_Z16__spirv_AbortKHRv() diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count-two-args.ll b/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count-two-args.ll new file mode 100644 index 0000000000..fbd9f45b85 --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count-two-args.ll @@ -0,0 +1,18 @@ +; Negative test: OpAbortKHR takes exactly one Message operand; a call to +; `__spirv_AbortKHR` with two arguments must be rejected by the translator. + +; RUN: llvm-as %s -o %t.bc +; RUN: not llvm-spirv %t.bc --spirv-ext=+SPV_KHR_abort -o /dev/null 2>&1 | FileCheck %s + +; CHECK: InvalidInstruction +; CHECK: __spirv_AbortKHR must be called with exactly one Message argument + +target triple = "spir64-unknown-unknown" + +define spir_func void @abort_two_args(i32 %a, i32 %b) { +entry: + call spir_func void @_Z16__spirv_AbortKHRii(i32 %a, i32 %b) + ret void +} + +declare spir_func void @_Z16__spirv_AbortKHRii(i32, i32) diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count.ll b/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count.ll deleted file mode 100644 index fb0f292d19..0000000000 --- a/test/extensions/KHR/SPV_KHR_abort/abort-invalid-arg-count.ll +++ /dev/null @@ -1,37 +0,0 @@ -; Negative tests for the SPIR-V translator's handling of `__spirv_AbortKHR`. -; OpAbortKHR takes exactly one Message operand; calls with any other arity must -; be rejected by the translator with a clear diagnostic rather than producing -; ill-formed SPIR-V. - -; RUN: split-file %s %t - -; RUN: llvm-as %t/no-args.ll -o %t/no-args.bc -; RUN: not llvm-spirv %t/no-args.bc --spirv-ext=+SPV_KHR_abort -o /dev/null 2>&1 | FileCheck %s --check-prefix=CHECK-ARG-COUNT - -; RUN: llvm-as %t/two-args.ll -o %t/two-args.bc -; RUN: not llvm-spirv %t/two-args.bc --spirv-ext=+SPV_KHR_abort -o /dev/null 2>&1 | FileCheck %s --check-prefix=CHECK-ARG-COUNT - -; CHECK-ARG-COUNT: InvalidInstruction -; CHECK-ARG-COUNT: __spirv_AbortKHR must be called with exactly one Message argument - -;--- no-args.ll -target triple = "spir64-unknown-unknown" - -define spir_func void @abort_no_args() { -entry: - call spir_func void @_Z16__spirv_AbortKHRv() - ret void -} - -declare spir_func void @_Z16__spirv_AbortKHRv() - -;--- two-args.ll -target triple = "spir64-unknown-unknown" - -define spir_func void @abort_two_args(i32 %a, i32 %b) { -entry: - call spir_func void @_Z16__spirv_AbortKHRii(i32 %a, i32 %b) - ret void -} - -declare spir_func void @_Z16__spirv_AbortKHRii(i32, i32) From 03609385efbbaedfe27917df62890914e7dd34dd Mon Sep 17 00:00:00 2001 From: Victor Mustya Date: Thu, 30 Apr 2026 08:15:28 -0700 Subject: [PATCH 7/7] Apply suggestions from the review --- lib/SPIRV/SPIRVReader.cpp | 2 +- .../KHR/SPV_KHR_abort/abort-conditional.ll | 2 +- .../KHR/SPV_KHR_abort/abort-in-kernel.ll | 6 +++--- .../SPV_KHR_abort/abort-multiple-blocks.ll | 21 ++++++++++++++----- .../abort-post-terminator-suppression.ll | 2 +- test/extensions/KHR/SPV_KHR_abort/abort.ll | 2 +- .../KHR/SPV_KHR_abort/no-abort-unaffected.ll | 5 +---- 7 files changed, 24 insertions(+), 16 deletions(-) diff --git a/lib/SPIRV/SPIRVReader.cpp b/lib/SPIRV/SPIRVReader.cpp index cc065e34e3..fe28aa9e07 100644 --- a/lib/SPIRV/SPIRVReader.cpp +++ b/lib/SPIRV/SPIRVReader.cpp @@ -1944,7 +1944,7 @@ Value *SPIRVToLLVM::transValueWithoutDecoration(SPIRVValue *BV, Function *F, // to the SPIR-V friendly builtin __spirv_AbortKHR followed by an // 'unreachable' terminator. auto *AbortInst = - transSPIRVBuiltinFromInst(static_cast(BV), BB); + transSPIRVBuiltinFromInst(static_cast(BV), BB); if (auto *Call = dyn_cast(AbortInst)) { Call->setDoesNotReturn(); if (auto *Callee = Call->getCalledFunction()) diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-conditional.ll b/test/extensions/KHR/SPV_KHR_abort/abort-conditional.ll index 91c1c76728..d6dc7964f2 100644 --- a/test/extensions/KHR/SPV_KHR_abort/abort-conditional.ll +++ b/test/extensions/KHR/SPV_KHR_abort/abort-conditional.ll @@ -14,7 +14,7 @@ ; RUN: llvm-dis < %t.rev.bc | FileCheck %s --check-prefix=CHECK-LLVM ; FIXME: enable the following run when the translator CI is updated to a new -; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; version of the SPIR-V Tools that includes the support for the SPV_KHR_abort ; extension. ; RUN: not spirv-val %t.spv diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-in-kernel.ll b/test/extensions/KHR/SPV_KHR_abort/abort-in-kernel.ll index 38ed8c3bcf..efc06d1594 100644 --- a/test/extensions/KHR/SPV_KHR_abort/abort-in-kernel.ll +++ b/test/extensions/KHR/SPV_KHR_abort/abort-in-kernel.ll @@ -14,7 +14,7 @@ ; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV ; FIXME: enable the following run when the translator CI is updated to a new -; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; version of the SPIR-V Tools that includes the support for the SPV_KHR_abort ; extension. ; RUN: not spirv-val %t.spv @@ -25,7 +25,7 @@ ; ---- SPIR-V ---- ; CHECK-SPIRV-DAG: Capability AbortKHR ; CHECK-SPIRV-DAG: Extension "SPV_KHR_abort" -; CHECK-SPIRV-DAG: EntryPoint 6 {{[0-9]+}} "test_kernel" +; CHECK-SPIRV-DAG: EntryPoint 6 [[#KernelId:]] "test_kernel" ; __assert_fail_internal: contains the abort -> OpAbortKHR ; CHECK-SPIRV: Function @@ -33,7 +33,7 @@ ; CHECK-SPIRV: FunctionEnd ; test_kernel: calls __assert_fail_internal, then unreachable (in caller) -; CHECK-SPIRV: Function +; CHECK-SPIRV: Function {{.*}} [[#KernelId]] ; CHECK-SPIRV: BranchConditional ; CHECK-SPIRV: Return ; CHECK-SPIRV: FunctionCall diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll b/test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll index 046e70ac3e..6dec9ea2b5 100644 --- a/test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll +++ b/test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll @@ -10,7 +10,7 @@ ; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV ; FIXME: enable the following run when the translator CI is updated to a new -; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; version of the SPIR-V Tools that includes the support for the SPV_KHR_abort ; extension. ; RUN: not spirv-val %t.spv @@ -22,22 +22,33 @@ ; CHECK-SPIRV-DAG: Capability AbortKHR ; CHECK-SPIRV-DAG: Extension "SPV_KHR_abort" +; CHECK-SPIRV: Name [[#LabelEntry:]] "entry" +; CHECK-SPIRV: Name [[#LabelWork:]] "work" +; CHECK-SPIRV: Name [[#LabelRet:]] "ret" +; CHECK-SPIRV: Name [[#LabelErr1:]] "err1" +; CHECK-SPIRV: Name [[#LabelErr2:]] "err2" + ; Function with multiple paths ; CHECK-SPIRV: Function ; -; Entry: conditional branch -; CHECK-SPIRV: BranchConditional +; Entry: conditional branch to %work / %err1 +; CHECK-SPIRV: Label [[#LabelEntry]] +; CHECK-SPIRV: BranchConditional [[#]] [[#LabelWork]] [[#LabelErr1]] ; -; Work block: second conditional branch -; CHECK-SPIRV: BranchConditional +; Work block: second conditional branch to %ret / %err2 +; CHECK-SPIRV: Label [[#LabelWork]] +; CHECK-SPIRV: BranchConditional [[#]] [[#LabelRet]] [[#LabelErr2]] ; ; Return block: normal return +; CHECK-SPIRV: Label [[#LabelRet]] ; CHECK-SPIRV: ReturnValue ; ; First abort block +; CHECK-SPIRV: Label [[#LabelErr1]] ; CHECK-SPIRV: AbortKHR ; ; Second abort block +; CHECK-SPIRV: Label [[#LabelErr2]] ; CHECK-SPIRV: AbortKHR ; CHECK-SPIRV: FunctionEnd diff --git a/test/extensions/KHR/SPV_KHR_abort/abort-post-terminator-suppression.ll b/test/extensions/KHR/SPV_KHR_abort/abort-post-terminator-suppression.ll index cf6f56f27d..b9fab11697 100644 --- a/test/extensions/KHR/SPV_KHR_abort/abort-post-terminator-suppression.ll +++ b/test/extensions/KHR/SPV_KHR_abort/abort-post-terminator-suppression.ll @@ -17,7 +17,7 @@ ; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV ; FIXME: enable the following run when the translator CI is updated to a new -; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; version of the SPIR-V Tools that includes the support for the SPV_KHR_abort ; extension. ; RUN: not spirv-val %t.spv diff --git a/test/extensions/KHR/SPV_KHR_abort/abort.ll b/test/extensions/KHR/SPV_KHR_abort/abort.ll index 8ab6723475..5cf41287e7 100644 --- a/test/extensions/KHR/SPV_KHR_abort/abort.ll +++ b/test/extensions/KHR/SPV_KHR_abort/abort.ll @@ -7,7 +7,7 @@ ; RUN: FileCheck < %t.rev.ll %s --check-prefix=CHECK-LLVM ; FIXME: enable the following run when the translator CI is updated to a new -; verion of the SPIR-V Tools that includes the support for the SPV_KHR_abort +; version of the SPIR-V Tools that includes the support for the SPV_KHR_abort ; extension. ; RUN: not spirv-val %t.spv diff --git a/test/extensions/KHR/SPV_KHR_abort/no-abort-unaffected.ll b/test/extensions/KHR/SPV_KHR_abort/no-abort-unaffected.ll index 6763f043ab..7bd0fef008 100644 --- a/test/extensions/KHR/SPV_KHR_abort/no-abort-unaffected.ll +++ b/test/extensions/KHR/SPV_KHR_abort/no-abort-unaffected.ll @@ -5,7 +5,7 @@ ; RUN: llvm-as %s -o %t.bc ; RUN: llvm-spirv %t.bc --spirv-ext=+SPV_KHR_abort -o %t.spv ; RUN: llvm-spirv %t.spv -to-text -o %t.spt -; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV +; RUN: FileCheck < %t.spt %s --check-prefix=CHECK-SPIRV --implicit-check-not AbortKHR ; Validate SPIR-V ; RUN: spirv-val %t.spv @@ -14,9 +14,6 @@ ; RUN: llvm-spirv -r %t.spv -o %t.rev.bc ; RUN: llvm-dis < %t.rev.bc | FileCheck %s --check-prefix=CHECK-LLVM -; No AbortKHR anywhere — only non-abort functions -; CHECK-SPIRV-NOT: AbortKHR - ; Normal void return preserved ; CHECK-SPIRV: Function ; CHECK-SPIRV: Return