[spirv-reader][ir] Support `OpKill`
Translate an `OpKill` SPIR-V instruction into a `discard; ret` IR
instructions. The semantics of `OpKill` don't currently exist in WGSL,
the `discard` instruction is more like `OpDemoteToHelperInvocation`. The
`discard; ret` is similar, although not completely the same, but it's as
close as we can currently get.
Bug: 391487196
Change-Id: I8abd5526b866e74f90a3c5cba06cf2001837ab28
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/225094
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/spirv/reader/parser/misc_test.cc b/src/tint/lang/spirv/reader/parser/misc_test.cc
index d814c5e..548223b 100644
--- a/src/tint/lang/spirv/reader/parser/misc_test.cc
+++ b/src/tint/lang/spirv/reader/parser/misc_test.cc
@@ -378,5 +378,137 @@
)");
}
+TEST_F(SpirvParserTest, OpKill_TopLevel) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ %void = OpTypeVoid
+ %ep_type = OpTypeFunction %void
+ %main = OpFunction %void None %ep_type
+ %10 = OpLabel
+ OpKill
+ OpFunctionEnd)",
+ R"(
+%main = @fragment func():void {
+ $B1: {
+ discard
+ ret
+ }
+}
+)");
+}
+
+// TODO(dsinclair): Requires OpSelectionMerge and OpBranchConditional
+TEST_F(SpirvParserTest, DISABLED_OpKill_InsideIf) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %ep_type = OpTypeFunction %void
+ %true = OpConstantTrue %bool
+ %main = OpFunction %void None %ep_type
+ %10 = OpLabel
+ OpSelectionMerge %99 None
+ OpBranchConditional %true %20 %99
+ %20 = OpLabel
+ OpKill
+ %99 = OpLabel
+ OpKill
+ OpFunctionEnd
+ )",
+ R"(
+%main = @fragment func():void {
+ $B1: {
+ %2:void = if true [t: $B2] {
+ discard
+ ret
+ }
+ discard
+ ret
+ }
+}
+)");
+}
+
+// TODO(dsinclair): Requires OpBranch
+TEST_F(SpirvParserTest, DISABLED_OpKill_InsideLoop) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %ep_type = OpTypeFunction %void
+ %true = OpConstantTrue %bool
+ %main = OpFunction %void None %ep_type
+ %10 = OpLabel
+ OpBranch %20
+ %20 = OpLabel
+ OpLoopMerge %99 %80 None
+ OpBranchConditional %true %30 %30
+ %30 = OpLabel
+ OpKill
+ %80 = OpLabel
+ OpBranch %20
+ %99 = OpLabel
+ OpKill
+ OpFunctionEnd
+ )",
+ R"(
+%main = @fragment func():void {
+ $B1: {
+ %2:void = loop %true []
+ discard
+ ret
+ }
+ discard
+ ret
+ }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, OpKill_InNonVoidFunction) {
+ EXPECT_IR(R"(
+ OpCapability Shader
+ OpMemoryModel Logical GLSL450
+ OpEntryPoint Fragment %main "main"
+ OpExecutionMode %main OriginUpperLeft
+ %void = OpTypeVoid
+ %bool = OpTypeBool
+ %ep_type = OpTypeFunction %void
+ %boolfn = OpTypeFunction %bool
+ %200 = OpFunction %bool None %boolfn
+ %210 = OpLabel
+ OpKill
+ OpFunctionEnd
+ %main = OpFunction %void None %ep_type
+ %10 = OpLabel
+ %11 = OpFunctionCall %bool %200
+ OpReturn
+ OpFunctionEnd
+ )",
+ R"(
+%1 = func():bool {
+ $B1: {
+ discard
+ ret false
+ }
+}
+%main = @fragment func():void {
+ $B2: {
+ %3:bool = call %1
+ ret
+ }
+}
+)");
+}
+
} // namespace
} // namespace tint::spirv::reader
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 86edda6..b73f9e6 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -113,8 +113,7 @@
}
EmitFunctions();
-
- EmitEntryPoints();
+ EmitEntryPointAttributes();
// TODO(crbug.com/tint/1907): Handle annotation instructions.
// TODO(crbug.com/tint/1907): Handle names.
@@ -490,7 +489,7 @@
}
/// Emit entry point attributes.
- void EmitEntryPoints() {
+ void EmitEntryPointAttributes() {
// Handle OpEntryPoint declarations.
for (auto& entry_point : spirv_context_->module()->entry_points()) {
auto model = entry_point.GetSingleWordInOperand(0);
@@ -617,6 +616,9 @@
case spv::Op::OpUnreachable:
EmitWithoutResult(b_.Unreachable());
break;
+ case spv::Op::OpKill:
+ EmitKill(inst);
+ break;
default:
TINT_UNIMPLEMENTED()
<< "unhandled SPIR-V instruction: " << static_cast<uint32_t>(inst.opcode());
@@ -624,6 +626,27 @@
}
}
+ /// @param inst the SPIR-V instruction
+ /// Note: This isn't technically correct, but there is no `kill` equivalent in WGSL. The closets
+ /// we have is `discard` which maps to `OpDemoteToHelperInvocation` in SPIR-V.
+ void EmitKill([[maybe_unused]] const spvtools::opt::Instruction& inst) {
+ EmitWithoutResult(b_.Discard());
+
+ // An `OpKill` is a terminator in SPIR-V. `discard` is not a terminator in WGSL. After the
+ // `discard` we inject a `return` for the current function. This is similar in spirit to
+ // what `OpKill` does although not totally correct (i.e. we don't early return from calling
+ // functions, just the function where `OpKill` was emitted. There are also limited places in
+ // which `OpKill` can be used. So, we don't have to worry about it in a `continuing` block
+ // because the continuing must end with a branching terminator which `OpKill` does not
+ // branch.
+ if (current_function_->ReturnType()->Is<core::type::Void>()) {
+ EmitWithoutResult(b_.Return(current_function_));
+ } else {
+ EmitWithoutResult(
+ b_.Return(current_function_, b_.Zero(current_function_->ReturnType())));
+ }
+ }
+
/// @param inst the SPIR-V instruction for OpCopyObject
void EmitCopyObject(const spvtools::opt::Instruction& inst) {
// Make the result Id a pointer to the original copied value.