diff --git a/include/LLVMSPIRVExtensions.inc b/include/LLVMSPIRVExtensions.inc index e60e723fe8..23f3bf9722 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 f5e5635ed3..a4c852138f 100644 --- a/lib/SPIRV/SPIRVReader.cpp +++ b/lib/SPIRV/SPIRVReader.cpp @@ -1951,6 +1951,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 8575a9b501..32bcb5ef38 100644 --- a/lib/SPIRV/SPIRVWriter.cpp +++ b/lib/SPIRV/SPIRVWriter.cpp @@ -2343,7 +2343,7 @@ LLVMToSPIRVBase::transValueWithoutDecoration(Value *V, SPIRVBasicBlock *BB, return mapValue(V, BI); } - if (dyn_cast(V)) + if (isa(V)) return mapValue(V, BM->addUnreachableInst(BB)); if (auto *RI = dyn_cast(V)) { @@ -5141,9 +5141,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: @@ -6337,6 +6338,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); } } @@ -6910,6 +6920,16 @@ 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: { + 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"); + 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], @@ -7600,7 +7620,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 bfc3256beb..2e09f8963b 100644 --- a/lib/SPIRV/libSPIRV/SPIRVInstruction.h +++ b/lib/SPIRV/libSPIRV/SPIRVInstruction.h @@ -858,6 +858,50 @@ 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; + } + + std::vector getOperands() override { + return std::vector(1, getValue(MessageId)); + } + + _SPIRV_DEF_ENCDEC2(MessageTypeId, MessageId) + +protected: + void setAttr() { + setHasNoId(); + setHasNoType(); + } + 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 72a495a0cd..fe1506fc63 100644 --- a/lib/SPIRV/libSPIRV/SPIRVNameMapEnum.h +++ b/lib/SPIRV/libSPIRV/SPIRVNameMapEnum.h @@ -660,6 +660,7 @@ template <> inline void SPIRVMap::init() { add(CapabilityTernaryBitwiseFunctionINTEL, "TernaryBitwiseFunctionINTEL"); add(CapabilityFMAKHR, "FMAKHR"); add(CapabilityPredicatedIOINTEL, "PredicatedIOINTEL"); + 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 b2929f0cd1..6e57f2e13a 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/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..d6dc7964f2 --- /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 +; version 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..efc06d1594 --- /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 +; version 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 [[#KernelId:]] "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 {{.*}} [[#KernelId]] +; 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-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-multiple-blocks.ll b/test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll new file mode 100644 index 0000000000..6dec9ea2b5 --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort-multiple-blocks.ll @@ -0,0 +1,96 @@ +; 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 +; version 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" + +; 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 to %work / %err1 +; CHECK-SPIRV: Label [[#LabelEntry]] +; CHECK-SPIRV: BranchConditional [[#]] [[#LabelWork]] [[#LabelErr1]] +; +; 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 + +; ---- 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..b9fab11697 --- /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 +; version 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 new file mode 100644 index 0000000000..5cf41287e7 --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/abort.ll @@ -0,0 +1,52 @@ +; 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 + +; FIXME: enable the following run when the translator CI is updated to a new +; version 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: +; 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 +} + +; 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} +!spirv.Source = !{!1} + +!0 = !{i32 1, i32 2} +!1 = !{i32 4, i32 100000} 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..7bd0fef008 --- /dev/null +++ b/test/extensions/KHR/SPV_KHR_abort/no-abort-unaffected.ll @@ -0,0 +1,85 @@ +; 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 --implicit-check-not AbortKHR + +; 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 + +; 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}