Add tooling support for SPV_KHR_maximal_reconvergence (#5542)
* Validation for SPV_KHR_maximal_reconvergence
* Add pass to add/remove maximal reconvergence execution mode
---------
Co-authored-by: David Neto <dneto@google.com>
diff --git a/Android.mk b/Android.mk
index afa0403..3cf3f19 100644
--- a/Android.mk
+++ b/Android.mk
@@ -157,6 +157,7 @@
source/opt/loop_utils.cpp \
source/opt/mem_pass.cpp \
source/opt/merge_return_pass.cpp \
+ source/opt/modify_maximal_reconvergence.cpp \
source/opt/module.cpp \
source/opt/optimizer.cpp \
source/opt/pass.cpp \
diff --git a/BUILD.gn b/BUILD.gn
index 9ff36d8..7c42a99 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -737,6 +737,8 @@
"source/opt/mem_pass.h",
"source/opt/merge_return_pass.cpp",
"source/opt/merge_return_pass.h",
+ "source/opt/modify_maximal_reconvergence.cpp",
+ "source/opt/modify_maximal_reconvergence.h",
"source/opt/module.cpp",
"source/opt/module.h",
"source/opt/null_pass.h",
diff --git a/DEPS b/DEPS
index 866f98e..7ba5521 100644
--- a/DEPS
+++ b/DEPS
@@ -13,7 +13,7 @@
'protobuf_revision': 'v21.12',
're2_revision': '264e71e88e1c8a4b5ec326e70e9cf1d476f58a58',
- 'spirv_headers_revision': '7b0309708da5126b89e4ce6f19835f36dc912f2f',
+ 'spirv_headers_revision': 'ae6a8b39717523d96683bc0d20b541944e28072f',
}
deps = {
diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp
index bc4f5be..926e438 100644
--- a/include/spirv-tools/optimizer.hpp
+++ b/include/spirv-tools/optimizer.hpp
@@ -1003,6 +1003,10 @@
// OpBeginInterlockInvocationEXT and one OpEndInterlockInvocationEXT, in that
// order.
Optimizer::PassToken CreateInvocationInterlockPlacementPass();
+
+// Creates a pass to add/remove maximal reconvergence execution mode.
+// This pass either adds or removes maximal reconvergence from all entry points.
+Optimizer::PassToken CreateModifyMaximalReconvergencePass(bool add);
} // namespace spvtools
#endif // INCLUDE_SPIRV_TOOLS_OPTIMIZER_HPP_
diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt
index 6ebbfbf..4e7d92d 100644
--- a/source/opt/CMakeLists.txt
+++ b/source/opt/CMakeLists.txt
@@ -94,6 +94,7 @@
loop_unswitch_pass.h
mem_pass.h
merge_return_pass.h
+ modify_maximal_reconvergence.h
module.h
null_pass.h
passes.h
@@ -214,6 +215,7 @@
loop_unswitch_pass.cpp
mem_pass.cpp
merge_return_pass.cpp
+ modify_maximal_reconvergence.cpp
module.cpp
optimizer.cpp
pass.cpp
diff --git a/source/opt/modify_maximal_reconvergence.cpp b/source/opt/modify_maximal_reconvergence.cpp
new file mode 100644
index 0000000..dd79b62
--- /dev/null
+++ b/source/opt/modify_maximal_reconvergence.cpp
@@ -0,0 +1,103 @@
+// Copyright (c) 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "modify_maximal_reconvergence.h"
+
+#include "source/opt/ir_context.h"
+#include "source/util/make_unique.h"
+
+namespace spvtools {
+namespace opt {
+
+Pass::Status ModifyMaximalReconvergence::Process() {
+ bool changed = false;
+ if (add_) {
+ changed = AddMaximalReconvergence();
+ } else {
+ changed = RemoveMaximalReconvergence();
+ }
+ return changed ? Pass::Status::SuccessWithChange
+ : Pass::Status::SuccessWithoutChange;
+}
+
+bool ModifyMaximalReconvergence::AddMaximalReconvergence() {
+ bool changed = false;
+ bool has_extension = false;
+ bool has_shader =
+ context()->get_feature_mgr()->HasCapability(spv::Capability::Shader);
+ for (auto extension : context()->extensions()) {
+ if (extension.GetOperand(0).AsString() == "SPV_KHR_maximal_reconvergence") {
+ has_extension = true;
+ break;
+ }
+ }
+
+ std::unordered_set<uint32_t> entry_points_with_mode;
+ for (auto mode : get_module()->execution_modes()) {
+ if (spv::ExecutionMode(mode.GetSingleWordInOperand(1)) ==
+ spv::ExecutionMode::MaximallyReconvergesKHR) {
+ entry_points_with_mode.insert(mode.GetSingleWordInOperand(0));
+ }
+ }
+
+ for (auto entry_point : get_module()->entry_points()) {
+ const uint32_t id = entry_point.GetSingleWordInOperand(1);
+ if (!entry_points_with_mode.count(id)) {
+ changed = true;
+ if (!has_extension) {
+ context()->AddExtension("SPV_KHR_maximal_reconvergence");
+ has_extension = true;
+ }
+ if (!has_shader) {
+ context()->AddCapability(spv::Capability::Shader);
+ has_shader = true;
+ }
+ context()->AddExecutionMode(MakeUnique<Instruction>(
+ context(), spv::Op::OpExecutionMode, 0, 0,
+ std::initializer_list<Operand>{
+ {SPV_OPERAND_TYPE_ID, {id}},
+ {SPV_OPERAND_TYPE_EXECUTION_MODE,
+ {static_cast<uint32_t>(
+ spv::ExecutionMode::MaximallyReconvergesKHR)}}}));
+ entry_points_with_mode.insert(id);
+ }
+ }
+
+ return changed;
+}
+
+bool ModifyMaximalReconvergence::RemoveMaximalReconvergence() {
+ bool changed = false;
+ std::vector<Instruction*> to_remove;
+ Instruction* mode = &*get_module()->execution_mode_begin();
+ while (mode) {
+ if (mode->opcode() != spv::Op::OpExecutionMode &&
+ mode->opcode() != spv::Op::OpExecutionModeId) {
+ break;
+ }
+ if (spv::ExecutionMode(mode->GetSingleWordInOperand(1)) ==
+ spv::ExecutionMode::MaximallyReconvergesKHR) {
+ mode = context()->KillInst(mode);
+ changed = true;
+ } else {
+ mode = mode->NextNode();
+ }
+ }
+
+ changed |=
+ context()->RemoveExtension(Extension::kSPV_KHR_maximal_reconvergence);
+ return changed;
+}
+} // namespace opt
+} // namespace spvtools
diff --git a/source/opt/modify_maximal_reconvergence.h b/source/opt/modify_maximal_reconvergence.h
new file mode 100644
index 0000000..8d9a698
--- /dev/null
+++ b/source/opt/modify_maximal_reconvergence.h
@@ -0,0 +1,53 @@
+// Copyright (c) 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#ifndef LIBSPIRV_OPT_MODIFY_MAXIMAL_RECONVERGENCE_H_
+#define LIBSPIRV_OPT_MODIFY_MAXIMAL_RECONVERGENCE_H_
+
+#include "pass.h"
+
+namespace spvtools {
+namespace opt {
+
+// Modifies entry points to either add or remove MaximallyReconvergesKHR
+//
+// This pass will either add or remove MaximallyReconvergesKHR to all entry
+// points in the module. When adding the execution mode, it does not attempt to
+// determine whether any ray tracing invocation repack instructions might be
+// executed because it is a runtime restriction. That is left to the user.
+class ModifyMaximalReconvergence : public Pass {
+ public:
+ const char* name() const override { return "modify-maximal-reconvergence"; }
+ Status Process() override;
+
+ explicit ModifyMaximalReconvergence(bool add = true) : Pass(), add_(add) {}
+
+ IRContext::Analysis GetPreservedAnalyses() override {
+ return IRContext::kAnalysisDefUse |
+ IRContext::kAnalysisInstrToBlockMapping |
+ IRContext::kAnalysisDecorations | IRContext::kAnalysisCombinators |
+ IRContext::kAnalysisCFG | IRContext::kAnalysisNameMap |
+ IRContext::kAnalysisConstants | IRContext::kAnalysisTypes;
+ }
+
+ private:
+ bool AddMaximalReconvergence();
+ bool RemoveMaximalReconvergence();
+
+ bool add_;
+};
+} // namespace opt
+} // namespace spvtools
+
+#endif // LIBSPIRV_OPT_MODIFY_MAXIMAL_RECONVERGENCE_H_
diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp
index 7cfc89f..4d089f6 100644
--- a/source/opt/optimizer.cpp
+++ b/source/opt/optimizer.cpp
@@ -606,6 +606,23 @@
return false;
}
RegisterPass(CreateSwitchDescriptorSetPass(from_set, to_set));
+ } else if (pass_name == "modify-maximal-reconvergence") {
+ if (pass_args.size() == 0) {
+ Error(consumer(), nullptr, {},
+ "--modify-maximal-reconvergence requires an argument");
+ return false;
+ }
+ if (pass_args == "add") {
+ RegisterPass(CreateModifyMaximalReconvergencePass(true));
+ } else if (pass_args == "remove") {
+ RegisterPass(CreateModifyMaximalReconvergencePass(false));
+ } else {
+ Errorf(consumer(), nullptr, {},
+ "Invalid argument for --modify-maximal-reconvergence: %s (must be "
+ "'add' or 'remove')",
+ pass_args.c_str());
+ return false;
+ }
} else {
Errorf(consumer(), nullptr, {},
"Unknown flag '--%s'. Use --help for a list of valid flags",
@@ -1141,6 +1158,11 @@
return MakeUnique<Optimizer::PassToken::Impl>(
MakeUnique<opt::InvocationInterlockPlacementPass>());
}
+
+Optimizer::PassToken CreateModifyMaximalReconvergencePass(bool add) {
+ return MakeUnique<Optimizer::PassToken::Impl>(
+ MakeUnique<opt::ModifyMaximalReconvergence>(add));
+}
} // namespace spvtools
extern "C" {
diff --git a/source/opt/passes.h b/source/opt/passes.h
index 305f578..9d027fb 100644
--- a/source/opt/passes.h
+++ b/source/opt/passes.h
@@ -65,6 +65,7 @@
#include "source/opt/loop_unroller.h"
#include "source/opt/loop_unswitch_pass.h"
#include "source/opt/merge_return_pass.h"
+#include "source/opt/modify_maximal_reconvergence.h"
#include "source/opt/null_pass.h"
#include "source/opt/private_to_local_pass.h"
#include "source/opt/reduce_load_size.h"
diff --git a/source/val/validate_cfg.cpp b/source/val/validate_cfg.cpp
index 7b3f748..9b7161f 100644
--- a/source/val/validate_cfg.cpp
+++ b/source/val/validate_cfg.cpp
@@ -190,6 +190,8 @@
"ID of an OpLabel instruction";
}
+ // A similar requirement for SPV_KHR_maximal_reconvergence is deferred until
+ // entry point call trees have been reconrded.
if (_.version() >= SPV_SPIRV_VERSION_WORD(1, 6) && true_id == false_id) {
return _.diag(SPV_ERROR_INVALID_ID, inst)
<< "In SPIR-V 1.6 or later, True Label and False Label must be "
@@ -875,6 +877,95 @@
return SPV_SUCCESS;
}
+spv_result_t MaximalReconvergenceChecks(ValidationState_t& _) {
+ // Find all the entry points with the MaximallyReconvergencesKHR execution
+ // mode.
+ std::unordered_set<uint32_t> maximal_funcs;
+ std::unordered_set<uint32_t> maximal_entry_points;
+ for (auto entry_point : _.entry_points()) {
+ const auto* exec_modes = _.GetExecutionModes(entry_point);
+ if (exec_modes &&
+ exec_modes->count(spv::ExecutionMode::MaximallyReconvergesKHR)) {
+ maximal_entry_points.insert(entry_point);
+ maximal_funcs.insert(entry_point);
+ }
+ }
+
+ if (maximal_entry_points.empty()) {
+ return SPV_SUCCESS;
+ }
+
+ // Find all the functions reachable from a maximal reconvergence entry point.
+ for (const auto& func : _.functions()) {
+ const auto& entry_points = _.EntryPointReferences(func.id());
+ for (auto id : entry_points) {
+ if (maximal_entry_points.count(id)) {
+ maximal_funcs.insert(func.id());
+ break;
+ }
+ }
+ }
+
+ // Check for conditional branches with the same true and false targets.
+ for (const auto& inst : _.ordered_instructions()) {
+ if (inst.opcode() == spv::Op::OpBranchConditional) {
+ const auto true_id = inst.GetOperandAs<uint32_t>(1);
+ const auto false_id = inst.GetOperandAs<uint32_t>(2);
+ if (true_id == false_id && maximal_funcs.count(inst.function()->id())) {
+ return _.diag(SPV_ERROR_INVALID_ID, &inst)
+ << "In entry points using the MaximallyReconvergesKHR execution "
+ "mode, True Label and False Label must be different labels";
+ }
+ }
+ }
+
+ // Check for invalid multiple predecessors. Only loop headers, continue
+ // targets, merge targets or switch targets or defaults may have multiple
+ // unique predecessors.
+ for (const auto& func : _.functions()) {
+ if (!maximal_funcs.count(func.id())) continue;
+
+ for (const auto* block : func.ordered_blocks()) {
+ std::unordered_set<uint32_t> unique_preds;
+ const auto* preds = block->predecessors();
+ if (!preds) continue;
+
+ for (const auto* pred : *preds) {
+ unique_preds.insert(pred->id());
+ }
+ if (unique_preds.size() < 2) continue;
+
+ const auto* terminator = block->terminator();
+ const auto index = terminator - &_.ordered_instructions()[0];
+ const auto* pre_terminator = &_.ordered_instructions()[index - 1];
+ if (pre_terminator->opcode() == spv::Op::OpLoopMerge) continue;
+
+ const auto* label = _.FindDef(block->id());
+ bool ok = false;
+ for (const auto& pair : label->uses()) {
+ const auto* use_inst = pair.first;
+ switch (use_inst->opcode()) {
+ case spv::Op::OpSelectionMerge:
+ case spv::Op::OpLoopMerge:
+ case spv::Op::OpSwitch:
+ ok = true;
+ break;
+ default:
+ break;
+ }
+ }
+ if (!ok) {
+ return _.diag(SPV_ERROR_INVALID_CFG, label)
+ << "In entry points using the MaximallyReconvergesKHR "
+ "execution mode, this basic block must not have multiple "
+ "unique predecessors";
+ }
+ }
+ }
+
+ return SPV_SUCCESS;
+}
+
spv_result_t PerformCfgChecks(ValidationState_t& _) {
for (auto& function : _.functions()) {
// Check all referenced blocks are defined within a function
@@ -999,6 +1090,11 @@
return error;
}
}
+
+ if (auto error = MaximalReconvergenceChecks(_)) {
+ return error;
+ }
+
return SPV_SUCCESS;
}
diff --git a/test/operand_capabilities_test.cpp b/test/operand_capabilities_test.cpp
index 4872228..6c4042d 100644
--- a/test/operand_capabilities_test.cpp
+++ b/test/operand_capabilities_test.cpp
@@ -486,7 +486,8 @@
CASE1(DECORATION, Decoration::XfbBuffer, TransformFeedback),
CASE1(DECORATION, Decoration::XfbStride, TransformFeedback),
CASE1(DECORATION, Decoration::FuncParamAttr, Kernel),
- CASE1(DECORATION, Decoration::FPFastMathMode, Kernel),
+ CASE2(DECORATION, Decoration::FPFastMathMode, Kernel,
+ FloatControls2),
CASE1(DECORATION, Decoration::LinkageAttributes, Linkage),
CASE1(DECORATION, Decoration::NoContraction, Shader),
CASE1(DECORATION, Decoration::InputAttachmentIndex,
diff --git a/test/opt/CMakeLists.txt b/test/opt/CMakeLists.txt
index ceada13..92d266b 100644
--- a/test/opt/CMakeLists.txt
+++ b/test/opt/CMakeLists.txt
@@ -78,6 +78,7 @@
local_single_block_elim.cpp
local_single_store_elim_test.cpp
local_ssa_elim_test.cpp
+ modify_maximal_reconvergence_test.cpp
module_test.cpp
module_utils.h
optimizer_test.cpp
diff --git a/test/opt/modify_maximal_reconvergence_test.cpp b/test/opt/modify_maximal_reconvergence_test.cpp
new file mode 100644
index 0000000..bef9237
--- /dev/null
+++ b/test/opt/modify_maximal_reconvergence_test.cpp
@@ -0,0 +1,312 @@
+// Copyright (c) 2024 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+#include "assembly_builder.h"
+#include "pass_fixture.h"
+#include "pass_utils.h"
+
+namespace {
+
+using namespace spvtools;
+
+using ModifyMaximalReconvergenceTest = opt::PassTest<::testing::Test>;
+
+TEST_F(ModifyMaximalReconvergenceTest, AddNoEntryPoint) {
+ const std::string text = R"(
+; CHECK-NOT: OpExtension
+OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, true);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, AddSingleEntryPoint) {
+ const std::string text = R"(
+; CHECK: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %main MaximallyReconvergesKHR
+
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, true);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, AddExtensionExists) {
+ const std::string text = R"(
+; CHECK: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK-NOT: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %main MaximallyReconvergesKHR
+
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, true);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, AddExecutionModeExists) {
+ const std::string text = R"(
+; CHECK: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK-NOT: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %main LocalSize 1 1 1
+; CHECK-NEXT: OpExecutionMode %main MaximallyReconvergesKHR
+; CHECK-NOT: OpExecutionMode %main MaximallyReconvergesKHR
+
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+OpName %main "main"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, true);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, AddTwoEntryPoints) {
+ const std::string text = R"(
+; CHECK: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %comp MaximallyReconvergesKHR
+; CHECK: OpExecutionMode %frag MaximallyReconvergesKHR
+
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %comp "main"
+OpEntryPoint Fragment %frag "main"
+OpExecutionMode %comp LocalSize 1 1 1
+OpExecutionMode %frag OriginUpperLeft
+OpName %comp "comp"
+OpName %frag "frag"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%comp = OpFunction %void None %void_fn
+%entry1 = OpLabel
+OpReturn
+OpFunctionEnd
+%frag = OpFunction %void None %void_fn
+%entry2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, true);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, AddTwoEntryPointsOneFunc) {
+ const std::string text = R"(
+; CHECK: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %comp MaximallyReconvergesKHR
+; CHECK-NOT: OpExecutionMode %comp MaximallyReconvergesKHR
+
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %comp "main1"
+OpEntryPoint GLCompute %comp "main2"
+OpExecutionMode %comp LocalSize 1 1 1
+OpName %comp "comp"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%comp = OpFunction %void None %void_fn
+%entry1 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, true);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, AddTwoEntryPointsOneExecutionMode) {
+ const std::string text = R"(
+; CHECK: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %comp MaximallyReconvergesKHR
+; CHECK-NOT: OpExecutionMode %comp MaximallyReconvergesKHR
+; CHECK: OpExecutionMode %frag MaximallyReconvergesKHR
+; CHECK-NOT: OpExecutionMode %comp MaximallyReconvergesKHR
+
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %comp "main"
+OpEntryPoint Fragment %frag "main"
+OpExecutionMode %comp LocalSize 1 1 1
+OpExecutionMode %frag OriginUpperLeft
+OpExecutionMode %comp MaximallyReconvergesKHR
+OpName %comp "comp"
+OpName %frag "frag"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%comp = OpFunction %void None %void_fn
+%entry1 = OpLabel
+OpReturn
+OpFunctionEnd
+%frag = OpFunction %void None %void_fn
+%entry2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, true);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, RemoveNoEntryPoint) {
+ const std::string text = R"(OpCapability Kernel
+OpCapability Linkage
+OpMemoryModel Logical OpenCL
+)";
+
+ SinglePassRunAndCheck<opt::ModifyMaximalReconvergence>(text, text, false,
+ true, false);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, RemoveOnlyExtension) {
+ const std::string text = R"(
+; CHECK-NOT: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %main LocalSize 1 1 1
+
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpName %main "main"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, false);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, RemoveSingleEntryPoint) {
+ const std::string text = R"(
+; CHECK-NOT: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %main LocalSize 1 1 1
+; CHECK-NOT: OpExecutionMode %main MaximallyReconvergesKHR
+
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+OpName %main "main"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, false);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, RemoveTwoEntryPointsOneExecutionMode) {
+ const std::string text = R"(
+; CHECK-NOT: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %comp LocalSize 1 1 1
+; CHECK-NEXT: OpExecutionMode %frag OriginUpperLeft
+; CHECK-NOT: OpExecutionMode %comp MaximallyReconvergesKHR
+
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %comp "main"
+OpEntryPoint Fragment %frag "main"
+OpExecutionMode %comp LocalSize 1 1 1
+OpExecutionMode %comp MaximallyReconvergesKHR
+OpExecutionMode %frag OriginUpperLeft
+OpName %comp "comp"
+OpName %frag "frag"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%comp = OpFunction %void None %void_fn
+%entry1 = OpLabel
+OpReturn
+OpFunctionEnd
+%frag = OpFunction %void None %void_fn
+%entry2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, false);
+}
+
+TEST_F(ModifyMaximalReconvergenceTest, RemoveTwoEntryPoints) {
+ const std::string text = R"(
+; CHECK-NOT: OpExtension "SPV_KHR_maximal_reconvergence"
+; CHECK: OpExecutionMode %comp LocalSize 1 1 1
+; CHECK-NEXT: OpExecutionMode %frag OriginUpperLeft
+; CHECK-NOT: OpExecutionMode {{%\w}} MaximallyReconvergesKHR
+
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %comp "main"
+OpEntryPoint Fragment %frag "main"
+OpExecutionMode %comp LocalSize 1 1 1
+OpExecutionMode %comp MaximallyReconvergesKHR
+OpExecutionMode %frag OriginUpperLeft
+OpExecutionMode %frag MaximallyReconvergesKHR
+OpName %comp "comp"
+OpName %frag "frag"
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%comp = OpFunction %void None %void_fn
+%entry1 = OpLabel
+OpReturn
+OpFunctionEnd
+%frag = OpFunction %void None %void_fn
+%entry2 = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ SinglePassRunAndMatch<opt::ModifyMaximalReconvergence>(text, true, false);
+}
+
+} // namespace
diff --git a/test/text_to_binary.extension_test.cpp b/test/text_to_binary.extension_test.cpp
index 55f8466..17adc55 100644
--- a/test/text_to_binary.extension_test.cpp
+++ b/test/text_to_binary.extension_test.cpp
@@ -1247,5 +1247,22 @@
MakeInstruction(spv::Op::OpStencilAttachmentReadEXT, {1, 2, 3})},
})));
+// SPV_KHR_maximal_reconvergence
+
+INSTANTIATE_TEST_SUITE_P(
+ SPV_KHR_maximal_reconvergence, ExtensionRoundTripTest,
+ Combine(
+ Values(SPV_ENV_UNIVERSAL_1_0, SPV_ENV_UNIVERSAL_1_5, SPV_ENV_VULKAN_1_0,
+ SPV_ENV_VULKAN_1_1, SPV_ENV_VULKAN_1_2, SPV_ENV_VULKAN_1_3),
+ ValuesIn(std::vector<AssemblyCase>{
+ {"OpExtension \"SPV_KHR_maximal_reconvergence\"\n",
+ MakeInstruction(spv::Op::OpExtension,
+ MakeVector("SPV_KHR_maximal_reconvergence"))},
+ {"OpExecutionMode %1 MaximallyReconvergesKHR\n",
+ MakeInstruction(
+ spv::Op::OpExecutionMode,
+ {1, (uint32_t)spv::ExecutionMode::MaximallyReconvergesKHR})},
+ })));
+
} // namespace
} // namespace spvtools
diff --git a/test/val/val_cfg_test.cpp b/test/val/val_cfg_test.cpp
index 3953057..233aee6 100644
--- a/test/val/val_cfg_test.cpp
+++ b/test/val/val_cfg_test.cpp
@@ -4803,6 +4803,321 @@
"via a structured exit"));
}
+TEST_F(ValidateCFG,
+ MaximalReconvergenceBranchConditionalSameTargetNotInCallTree) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%func_entry = OpLabel
+OpBranchConditional %cond %func_exit %func_exit
+%func_exit = OpLabel
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+}
+
+TEST_F(ValidateCFG, MaximalReconvergenceBranchConditionalSameTargetInCallTree) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%func_entry = OpLabel
+OpBranchConditional %cond %func_exit %func_exit
+%func_exit = OpLabel
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+%call = OpFunctionCall %void %func
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text, SPV_ENV_UNIVERSAL_1_3);
+ EXPECT_EQ(SPV_ERROR_INVALID_ID, ValidateInstructions(SPV_ENV_UNIVERSAL_1_3));
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("In entry points using the MaximallyReconvergesKHR "
+ "execution mode, True "
+ "Label and False Label must be different labels"));
+}
+
+TEST_F(ValidateCFG, MaximalReconvergenceEarlyReconvergenceNotInCallTree) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%func_entry = OpLabel
+OpSelectionMerge %func_exit None
+OpBranchConditional %cond %then %else
+%then = OpLabel
+OpBranch %merge
+%else = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpBranch %func_exit
+%func_exit = OpLabel
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, MaximalReconvergenceEarlyReconvergenceInCallTree) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%func = OpFunction %void None %void_fn
+%func_entry = OpLabel
+OpSelectionMerge %func_exit None
+OpBranchConditional %cond %then %else
+%then = OpLabel
+OpBranch %merge
+%else = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpBranch %func_exit
+%func_exit = OpLabel
+OpReturn
+OpFunctionEnd
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+%call = OpFunctionCall %void %func
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text);
+ EXPECT_EQ(SPV_ERROR_INVALID_CFG, ValidateInstructions());
+ EXPECT_THAT(
+ getDiagnosticString(),
+ HasSubstr(
+ "In entry points using the MaximallyReconvergesKHR execution mode, "
+ "this basic block must not have multiple unique predecessors"));
+}
+
+TEST_F(ValidateCFG, MaximalReconvergenceLoopMultiplePredsOk) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+OpBranch %loop
+%loop = OpLabel
+OpLoopMerge %merge %loop None
+OpBranchConditional %cond %loop %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, MaximalReconvergenceLoopMultiplePredsOk2) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+OpBranch %loop
+%loop = OpLabel
+OpLoopMerge %merge %cont None
+OpBranch %body
+%body = OpLabel
+OpBranch %cont
+%cont = OpLabel
+OpBranchConditional %cond %loop %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, MaximalReconvergenceSelectionMergeMultiplePredsOk) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+OpSelectionMerge %merge None
+OpBranchConditional %cond %then %else
+%then = OpLabel
+OpBranch %merge
+%else = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, MaximalReconvergenceSelectionMergeMultiplePredsOk2) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+OpName %merge "merge"
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+OpSelectionMerge %merge None
+OpBranchConditional %cond %then %else
+%then = OpLabel
+OpBranch %merge
+%else = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, MaximalReconvergenceLoopMergeMultiplePredsOk) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+OpBranch %loop
+%loop = OpLabel
+OpLoopMerge %merge %continue None
+OpBranchConditional %cond %merge %continue
+%continue = OpLabel
+OpBranchConditional %cond %loop %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
+TEST_F(ValidateCFG, MaximalReconvergenceCaseFallthroughMultiplePredsOk) {
+ const std::string text = R"(
+OpCapability Shader
+OpExtension "SPV_KHR_maximal_reconvergence"
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%bool = OpTypeBool
+%cond = OpUndef %bool
+%int = OpTypeInt 32 0
+%val = OpUndef %int
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%main_entry = OpLabel
+OpSelectionMerge %merge None
+OpSwitch %val %merge 0 %case1 1 %case2
+%case1 = OpLabel
+OpBranch %case2
+%case2 = OpLabel
+OpBranch %merge
+%merge = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(text);
+ EXPECT_EQ(SPV_SUCCESS, ValidateInstructions());
+}
+
} // namespace
} // namespace val
} // namespace spvtools
diff --git a/test/val/val_modes_test.cpp b/test/val/val_modes_test.cpp
index 8dc0fbc..97d8c39 100644
--- a/test/val/val_modes_test.cpp
+++ b/test/val/val_modes_test.cpp
@@ -1306,6 +1306,28 @@
EXPECT_EQ(SPV_SUCCESS, ValidateInstructions(SPV_ENV_VULKAN_1_3));
}
+TEST_F(ValidateMode, MaximalReconvergenceRequiresExtension) {
+ const std::string spirv = R"(
+OpCapability Shader
+OpMemoryModel Logical GLSL450
+OpEntryPoint GLCompute %main "main"
+OpExecutionMode %main LocalSize 1 1 1
+OpExecutionMode %main MaximallyReconvergesKHR
+%void = OpTypeVoid
+%void_fn = OpTypeFunction %void
+%main = OpFunction %void None %void_fn
+%entry = OpLabel
+OpReturn
+OpFunctionEnd
+)";
+
+ CompileSuccessfully(spirv);
+ EXPECT_EQ(SPV_ERROR_MISSING_EXTENSION, ValidateInstructions());
+ EXPECT_THAT(getDiagnosticString(),
+ HasSubstr("(6023) requires one of these extensions: "
+ "SPV_KHR_maximal_reconvergence "));
+}
+
} // namespace
} // namespace val
} // namespace spvtools