diff --git a/Android.mk b/Android.mk index c450fd0e5..1e436d04d 100644 --- a/Android.mk +++ b/Android.mk @@ -108,6 +108,8 @@ SPVTOOLS_OPT_SRC_FILES := \ source/opt/loop_dependence_helpers.cpp \ source/opt/loop_descriptor.cpp \ source/opt/loop_fission.cpp \ + source/opt/loop_fusion.cpp \ + source/opt/loop_fusion_pass.cpp \ source/opt/loop_peeling.cpp \ source/opt/loop_unroller.cpp \ source/opt/loop_unswitch_pass.cpp \ diff --git a/include/spirv-tools/optimizer.hpp b/include/spirv-tools/optimizer.hpp index 4e2bc2651..9c26965cf 100644 --- a/include/spirv-tools/optimizer.hpp +++ b/include/spirv-tools/optimizer.hpp @@ -488,6 +488,12 @@ Optimizer::PassToken CreateLoopInvariantCodeMotionPass(); // given |threshold|. Optimizer::PassToken CreateLoopFissionPass(size_t threshold); +// Creates a loop fusion pass. +// This pass will look for adjacent loops that are compatible and legal to be +// fused. The fuse all such loops as long as the register usage for the fused +// loop stays under the threshold defined by |max_registers_per_loop|. +Optimizer::PassToken CreateLoopFusionPass(size_t max_registers_per_loop); + // Creates a loop peeling pass. // This pass will look for conditions inside a loop that are true or false only // for the N first or last iteration. For loop with such condition, those N diff --git a/source/opt/CMakeLists.txt b/source/opt/CMakeLists.txt index 1ad75642a..c1648c578 100644 --- a/source/opt/CMakeLists.txt +++ b/source/opt/CMakeLists.txt @@ -61,6 +61,8 @@ add_library(SPIRV-Tools-opt loop_dependence.h loop_descriptor.h loop_fission.h + loop_fusion.h + loop_fusion_pass.h loop_peeling.h loop_unroller.h loop_utils.h @@ -145,6 +147,8 @@ add_library(SPIRV-Tools-opt loop_dependence_helpers.cpp loop_descriptor.cpp loop_fission.cpp + loop_fusion.cpp + loop_fusion_pass.cpp loop_peeling.cpp loop_utils.cpp loop_unroller.cpp diff --git a/source/opt/function.h b/source/opt/function.h index c4d4c613a..2ef4acce5 100644 --- a/source/opt/function.h +++ b/source/opt/function.h @@ -66,6 +66,13 @@ class Function { template inline void AddBasicBlocks(T begin, T end, iterator ip); + // Move basic block with |id| to the position after |ip|. Both have to be + // contained in this function. + inline void MoveBasicBlockToAfter(uint32_t id, BasicBlock* ip); + + // Delete all basic blocks that contain no instructions. + inline void RemoveEmptyBlocks(); + // Saves the given function end instruction. inline void SetFunctionEnd(std::unique_ptr end_inst); @@ -162,6 +169,25 @@ inline void Function::AddBasicBlocks(T src_begin, T src_end, iterator ip) { std::make_move_iterator(src_end)); } +inline void Function::MoveBasicBlockToAfter(uint32_t id, BasicBlock* ip) { + auto block_to_move = std::move(*FindBlock(id).Get()); + + assert(block_to_move->GetParent() == ip->GetParent() && + "Both blocks have to be in the same function."); + + InsertBasicBlockAfter(std::move(block_to_move), ip); + blocks_.erase(std::find(std::begin(blocks_), std::end(blocks_), nullptr)); +} + +inline void Function::RemoveEmptyBlocks() { + auto first_empty = + std::remove_if(std::begin(blocks_), std::end(blocks_), + [](const std::unique_ptr& bb) -> bool { + return bb->GetLabelInst()->opcode() == SpvOpNop; + }); + blocks_.erase(first_empty, std::end(blocks_)); +} + inline void Function::SetFunctionEnd(std::unique_ptr end_inst) { end_inst_ = std::move(end_inst); } diff --git a/source/opt/loop_dependence.cpp b/source/opt/loop_dependence.cpp index 53997a9c9..3f67d63bb 100644 --- a/source/opt/loop_dependence.cpp +++ b/source/opt/loop_dependence.cpp @@ -192,11 +192,61 @@ bool LoopDependenceAnalysis::GetDependence(const ir::Instruction* source, ir::Instruction* destination_access_chain = GetOperandDefinition(destination, 0); + auto num_access_chains = + (source_access_chain->opcode() == SpvOpAccessChain) + + (destination_access_chain->opcode() == SpvOpAccessChain); + + // If neither is an access chain, then they are load/store to a variable. + if (num_access_chains == 0) { + if (source_access_chain != destination_access_chain) { + // Not the same location, report independence + return true; + } else { + // Accessing the same variable + for (auto& entry : distance_vector->GetEntries()) { + entry = DistanceEntry(); + } + return false; + } + } + + // If only one is an access chain, it could be accessing a part of a struct + if (num_access_chains == 1) { + auto source_is_chain = source_access_chain->opcode() == SpvOpAccessChain; + auto access_chain = + source_is_chain ? source_access_chain : destination_access_chain; + auto variable = + source_is_chain ? destination_access_chain : source_access_chain; + + auto location_in_chain = GetOperandDefinition(access_chain, 0); + + if (variable != location_in_chain) { + // Not the same location, report independence + return true; + } else { + // Accessing the same variable + for (auto& entry : distance_vector->GetEntries()) { + entry = DistanceEntry(); + } + return false; + } + } + // If the access chains aren't collecting from the same structure there is no // dependence. ir::Instruction* source_array = GetOperandDefinition(source_access_chain, 0); ir::Instruction* destination_array = GetOperandDefinition(destination_access_chain, 0); + + // Nested access chains are not supported yet, bail out. + if (source_array->opcode() == SpvOpAccessChain || + destination_array->opcode() == SpvOpAccessChain) { + for (auto& entry : distance_vector->GetEntries()) { + entry = DistanceEntry(); + } + return false; + } + if (source_array != destination_array) { PrintDebug("Proved independence through different arrays."); return true; diff --git a/source/opt/loop_descriptor.cpp b/source/opt/loop_descriptor.cpp index 9889b40fe..7ba155931 100644 --- a/source/opt/loop_descriptor.cpp +++ b/source/opt/loop_descriptor.cpp @@ -550,6 +550,29 @@ void LoopDescriptor::PopulateList(const Function* f) { } } +std::vector LoopDescriptor::GetLoopsInBinaryLayoutOrder() { + std::vector ids{}; + + for (size_t i = 0; i < NumLoops(); ++i) { + ids.push_back(GetLoopByIndex(i).GetHeaderBlock()->id()); + } + + std::vector loops{}; + if (!ids.empty()) { + auto function = GetLoopByIndex(0).GetHeaderBlock()->GetParent(); + for (const auto& block : *function) { + auto block_id = block.id(); + + auto element = std::find(std::begin(ids), std::end(ids), block_id); + if (element != std::end(ids)) { + loops.push_back(&GetLoopByIndex(element - std::begin(ids))); + } + } + } + + return loops; +} + ir::BasicBlock* Loop::FindConditionBlock() const { if (!loop_merge_) { return nullptr; @@ -856,6 +879,19 @@ ir::Instruction* Loop::FindConditionVariable( return induction; } +bool LoopDescriptor::CreatePreHeaderBlocksIfMissing() { + auto modified = false; + + for (auto& loop : *this) { + if (!loop.GetPreHeaderBlock()) { + modified = true; + loop.GetOrCreatePreHeaderBlock(); + } + } + + return modified; +} + // Add and remove loops which have been marked for addition and removal to // maintain the state of the loop descriptor class. void LoopDescriptor::PostModificationCleanup() { diff --git a/source/opt/loop_descriptor.h b/source/opt/loop_descriptor.h index ebd67381e..b04a97d37 100644 --- a/source/opt/loop_descriptor.h +++ b/source/opt/loop_descriptor.h @@ -437,6 +437,10 @@ class LoopDescriptor { return *loops_[index]; } + // Returns the loops in |this| in the order their headers appear in the + // binary. + std::vector GetLoopsInBinaryLayoutOrder(); + // Returns the inner most loop that contains the basic block id |block_id|. inline Loop* operator[](uint32_t block_id) const { return FindLoopForBasicBlock(block_id); @@ -482,6 +486,10 @@ class LoopDescriptor { loops_to_add_.emplace_back(std::make_pair(parent, loop_to_add)); } + // Checks all loops in |this| and will create pre-headers for all loops + // that don't have one. Returns |true| if any blocks were created. + bool CreatePreHeaderBlocksIfMissing(); + // Should be called to preserve the LoopAnalysis after loops have been marked // for addition with AddLoop or MarkLoopForRemoval. void PostModificationCleanup(); diff --git a/source/opt/loop_fusion.cpp b/source/opt/loop_fusion.cpp new file mode 100644 index 000000000..0fc2bbdcc --- /dev/null +++ b/source/opt/loop_fusion.cpp @@ -0,0 +1,731 @@ +// 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 "opt/loop_fusion.h" + +#include +#include + +#include "opt/ir_context.h" +#include "opt/loop_dependence.h" +#include "opt/loop_descriptor.h" + +namespace spvtools { +namespace opt { + +namespace { + +// Append all the loops nested in |loop| to |loops|. +void CollectChildren(ir::Loop* loop, std::vector* loops) { + for (auto child : *loop) { + loops->push_back(child); + if (child->NumImmediateChildren() != 0) { + CollectChildren(child, loops); + } + } +} + +// Return the set of locations accessed by |stores| and |loads|. +std::set GetLocationsAccessed( + const std::map>& stores, + const std::map>& loads) { + std::set locations{}; + + for (const auto& kv : stores) { + locations.insert(std::get<0>(kv)); + } + + for (const auto& kv : loads) { + locations.insert(std::get<0>(kv)); + } + + return locations; +} + +// Append all dependences from |sources| to |destinations| to |dependences|. +void GetDependences(std::vector* dependences, + LoopDependenceAnalysis* analysis, + const std::vector& sources, + const std::vector& destinations, + size_t num_entries) { + for (auto source : sources) { + for (auto destination : destinations) { + DistanceVector dist(num_entries); + if (!analysis->GetDependence(source, destination, &dist)) { + dependences->push_back(dist); + } + } + } +} + +// Apped all instructions in |block| to |instructions|. +void AddInstructionsInBlock(std::vector* instructions, + ir::BasicBlock* block) { + for (auto& inst : *block) { + instructions->push_back(&inst); + } + + instructions->push_back(block->GetLabelInst()); +} + +} // namespace + +bool LoopFusion::UsedInContinueOrConditionBlock( + ir::Instruction* phi_instruction, ir::Loop* loop) { + auto condition_block = loop->FindConditionBlock()->id(); + auto continue_block = loop->GetLatchBlock()->id(); + auto not_used = context_->get_def_use_mgr()->WhileEachUser( + phi_instruction, + [this, condition_block, continue_block](ir::Instruction* instruction) { + auto block_id = context_->get_instr_block(instruction)->id(); + return block_id != condition_block && block_id != continue_block; + }); + + return !not_used; +} + +void LoopFusion::RemoveIfNotUsedContinueOrConditionBlock( + std::vector* instructions, ir::Loop* loop) { + instructions->erase( + std::remove_if(std::begin(*instructions), std::end(*instructions), + [this, loop](ir::Instruction* instruction) { + return !UsedInContinueOrConditionBlock(instruction, + loop); + }), + std::end(*instructions)); +} + +bool LoopFusion::AreCompatible() { + // Check that the loops are in the same function. + if (loop_0_->GetHeaderBlock()->GetParent() != + loop_1_->GetHeaderBlock()->GetParent()) { + return false; + } + + // Check that both loops have pre-header blocks. + if (!loop_0_->GetPreHeaderBlock() || !loop_1_->GetPreHeaderBlock()) { + return false; + } + + // Check there are no breaks. + if (context_->cfg()->preds(loop_0_->GetMergeBlock()->id()).size() != 1 || + context_->cfg()->preds(loop_1_->GetMergeBlock()->id()).size() != 1) { + return false; + } + + // Check there are no continues. + if (context_->cfg()->preds(loop_0_->GetLatchBlock()->id()).size() != 1 || + context_->cfg()->preds(loop_1_->GetLatchBlock()->id()).size() != 1) { + return false; + } + + // |GetInductionVariables| returns all OpPhi in the header. Check that both + // loops have exactly one that is used in the continue and condition blocks. + std::vector inductions_0{}, inductions_1{}; + loop_0_->GetInductionVariables(inductions_0); + RemoveIfNotUsedContinueOrConditionBlock(&inductions_0, loop_0_); + + if (inductions_0.size() != 1) { + return false; + } + + induction_0_ = inductions_0.front(); + + loop_1_->GetInductionVariables(inductions_1); + RemoveIfNotUsedContinueOrConditionBlock(&inductions_1, loop_1_); + + if (inductions_1.size() != 1) { + return false; + } + + induction_1_ = inductions_1.front(); + + if (!CheckInit()) { + return false; + } + + if (!CheckCondition()) { + return false; + } + + if (!CheckStep()) { + return false; + } + + // Check adjacency, |loop_0_| should come just before |loop_1_|. + // There is always at least one block between loops, even if it's empty. + // We'll check at most 2 preceeding blocks. + + auto pre_header_1 = loop_1_->GetPreHeaderBlock(); + + std::vector block_to_check{}; + block_to_check.push_back(pre_header_1); + + if (loop_0_->GetMergeBlock() != loop_1_->GetPreHeaderBlock()) { + // Follow CFG for one more block. + auto preds = context_->cfg()->preds(pre_header_1->id()); + if (preds.size() == 1) { + auto block = &*containing_function_->FindBlock(preds.front()); + if (block == loop_0_->GetMergeBlock()) { + block_to_check.push_back(block); + } else { + return false; + } + } else { + return false; + } + } + + // Check that the separating blocks are either empty or only contains a store + // to a local variable that is never read (left behind by + // '--eliminate-local-multi-store'). Also allow OpPhi, since the loop could be + // in LCSSA form. + for (auto block : block_to_check) { + for (auto& inst : *block) { + if (inst.opcode() == SpvOpStore) { + // Get the definition of the target to check it's function scope so + // there are no observable side effects. + auto variable = + context_->get_def_use_mgr()->GetDef(inst.GetSingleWordInOperand(0)); + + if (variable->opcode() != SpvOpVariable || + variable->GetSingleWordInOperand(0) != SpvStorageClassFunction) { + return false; + } + + // Check the target is never loaded. + auto is_used = false; + context_->get_def_use_mgr()->ForEachUse( + inst.GetSingleWordInOperand(0), + [&is_used](ir::Instruction* use_inst, uint32_t) { + if (use_inst->opcode() == SpvOpLoad) { + is_used = true; + } + }); + + if (is_used) { + return false; + } + } else if (inst.opcode() == SpvOpPhi) { + if (inst.NumInOperands() != 2) { + return false; + } + } else if (inst.opcode() != SpvOpBranch) { + return false; + } + } + } + + return true; +} // namespace opt + +bool LoopFusion::ContainsBarriersOrFunctionCalls(ir::Loop* loop) { + for (const auto& block : loop->GetBlocks()) { + for (const auto& inst : *containing_function_->FindBlock(block)) { + auto opcode = inst.opcode(); + if (opcode == SpvOpFunctionCall || opcode == SpvOpControlBarrier || + opcode == SpvOpMemoryBarrier || opcode == SpvOpTypeNamedBarrier || + opcode == SpvOpNamedBarrierInitialize || + opcode == SpvOpMemoryNamedBarrier) { + return true; + } + } + } + + return false; +} + +bool LoopFusion::CheckInit() { + int64_t loop_0_init; + if (!loop_0_->GetInductionInitValue(induction_0_, &loop_0_init)) { + return false; + } + + int64_t loop_1_init; + if (!loop_1_->GetInductionInitValue(induction_1_, &loop_1_init)) { + return false; + } + + if (loop_0_init != loop_1_init) { + return false; + } + + return true; +} + +bool LoopFusion::CheckCondition() { + auto condition_0 = loop_0_->GetConditionInst(); + auto condition_1 = loop_1_->GetConditionInst(); + + if (!loop_0_->IsSupportedCondition(condition_0->opcode()) || + !loop_1_->IsSupportedCondition(condition_1->opcode())) { + return false; + } + + if (condition_0->opcode() != condition_1->opcode()) { + return false; + } + + for (uint32_t i = 0; i < condition_0->NumInOperandWords(); ++i) { + auto arg_0 = context_->get_def_use_mgr()->GetDef( + condition_0->GetSingleWordInOperand(i)); + auto arg_1 = context_->get_def_use_mgr()->GetDef( + condition_1->GetSingleWordInOperand(i)); + + if (arg_0 == induction_0_ && arg_1 == induction_1_) { + continue; + } + + if (arg_0 == induction_0_ && arg_1 != induction_1_) { + return false; + } + + if (arg_1 == induction_1_ && arg_0 != induction_0_) { + return false; + } + + if (arg_0 != arg_1) { + return false; + } + } + + return true; +} + +bool LoopFusion::CheckStep() { + auto scalar_analysis = context_->GetScalarEvolutionAnalysis(); + SENode* induction_node_0 = scalar_analysis->SimplifyExpression( + scalar_analysis->AnalyzeInstruction(induction_0_)); + if (!induction_node_0->AsSERecurrentNode()) { + return false; + } + + SENode* induction_step_0 = + induction_node_0->AsSERecurrentNode()->GetCoefficient(); + if (!induction_step_0->AsSEConstantNode()) { + return false; + } + + SENode* induction_node_1 = scalar_analysis->SimplifyExpression( + scalar_analysis->AnalyzeInstruction(induction_1_)); + if (!induction_node_1->AsSERecurrentNode()) { + return false; + } + + SENode* induction_step_1 = + induction_node_1->AsSERecurrentNode()->GetCoefficient(); + if (!induction_step_1->AsSEConstantNode()) { + return false; + } + + if (*induction_step_0 != *induction_step_1) { + return false; + } + + return true; +} + +std::map> +LoopFusion::LocationToMemOps(const std::vector& mem_ops) { + std::map> location_map{}; + + for (auto instruction : mem_ops) { + auto access_location = context_->get_def_use_mgr()->GetDef( + instruction->GetSingleWordInOperand(0)); + + while (access_location->opcode() == SpvOpAccessChain) { + access_location = context_->get_def_use_mgr()->GetDef( + access_location->GetSingleWordInOperand(0)); + } + + location_map[access_location].push_back(instruction); + } + + return location_map; +} + +std::pair, std::vector> +LoopFusion::GetLoadsAndStoresInLoop(ir::Loop* loop) { + std::vector loads{}; + std::vector stores{}; + + for (auto block_id : loop->GetBlocks()) { + if (block_id == loop->GetLatchBlock()->id()) { + continue; + } + + for (auto& instruction : *containing_function_->FindBlock(block_id)) { + if (instruction.opcode() == SpvOpLoad) { + loads.push_back(&instruction); + } else if (instruction.opcode() == SpvOpStore) { + stores.push_back(&instruction); + } + } + } + + return std::make_pair(loads, stores); +} + +bool LoopFusion::IsUsedInLoop(ir::Instruction* instruction, ir::Loop* loop) { + auto not_used = context_->get_def_use_mgr()->WhileEachUser( + instruction, [this, loop](ir::Instruction* user) { + auto block_id = context_->get_instr_block(user)->id(); + return !loop->IsInsideLoop(block_id); + }); + + return !not_used; +} + +bool LoopFusion::IsLegal() { + assert(AreCompatible() && "Fusion can't be legal, loops are not compatible."); + + // Bail out if there are function calls as they could have side-effects that + // cause dependencies or if there are any barriers. + if (ContainsBarriersOrFunctionCalls(loop_0_) || + ContainsBarriersOrFunctionCalls(loop_1_)) { + return false; + } + + std::vector phi_instructions{}; + loop_0_->GetInductionVariables(phi_instructions); + + // Check no OpPhi in |loop_0_| is used in |loop_1_|. + for (auto phi_instruction : phi_instructions) { + if (IsUsedInLoop(phi_instruction, loop_1_)) { + return false; + } + } + + // Check no LCSSA OpPhi in merge block of |loop_0_| is used in |loop_1_|. + auto phi_used = false; + loop_0_->GetMergeBlock()->ForEachPhiInst( + [this, &phi_used](ir::Instruction* phi_instruction) { + phi_used |= IsUsedInLoop(phi_instruction, loop_1_); + }); + + if (phi_used) { + return false; + } + + // Grab loads & stores from both loops. + auto loads_stores_0 = GetLoadsAndStoresInLoop(loop_0_); + auto loads_stores_1 = GetLoadsAndStoresInLoop(loop_1_); + + // Build memory location to operation maps. + auto load_locs_0 = LocationToMemOps(std::get<0>(loads_stores_0)); + auto store_locs_0 = LocationToMemOps(std::get<1>(loads_stores_0)); + + auto load_locs_1 = LocationToMemOps(std::get<0>(loads_stores_1)); + auto store_locs_1 = LocationToMemOps(std::get<1>(loads_stores_1)); + + // Get the locations accessed in both loops. + auto locations_0 = GetLocationsAccessed(store_locs_0, load_locs_0); + auto locations_1 = GetLocationsAccessed(store_locs_1, load_locs_1); + + std::vector potential_clashes{}; + + std::set_intersection(std::begin(locations_0), std::end(locations_0), + std::begin(locations_1), std::end(locations_1), + std::back_inserter(potential_clashes)); + + // If the loops don't access the same variables, the fusion is legal. + if (potential_clashes.empty()) { + return true; + } + + // Find variables that have at least one store. + std::vector potential_clashes_with_stores{}; + for (auto location : potential_clashes) { + if (store_locs_0.find(location) != std::end(store_locs_0) || + store_locs_1.find(location) != std::end(store_locs_1)) { + potential_clashes_with_stores.push_back(location); + } + } + + // If there are only loads to the same variables, the fusion is legal. + if (potential_clashes_with_stores.empty()) { + return true; + } + + // Else if loads and at least one store (across loops) to the same variable + // there is a potential dependence and we need to check the dependence + // distance. + + // Find all the loops in this loop nest for the dependency analysis. + std::vector loops{}; + + // Find the parents. + for (auto current_loop = loop_0_; current_loop != nullptr; + current_loop = current_loop->GetParent()) { + loops.push_back(current_loop); + } + + auto this_loop_position = loops.size() - 1; + std::reverse(std::begin(loops), std::end(loops)); + + // Find the children. + CollectChildren(loop_0_, &loops); + CollectChildren(loop_1_, &loops); + + // Check that any dependes created are legal. That means the fused loops do + // not have any dependencies with dependence distance greater than 0 that did + // not exist in the original loops. + + LoopDependenceAnalysis analysis(context_, loops); + + analysis.GetScalarEvolution()->AddLoopsToPretendAreTheSame( + {loop_0_, loop_1_}); + + for (auto location : potential_clashes_with_stores) { + // Analyse dependences from |loop_0_| to |loop_1_|. + std::vector dependences; + // Read-After-Write. + GetDependences(&dependences, &analysis, store_locs_0[location], + load_locs_1[location], loops.size()); + // Write-After-Read. + GetDependences(&dependences, &analysis, load_locs_0[location], + store_locs_1[location], loops.size()); + // Write-After-Write. + GetDependences(&dependences, &analysis, store_locs_0[location], + store_locs_1[location], loops.size()); + + // Check that the induction variables either don't appear in the subscripts + // or the dependence distance is negative. + for (const auto& dependence : dependences) { + const auto& entry = dependence.GetEntries()[this_loop_position]; + if ((entry.dependence_information == + DistanceEntry::DependenceInformation::DISTANCE && + entry.distance < 1) || + (entry.dependence_information == + DistanceEntry::DependenceInformation::IRRELEVANT)) { + continue; + } else { + return false; + } + } + } + + return true; +} + +void ReplacePhiParentWith(ir::Instruction* inst, uint32_t orig_block, + uint32_t new_block) { + if (inst->GetSingleWordInOperand(1) == orig_block) { + inst->SetInOperand(1, {new_block}); + } else { + inst->SetInOperand(3, {new_block}); + } +} + +void LoopFusion::Fuse() { + assert(AreCompatible() && "Can't fuse, loops aren't compatible"); + assert(IsLegal() && "Can't fuse, illegal"); + + // Save the pointers/ids, won't be found in the middle of doing modifications. + auto header_1 = loop_1_->GetHeaderBlock()->id(); + auto condition_1 = loop_1_->FindConditionBlock()->id(); + auto continue_1 = loop_1_->GetLatchBlock()->id(); + auto continue_0 = loop_0_->GetLatchBlock()->id(); + auto condition_block_of_0 = loop_0_->FindConditionBlock(); + + // Find the blocks whose branches need updating. + auto first_block_of_1 = &*(++containing_function_->FindBlock(condition_1)); + auto last_block_of_1 = &*(--containing_function_->FindBlock(continue_1)); + auto last_block_of_0 = &*(--containing_function_->FindBlock(continue_0)); + + // Update the branch for |last_block_of_loop_0| to go to |first_block_of_1|. + last_block_of_0->ForEachSuccessorLabel( + [first_block_of_1](uint32_t* succ) { *succ = first_block_of_1->id(); }); + + // Update the branch for the |last_block_of_loop_1| to go to the continue + // block of |loop_0_|. + last_block_of_1->ForEachSuccessorLabel( + [this](uint32_t* succ) { *succ = loop_0_->GetLatchBlock()->id(); }); + + // Update merge block id in the header of |loop_0_| to the merge block of + // |loop_1_|. + loop_0_->GetHeaderBlock()->ForEachInst([this](ir::Instruction* inst) { + if (inst->opcode() == SpvOpLoopMerge) { + inst->SetInOperand(0, {loop_1_->GetMergeBlock()->id()}); + } + }); + + // Update condition branch target in |loop_0_| to the merge block of + // |loop_1_|. + condition_block_of_0->ForEachInst([this](ir::Instruction* inst) { + if (inst->opcode() == SpvOpBranchConditional) { + auto loop_0_merge_block_id = loop_0_->GetMergeBlock()->id(); + + if (inst->GetSingleWordInOperand(1) == loop_0_merge_block_id) { + inst->SetInOperand(1, {loop_1_->GetMergeBlock()->id()}); + } else { + inst->SetInOperand(2, {loop_1_->GetMergeBlock()->id()}); + } + } + }); + + // Move OpPhi instructions not corresponding to the induction variable from + // the header of |loop_1_| to the header of |loop_0_|. + std::vector instructions_to_move{}; + for (auto& instruction : *loop_1_->GetHeaderBlock()) { + if (instruction.opcode() == SpvOpPhi && &instruction != induction_1_) { + instructions_to_move.push_back(&instruction); + } + } + + for (auto& it : instructions_to_move) { + it->RemoveFromList(); + it->InsertBefore(induction_0_); + } + + // Update the OpPhi parents to the correct blocks in |loop_0_|. + loop_0_->GetHeaderBlock()->ForEachPhiInst([this](ir::Instruction* i) { + ReplacePhiParentWith(i, loop_1_->GetPreHeaderBlock()->id(), + loop_0_->GetPreHeaderBlock()->id()); + + ReplacePhiParentWith(i, loop_1_->GetLatchBlock()->id(), + loop_0_->GetLatchBlock()->id()); + }); + + // Update instruction to block mapping & DefUseManager. + for (auto& phi_instruction : instructions_to_move) { + context_->set_instr_block(phi_instruction, loop_0_->GetHeaderBlock()); + context_->get_def_use_mgr()->AnalyzeInstUse(phi_instruction); + } + + // Replace the uses of the induction variable of |loop_1_| with that the + // induction variable of |loop_0_|. + context_->ReplaceAllUsesWith(induction_1_->result_id(), + induction_0_->result_id()); + + // Replace LCSSA OpPhi in merge block of |loop_0_|. + loop_0_->GetMergeBlock()->ForEachPhiInst( + [this](ir::Instruction* instruction) { + context_->ReplaceAllUsesWith(instruction->result_id(), + instruction->GetSingleWordInOperand(0)); + }); + + // Update LCSSA OpPhi in merge block of |loop_1_|. + loop_1_->GetMergeBlock()->ForEachPhiInst( + [condition_block_of_0](ir::Instruction* instruction) { + instruction->SetInOperand(1, {condition_block_of_0->id()}); + }); + + // Move the continue block of |loop_0_| after the last block of |loop_1_|. + containing_function_->MoveBasicBlockToAfter(continue_0, last_block_of_1); + + // Gather all instructions to be killed from |loop_1_| (induction variable + // initialisation, header, condition and continue blocks). + std::vector instr_to_delete{}; + AddInstructionsInBlock(&instr_to_delete, loop_1_->GetPreHeaderBlock()); + AddInstructionsInBlock(&instr_to_delete, loop_1_->GetHeaderBlock()); + AddInstructionsInBlock(&instr_to_delete, loop_1_->FindConditionBlock()); + AddInstructionsInBlock(&instr_to_delete, loop_1_->GetLatchBlock()); + + // There was an additional empty block between the loops, kill that too. + if (loop_0_->GetMergeBlock() != loop_1_->GetPreHeaderBlock()) { + AddInstructionsInBlock(&instr_to_delete, loop_0_->GetMergeBlock()); + } + + // Update the CFG, so it wouldn't need invalidating. + auto cfg = context_->cfg(); + + cfg->ForgetBlock(loop_1_->GetPreHeaderBlock()); + cfg->ForgetBlock(loop_1_->GetHeaderBlock()); + cfg->ForgetBlock(loop_1_->FindConditionBlock()); + cfg->ForgetBlock(loop_1_->GetLatchBlock()); + + if (loop_0_->GetMergeBlock() != loop_1_->GetPreHeaderBlock()) { + cfg->ForgetBlock(loop_0_->GetMergeBlock()); + } + + cfg->RemoveEdge(last_block_of_0->id(), loop_0_->GetLatchBlock()->id()); + cfg->AddEdge(last_block_of_0->id(), first_block_of_1->id()); + + cfg->AddEdge(last_block_of_1->id(), loop_0_->GetLatchBlock()->id()); + + cfg->AddEdge(loop_0_->GetLatchBlock()->id(), loop_1_->GetHeaderBlock()->id()); + + cfg->AddEdge(condition_block_of_0->id(), loop_1_->GetMergeBlock()->id()); + + // Update DefUseManager. + auto def_use_mgr = context_->get_def_use_mgr(); + + // Uses of labels that are in updated branches need analysing. + def_use_mgr->AnalyzeInstUse(last_block_of_0->terminator()); + def_use_mgr->AnalyzeInstUse(last_block_of_1->terminator()); + def_use_mgr->AnalyzeInstUse(loop_0_->GetHeaderBlock()->GetLoopMergeInst()); + def_use_mgr->AnalyzeInstUse(condition_block_of_0->terminator()); + + // Update the LoopDescriptor, so it wouldn't need invalidating. + auto ld = context_->GetLoopDescriptor(containing_function_); + + // Create a copy, so the iterator wouldn't be invalidated. + std::vector loops_to_add_remove{}; + for (auto child_loop : *loop_1_) { + loops_to_add_remove.push_back(child_loop); + } + + for (auto child_loop : loops_to_add_remove) { + loop_1_->RemoveChildLoop(child_loop); + loop_0_->AddNestedLoop(child_loop); + } + + auto loop_1_blocks = loop_1_->GetBlocks(); + + for (auto block : loop_1_blocks) { + loop_1_->RemoveBasicBlock(block); + if (block != header_1 && block != condition_1 && block != continue_1) { + loop_0_->AddBasicBlock(block); + if ((*ld)[block] == loop_1_) { + ld->SetBasicBlockToLoop(block, loop_0_); + } + } + + if ((*ld)[block] == loop_1_) { + ld->ForgetBasicBlock(block); + } + } + + loop_1_->RemoveBasicBlock(loop_1_->GetPreHeaderBlock()->id()); + ld->ForgetBasicBlock(loop_1_->GetPreHeaderBlock()->id()); + + if (loop_0_->GetMergeBlock() != loop_1_->GetPreHeaderBlock()) { + loop_0_->RemoveBasicBlock(loop_0_->GetMergeBlock()->id()); + ld->ForgetBasicBlock(loop_0_->GetMergeBlock()->id()); + } + + loop_0_->SetMergeBlock(loop_1_->GetMergeBlock()); + + loop_1_->ClearBlocks(); + + ld->RemoveLoop(loop_1_); + + // Kill unnessecary instructions and remove all empty blocks. + for (auto inst : instr_to_delete) { + context_->KillInst(inst); + } + + containing_function_->RemoveEmptyBlocks(); + + // Invalidate analyses. + context_->InvalidateAnalysesExceptFor( + ir::IRContext::Analysis::kAnalysisInstrToBlockMapping | + ir::IRContext::Analysis::kAnalysisLoopAnalysis | + ir::IRContext::Analysis::kAnalysisDefUse | + ir::IRContext::Analysis::kAnalysisCFG); +} + +} // namespace opt +} // namespace spvtools diff --git a/source/opt/loop_fusion.h b/source/opt/loop_fusion.h new file mode 100644 index 000000000..c3efc7ada --- /dev/null +++ b/source/opt/loop_fusion.h @@ -0,0 +1,114 @@ +// 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_OPT_LOOP_FUSION_H_ +#define SOURCE_OPT_LOOP_FUSION_H_ + +#include +#include +#include + +#include "opt/ir_context.h" +#include "opt/loop_descriptor.h" +#include "opt/loop_utils.h" +#include "opt/scalar_analysis.h" + +namespace spvtools { +namespace opt { + +class LoopFusion { + public: + LoopFusion(ir::IRContext* context, ir::Loop* loop_0, ir::Loop* loop_1) + : context_(context), + loop_0_(loop_0), + loop_1_(loop_1), + containing_function_(loop_0->GetHeaderBlock()->GetParent()) {} + + // Checks if the |loop_0| and |loop_1| are compatible for fusion. + // That means: + // * they both have one induction variable + // * they have the same upper and lower bounds + // - same inital value + // - same condition + // * they have the same update step + // * they are adjacent, with |loop_0| appearing before |loop_1| + // * there are no break/continue in either of them + // * they both have pre-header blocks (required for ScalarEvolutionAnalysis + // and dependence checking). + bool AreCompatible(); + + // Checks if compatible |loop_0| and |loop_1| are legal to fuse. + // * fused loops do not have any dependencies with dependence distance greater + // than 0 that did not exist in the original loops. + // * there are no function calls in the loops (could have side-effects) + bool IsLegal(); + + // Perform the actual fusion of |loop_0_| and |loop_1_|. The loops have to be + // compatible and the fusion has to be legal. + void Fuse(); + + private: + // Check that the initial values are the same. + bool CheckInit(); + + // Check that the conditions are the same. + bool CheckCondition(); + + // Check that the steps are the same. + bool CheckStep(); + + // Returns |true| if |instruction| is used in the continue or condition block + // of |loop|. + bool UsedInContinueOrConditionBlock(ir::Instruction* instruction, + ir::Loop* loop); + + // Remove entries in |instructions| that are not used in the continue or + // condition block of |loop|. + void RemoveIfNotUsedContinueOrConditionBlock( + std::vector* instructions, ir::Loop* loop); + + // Returns |true| if |instruction| is used in |loop|. + bool IsUsedInLoop(ir::Instruction* instruction, ir::Loop* loop); + + // Returns |true| if |loop| has at least one barrier or function call. + bool ContainsBarriersOrFunctionCalls(ir::Loop* loop); + + // Get all instructions in the |loop| (except in the latch block) that have + // the opcode |opcode|. + std::pair, std::vector> + GetLoadsAndStoresInLoop(ir::Loop* loop); + + // Given a vector of memory operations (OpLoad/OpStore), constructs a map from + // variables to the loads/stores that those variables. + std::map> LocationToMemOps( + const std::vector& mem_ops); + + ir::IRContext* context_; + + // The original loops to be fused. + ir::Loop* loop_0_; + ir::Loop* loop_1_; + + // The function that contains |loop_0_| and |loop_1_|. + ir::Function* containing_function_ = nullptr; + + // The induction variables for |loop_0_| and |loop_1_|. + ir::Instruction* induction_0_ = nullptr; + ir::Instruction* induction_1_ = nullptr; +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_LOOP_FUSION_H_ diff --git a/source/opt/loop_fusion_pass.cpp b/source/opt/loop_fusion_pass.cpp new file mode 100644 index 000000000..e891c88e4 --- /dev/null +++ b/source/opt/loop_fusion_pass.cpp @@ -0,0 +1,70 @@ +// 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 "opt/loop_fusion_pass.h" + +#include "opt/ir_context.h" +#include "opt/loop_descriptor.h" +#include "opt/loop_fusion.h" +#include "opt/register_pressure.h" + +namespace spvtools { +namespace opt { + +Pass::Status LoopFusionPass::Process(ir::IRContext* c) { + bool modified = false; + ir::Module* module = c->module(); + + // Process each function in the module + for (ir::Function& f : *module) { + modified |= ProcessFunction(&f); + } + + return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange; +} + +bool LoopFusionPass::ProcessFunction(ir::Function* function) { + auto c = function->context(); + ir::LoopDescriptor& ld = *c->GetLoopDescriptor(function); + + // If a loop doesn't have a preheader needs then it needs to be created. Make + // sure to return Status::SuccessWithChange in that case. + auto modified = ld.CreatePreHeaderBlocksIfMissing(); + + // TODO(tremmelg): Could the only loop that |loop| could possibly be fused be + // picked out so don't have to check every loop + for (auto& loop_0 : ld) { + for (auto& loop_1 : ld) { + LoopFusion fusion(c, &loop_0, &loop_1); + + if (fusion.AreCompatible() && fusion.IsLegal()) { + RegisterLiveness liveness(c, function); + RegisterLiveness::RegionRegisterLiveness reg_pressure{}; + liveness.SimulateFusion(loop_0, loop_1, ®_pressure); + + if (reg_pressure.used_registers_ <= max_registers_per_loop_) { + fusion.Fuse(); + // Recurse, as the current iterators will have been invalidated. + ProcessFunction(function); + return true; + } + } + } + } + + return modified; +} + +} // namespace opt +} // namespace spvtools diff --git a/source/opt/loop_fusion_pass.h b/source/opt/loop_fusion_pass.h new file mode 100644 index 000000000..868e9b5e9 --- /dev/null +++ b/source/opt/loop_fusion_pass.h @@ -0,0 +1,51 @@ +// 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_OPT_LOOP_FUSION_PASS_H_ +#define SOURCE_OPT_LOOP_FUSION_PASS_H_ + +#include "opt/pass.h" + +namespace spvtools { +namespace opt { + +// Implements a loop fusion pass. +// This pass will look for adjacent loops that are compatible and legal to be +// fused. It will fuse all such loops as long as the register usage for the +// fused loop stays under the threshold defined by |max_registers_per_loop|. +class LoopFusionPass : public Pass { + public: + explicit LoopFusionPass(size_t max_registers_per_loop) + : Pass(), max_registers_per_loop_(max_registers_per_loop) {} + + const char* name() const override { return "loop-fusion"; } + + // Processes the given |module|. Returns Status::Failure if errors occur when + // processing. Returns the corresponding Status::Success if processing is + // succesful to indicate whether changes have been made to the modue. + Status Process(ir::IRContext* c) override; + + private: + // Fuse loops in |function| if compatible, legal and the fused loop won't use + // too many registers. + bool ProcessFunction(ir::Function* function); + + // The maximum number of registers a fused loop is allowed to use. + size_t max_registers_per_loop_; +}; + +} // namespace opt +} // namespace spvtools + +#endif // SOURCE_OPT_LOOP_FUSION_PASS_H_ diff --git a/source/opt/optimizer.cpp b/source/opt/optimizer.cpp index f184a9a49..51a775949 100644 --- a/source/opt/optimizer.cpp +++ b/source/opt/optimizer.cpp @@ -384,6 +384,11 @@ Optimizer::PassToken CreateLoopFissionPass(size_t threshold) { MakeUnique(threshold)); } +Optimizer::PassToken CreateLoopFusionPass(size_t max_registers_per_loop) { + return MakeUnique( + MakeUnique(max_registers_per_loop)); +} + Optimizer::PassToken CreateLoopInvariantCodeMotionPass() { return MakeUnique(MakeUnique()); } diff --git a/source/opt/passes.h b/source/opt/passes.h index 4f44d613b..3a26220c3 100644 --- a/source/opt/passes.h +++ b/source/opt/passes.h @@ -43,6 +43,7 @@ #include "local_single_store_elim_pass.h" #include "local_ssa_elim_pass.h" #include "loop_fission.h" +#include "loop_fusion_pass.h" #include "loop_peeling.h" #include "loop_unroller.h" #include "loop_unswitch_pass.h" diff --git a/source/opt/scalar_analysis.cpp b/source/opt/scalar_analysis.cpp index 0920c2ea3..a67afb8e1 100644 --- a/source/opt/scalar_analysis.cpp +++ b/source/opt/scalar_analysis.cpp @@ -49,7 +49,7 @@ namespace opt { uint32_t SENode::NumberOfNodes = 0; ScalarEvolutionAnalysis::ScalarEvolutionAnalysis(ir::IRContext* context) - : context_(context) { + : context_(context), pretend_equal_{} { // Create and cached the CantComputeNode. cached_cant_compute_ = GetCachedOrAdd(std::unique_ptr(new SECantCompute(this))); @@ -80,7 +80,15 @@ SENode* ScalarEvolutionAnalysis::CreateRecurrentExpression( if (offset->IsCantCompute() || coefficient->IsCantCompute()) return CreateCantComputeNode(); - std::unique_ptr phi_node{new SERecurrentNode(this, loop)}; + const ir::Loop* loop_to_use = nullptr; + if (pretend_equal_[loop]) { + loop_to_use = pretend_equal_[loop]; + } else { + loop_to_use = loop; + } + + std::unique_ptr phi_node{ + new SERecurrentNode(this, loop_to_use)}; phi_node->AddOffset(offset); phi_node->AddCoefficient(coefficient); @@ -270,7 +278,14 @@ SENode* ScalarEvolutionAnalysis::AnalyzePhiInstruction( loop->GetHeaderBlock() != basic_block) return recurrent_node_map_[phi] = CreateCantComputeNode(); - std::unique_ptr phi_node{new SERecurrentNode(this, loop)}; + const ir::Loop* loop_to_use = nullptr; + if (pretend_equal_[loop]) { + loop_to_use = pretend_equal_[loop]; + } else { + loop_to_use = loop; + } + std::unique_ptr phi_node{ + new SERecurrentNode(this, loop_to_use)}; // We add the node to this map to allow it to be returned before the node is // fully built. This is needed as the subsequent call to AnalyzeInstruction diff --git a/source/opt/scalar_analysis.h b/source/opt/scalar_analysis.h index 88726c982..35414330f 100644 --- a/source/opt/scalar_analysis.h +++ b/source/opt/scalar_analysis.h @@ -120,6 +120,14 @@ class ScalarEvolutionAnalysis { SENode* UpdateChildNode(SENode* parent, SENode* child, SENode* new_child); + // The loops in |loop_pair| will be considered the same when constructing + // SERecurrentNode objects. This enables analysing dependencies that will be + // created during loop fusion. + void AddLoopsToPretendAreTheSame( + const std::pair& loop_pair) { + pretend_equal_[std::get<1>(loop_pair)] = std::get<0>(loop_pair); + } + private: SENode* AnalyzeConstant(const ir::Instruction* inst); @@ -158,6 +166,10 @@ class ScalarEvolutionAnalysis { // managed by they set. std::unordered_set, SENodeHash, NodePointersEquality> node_cache_; + + // Loops that should be considered the same for performing analysis for loop + // fusion. + std::map pretend_equal_; }; // Wrapping class to manipulate SENode pointer using + - * / operators. diff --git a/test/opt/loop_optimizations/CMakeLists.txt b/test/opt/loop_optimizations/CMakeLists.txt index 26f323868..667ceb20b 100644 --- a/test/opt/loop_optimizations/CMakeLists.txt +++ b/test/opt/loop_optimizations/CMakeLists.txt @@ -115,4 +115,26 @@ add_spvtools_unittest(TARGET loop_fission LIBS SPIRV-Tools-opt ) +add_spvtools_unittest(TARGET fusion_compatibility + SRCS ../function_utils.h + fusion_compatibility.cpp + LIBS SPIRV-Tools-opt +) +add_spvtools_unittest(TARGET fusion_illegal + SRCS ../function_utils.h + fusion_illegal.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET fusion_legal + SRCS ../function_utils.h + fusion_legal.cpp + LIBS SPIRV-Tools-opt +) + +add_spvtools_unittest(TARGET fusion_pass + SRCS ../function_utils.h + fusion_pass.cpp + LIBS SPIRV-Tools-opt +) diff --git a/test/opt/loop_optimizations/fusion_compatibility.cpp b/test/opt/loop_optimizations/fusion_compatibility.cpp new file mode 100644 index 000000000..709b34fbf --- /dev/null +++ b/test/opt/loop_optimizations/fusion_compatibility.cpp @@ -0,0 +1,1790 @@ +// 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 + +#include +#include +#include +#include +#include + +#include "../pass_fixture.h" +#include "opt/loop_descriptor.h" +#include "opt/loop_fusion.h" + +namespace { + +using namespace spvtools; + +using FusionCompatibilityTest = PassTest<::testing::Test>; + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int i = 0; // Can't fuse, i=0 in first & i=10 in second + for (; i < 10; i++) {} + for (; i < 10; i++) {} +} +*/ +TEST_F(FusionCompatibilityTest, SameInductionVariableDifferentBounds) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %31 = OpPhi %6 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %31 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %6 %31 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpBranch %22 + %22 = OpLabel + %32 = OpPhi %6 %31 %12 %30 %25 + OpLoopMerge %24 %25 None + OpBranch %26 + %26 = OpLabel + %28 = OpSLessThan %17 %32 %16 + OpBranchConditional %28 %23 %24 + %23 = OpLabel + OpBranch %25 + %25 = OpLabel + %30 = OpIAdd %6 %32 %20 + OpStore %8 %30 + OpBranch %22 + %24 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_FALSE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 1 +#version 440 core +void main() { + for (int i = 0; i < 10; i++) {} + for (int i = 0; i < 10; i++) {} +} +*/ +TEST_F(FusionCompatibilityTest, Compatible) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %22 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %22 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %32 = OpPhi %6 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %32 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %6 %32 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpStore %22 %9 + OpBranch %23 + %23 = OpLabel + %33 = OpPhi %6 %9 %12 %31 %26 + OpLoopMerge %25 %26 None + OpBranch %27 + %27 = OpLabel + %29 = OpSLessThan %17 %33 %16 + OpBranchConditional %29 %24 %25 + %24 = OpLabel + OpBranch %26 + %26 = OpLabel + %31 = OpIAdd %6 %33 %20 + OpStore %22 %31 + OpBranch %23 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 2 +#version 440 core +void main() { + for (int i = 0; i < 10; i++) {} + for (int j = 0; j < 10; j++) {} +} + +*/ +TEST_F(FusionCompatibilityTest, DifferentName) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %22 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %22 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %32 = OpPhi %6 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %32 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %6 %32 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpStore %22 %9 + OpBranch %23 + %23 = OpLabel + %33 = OpPhi %6 %9 %12 %31 %26 + OpLoopMerge %25 %26 None + OpBranch %27 + %27 = OpLabel + %29 = OpSLessThan %17 %33 %16 + OpBranchConditional %29 %24 %25 + %24 = OpLabel + OpBranch %26 + %26 = OpLabel + %31 = OpIAdd %6 %33 %20 + OpStore %22 %31 + OpBranch %23 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + // Can't fuse, different step + for (int i = 0; i < 10; i++) {} + for (int j = 0; j < 10; j=j+2) {} +} + +*/ +TEST_F(FusionCompatibilityTest, SameBoundsDifferentStep) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %22 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %31 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %22 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %33 = OpPhi %6 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %33 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %6 %33 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpStore %22 %9 + OpBranch %23 + %23 = OpLabel + %34 = OpPhi %6 %9 %12 %32 %26 + OpLoopMerge %25 %26 None + OpBranch %27 + %27 = OpLabel + %29 = OpSLessThan %17 %34 %16 + OpBranchConditional %29 %24 %25 + %24 = OpLabel + OpBranch %26 + %26 = OpLabel + %32 = OpIAdd %6 %34 %31 + OpStore %22 %32 + OpBranch %23 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_FALSE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 4 +#version 440 core +void main() { + // Can't fuse, different upper bound + for (int i = 0; i < 10; i++) {} + for (int j = 0; j < 20; j++) {} +} + +*/ +TEST_F(FusionCompatibilityTest, DifferentUpperBound) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %22 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %29 = OpConstant %6 20 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %22 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %33 = OpPhi %6 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %33 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %6 %33 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpStore %22 %9 + OpBranch %23 + %23 = OpLabel + %34 = OpPhi %6 %9 %12 %32 %26 + OpLoopMerge %25 %26 None + OpBranch %27 + %27 = OpLabel + %30 = OpSLessThan %17 %34 %29 + OpBranchConditional %30 %24 %25 + %24 = OpLabel + OpBranch %26 + %26 = OpLabel + %32 = OpIAdd %6 %34 %20 + OpStore %22 %32 + OpBranch %23 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_FALSE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 5 +#version 440 core +void main() { + // Can't fuse, different lower bound + for (int i = 5; i < 10; i++) {} + for (int j = 0; j < 10; j++) {} +} + +*/ +TEST_F(FusionCompatibilityTest, DifferentLowerBound) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %22 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 5 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %23 = OpConstant %6 0 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %22 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %33 = OpPhi %6 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %33 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %6 %33 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpStore %22 %23 + OpBranch %24 + %24 = OpLabel + %34 = OpPhi %6 %23 %12 %32 %27 + OpLoopMerge %26 %27 None + OpBranch %28 + %28 = OpLabel + %30 = OpSLessThan %17 %34 %16 + OpBranchConditional %30 %25 %26 + %25 = OpLabel + OpBranch %27 + %27 = OpLabel + %32 = OpIAdd %6 %34 %20 + OpStore %22 %32 + OpBranch %24 + %26 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_FALSE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 6 +#version 440 core +void main() { + // Can't fuse, break in first loop + for (int i = 0; i < 10; i++) { + if (i == 5) { + break; + } + } + for (int j = 0; j < 10; j++) {} +} + +*/ +TEST_F(FusionCompatibilityTest, Break) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %28 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 5 + %26 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %28 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %38 = OpPhi %6 %9 %5 %27 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %38 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %21 = OpIEqual %17 %38 %20 + OpSelectionMerge %23 None + OpBranchConditional %21 %22 %23 + %22 = OpLabel + OpBranch %12 + %23 = OpLabel + OpBranch %13 + %13 = OpLabel + %27 = OpIAdd %6 %38 %26 + OpStore %8 %27 + OpBranch %10 + %12 = OpLabel + OpStore %28 %9 + OpBranch %29 + %29 = OpLabel + %39 = OpPhi %6 %9 %12 %37 %32 + OpLoopMerge %31 %32 None + OpBranch %33 + %33 = OpLabel + %35 = OpSLessThan %17 %39 %16 + OpBranchConditional %35 %30 %31 + %30 = OpLabel + OpBranch %32 + %32 = OpLabel + %37 = OpIAdd %6 %39 %26 + OpStore %28 %37 + OpBranch %29 + %31 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_FALSE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +layout(location = 0) in vec4 c; +void main() { + int N = int(c.x); + for (int i = 0; i < N; i++) {} + for (int j = 0; j < N; j++) {} +} + +*/ +TEST_F(FusionCompatibilityTest, UnknownButSameUpperBound) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %12 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "N" + OpName %12 "c" + OpName %19 "i" + OpName %33 "j" + OpDecorate %12 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpTypeFloat 32 + %10 = OpTypeVector %9 4 + %11 = OpTypePointer Input %10 + %12 = OpVariable %11 Input + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 0 + %15 = OpTypePointer Input %9 + %20 = OpConstant %6 0 + %28 = OpTypeBool + %31 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %33 = OpVariable %7 Function + %16 = OpAccessChain %15 %12 %14 + %17 = OpLoad %9 %16 + %18 = OpConvertFToS %6 %17 + OpStore %8 %18 + OpStore %19 %20 + OpBranch %21 + %21 = OpLabel + %44 = OpPhi %6 %20 %5 %32 %24 + OpLoopMerge %23 %24 None + OpBranch %25 + %25 = OpLabel + %29 = OpSLessThan %28 %44 %18 + OpBranchConditional %29 %22 %23 + %22 = OpLabel + OpBranch %24 + %24 = OpLabel + %32 = OpIAdd %6 %44 %31 + OpStore %19 %32 + OpBranch %21 + %23 = OpLabel + OpStore %33 %20 + OpBranch %34 + %34 = OpLabel + %46 = OpPhi %6 %20 %23 %43 %37 + OpLoopMerge %36 %37 None + OpBranch %38 + %38 = OpLabel + %41 = OpSLessThan %28 %46 %18 + OpBranchConditional %41 %35 %36 + %35 = OpLabel + OpBranch %37 + %37 = OpLabel + %43 = OpIAdd %6 %46 %31 + OpStore %33 %43 + OpBranch %34 + %36 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +layout(location = 0) in vec4 c; +void main() { + int N = int(c.x); + for (int i = 0; N > j; i++) {} + for (int j = 0; N > j; j++) {} +} +*/ +TEST_F(FusionCompatibilityTest, UnknownButSameUpperBoundReverseCondition) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %12 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "N" + OpName %12 "c" + OpName %19 "i" + OpName %33 "j" + OpDecorate %12 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpTypeFloat 32 + %10 = OpTypeVector %9 4 + %11 = OpTypePointer Input %10 + %12 = OpVariable %11 Input + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 0 + %15 = OpTypePointer Input %9 + %20 = OpConstant %6 0 + %28 = OpTypeBool + %31 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %33 = OpVariable %7 Function + %16 = OpAccessChain %15 %12 %14 + %17 = OpLoad %9 %16 + %18 = OpConvertFToS %6 %17 + OpStore %8 %18 + OpStore %19 %20 + OpBranch %21 + %21 = OpLabel + %45 = OpPhi %6 %20 %5 %32 %24 + OpLoopMerge %23 %24 None + OpBranch %25 + %25 = OpLabel + %29 = OpSGreaterThan %28 %18 %45 + OpBranchConditional %29 %22 %23 + %22 = OpLabel + OpBranch %24 + %24 = OpLabel + %32 = OpIAdd %6 %45 %31 + OpStore %19 %32 + OpBranch %21 + %23 = OpLabel + OpStore %33 %20 + OpBranch %34 + %34 = OpLabel + %47 = OpPhi %6 %20 %23 %43 %37 + OpLoopMerge %36 %37 None + OpBranch %38 + %38 = OpLabel + %41 = OpSGreaterThan %28 %18 %47 + OpBranchConditional %41 %35 %36 + %35 = OpLabel + OpBranch %37 + %37 = OpLabel + %43 = OpIAdd %6 %47 %31 + OpStore %33 %43 + OpBranch %34 + %36 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +layout(location = 0) in vec4 c; +void main() { + // Can't fuse different bound + int N = int(c.x); + for (int i = 0; i < N; i++) {} + for (int j = 0; j < N+1; j++) {} +} + +*/ +TEST_F(FusionCompatibilityTest, UnknownUpperBoundAddition) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %12 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "N" + OpName %12 "c" + OpName %19 "i" + OpName %33 "j" + OpDecorate %12 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpTypeFloat 32 + %10 = OpTypeVector %9 4 + %11 = OpTypePointer Input %10 + %12 = OpVariable %11 Input + %13 = OpTypeInt 32 0 + %14 = OpConstant %13 0 + %15 = OpTypePointer Input %9 + %20 = OpConstant %6 0 + %28 = OpTypeBool + %31 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %33 = OpVariable %7 Function + %16 = OpAccessChain %15 %12 %14 + %17 = OpLoad %9 %16 + %18 = OpConvertFToS %6 %17 + OpStore %8 %18 + OpStore %19 %20 + OpBranch %21 + %21 = OpLabel + %45 = OpPhi %6 %20 %5 %32 %24 + OpLoopMerge %23 %24 None + OpBranch %25 + %25 = OpLabel + %29 = OpSLessThan %28 %45 %18 + OpBranchConditional %29 %22 %23 + %22 = OpLabel + OpBranch %24 + %24 = OpLabel + %32 = OpIAdd %6 %45 %31 + OpStore %19 %32 + OpBranch %21 + %23 = OpLabel + OpStore %33 %20 + OpBranch %34 + %34 = OpLabel + %47 = OpPhi %6 %20 %23 %44 %37 + OpLoopMerge %36 %37 None + OpBranch %38 + %38 = OpLabel + %41 = OpIAdd %6 %18 %31 + %42 = OpSLessThan %28 %47 %41 + OpBranchConditional %42 %35 %36 + %35 = OpLabel + OpBranch %37 + %37 = OpLabel + %44 = OpIAdd %6 %47 %31 + OpStore %33 %44 + OpBranch %34 + %36 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_FALSE(fusion.AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 10 +#version 440 core +void main() { + for (int i = 0; i < 10; i++) {} + for (int j = 0; j < 10; j++) {} + for (int k = 0; k < 10; k++) {} +} + +*/ +TEST_F(FusionCompatibilityTest, SeveralAdjacentLoops) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %22 "j" + OpName %32 "k" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %22 = OpVariable %7 Function + %32 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %42 = OpPhi %6 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %42 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %6 %42 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpStore %22 %9 + OpBranch %23 + %23 = OpLabel + %43 = OpPhi %6 %9 %12 %31 %26 + OpLoopMerge %25 %26 None + OpBranch %27 + %27 = OpLabel + %29 = OpSLessThan %17 %43 %16 + OpBranchConditional %29 %24 %25 + %24 = OpLabel + OpBranch %26 + %26 = OpLabel + %31 = OpIAdd %6 %43 %20 + OpStore %22 %31 + OpBranch %23 + %25 = OpLabel + OpStore %32 %9 + OpBranch %33 + %33 = OpLabel + %44 = OpPhi %6 %9 %25 %41 %36 + OpLoopMerge %35 %36 None + OpBranch %37 + %37 = OpLabel + %39 = OpSLessThan %17 %44 %16 + OpBranchConditional %39 %34 %35 + %34 = OpLabel + OpBranch %36 + %36 = OpLabel + %41 = OpIAdd %6 %44 %20 + OpStore %32 %41 + OpBranch %33 + %35 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + + EXPECT_FALSE(opt::LoopFusion(context.get(), loop_0, loop_0).AreCompatible()); + EXPECT_FALSE(opt::LoopFusion(context.get(), loop_0, loop_2).AreCompatible()); + EXPECT_FALSE(opt::LoopFusion(context.get(), loop_1, loop_0).AreCompatible()); + EXPECT_TRUE(opt::LoopFusion(context.get(), loop_0, loop_1).AreCompatible()); + EXPECT_TRUE(opt::LoopFusion(context.get(), loop_1, loop_2).AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + // Can't fuse, not adjacent + int x = 0; + for (int i = 0; i < 10; i++) { + if (i > 10) { + x++; + } + } + x++; + for (int j = 0; j < 10; j++) {} + for (int k = 0; k < 10; k++) {} +} + +*/ +TEST_F(FusionCompatibilityTest, NonAdjacentLoops) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "x" + OpName %10 "i" + OpName %31 "j" + OpName %41 "k" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %25 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %31 = OpVariable %7 Function + %41 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + %52 = OpPhi %6 %9 %5 %56 %14 + %51 = OpPhi %6 %9 %5 %28 %14 + OpLoopMerge %13 %14 None + OpBranch %15 + %15 = OpLabel + %19 = OpSLessThan %18 %51 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + %21 = OpSGreaterThan %18 %52 %17 + OpSelectionMerge %23 None + OpBranchConditional %21 %22 %23 + %22 = OpLabel + %26 = OpIAdd %6 %52 %25 + OpStore %8 %26 + OpBranch %23 + %23 = OpLabel + %56 = OpPhi %6 %52 %12 %26 %22 + OpBranch %14 + %14 = OpLabel + %28 = OpIAdd %6 %51 %25 + OpStore %10 %28 + OpBranch %11 + %13 = OpLabel + %30 = OpIAdd %6 %52 %25 + OpStore %8 %30 + OpStore %31 %9 + OpBranch %32 + %32 = OpLabel + %53 = OpPhi %6 %9 %13 %40 %35 + OpLoopMerge %34 %35 None + OpBranch %36 + %36 = OpLabel + %38 = OpSLessThan %18 %53 %17 + OpBranchConditional %38 %33 %34 + %33 = OpLabel + OpBranch %35 + %35 = OpLabel + %40 = OpIAdd %6 %53 %25 + OpStore %31 %40 + OpBranch %32 + %34 = OpLabel + OpStore %41 %9 + OpBranch %42 + %42 = OpLabel + %54 = OpPhi %6 %9 %34 %50 %45 + OpLoopMerge %44 %45 None + OpBranch %46 + %46 = OpLabel + %48 = OpSLessThan %18 %54 %17 + OpBranchConditional %48 %43 %44 + %43 = OpLabel + OpBranch %45 + %45 = OpLabel + %50 = OpIAdd %6 %54 %25 + OpStore %41 %50 + OpBranch %42 + %44 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + + EXPECT_FALSE(opt::LoopFusion(context.get(), loop_0, loop_0).AreCompatible()); + EXPECT_FALSE(opt::LoopFusion(context.get(), loop_0, loop_2).AreCompatible()); + EXPECT_FALSE(opt::LoopFusion(context.get(), loop_0, loop_1).AreCompatible()); + EXPECT_TRUE(opt::LoopFusion(context.get(), loop_1, loop_2).AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 12 +#version 440 core +void main() { + int j = 0; + int i = 0; + for (; i < 10; i++) {} + for (; j < 10; j++) {} +} + +*/ +TEST_F(FusionCompatibilityTest, CompatibleInitDeclaredBeforeLoops) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "j" + OpName %10 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %21 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + %32 = OpPhi %6 %9 %5 %22 %14 + OpLoopMerge %13 %14 None + OpBranch %15 + %15 = OpLabel + %19 = OpSLessThan %18 %32 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + OpBranch %14 + %14 = OpLabel + %22 = OpIAdd %6 %32 %21 + OpStore %10 %22 + OpBranch %11 + %13 = OpLabel + OpBranch %23 + %23 = OpLabel + %33 = OpPhi %6 %9 %13 %31 %26 + OpLoopMerge %25 %26 None + OpBranch %27 + %27 = OpLabel + %29 = OpSLessThan %18 %33 %17 + OpBranchConditional %29 %24 %25 + %24 = OpLabel + OpBranch %26 + %26 = OpLabel + %31 = OpIAdd %6 %33 %21 + OpStore %8 %31 + OpBranch %23 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + EXPECT_TRUE( + opt::LoopFusion(context.get(), loops[0], loops[1]).AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 13 regenerate! +#version 440 core +void main() { + int[10] a; + int[10] b; + // Can't fuse, several induction variables + for (int j = 0; j < 10; j++) { + b[i] = a[i]; + } + for (int i = 0, j = 0; i < 10; i++, j = j+2) { + } +} + +*/ +TEST_F(FusionCompatibilityTest, SeveralInductionVariables) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "j" + OpName %23 "b" + OpName %25 "a" + OpName %33 "i" + OpName %34 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %31 = OpConstant %6 1 + %48 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %33 = OpVariable %7 Function + %34 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %50 = OpPhi %6 %9 %5 %32 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %50 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %50 + %28 = OpLoad %6 %27 + %29 = OpAccessChain %7 %23 %50 + OpStore %29 %28 + OpBranch %13 + %13 = OpLabel + %32 = OpIAdd %6 %50 %31 + OpStore %8 %32 + OpBranch %10 + %12 = OpLabel + OpStore %33 %9 + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %52 = OpPhi %6 %9 %12 %49 %38 + %51 = OpPhi %6 %9 %12 %46 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %51 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %44 = OpAccessChain %7 %25 %52 + OpStore %44 %51 + OpBranch %38 + %38 = OpLabel + %46 = OpIAdd %6 %51 %31 + OpStore %33 %46 + %49 = OpIAdd %6 %52 %48 + OpStore %34 %49 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + EXPECT_FALSE( + opt::LoopFusion(context.get(), loops[0], loops[1]).AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 14 +#version 440 core +void main() { + // Fine + for (int i = 0; i < 10; i = i + 2) {} + for (int j = 0; j < 10; j = j + 2) {} +} + +*/ +TEST_F(FusionCompatibilityTest, CompatibleNonIncrementStep) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "j" + OpName %10 "i" + OpName %11 "i" + OpName %24 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %18 = OpConstant %6 10 + %19 = OpTypeBool + %22 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %11 = OpVariable %7 Function + %24 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpStore %11 %9 + OpBranch %12 + %12 = OpLabel + %34 = OpPhi %6 %9 %5 %23 %15 + OpLoopMerge %14 %15 None + OpBranch %16 + %16 = OpLabel + %20 = OpSLessThan %19 %34 %18 + OpBranchConditional %20 %13 %14 + %13 = OpLabel + OpBranch %15 + %15 = OpLabel + %23 = OpIAdd %6 %34 %22 + OpStore %11 %23 + OpBranch %12 + %14 = OpLabel + OpStore %24 %9 + OpBranch %25 + %25 = OpLabel + %35 = OpPhi %6 %9 %14 %33 %28 + OpLoopMerge %27 %28 None + OpBranch %29 + %29 = OpLabel + %31 = OpSLessThan %19 %35 %18 + OpBranchConditional %31 %26 %27 + %26 = OpLabel + OpBranch %28 + %28 = OpLabel + %33 = OpIAdd %6 %35 %22 + OpStore %24 %33 + OpBranch %25 + %27 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + EXPECT_TRUE( + opt::LoopFusion(context.get(), loops[0], loops[1]).AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 15 +#version 440 core + +int j = 0; + +void main() { + // Not compatible, unknown init for second. + for (int i = 0; i < 10; i = i + 2) {} + for (; j < 10; j = j + 2) {} +} + +*/ +TEST_F(FusionCompatibilityTest, UnknonInitForSecondLoop) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "j" + OpName %11 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Private %6 + %8 = OpVariable %7 Private + %9 = OpConstant %6 0 + %10 = OpTypePointer Function %6 + %18 = OpConstant %6 10 + %19 = OpTypeBool + %22 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %11 = OpVariable %10 Function + OpStore %8 %9 + OpStore %11 %9 + OpBranch %12 + %12 = OpLabel + %33 = OpPhi %6 %9 %5 %23 %15 + OpLoopMerge %14 %15 None + OpBranch %16 + %16 = OpLabel + %20 = OpSLessThan %19 %33 %18 + OpBranchConditional %20 %13 %14 + %13 = OpLabel + OpBranch %15 + %15 = OpLabel + %23 = OpIAdd %6 %33 %22 + OpStore %11 %23 + OpBranch %12 + %14 = OpLabel + OpBranch %24 + %24 = OpLabel + OpLoopMerge %26 %27 None + OpBranch %28 + %28 = OpLabel + %29 = OpLoad %6 %8 + %30 = OpSLessThan %19 %29 %18 + OpBranchConditional %30 %25 %26 + %25 = OpLabel + OpBranch %27 + %27 = OpLabel + %31 = OpLoad %6 %8 + %32 = OpIAdd %6 %31 %22 + OpStore %8 %32 + OpBranch %24 + %26 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + EXPECT_FALSE( + opt::LoopFusion(context.get(), loops[0], loops[1]).AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 16 +#version 440 core +void main() { + // Not compatible, continue in loop 0 + for (int i = 0; i < 10; ++i) { + if (i % 2 == 1) { + continue; + } + } + for (int j = 0; j < 10; ++j) {} +} + +*/ +TEST_F(FusionCompatibilityTest, Continue) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %29 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 2 + %22 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %29 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %39 = OpPhi %6 %9 %5 %28 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %39 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %21 = OpSMod %6 %39 %20 + %23 = OpIEqual %17 %21 %22 + OpSelectionMerge %25 None + OpBranchConditional %23 %24 %25 + %24 = OpLabel + OpBranch %13 + %25 = OpLabel + OpBranch %13 + %13 = OpLabel + %28 = OpIAdd %6 %39 %22 + OpStore %8 %28 + OpBranch %10 + %12 = OpLabel + OpStore %29 %9 + OpBranch %30 + %30 = OpLabel + %40 = OpPhi %6 %9 %12 %38 %33 + OpLoopMerge %32 %33 None + OpBranch %34 + %34 = OpLabel + %36 = OpSLessThan %17 %40 %16 + OpBranchConditional %36 %31 %32 + %31 = OpLabel + OpBranch %33 + %33 = OpLabel + %38 = OpIAdd %6 %40 %22 + OpStore %29 %38 + OpBranch %30 + %32 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + EXPECT_FALSE( + opt::LoopFusion(context.get(), loops[0], loops[1]).AreCompatible()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + // Compatible + for (int i = 0; i < 10; ++i) { + if (i % 2 == 1) { + } else { + a[i] = i; + } + } + for (int j = 0; j < 10; ++j) {} +} + +*/ +TEST_F(FusionCompatibilityTest, IfElseInLoop) { + const std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %31 "a" + OpName %37 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 2 + %22 = OpConstant %6 1 + %27 = OpTypeInt 32 0 + %28 = OpConstant %27 10 + %29 = OpTypeArray %6 %28 + %30 = OpTypePointer Function %29 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %31 = OpVariable %30 Function + %37 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %47 = OpPhi %6 %9 %5 %36 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %47 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %21 = OpSMod %6 %47 %20 + %23 = OpIEqual %17 %21 %22 + OpSelectionMerge %25 None + OpBranchConditional %23 %24 %26 + %24 = OpLabel + OpBranch %25 + %26 = OpLabel + %34 = OpAccessChain %7 %31 %47 + OpStore %34 %47 + OpBranch %25 + %25 = OpLabel + OpBranch %13 + %13 = OpLabel + %36 = OpIAdd %6 %47 %22 + OpStore %8 %36 + OpBranch %10 + %12 = OpLabel + OpStore %37 %9 + OpBranch %38 + %38 = OpLabel + %48 = OpPhi %6 %9 %12 %46 %41 + OpLoopMerge %40 %41 None + OpBranch %42 + %42 = OpLabel + %44 = OpSLessThan %17 %48 %16 + OpBranchConditional %44 %39 %40 + %39 = OpLabel + OpBranch %41 + %41 = OpLabel + %46 = OpIAdd %6 %48 %22 + OpStore %37 %46 + OpBranch %38 + %40 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + EXPECT_TRUE( + opt::LoopFusion(context.get(), loops[0], loops[1]).AreCompatible()); +} + +} // namespace diff --git a/test/opt/loop_optimizations/fusion_illegal.cpp b/test/opt/loop_optimizations/fusion_illegal.cpp new file mode 100644 index 000000000..a30702281 --- /dev/null +++ b/test/opt/loop_optimizations/fusion_illegal.cpp @@ -0,0 +1,1591 @@ +// 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 + +#include +#include +#include +#include +#include + +#include "../pass_fixture.h" +#include "opt/loop_descriptor.h" +#include "opt/loop_fusion.h" + +namespace { + +using namespace spvtools; + +using FusionIllegalTest = PassTest<::testing::Test>; + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Illegal, loop-independent dependence will become a + // backward loop-carried antidependence + for (int i = 0; i < 10; i++) { + a[i] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[i+1] + 2; + } +} + +*/ +TEST_F(FusionIllegalTest, PositiveDistanceCreatedRAW) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %25 "b" + OpName %34 "i" + OpName %42 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %29 = OpConstant %6 1 + %48 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %34 = OpVariable %7 Function + %42 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %53 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %53 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %53 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %28 %29 + %31 = OpAccessChain %7 %23 %53 + OpStore %31 %30 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %53 %29 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %54 = OpPhi %6 %9 %12 %52 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %54 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpIAdd %6 %54 %29 + %46 = OpAccessChain %7 %23 %45 + %47 = OpLoad %6 %46 + %49 = OpIAdd %6 %47 %48 + %50 = OpAccessChain %7 %42 %54 + OpStore %50 %49 + OpBranch %38 + %38 = OpLabel + %52 = OpIAdd %6 %54 %29 + OpStore %34 %52 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core + +int func() { + return 10; +} + +void main() { + int[10] a; + int[10] b; + // Illegal, function call + for (int i = 0; i < 10; i++) { + a[i] = func(); + } + for (int i = 0; i < 10; i++) { + b[i] = a[i]; + } +} +*/ +TEST_F(FusionIllegalTest, FunctionCall) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "func(" + OpName %14 "i" + OpName %28 "a" + OpName %35 "i" + OpName %43 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeFunction %6 + %10 = OpConstant %6 10 + %13 = OpTypePointer Function %6 + %15 = OpConstant %6 0 + %22 = OpTypeBool + %24 = OpTypeInt 32 0 + %25 = OpConstant %24 10 + %26 = OpTypeArray %6 %25 + %27 = OpTypePointer Function %26 + %33 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %14 = OpVariable %13 Function + %28 = OpVariable %27 Function + %35 = OpVariable %13 Function + %43 = OpVariable %27 Function + OpStore %14 %15 + OpBranch %16 + %16 = OpLabel + %51 = OpPhi %6 %15 %5 %34 %19 + OpLoopMerge %18 %19 None + OpBranch %20 + %20 = OpLabel + %23 = OpSLessThan %22 %51 %10 + OpBranchConditional %23 %17 %18 + %17 = OpLabel + %30 = OpFunctionCall %6 %8 + %31 = OpAccessChain %13 %28 %51 + OpStore %31 %30 + OpBranch %19 + %19 = OpLabel + %34 = OpIAdd %6 %51 %33 + OpStore %14 %34 + OpBranch %16 + %18 = OpLabel + OpStore %35 %15 + OpBranch %36 + %36 = OpLabel + %52 = OpPhi %6 %15 %18 %50 %39 + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + %42 = OpSLessThan %22 %52 %10 + OpBranchConditional %42 %37 %38 + %37 = OpLabel + %46 = OpAccessChain %13 %28 %52 + %47 = OpLoad %6 %46 + %48 = OpAccessChain %13 %43 %52 + OpStore %48 %47 + OpBranch %39 + %39 = OpLabel + %50 = OpIAdd %6 %52 %33 + OpStore %35 %50 + OpBranch %36 + %38 = OpLabel + OpReturn + OpFunctionEnd + %8 = OpFunction %6 None %7 + %9 = OpLabel + OpReturnValue %10 + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 16 +#version 440 core +void main() { + int[10][10] a; + int[10][10] b; + int[10][10] c; + // Illegal outer. + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + c[i][j] = a[i][j] + 2; + } + } + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + b[i][j] = c[i+1][j] + 10; + } + } +} + +*/ +TEST_F(FusionIllegalTest, PositiveDistanceCreatedRAWOuterLoop) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %19 "j" + OpName %32 "c" + OpName %35 "a" + OpName %48 "i" + OpName %56 "j" + OpName %64 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %27 = OpTypeInt 32 0 + %28 = OpConstant %27 10 + %29 = OpTypeArray %6 %28 + %30 = OpTypeArray %29 %28 + %31 = OpTypePointer Function %30 + %40 = OpConstant %6 2 + %44 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %31 Function + %35 = OpVariable %31 Function + %48 = OpVariable %7 Function + %56 = OpVariable %7 Function + %64 = OpVariable %31 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %78 = OpPhi %6 %9 %5 %47 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %78 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + %82 = OpPhi %6 %9 %11 %45 %23 + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %26 = OpSLessThan %17 %82 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + %38 = OpAccessChain %7 %35 %78 %82 + %39 = OpLoad %6 %38 + %41 = OpIAdd %6 %39 %40 + %42 = OpAccessChain %7 %32 %78 %82 + OpStore %42 %41 + OpBranch %23 + %23 = OpLabel + %45 = OpIAdd %6 %82 %44 + OpStore %19 %45 + OpBranch %20 + %22 = OpLabel + OpBranch %13 + %13 = OpLabel + %47 = OpIAdd %6 %78 %44 + OpStore %8 %47 + OpBranch %10 + %12 = OpLabel + OpStore %48 %9 + OpBranch %49 + %49 = OpLabel + %79 = OpPhi %6 %9 %12 %77 %52 + OpLoopMerge %51 %52 None + OpBranch %53 + %53 = OpLabel + %55 = OpSLessThan %17 %79 %16 + OpBranchConditional %55 %50 %51 + %50 = OpLabel + OpStore %56 %9 + OpBranch %57 + %57 = OpLabel + %80 = OpPhi %6 %9 %50 %75 %60 + OpLoopMerge %59 %60 None + OpBranch %61 + %61 = OpLabel + %63 = OpSLessThan %17 %80 %16 + OpBranchConditional %63 %58 %59 + %58 = OpLabel + %68 = OpIAdd %6 %79 %44 + %70 = OpAccessChain %7 %32 %68 %80 + %71 = OpLoad %6 %70 + %72 = OpIAdd %6 %71 %16 + %73 = OpAccessChain %7 %64 %79 %80 + OpStore %73 %72 + OpBranch %60 + %60 = OpLabel + %75 = OpIAdd %6 %80 %44 + OpStore %56 %75 + OpBranch %57 + %59 = OpLabel + OpBranch %52 + %52 = OpLabel + %77 = OpIAdd %6 %79 %44 + OpStore %48 %77 + OpBranch %49 + %51 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 4u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + auto loop_3 = loops[3]; + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_1); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_2); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_2); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_2, loop_3); + EXPECT_FALSE(fusion.AreCompatible()); + } + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 19 +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Illegal, would create a backward loop-carried anti-dependence. + for (int i = 0; i < 10; i++) { + c[i] = a[i] + 1; + } + for (int i = 0; i < 10; i++) { + a[i+1] = c[i] + 2; + } +} + +*/ +TEST_F(FusionIllegalTest, PositiveDistanceCreatedWAR) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "c" + OpName %25 "a" + OpName %34 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %29 = OpConstant %6 1 + %47 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %34 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %52 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %52 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %52 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %28 %29 + %31 = OpAccessChain %7 %23 %52 + OpStore %31 %30 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %52 %29 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %53 = OpPhi %6 %9 %12 %51 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %53 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %43 = OpIAdd %6 %53 %29 + %45 = OpAccessChain %7 %23 %53 + %46 = OpLoad %6 %45 + %48 = OpIAdd %6 %46 %47 + %49 = OpAccessChain %7 %25 %43 + OpStore %49 %48 + OpBranch %38 + %38 = OpLabel + %51 = OpIAdd %6 %53 %29 + OpStore %34 %51 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 21 +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Illegal, would create a backward loop-carried anti-dependence. + for (int i = 0; i < 10; i++) { + a[i] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + a[i+1] = c[i+1] + 2; + } +} + +*/ +TEST_F(FusionIllegalTest, PositiveDistanceCreatedWAW) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %25 "b" + OpName %34 "i" + OpName %44 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %29 = OpConstant %6 1 + %49 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %34 = OpVariable %7 Function + %44 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %54 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %54 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %54 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %28 %29 + %31 = OpAccessChain %7 %23 %54 + OpStore %31 %30 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %54 %29 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %55 = OpPhi %6 %9 %12 %53 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %55 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %43 = OpIAdd %6 %55 %29 + %46 = OpIAdd %6 %55 %29 + %47 = OpAccessChain %7 %44 %46 + %48 = OpLoad %6 %47 + %50 = OpIAdd %6 %48 %49 + %51 = OpAccessChain %7 %23 %43 + OpStore %51 %50 + OpBranch %38 + %38 = OpLabel + %53 = OpIAdd %6 %55 %29 + OpStore %34 %53 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 28 +#version 440 core +void main() { + int[10] a; + int[10] b; + + int sum_0 = 0; + + // Illegal + for (int i = 0; i < 10; i++) { + sum_0 += a[i]; + } + for (int j = 0; j < 10; j++) { + sum_0 += b[j]; + } +} + +*/ +TEST_F(FusionIllegalTest, SameReductionVariable) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "sum_0" + OpName %10 "i" + OpName %24 "a" + OpName %33 "j" + OpName %41 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 10 + %22 = OpTypeArray %6 %21 + %23 = OpTypePointer Function %22 + %31 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %24 = OpVariable %23 Function + %33 = OpVariable %7 Function + %41 = OpVariable %23 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + %52 = OpPhi %6 %9 %5 %29 %14 + %49 = OpPhi %6 %9 %5 %32 %14 + OpLoopMerge %13 %14 None + OpBranch %15 + %15 = OpLabel + %19 = OpSLessThan %18 %49 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + %26 = OpAccessChain %7 %24 %49 + %27 = OpLoad %6 %26 + %29 = OpIAdd %6 %52 %27 + OpStore %8 %29 + OpBranch %14 + %14 = OpLabel + %32 = OpIAdd %6 %49 %31 + OpStore %10 %32 + OpBranch %11 + %13 = OpLabel + OpStore %33 %9 + OpBranch %34 + %34 = OpLabel + %51 = OpPhi %6 %52 %13 %46 %37 + %50 = OpPhi %6 %9 %13 %48 %37 + OpLoopMerge %36 %37 None + OpBranch %38 + %38 = OpLabel + %40 = OpSLessThan %18 %50 %17 + OpBranchConditional %40 %35 %36 + %35 = OpLabel + %43 = OpAccessChain %7 %41 %50 + %44 = OpLoad %6 %43 + %46 = OpIAdd %6 %51 %44 + OpStore %8 %46 + OpBranch %37 + %37 = OpLabel + %48 = OpIAdd %6 %50 %31 + OpStore %33 %48 + OpBranch %34 + %36 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 28 +#version 440 core +void main() { + int[10] a; + int[10] b; + + int sum_0 = 0; + + // Illegal + for (int i = 0; i < 10; i++) { + sum_0 += a[i]; + } + for (int j = 0; j < 10; j++) { + sum_0 += b[j]; + } +} + +*/ +TEST_F(FusionIllegalTest, SameReductionVariableLCSSA) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "sum_0" + OpName %10 "i" + OpName %24 "a" + OpName %33 "j" + OpName %41 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 10 + %22 = OpTypeArray %6 %21 + %23 = OpTypePointer Function %22 + %31 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %24 = OpVariable %23 Function + %33 = OpVariable %7 Function + %41 = OpVariable %23 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + %52 = OpPhi %6 %9 %5 %29 %14 + %49 = OpPhi %6 %9 %5 %32 %14 + OpLoopMerge %13 %14 None + OpBranch %15 + %15 = OpLabel + %19 = OpSLessThan %18 %49 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + %26 = OpAccessChain %7 %24 %49 + %27 = OpLoad %6 %26 + %29 = OpIAdd %6 %52 %27 + OpStore %8 %29 + OpBranch %14 + %14 = OpLabel + %32 = OpIAdd %6 %49 %31 + OpStore %10 %32 + OpBranch %11 + %13 = OpLabel + OpStore %33 %9 + OpBranch %34 + %34 = OpLabel + %51 = OpPhi %6 %52 %13 %46 %37 + %50 = OpPhi %6 %9 %13 %48 %37 + OpLoopMerge %36 %37 None + OpBranch %38 + %38 = OpLabel + %40 = OpSLessThan %18 %50 %17 + OpBranchConditional %40 %35 %36 + %35 = OpLabel + %43 = OpAccessChain %7 %41 %50 + %44 = OpLoad %6 %43 + %46 = OpIAdd %6 %51 %44 + OpStore %8 %46 + OpBranch %37 + %37 = OpLabel + %48 = OpIAdd %6 %50 %31 + OpStore %33 %48 + OpBranch %34 + %36 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopUtils utils_0(context.get(), loops[0]); + utils_0.MakeLoopClosedSSA(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 30 +#version 440 core +int x; +void main() { + int[10] a; + int[10] b; + + // Illegal, x is unknown. + for (int i = 0; i < 10; i++) { + a[x] = a[i]; + } + for (int j = 0; j < 10; j++) { + a[j] = b[j]; + } +} + +*/ +TEST_F(FusionIllegalTest, UnknownIndexVariable) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %25 "x" + OpName %34 "j" + OpName %43 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %24 = OpTypePointer Private %6 + %25 = OpVariable %24 Private + %32 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %34 = OpVariable %7 Function + %43 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %50 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %50 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpLoad %6 %25 + %28 = OpAccessChain %7 %23 %50 + %29 = OpLoad %6 %28 + %30 = OpAccessChain %7 %23 %26 + OpStore %30 %29 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %50 %32 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %51 = OpPhi %6 %9 %12 %49 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %51 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %43 %51 + %46 = OpLoad %6 %45 + %47 = OpAccessChain %7 %23 %51 + OpStore %47 %46 + OpBranch %38 + %38 = OpLabel + %49 = OpIAdd %6 %51 %32 + OpStore %34 %49 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + + int sum = 0; + + // Illegal, accumulator used for indexing. + for (int i = 0; i < 10; i++) { + sum += a[i]; + b[sum] = a[i]; + } + for (int j = 0; j < 10; j++) { + b[j] = b[j]+1; + } +} + +*/ +TEST_F(FusionIllegalTest, AccumulatorIndexing) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "sum" + OpName %10 "i" + OpName %24 "a" + OpName %30 "b" + OpName %39 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 10 + %22 = OpTypeArray %6 %21 + %23 = OpTypePointer Function %22 + %37 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %24 = OpVariable %23 Function + %30 = OpVariable %23 Function + %39 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + %57 = OpPhi %6 %9 %5 %29 %14 + %55 = OpPhi %6 %9 %5 %38 %14 + OpLoopMerge %13 %14 None + OpBranch %15 + %15 = OpLabel + %19 = OpSLessThan %18 %55 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + %26 = OpAccessChain %7 %24 %55 + %27 = OpLoad %6 %26 + %29 = OpIAdd %6 %57 %27 + OpStore %8 %29 + %33 = OpAccessChain %7 %24 %55 + %34 = OpLoad %6 %33 + %35 = OpAccessChain %7 %30 %29 + OpStore %35 %34 + OpBranch %14 + %14 = OpLabel + %38 = OpIAdd %6 %55 %37 + OpStore %10 %38 + OpBranch %11 + %13 = OpLabel + OpStore %39 %9 + OpBranch %40 + %40 = OpLabel + %56 = OpPhi %6 %9 %13 %54 %43 + OpLoopMerge %42 %43 None + OpBranch %44 + %44 = OpLabel + %46 = OpSLessThan %18 %56 %17 + OpBranchConditional %46 %41 %42 + %41 = OpLabel + %49 = OpAccessChain %7 %30 %56 + %50 = OpLoad %6 %49 + %51 = OpIAdd %6 %50 %37 + %52 = OpAccessChain %7 %30 %56 + OpStore %52 %51 + OpBranch %43 + %43 = OpLabel + %54 = OpIAdd %6 %56 %37 + OpStore %39 %54 + OpBranch %40 + %42 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 33 +#version 440 core +void main() { + int[10] a; + int[10] b; + + // Illegal, barrier. + for (int i = 0; i < 10; i++) { + a[i] = a[i] * 2; + memoryBarrier(); + } + for (int j = 0; j < 10; j++) { + b[j] = b[j] + 1; + } +} + +*/ +TEST_F(FusionIllegalTest, Barrier) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %36 "j" + OpName %44 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %28 = OpConstant %6 2 + %31 = OpConstant %19 1 + %32 = OpConstant %19 3400 + %34 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %36 = OpVariable %7 Function + %44 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %53 = OpPhi %6 %9 %5 %35 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %53 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpAccessChain %7 %23 %53 + %27 = OpLoad %6 %26 + %29 = OpIMul %6 %27 %28 + %30 = OpAccessChain %7 %23 %53 + OpStore %30 %29 + OpMemoryBarrier %31 %32 + OpBranch %13 + %13 = OpLabel + %35 = OpIAdd %6 %53 %34 + OpStore %8 %35 + OpBranch %10 + %12 = OpLabel + OpStore %36 %9 + OpBranch %37 + %37 = OpLabel + %54 = OpPhi %6 %9 %12 %52 %40 + OpLoopMerge %39 %40 None + OpBranch %41 + %41 = OpLabel + %43 = OpSLessThan %17 %54 %16 + OpBranchConditional %43 %38 %39 + %38 = OpLabel + %47 = OpAccessChain %7 %44 %54 + %48 = OpLoad %6 %47 + %49 = OpIAdd %6 %48 %34 + %50 = OpAccessChain %7 %44 %54 + OpStore %50 %49 + OpBranch %40 + %40 = OpLabel + %52 = OpIAdd %6 %54 %34 + OpStore %36 %52 + OpBranch %37 + %39 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +struct TestStruct { + int[10] a; + int b; +}; + +void main() { + TestStruct test_0; + TestStruct test_1; + + for (int i = 0; i < 10; i++) { + test_0.a[i] = i; + } + for (int j = 0; j < 10; j++) { + test_0 = test_1; + } +} + +*/ +TEST_F(FusionIllegalTest, ArrayInStruct) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %22 "TestStruct" + OpMemberName %22 0 "a" + OpMemberName %22 1 "b" + OpName %24 "test_0" + OpName %31 "j" + OpName %39 "test_1" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypeStruct %21 %6 + %23 = OpTypePointer Function %22 + %29 = OpConstant %6 1 + %47 = OpUndef %22 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %24 = OpVariable %23 Function + %31 = OpVariable %7 Function + %39 = OpVariable %23 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %43 = OpPhi %6 %9 %5 %30 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %43 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %24 %9 %43 + OpStore %27 %43 + OpBranch %13 + %13 = OpLabel + %30 = OpIAdd %6 %43 %29 + OpStore %8 %30 + OpBranch %10 + %12 = OpLabel + OpStore %31 %9 + OpBranch %32 + %32 = OpLabel + %44 = OpPhi %6 %9 %12 %42 %35 + OpLoopMerge %34 %35 None + OpBranch %36 + %36 = OpLabel + %38 = OpSLessThan %17 %44 %16 + OpBranchConditional %38 %33 %34 + %33 = OpLabel + OpStore %24 %47 + OpBranch %35 + %35 = OpLabel + %42 = OpIAdd %6 %44 %29 + OpStore %31 %42 + OpBranch %32 + %34 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 450 + +struct P {float x,y,z;}; +uniform G { int a; P b[2]; int c; } g; +layout(location = 0) out float o; + +void main() +{ + P p[2]; + for (int i = 0; i < 2; ++i) { + p = g.b; + } + for (int j = 0; j < 2; ++j) { + o = p[g.a].x; + } +} + +*/ +TEST_F(FusionIllegalTest, NestedAccessChain) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" %64 + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 450 + OpName %4 "main" + OpName %8 "i" + OpName %20 "P" + OpMemberName %20 0 "x" + OpMemberName %20 1 "y" + OpMemberName %20 2 "z" + OpName %25 "p" + OpName %26 "P" + OpMemberName %26 0 "x" + OpMemberName %26 1 "y" + OpMemberName %26 2 "z" + OpName %28 "G" + OpMemberName %28 0 "a" + OpMemberName %28 1 "b" + OpMemberName %28 2 "c" + OpName %30 "g" + OpName %55 "j" + OpName %64 "o" + OpMemberDecorate %26 0 Offset 0 + OpMemberDecorate %26 1 Offset 4 + OpMemberDecorate %26 2 Offset 8 + OpDecorate %27 ArrayStride 16 + OpMemberDecorate %28 0 Offset 0 + OpMemberDecorate %28 1 Offset 16 + OpMemberDecorate %28 2 Offset 48 + OpDecorate %28 Block + OpDecorate %30 DescriptorSet 0 + OpDecorate %64 Location 0 + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 2 + %17 = OpTypeBool + %19 = OpTypeFloat 32 + %20 = OpTypeStruct %19 %19 %19 + %21 = OpTypeInt 32 0 + %22 = OpConstant %21 2 + %23 = OpTypeArray %20 %22 + %24 = OpTypePointer Function %23 + %26 = OpTypeStruct %19 %19 %19 + %27 = OpTypeArray %26 %22 + %28 = OpTypeStruct %6 %27 %6 + %29 = OpTypePointer Uniform %28 + %30 = OpVariable %29 Uniform + %31 = OpConstant %6 1 + %32 = OpTypePointer Uniform %27 + %36 = OpTypePointer Function %20 + %39 = OpTypePointer Function %19 + %63 = OpTypePointer Output %19 + %64 = OpVariable %63 Output + %65 = OpTypePointer Uniform %6 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %25 = OpVariable %24 Function + %55 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %72 = OpPhi %6 %9 %5 %54 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %72 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %33 = OpAccessChain %32 %30 %31 + %34 = OpLoad %27 %33 + %35 = OpCompositeExtract %26 %34 0 + %37 = OpAccessChain %36 %25 %9 + %38 = OpCompositeExtract %19 %35 0 + %40 = OpAccessChain %39 %37 %9 + OpStore %40 %38 + %41 = OpCompositeExtract %19 %35 1 + %42 = OpAccessChain %39 %37 %31 + OpStore %42 %41 + %43 = OpCompositeExtract %19 %35 2 + %44 = OpAccessChain %39 %37 %16 + OpStore %44 %43 + %45 = OpCompositeExtract %26 %34 1 + %46 = OpAccessChain %36 %25 %31 + %47 = OpCompositeExtract %19 %45 0 + %48 = OpAccessChain %39 %46 %9 + OpStore %48 %47 + %49 = OpCompositeExtract %19 %45 1 + %50 = OpAccessChain %39 %46 %31 + OpStore %50 %49 + %51 = OpCompositeExtract %19 %45 2 + %52 = OpAccessChain %39 %46 %16 + OpStore %52 %51 + OpBranch %13 + %13 = OpLabel + %54 = OpIAdd %6 %72 %31 + OpStore %8 %54 + OpBranch %10 + %12 = OpLabel + OpStore %55 %9 + OpBranch %56 + %56 = OpLabel + %73 = OpPhi %6 %9 %12 %71 %59 + OpLoopMerge %58 %59 None + OpBranch %60 + %60 = OpLabel + %62 = OpSLessThan %17 %73 %16 + OpBranchConditional %62 %57 %58 + %57 = OpLabel + %66 = OpAccessChain %65 %30 %9 + %67 = OpLoad %6 %66 + %68 = OpAccessChain %39 %25 %67 %9 + %69 = OpLoad %19 %68 + OpStore %64 %69 + OpBranch %59 + %59 = OpLabel + %71 = OpIAdd %6 %73 %31 + OpStore %55 %71 + OpBranch %56 + %58 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_FALSE(fusion.IsLegal()); + } +} + +} // namespace diff --git a/test/opt/loop_optimizations/fusion_legal.cpp b/test/opt/loop_optimizations/fusion_legal.cpp new file mode 100644 index 000000000..1957d5a5e --- /dev/null +++ b/test/opt/loop_optimizations/fusion_legal.cpp @@ -0,0 +1,4581 @@ +// 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 + +#include +#include +#include +#include +#include + +#ifdef SPIRV_EFFCEE +#include "effcee/effcee.h" +#endif + +#include "../pass_fixture.h" +#include "opt/loop_descriptor.h" +#include "opt/loop_fusion.h" + +namespace { + +using namespace spvtools; + +using FusionLegalTest = PassTest<::testing::Test>; + +bool Validate(const std::vector& bin) { + spv_target_env target_env = SPV_ENV_UNIVERSAL_1_2; + spv_context spvContext = spvContextCreate(target_env); + spv_diagnostic diagnostic = nullptr; + spv_const_binary_t binary = {bin.data(), bin.size()}; + spv_result_t error = spvValidate(spvContext, &binary, &diagnostic); + if (error != 0) spvDiagnosticPrint(diagnostic); + spvDiagnosticDestroy(diagnostic); + spvContextDestroy(spvContext); + return error == 0; +} + +void Match(const std::string& checks, ir::IRContext* context) { + std::vector bin; + context->module()->ToBinary(&bin, true); + EXPECT_TRUE(Validate(bin)); +#ifdef SPIRV_EFFCEE + std::string assembly; + SpirvTools tools(SPV_ENV_UNIVERSAL_1_2); + EXPECT_TRUE( + tools.Disassemble(bin, &assembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER)) + << "Disassembling failed for shader:\n" + << assembly << std::endl; + auto match_result = effcee::Match(assembly, checks); + EXPECT_EQ(effcee::Result::Status::Ok, match_result.status()) + << match_result.message() << "\nChecking result:\n" + << assembly; +#endif // ! SPIRV_EFFCEE +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + // No dependence, legal + for (int i = 0; i < 10; i++) { + a[i] = a[i]*2; + } + for (int i = 0; i < 10; i++) { + b[i] = b[i]+2; + } +} + +*/ +TEST_F(FusionLegalTest, DifferentArraysInLoops) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %34 "i" + OpName %42 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %28 = OpConstant %6 2 + %32 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %34 = OpVariable %7 Function + %42 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %51 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %51 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpAccessChain %7 %23 %51 + %27 = OpLoad %6 %26 + %29 = OpIMul %6 %27 %28 + %30 = OpAccessChain %7 %23 %51 + OpStore %30 %29 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %51 %32 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %52 = OpPhi %6 %9 %12 %50 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %52 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %42 %52 + %46 = OpLoad %6 %45 + %47 = OpIAdd %6 %46 %28 + %48 = OpAccessChain %7 %42 %52 + OpStore %48 %47 + OpBranch %38 + %38 = OpLabel + %50 = OpIAdd %6 %52 %32 + OpStore %34 %50 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +)"; + + Match(checks, context.get()); + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Only loads to the same array, legal + for (int i = 0; i < 10; i++) { + b[i] = a[i]*2; + } + for (int i = 0; i < 10; i++) { + c[i] = a[i]+2; + } +} + +*/ +TEST_F(FusionLegalTest, OnlyLoadsToSameArray) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "b" + OpName %25 "a" + OpName %35 "i" + OpName %43 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %29 = OpConstant %6 2 + %33 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %35 = OpVariable %7 Function + %43 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %52 = OpPhi %6 %9 %5 %34 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %52 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %52 + %28 = OpLoad %6 %27 + %30 = OpIMul %6 %28 %29 + %31 = OpAccessChain %7 %23 %52 + OpStore %31 %30 + OpBranch %13 + %13 = OpLabel + %34 = OpIAdd %6 %52 %33 + OpStore %8 %34 + OpBranch %10 + %12 = OpLabel + OpStore %35 %9 + OpBranch %36 + %36 = OpLabel + %53 = OpPhi %6 %9 %12 %51 %39 + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + %42 = OpSLessThan %17 %53 %16 + OpBranchConditional %42 %37 %38 + %37 = OpLabel + %46 = OpAccessChain %7 %25 %53 + %47 = OpLoad %6 %46 + %48 = OpIAdd %6 %47 %29 + %49 = OpAccessChain %7 %43 %53 + OpStore %49 %48 + OpBranch %39 + %39 = OpLabel + %51 = OpIAdd %6 %53 %33 + OpStore %35 %51 + OpBranch %36 + %38 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +)"; + + Match(checks, context.get()); + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + // No loop-carried dependences, legal + for (int i = 0; i < 10; i++) { + a[i] = a[i]*2; + } + for (int i = 0; i < 10; i++) { + b[i] = a[i]+2; + } +} + +*/ +TEST_F(FusionLegalTest, NoLoopCarriedDependences) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %34 "i" + OpName %42 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %28 = OpConstant %6 2 + %32 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %34 = OpVariable %7 Function + %42 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %51 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %51 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpAccessChain %7 %23 %51 + %27 = OpLoad %6 %26 + %29 = OpIMul %6 %27 %28 + %30 = OpAccessChain %7 %23 %51 + OpStore %30 %29 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %51 %32 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %52 = OpPhi %6 %9 %12 %50 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %52 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %23 %52 + %46 = OpLoad %6 %45 + %47 = OpIAdd %6 %46 %28 + %48 = OpAccessChain %7 %42 %52 + OpStore %48 %47 + OpBranch %38 + %38 = OpLabel + %50 = OpIAdd %6 %52 %32 + OpStore %34 %50 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +)"; + + Match(checks, context.get()); + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Parallelism inhibiting, but legal. + for (int i = 0; i < 10; i++) { + a[i] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[i] + c[i-1]; + } +} + +*/ +TEST_F(FusionLegalTest, ExistingLoopCarriedDependence) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %25 "b" + OpName %34 "i" + OpName %42 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %29 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %34 = OpVariable %7 Function + %42 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %55 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %55 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %55 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %28 %29 + %31 = OpAccessChain %7 %23 %55 + OpStore %31 %30 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %55 %29 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %56 = OpPhi %6 %9 %12 %54 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %56 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %23 %56 + %46 = OpLoad %6 %45 + %48 = OpISub %6 %56 %29 + %49 = OpAccessChain %7 %42 %48 + %50 = OpLoad %6 %49 + %51 = OpIAdd %6 %46 %50 + %52 = OpAccessChain %7 %42 %56 + OpStore %52 %51 + OpBranch %38 + %38 = OpLabel + %54 = OpIAdd %6 %56 %29 + OpStore %34 %54 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[I_1:%\w+]] = OpISub {{%\w+}} [[PHI]] {{%\w+}} +CHECK-NEXT: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +)"; + + Match(checks, context.get()); + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Creates a loop-carried dependence, but negative, so legal + for (int i = 0; i < 10; i++) { + a[i+1] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[i] + 2; + } +} + +*/ +TEST_F(FusionLegalTest, NegativeDistanceCreatedRAW) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %27 "b" + OpName %35 "i" + OpName %43 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %25 = OpConstant %6 1 + %48 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %27 = OpVariable %22 Function + %35 = OpVariable %7 Function + %43 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %53 = OpPhi %6 %9 %5 %34 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %53 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpIAdd %6 %53 %25 + %29 = OpAccessChain %7 %27 %53 + %30 = OpLoad %6 %29 + %31 = OpIAdd %6 %30 %25 + %32 = OpAccessChain %7 %23 %26 + OpStore %32 %31 + OpBranch %13 + %13 = OpLabel + %34 = OpIAdd %6 %53 %25 + OpStore %8 %34 + OpBranch %10 + %12 = OpLabel + OpStore %35 %9 + OpBranch %36 + %36 = OpLabel + %54 = OpPhi %6 %9 %12 %52 %39 + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + %42 = OpSLessThan %17 %54 %16 + OpBranchConditional %42 %37 %38 + %37 = OpLabel + %46 = OpAccessChain %7 %23 %54 + %47 = OpLoad %6 %46 + %49 = OpIAdd %6 %47 %48 + %50 = OpAccessChain %7 %43 %54 + OpStore %50 %49 + OpBranch %39 + %39 = OpLabel + %52 = OpIAdd %6 %54 %25 + OpStore %35 %52 + OpBranch %36 + %38 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}} +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } + + { + auto& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Legal + for (int i = 0; i < 10; i++) { + a[i+1] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[i+1] + 2; + } +} + +*/ +TEST_F(FusionLegalTest, NoLoopCarriedDependencesAdjustedIndex) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %27 "b" + OpName %35 "i" + OpName %43 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %25 = OpConstant %6 1 + %49 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %27 = OpVariable %22 Function + %35 = OpVariable %7 Function + %43 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %54 = OpPhi %6 %9 %5 %34 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %54 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpIAdd %6 %54 %25 + %29 = OpAccessChain %7 %27 %54 + %30 = OpLoad %6 %29 + %31 = OpIAdd %6 %30 %25 + %32 = OpAccessChain %7 %23 %26 + OpStore %32 %31 + OpBranch %13 + %13 = OpLabel + %34 = OpIAdd %6 %54 %25 + OpStore %8 %34 + OpBranch %10 + %12 = OpLabel + OpStore %35 %9 + OpBranch %36 + %36 = OpLabel + %55 = OpPhi %6 %9 %12 %53 %39 + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + %42 = OpSLessThan %17 %55 %16 + OpBranchConditional %42 %37 %38 + %37 = OpLabel + %46 = OpIAdd %6 %55 %25 + %47 = OpAccessChain %7 %23 %46 + %48 = OpLoad %6 %47 + %50 = OpIAdd %6 %48 %49 + %51 = OpAccessChain %7 %43 %55 + OpStore %51 %50 + OpBranch %39 + %39 = OpLabel + %53 = OpIAdd %6 %55 %25 + OpStore %35 %53 + OpBranch %36 + %38 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}} +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}} +CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +)"; + + Match(checks, context.get()); + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Legal, independent locations in |a|, SIV + for (int i = 0; i < 10; i++) { + a[2*i+1] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[2*i] + 2; + } +} + +*/ +TEST_F(FusionLegalTest, IndependentSIV) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %29 "b" + OpName %37 "i" + OpName %45 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %24 = OpConstant %6 2 + %27 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %29 = OpVariable %22 Function + %37 = OpVariable %7 Function + %45 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %55 = OpPhi %6 %9 %5 %36 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %55 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpIMul %6 %24 %55 + %28 = OpIAdd %6 %26 %27 + %31 = OpAccessChain %7 %29 %55 + %32 = OpLoad %6 %31 + %33 = OpIAdd %6 %32 %27 + %34 = OpAccessChain %7 %23 %28 + OpStore %34 %33 + OpBranch %13 + %13 = OpLabel + %36 = OpIAdd %6 %55 %27 + OpStore %8 %36 + OpBranch %10 + %12 = OpLabel + OpStore %37 %9 + OpBranch %38 + %38 = OpLabel + %56 = OpPhi %6 %9 %12 %54 %41 + OpLoopMerge %40 %41 None + OpBranch %42 + %42 = OpLabel + %44 = OpSLessThan %17 %56 %16 + OpBranchConditional %44 %39 %40 + %39 = OpLabel + %48 = OpIMul %6 %24 %56 + %49 = OpAccessChain %7 %23 %48 + %50 = OpLoad %6 %49 + %51 = OpIAdd %6 %50 %24 + %52 = OpAccessChain %7 %45 %56 + OpStore %52 %51 + OpBranch %41 + %41 = OpLabel + %54 = OpIAdd %6 %56 %27 + OpStore %37 %54 + OpBranch %38 + %40 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[I_2:%\w+]] = OpIMul {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: [[I_2_1:%\w+]] = OpIAdd {{%\w+}} [[I_2]] {{%\w+}} +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_2_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[I_2:%\w+]] = OpIMul {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_2]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +)"; + + Match(checks, context.get()); + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Legal, independent locations in |a|, ZIV + for (int i = 0; i < 10; i++) { + a[1] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[9] + 2; + } +} + +*/ +TEST_F(FusionLegalTest, IndependentZIV) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %25 "b" + OpName %33 "i" + OpName %41 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %24 = OpConstant %6 1 + %43 = OpConstant %6 9 + %46 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %33 = OpVariable %7 Function + %41 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %51 = OpPhi %6 %9 %5 %32 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %51 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %51 + %28 = OpLoad %6 %27 + %29 = OpIAdd %6 %28 %24 + %30 = OpAccessChain %7 %23 %24 + OpStore %30 %29 + OpBranch %13 + %13 = OpLabel + %32 = OpIAdd %6 %51 %24 + OpStore %8 %32 + OpBranch %10 + %12 = OpLabel + OpStore %33 %9 + OpBranch %34 + %34 = OpLabel + %52 = OpPhi %6 %9 %12 %50 %37 + OpLoopMerge %36 %37 None + OpBranch %38 + %38 = OpLabel + %40 = OpSLessThan %17 %52 %16 + OpBranchConditional %40 %35 %36 + %35 = OpLabel + %44 = OpAccessChain %7 %23 %43 + %45 = OpLoad %6 %44 + %47 = OpIAdd %6 %45 %46 + %48 = OpAccessChain %7 %41 %52 + OpStore %48 %47 + OpBranch %37 + %37 = OpLabel + %50 = OpIAdd %6 %52 %24 + OpStore %33 %50 + OpBranch %34 + %36 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK-NOT: OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK: OpStore +CHECK-NOT: OpPhi +CHECK-NOT: OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK: OpLoad +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +)"; + + Match(checks, context.get()); + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[20] a; + int[10] b; + int[10] c; + // Legal, non-overlapping sections in |a| + for (int i = 0; i < 10; i++) { + a[i] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[i+10] + 2; + } +} + +*/ +TEST_F(FusionLegalTest, NonOverlappingAccesses) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %28 "b" + OpName %37 "i" + OpName %45 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 20 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %25 = OpConstant %19 10 + %26 = OpTypeArray %6 %25 + %27 = OpTypePointer Function %26 + %32 = OpConstant %6 1 + %51 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %28 = OpVariable %27 Function + %37 = OpVariable %7 Function + %45 = OpVariable %27 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %56 = OpPhi %6 %9 %5 %36 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %56 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %30 = OpAccessChain %7 %28 %56 + %31 = OpLoad %6 %30 + %33 = OpIAdd %6 %31 %32 + %34 = OpAccessChain %7 %23 %56 + OpStore %34 %33 + OpBranch %13 + %13 = OpLabel + %36 = OpIAdd %6 %56 %32 + OpStore %8 %36 + OpBranch %10 + %12 = OpLabel + OpStore %37 %9 + OpBranch %38 + %38 = OpLabel + %57 = OpPhi %6 %9 %12 %55 %41 + OpLoopMerge %40 %41 None + OpBranch %42 + %42 = OpLabel + %44 = OpSLessThan %17 %57 %16 + OpBranchConditional %44 %39 %40 + %39 = OpLabel + %48 = OpIAdd %6 %57 %16 + %49 = OpAccessChain %7 %23 %48 + %50 = OpLoad %6 %49 + %52 = OpIAdd %6 %50 %51 + %53 = OpAccessChain %7 %45 %57 + OpStore %53 %52 + OpBranch %41 + %41 = OpLabel + %55 = OpIAdd %6 %57 %32 + OpStore %37 %55 + OpBranch %38 + %40 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NOT: OpPhi +CHECK: [[I_10:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}} +CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_10]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +)"; + + Match(checks, context.get()); + + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Legal, 3 adjacent loops + for (int i = 0; i < 10; i++) { + a[i] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[i] + 2; + } + for (int i = 0; i < 10; i++) { + b[i] = c[i] + 10; + } +} + +*/ +TEST_F(FusionLegalTest, AdjacentLoops) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %25 "b" + OpName %34 "i" + OpName %42 "c" + OpName %52 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %29 = OpConstant %6 1 + %47 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %34 = OpVariable %7 Function + %42 = OpVariable %22 Function + %52 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %68 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %68 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %68 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %28 %29 + %31 = OpAccessChain %7 %23 %68 + OpStore %31 %30 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %68 %29 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %69 = OpPhi %6 %9 %12 %51 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %69 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %23 %69 + %46 = OpLoad %6 %45 + %48 = OpIAdd %6 %46 %47 + %49 = OpAccessChain %7 %42 %69 + OpStore %49 %48 + OpBranch %38 + %38 = OpLabel + %51 = OpIAdd %6 %69 %29 + OpStore %34 %51 + OpBranch %35 + %37 = OpLabel + OpStore %52 %9 + OpBranch %53 + %53 = OpLabel + %70 = OpPhi %6 %9 %37 %67 %56 + OpLoopMerge %55 %56 None + OpBranch %57 + %57 = OpLabel + %59 = OpSLessThan %17 %70 %16 + OpBranchConditional %59 %54 %55 + %54 = OpLabel + %62 = OpAccessChain %7 %42 %70 + %63 = OpLoad %6 %62 + %64 = OpIAdd %6 %63 %16 + %65 = OpAccessChain %7 %25 %70 + OpStore %65 %64 + OpBranch %56 + %56 = OpLabel + %67 = OpIAdd %6 %70 %29 + OpStore %52 %67 + OpBranch %53 + %55 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[1], loops[2]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_1]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]] +CHECK: [[STORE_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_2]] + )"; + + Match(checks, context.get()); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + std::string checks_ = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]] +CHECK: [[STORE_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_2]] + )"; + + Match(checks_, context.get()); + + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 1u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10][10] a; + int[10][10] b; + int[10][10] c; + // Legal inner loop fusion + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + c[i][j] = a[i][j] + 2; + } + for (int j = 0; j < 10; j++) { + b[i][j] = c[i][j] + 10; + } + } +} + +*/ +TEST_F(FusionLegalTest, InnerLoopFusion) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %19 "j" + OpName %32 "c" + OpName %35 "a" + OpName %46 "j" + OpName %54 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %27 = OpTypeInt 32 0 + %28 = OpConstant %27 10 + %29 = OpTypeArray %6 %28 + %30 = OpTypeArray %29 %28 + %31 = OpTypePointer Function %30 + %40 = OpConstant %6 2 + %44 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %31 Function + %35 = OpVariable %31 Function + %46 = OpVariable %7 Function + %54 = OpVariable %31 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %67 = OpPhi %6 %9 %5 %66 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %67 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + %68 = OpPhi %6 %9 %11 %45 %23 + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %26 = OpSLessThan %17 %68 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + %38 = OpAccessChain %7 %35 %67 %68 + %39 = OpLoad %6 %38 + %41 = OpIAdd %6 %39 %40 + %42 = OpAccessChain %7 %32 %67 %68 + OpStore %42 %41 + OpBranch %23 + %23 = OpLabel + %45 = OpIAdd %6 %68 %44 + OpStore %19 %45 + OpBranch %20 + %22 = OpLabel + OpStore %46 %9 + OpBranch %47 + %47 = OpLabel + %69 = OpPhi %6 %9 %22 %64 %50 + OpLoopMerge %49 %50 None + OpBranch %51 + %51 = OpLabel + %53 = OpSLessThan %17 %69 %16 + OpBranchConditional %53 %48 %49 + %48 = OpLabel + %59 = OpAccessChain %7 %32 %67 %69 + %60 = OpLoad %6 %59 + %61 = OpIAdd %6 %60 %16 + %62 = OpAccessChain %7 %54 %67 %69 + OpStore %62 %61 + OpBranch %50 + %50 = OpLabel + %64 = OpIAdd %6 %69 %44 + OpStore %46 %64 + OpBranch %47 + %49 = OpLabel + OpBranch %13 + %13 = OpLabel + %66 = OpIAdd %6 %67 %44 + OpStore %8 %66 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_1); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_2); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_2); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + + auto& ld_final = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld_final.NumLoops(), 2u); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// 12 +#version 440 core +void main() { + int[10][10] a; + int[10][10] b; + int[10][10] c; + // Legal both + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + c[i][j] = a[i][j] + 2; + } + } + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + b[i][j] = c[i][j] + 10; + } + } +} + +*/ +TEST_F(FusionLegalTest, OuterAndInnerLoop) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %19 "j" + OpName %32 "c" + OpName %35 "a" + OpName %48 "i" + OpName %56 "j" + OpName %64 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %27 = OpTypeInt 32 0 + %28 = OpConstant %27 10 + %29 = OpTypeArray %6 %28 + %30 = OpTypeArray %29 %28 + %31 = OpTypePointer Function %30 + %40 = OpConstant %6 2 + %44 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %31 Function + %35 = OpVariable %31 Function + %48 = OpVariable %7 Function + %56 = OpVariable %7 Function + %64 = OpVariable %31 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %77 = OpPhi %6 %9 %5 %47 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %77 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + %81 = OpPhi %6 %9 %11 %45 %23 + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %26 = OpSLessThan %17 %81 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + %38 = OpAccessChain %7 %35 %77 %81 + %39 = OpLoad %6 %38 + %41 = OpIAdd %6 %39 %40 + %42 = OpAccessChain %7 %32 %77 %81 + OpStore %42 %41 + OpBranch %23 + %23 = OpLabel + %45 = OpIAdd %6 %81 %44 + OpStore %19 %45 + OpBranch %20 + %22 = OpLabel + OpBranch %13 + %13 = OpLabel + %47 = OpIAdd %6 %77 %44 + OpStore %8 %47 + OpBranch %10 + %12 = OpLabel + OpStore %48 %9 + OpBranch %49 + %49 = OpLabel + %78 = OpPhi %6 %9 %12 %76 %52 + OpLoopMerge %51 %52 None + OpBranch %53 + %53 = OpLabel + %55 = OpSLessThan %17 %78 %16 + OpBranchConditional %55 %50 %51 + %50 = OpLabel + OpStore %56 %9 + OpBranch %57 + %57 = OpLabel + %79 = OpPhi %6 %9 %50 %74 %60 + OpLoopMerge %59 %60 None + OpBranch %61 + %61 = OpLabel + %63 = OpSLessThan %17 %79 %16 + OpBranchConditional %63 %58 %59 + %58 = OpLabel + %69 = OpAccessChain %7 %32 %78 %79 + %70 = OpLoad %6 %69 + %71 = OpIAdd %6 %70 %16 + %72 = OpAccessChain %7 %64 %78 %79 + OpStore %72 %71 + OpBranch %60 + %60 = OpLabel + %74 = OpIAdd %6 %79 %44 + OpStore %56 %74 + OpBranch %57 + %59 = OpLabel + OpBranch %52 + %52 = OpLabel + %76 = OpIAdd %6 %78 %44 + OpStore %48 %76 + OpBranch %49 + %51 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 4u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + auto loop_3 = loops[3]; + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_1); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_2); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_2, loop_3); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_3); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_2); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + fusion.Fuse(); + } + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK: [[PHI_2:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } + + { + auto& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_1); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_2); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_2); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + fusion.Fuse(); + } + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } + + { + auto& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10][10] a; + int[10][10] b; + int[10][10] c; + // Legal both, more complex + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + if (i % 2 == 0 && j % 2 == 0) { + c[i][j] = a[i][j] + 2; + } + } + } + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + b[i][j] = c[i][j] + 10; + } + } +} + +*/ +TEST_F(FusionLegalTest, OuterAndInnerLoopMoreComplex) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %19 "j" + OpName %44 "c" + OpName %47 "a" + OpName %59 "i" + OpName %67 "j" + OpName %75 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %28 = OpConstant %6 2 + %39 = OpTypeInt 32 0 + %40 = OpConstant %39 10 + %41 = OpTypeArray %6 %40 + %42 = OpTypeArray %41 %40 + %43 = OpTypePointer Function %42 + %55 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %44 = OpVariable %43 Function + %47 = OpVariable %43 Function + %59 = OpVariable %7 Function + %67 = OpVariable %7 Function + %75 = OpVariable %43 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %88 = OpPhi %6 %9 %5 %58 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %88 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + %92 = OpPhi %6 %9 %11 %56 %23 + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %26 = OpSLessThan %17 %92 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + %29 = OpSMod %6 %88 %28 + %30 = OpIEqual %17 %29 %9 + OpSelectionMerge %32 None + OpBranchConditional %30 %31 %32 + %31 = OpLabel + %34 = OpSMod %6 %92 %28 + %35 = OpIEqual %17 %34 %9 + OpBranch %32 + %32 = OpLabel + %36 = OpPhi %17 %30 %21 %35 %31 + OpSelectionMerge %38 None + OpBranchConditional %36 %37 %38 + %37 = OpLabel + %50 = OpAccessChain %7 %47 %88 %92 + %51 = OpLoad %6 %50 + %52 = OpIAdd %6 %51 %28 + %53 = OpAccessChain %7 %44 %88 %92 + OpStore %53 %52 + OpBranch %38 + %38 = OpLabel + OpBranch %23 + %23 = OpLabel + %56 = OpIAdd %6 %92 %55 + OpStore %19 %56 + OpBranch %20 + %22 = OpLabel + OpBranch %13 + %13 = OpLabel + %58 = OpIAdd %6 %88 %55 + OpStore %8 %58 + OpBranch %10 + %12 = OpLabel + OpStore %59 %9 + OpBranch %60 + %60 = OpLabel + %89 = OpPhi %6 %9 %12 %87 %63 + OpLoopMerge %62 %63 None + OpBranch %64 + %64 = OpLabel + %66 = OpSLessThan %17 %89 %16 + OpBranchConditional %66 %61 %62 + %61 = OpLabel + OpStore %67 %9 + OpBranch %68 + %68 = OpLabel + %90 = OpPhi %6 %9 %61 %85 %71 + OpLoopMerge %70 %71 None + OpBranch %72 + %72 = OpLabel + %74 = OpSLessThan %17 %90 %16 + OpBranchConditional %74 %69 %70 + %69 = OpLabel + %80 = OpAccessChain %7 %44 %89 %90 + %81 = OpLoad %6 %80 + %82 = OpIAdd %6 %81 %16 + %83 = OpAccessChain %7 %75 %89 %90 + OpStore %83 %82 + OpBranch %71 + %71 = OpLabel + %85 = OpIAdd %6 %90 %55 + OpStore %67 %85 + OpBranch %68 + %70 = OpLabel + OpBranch %63 + %63 = OpLabel + %87 = OpIAdd %6 %89 %55 + OpStore %59 %87 + OpBranch %60 + %62 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 4u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + auto loop_3 = loops[3]; + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_1); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_2); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_2, loop_3); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_3); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_2); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + fusion.Fuse(); + } + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: OpPhi +CHECK-NEXT: OpSelectionMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK: [[PHI_2:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_1); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_2); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_2); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + fusion.Fuse(); + } + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: OpPhi +CHECK-NEXT: OpSelectionMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10][10] a; + int[10][10] b; + int[10][10] c; + // Outer would have been illegal to fuse, but since written + // like this, inner loop fusion is legal. + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + c[i][j] = a[i][j] + 2; + } + for (int j = 0; j < 10; j++) { + b[i][j] = c[i+1][j] + 10; + } + } +} + +*/ +TEST_F(FusionLegalTest, InnerWithExistingDependenceOnOuter) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %19 "j" + OpName %32 "c" + OpName %35 "a" + OpName %46 "j" + OpName %54 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %27 = OpTypeInt 32 0 + %28 = OpConstant %27 10 + %29 = OpTypeArray %6 %28 + %30 = OpTypeArray %29 %28 + %31 = OpTypePointer Function %30 + %40 = OpConstant %6 2 + %44 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %31 Function + %35 = OpVariable %31 Function + %46 = OpVariable %7 Function + %54 = OpVariable %31 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %68 = OpPhi %6 %9 %5 %67 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %68 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + %69 = OpPhi %6 %9 %11 %45 %23 + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %26 = OpSLessThan %17 %69 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + %38 = OpAccessChain %7 %35 %68 %69 + %39 = OpLoad %6 %38 + %41 = OpIAdd %6 %39 %40 + %42 = OpAccessChain %7 %32 %68 %69 + OpStore %42 %41 + OpBranch %23 + %23 = OpLabel + %45 = OpIAdd %6 %69 %44 + OpStore %19 %45 + OpBranch %20 + %22 = OpLabel + OpStore %46 %9 + OpBranch %47 + %47 = OpLabel + %70 = OpPhi %6 %9 %22 %65 %50 + OpLoopMerge %49 %50 None + OpBranch %51 + %51 = OpLabel + %53 = OpSLessThan %17 %70 %16 + OpBranchConditional %53 %48 %49 + %48 = OpLabel + %58 = OpIAdd %6 %68 %44 + %60 = OpAccessChain %7 %32 %58 %70 + %61 = OpLoad %6 %60 + %62 = OpIAdd %6 %61 %16 + %63 = OpAccessChain %7 %54 %68 %70 + OpStore %63 %62 + OpBranch %50 + %50 = OpLabel + %65 = OpIAdd %6 %70 %44 + OpStore %46 %65 + OpBranch %47 + %49 = OpLabel + OpBranch %13 + %13 = OpLabel + %67 = OpIAdd %6 %68 %44 + OpStore %8 %67 + OpBranch %10 + %12 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_1); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_2); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_2); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI_0]] {{%\w+}} +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // One dimensional arrays. Legal, outer dist 0, inner independent. + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + c[i] = a[j] + 2; + } + } + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + b[j] = c[i] + 10; + } + } +} + +*/ +TEST_F(FusionLegalTest, OuterAndInnerLoopOneDimArrays) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %19 "j" + OpName %31 "c" + OpName %33 "a" + OpName %45 "i" + OpName %53 "j" + OpName %61 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %27 = OpTypeInt 32 0 + %28 = OpConstant %27 10 + %29 = OpTypeArray %6 %28 + %30 = OpTypePointer Function %29 + %37 = OpConstant %6 2 + %41 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %31 = OpVariable %30 Function + %33 = OpVariable %30 Function + %45 = OpVariable %7 Function + %53 = OpVariable %7 Function + %61 = OpVariable %30 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %72 = OpPhi %6 %9 %5 %44 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %72 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + %76 = OpPhi %6 %9 %11 %42 %23 + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %26 = OpSLessThan %17 %76 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + %35 = OpAccessChain %7 %33 %76 + %36 = OpLoad %6 %35 + %38 = OpIAdd %6 %36 %37 + %39 = OpAccessChain %7 %31 %72 + OpStore %39 %38 + OpBranch %23 + %23 = OpLabel + %42 = OpIAdd %6 %76 %41 + OpStore %19 %42 + OpBranch %20 + %22 = OpLabel + OpBranch %13 + %13 = OpLabel + %44 = OpIAdd %6 %72 %41 + OpStore %8 %44 + OpBranch %10 + %12 = OpLabel + OpStore %45 %9 + OpBranch %46 + %46 = OpLabel + %73 = OpPhi %6 %9 %12 %71 %49 + OpLoopMerge %48 %49 None + OpBranch %50 + %50 = OpLabel + %52 = OpSLessThan %17 %73 %16 + OpBranchConditional %52 %47 %48 + %47 = OpLabel + OpStore %53 %9 + OpBranch %54 + %54 = OpLabel + %74 = OpPhi %6 %9 %47 %69 %57 + OpLoopMerge %56 %57 None + OpBranch %58 + %58 = OpLabel + %60 = OpSLessThan %17 %74 %16 + OpBranchConditional %60 %55 %56 + %55 = OpLabel + %64 = OpAccessChain %7 %31 %73 + %65 = OpLoad %6 %64 + %66 = OpIAdd %6 %65 %16 + %67 = OpAccessChain %7 %61 %74 + OpStore %67 %66 + OpBranch %57 + %57 = OpLabel + %69 = OpIAdd %6 %74 %41 + OpStore %53 %69 + OpBranch %54 + %56 = OpLabel + OpBranch %49 + %49 = OpLabel + %71 = OpIAdd %6 %73 %41 + OpStore %45 %71 + OpBranch %46 + %48 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 4u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + auto loop_3 = loops[3]; + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_1); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_2); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_2, loop_3); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_2); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + fusion.Fuse(); + } + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK: [[PHI_2:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_2]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + auto loop_0 = loops[0]; + auto loop_1 = loops[1]; + auto loop_2 = loops[2]; + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_1); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_0, loop_2); + EXPECT_FALSE(fusion.AreCompatible()); + } + + { + opt::LoopFusion fusion(context.get(), loop_1, loop_2); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Legal, creates a loop-carried dependence, but has negative distance + for (int i = 0; i < 10; i++) { + c[i] = a[i+1] + 1; + } + for (int i = 0; i < 10; i++) { + a[i] = c[i] + 2; + } +} + +*/ +TEST_F(FusionLegalTest, NegativeDistanceCreatedWAR) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "c" + OpName %25 "a" + OpName %35 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %27 = OpConstant %6 1 + %47 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %35 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %52 = OpPhi %6 %9 %5 %34 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %52 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %28 = OpIAdd %6 %52 %27 + %29 = OpAccessChain %7 %25 %28 + %30 = OpLoad %6 %29 + %31 = OpIAdd %6 %30 %27 + %32 = OpAccessChain %7 %23 %52 + OpStore %32 %31 + OpBranch %13 + %13 = OpLabel + %34 = OpIAdd %6 %52 %27 + OpStore %8 %34 + OpBranch %10 + %12 = OpLabel + OpStore %35 %9 + OpBranch %36 + %36 = OpLabel + %53 = OpPhi %6 %9 %12 %51 %39 + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + %42 = OpSLessThan %17 %53 %16 + OpBranchConditional %42 %37 %38 + %37 = OpLabel + %45 = OpAccessChain %7 %23 %53 + %46 = OpLoad %6 %45 + %48 = OpIAdd %6 %46 %47 + %49 = OpAccessChain %7 %25 %53 + OpStore %49 %48 + OpBranch %39 + %39 = OpLabel + %51 = OpIAdd %6 %53 %27 + OpStore %35 %51 + OpBranch %36 + %38 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}} +CHECK-NEXT: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } + + { + auto& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Legal, creates a loop-carried dependence, but has negative distance + for (int i = 0; i < 10; i++) { + a[i+1] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + a[i] = c[i+1] + 2; + } +} + +*/ +TEST_F(FusionLegalTest, NegativeDistanceCreatedWAW) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %27 "b" + OpName %35 "i" + OpName %44 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %25 = OpConstant %6 1 + %49 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %27 = OpVariable %22 Function + %35 = OpVariable %7 Function + %44 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %54 = OpPhi %6 %9 %5 %34 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %54 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpIAdd %6 %54 %25 + %29 = OpAccessChain %7 %27 %54 + %30 = OpLoad %6 %29 + %31 = OpIAdd %6 %30 %25 + %32 = OpAccessChain %7 %23 %26 + OpStore %32 %31 + OpBranch %13 + %13 = OpLabel + %34 = OpIAdd %6 %54 %25 + OpStore %8 %34 + OpBranch %10 + %12 = OpLabel + OpStore %35 %9 + OpBranch %36 + %36 = OpLabel + %55 = OpPhi %6 %9 %12 %53 %39 + OpLoopMerge %38 %39 None + OpBranch %40 + %40 = OpLabel + %42 = OpSLessThan %17 %55 %16 + OpBranchConditional %42 %37 %38 + %37 = OpLabel + %46 = OpIAdd %6 %55 %25 + %47 = OpAccessChain %7 %44 %46 + %48 = OpLoad %6 %47 + %50 = OpIAdd %6 %48 %49 + %51 = OpAccessChain %7 %23 %55 + OpStore %51 %50 + OpBranch %39 + %39 = OpLabel + %53 = OpIAdd %6 %55 %25 + OpStore %35 %53 + OpBranch %36 + %38 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}} +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] +CHECK-NEXT: OpStore +CHECK-NOT: OpPhi +CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}} +CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Legal, no loop-carried dependence + for (int i = 0; i < 10; i++) { + a[i] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + a[i] = c[i+1] + 2; + } +} + +*/ +TEST_F(FusionLegalTest, NoLoopCarriedDependencesWAW) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %25 "b" + OpName %34 "i" + OpName %43 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %29 = OpConstant %6 1 + %48 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %34 = OpVariable %7 Function + %43 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %53 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %53 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %53 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %28 %29 + %31 = OpAccessChain %7 %23 %53 + OpStore %31 %30 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %53 %29 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %54 = OpPhi %6 %9 %12 %52 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %54 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpIAdd %6 %54 %29 + %46 = OpAccessChain %7 %43 %45 + %47 = OpLoad %6 %46 + %49 = OpIAdd %6 %47 %48 + %50 = OpAccessChain %7 %23 %54 + OpStore %50 %49 + OpBranch %38 + %38 = OpLabel + %52 = OpIAdd %6 %54 %29 + OpStore %34 %52 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[I_1:%\w+]] = OpIAdd {{%\w+}} [[PHI]] {{%\w+}} +CHECK-NEXT: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[I_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10][10] a; + int[10][10] b; + int[10][10] c; + // Legal outer. Continue and break are fine if nested in inner loops + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + if (j % 2 == 0) { + c[i][j] = a[i][j] + 2; + } else { + continue; + } + } + } + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + if (j % 2 == 0) { + b[i][j] = c[i][j] + 10; + } else { + break; + } + } + } +} + +*/ +TEST_F(FusionLegalTest, OuterloopWithBreakContinueInInner) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %19 "j" + OpName %38 "c" + OpName %41 "a" + OpName %55 "i" + OpName %63 "j" + OpName %76 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %28 = OpConstant %6 2 + %33 = OpTypeInt 32 0 + %34 = OpConstant %33 10 + %35 = OpTypeArray %6 %34 + %36 = OpTypeArray %35 %34 + %37 = OpTypePointer Function %36 + %51 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %38 = OpVariable %37 Function + %41 = OpVariable %37 Function + %55 = OpVariable %7 Function + %63 = OpVariable %7 Function + %76 = OpVariable %37 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %91 = OpPhi %6 %9 %5 %54 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %91 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + %96 = OpPhi %6 %9 %11 %52 %23 + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %26 = OpSLessThan %17 %96 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + %29 = OpSMod %6 %96 %28 + %30 = OpIEqual %17 %29 %9 + OpSelectionMerge %32 None + OpBranchConditional %30 %31 %48 + %31 = OpLabel + %44 = OpAccessChain %7 %41 %91 %96 + %45 = OpLoad %6 %44 + %46 = OpIAdd %6 %45 %28 + %47 = OpAccessChain %7 %38 %91 %96 + OpStore %47 %46 + OpBranch %32 + %48 = OpLabel + OpBranch %23 + %32 = OpLabel + OpBranch %23 + %23 = OpLabel + %52 = OpIAdd %6 %96 %51 + OpStore %19 %52 + OpBranch %20 + %22 = OpLabel + OpBranch %13 + %13 = OpLabel + %54 = OpIAdd %6 %91 %51 + OpStore %8 %54 + OpBranch %10 + %12 = OpLabel + OpStore %55 %9 + OpBranch %56 + %56 = OpLabel + %92 = OpPhi %6 %9 %12 %90 %59 + OpLoopMerge %58 %59 None + OpBranch %60 + %60 = OpLabel + %62 = OpSLessThan %17 %92 %16 + OpBranchConditional %62 %57 %58 + %57 = OpLabel + OpStore %63 %9 + OpBranch %64 + %64 = OpLabel + %93 = OpPhi %6 %9 %57 %88 %67 + OpLoopMerge %66 %67 None + OpBranch %68 + %68 = OpLabel + %70 = OpSLessThan %17 %93 %16 + OpBranchConditional %70 %65 %66 + %65 = OpLabel + %72 = OpSMod %6 %93 %28 + %73 = OpIEqual %17 %72 %9 + OpSelectionMerge %75 None + OpBranchConditional %73 %74 %85 + %74 = OpLabel + %81 = OpAccessChain %7 %38 %92 %93 + %82 = OpLoad %6 %81 + %83 = OpIAdd %6 %82 %16 + %84 = OpAccessChain %7 %76 %92 %93 + OpStore %84 %83 + OpBranch %75 + %85 = OpLabel + OpBranch %66 + %75 = OpLabel + OpBranch %67 + %67 = OpLabel + %88 = OpIAdd %6 %93 %51 + OpStore %63 %88 + OpBranch %64 + %66 = OpLabel + OpBranch %59 + %59 = OpLabel + %90 = OpIAdd %6 %92 %51 + OpStore %55 %90 + OpBranch %56 + %58 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 4u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[2]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[1], loops[2]); + EXPECT_FALSE(fusion.AreCompatible()); + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK: [[PHI_2:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] [[PHI_2]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// j loop preheader removed manually +#version 440 core +void main() { + int[10] a; + int[10] b; + int i = 0; + int j = 0; + // No loop-carried dependences, legal + for (; i < 10; i++) { + a[i] = a[i]*2; + } + for (; j < 10; j++) { + b[j] = a[j]+2; + } +} + +*/ +TEST_F(FusionLegalTest, DifferentArraysInLoopsNoPreheader) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %10 "j" + OpName %24 "a" + OpName %42 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 10 + %22 = OpTypeArray %6 %21 + %23 = OpTypePointer Function %22 + %29 = OpConstant %6 2 + %33 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %24 = OpVariable %23 Function + %42 = OpVariable %23 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + %51 = OpPhi %6 %9 %5 %34 %14 + OpLoopMerge %35 %14 None + OpBranch %15 + %15 = OpLabel + %19 = OpSLessThan %18 %51 %17 + OpBranchConditional %19 %12 %35 + %12 = OpLabel + %27 = OpAccessChain %7 %24 %51 + %28 = OpLoad %6 %27 + %30 = OpIMul %6 %28 %29 + %31 = OpAccessChain %7 %24 %51 + OpStore %31 %30 + OpBranch %14 + %14 = OpLabel + %34 = OpIAdd %6 %51 %33 + OpStore %8 %34 + OpBranch %11 + %35 = OpLabel + %52 = OpPhi %6 %9 %15 %50 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %18 %52 %17 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %24 %52 + %46 = OpLoad %6 %45 + %47 = OpIAdd %6 %46 %29 + %48 = OpAccessChain %7 %42 %52 + OpStore %48 %47 + OpBranch %38 + %38 = OpLabel + %50 = OpIAdd %6 %52 %33 + OpStore %10 %50 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + { + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_FALSE(fusion.AreCompatible()); + } + + ld.CreatePreHeaderBlocksIfMissing(); + + { + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +// j & k loop preheaders removed manually +#version 440 core +void main() { + int[10] a; + int[10] b; + int i = 0; + int j = 0; + int k = 0; + // No loop-carried dependences, legal + for (; i < 10; i++) { + a[i] = a[i]*2; + } + for (; j < 10; j++) { + b[j] = a[j]+2; + } + for (; k < 10; k++) { + a[k] = a[k]*2; + } +} + +*/ +TEST_F(FusionLegalTest, AdjacentLoopsNoPreheaders) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %10 "j" + OpName %11 "k" + OpName %25 "a" + OpName %43 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %18 = OpConstant %6 10 + %19 = OpTypeBool + %21 = OpTypeInt 32 0 + %22 = OpConstant %21 10 + %23 = OpTypeArray %6 %22 + %24 = OpTypePointer Function %23 + %30 = OpConstant %6 2 + %34 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %11 = OpVariable %7 Function + %25 = OpVariable %24 Function + %43 = OpVariable %24 Function + OpStore %8 %9 + OpStore %10 %9 + OpStore %11 %9 + OpBranch %12 + %12 = OpLabel + %67 = OpPhi %6 %9 %5 %35 %15 + OpLoopMerge %36 %15 None + OpBranch %16 + %16 = OpLabel + %20 = OpSLessThan %19 %67 %18 + OpBranchConditional %20 %13 %36 + %13 = OpLabel + %28 = OpAccessChain %7 %25 %67 + %29 = OpLoad %6 %28 + %31 = OpIMul %6 %29 %30 + %32 = OpAccessChain %7 %25 %67 + OpStore %32 %31 + OpBranch %15 + %15 = OpLabel + %35 = OpIAdd %6 %67 %34 + OpStore %8 %35 + OpBranch %12 + %36 = OpLabel + %68 = OpPhi %6 %9 %16 %51 %39 + OpLoopMerge %52 %39 None + OpBranch %40 + %40 = OpLabel + %42 = OpSLessThan %19 %68 %18 + OpBranchConditional %42 %37 %52 + %37 = OpLabel + %46 = OpAccessChain %7 %25 %68 + %47 = OpLoad %6 %46 + %48 = OpIAdd %6 %47 %30 + %49 = OpAccessChain %7 %43 %68 + OpStore %49 %48 + OpBranch %39 + %39 = OpLabel + %51 = OpIAdd %6 %68 %34 + OpStore %10 %51 + OpBranch %36 + %52 = OpLabel + %70 = OpPhi %6 %9 %40 %66 %55 + OpLoopMerge %54 %55 None + OpBranch %56 + %56 = OpLabel + %58 = OpSLessThan %19 %70 %18 + OpBranchConditional %58 %53 %54 + %53 = OpLabel + %61 = OpAccessChain %7 %25 %70 + %62 = OpLoad %6 %61 + %63 = OpIMul %6 %62 %30 + %64 = OpAccessChain %7 %25 %70 + OpStore %64 %63 + OpBranch %55 + %55 = OpLabel + %66 = OpIAdd %6 %70 %34 + OpStore %11 %66 + OpBranch %52 + %54 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 3u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + { + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_FALSE(fusion.AreCompatible()); + } + + ld.CreatePreHeaderBlocksIfMissing(); + + { + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + std::string checks = R"( +CHECK: [[PHI_0:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_0]] +CHECK-NEXT: OpStore [[STORE_1]] +CHECK: [[PHI_1:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]] +CHECK: [[STORE_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI_1]] +CHECK-NEXT: OpStore [[STORE_2]] + )"; + + Match(checks, context.get()); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + + std::string checks = R"( +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_0]] +CHECK: [[STORE_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpLoad {{%\w+}} [[LOAD_2]] +CHECK: [[STORE_2:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_2]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + + int sum_0 = 0; + int sum_1 = 0; + + // No loop-carried dependences, legal + for (int i = 0; i < 10; i++) { + sum_0 += a[i]; + } + for (int j = 0; j < 10; j++) { + sum_1 += b[j]; + } + + int total = sum_0 + sum_1; +} + +*/ +TEST_F(FusionLegalTest, IndependentReductions) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "sum_0" + OpName %10 "sum_1" + OpName %11 "i" + OpName %25 "a" + OpName %34 "j" + OpName %42 "b" + OpName %50 "total" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %18 = OpConstant %6 10 + %19 = OpTypeBool + %21 = OpTypeInt 32 0 + %22 = OpConstant %21 10 + %23 = OpTypeArray %6 %22 + %24 = OpTypePointer Function %23 + %32 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %11 = OpVariable %7 Function + %25 = OpVariable %24 Function + %34 = OpVariable %7 Function + %42 = OpVariable %24 Function + %50 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpStore %11 %9 + OpBranch %12 + %12 = OpLabel + %57 = OpPhi %6 %9 %5 %30 %15 + %54 = OpPhi %6 %9 %5 %33 %15 + OpLoopMerge %14 %15 None + OpBranch %16 + %16 = OpLabel + %20 = OpSLessThan %19 %54 %18 + OpBranchConditional %20 %13 %14 + %13 = OpLabel + %27 = OpAccessChain %7 %25 %54 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %57 %28 + OpStore %8 %30 + OpBranch %15 + %15 = OpLabel + %33 = OpIAdd %6 %54 %32 + OpStore %11 %33 + OpBranch %12 + %14 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %58 = OpPhi %6 %9 %14 %47 %38 + %55 = OpPhi %6 %9 %14 %49 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %19 %55 %18 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %44 = OpAccessChain %7 %42 %55 + %45 = OpLoad %6 %44 + %47 = OpIAdd %6 %58 %45 + OpStore %10 %47 + OpBranch %38 + %38 = OpLabel + %49 = OpIAdd %6 %55 %32 + OpStore %34 %49 + OpBranch %35 + %37 = OpLabel + %53 = OpIAdd %6 %57 %58 + OpStore %50 %53 + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + + std::string checks = R"( +CHECK: [[SUM_0:%\w+]] = OpPhi +CHECK-NEXT: [[SUM_1:%\w+]] = OpPhi +CHECK-NEXT: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]] +CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[SUM_0]] [[LOAD_RES_0]] +CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: [[LOAD_RES_1:%\w+]] = OpLoad {{%\w+}} [[LOAD_1]] +CHECK-NEXT: [[ADD_RES_1:%\w+]] = OpIAdd {{%\w+}} [[SUM_1]] [[LOAD_RES_1]] +CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + + int sum_0 = 0; + int sum_1 = 0; + + // No loop-carried dependences, legal + for (int i = 0; i < 10; i++) { + sum_0 += a[i]; + } + for (int j = 0; j < 10; j++) { + sum_1 += b[j]; + } + + int total = sum_0 + sum_1; +} + +*/ +TEST_F(FusionLegalTest, IndependentReductionsOneLCSSA) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "sum_0" + OpName %10 "sum_1" + OpName %11 "i" + OpName %25 "a" + OpName %34 "j" + OpName %42 "b" + OpName %50 "total" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %18 = OpConstant %6 10 + %19 = OpTypeBool + %21 = OpTypeInt 32 0 + %22 = OpConstant %21 10 + %23 = OpTypeArray %6 %22 + %24 = OpTypePointer Function %23 + %32 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %11 = OpVariable %7 Function + %25 = OpVariable %24 Function + %34 = OpVariable %7 Function + %42 = OpVariable %24 Function + %50 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpStore %11 %9 + OpBranch %12 + %12 = OpLabel + %57 = OpPhi %6 %9 %5 %30 %15 + %54 = OpPhi %6 %9 %5 %33 %15 + OpLoopMerge %14 %15 None + OpBranch %16 + %16 = OpLabel + %20 = OpSLessThan %19 %54 %18 + OpBranchConditional %20 %13 %14 + %13 = OpLabel + %27 = OpAccessChain %7 %25 %54 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %57 %28 + OpStore %8 %30 + OpBranch %15 + %15 = OpLabel + %33 = OpIAdd %6 %54 %32 + OpStore %11 %33 + OpBranch %12 + %14 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %58 = OpPhi %6 %9 %14 %47 %38 + %55 = OpPhi %6 %9 %14 %49 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %19 %55 %18 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %44 = OpAccessChain %7 %42 %55 + %45 = OpLoad %6 %44 + %47 = OpIAdd %6 %58 %45 + OpStore %10 %47 + OpBranch %38 + %38 = OpLabel + %49 = OpIAdd %6 %55 %32 + OpStore %34 %49 + OpBranch %35 + %37 = OpLabel + %53 = OpIAdd %6 %57 %58 + OpStore %50 %53 + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopUtils utils_0(context.get(), loops[0]); + utils_0.MakeLoopClosedSSA(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + + std::string checks = R"( +CHECK: [[SUM_0:%\w+]] = OpPhi +CHECK-NEXT: [[SUM_1:%\w+]] = OpPhi +CHECK-NEXT: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]] +CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[SUM_0]] [[LOAD_RES_0]] +CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: [[LOAD_RES_1:%\w+]] = OpLoad {{%\w+}} [[LOAD_1]] +CHECK-NEXT: [[ADD_RES_1:%\w+]] = OpIAdd {{%\w+}} [[SUM_1]] [[LOAD_RES_1]] +CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + + int sum_0 = 0; + int sum_1 = 0; + + // No loop-carried dependences, legal + for (int i = 0; i < 10; i++) { + sum_0 += a[i]; + } + for (int j = 0; j < 10; j++) { + sum_1 += b[j]; + } + + int total = sum_0 + sum_1; +} + +*/ +TEST_F(FusionLegalTest, IndependentReductionsBothLCSSA) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "sum_0" + OpName %10 "sum_1" + OpName %11 "i" + OpName %25 "a" + OpName %34 "j" + OpName %42 "b" + OpName %50 "total" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %18 = OpConstant %6 10 + %19 = OpTypeBool + %21 = OpTypeInt 32 0 + %22 = OpConstant %21 10 + %23 = OpTypeArray %6 %22 + %24 = OpTypePointer Function %23 + %32 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %11 = OpVariable %7 Function + %25 = OpVariable %24 Function + %34 = OpVariable %7 Function + %42 = OpVariable %24 Function + %50 = OpVariable %7 Function + OpStore %8 %9 + OpStore %10 %9 + OpStore %11 %9 + OpBranch %12 + %12 = OpLabel + %57 = OpPhi %6 %9 %5 %30 %15 + %54 = OpPhi %6 %9 %5 %33 %15 + OpLoopMerge %14 %15 None + OpBranch %16 + %16 = OpLabel + %20 = OpSLessThan %19 %54 %18 + OpBranchConditional %20 %13 %14 + %13 = OpLabel + %27 = OpAccessChain %7 %25 %54 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %57 %28 + OpStore %8 %30 + OpBranch %15 + %15 = OpLabel + %33 = OpIAdd %6 %54 %32 + OpStore %11 %33 + OpBranch %12 + %14 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %58 = OpPhi %6 %9 %14 %47 %38 + %55 = OpPhi %6 %9 %14 %49 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %19 %55 %18 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %44 = OpAccessChain %7 %42 %55 + %45 = OpLoad %6 %44 + %47 = OpIAdd %6 %58 %45 + OpStore %10 %47 + OpBranch %38 + %38 = OpLabel + %49 = OpIAdd %6 %55 %32 + OpStore %34 %49 + OpBranch %35 + %37 = OpLabel + %53 = OpIAdd %6 %57 %58 + OpStore %50 %53 + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopUtils utils_0(context.get(), loops[0]); + utils_0.MakeLoopClosedSSA(); + opt::LoopUtils utils_1(context.get(), loops[1]); + utils_1.MakeLoopClosedSSA(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + + std::string checks = R"( +CHECK: [[SUM_0:%\w+]] = OpPhi +CHECK-NEXT: [[SUM_1:%\w+]] = OpPhi +CHECK-NEXT: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]] +CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[SUM_0]] [[LOAD_RES_0]] +CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: [[LOAD_RES_1:%\w+]] = OpLoad {{%\w+}} [[LOAD_1]] +CHECK-NEXT: [[ADD_RES_1:%\w+]] = OpIAdd {{%\w+}} [[SUM_1]] [[LOAD_RES_1]] +CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + + int sum_0 = 0; + + // No loop-carried dependences, legal + for (int i = 0; i < 10; i++) { + sum_0 += a[i]; + } + for (int j = 0; j < 10; j++) { + a[j] = b[j]; + } +} + +*/ +TEST_F(FusionLegalTest, LoadStoreReductionAndNonLoopCarriedDependence) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "sum_0" + OpName %10 "i" + OpName %24 "a" + OpName %33 "j" + OpName %42 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %17 = OpConstant %6 10 + %18 = OpTypeBool + %20 = OpTypeInt 32 0 + %21 = OpConstant %20 10 + %22 = OpTypeArray %6 %21 + %23 = OpTypePointer Function %22 + %31 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %10 = OpVariable %7 Function + %24 = OpVariable %23 Function + %33 = OpVariable %7 Function + %42 = OpVariable %23 Function + OpStore %8 %9 + OpStore %10 %9 + OpBranch %11 + %11 = OpLabel + %51 = OpPhi %6 %9 %5 %29 %14 + %49 = OpPhi %6 %9 %5 %32 %14 + OpLoopMerge %13 %14 None + OpBranch %15 + %15 = OpLabel + %19 = OpSLessThan %18 %49 %17 + OpBranchConditional %19 %12 %13 + %12 = OpLabel + %26 = OpAccessChain %7 %24 %49 + %27 = OpLoad %6 %26 + %29 = OpIAdd %6 %51 %27 + OpStore %8 %29 + OpBranch %14 + %14 = OpLabel + %32 = OpIAdd %6 %49 %31 + OpStore %10 %32 + OpBranch %11 + %13 = OpLabel + OpStore %33 %9 + OpBranch %34 + %34 = OpLabel + %50 = OpPhi %6 %9 %13 %48 %37 + OpLoopMerge %36 %37 None + OpBranch %38 + %38 = OpLabel + %40 = OpSLessThan %18 %50 %17 + OpBranchConditional %40 %35 %36 + %35 = OpLabel + %44 = OpAccessChain %7 %42 %50 + %45 = OpLoad %6 %44 + %46 = OpAccessChain %7 %24 %50 + OpStore %46 %45 + OpBranch %37 + %37 = OpLabel + %48 = OpIAdd %6 %50 %31 + OpStore %33 %48 + OpBranch %34 + %36 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + // TODO: Loop descriptor doesn't return induction variables but all OpPhi + // in the header and LoopDependenceAnalysis falls over. + // EXPECT_TRUE(fusion.IsLegal()); + + // fusion.Fuse(); + } + + { + // ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + // EXPECT_EQ(ld.NumLoops(), 1u); + + // std::string checks = R"( + // CHECK: [[SUM_0:%\w+]] = OpPhi + // CHECK-NEXT: [[PHI:%\w+]] = OpPhi + // CHECK-NEXT: OpLoopMerge + // CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] + // CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]] + // CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[SUM_0]] [[LOAD_RES_0]] + // CHECK-NEXT: OpStore {{%\w+}} [[ADD_RES_0]] + // CHECK-NOT: OpPhi + // CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] + // CHECK-NEXT: [[LOAD_RES_1:%\w+]] = OpLoad {{%\w+}} [[LOAD_1]] + // CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] + // CHECK-NEXT: OpStore [[STORE_1]] [[LOAD_RES_1]] + // )"; + + // Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +int x; +void main() { + int[10] a; + int[10] b; + + // Legal. + for (int i = 0; i < 10; i++) { + x += a[i]; + } + for (int j = 0; j < 10; j++) { + b[j] = b[j]+1; + } +} + +*/ +TEST_F(FusionLegalTest, ReductionAndNonLoopCarriedDependence) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %20 "x" + OpName %25 "a" + OpName %34 "j" + OpName %42 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypePointer Private %6 + %20 = OpVariable %19 Private + %21 = OpTypeInt 32 0 + %22 = OpConstant %21 10 + %23 = OpTypeArray %6 %22 + %24 = OpTypePointer Function %23 + %32 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %25 = OpVariable %24 Function + %34 = OpVariable %7 Function + %42 = OpVariable %24 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %51 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %51 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %51 + %28 = OpLoad %6 %27 + %29 = OpLoad %6 %20 + %30 = OpIAdd %6 %29 %28 + OpStore %20 %30 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %51 %32 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %52 = OpPhi %6 %9 %12 %50 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %52 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %42 %52 + %46 = OpLoad %6 %45 + %47 = OpIAdd %6 %46 %32 + %48 = OpAccessChain %7 %42 %52 + OpStore %48 %47 + OpBranch %38 + %38 = OpLabel + %50 = OpIAdd %6 %52 %32 + OpStore %34 %50 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + + std::string checks = R"( +CHECK: OpName [[X:%\w+]] "x" +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[LOAD_0:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: [[LOAD_RES_0:%\w+]] = OpLoad {{%\w+}} [[LOAD_0]] +CHECK-NEXT: [[X_LOAD:%\w+]] = OpLoad {{%\w+}} [[X]] +CHECK-NEXT: [[ADD_RES_0:%\w+]] = OpIAdd {{%\w+}} [[X_LOAD]] [[LOAD_RES_0]] +CHECK-NEXT: OpStore [[X]] [[ADD_RES_0]] +CHECK-NOT: OpPhi +CHECK: [[LOAD_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: {{%\w+}} = OpLoad {{%\w+}} [[LOAD_1]] +CHECK: [[STORE_1:%\w+]] = OpAccessChain {{%\w+}} {{%\w+}} [[PHI]] +CHECK-NEXT: OpStore [[STORE_1]] + )"; + + Match(checks, context.get()); + } +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +struct TestStruct { + int[10] a; + int b; +}; + +void main() { + TestStruct test_0; + TestStruct test_1; + TestStruct test_2; + + test_1.b = 2; + + for (int i = 0; i < 10; i++) { + test_0.a[i] = i; + } + for (int j = 0; j < 10; j++) { + test_2 = test_1; + } +} + +*/ +TEST_F(FusionLegalTest, ArrayInStruct) { + std::string text = R"( + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %10 "TestStruct" + OpMemberName %10 0 "a" + OpMemberName %10 1 "b" + OpName %12 "test_1" + OpName %17 "i" + OpName %28 "test_0" + OpName %34 "j" + OpName %42 "test_2" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypeInt 32 0 + %8 = OpConstant %7 10 + %9 = OpTypeArray %6 %8 + %10 = OpTypeStruct %9 %6 + %11 = OpTypePointer Function %10 + %13 = OpConstant %6 1 + %14 = OpConstant %6 2 + %15 = OpTypePointer Function %6 + %18 = OpConstant %6 0 + %25 = OpConstant %6 10 + %26 = OpTypeBool + %4 = OpFunction %2 None %3 + %5 = OpLabel + %12 = OpVariable %11 Function + %17 = OpVariable %15 Function + %28 = OpVariable %11 Function + %34 = OpVariable %15 Function + %42 = OpVariable %11 Function + %16 = OpAccessChain %15 %12 %13 + OpStore %16 %14 + OpStore %17 %18 + OpBranch %19 + %19 = OpLabel + %46 = OpPhi %6 %18 %5 %33 %22 + OpLoopMerge %21 %22 None + OpBranch %23 + %23 = OpLabel + %27 = OpSLessThan %26 %46 %25 + OpBranchConditional %27 %20 %21 + %20 = OpLabel + %31 = OpAccessChain %15 %28 %18 %46 + OpStore %31 %46 + OpBranch %22 + %22 = OpLabel + %33 = OpIAdd %6 %46 %13 + OpStore %17 %33 + OpBranch %19 + %21 = OpLabel + OpStore %34 %18 + OpBranch %35 + %35 = OpLabel + %47 = OpPhi %6 %18 %21 %45 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %26 %47 %25 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %43 = OpLoad %10 %12 + OpStore %42 %43 + OpBranch %38 + %38 = OpLabel + %45 = OpIAdd %6 %47 %13 + OpStore %34 %45 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + std::unique_ptr context = + BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, text, + SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS); + ir::Module* module = context->module(); + EXPECT_NE(nullptr, module) << "Assembling failed for shader:\n" + << text << std::endl; + ir::Function& f = *module->begin(); + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 2u); + + auto loops = ld.GetLoopsInBinaryLayoutOrder(); + + opt::LoopFusion fusion(context.get(), loops[0], loops[1]); + EXPECT_TRUE(fusion.AreCompatible()); + EXPECT_TRUE(fusion.IsLegal()); + + fusion.Fuse(); + } + + { + ir::LoopDescriptor& ld = *context->GetLoopDescriptor(&f); + EXPECT_EQ(ld.NumLoops(), 1u); + + // clang-format off + std::string checks = R"( +CHECK: OpName [[TEST_1:%\w+]] "test_1" +CHECK: OpName [[TEST_0:%\w+]] "test_0" +CHECK: OpName [[TEST_2:%\w+]] "test_2" +CHECK: [[PHI:%\w+]] = OpPhi +CHECK-NEXT: OpLoopMerge +CHECK: [[TEST_0_STORE:%\w+]] = OpAccessChain {{%\w+}} [[TEST_0]] {{%\w+}} {{%\w+}} +CHECK-NEXT: OpStore [[TEST_0_STORE]] [[PHI]] +CHECK-NOT: OpPhi +CHECK: [[TEST_1_LOAD:%\w+]] = OpLoad {{%\w+}} [[TEST_1]] +CHECK: OpStore [[TEST_2]] [[TEST_1_LOAD]] + )"; + // clang-format on + + Match(checks, context.get()); + } +} + +} // namespace diff --git a/test/opt/loop_optimizations/fusion_pass.cpp b/test/opt/loop_optimizations/fusion_pass.cpp new file mode 100644 index 000000000..773295d66 --- /dev/null +++ b/test/opt/loop_optimizations/fusion_pass.cpp @@ -0,0 +1,721 @@ +// 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 + +#ifdef SPIRV_EFFCEE +#include "effcee/effcee.h" +#endif + +#include "../pass_fixture.h" + +namespace { + +using namespace spvtools; + +using FusionPassTest = PassTest<::testing::Test>; + +#ifdef SPIRV_EFFCEE + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + for (int i = 0; i < 10; i++) { + a[i] = a[i]*2; + } + for (int i = 0; i < 10; i++) { + b[i] = a[i]+2; + } +} + +*/ +TEST_F(FusionPassTest, SimpleFusion) { + const std::string text = R"( +; CHECK: OpPhi +; CHECK: OpLoad +; CHECK: OpStore +; CHECK-NOT: OpPhi +; CHECK: OpLoad +; CHECK: OpStore + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %34 "i" + OpName %42 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %28 = OpConstant %6 2 + %32 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %34 = OpVariable %7 Function + %42 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %51 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %51 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpAccessChain %7 %23 %51 + %27 = OpLoad %6 %26 + %29 = OpIMul %6 %27 %28 + %30 = OpAccessChain %7 %23 %51 + OpStore %30 %29 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %51 %32 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %52 = OpPhi %6 %9 %12 %50 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %52 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %23 %52 + %46 = OpLoad %6 %45 + %47 = OpIAdd %6 %46 %28 + %48 = OpAccessChain %7 %42 %52 + OpStore %48 %47 + OpBranch %38 + %38 = OpLabel + %50 = OpIAdd %6 %52 %32 + OpStore %34 %50 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true, 20); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + for (int i = 0; i < 10; i++) { + a[i] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[i] + 2; + } + for (int i = 0; i < 10; i++) { + b[i] = c[i] + 10; + } +} + +*/ +TEST_F(FusionPassTest, ThreeLoopsFused) { + const std::string text = R"( +; CHECK: OpPhi +; CHECK: OpLoad +; CHECK: OpStore +; CHECK-NOT: OpPhi +; CHECK: OpLoad +; CHECK: OpStore +; CHECK-NOT: OpPhi +; CHECK: OpLoad +; CHECK: OpStore + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %25 "b" + OpName %34 "i" + OpName %42 "c" + OpName %52 "i" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %29 = OpConstant %6 1 + %47 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %34 = OpVariable %7 Function + %42 = OpVariable %22 Function + %52 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %68 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %68 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %68 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %28 %29 + %31 = OpAccessChain %7 %23 %68 + OpStore %31 %30 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %68 %29 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %69 = OpPhi %6 %9 %12 %51 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %69 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %23 %69 + %46 = OpLoad %6 %45 + %48 = OpIAdd %6 %46 %47 + %49 = OpAccessChain %7 %42 %69 + OpStore %49 %48 + OpBranch %38 + %38 = OpLabel + %51 = OpIAdd %6 %69 %29 + OpStore %34 %51 + OpBranch %35 + %37 = OpLabel + OpStore %52 %9 + OpBranch %53 + %53 = OpLabel + %70 = OpPhi %6 %9 %37 %67 %56 + OpLoopMerge %55 %56 None + OpBranch %57 + %57 = OpLabel + %59 = OpSLessThan %17 %70 %16 + OpBranchConditional %59 %54 %55 + %54 = OpLabel + %62 = OpAccessChain %7 %42 %70 + %63 = OpLoad %6 %62 + %64 = OpIAdd %6 %63 %16 + %65 = OpAccessChain %7 %25 %70 + OpStore %65 %64 + OpBranch %56 + %56 = OpLabel + %67 = OpIAdd %6 %70 %29 + OpStore %52 %67 + OpBranch %53 + %55 = OpLabel + OpReturn + OpFunctionEnd + + )"; + + SinglePassRunAndMatch(text, true, 20); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10][10] a; + int[10][10] b; + int[10][10] c; + // Legal both + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + c[i][j] = a[i][j] + 2; + } + } + for (int i = 0; i < 10; i++) { + for (int j = 0; j < 10; j++) { + b[i][j] = c[i][j] + 10; + } + } +} + +*/ +TEST_F(FusionPassTest, NestedLoopsFused) { + const std::string text = R"( +; CHECK: OpPhi +; CHECK: OpPhi +; CHECK: OpLoad +; CHECK: OpStore +; CHECK-NOT: OpPhi +; CHECK: OpLoad +; CHECK: OpStore + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %19 "j" + OpName %32 "c" + OpName %35 "a" + OpName %48 "i" + OpName %56 "j" + OpName %64 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %27 = OpTypeInt 32 0 + %28 = OpConstant %27 10 + %29 = OpTypeArray %6 %28 + %30 = OpTypeArray %29 %28 + %31 = OpTypePointer Function %30 + %40 = OpConstant %6 2 + %44 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %19 = OpVariable %7 Function + %32 = OpVariable %31 Function + %35 = OpVariable %31 Function + %48 = OpVariable %7 Function + %56 = OpVariable %7 Function + %64 = OpVariable %31 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %77 = OpPhi %6 %9 %5 %47 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %77 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpStore %19 %9 + OpBranch %20 + %20 = OpLabel + %81 = OpPhi %6 %9 %11 %45 %23 + OpLoopMerge %22 %23 None + OpBranch %24 + %24 = OpLabel + %26 = OpSLessThan %17 %81 %16 + OpBranchConditional %26 %21 %22 + %21 = OpLabel + %38 = OpAccessChain %7 %35 %77 %81 + %39 = OpLoad %6 %38 + %41 = OpIAdd %6 %39 %40 + %42 = OpAccessChain %7 %32 %77 %81 + OpStore %42 %41 + OpBranch %23 + %23 = OpLabel + %45 = OpIAdd %6 %81 %44 + OpStore %19 %45 + OpBranch %20 + %22 = OpLabel + OpBranch %13 + %13 = OpLabel + %47 = OpIAdd %6 %77 %44 + OpStore %8 %47 + OpBranch %10 + %12 = OpLabel + OpStore %48 %9 + OpBranch %49 + %49 = OpLabel + %78 = OpPhi %6 %9 %12 %76 %52 + OpLoopMerge %51 %52 None + OpBranch %53 + %53 = OpLabel + %55 = OpSLessThan %17 %78 %16 + OpBranchConditional %55 %50 %51 + %50 = OpLabel + OpStore %56 %9 + OpBranch %57 + %57 = OpLabel + %79 = OpPhi %6 %9 %50 %74 %60 + OpLoopMerge %59 %60 None + OpBranch %61 + %61 = OpLabel + %63 = OpSLessThan %17 %79 %16 + OpBranchConditional %63 %58 %59 + %58 = OpLabel + %69 = OpAccessChain %7 %32 %78 %79 + %70 = OpLoad %6 %69 + %71 = OpIAdd %6 %70 %16 + %72 = OpAccessChain %7 %64 %78 %79 + OpStore %72 %71 + OpBranch %60 + %60 = OpLabel + %74 = OpIAdd %6 %79 %44 + OpStore %56 %74 + OpBranch %57 + %59 = OpLabel + OpBranch %52 + %52 = OpLabel + %76 = OpIAdd %6 %78 %44 + OpStore %48 %76 + OpBranch %49 + %51 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true, 20); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + // Can't fuse, different step + for (int i = 0; i < 10; i++) {} + for (int j = 0; j < 10; j=j+2) {} +} + +*/ +TEST_F(FusionPassTest, Incompatible) { + const std::string text = R"( +; CHECK: OpPhi +; CHECK-NEXT: OpLoopMerge +; CHECK: OpPhi +; CHECK-NEXT: OpLoopMerge + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %22 "j" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %20 = OpConstant %6 1 + %31 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %22 = OpVariable %7 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %33 = OpPhi %6 %9 %5 %21 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %33 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + OpBranch %13 + %13 = OpLabel + %21 = OpIAdd %6 %33 %20 + OpStore %8 %21 + OpBranch %10 + %12 = OpLabel + OpStore %22 %9 + OpBranch %23 + %23 = OpLabel + %34 = OpPhi %6 %9 %12 %32 %26 + OpLoopMerge %25 %26 None + OpBranch %27 + %27 = OpLabel + %29 = OpSLessThan %17 %34 %16 + OpBranchConditional %29 %24 %25 + %24 = OpLabel + OpBranch %26 + %26 = OpLabel + %32 = OpIAdd %6 %34 %31 + OpStore %22 %32 + OpBranch %23 + %25 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true, 20); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + int[10] c; + // Illegal, loop-independent dependence will become a + // backward loop-carried antidependence + for (int i = 0; i < 10; i++) { + a[i] = b[i] + 1; + } + for (int i = 0; i < 10; i++) { + c[i] = a[i+1] + 2; + } +} + +*/ +TEST_F(FusionPassTest, Illegal) { + std::string text = R"( +; CHECK: OpPhi +; CHECK-NEXT: OpLoopMerge +; CHECK: OpLoad +; CHECK: OpStore +; CHECK: OpPhi +; CHECK-NEXT: OpLoopMerge +; CHECK: OpLoad +; CHECK: OpStore + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %25 "b" + OpName %34 "i" + OpName %42 "c" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %29 = OpConstant %6 1 + %48 = OpConstant %6 2 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %25 = OpVariable %22 Function + %34 = OpVariable %7 Function + %42 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %53 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %53 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %27 = OpAccessChain %7 %25 %53 + %28 = OpLoad %6 %27 + %30 = OpIAdd %6 %28 %29 + %31 = OpAccessChain %7 %23 %53 + OpStore %31 %30 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %53 %29 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %54 = OpPhi %6 %9 %12 %52 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %54 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpIAdd %6 %54 %29 + %46 = OpAccessChain %7 %23 %45 + %47 = OpLoad %6 %46 + %49 = OpIAdd %6 %47 %48 + %50 = OpAccessChain %7 %42 %54 + OpStore %50 %49 + OpBranch %38 + %38 = OpLabel + %52 = OpIAdd %6 %54 %29 + OpStore %34 %52 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true, 20); +} + +/* +Generated from the following GLSL + --eliminate-local-multi-store + +#version 440 core +void main() { + int[10] a; + int[10] b; + for (int i = 0; i < 10; i++) { + a[i] = a[i]*2; + } + for (int i = 0; i < 10; i++) { + b[i] = a[i]+2; + } +} + +*/ +TEST_F(FusionPassTest, TooManyRegisters) { + const std::string text = R"( +; CHECK: OpPhi +; CHECK-NEXT: OpLoopMerge +; CHECK: OpLoad +; CHECK: OpStore +; CHECK: OpPhi +; CHECK-NEXT: OpLoopMerge +; CHECK: OpLoad +; CHECK: OpStore + + OpCapability Shader + %1 = OpExtInstImport "GLSL.std.450" + OpMemoryModel Logical GLSL450 + OpEntryPoint Fragment %4 "main" + OpExecutionMode %4 OriginUpperLeft + OpSource GLSL 440 + OpName %4 "main" + OpName %8 "i" + OpName %23 "a" + OpName %34 "i" + OpName %42 "b" + %2 = OpTypeVoid + %3 = OpTypeFunction %2 + %6 = OpTypeInt 32 1 + %7 = OpTypePointer Function %6 + %9 = OpConstant %6 0 + %16 = OpConstant %6 10 + %17 = OpTypeBool + %19 = OpTypeInt 32 0 + %20 = OpConstant %19 10 + %21 = OpTypeArray %6 %20 + %22 = OpTypePointer Function %21 + %28 = OpConstant %6 2 + %32 = OpConstant %6 1 + %4 = OpFunction %2 None %3 + %5 = OpLabel + %8 = OpVariable %7 Function + %23 = OpVariable %22 Function + %34 = OpVariable %7 Function + %42 = OpVariable %22 Function + OpStore %8 %9 + OpBranch %10 + %10 = OpLabel + %51 = OpPhi %6 %9 %5 %33 %13 + OpLoopMerge %12 %13 None + OpBranch %14 + %14 = OpLabel + %18 = OpSLessThan %17 %51 %16 + OpBranchConditional %18 %11 %12 + %11 = OpLabel + %26 = OpAccessChain %7 %23 %51 + %27 = OpLoad %6 %26 + %29 = OpIMul %6 %27 %28 + %30 = OpAccessChain %7 %23 %51 + OpStore %30 %29 + OpBranch %13 + %13 = OpLabel + %33 = OpIAdd %6 %51 %32 + OpStore %8 %33 + OpBranch %10 + %12 = OpLabel + OpStore %34 %9 + OpBranch %35 + %35 = OpLabel + %52 = OpPhi %6 %9 %12 %50 %38 + OpLoopMerge %37 %38 None + OpBranch %39 + %39 = OpLabel + %41 = OpSLessThan %17 %52 %16 + OpBranchConditional %41 %36 %37 + %36 = OpLabel + %45 = OpAccessChain %7 %23 %52 + %46 = OpLoad %6 %45 + %47 = OpIAdd %6 %46 %28 + %48 = OpAccessChain %7 %42 %52 + OpStore %48 %47 + OpBranch %38 + %38 = OpLabel + %50 = OpIAdd %6 %52 %32 + OpStore %34 %50 + OpBranch %35 + %37 = OpLabel + OpReturn + OpFunctionEnd + )"; + + SinglePassRunAndMatch(text, true, 5); +} + +#endif + +} // namespace diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp index db069c0b8..e8fe1c590 100644 --- a/tools/opt/opt.cpp +++ b/tools/opt/opt.cpp @@ -180,6 +180,13 @@ Options (in lexicographical order): Splits any top level loops in which the register pressure has exceeded a given threshold. The threshold must follow the use of this flag and must be a positive integer value. + --loop-fusion + Identifies adjacent loops with the same lower and upper bound. + If this is legal, then merge the loops into a single loop. + Includes heuristics to ensure it does not increase number of + registers too much, while reducing the number of loads from + memory. Takes an additional positive integer argument to set + the maximum number of registers. --loop-unroll Fully unrolls loops marked with the Unroll flag --loop-unroll-partial @@ -418,6 +425,21 @@ OptStatus ParseLoopFissionArg(int argc, const char** argv, int argi, fprintf( stderr, "error: --loop-fission must be followed by a positive integer value\n"); +} + +OptStatus ParseLoopFusionArg(int argc, const char** argv, int argi, + Optimizer* optimizer) { + if (argi < argc) { + int max_registers_per_loop = atoi(argv[argi]); + if (max_registers_per_loop > 0) { + optimizer->RegisterPass( + CreateLoopFusionPass(static_cast(max_registers_per_loop))); + return {OPT_CONTINUE, 0}; + } + } + fprintf(stderr, + "error: --loop-loop-fusion must be followed by a positive " + "integer\n"); return {OPT_STOP, 1}; } @@ -577,6 +599,8 @@ OptStatus ParseFlags(int argc, const char** argv, Optimizer* optimizer, optimizer->RegisterPass(CreateCopyPropagateArraysPass()); } else if (0 == strcmp(cur_arg, "--loop-fission")) { OptStatus status = ParseLoopFissionArg(argc, argv, ++argi, optimizer); + } else if (0 == strcmp(cur_arg, "--loop-fusion")) { + OptStatus status = ParseLoopFusionArg(argc, argv, ++argi, optimizer); if (status.action != OPT_CONTINUE) { return status; }