From 6679d5df89834c58ec3357fd5fcb1e5e554ad543 Mon Sep 17 00:00:00 2001 From: Alastair Donaldson Date: Fri, 7 Dec 2018 17:44:46 +0000 Subject: [PATCH] Replace loop with selection (#2164) Add a pass for spirv-reduce that will turn a loop into a selection. --- source/reduce/CMakeLists.txt | 4 + ...oop_to_selection_reduction_opportunity.cpp | 377 ++ ..._loop_to_selection_reduction_opportunity.h | 125 + ...tured_loop_to_selection_reduction_pass.cpp | 95 + ...uctured_loop_to_selection_reduction_pass.h | 64 + test/reduce/CMakeLists.txt | 1 + test/reduce/reduce_test_util.cpp | 16 + test/reduce/reduce_test_util.h | 8 + ..._loop_to_selection_reduction_pass_test.cpp | 3299 +++++++++++++++++ tools/reduce/reduce.cpp | 3 + 10 files changed, 3992 insertions(+) create mode 100644 source/reduce/structured_loop_to_selection_reduction_opportunity.cpp create mode 100644 source/reduce/structured_loop_to_selection_reduction_opportunity.h create mode 100644 source/reduce/structured_loop_to_selection_reduction_pass.cpp create mode 100644 source/reduce/structured_loop_to_selection_reduction_pass.h create mode 100644 test/reduce/structured_loop_to_selection_reduction_pass_test.cpp diff --git a/source/reduce/CMakeLists.txt b/source/reduce/CMakeLists.txt index fc7369fa..1a1cd7d4 100644 --- a/source/reduce/CMakeLists.txt +++ b/source/reduce/CMakeLists.txt @@ -20,6 +20,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES reduction_pass.h remove_instruction_reduction_opportunity.h remove_unreferenced_instruction_reduction_pass.h + structured_loop_to_selection_reduction_opportunity.h + structured_loop_to_selection_reduction_pass.h change_operand_reduction_opportunity.cpp operand_to_const_reduction_pass.cpp @@ -29,6 +31,8 @@ set(SPIRV_TOOLS_REDUCE_SOURCES reduction_pass.cpp remove_instruction_reduction_opportunity.cpp remove_unreferenced_instruction_reduction_pass.cpp + structured_loop_to_selection_reduction_opportunity.cpp + structured_loop_to_selection_reduction_pass.cpp ) if(MSVC) diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp new file mode 100644 index 00000000..679cfc1a --- /dev/null +++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.cpp @@ -0,0 +1,377 @@ +// Copyright (c) 2018 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 "structured_loop_to_selection_reduction_opportunity.h" +#include "source/opt/aggressive_dead_code_elim_pass.h" +#include "source/opt/ir_context.h" + +namespace spvtools { +namespace reduce { + +namespace { +const uint32_t kMergeNodeIndex = 0; +const uint32_t kContinueNodeIndex = 1; +} // namespace + +bool StructuredLoopToSelectionReductionOpportunity::PreconditionHolds() { + // Is the loop header reachable? + return loop_construct_header_->GetLabel() + ->context() + ->GetDominatorAnalysis(enclosing_function_) + ->IsReachable(loop_construct_header_); +} + +void StructuredLoopToSelectionReductionOpportunity::Apply() { + // Force computation of dominator analysis, CFG and structured CFG analysis + // before we start to mess with edges in the function. + context_->GetDominatorAnalysis(enclosing_function_); + context_->cfg(); + context_->GetStructuredCFGAnalysis(); + + // (1) Redirect edges that point to the loop's continue target to their + // closest merge block. + RedirectToClosestMergeBlock( + loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand( + kContinueNodeIndex)); + + // (2) Redirect edges that point to the loop's merge block to their closest + // merge block (which might be that of an enclosing selection, for instance). + RedirectToClosestMergeBlock( + loop_construct_header_->GetLoopMergeInst()->GetSingleWordOperand( + kMergeNodeIndex)); + + // (3) Turn the loop construct header into a selection. + ChangeLoopToSelection(); + + // We have made control flow changes that do not preserve the analyses that + // were performed. + context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); + + // (4) By changing CFG edges we may have created scenarios where ids are used + // without being dominated; we fix instances of this. + FixNonDominatedIdUses(); + + // Invalidate the analyses we just used. + context_->InvalidateAnalysesExceptFor(IRContext::Analysis::kAnalysisNone); +} + +void StructuredLoopToSelectionReductionOpportunity::RedirectToClosestMergeBlock( + uint32_t original_target_id) { + // Consider every predecessor of the node with respect to which edges should + // be redirected. + std::set already_seen; + for (auto pred : context_->cfg()->preds(original_target_id)) { + if (already_seen.find(pred) != already_seen.end()) { + // We have already handled this predecessor (this scenario can arise if + // there are multiple edges from a block b to original_target_id). + continue; + } + already_seen.insert(pred); + + if (!context_->GetDominatorAnalysis(enclosing_function_) + ->IsReachable(pred)) { + // We do not care about unreachable predecessors (and dominance + // information, and thus the notion of structured control flow, makes + // little sense for unreachable blocks). + continue; + } + // Find the merge block of the structured control construct that most + // tightly encloses the predecessor. + uint32_t new_merge_target; + // The structured CFG analysis deliberately does not regard a header as + // belonging to the structure that it heads. We want it to, so handle this + // case specially. + if (context_->cfg()->block(pred)->MergeBlockIdIfAny()) { + new_merge_target = context_->cfg()->block(pred)->MergeBlockIdIfAny(); + } else { + new_merge_target = context_->GetStructuredCFGAnalysis()->MergeBlock(pred); + } + assert(new_merge_target != pred); + + if (!new_merge_target) { + // If the loop being transformed is outermost, and the predecessor is + // part of that loop's continue construct, there will be no such + // enclosing control construct. In this case, the continue construct + // will become unreachable anyway, so it is fine not to redirect the + // edge. + continue; + } + + if (new_merge_target != original_target_id) { + // Redirect the edge if it doesn't already point to the desired block. + RedirectEdge(pred, original_target_id, new_merge_target); + } + } +} + +void StructuredLoopToSelectionReductionOpportunity::RedirectEdge( + uint32_t source_id, uint32_t original_target_id, uint32_t new_target_id) { + // Redirect edge source_id->original_target_id to edge + // source_id->new_target_id, where the blocks involved are all different. + assert(source_id != original_target_id); + assert(source_id != new_target_id); + assert(original_target_id != new_target_id); + + // original_target_id must either be the merge target or continue construct + // for the loop being operated on. + assert(original_target_id == + loop_construct_header_->GetMergeInst()->GetSingleWordOperand( + kMergeNodeIndex) || + original_target_id == + loop_construct_header_->GetMergeInst()->GetSingleWordOperand( + kContinueNodeIndex)); + + auto terminator = context_->cfg()->block(source_id)->terminator(); + + // Figure out which operands of the terminator need to be considered for + // redirection. + std::vector operand_indices; + if (terminator->opcode() == SpvOpBranch) { + operand_indices = {0}; + } else if (terminator->opcode() == SpvOpBranchConditional) { + operand_indices = {1, 2}; + } else { + assert(terminator->opcode() == SpvOpSwitch); + for (uint32_t label_index = 1; label_index < terminator->NumOperands(); + label_index += 2) { + operand_indices.push_back(label_index); + } + } + + // Redirect the relevant operands, asserting that at least one redirection is + // made. + bool redirected = false; + for (auto operand_index : operand_indices) { + if (terminator->GetSingleWordOperand(operand_index) == original_target_id) { + terminator->SetOperand(operand_index, {new_target_id}); + redirected = true; + } + } + (void)(redirected); + assert(redirected); + + // The old and new targets may have phi instructions; these will need to + // respect the change in edges. + AdaptPhiInstructionsForRemovedEdge( + source_id, context_->cfg()->block(original_target_id)); + AdaptPhiInstructionsForAddedEdge(source_id, + context_->cfg()->block(new_target_id)); +} + +void StructuredLoopToSelectionReductionOpportunity:: + AdaptPhiInstructionsForRemovedEdge(uint32_t from_id, BasicBlock* to_block) { + to_block->ForEachPhiInst([&from_id](Instruction* phi_inst) { + Instruction::OperandList new_in_operands; + // Go through the OpPhi's input operands in (variable, parent) pairs. + for (uint32_t index = 0; index < phi_inst->NumInOperands(); index += 2) { + // Keep all pairs where the parent is not the block from which the edge + // is being removed. + if (phi_inst->GetInOperand(index + 1).words[0] != from_id) { + new_in_operands.push_back(phi_inst->GetInOperand(index)); + new_in_operands.push_back(phi_inst->GetInOperand(index + 1)); + } + } + phi_inst->SetInOperands(std::move(new_in_operands)); + }); +} + +void StructuredLoopToSelectionReductionOpportunity:: + AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block) { + to_block->ForEachPhiInst([this, &from_id](Instruction* phi_inst) { + // Add to the phi operand an (undef, from_id) pair to reflect the added + // edge. + auto undef_id = FindOrCreateGlobalUndef(phi_inst->type_id()); + phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {undef_id})); + phi_inst->AddOperand(Operand(SPV_OPERAND_TYPE_ID, {from_id})); + }); +} + +void StructuredLoopToSelectionReductionOpportunity::ChangeLoopToSelection() { + // Change the merge instruction from OpLoopMerge to OpSelectionMerge, with + // the same merge block. + auto loop_merge_inst = loop_construct_header_->GetLoopMergeInst(); + auto const loop_merge_block_id = + loop_merge_inst->GetSingleWordOperand(kMergeNodeIndex); + loop_merge_inst->SetOpcode(SpvOpSelectionMerge); + loop_merge_inst->ReplaceOperands( + {{loop_merge_inst->GetOperand(kMergeNodeIndex).type, + {loop_merge_block_id}}, + {SPV_OPERAND_TYPE_SELECTION_CONTROL, {SpvSelectionControlMaskNone}}}); + + // The loop header either finishes with OpBranch or OpBranchConditional. + // The latter is fine for a selection. In the former case we need to turn + // it into OpBranchConditional. We use "true" as the condition, and make + // the "else" branch be the merge block. + auto terminator = loop_construct_header_->terminator(); + if (terminator->opcode() == SpvOpBranch) { + analysis::Bool temp; + const analysis::Bool* bool_type = + context_->get_type_mgr()->GetRegisteredType(&temp)->AsBool(); + auto const_mgr = context_->get_constant_mgr(); + auto true_const = const_mgr->GetConstant(bool_type, {true}); + auto true_const_result_id = + const_mgr->GetDefiningInstruction(true_const)->result_id(); + auto original_branch_id = terminator->GetSingleWordOperand(0); + terminator->SetOpcode(SpvOpBranchConditional); + terminator->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {true_const_result_id}}, + {SPV_OPERAND_TYPE_ID, {original_branch_id}}, + {SPV_OPERAND_TYPE_ID, {loop_merge_block_id}}}); + if (original_branch_id != loop_merge_block_id) { + AdaptPhiInstructionsForAddedEdge( + loop_construct_header_->id(), + context_->cfg()->block(loop_merge_block_id)); + } + } +} + +void StructuredLoopToSelectionReductionOpportunity::FixNonDominatedIdUses() { + // Consider each instruction in the function. + for (auto& block : *enclosing_function_) { + for (auto& def : block) { + if (def.opcode() == SpvOpVariable) { + // Variables are defined at the start of the function, and can be + // accessed by all blocks, even by unreachable blocks that have no + // dominators, so we do not need to worry about them. + continue; + } + context_->get_def_use_mgr()->ForEachUse(&def, [this, &block, &def]( + Instruction* use, + uint32_t index) { + // If a use is not appropriately dominated by its definition, + // replace the use with an OpUndef, unless the definition is an + // access chain, in which case replace it with some (possibly fresh) + // variable (as we cannot load from / store to OpUndef). + if (!DefinitionSufficientlyDominatesUse(&def, use, index, block)) { + if (def.opcode() == SpvOpAccessChain) { + auto pointer_type = + context_->get_type_mgr()->GetType(def.type_id())->AsPointer(); + switch (pointer_type->storage_class()) { + case SpvStorageClassFunction: + use->SetOperand( + index, {FindOrCreateFunctionVariable( + context_->get_type_mgr()->GetId(pointer_type))}); + break; + default: + // TODO(2183) Need to think carefully about whether it makes + // sense to add new variables for all storage classes; it's fine + // for Private but might not be OK for input/output storage + // classes for example. + use->SetOperand( + index, {FindOrCreateGlobalVariable( + context_->get_type_mgr()->GetId(pointer_type))}); + break; + } + } else { + use->SetOperand(index, {FindOrCreateGlobalUndef(def.type_id())}); + } + } + }); + } + } +} + +bool StructuredLoopToSelectionReductionOpportunity:: + DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use, + uint32_t use_index, + BasicBlock& def_block) { + if (use->opcode() == SpvOpPhi) { + // A use in a phi doesn't need to be dominated by its definition, but the + // associated parent block does need to be dominated by the definition. + return context_->GetDominatorAnalysis(enclosing_function_) + ->Dominates(def_block.id(), use->GetSingleWordOperand(use_index + 1)); + } + // In non-phi cases, a use needs to be dominated by its definition. + return context_->GetDominatorAnalysis(enclosing_function_) + ->Dominates(def, use); +} + +uint32_t StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalUndef( + uint32_t type_id) { + for (auto& inst : context_->module()->types_values()) { + if (inst.opcode() != SpvOpUndef) { + continue; + } + if (inst.type_id() == type_id) { + return inst.result_id(); + } + } + // TODO(2182): this is adapted from MemPass::Type2Undef. In due course it + // would be good to factor out this duplication. + const uint32_t undef_id = context_->TakeNextId(); + std::unique_ptr undef_inst( + new Instruction(context_, SpvOpUndef, type_id, undef_id, {})); + assert(undef_id == undef_inst->result_id()); + context_->module()->AddGlobalValue(std::move(undef_inst)); + return undef_id; +} + +uint32_t +StructuredLoopToSelectionReductionOpportunity::FindOrCreateGlobalVariable( + uint32_t pointer_type_id) { + for (auto& inst : context_->module()->types_values()) { + if (inst.opcode() != SpvOpVariable) { + continue; + } + if (inst.type_id() == pointer_type_id) { + return inst.result_id(); + } + } + const uint32_t variable_id = context_->TakeNextId(); + std::unique_ptr variable_inst( + new Instruction(context_, SpvOpVariable, pointer_type_id, variable_id, + {{SPV_OPERAND_TYPE_STORAGE_CLASS, + {(uint32_t)context_->get_type_mgr() + ->GetType(pointer_type_id) + ->AsPointer() + ->storage_class()}}})); + context_->module()->AddGlobalValue(std::move(variable_inst)); + return variable_id; +} + +uint32_t +StructuredLoopToSelectionReductionOpportunity::FindOrCreateFunctionVariable( + uint32_t pointer_type_id) { + // The pointer type of a function variable must have Function storage class. + assert(context_->get_type_mgr() + ->GetType(pointer_type_id) + ->AsPointer() + ->storage_class() == SpvStorageClassFunction); + + // Go through the instructions in the function's first block until we find a + // suitable variable, or go past all the variables. + BasicBlock::iterator iter = enclosing_function_->begin()->begin(); + for (;; ++iter) { + // We will either find a suitable variable, or find a non-variable + // instruction; we won't exhaust all instructions. + assert(iter != enclosing_function_->begin()->end()); + if (iter->opcode() != SpvOpVariable) { + // If we see a non-variable, we have gone through all the variables. + break; + } + if (iter->type_id() == pointer_type_id) { + return iter->result_id(); + } + } + // At this point, iter refers to the first non-function instruction of the + // function's entry block. + const uint32_t variable_id = context_->TakeNextId(); + std::unique_ptr variable_inst(new Instruction( + context_, SpvOpVariable, pointer_type_id, variable_id, + {{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}})); + iter->InsertBefore(std::move(variable_inst)); + return variable_id; +} + +} // namespace reduce +} // namespace spvtools diff --git a/source/reduce/structured_loop_to_selection_reduction_opportunity.h b/source/reduce/structured_loop_to_selection_reduction_opportunity.h new file mode 100644 index 00000000..b1390168 --- /dev/null +++ b/source/reduce/structured_loop_to_selection_reduction_opportunity.h @@ -0,0 +1,125 @@ +// Copyright (c) 2018 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 SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_ +#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_ + +#include +#include "reduction_opportunity.h" +#include "source/opt/dominator_analysis.h" +#include "source/opt/function.h" + +namespace spvtools { +namespace reduce { + +using namespace opt; + +// Captures an opportunity to replace a structured loop with a selection. +class StructuredLoopToSelectionReductionOpportunity + : public ReductionOpportunity { + public: + // Constructs an opportunity from a loop header block and the function that + // encloses it. + explicit StructuredLoopToSelectionReductionOpportunity( + IRContext* context, BasicBlock* loop_construct_header, + Function* enclosing_function) + : context_(context), + loop_construct_header_(loop_construct_header), + enclosing_function_(enclosing_function) {} + + // We require the loop header to be reachable. A structured loop might + // become unreachable as a result of turning another structured loop into + // a selection. + bool PreconditionHolds() override; + + protected: + // Perform the structured loop to selection transformation. + void Apply() override; + + private: + // Parameter |original_target_id| is the id of the loop's merge block or + // continue target. This method considers each edge of the form + // b->original_target_id and transforms it into an edge of the form b->c, + // where c is the merge block of the structured control flow construct that + // most tightly contains b. + void RedirectToClosestMergeBlock(uint32_t original_target_id); + + // |source_id|, |original_target_id| and |new_target_id| are required to all + // be distinct, with a CFG edge existing from |source_id| to + // |original_target_id|, and |original_target_id| being either the merge block + // or continue target for the loop being operated on. + // The method removes this edge and adds an edge from + // |source_id| to |new_target_id|. It takes care of fixing up any OpPhi + // instructions associated with |original_target_id| and |new_target_id|. + void RedirectEdge(uint32_t source_id, uint32_t original_target_id, + uint32_t new_target_id); + + // Removes any components of |to_block|'s phi instructions relating to + // |from_id|. + void AdaptPhiInstructionsForRemovedEdge(uint32_t from_id, + BasicBlock* to_block); + + // Adds components to |to_block|'s phi instructions to account for a new + // incoming edge from |from_id|. + void AdaptPhiInstructionsForAddedEdge(uint32_t from_id, BasicBlock* to_block); + + // Turns the OpLoopMerge for the loop into OpSelectionMerge, and adapts the + // following branch instruction accordingly. + void ChangeLoopToSelection(); + + // Fixes any scenarios where, due to CFG changes, ids have uses not dominated + // by their definitions, by changing such uses to uses of OpUndef or of dummy + // variables. + void FixNonDominatedIdUses(); + + // Returns true if and only if at least one of the following holds: + // 1) |def| dominates |use| + // 2) |def| is an OpVariable + // 3) |use| is part of an OpPhi, with associated incoming block b, and |def| + // dominates b. + bool DefinitionSufficientlyDominatesUse(Instruction* def, Instruction* use, + uint32_t use_index, + BasicBlock& def_block); + + // Checks whether the global value list has an OpUndef of the given type, + // adding one if not, and returns the id of such an OpUndef. + // + // TODO(2184): This will likely be used by other reduction passes, so should + // be factored out in due course. Parts of the spirv-opt framework provide + // similar functionality, so there may be a case for further refactoring. + uint32_t FindOrCreateGlobalUndef(uint32_t type_id); + + // Checks whether the global value list has an OpVariable of the given pointer + // type, adding one if not, and returns the id of such an OpVariable. + // + // TODO(2184): This will likely be used by other reduction passes, so should + // be factored out in due course. + uint32_t FindOrCreateGlobalVariable(uint32_t pointer_type_id); + + // Checks whether the enclosing function has an OpVariable of the given + // pointer type, adding one if not, and returns the id of such an OpVariable. + // + // TODO(2184): This will likely be used by other reduction passes, so should + // be factored out in due course. + uint32_t FindOrCreateFunctionVariable(uint32_t pointer_type_id); + + IRContext* context_; + BasicBlock* loop_construct_header_; + Function* enclosing_function_; +}; + +} // namespace reduce +} // namespace spvtools + +#endif // SOURCE_REDUCE_CUT_LOOP_REDUCTION_OPPORTUNITY_H_ diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.cpp b/source/reduce/structured_loop_to_selection_reduction_pass.cpp new file mode 100644 index 00000000..768a2e8e --- /dev/null +++ b/source/reduce/structured_loop_to_selection_reduction_pass.cpp @@ -0,0 +1,95 @@ +// Copyright (c) 2018 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 "structured_loop_to_selection_reduction_pass.h" +#include "structured_loop_to_selection_reduction_opportunity.h" + +namespace spvtools { +namespace reduce { + +using namespace opt; + +namespace { +const uint32_t kMergeNodeIndex = 0; +const uint32_t kContinueNodeIndex = 1; +} // namespace + +std::vector> +StructuredLoopToSelectionReductionPass::GetAvailableOpportunities( + opt::IRContext* context) const { + std::vector> result; + + std::set merge_block_ids; + for (auto& function : *context->module()) { + for (auto& block : function) { + auto merge_inst = block.GetMergeInst(); + if (merge_inst) { + merge_block_ids.insert( + merge_inst->GetSingleWordOperand(kMergeNodeIndex)); + } + } + } + + // Consider each loop construct header in the module. + for (auto& function : *context->module()) { + for (auto& block : function) { + auto loop_merge_inst = block.GetLoopMergeInst(); + if (!loop_merge_inst) { + // This is not a loop construct header. + continue; + } + + // Check whether the loop construct's continue target is the merge block + // of some structured control flow construct. If it is, we cautiously do + // not consider applying a transformation. + if (merge_block_ids.find(loop_merge_inst->GetSingleWordOperand( + kContinueNodeIndex)) != merge_block_ids.end()) { + continue; + } + + // Check whether the loop construct header dominates its merge block. + // If not, the merge block must be unreachable in the control flow graph + // so we cautiously do not consider applying a transformation. + auto merge_block_id = + loop_merge_inst->GetSingleWordInOperand(kMergeNodeIndex); + if (!context->GetDominatorAnalysis(&function)->Dominates( + block.id(), merge_block_id)) { + continue; + } + + // Check whether the loop construct merge block postdominates the loop + // construct header. If not (e.g. because the loop contains OpReturn, + // OpKill or OpUnreachable), we cautiously do not consider applying + // a transformation. + if (!context->GetPostDominatorAnalysis(&function)->Dominates( + merge_block_id, block.id())) { + continue; + } + + // We can turn this structured loop into a selection, so add the + // opportunity to do so. + result.push_back( + MakeUnique( + context, &block, &function)); + } + } + return result; +} + +std::string StructuredLoopToSelectionReductionPass::GetName() const { + return "StructuredLoopToSelectionReductionPass"; +} + +} // namespace reduce +} // namespace spvtools diff --git a/source/reduce/structured_loop_to_selection_reduction_pass.h b/source/reduce/structured_loop_to_selection_reduction_pass.h new file mode 100644 index 00000000..9c4d4cae --- /dev/null +++ b/source/reduce/structured_loop_to_selection_reduction_pass.h @@ -0,0 +1,64 @@ +// Copyright (c) 2018 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 SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_ +#define SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_ + +#include "reduction_pass.h" + +namespace spvtools { +namespace reduce { + +// Turns structured loops into selections, generalizing from a human-writable +// language the idea of turning a loop: +// +// while (c) { +// body; +// } +// +// into: +// +// if (c) { +// body; +// } +// +// The pass results in continue constructs of transformed loops becoming +// unreachable; another pass for eliminating blocks may end up being able to +// remove them. +class StructuredLoopToSelectionReductionPass : public ReductionPass { + public: + // Creates the reduction pass in the context of the given target environment + // |target_env| + explicit StructuredLoopToSelectionReductionPass( + const spv_target_env target_env) + : ReductionPass(target_env) {} + + ~StructuredLoopToSelectionReductionPass() override = default; + + // The name of this pass. + std::string GetName() const final; + + protected: + // Finds all opportunities for transforming a structured loop to a selection + // in the given module. + std::vector> GetAvailableOpportunities( + opt::IRContext* context) const final; + + private: +}; + +} // namespace reduce +} // namespace spvtools + +#endif // SOURCE_REDUCE_CUT_LOOP_REDUCTION_PASS_H_ diff --git a/test/reduce/CMakeLists.txt b/test/reduce/CMakeLists.txt index 828bf68a..235d74aa 100644 --- a/test/reduce/CMakeLists.txt +++ b/test/reduce/CMakeLists.txt @@ -19,6 +19,7 @@ add_spvtools_unittest(TARGET reduce reduce_test_util.h reducer_test.cpp remove_unreferenced_instruction_reduction_pass_test.cpp + structured_loop_to_selection_reduction_pass_test.cpp LIBS SPIRV-Tools-reduce ) diff --git a/test/reduce/reduce_test_util.cpp b/test/reduce/reduce_test_util.cpp index 022e7e36..4561439f 100644 --- a/test/reduce/reduce_test_util.cpp +++ b/test/reduce/reduce_test_util.cpp @@ -48,5 +48,21 @@ void CheckEqual(const spv_target_env env, const std::string& expected_text, CheckEqual(env, expected_text, actual_binary); } +void CheckValid(spv_target_env env, const opt::IRContext* ir) { + std::vector binary; + ir->module()->ToBinary(&binary, false); + SpirvTools t(env); + ASSERT_TRUE(t.Validate(binary)); +} + +std::string ToString(spv_target_env env, const opt::IRContext* ir) { + std::vector binary; + ir->module()->ToBinary(&binary, false); + SpirvTools t(env); + std::string result; + t.Disassemble(binary, &result, kReduceDisassembleOption); + return result; +} + } // namespace reduce } // namespace spvtools diff --git a/test/reduce/reduce_test_util.h b/test/reduce/reduce_test_util.h index 0331799f..80cc0921 100644 --- a/test/reduce/reduce_test_util.h +++ b/test/reduce/reduce_test_util.h @@ -56,6 +56,14 @@ void CheckEqual(spv_target_env env, const std::string& expected_text, void CheckEqual(spv_target_env env, const std::string& expected_text, const opt::IRContext* actual_ir); +// Assembles the given IR context and checks whether the resulting binary is +// valid. +void CheckValid(spv_target_env env, const opt::IRContext* ir); + +// Assembles the given IR context, then returns its disassembly as a string. +// Useful for debugging. +std::string ToString(spv_target_env env, const opt::IRContext* ir); + // Assembly options for writing reduction tests. It simplifies matters if // numeric ids do not change. const uint32_t kReduceAssembleOption = diff --git a/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp b/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp new file mode 100644 index 00000000..3fc6dbfa --- /dev/null +++ b/test/reduce/structured_loop_to_selection_reduction_pass_test.cpp @@ -0,0 +1,3299 @@ +// Copyright (c) 2018 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 "source/reduce/structured_loop_to_selection_reduction_pass.h" +#include "reduce_test_util.h" +#include "source/opt/build_module.h" + +namespace spvtools { +namespace reduce { +namespace { + +TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader1) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 100 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSLessThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %19 = OpLoad %6 %8 + %21 = OpIAdd %6 %19 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 100 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %22 = OpConstantTrue %17 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %22 %14 %12 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSLessThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %12 + %13 = OpLabel + %19 = OpLoad %6 %8 + %21 = OpIAdd %6 %19 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader2) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 100 + %17 = OpTypeBool + %28 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %7 Function + %40 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSLessThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %25 = OpLoad %6 %19 + %26 = OpSLessThan %17 %25 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + OpBranch %23 + %23 = OpLabel + %27 = OpLoad %6 %19 + %29 = OpIAdd %6 %27 %28 + OpStore %19 %29 + OpBranch %20 + %22 = OpLabel + OpBranch %13 + %13 = OpLabel + %30 = OpLoad %6 %8 + %31 = OpIAdd %6 %30 %28 + OpStore %8 %31 + OpBranch %10 + %12 = OpLabel + OpStore %32 %9 + OpBranch %33 + %33 = OpLabel + OpLoopMerge %35 %36 None + OpBranch %37 + %37 = OpLabel + %38 = OpLoad %6 %32 + %39 = OpSLessThan %17 %38 %16 + OpBranchConditional %39 %34 %35 + %34 = OpLabel + OpStore %40 %9 + OpBranch %41 + %41 = OpLabel + OpLoopMerge %43 %44 None + OpBranch %45 + %45 = OpLabel + %46 = OpLoad %6 %40 + %47 = OpSLessThan %17 %46 %16 + OpBranchConditional %47 %42 %43 + %42 = OpLabel + OpBranch %44 + %44 = OpLabel + %48 = OpLoad %6 %40 + %49 = OpIAdd %6 %48 %28 + OpStore %40 %49 + OpBranch %41 + %43 = OpLabel + OpBranch %36 + %36 = OpLabel + %50 = OpLoad %6 %32 + %51 = OpIAdd %6 %50 %28 + OpStore %32 %51 + OpBranch %33 + %35 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(4, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 100 + %17 = OpTypeBool + %28 = OpConstant %6 1 + %52 = OpConstantTrue %17 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %7 Function + %40 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %52 %14 %12 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSLessThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %25 = OpLoad %6 %19 + %26 = OpSLessThan %17 %25 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + OpBranch %23 + %23 = OpLabel + %27 = OpLoad %6 %19 + %29 = OpIAdd %6 %27 %28 + OpStore %19 %29 + OpBranch %20 + %22 = OpLabel + OpBranch %12 + %13 = OpLabel + %30 = OpLoad %6 %8 + %31 = OpIAdd %6 %30 %28 + OpStore %8 %31 + OpBranch %10 + %12 = OpLabel + OpStore %32 %9 + OpBranch %33 + %33 = OpLabel + OpLoopMerge %35 %36 None + OpBranch %37 + %37 = OpLabel + %38 = OpLoad %6 %32 + %39 = OpSLessThan %17 %38 %16 + OpBranchConditional %39 %34 %35 + %34 = OpLabel + OpStore %40 %9 + OpBranch %41 + %41 = OpLabel + OpLoopMerge %43 %44 None + OpBranch %45 + %45 = OpLabel + %46 = OpLoad %6 %40 + %47 = OpSLessThan %17 %46 %16 + OpBranchConditional %47 %42 %43 + %42 = OpLabel + OpBranch %44 + %44 = OpLabel + %48 = OpLoad %6 %40 + %49 = OpIAdd %6 %48 %28 + OpStore %40 %49 + OpBranch %41 + %43 = OpLabel + OpBranch %36 + %36 = OpLabel + %50 = OpLoad %6 %32 + %51 = OpIAdd %6 %50 %28 + OpStore %32 %51 + OpBranch %33 + %35 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); + + ASSERT_TRUE(ops[1]->PreconditionHolds()); + ops[1]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_1 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 100 + %17 = OpTypeBool + %28 = OpConstant %6 1 + %52 = OpConstantTrue %17 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %7 Function + %40 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %52 %14 %12 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSLessThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + OpSelectionMerge %22 None + OpBranchConditional %52 %24 %22 + %24 = OpLabel + %25 = OpLoad %6 %19 + %26 = OpSLessThan %17 %25 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + OpBranch %22 + %23 = OpLabel + %27 = OpLoad %6 %19 + %29 = OpIAdd %6 %27 %28 + OpStore %19 %29 + OpBranch %20 + %22 = OpLabel + OpBranch %12 + %13 = OpLabel + %30 = OpLoad %6 %8 + %31 = OpIAdd %6 %30 %28 + OpStore %8 %31 + OpBranch %10 + %12 = OpLabel + OpStore %32 %9 + OpBranch %33 + %33 = OpLabel + OpLoopMerge %35 %36 None + OpBranch %37 + %37 = OpLabel + %38 = OpLoad %6 %32 + %39 = OpSLessThan %17 %38 %16 + OpBranchConditional %39 %34 %35 + %34 = OpLabel + OpStore %40 %9 + OpBranch %41 + %41 = OpLabel + OpLoopMerge %43 %44 None + OpBranch %45 + %45 = OpLabel + %46 = OpLoad %6 %40 + %47 = OpSLessThan %17 %46 %16 + OpBranchConditional %47 %42 %43 + %42 = OpLabel + OpBranch %44 + %44 = OpLabel + %48 = OpLoad %6 %40 + %49 = OpIAdd %6 %48 %28 + OpStore %40 %49 + OpBranch %41 + %43 = OpLabel + OpBranch %36 + %36 = OpLabel + %50 = OpLoad %6 %32 + %51 = OpIAdd %6 %50 %28 + OpStore %32 %51 + OpBranch %33 + %35 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_1, context.get()); + + ASSERT_TRUE(ops[2]->PreconditionHolds()); + ops[2]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_2 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 100 + %17 = OpTypeBool + %28 = OpConstant %6 1 + %52 = OpConstantTrue %17 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %7 Function + %40 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %52 %14 %12 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSLessThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + OpSelectionMerge %22 None + OpBranchConditional %52 %24 %22 + %24 = OpLabel + %25 = OpLoad %6 %19 + %26 = OpSLessThan %17 %25 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + OpBranch %22 + %23 = OpLabel + %27 = OpLoad %6 %19 + %29 = OpIAdd %6 %27 %28 + OpStore %19 %29 + OpBranch %20 + %22 = OpLabel + OpBranch %12 + %13 = OpLabel + %30 = OpLoad %6 %8 + %31 = OpIAdd %6 %30 %28 + OpStore %8 %31 + OpBranch %10 + %12 = OpLabel + OpStore %32 %9 + OpBranch %33 + %33 = OpLabel + OpSelectionMerge %35 None + OpBranchConditional %52 %37 %35 + %37 = OpLabel + %38 = OpLoad %6 %32 + %39 = OpSLessThan %17 %38 %16 + OpBranchConditional %39 %34 %35 + %34 = OpLabel + OpStore %40 %9 + OpBranch %41 + %41 = OpLabel + OpLoopMerge %43 %44 None + OpBranch %45 + %45 = OpLabel + %46 = OpLoad %6 %40 + %47 = OpSLessThan %17 %46 %16 + OpBranchConditional %47 %42 %43 + %42 = OpLabel + OpBranch %44 + %44 = OpLabel + %48 = OpLoad %6 %40 + %49 = OpIAdd %6 %48 %28 + OpStore %40 %49 + OpBranch %41 + %43 = OpLabel + OpBranch %35 + %36 = OpLabel + %50 = OpLoad %6 %32 + %51 = OpIAdd %6 %50 %28 + OpStore %32 %51 + OpBranch %33 + %35 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_2, context.get()); + + ASSERT_TRUE(ops[3]->PreconditionHolds()); + ops[3]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_3 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 100 + %17 = OpTypeBool + %28 = OpConstant %6 1 + %52 = OpConstantTrue %17 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %7 Function + %40 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %52 %14 %12 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSLessThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + OpSelectionMerge %22 None + OpBranchConditional %52 %24 %22 + %24 = OpLabel + %25 = OpLoad %6 %19 + %26 = OpSLessThan %17 %25 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + OpBranch %22 + %23 = OpLabel + %27 = OpLoad %6 %19 + %29 = OpIAdd %6 %27 %28 + OpStore %19 %29 + OpBranch %20 + %22 = OpLabel + OpBranch %12 + %13 = OpLabel + %30 = OpLoad %6 %8 + %31 = OpIAdd %6 %30 %28 + OpStore %8 %31 + OpBranch %10 + %12 = OpLabel + OpStore %32 %9 + OpBranch %33 + %33 = OpLabel + OpSelectionMerge %35 None + OpBranchConditional %52 %37 %35 + %37 = OpLabel + %38 = OpLoad %6 %32 + %39 = OpSLessThan %17 %38 %16 + OpBranchConditional %39 %34 %35 + %34 = OpLabel + OpStore %40 %9 + OpBranch %41 + %41 = OpLabel + OpSelectionMerge %43 None + OpBranchConditional %52 %45 %43 + %45 = OpLabel + %46 = OpLoad %6 %40 + %47 = OpSLessThan %17 %46 %16 + OpBranchConditional %47 %42 %43 + %42 = OpLabel + OpBranch %43 + %44 = OpLabel + %48 = OpLoad %6 %40 + %49 = OpIAdd %6 %48 %28 + OpStore %40 %49 + OpBranch %41 + %43 = OpLabel + OpBranch %35 + %36 = OpLabel + %50 = OpLoad %6 %32 + %51 = OpIAdd %6 %50 %28 + OpStore %32 %51 + OpBranch %33 + %35 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_3, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader3) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 10 + %16 = OpConstant %6 0 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %23 = OpConstant %6 3 + %40 = OpConstant %6 5 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %15 = OpLoad %6 %8 + %18 = OpSGreaterThan %17 %15 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %19 = OpLoad %6 %8 + %21 = OpISub %6 %19 %20 + OpStore %8 %21 + %22 = OpLoad %6 %8 + %24 = OpSLessThan %17 %22 %23 + OpSelectionMerge %26 None + OpBranchConditional %24 %25 %26 + %25 = OpLabel + OpBranch %13 + %26 = OpLabel + OpBranch %28 + %28 = OpLabel + OpLoopMerge %30 %31 None + OpBranch %29 + %29 = OpLabel + %32 = OpLoad %6 %8 + %33 = OpISub %6 %32 %20 + OpStore %8 %33 + %34 = OpLoad %6 %8 + %35 = OpIEqual %17 %34 %20 + OpSelectionMerge %37 None + OpBranchConditional %35 %36 %37 + %36 = OpLabel + OpReturn ; This return spoils everything: it means the merge does not post-dominate the header. + %37 = OpLabel + OpBranch %31 + %31 = OpLabel + %39 = OpLoad %6 %8 + %41 = OpSGreaterThan %17 %39 %40 + OpBranchConditional %41 %28 %30 + %30 = OpLabel + OpBranch %13 + %13 = OpLabel + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(0, ops.size()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, LoopyShader4) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %6 %7 + %13 = OpConstant %6 0 + %22 = OpTypeBool + %25 = OpConstant %6 1 + %39 = OpConstant %6 100 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %45 = OpVariable %7 Function + %46 = OpVariable %7 Function + %47 = OpVariable %7 Function + %32 = OpVariable %7 Function + %42 = OpVariable %7 Function + OpStore %32 %13 + OpBranch %33 + %33 = OpLabel + OpLoopMerge %35 %36 None + OpBranch %37 + %37 = OpLabel + %38 = OpLoad %6 %32 + %40 = OpSLessThan %22 %38 %39 + OpBranchConditional %40 %34 %35 + %34 = OpLabel + OpBranch %36 + %36 = OpLabel + %41 = OpLoad %6 %32 + OpStore %42 %25 + OpStore %45 %13 + OpStore %46 %13 + OpBranch %48 + %48 = OpLabel + OpLoopMerge %49 %50 None + OpBranch %51 + %51 = OpLabel + %52 = OpLoad %6 %46 + %53 = OpLoad %6 %42 + %54 = OpSLessThan %22 %52 %53 + OpBranchConditional %54 %55 %49 + %55 = OpLabel + %56 = OpLoad %6 %45 + %57 = OpIAdd %6 %56 %25 + OpStore %45 %57 + OpBranch %50 + %50 = OpLabel + %58 = OpLoad %6 %46 + %59 = OpIAdd %6 %58 %25 + OpStore %46 %59 + OpBranch %48 + %49 = OpLabel + %60 = OpLoad %6 %45 + OpStore %47 %60 + %43 = OpLoad %6 %47 + %44 = OpIAdd %6 %41 %43 + OpStore %32 %44 + OpBranch %33 + %35 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + // Initially there are two opportunities. + ASSERT_EQ(2, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %8 = OpTypeFunction %6 %7 + %13 = OpConstant %6 0 + %22 = OpTypeBool + %25 = OpConstant %6 1 + %39 = OpConstant %6 100 + %61 = OpConstantTrue %22 + %62 = OpUndef %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %45 = OpVariable %7 Function + %46 = OpVariable %7 Function + %47 = OpVariable %7 Function + %32 = OpVariable %7 Function + %42 = OpVariable %7 Function + OpStore %32 %13 + OpBranch %33 + %33 = OpLabel + OpSelectionMerge %35 None + OpBranchConditional %61 %37 %35 + %37 = OpLabel + %38 = OpLoad %6 %32 + %40 = OpSLessThan %22 %38 %39 + OpBranchConditional %40 %34 %35 + %34 = OpLabel + OpBranch %35 + %36 = OpLabel + %41 = OpLoad %6 %32 + OpStore %42 %25 + OpStore %45 %13 + OpStore %46 %13 + OpBranch %48 + %48 = OpLabel + OpLoopMerge %49 %50 None + OpBranch %51 + %51 = OpLabel + %52 = OpLoad %6 %46 + %53 = OpLoad %6 %42 + %54 = OpSLessThan %22 %52 %53 + OpBranchConditional %54 %55 %49 + %55 = OpLabel + %56 = OpLoad %6 %45 + %57 = OpIAdd %6 %56 %25 + OpStore %45 %57 + OpBranch %50 + %50 = OpLabel + %58 = OpLoad %6 %46 + %59 = OpIAdd %6 %58 %25 + OpStore %46 %59 + OpBranch %48 + %49 = OpLabel + %60 = OpLoad %6 %45 + OpStore %47 %60 + %43 = OpLoad %6 %47 + %44 = OpIAdd %6 %62 %43 + OpStore %32 %44 + OpBranch %33 + %35 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); + + // Applying the first opportunity has killed the second opportunity, because + // there was a loop embedded in the continue target of the loop we have just + // eliminated; the continue-embedded loop is now unreachable. + ASSERT_FALSE(ops[1]->PreconditionHolds()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak1) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %10 = OpTypeBool + %11 = OpConstantFalse %10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpLoopMerge %8 %9 None + OpBranch %7 + %7 = OpLabel + OpSelectionMerge %13 None + OpBranchConditional %11 %12 %13 + %12 = OpLabel + OpBranch %8 + %13 = OpLabel + OpBranch %9 + %9 = OpLabel + OpBranchConditional %11 %6 %8 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %10 = OpTypeBool + %11 = OpConstantFalse %10 + %14 = OpConstantTrue %10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpSelectionMerge %8 None + OpBranchConditional %14 %7 %8 + %7 = OpLabel + OpSelectionMerge %13 None + OpBranchConditional %11 %12 %13 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel + OpBranch %8 + %9 = OpLabel + OpBranchConditional %11 %6 %8 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, ConditionalBreak2) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %10 = OpTypeBool + %11 = OpConstantFalse %10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpLoopMerge %8 %9 None + OpBranch %7 + %7 = OpLabel + OpSelectionMerge %13 None + OpBranchConditional %11 %8 %13 + %13 = OpLabel + OpBranch %9 + %9 = OpLabel + OpBranchConditional %11 %6 %8 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %10 = OpTypeBool + %11 = OpConstantFalse %10 + %14 = OpConstantTrue %10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpSelectionMerge %8 None + OpBranchConditional %14 %7 %8 + %7 = OpLabel + OpSelectionMerge %13 None + OpBranchConditional %11 %13 %13 + %13 = OpLabel + OpBranch %8 + %9 = OpLabel + OpBranchConditional %11 %6 %8 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, UnconditionalBreak) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpLoopMerge %8 %9 None + OpBranch %7 + %7 = OpLabel + OpBranch %8 + %9 = OpLabel + OpBranch %6 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpName %4 "main" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %10 = OpTypeBool + %11 = OpConstantTrue %10 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpSelectionMerge %8 None + OpBranchConditional %11 %7 %8 + %7 = OpLabel + OpBranch %8 + %9 = OpLabel + OpBranch %6 + %8 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, Complex) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpMemberDecorate %4 0 Offset 0 + OpMemberDecorate %4 1 Offset 4 + OpMemberDecorate %4 2 Offset 8 + OpMemberDecorate %4 3 Offset 12 + OpDecorate %4 Block + OpDecorate %5 DescriptorSet 0 + OpDecorate %5 Binding 0 + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeBool + %9 = OpTypePointer Function %8 + %10 = OpTypeInt 32 1 + %4 = OpTypeStruct %10 %10 %10 %10 + %11 = OpTypePointer Uniform %4 + %5 = OpVariable %11 Uniform + %12 = OpConstant %10 0 + %13 = OpTypePointer Uniform %10 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %10 1 + %17 = OpConstant %10 2 + %18 = OpConstant %10 3 + %19 = OpTypePointer Function %10 + %20 = OpConstantFalse %8 + %21 = OpTypeFloat 32 + %22 = OpTypeVector %21 4 + %23 = OpTypePointer Output %22 + %3 = OpVariable %23 Output + %2 = OpFunction %6 None %7 + %24 = OpLabel + %25 = OpVariable %9 Function + %26 = OpVariable %9 Function + %27 = OpVariable %9 Function + %28 = OpVariable %9 Function + %29 = OpVariable %9 Function + %30 = OpVariable %19 Function + %31 = OpAccessChain %13 %5 %12 + %32 = OpLoad %10 %31 + %33 = OpINotEqual %8 %32 %15 + OpStore %25 %33 + %34 = OpAccessChain %13 %5 %16 + %35 = OpLoad %10 %34 + %36 = OpINotEqual %8 %35 %15 + OpStore %26 %36 + %37 = OpAccessChain %13 %5 %17 + %38 = OpLoad %10 %37 + %39 = OpINotEqual %8 %38 %15 + OpStore %27 %39 + %40 = OpAccessChain %13 %5 %18 + %41 = OpLoad %10 %40 + %42 = OpINotEqual %8 %41 %15 + OpStore %28 %42 + %43 = OpLoad %8 %25 + OpStore %29 %43 + OpStore %30 %12 + OpBranch %44 + %44 = OpLabel + OpLoopMerge %45 %46 None + OpBranch %47 + %47 = OpLabel + %48 = OpLoad %8 %29 + OpBranchConditional %48 %49 %45 + %49 = OpLabel + %50 = OpLoad %8 %25 + OpSelectionMerge %51 None + OpBranchConditional %50 %52 %51 + %52 = OpLabel + %53 = OpLoad %8 %26 + OpStore %29 %53 + %54 = OpLoad %10 %30 + %55 = OpIAdd %10 %54 %16 + OpStore %30 %55 + OpBranch %51 + %51 = OpLabel + %56 = OpLoad %8 %26 + OpSelectionMerge %57 None + OpBranchConditional %56 %58 %57 + %58 = OpLabel + %59 = OpLoad %10 %30 + %60 = OpIAdd %10 %59 %16 + OpStore %30 %60 + %61 = OpLoad %8 %29 + %62 = OpLoad %8 %25 + %63 = OpLogicalOr %8 %61 %62 + OpStore %29 %63 + %64 = OpLoad %8 %27 + OpSelectionMerge %65 None + OpBranchConditional %64 %66 %65 + %66 = OpLabel + %67 = OpLoad %10 %30 + %68 = OpIAdd %10 %67 %17 + OpStore %30 %68 + %69 = OpLoad %8 %29 + %70 = OpLogicalNot %8 %69 + OpStore %29 %70 + OpBranch %46 + %65 = OpLabel + %71 = OpLoad %8 %29 + %72 = OpLogicalOr %8 %71 %20 + OpStore %29 %72 + OpBranch %46 + %57 = OpLabel + OpBranch %73 + %73 = OpLabel + OpLoopMerge %74 %75 None + OpBranch %76 + %76 = OpLabel + %77 = OpLoad %8 %28 + OpSelectionMerge %78 None + OpBranchConditional %77 %79 %80 + %79 = OpLabel + %81 = OpLoad %10 %30 + OpSelectionMerge %82 None + OpSwitch %81 %83 1 %84 2 %85 + %83 = OpLabel + OpBranch %82 + %84 = OpLabel + %86 = OpLoad %8 %29 + %87 = OpSelect %10 %86 %16 %17 + %88 = OpLoad %10 %30 + %89 = OpIAdd %10 %88 %87 + OpStore %30 %89 + OpBranch %82 + %85 = OpLabel + OpBranch %75 + %82 = OpLabel + %90 = OpLoad %8 %27 + OpSelectionMerge %91 None + OpBranchConditional %90 %92 %91 + %92 = OpLabel + OpBranch %75 + %91 = OpLabel + OpBranch %78 + %80 = OpLabel + OpBranch %74 + %78 = OpLabel + OpBranch %75 + %75 = OpLabel + %93 = OpLoad %8 %29 + OpBranchConditional %93 %73 %74 + %74 = OpLabel + OpBranch %46 + %46 = OpLabel + OpBranch %44 + %45 = OpLabel + %94 = OpLoad %10 %30 + %95 = OpConvertSToF %21 %94 + %96 = OpCompositeConstruct %22 %95 %95 %95 %95 + OpStore %3 %96 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + ASSERT_EQ(2, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpMemberDecorate %4 0 Offset 0 + OpMemberDecorate %4 1 Offset 4 + OpMemberDecorate %4 2 Offset 8 + OpMemberDecorate %4 3 Offset 12 + OpDecorate %4 Block + OpDecorate %5 DescriptorSet 0 + OpDecorate %5 Binding 0 + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeBool + %9 = OpTypePointer Function %8 + %10 = OpTypeInt 32 1 + %4 = OpTypeStruct %10 %10 %10 %10 + %11 = OpTypePointer Uniform %4 + %5 = OpVariable %11 Uniform + %12 = OpConstant %10 0 + %13 = OpTypePointer Uniform %10 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %10 1 + %17 = OpConstant %10 2 + %18 = OpConstant %10 3 + %19 = OpTypePointer Function %10 + %20 = OpConstantFalse %8 + %21 = OpTypeFloat 32 + %22 = OpTypeVector %21 4 + %23 = OpTypePointer Output %22 + %3 = OpVariable %23 Output + %97 = OpConstantTrue %8 + %2 = OpFunction %6 None %7 + %24 = OpLabel + %25 = OpVariable %9 Function + %26 = OpVariable %9 Function + %27 = OpVariable %9 Function + %28 = OpVariable %9 Function + %29 = OpVariable %9 Function + %30 = OpVariable %19 Function + %31 = OpAccessChain %13 %5 %12 + %32 = OpLoad %10 %31 + %33 = OpINotEqual %8 %32 %15 + OpStore %25 %33 + %34 = OpAccessChain %13 %5 %16 + %35 = OpLoad %10 %34 + %36 = OpINotEqual %8 %35 %15 + OpStore %26 %36 + %37 = OpAccessChain %13 %5 %17 + %38 = OpLoad %10 %37 + %39 = OpINotEqual %8 %38 %15 + OpStore %27 %39 + %40 = OpAccessChain %13 %5 %18 + %41 = OpLoad %10 %40 + %42 = OpINotEqual %8 %41 %15 + OpStore %28 %42 + %43 = OpLoad %8 %25 + OpStore %29 %43 + OpStore %30 %12 + OpBranch %44 + %44 = OpLabel + OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None + OpBranchConditional %97 %47 %45 ; Was OpBranch %47 + %47 = OpLabel + %48 = OpLoad %8 %29 + OpBranchConditional %48 %49 %45 + %49 = OpLabel + %50 = OpLoad %8 %25 + OpSelectionMerge %51 None + OpBranchConditional %50 %52 %51 + %52 = OpLabel + %53 = OpLoad %8 %26 + OpStore %29 %53 + %54 = OpLoad %10 %30 + %55 = OpIAdd %10 %54 %16 + OpStore %30 %55 + OpBranch %51 + %51 = OpLabel + %56 = OpLoad %8 %26 + OpSelectionMerge %57 None + OpBranchConditional %56 %58 %57 + %58 = OpLabel + %59 = OpLoad %10 %30 + %60 = OpIAdd %10 %59 %16 + OpStore %30 %60 + %61 = OpLoad %8 %29 + %62 = OpLoad %8 %25 + %63 = OpLogicalOr %8 %61 %62 + OpStore %29 %63 + %64 = OpLoad %8 %27 + OpSelectionMerge %65 None + OpBranchConditional %64 %66 %65 + %66 = OpLabel + %67 = OpLoad %10 %30 + %68 = OpIAdd %10 %67 %17 + OpStore %30 %68 + %69 = OpLoad %8 %29 + %70 = OpLogicalNot %8 %69 + OpStore %29 %70 + OpBranch %65 ; Was OpBranch %46 + %65 = OpLabel + %71 = OpLoad %8 %29 + %72 = OpLogicalOr %8 %71 %20 + OpStore %29 %72 + OpBranch %57 ; Was OpBranch %46 + %57 = OpLabel + OpBranch %73 + %73 = OpLabel + OpLoopMerge %74 %75 None + OpBranch %76 + %76 = OpLabel + %77 = OpLoad %8 %28 + OpSelectionMerge %78 None + OpBranchConditional %77 %79 %80 + %79 = OpLabel + %81 = OpLoad %10 %30 + OpSelectionMerge %82 None + OpSwitch %81 %83 1 %84 2 %85 + %83 = OpLabel + OpBranch %82 + %84 = OpLabel + %86 = OpLoad %8 %29 + %87 = OpSelect %10 %86 %16 %17 + %88 = OpLoad %10 %30 + %89 = OpIAdd %10 %88 %87 + OpStore %30 %89 + OpBranch %82 + %85 = OpLabel + OpBranch %75 + %82 = OpLabel + %90 = OpLoad %8 %27 + OpSelectionMerge %91 None + OpBranchConditional %90 %92 %91 + %92 = OpLabel + OpBranch %75 + %91 = OpLabel + OpBranch %78 + %80 = OpLabel + OpBranch %74 + %78 = OpLabel + OpBranch %75 + %75 = OpLabel + %93 = OpLoad %8 %29 + OpBranchConditional %93 %73 %74 + %74 = OpLabel + OpBranch %45 ; Was OpBranch %46 + %46 = OpLabel + OpBranch %44 + %45 = OpLabel + %94 = OpLoad %10 %30 + %95 = OpConvertSToF %21 %94 + %96 = OpCompositeConstruct %22 %95 %95 %95 %95 + OpStore %3 %96 + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); + ASSERT_TRUE(ops[1]->PreconditionHolds()); + ops[1]->TryToApply(); + CheckValid(env, context.get()); + + std::string after_op_1 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpMemberDecorate %4 0 Offset 0 + OpMemberDecorate %4 1 Offset 4 + OpMemberDecorate %4 2 Offset 8 + OpMemberDecorate %4 3 Offset 12 + OpDecorate %4 Block + OpDecorate %5 DescriptorSet 0 + OpDecorate %5 Binding 0 + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeBool + %9 = OpTypePointer Function %8 + %10 = OpTypeInt 32 1 + %4 = OpTypeStruct %10 %10 %10 %10 + %11 = OpTypePointer Uniform %4 + %5 = OpVariable %11 Uniform + %12 = OpConstant %10 0 + %13 = OpTypePointer Uniform %10 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %10 1 + %17 = OpConstant %10 2 + %18 = OpConstant %10 3 + %19 = OpTypePointer Function %10 + %20 = OpConstantFalse %8 + %21 = OpTypeFloat 32 + %22 = OpTypeVector %21 4 + %23 = OpTypePointer Output %22 + %3 = OpVariable %23 Output + %97 = OpConstantTrue %8 + %2 = OpFunction %6 None %7 + %24 = OpLabel + %25 = OpVariable %9 Function + %26 = OpVariable %9 Function + %27 = OpVariable %9 Function + %28 = OpVariable %9 Function + %29 = OpVariable %9 Function + %30 = OpVariable %19 Function + %31 = OpAccessChain %13 %5 %12 + %32 = OpLoad %10 %31 + %33 = OpINotEqual %8 %32 %15 + OpStore %25 %33 + %34 = OpAccessChain %13 %5 %16 + %35 = OpLoad %10 %34 + %36 = OpINotEqual %8 %35 %15 + OpStore %26 %36 + %37 = OpAccessChain %13 %5 %17 + %38 = OpLoad %10 %37 + %39 = OpINotEqual %8 %38 %15 + OpStore %27 %39 + %40 = OpAccessChain %13 %5 %18 + %41 = OpLoad %10 %40 + %42 = OpINotEqual %8 %41 %15 + OpStore %28 %42 + %43 = OpLoad %8 %25 + OpStore %29 %43 + OpStore %30 %12 + OpBranch %44 + %44 = OpLabel + OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None + OpBranchConditional %97 %47 %45 ; Was OpBranch %47 + %47 = OpLabel + %48 = OpLoad %8 %29 + OpBranchConditional %48 %49 %45 + %49 = OpLabel + %50 = OpLoad %8 %25 + OpSelectionMerge %51 None + OpBranchConditional %50 %52 %51 + %52 = OpLabel + %53 = OpLoad %8 %26 + OpStore %29 %53 + %54 = OpLoad %10 %30 + %55 = OpIAdd %10 %54 %16 + OpStore %30 %55 + OpBranch %51 + %51 = OpLabel + %56 = OpLoad %8 %26 + OpSelectionMerge %57 None + OpBranchConditional %56 %58 %57 + %58 = OpLabel + %59 = OpLoad %10 %30 + %60 = OpIAdd %10 %59 %16 + OpStore %30 %60 + %61 = OpLoad %8 %29 + %62 = OpLoad %8 %25 + %63 = OpLogicalOr %8 %61 %62 + OpStore %29 %63 + %64 = OpLoad %8 %27 + OpSelectionMerge %65 None + OpBranchConditional %64 %66 %65 + %66 = OpLabel + %67 = OpLoad %10 %30 + %68 = OpIAdd %10 %67 %17 + OpStore %30 %68 + %69 = OpLoad %8 %29 + %70 = OpLogicalNot %8 %69 + OpStore %29 %70 + OpBranch %65 ; Was OpBranch %46 + %65 = OpLabel + %71 = OpLoad %8 %29 + %72 = OpLogicalOr %8 %71 %20 + OpStore %29 %72 + OpBranch %57 ; Was OpBranch %46 + %57 = OpLabel + OpBranch %73 + %73 = OpLabel + OpSelectionMerge %74 None ; Was OpLoopMerge %74 %75 None + OpBranchConditional %97 %76 %74 ; Was OpBranch %76 + %76 = OpLabel + %77 = OpLoad %8 %28 + OpSelectionMerge %78 None + OpBranchConditional %77 %79 %80 + %79 = OpLabel + %81 = OpLoad %10 %30 + OpSelectionMerge %82 None + OpSwitch %81 %83 1 %84 2 %85 + %83 = OpLabel + OpBranch %82 + %84 = OpLabel + %86 = OpLoad %8 %29 + %87 = OpSelect %10 %86 %16 %17 + %88 = OpLoad %10 %30 + %89 = OpIAdd %10 %88 %87 + OpStore %30 %89 + OpBranch %82 + %85 = OpLabel + OpBranch %82 + %82 = OpLabel + %90 = OpLoad %8 %27 + OpSelectionMerge %91 None + OpBranchConditional %90 %92 %91 + %92 = OpLabel + OpBranch %91 + %91 = OpLabel + OpBranch %78 + %80 = OpLabel + OpBranch %78 ; Was OpBranch %74 + %78 = OpLabel + OpBranch %74 + %75 = OpLabel + %93 = OpLoad %8 %29 + OpBranchConditional %93 %73 %74 + %74 = OpLabel + OpBranch %45 ; Was OpBranch %46 + %46 = OpLabel + OpBranch %44 + %45 = OpLabel + %94 = OpLoad %10 %30 + %95 = OpConvertSToF %21 %94 + %96 = OpCompositeConstruct %22 %95 %95 %95 %95 + OpStore %3 %96 + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_1, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, ComplexOptimized) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpMemberDecorate %4 0 Offset 0 + OpMemberDecorate %4 1 Offset 4 + OpMemberDecorate %4 2 Offset 8 + OpMemberDecorate %4 3 Offset 12 + OpDecorate %4 Block + OpDecorate %5 DescriptorSet 0 + OpDecorate %5 Binding 0 + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeBool + %10 = OpTypeInt 32 1 + %4 = OpTypeStruct %10 %10 %10 %10 + %11 = OpTypePointer Uniform %4 + %5 = OpVariable %11 Uniform + %12 = OpConstant %10 0 + %13 = OpTypePointer Uniform %10 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %10 1 + %17 = OpConstant %10 2 + %18 = OpConstant %10 3 + %20 = OpConstantFalse %8 + %21 = OpTypeFloat 32 + %22 = OpTypeVector %21 4 + %23 = OpTypePointer Output %22 + %3 = OpVariable %23 Output + %2 = OpFunction %6 None %7 + %24 = OpLabel + %31 = OpAccessChain %13 %5 %12 + %32 = OpLoad %10 %31 + %33 = OpINotEqual %8 %32 %15 + %34 = OpAccessChain %13 %5 %16 + %35 = OpLoad %10 %34 + %36 = OpINotEqual %8 %35 %15 + %37 = OpAccessChain %13 %5 %17 + %38 = OpLoad %10 %37 + %39 = OpINotEqual %8 %38 %15 + %40 = OpAccessChain %13 %5 %18 + %41 = OpLoad %10 %40 + %42 = OpINotEqual %8 %41 %15 + OpBranch %44 + %44 = OpLabel + %98 = OpPhi %10 %12 %24 %107 %46 + %97 = OpPhi %8 %33 %24 %105 %46 + OpLoopMerge %45 %46 None + OpBranchConditional %97 %49 %45 + %49 = OpLabel + OpSelectionMerge %51 None + OpBranchConditional %33 %52 %51 + %52 = OpLabel + %55 = OpIAdd %10 %98 %16 + OpBranch %51 + %51 = OpLabel + %100 = OpPhi %10 %98 %49 %55 %52 + %113 = OpSelect %8 %33 %36 %97 + OpSelectionMerge %57 None + OpBranchConditional %36 %58 %57 + %58 = OpLabel + %60 = OpIAdd %10 %100 %16 + %63 = OpLogicalOr %8 %113 %33 + OpSelectionMerge %65 None + OpBranchConditional %39 %66 %65 + %66 = OpLabel + %68 = OpIAdd %10 %100 %18 + %70 = OpLogicalNot %8 %63 + OpBranch %46 + %65 = OpLabel + %72 = OpLogicalOr %8 %63 %20 + OpBranch %46 + %57 = OpLabel + OpBranch %73 + %73 = OpLabel + %99 = OpPhi %10 %100 %57 %109 %75 + OpLoopMerge %74 %75 None + OpBranch %76 + %76 = OpLabel + OpSelectionMerge %78 None + OpBranchConditional %42 %79 %80 + %79 = OpLabel + OpSelectionMerge %82 None + OpSwitch %99 %83 1 %84 2 %85 + %83 = OpLabel + OpBranch %82 + %84 = OpLabel + %87 = OpSelect %10 %113 %16 %17 + %89 = OpIAdd %10 %99 %87 + OpBranch %82 + %85 = OpLabel + OpBranch %75 + %82 = OpLabel + %110 = OpPhi %10 %99 %83 %89 %84 + OpSelectionMerge %91 None + OpBranchConditional %39 %92 %91 + %92 = OpLabel + OpBranch %75 + %91 = OpLabel + OpBranch %78 + %80 = OpLabel + OpBranch %74 + %78 = OpLabel + OpBranch %75 + %75 = OpLabel + %109 = OpPhi %10 %99 %85 %110 %92 %110 %78 + OpBranchConditional %113 %73 %74 + %74 = OpLabel + %108 = OpPhi %10 %99 %80 %109 %75 + OpBranch %46 + %46 = OpLabel + %107 = OpPhi %10 %68 %66 %60 %65 %108 %74 + %105 = OpPhi %8 %70 %66 %72 %65 %113 %74 + OpBranch %44 + %45 = OpLabel + %95 = OpConvertSToF %21 %98 + %96 = OpCompositeConstruct %22 %95 %95 %95 %95 + OpStore %3 %96 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + ASSERT_EQ(2, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpMemberDecorate %4 0 Offset 0 + OpMemberDecorate %4 1 Offset 4 + OpMemberDecorate %4 2 Offset 8 + OpMemberDecorate %4 3 Offset 12 + OpDecorate %4 Block + OpDecorate %5 DescriptorSet 0 + OpDecorate %5 Binding 0 + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeBool + %10 = OpTypeInt 32 1 + %4 = OpTypeStruct %10 %10 %10 %10 + %11 = OpTypePointer Uniform %4 + %5 = OpVariable %11 Uniform + %12 = OpConstant %10 0 + %13 = OpTypePointer Uniform %10 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %10 1 + %17 = OpConstant %10 2 + %18 = OpConstant %10 3 + %20 = OpConstantFalse %8 + %21 = OpTypeFloat 32 + %22 = OpTypeVector %21 4 + %23 = OpTypePointer Output %22 + %3 = OpVariable %23 Output + %114 = OpUndef %10 + %115 = OpUndef %8 + %2 = OpFunction %6 None %7 + %24 = OpLabel + %31 = OpAccessChain %13 %5 %12 + %32 = OpLoad %10 %31 + %33 = OpINotEqual %8 %32 %15 + %34 = OpAccessChain %13 %5 %16 + %35 = OpLoad %10 %34 + %36 = OpINotEqual %8 %35 %15 + %37 = OpAccessChain %13 %5 %17 + %38 = OpLoad %10 %37 + %39 = OpINotEqual %8 %38 %15 + %40 = OpAccessChain %13 %5 %18 + %41 = OpLoad %10 %40 + %42 = OpINotEqual %8 %41 %15 + OpBranch %44 + %44 = OpLabel + %98 = OpPhi %10 %12 %24 %114 %46 + %97 = OpPhi %8 %33 %24 %115 %46 + OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None + OpBranchConditional %97 %49 %45 + %49 = OpLabel + OpSelectionMerge %51 None + OpBranchConditional %33 %52 %51 + %52 = OpLabel + %55 = OpIAdd %10 %98 %16 + OpBranch %51 + %51 = OpLabel + %100 = OpPhi %10 %98 %49 %55 %52 + %113 = OpSelect %8 %33 %36 %97 + OpSelectionMerge %57 None + OpBranchConditional %36 %58 %57 + %58 = OpLabel + %60 = OpIAdd %10 %100 %16 + %63 = OpLogicalOr %8 %113 %33 + OpSelectionMerge %65 None + OpBranchConditional %39 %66 %65 + %66 = OpLabel + %68 = OpIAdd %10 %100 %18 + %70 = OpLogicalNot %8 %63 + OpBranch %65 ; Was OpBranch %46 + %65 = OpLabel + %72 = OpLogicalOr %8 %63 %20 + OpBranch %57 ; Was OpBranch %46 + %57 = OpLabel + OpBranch %73 + %73 = OpLabel + %99 = OpPhi %10 %100 %57 %109 %75 + OpLoopMerge %74 %75 None + OpBranch %76 + %76 = OpLabel + OpSelectionMerge %78 None + OpBranchConditional %42 %79 %80 + %79 = OpLabel + OpSelectionMerge %82 None + OpSwitch %99 %83 1 %84 2 %85 + %83 = OpLabel + OpBranch %82 + %84 = OpLabel + %87 = OpSelect %10 %113 %16 %17 + %89 = OpIAdd %10 %99 %87 + OpBranch %82 + %85 = OpLabel + OpBranch %75 + %82 = OpLabel + %110 = OpPhi %10 %99 %83 %89 %84 + OpSelectionMerge %91 None + OpBranchConditional %39 %92 %91 + %92 = OpLabel + OpBranch %75 + %91 = OpLabel + OpBranch %78 + %80 = OpLabel + OpBranch %74 + %78 = OpLabel + OpBranch %75 + %75 = OpLabel + %109 = OpPhi %10 %99 %85 %110 %92 %110 %78 + OpBranchConditional %113 %73 %74 + %74 = OpLabel + %108 = OpPhi %10 %99 %80 %109 %75 + OpBranch %45 ; Was OpBranch %46 + %46 = OpLabel + %107 = OpPhi %10 ; Was OpPhi %10 %68 %66 %60 %65 %108 %74 + %105 = OpPhi %8 ; Was OpPhi %8 %70 %66 %72 %65 %113 %74 + OpBranch %44 + %45 = OpLabel + %95 = OpConvertSToF %21 %98 + %96 = OpCompositeConstruct %22 %95 %95 %95 %95 + OpStore %3 %96 + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); + + ASSERT_TRUE(ops[1]->PreconditionHolds()); + ops[1]->TryToApply(); + CheckValid(env, context.get()); + std::string after_op_1 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" %3 + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + OpMemberDecorate %4 0 Offset 0 + OpMemberDecorate %4 1 Offset 4 + OpMemberDecorate %4 2 Offset 8 + OpMemberDecorate %4 3 Offset 12 + OpDecorate %4 Block + OpDecorate %5 DescriptorSet 0 + OpDecorate %5 Binding 0 + OpDecorate %3 Location 0 + %6 = OpTypeVoid + %7 = OpTypeFunction %6 + %8 = OpTypeBool + %10 = OpTypeInt 32 1 + %4 = OpTypeStruct %10 %10 %10 %10 + %11 = OpTypePointer Uniform %4 + %5 = OpVariable %11 Uniform + %12 = OpConstant %10 0 + %13 = OpTypePointer Uniform %10 + %14 = OpTypeInt 32 0 + %15 = OpConstant %14 0 + %16 = OpConstant %10 1 + %17 = OpConstant %10 2 + %18 = OpConstant %10 3 + %20 = OpConstantFalse %8 + %21 = OpTypeFloat 32 + %22 = OpTypeVector %21 4 + %23 = OpTypePointer Output %22 + %3 = OpVariable %23 Output + %114 = OpUndef %10 + %115 = OpUndef %8 + %116 = OpConstantTrue %8 + %2 = OpFunction %6 None %7 + %24 = OpLabel + %31 = OpAccessChain %13 %5 %12 + %32 = OpLoad %10 %31 + %33 = OpINotEqual %8 %32 %15 + %34 = OpAccessChain %13 %5 %16 + %35 = OpLoad %10 %34 + %36 = OpINotEqual %8 %35 %15 + %37 = OpAccessChain %13 %5 %17 + %38 = OpLoad %10 %37 + %39 = OpINotEqual %8 %38 %15 + %40 = OpAccessChain %13 %5 %18 + %41 = OpLoad %10 %40 + %42 = OpINotEqual %8 %41 %15 + OpBranch %44 + %44 = OpLabel + %98 = OpPhi %10 %12 %24 %114 %46 + %97 = OpPhi %8 %33 %24 %115 %46 + OpSelectionMerge %45 None ; Was OpLoopMerge %45 %46 None + OpBranchConditional %97 %49 %45 + %49 = OpLabel + OpSelectionMerge %51 None + OpBranchConditional %33 %52 %51 + %52 = OpLabel + %55 = OpIAdd %10 %98 %16 + OpBranch %51 + %51 = OpLabel + %100 = OpPhi %10 %98 %49 %55 %52 + %113 = OpSelect %8 %33 %36 %97 + OpSelectionMerge %57 None + OpBranchConditional %36 %58 %57 + %58 = OpLabel + %60 = OpIAdd %10 %100 %16 + %63 = OpLogicalOr %8 %113 %33 + OpSelectionMerge %65 None + OpBranchConditional %39 %66 %65 + %66 = OpLabel + %68 = OpIAdd %10 %100 %18 + %70 = OpLogicalNot %8 %63 + OpBranch %65 ; Was OpBranch %46 + %65 = OpLabel + %72 = OpLogicalOr %8 %63 %20 + OpBranch %57 ; Was OpBranch %46 + %57 = OpLabel + OpBranch %73 + %73 = OpLabel + %99 = OpPhi %10 %100 %57 %114 %75 + OpSelectionMerge %74 None ; Was OpLoopMerge %74 %75 None + OpBranchConditional %116 %76 %74 + %76 = OpLabel + OpSelectionMerge %78 None + OpBranchConditional %42 %79 %80 + %79 = OpLabel + OpSelectionMerge %82 None + OpSwitch %99 %83 1 %84 2 %85 + %83 = OpLabel + OpBranch %82 + %84 = OpLabel + %87 = OpSelect %10 %113 %16 %17 + %89 = OpIAdd %10 %99 %87 + OpBranch %82 + %85 = OpLabel + OpBranch %82 ; Was OpBranch %75 + %82 = OpLabel + %110 = OpPhi %10 %99 %83 %89 %84 %114 %85 ; Was OpPhi %10 %99 %83 %89 %84 + OpSelectionMerge %91 None + OpBranchConditional %39 %92 %91 + %92 = OpLabel + OpBranch %91 ; OpBranch %75 + %91 = OpLabel + OpBranch %78 + %80 = OpLabel + OpBranch %78 ; Was OpBranch %74 + %78 = OpLabel + OpBranch %74 ; Was OpBranch %75 + %75 = OpLabel + %109 = OpPhi %10 ; Was OpPhi %10 %99 %85 %110 %92 %110 %78 + OpBranchConditional %115 %73 %74 + %74 = OpLabel + %108 = OpPhi %10 %114 %75 %114 %78 %114 %73 ; Was OpPhi %10 %99 %80 %109 %75 + OpBranch %45 ; Was OpBranch %46 + %46 = OpLabel + %107 = OpPhi %10 ; Was OpPhi %10 %68 %66 %60 %65 %108 %74 + %105 = OpPhi %8 ; Was OpPhi %8 %70 %66 %72 %65 %113 %74 + OpBranch %44 + %45 = OpLabel + %95 = OpConvertSToF %21 %98 + %96 = OpCompositeConstruct %22 %95 %95 %95 %95 + OpStore %3 %96 + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_1, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, DominanceIssue) { + // Exposes a scenario where redirecting edges results in uses of ids being + // non-dominated. We replace such uses with OpUndef to account for this. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %5 = OpTypeInt 32 1 + %7 = OpTypePointer Function %5 + %6 = OpTypeBool + %8 = OpConstantTrue %6 + %9 = OpConstant %5 10 + %10 = OpConstant %5 20 + %11 = OpConstant %5 30 + %4 = OpFunction %2 None %3 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel + OpLoopMerge %14 %15 None + OpBranch %16 + %16 = OpLabel + OpSelectionMerge %17 None + OpBranchConditional %8 %18 %19 + %18 = OpLabel + OpBranch %14 + %19 = OpLabel + %20 = OpIAdd %5 %9 %10 + OpBranch %17 + %17 = OpLabel + %21 = OpIAdd %5 %20 %11 + OpBranchConditional %8 %14 %15 + %15 = OpLabel + OpBranch %13 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + + std::string expected = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %5 = OpTypeInt 32 1 + %7 = OpTypePointer Function %5 + %6 = OpTypeBool + %8 = OpConstantTrue %6 + %9 = OpConstant %5 10 + %10 = OpConstant %5 20 + %11 = OpConstant %5 30 + %22 = OpUndef %5 + %4 = OpFunction %2 None %3 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel + OpSelectionMerge %14 None + OpBranchConditional %8 %16 %14 + %16 = OpLabel + OpSelectionMerge %17 None + OpBranchConditional %8 %18 %19 + %18 = OpLabel + OpBranch %17 + %19 = OpLabel + %20 = OpIAdd %5 %9 %10 + OpBranch %17 + %17 = OpLabel + %21 = OpIAdd %5 %22 %11 + OpBranchConditional %8 %14 %14 + %15 = OpLabel + OpBranch %13 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, expected, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, AccessChainIssue) { + // Exposes a scenario where redirecting edges results in a use of an id + // generated by an access chain being non-dominated. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %56 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpMemberDecorate %28 0 Offset 0 + OpDecorate %28 Block + OpDecorate %30 DescriptorSet 0 + OpDecorate %30 Binding 0 + OpDecorate %56 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 2 + %8 = OpTypePointer Function %7 + %60 = OpTypePointer Private %7 + %10 = OpConstant %6 0 + %11 = OpConstantComposite %7 %10 %10 + %12 = OpTypePointer Function %6 + %59 = OpTypePointer Private %6 + %14 = OpTypeInt 32 1 + %15 = OpTypePointer Function %14 + %17 = OpConstant %14 0 + %24 = OpConstant %14 100 + %25 = OpTypeBool + %28 = OpTypeStruct %6 + %29 = OpTypePointer Uniform %28 + %30 = OpVariable %29 Uniform + %31 = OpTypePointer Uniform %6 + %39 = OpTypeInt 32 0 + %40 = OpConstant %39 1 + %45 = OpConstant %39 0 + %52 = OpConstant %14 1 + %54 = OpTypeVector %6 4 + %55 = OpTypePointer Output %54 + %56 = OpVariable %55 Output + %9 = OpVariable %60 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %13 = OpVariable %12 Function + %16 = OpVariable %15 Function + %38 = OpVariable %12 Function + OpStore %9 %11 + OpStore %13 %10 + OpStore %16 %17 + OpBranch %18 + %18 = OpLabel + OpLoopMerge %20 %21 None + OpBranch %22 + %22 = OpLabel + %23 = OpLoad %14 %16 + %26 = OpSLessThan %25 %23 %24 + OpBranchConditional %26 %19 %20 + %19 = OpLabel + %27 = OpLoad %14 %16 + %32 = OpAccessChain %31 %30 %17 + %33 = OpLoad %6 %32 + %34 = OpConvertFToS %14 %33 + %35 = OpSLessThan %25 %27 %34 + OpSelectionMerge %37 None + OpBranchConditional %35 %36 %44 + %36 = OpLabel + %41 = OpAccessChain %59 %9 %40 + %42 = OpLoad %6 %41 + OpStore %38 %42 + OpBranch %20 + %44 = OpLabel + %46 = OpAccessChain %59 %9 %45 + OpBranch %37 + %37 = OpLabel + %47 = OpLoad %6 %46 + OpStore %38 %47 + %48 = OpLoad %6 %38 + %49 = OpLoad %6 %13 + %50 = OpFAdd %6 %49 %48 + OpStore %13 %50 + OpBranch %21 + %21 = OpLabel + %51 = OpLoad %14 %16 + %53 = OpIAdd %14 %51 %52 + OpStore %16 %53 + OpBranch %18 + %20 = OpLabel + %57 = OpLoad %6 %13 + %58 = OpCompositeConstruct %54 %57 %57 %57 %57 + OpStore %56 %58 + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + CheckValid(env, context.get()); + + std::string expected = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %56 + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + OpMemberDecorate %28 0 Offset 0 + OpDecorate %28 Block + OpDecorate %30 DescriptorSet 0 + OpDecorate %30 Binding 0 + OpDecorate %56 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeFloat 32 + %7 = OpTypeVector %6 2 + %8 = OpTypePointer Function %7 + %60 = OpTypePointer Private %7 + %10 = OpConstant %6 0 + %11 = OpConstantComposite %7 %10 %10 + %12 = OpTypePointer Function %6 + %59 = OpTypePointer Private %6 + %14 = OpTypeInt 32 1 + %15 = OpTypePointer Function %14 + %17 = OpConstant %14 0 + %24 = OpConstant %14 100 + %25 = OpTypeBool + %28 = OpTypeStruct %6 + %29 = OpTypePointer Uniform %28 + %30 = OpVariable %29 Uniform + %31 = OpTypePointer Uniform %6 + %39 = OpTypeInt 32 0 + %40 = OpConstant %39 1 + %45 = OpConstant %39 0 + %52 = OpConstant %14 1 + %54 = OpTypeVector %6 4 + %55 = OpTypePointer Output %54 + %56 = OpVariable %55 Output + %9 = OpVariable %60 Private + %61 = OpConstantTrue %25 + %62 = OpVariable %59 Private + %4 = OpFunction %2 None %3 + %5 = OpLabel + %13 = OpVariable %12 Function + %16 = OpVariable %15 Function + %38 = OpVariable %12 Function + OpStore %9 %11 + OpStore %13 %10 + OpStore %16 %17 + OpBranch %18 + %18 = OpLabel + OpSelectionMerge %20 None + OpBranchConditional %61 %22 %20 + %22 = OpLabel + %23 = OpLoad %14 %16 + %26 = OpSLessThan %25 %23 %24 + OpBranchConditional %26 %19 %20 + %19 = OpLabel + %27 = OpLoad %14 %16 + %32 = OpAccessChain %31 %30 %17 + %33 = OpLoad %6 %32 + %34 = OpConvertFToS %14 %33 + %35 = OpSLessThan %25 %27 %34 + OpSelectionMerge %37 None + OpBranchConditional %35 %36 %44 + %36 = OpLabel + %41 = OpAccessChain %59 %9 %40 + %42 = OpLoad %6 %41 + OpStore %38 %42 + OpBranch %37 + %44 = OpLabel + %46 = OpAccessChain %59 %9 %45 + OpBranch %37 + %37 = OpLabel + %47 = OpLoad %6 %62 + OpStore %38 %47 + %48 = OpLoad %6 %38 + %49 = OpLoad %6 %13 + %50 = OpFAdd %6 %49 %48 + OpStore %13 %50 + OpBranch %20 + %21 = OpLabel + %51 = OpLoad %14 %16 + %53 = OpIAdd %14 %51 %52 + OpStore %16 %53 + OpBranch %18 + %20 = OpLabel + %57 = OpLoad %6 %13 + %58 = OpCompositeConstruct %54 %57 %57 %57 %57 + OpStore %56 %58 + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, expected, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, DominanceAndPhiIssue) { + // Exposes an interesting scenario where a use in a phi stops being dominated + // by the block with which it is associated in the phi. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %17 = OpTypeBool + %18 = OpConstantTrue %17 + %19 = OpConstantFalse %17 + %20 = OpTypeInt 32 1 + %21 = OpConstant %20 5 + %22 = OpConstant %20 6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpLoopMerge %16 %15 None + OpBranch %7 + %7 = OpLabel + OpSelectionMerge %13 None + OpBranchConditional %18 %8 %9 + %8 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %18 %10 %11 + %9 = OpLabel + OpBranch %16 + %10 = OpLabel + OpBranch %16 + %11 = OpLabel + %23 = OpIAdd %20 %21 %22 + OpBranch %12 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel + OpBranch %14 + %14 = OpLabel + %24 = OpPhi %20 %23 %13 + OpBranchConditional %19 %15 %16 + %15 = OpLabel + OpBranch %6 + %16 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string expected = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %17 = OpTypeBool + %18 = OpConstantTrue %17 + %19 = OpConstantFalse %17 + %20 = OpTypeInt 32 1 + %21 = OpConstant %20 5 + %22 = OpConstant %20 6 + %25 = OpUndef %20 + %4 = OpFunction %2 None %3 + %5 = OpLabel + OpBranch %6 + %6 = OpLabel + OpSelectionMerge %16 None + OpBranchConditional %18 %7 %16 + %7 = OpLabel + OpSelectionMerge %13 None + OpBranchConditional %18 %8 %9 + %8 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %18 %10 %11 + %9 = OpLabel + OpBranch %13 + %10 = OpLabel + OpBranch %12 + %11 = OpLabel + %23 = OpIAdd %20 %21 %22 + OpBranch %12 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel + OpBranch %14 + %14 = OpLabel + %24 = OpPhi %20 %25 %13 + OpBranchConditional %19 %16 %16 + %15 = OpLabel + OpBranch %6 + %16 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, expected, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, OpLineBeforeOpPhi) { + // Test to ensure the pass knows OpLine and OpPhi instructions can be + // interleaved. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpString "somefile" + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeInt 32 1 + %7 = OpConstant %6 10 + %8 = OpConstant %6 20 + %9 = OpConstant %6 30 + %10 = OpTypeBool + %11 = OpConstantTrue %10 + %2 = OpFunction %4 None %5 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel + OpLoopMerge %14 %15 None + OpBranch %16 + %16 = OpLabel + OpSelectionMerge %17 None + OpBranchConditional %11 %18 %19 + %18 = OpLabel + %20 = OpIAdd %6 %7 %8 + %21 = OpIAdd %6 %7 %9 + OpBranch %17 + %19 = OpLabel + OpBranch %14 + %17 = OpLabel + %22 = OpPhi %6 %20 %18 + OpLine %3 0 0 + %23 = OpPhi %6 %21 %18 + OpBranch %15 + %15 = OpLabel + OpBranch %13 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + ASSERT_EQ(1, ops.size()); + + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string expected = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpString "somefile" + %4 = OpTypeVoid + %5 = OpTypeFunction %4 + %6 = OpTypeInt 32 1 + %7 = OpConstant %6 10 + %8 = OpConstant %6 20 + %9 = OpConstant %6 30 + %10 = OpTypeBool + %11 = OpConstantTrue %10 + %24 = OpUndef %6 + %2 = OpFunction %4 None %5 + %12 = OpLabel + OpBranch %13 + %13 = OpLabel + OpSelectionMerge %14 None + OpBranchConditional %11 %16 %14 + %16 = OpLabel + OpSelectionMerge %17 None + OpBranchConditional %11 %18 %19 + %18 = OpLabel + %20 = OpIAdd %6 %7 %8 + %21 = OpIAdd %6 %7 %9 + OpBranch %17 + %19 = OpLabel + OpBranch %17 + %17 = OpLabel + %22 = OpPhi %6 %20 %18 %24 %19 + OpLine %3 0 0 + %23 = OpPhi %6 %21 %18 %24 %19 + OpBranch %14 + %15 = OpLabel + OpBranch %13 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, expected, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, + SelectionMergeIsContinueTarget) { + // Example where a loop's continue target is also the target of a selection. + // In this scenario we cautiously do not apply the transformation. + std::string shader = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %3 = OpTypeBool + %4 = OpTypeFunction %2 + %1 = OpFunction %2 None %4 + %5 = OpLabel + %6 = OpUndef %3 + OpBranch %7 + %7 = OpLabel + %8 = OpPhi %3 %6 %5 %9 %10 + OpLoopMerge %11 %10 None + OpBranch %12 + %12 = OpLabel + %13 = OpUndef %3 + OpSelectionMerge %10 None + OpBranchConditional %13 %14 %10 + %14 = OpLabel + OpBranch %10 + %10 = OpLabel + %9 = OpUndef %3 + OpBranchConditional %9 %7 %11 + %11 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + // There should be no opportunities. + ASSERT_EQ(0, ops.size()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, + SwitchSelectionMergeIsContinueTarget) { + // Another example where a loop's continue target is also the target of a + // selection; this time a selection associated with an OpSwitch. We + // cautiously do not apply the transformation. + std::string shader = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %3 = OpTypeBool + %5 = OpTypeInt 32 1 + %4 = OpTypeFunction %2 + %6 = OpConstant %5 2 + %7 = OpConstantTrue %3 + %1 = OpFunction %2 None %4 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpLoopMerge %14 %15 None + OpBranchConditional %7 %10 %14 + %10 = OpLabel + OpSelectionMerge %15 None + OpSwitch %6 %12 1 %11 2 %11 3 %15 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + OpBranch %15 + %15 = OpLabel + OpBranch %9 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + // There should be no opportunities. + ASSERT_EQ(0, ops.size()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, ContinueTargetIsSwitchTarget) { + std::string shader = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %3 = OpTypeBool + %5 = OpTypeInt 32 1 + %4 = OpTypeFunction %2 + %6 = OpConstant %5 2 + %7 = OpConstantTrue %3 + %1 = OpFunction %2 None %4 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpLoopMerge %14 %12 None + OpBranchConditional %7 %10 %14 + %10 = OpLabel + OpSelectionMerge %15 None + OpSwitch %6 %12 1 %11 2 %11 3 %15 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + OpBranch %9 + %15 = OpLabel + OpBranch %14 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + ASSERT_EQ(1, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string expected = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %3 = OpTypeBool + %5 = OpTypeInt 32 1 + %4 = OpTypeFunction %2 + %6 = OpConstant %5 2 + %7 = OpConstantTrue %3 + %1 = OpFunction %2 None %4 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpSelectionMerge %14 None + OpBranchConditional %7 %10 %14 + %10 = OpLabel + OpSelectionMerge %15 None + OpSwitch %6 %15 1 %11 2 %11 3 %15 + %11 = OpLabel + OpBranch %15 + %12 = OpLabel + OpBranch %9 + %15 = OpLabel + OpBranch %14 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, expected, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, + MultipleSwitchTargetsAreContinueTarget) { + std::string shader = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %3 = OpTypeBool + %5 = OpTypeInt 32 1 + %4 = OpTypeFunction %2 + %6 = OpConstant %5 2 + %7 = OpConstantTrue %3 + %1 = OpFunction %2 None %4 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpLoopMerge %14 %12 None + OpBranchConditional %7 %10 %14 + %10 = OpLabel + OpSelectionMerge %15 None + OpSwitch %6 %11 1 %12 2 %12 3 %15 + %11 = OpLabel + OpBranch %12 + %12 = OpLabel + OpBranch %9 + %15 = OpLabel + OpBranch %14 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + ASSERT_EQ(1, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string expected = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %3 = OpTypeBool + %5 = OpTypeInt 32 1 + %4 = OpTypeFunction %2 + %6 = OpConstant %5 2 + %7 = OpConstantTrue %3 + %1 = OpFunction %2 None %4 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpSelectionMerge %14 None + OpBranchConditional %7 %10 %14 + %10 = OpLabel + OpSelectionMerge %15 None + OpSwitch %6 %11 1 %15 2 %15 3 %15 + %11 = OpLabel + OpBranch %15 + %12 = OpLabel + OpBranch %9 + %15 = OpLabel + OpBranch %14 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, expected, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, LoopBranchesStraightToMerge) { + std::string shader = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %4 = OpTypeFunction %2 + %1 = OpFunction %2 None %4 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpLoopMerge %14 %12 None + OpBranch %14 + %12 = OpLabel + OpBranch %9 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + ASSERT_EQ(1, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string expected = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %4 = OpTypeFunction %2 + %15 = OpTypeBool + %16 = OpConstantTrue %15 + %1 = OpFunction %2 None %4 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpSelectionMerge %14 None + OpBranchConditional %16 %14 %14 + %12 = OpLabel + OpBranch %9 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, expected, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, + LoopConditionallyJumpsToMergeOrContinue) { + std::string shader = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %3 = OpTypeBool + %4 = OpTypeFunction %2 + %7 = OpConstantTrue %3 + %1 = OpFunction %2 None %4 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpLoopMerge %14 %12 None + OpBranchConditional %7 %14 %12 + %12 = OpLabel + OpBranch %9 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + ASSERT_EQ(1, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string expected = R"( + OpCapability Shader + OpMemoryModel Logical GLSL450 + OpEntryPoint Vertex %1 "main" + %2 = OpTypeVoid + %3 = OpTypeBool + %4 = OpTypeFunction %2 + %7 = OpConstantTrue %3 + %1 = OpFunction %2 None %4 + %8 = OpLabel + OpBranch %9 + %9 = OpLabel + OpSelectionMerge %14 None + OpBranchConditional %7 %14 %14 + %12 = OpLabel + OpBranch %9 + %14 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, expected, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, MultipleAccessChains) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeStruct %6 + %8 = OpTypeStruct %7 + %9 = OpTypePointer Function %8 + %11 = OpConstant %6 3 + %12 = OpConstantComposite %7 %11 + %13 = OpConstantComposite %8 %12 + %14 = OpTypePointer Function %7 + %16 = OpConstant %6 0 + %19 = OpTypePointer Function %6 + %15 = OpTypeBool + %18 = OpConstantTrue %15 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %10 = OpVariable %9 Function + %20 = OpVariable %19 Function + OpStore %10 %13 + OpBranch %23 + %23 = OpLabel + OpLoopMerge %25 %26 None + OpBranch %27 + %27 = OpLabel + OpSelectionMerge %28 None + OpBranchConditional %18 %29 %25 + %29 = OpLabel + %17 = OpAccessChain %14 %10 %16 + OpBranch %28 + %28 = OpLabel + %21 = OpAccessChain %19 %17 %16 + %22 = OpLoad %6 %21 + %24 = OpAccessChain %19 %10 %16 %16 + OpStore %24 %22 + OpBranch %25 + %26 = OpLabel + OpBranch %23 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + ASSERT_EQ(1, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string expected = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource ESSL 310 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeStruct %6 + %8 = OpTypeStruct %7 + %9 = OpTypePointer Function %8 + %11 = OpConstant %6 3 + %12 = OpConstantComposite %7 %11 + %13 = OpConstantComposite %8 %12 + %14 = OpTypePointer Function %7 + %16 = OpConstant %6 0 + %19 = OpTypePointer Function %6 + %15 = OpTypeBool + %18 = OpConstantTrue %15 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %10 = OpVariable %9 Function + %20 = OpVariable %19 Function + %30 = OpVariable %14 Function + OpStore %10 %13 + OpBranch %23 + %23 = OpLabel + OpSelectionMerge %25 None + OpBranchConditional %18 %27 %25 + %27 = OpLabel + OpSelectionMerge %28 None + OpBranchConditional %18 %29 %28 + %29 = OpLabel + %17 = OpAccessChain %14 %10 %16 + OpBranch %28 + %28 = OpLabel + %21 = OpAccessChain %19 %30 %16 + %22 = OpLoad %6 %21 + %24 = OpAccessChain %19 %10 %16 %16 + OpStore %24 %22 + OpBranch %25 + %26 = OpLabel + OpBranch %23 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, expected, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, + UnreachableInnerLoopContinueBranchingToOuterLoopMerge) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpLoopMerge %9 %10 None + OpBranch %11 + %11 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %12 + %13 = OpLabel + OpBranchConditional %6 %9 %11 + %12 = OpLabel + OpBranch %10 + %10 = OpLabel + OpBranchConditional %6 %9 %8 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + ASSERT_EQ(2, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %6 %11 %9 + %11 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %12 + %13 = OpLabel + OpBranchConditional %6 %9 %11 + %12 = OpLabel + OpBranch %9 + %10 = OpLabel + OpBranchConditional %6 %9 %8 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); + + ASSERT_TRUE(ops[1]->PreconditionHolds()); + ops[1]->TryToApply(); + + CheckValid(env, context.get()); + + std::string after_op_1 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %6 %11 %9 + %11 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %6 %12 %12 + %13 = OpLabel + OpBranchConditional %6 %9 %11 + %12 = OpLabel + OpBranch %9 + %10 = OpLabel + OpBranchConditional %6 %9 %8 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_1, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, + UnreachableInnerLoopContinueBranchingToOuterLoopMerge2) { + // In this test, the branch to the outer loop merge from the inner loop's + // continue is part of a structured selection. + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpLoopMerge %9 %10 None + OpBranch %11 + %11 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %12 + %13 = OpLabel + OpSelectionMerge %14 None + OpBranchConditional %6 %9 %14 + %14 = OpLabel + OpBranch %11 + %12 = OpLabel + OpBranch %10 + %10 = OpLabel + OpBranchConditional %6 %9 %8 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + const auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + ASSERT_EQ(2, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %6 %11 %9 + %11 = OpLabel + OpLoopMerge %12 %13 None + OpBranch %12 + %13 = OpLabel + OpSelectionMerge %14 None + OpBranchConditional %6 %9 %14 + %14 = OpLabel + OpBranch %11 + %12 = OpLabel + OpBranch %9 + %10 = OpLabel + OpBranchConditional %6 %9 %8 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); + + ASSERT_TRUE(ops[1]->PreconditionHolds()); + ops[1]->TryToApply(); + + CheckValid(env, context.get()); + + std::string after_op_1 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %6 %11 %9 + %11 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %6 %12 %12 + %13 = OpLabel + OpSelectionMerge %14 None + OpBranchConditional %6 %9 %14 + %14 = OpLabel + OpBranch %11 + %12 = OpLabel + OpBranch %9 + %10 = OpLabel + OpBranchConditional %6 %9 %8 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_1, context.get()); +} + +TEST(StructuredLoopToSelectionReductionPassTest, + InnerLoopHeaderBranchesToOuterLoopMerge) { + std::string shader = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpLoopMerge %9 %10 None + OpBranch %11 + %11 = OpLabel + OpLoopMerge %12 %13 None + OpBranchConditional %6 %9 %13 + %13 = OpLabel + OpBranchConditional %6 %11 %12 + %12 = OpLabel + OpBranch %10 + %10 = OpLabel + OpBranchConditional %6 %9 %8 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + + const auto env = SPV_ENV_UNIVERSAL_1_3; + const auto context = BuildModule(env, nullptr, shader, kReduceAssembleOption); + const auto pass = TestSubclass(env); + auto ops = pass.WrapGetAvailableOpportunities(context.get()); + + // We cannot transform the inner loop due to its header jumping straight to + // the outer loop merge (the inner loop's merge does not post-dominate its + // header). + ASSERT_EQ(1, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string after_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %6 %11 %9 + %11 = OpLabel + OpLoopMerge %12 %13 None + OpBranchConditional %6 %12 %13 + %13 = OpLabel + OpBranchConditional %6 %11 %12 + %12 = OpLabel + OpBranch %9 + %10 = OpLabel + OpBranchConditional %6 %9 %8 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_op_0, context.get()); + + // Now look again for more opportunities. + ops = pass.WrapGetAvailableOpportunities(context.get()); + + // What was the inner loop should now be transformable, as the jump to the + // outer loop's merge has been redirected. + ASSERT_EQ(1, ops.size()); + ASSERT_TRUE(ops[0]->PreconditionHolds()); + ops[0]->TryToApply(); + + CheckValid(env, context.get()); + + std::string after_another_op_0 = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %2 "main" + OpExecutionMode %2 OriginUpperLeft + OpSource ESSL 310 + %3 = OpTypeVoid + %4 = OpTypeFunction %3 + %5 = OpTypeBool + %6 = OpConstantTrue %5 + %2 = OpFunction %3 None %4 + %7 = OpLabel + OpBranch %8 + %8 = OpLabel + OpSelectionMerge %9 None + OpBranchConditional %6 %11 %9 + %11 = OpLabel + OpSelectionMerge %12 None + OpBranchConditional %6 %12 %12 + %13 = OpLabel + OpBranchConditional %6 %11 %12 + %12 = OpLabel + OpBranch %9 + %10 = OpLabel + OpBranchConditional %6 %9 %8 + %9 = OpLabel + OpReturn + OpFunctionEnd + )"; + CheckEqual(env, after_another_op_0, context.get()); +} + +} // namespace +} // namespace reduce +} // namespace spvtools diff --git a/tools/reduce/reduce.cpp b/tools/reduce/reduce.cpp index 6b0af098..9b9748dc 100644 --- a/tools/reduce/reduce.cpp +++ b/tools/reduce/reduce.cpp @@ -24,6 +24,7 @@ #include "source/reduce/operand_to_dominating_id_reduction_pass.h" #include "source/reduce/reducer.h" #include "source/reduce/remove_unreferenced_instruction_reduction_pass.h" +#include "source/reduce/structured_loop_to_selection_reduction_pass.h" #include "source/spirv_reducer_options.h" #include "source/util/make_unique.h" #include "source/util/string_utils.h" @@ -210,6 +211,8 @@ int main(int argc, const char** argv) { reducer.AddReductionPass( spvtools::MakeUnique( target_env)); + reducer.AddReductionPass( + spvtools::MakeUnique(target_env)); reducer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);