Implement SPV_KHR_abort extension#3691
Conversation
The SPV_KHR_abort extension introduces the `OpAbortKHR` instruction, allowing shaders to terminate execution early. Assisted-by: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
can we have spirv-val RUN lines here?
also, please add a test case where unreachable path is also tested:
define spir_func void @test_abort_unreachable(i32 %msg) {
entry:
call spir_func void @_Z16__spirv_AbortKHRIiEvT_(i32 %msg)
unreachable
}
There was a problem hiding this comment.
can we have
spirv-valRUN lines here?
@vmaksimo , the spirv-val tool used for the CI is too old to recognize the SPV_KHR_abort extension. However, locally-built top of trunk version works correctly. Should we disable the spirv-val checks until the CI is updated?
There was a problem hiding this comment.
Ouch, I was afraid of this.
Yes please, you may put smth like RUN: not spirv-val ; TODO: remove "not" once SPIR-V tools are updated so the test will start to fail in CI once tools get updated.
| if (auto *Last = BB->getTerminateInstr()) | ||
| if (Last->getOpCode() == OpAbortKHR) | ||
| return mapValue(V, const_cast<SPIRVInstruction *>(Last)); |
There was a problem hiding this comment.
if a non-void function has call @__spirv_AbortKHR the return value is silently dropped.
maybe this needs to be moved after if (auto *RV = RI->getReturnValue()) block? not sure though what LLVM semantics is expected here
The clang-tidy fails as follows: The reported issue is that the I don't really want to deviate the style other instructions are implemented. So, I believe, that either all the instructions should be changed by a separate commit, or the clang-tidy error should be suppressed. @vmaksimo, what would you suggest? |
| // 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 |
There was a problem hiding this comment.
Could we do this in this patch too? Seems very related.
There was a problem hiding this comment.
@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?
| public: | ||
| static const Op OC = OpAbortKHR; | ||
| static const SPIRVWord FixedWordCount = 3; | ||
| // Complete constructor |
There was a problem hiding this comment.
| // Complete constructor | |
| // Complete constructor. |
| validate(); | ||
| assert(TheBB && "Invalid BB"); | ||
| } | ||
| // Incomplete constructor |
There was a problem hiding this comment.
| // Incomplete constructor | |
| // Incomplete constructor. |
| if (isa<UnreachableInst>(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<SPIRVInstruction *>(Last)); | ||
| return mapValue(V, BM->addUnreachableInst(BB)); | ||
| } | ||
|
|
||
| if (auto *RI = dyn_cast<ReturnInst>(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 <value>` 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<SPIRVInstruction *>(Last)); |
There was a problem hiding this comment.
I am not sure this is enough to handle all the cases. For example:
call void @llvm.trap()/__spirv_AbortKHR
cal void @llvm.lifetime.end()
ret void
This would be invalid, because we would have an instruction after a terminator. As far as I can tell, nothing would prevent translating the call to @llvm.lifetime.end() after the OpAbortKHR, which is a terminator. Am I right?
| } | ||
|
|
||
| case OpAbortKHR: { | ||
| // OpAbortKHR is a SPIR-V block terminator. In LLVM IR, model it as a call |
There was a problem hiding this comment.
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?
@maarquitos14, sure! I'd really appreciate that. |
You can find them in maarquitos14@fe51642. I think all of them assume llvm.trap gets translated to OpAbortKHR and back, but still I think they can be useful. |
@vmustya If can - we should not introduce new errors, and I believe this is the case. So please just put encode/decode to public - we'll take a look at other instructions separately. |
The SPV_KHR_abort extension introduces the
OpAbortKHRinstruction,allowing shaders to terminate execution early.
Assisted-by: Claude Opus 4.7 noreply@anthropic.com