-
Notifications
You must be signed in to change notification settings - Fork 271
Implement SPV_KHR_abort extension #3691
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
d209681
9493cdf
0b02b53
f541009
c5de6a4
b80d64e
0360938
e15f699
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2343,7 +2343,7 @@ LLVMToSPIRVBase::transValueWithoutDecoration(Value *V, SPIRVBasicBlock *BB, | |
| return mapValue(V, BI); | ||
| } | ||
|
|
||
| if (dyn_cast<UnreachableInst>(V)) | ||
| if (isa<UnreachableInst>(V)) | ||
| return mapValue(V, BM->addUnreachableInst(BB)); | ||
|
|
||
| if (auto *RI = dyn_cast<ReturnInst>(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 | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could we do this in this patch too? Seems very related.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @maarquitos14 Maybe not to overload this PR, we can follow-up in a separate one (together with Reader part update for default/non-friendly IR path). What do you think?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I won't block this PR for this. If you want to follow-up in a separate PR, go for it. |
||
| // (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<SPIRVBasicBlock *>(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); | ||
|
vmaksimo marked this conversation as resolved.
|
||
| 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"; | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we split in two paths? This one for spirv-friendly IR, and another one for non-friendly which would translate into... llvm.trap maybe?