// Copyright (c) 2021 Alastair F. Donaldson // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #include "source/reduce/structured_construct_to_block_reduction_opportunity_finder.h" #include #include "source/reduce/structured_construct_to_block_reduction_opportunity.h" namespace spvtools { namespace reduce { std::vector> StructuredConstructToBlockReductionOpportunityFinder::GetAvailableOpportunities( opt::IRContext* context, uint32_t target_function) const { std::vector> result; // Consider every function in the module. for (auto* function : GetTargetFunctions(context, target_function)) { // For every header block in the function, there is potentially a region of // blocks that could be collapsed. std::unordered_map> regions; // Regions are identified using dominators and postdominators, so we compute // those for the function. auto* dominators = context->GetDominatorAnalysis(function); auto* postdominators = context->GetPostDominatorAnalysis(function); // Consider every block in the function. for (auto& block : *function) { // If a block has an unreachable predecessor then folding away a region in // which that block is contained gets complicated, so we ignore regions // that contain such blocks. We note whether this block suffers from this // problem. bool has_unreachable_predecessor = HasUnreachablePredecessor(block, context); // Look through all the regions we have identified so far to see whether // this block is part of a region, or spoils a region (by having an // unreachable predecessor). for (auto entry = regions.begin(); entry != regions.end();) { // |block| is in this region if it is dominated by the header, // post-dominated by the merge, and different from the merge. assert(&block != entry->first && "The block should not be the region's header because we only " "make a region when we encounter its header."); if (entry->first->MergeBlockId() != block.id() && dominators->Dominates(entry->first, &block) && postdominators->Dominates( entry->first->GetMergeInst()->GetSingleWordInOperand(0), block.id())) { if (has_unreachable_predecessor) { // The block would be in this region, but it has an unreachable // predecessor. This spoils the region, so we remove it. entry = regions.erase(entry); continue; } else { // Add the block to the region. entry->second.insert(&block); } } ++entry; } if (block.MergeBlockIdIfAny() == 0) { // The block isn't a header, so it doesn't constitute a new region. continue; } if (!context->IsReachable(block)) { // The block isn't reachable, so it doesn't constitute a new region. continue; } auto* merge_block = context->cfg()->block( block.GetMergeInst()->GetSingleWordInOperand(0)); if (!context->IsReachable(*merge_block)) { // The block's merge is unreachable, so it doesn't constitute a new // region. continue; } assert(dominators->Dominates(&block, merge_block) && "The merge block is reachable, so the header must dominate it"); if (!postdominators->Dominates(merge_block, &block)) { // The block is not post-dominated by its merge. This happens for // instance when there is a break from a conditional, or an early exit. // This also means that we don't add a region. continue; } // We have a reachable header block with a reachable merge that // postdominates the header: this means we have a new region. regions.emplace(&block, std::unordered_set()); } // Now that we have found all the regions and blocks within them, we check // whether any region defines an id that is used outside the region. If this // is *not* the case, then we have an opportunity to collapse the region // down to its header block and merge block. for (auto& entry : regions) { if (DefinitionsRestrictedToRegion(*entry.first, entry.second, context)) { result.emplace_back( MakeUnique( context, entry.first->id())); } } } return result; } bool StructuredConstructToBlockReductionOpportunityFinder:: DefinitionsRestrictedToRegion( const opt::BasicBlock& header, const std::unordered_set& region, opt::IRContext* context) { // Consider every block in the region. for (auto& block : region) { // Consider every instruction in the block - this includes the label // instruction if (!block->WhileEachInst( [context, &header, ®ion](opt::Instruction* inst) -> bool { if (inst->result_id() == 0) { // The instruction does not generate a result id, thus it cannot // be referred to outside the region - this is fine. return true; } // Consider every use of the instruction's result id. if (!context->get_def_use_mgr()->WhileEachUse( inst->result_id(), [context, &header, ®ion](opt::Instruction* user, uint32_t) -> bool { auto user_block = context->get_instr_block(user); if (user == header.GetMergeInst() || user == header.terminator()) { // We are going to delete the header's merge // instruction and rewrite its terminator, so it does // not matter if the user is one of these // instructions. return true; } if (user_block == nullptr || region.count(user_block) == 0) { // The user is either a global instruction, or an // instruction in a block outside the region. Removing // the region would invalidate this user. return false; } return true; })) { return false; } return true; })) { return false; } } return true; } bool StructuredConstructToBlockReductionOpportunityFinder:: HasUnreachablePredecessor(const opt::BasicBlock& block, opt::IRContext* context) { for (auto pred : context->cfg()->preds(block.id())) { if (!context->IsReachable(*context->cfg()->block(pred))) { return true; } } return false; } std::string StructuredConstructToBlockReductionOpportunityFinder::GetName() const { return "StructuredConstructToBlockReductionOpportunityFinder"; } } // namespace reduce } // namespace spvtools