From 94bec26afe1af1cfadce68cef9508130ae3d1bc5 Mon Sep 17 00:00:00 2001 From: GregF Date: Tue, 17 Oct 2017 16:33:43 -0600 Subject: [PATCH] ADCE: Dead if elimination Mark structured conditional branches live only if one or more instructions in their associated construct is marked live. After closure, replace dead structured conditional branches with a branch to its merge and remove dead blocks. ADCE: Dead If Elim: Remove duplicate StructuredOrder code Also generalize ComputeStructuredOrder so that the caller can specify the root block for the order. Phi insertion uses pseudo_entry_block and adce and dead branch elim use the first block of the function. ADCE: Dead If Elim: Pull redundant code out of InsertPhiInstructions ADCE: Dead If Elim: Encapsulate CFG Cleanup Initialization ADCE: Dead If Elim: Remove redundant code from ADCE initialization ADCE: Dead If: Use CFGCleanup to eliminate newly dead blocks Moved bulk of CFG Cleanup code into MemPass. --- source/opt/aggressive_dead_code_elim_pass.cpp | 203 ++++- source/opt/aggressive_dead_code_elim_pass.h | 51 ++ source/opt/cfg_cleanup_pass.cpp | 216 +---- source/opt/cfg_cleanup_pass.h | 24 - source/opt/dead_branch_elim_pass.cpp | 9 +- source/opt/dead_branch_elim_pass.h | 3 - source/opt/mem_pass.cpp | 236 +++++- source/opt/mem_pass.h | 32 +- source/opt/pass.cpp | 7 +- source/opt/pass.h | 21 +- test/opt/aggressive_dead_code_elim_test.cpp | 744 ++++++++++++++++++ 11 files changed, 1241 insertions(+), 305 deletions(-) diff --git a/source/opt/aggressive_dead_code_elim_pass.cpp b/source/opt/aggressive_dead_code_elim_pass.cpp index 71df2f6e6..0be802a54 100644 --- a/source/opt/aggressive_dead_code_elim_pass.cpp +++ b/source/opt/aggressive_dead_code_elim_pass.cpp @@ -16,9 +16,12 @@ #include "aggressive_dead_code_elim_pass.h" +#include "cfa.h" #include "iterator.h" #include "spirv/1.0/GLSL.std.450.h" +#include + namespace spvtools { namespace opt { @@ -28,6 +31,8 @@ const uint32_t kTypePointerStorageClassInIdx = 0; const uint32_t kExtInstSetIdInIndx = 0; const uint32_t kExtInstInstructionInIndx = 1; const uint32_t kEntryPointFunctionIdInIdx = 1; +const uint32_t kSelectionMergeMergeBlockIdInIdx = 0; +const uint32_t kLoopMergeMergeBlockIdInIdx = 0; } // namespace anonymous @@ -67,8 +72,8 @@ void AggressiveDCEPass::AddStores(uint32_t ptrId) { // If default, assume it stores eg frexp, modf, function call case SpvOpStore: default: { - if (live_insts_.find(u.inst) == live_insts_.end()) - worklist_.push(u.inst); + if (!IsLive(u.inst)) + AddToWorklist(u.inst); } break; } } @@ -123,40 +128,145 @@ void AggressiveDCEPass::ProcessLoad(uint32_t varId) { live_local_vars_.insert(varId); } +bool AggressiveDCEPass::IsStructuredIfHeader(ir::BasicBlock* bp, + ir::Instruction** mergeInst, ir::Instruction** branchInst, + uint32_t* mergeBlockId) { + auto ii = bp->end(); + --ii; + if (ii->opcode() != SpvOpBranchConditional) + return false; + if (ii == bp->begin()) + return false; + if (branchInst != nullptr) *branchInst = &*ii; + --ii; + if (ii->opcode() != SpvOpSelectionMerge) + return false; + if (mergeInst != nullptr) + *mergeInst = &*ii; + if (mergeBlockId != nullptr) + *mergeBlockId = + ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx); + return true; +} + +void AggressiveDCEPass::ComputeBlock2HeaderMaps( + std::list& structuredOrder) { + block2headerMerge_.clear(); + block2headerBranch_.clear(); + std::stack currentMergeInst; + std::stack currentBranchInst; + std::stack currentMergeBlockId; + currentMergeInst.push(nullptr); + currentBranchInst.push(nullptr); + currentMergeBlockId.push(0); + for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) { + if ((*bi)->id() == currentMergeBlockId.top()) { + currentMergeBlockId.pop(); + currentMergeInst.pop(); + currentBranchInst.pop(); + } + block2headerMerge_[*bi] = currentMergeInst.top(); + block2headerBranch_[*bi] = currentBranchInst.top(); + ir::Instruction* mergeInst; + ir::Instruction* branchInst; + uint32_t mergeBlockId; + if (IsStructuredIfHeader(*bi, &mergeInst, &branchInst, &mergeBlockId)) { + currentMergeBlockId.push(mergeBlockId); + currentMergeInst.push(mergeInst); + currentBranchInst.push(branchInst); + } + } +} + +void AggressiveDCEPass::ComputeInst2BlockMap(ir::Function* func) { + for (auto& blk : *func) { + blk.ForEachInst([&blk,this](ir::Instruction* ip) { + inst2block_[ip] = &blk; + }); + } +} + +void AggressiveDCEPass::AddBranch(uint32_t labelId, ir::BasicBlock* bp) { + std::unique_ptr newBranch( + new ir::Instruction(SpvOpBranch, 0, 0, + {{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {labelId}}})); + get_def_use_mgr()->AnalyzeInstDefUse(&*newBranch); + bp->AddInstruction(std::move(newBranch)); +} + bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) { + // Compute map from instruction to block + ComputeInst2BlockMap(func); + // Compute map from block to controlling conditional branch + std::list structuredOrder; + ComputeStructuredOrder(func, &*func->begin(), &structuredOrder); + ComputeBlock2HeaderMaps(structuredOrder); bool modified = false; - // Add all control flow and instructions with external side effects - // to worklist + // Add instructions with external side effects to worklist. Also add branches + // EXCEPT those immediately contained in an "if" selection construct. // TODO(greg-lunarg): Handle Frexp, Modf more optimally call_in_func_ = false; func_is_entry_point_ = false; private_stores_.clear(); - for (auto& blk : *func) { - for (auto& inst : blk) { - uint32_t op = inst.opcode(); + // Stacks to keep track of when we are inside an if-construct. When not + // immediately inside an in-construct, we must assume all branches are live. + std::stack assume_branches_live; + std::stack currentMergeBlockId; + // Push sentinel values on stack for when outside of any control flow. + assume_branches_live.push(true); + currentMergeBlockId.push(0); + for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) { + if ((*bi)->id() == currentMergeBlockId.top()) { + assume_branches_live.pop(); + currentMergeBlockId.pop(); + } + for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) { + SpvOp op = ii->opcode(); switch (op) { case SpvOpStore: { uint32_t varId; - (void) GetPtr(&inst, &varId); + (void) GetPtr(&*ii, &varId); // Mark stores as live if their variable is not function scope // and is not private scope. Remember private stores for possible // later inclusion if (IsVarOfStorage(varId, SpvStorageClassPrivate)) - private_stores_.push_back(&inst); + private_stores_.push_back(&*ii); else if (!IsVarOfStorage(varId, SpvStorageClassFunction)) - worklist_.push(&inst); + AddToWorklist(&*ii); } break; case SpvOpExtInst: { // eg. GLSL frexp, modf - if (!IsCombinatorExt(&inst)) - worklist_.push(&inst); + if (!IsCombinatorExt(&*ii)) + AddToWorklist(&*ii); + } break; + case SpvOpLoopMerge: { + // Assume loops live (for now) + // TODO(greg-lunarg): Add dead loop elimination + assume_branches_live.push(true); + currentMergeBlockId.push( + ii->GetSingleWordInOperand(kLoopMergeMergeBlockIdInIdx)); + AddToWorklist(&*ii); + } break; + case SpvOpSelectionMerge: { + auto brii = ii; + ++brii; + bool is_structured_if = brii->opcode() == SpvOpBranchConditional; + assume_branches_live.push(!is_structured_if); + currentMergeBlockId.push( + ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx)); + if (!is_structured_if) + AddToWorklist(&*ii); + } break; + case SpvOpBranch: + case SpvOpBranchConditional: { + if (assume_branches_live.top()) + AddToWorklist(&*ii); } break; default: { - // eg. control flow, function call, atomics, function param, - // function return + // Function calls, atomics, function params, function returns, etc. // TODO(greg-lunarg): function calls live only if write to non-local if (!IsCombinator(op)) - worklist_.push(&inst); + AddToWorklist(&*ii); // Remember function calls if (op == SpvOpFunctionCall) call_in_func_ = true; @@ -178,24 +288,32 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) { // If privates are not like local, add their stores to worklist if (!private_like_local_) for (auto& ps : private_stores_) - worklist_.push(ps); + AddToWorklist(ps); // Add OpGroupDecorates to worklist because they are a pain to remove // ids from. // TODO(greg-lunarg): Handle dead ids in OpGroupDecorate for (auto& ai : get_module()->annotations()) { if (ai.opcode() == SpvOpGroupDecorate) - worklist_.push(&ai); + AddToWorklist(&ai); } // Perform closure on live instruction set. while (!worklist_.empty()) { ir::Instruction* liveInst = worklist_.front(); - live_insts_.insert(liveInst); // Add all operand instructions if not already live liveInst->ForEachInId([this](const uint32_t* iid) { ir::Instruction* inInst = get_def_use_mgr()->GetDef(*iid); - if (live_insts_.find(inInst) == live_insts_.end()) - worklist_.push(inInst); + if (!IsLive(inInst)) + AddToWorklist(inInst); }); + // If in a structured if construct, add the controlling conditional branch + // and its merge. Any containing if construct is marked live when the + // the merge and branch are processed out of the worklist. + ir::BasicBlock* blk = inst2block_[liveInst]; + ir::Instruction* branchInst = block2headerBranch_[blk]; + if (branchInst != nullptr && !IsLive(branchInst)) { + AddToWorklist(branchInst); + AddToWorklist(block2headerMerge_[blk]); + } // If local load, add all variable's stores if variable not already live if (liveInst->opcode() == SpvOpLoad) { uint32_t varId; @@ -218,12 +336,16 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) { } worklist_.pop(); } - // Mark all non-live instructions dead - for (auto& blk : *func) { - for (auto& inst : blk) { - if (live_insts_.find(&inst) != live_insts_.end()) + // Mark all non-live instructions dead, except branches which are not + // at the end of an if-header, which indicate a dead if. + for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) { + for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) { + if (IsLive(&*ii)) continue; - dead_insts_.insert(&inst); + if (IsBranch(ii->opcode()) && + !IsStructuredIfHeader(*bi, nullptr, nullptr, nullptr)) + continue; + dead_insts_.insert(&*ii); } } // Remove debug and annotation statements referencing dead instructions. @@ -241,20 +363,41 @@ bool AggressiveDCEPass::AggressiveDCE(ir::Function* func) { if (KillInstIfTargetDead(&ai)) modified = true; } - // Kill dead instructions - for (auto& blk : *func) { - for (auto& inst : blk) { - if (dead_insts_.find(&inst) == dead_insts_.end()) + // Kill dead instructions and remember dead blocks + for (auto bi = structuredOrder.begin(); bi != structuredOrder.end();) { + uint32_t mergeBlockId = 0; + for (auto ii = (*bi)->begin(); ii != (*bi)->end(); ++ii) { + if (dead_insts_.find(&*ii) == dead_insts_.end()) continue; - get_def_use_mgr()->KillInst(&inst); + // If dead instruction is selection merge, remember merge block + // for new branch at end of block + if (ii->opcode() == SpvOpSelectionMerge) + mergeBlockId = + ii->GetSingleWordInOperand(kSelectionMergeMergeBlockIdInIdx); + get_def_use_mgr()->KillInst(&*ii); modified = true; } + // If a structured if was deleted, add a branch to its merge block, + // and traverse to the merge block, continuing processing there. + // The block still exists as the OpLabel at least is still intact. + if (mergeBlockId != 0) { + AddBranch(mergeBlockId, *bi); + for (++bi; (*bi)->id() != mergeBlockId; ++bi) { + } + } + else { + ++bi; + } } + // Cleanup all CFG including all unreachable blocks + CFGCleanup(func); + return modified; } void AggressiveDCEPass::Initialize(ir::Module* module) { InitializeProcessing(module); + InitializeCFGCleanup(module); // Clear collections worklist_ = std::queue{}; diff --git a/source/opt/aggressive_dead_code_elim_pass.h b/source/opt/aggressive_dead_code_elim_pass.h index 5495c3641..e8a0f8121 100644 --- a/source/opt/aggressive_dead_code_elim_pass.h +++ b/source/opt/aggressive_dead_code_elim_pass.h @@ -54,6 +54,22 @@ class AggressiveDCEPass : public MemPass { // privates_like_local_) bool IsLocalVar(uint32_t varId); + // Return true if |op| is branch instruction + bool IsBranch(SpvOp op) { + return op == SpvOpBranch || op == SpvOpBranchConditional; + } + + // Return true if |inst| is marked live + bool IsLive(ir::Instruction* inst) { + return live_insts_.find(inst) != live_insts_.end(); + } + + // Add |inst| to worklist_ and live_insts_. + void AddToWorklist(ir::Instruction* inst) { + worklist_.push(inst); + live_insts_.insert(inst); + } + // Add all store instruction which use |ptrId|, directly or indirectly, // to the live instruction worklist. void AddStores(uint32_t ptrId); @@ -84,6 +100,22 @@ class AggressiveDCEPass : public MemPass { // If |varId| is local, mark all stores of varId as live. void ProcessLoad(uint32_t varId); + // If |bp| is structured if header block, return true and set |branchInst| + // to the conditional branch and |mergeBlockId| to the merge block. + bool IsStructuredIfHeader(ir::BasicBlock* bp, + ir::Instruction** mergeInst, ir::Instruction** branchInst, + uint32_t* mergeBlockId); + + // Initialize block2branch_ and block2merge_ using |structuredOrder| to + // order blocks. + void ComputeBlock2HeaderMaps(std::list& structuredOrder); + + // Initialize inst2block_ for |func|. + void ComputeInst2BlockMap(ir::Function* func); + + // Add branch to |labelId| to end of block |bp|. + void AddBranch(uint32_t labelId, ir::BasicBlock* bp); + // For function |func|, mark all Stores to non-function-scope variables // and block terminating instructions as live. Recursively mark the values // they use. When complete, delete any non-live instructions. Return true @@ -114,6 +146,25 @@ class AggressiveDCEPass : public MemPass { // building up the live instructions set |live_insts_|. std::queue worklist_; + // Map from block to the branch instruction in the header of the most + // immediate controlling structured if. + std::unordered_map block2headerBranch_; + + // Map from block to the merge instruction in the header of the most + // immediate controlling structured if. + std::unordered_map block2headerMerge_; + + // Map from instruction containing block + std::unordered_map inst2block_; + + // Map from block's label id to block. + std::unordered_map id2block_; + + // Map from block to its structured successor blocks. See + // ComputeStructuredSuccessors() for definition. + std::unordered_map> + block2structured_succs_; + // Store instructions to variables of private storage std::vector private_stores_; diff --git a/source/opt/cfg_cleanup_pass.cpp b/source/opt/cfg_cleanup_pass.cpp index ee6536d40..cb5729039 100644 --- a/source/opt/cfg_cleanup_pass.cpp +++ b/source/opt/cfg_cleanup_pass.cpp @@ -27,223 +27,9 @@ namespace spvtools { namespace opt { -// Remove all |phi| operands coming from unreachable blocks (i.e., blocks not in -// |reachable_blocks|). There are two types of removal that this function can -// perform: -// -// 1- Any operand that comes directly from an unreachable block is completely -// removed. Since the block is unreachable, the edge between the unreachable -// block and the block holding |phi| has been removed. -// -// 2- Any operand that comes via a live block and was defined at an unreachable -// block gets its value replaced with an OpUndef value. Since the argument -// was generated in an unreachable block, it no longer exists, so it cannot -// be referenced. However, since the value does not reach |phi| directly -// from the unreachable block, the operand cannot be removed from |phi|. -// Therefore, we replace the argument value with OpUndef. -// -// For example, in the switch() below, assume that we want to remove the -// argument with value %11 coming from block %41. -// -// [ ... ] -// %41 = OpLabel <--- Unreachable block -// %11 = OpLoad %int %y -// [ ... ] -// OpSelectionMerge %16 None -// OpSwitch %12 %16 10 %13 13 %14 18 %15 -// %13 = OpLabel -// OpBranch %16 -// %14 = OpLabel -// OpStore %outparm %int_14 -// OpBranch %16 -// %15 = OpLabel -// OpStore %outparm %int_15 -// OpBranch %16 -// %16 = OpLabel -// %30 = OpPhi %int %11 %41 %int_42 %13 %11 %14 %11 %15 -// -// Since %41 is now an unreachable block, the first operand of |phi| needs to -// be removed completely. But the operands (%11 %14) and (%11 %15) cannot be -// removed because %14 and %15 are reachable blocks. Since %11 no longer exist, -// in those arguments, we replace all references to %11 with an OpUndef value. -// This results in |phi| looking like: -// -// %50 = OpUndef %int -// [ ... ] -// %30 = OpPhi %int %int_42 %13 %50 %14 %50 %15 -void CFGCleanupPass::RemovePhiOperands( - ir::Instruction* phi, - std::unordered_set reachable_blocks) { - std::vector keep_operands; - uint32_t type_id = 0; - // The id of an undefined value we've generated. - uint32_t undef_id = 0; - - // Traverse all the operands in |phi|. Build the new operand vector by adding - // all the original operands from |phi| except the unwanted ones. - for (uint32_t i = 0; i < phi->NumOperands();) { - if (i < 2) { - // The first two arguments are always preserved. - keep_operands.push_back(phi->GetOperand(i)); - ++i; - continue; - } - - // The remaining Phi arguments come in pairs. Index 'i' contains the - // variable id, index 'i + 1' is the originating block id. - assert(i % 2 == 0 && i < phi->NumOperands() - 1 && - "malformed Phi arguments"); - - ir::BasicBlock *in_block = label2block_[phi->GetSingleWordOperand(i + 1)]; - if (reachable_blocks.find(in_block) == reachable_blocks.end()) { - // If the incoming block is unreachable, remove both operands as this - // means that the |phi| has lost an incoming edge. - i += 2; - continue; - } - - // In all other cases, the operand must be kept but may need to be changed. - uint32_t arg_id = phi->GetSingleWordOperand(i); - ir::BasicBlock *def_block = def_block_[arg_id]; - if (def_block && - reachable_blocks.find(def_block_[arg_id]) == reachable_blocks.end()) { - // If the current |phi| argument was defined in an unreachable block, it - // means that this |phi| argument is no longer defined. Replace it with - // |undef_id|. - if (!undef_id) { - type_id = get_def_use_mgr()->GetDef(arg_id)->type_id(); - undef_id = Type2Undef(type_id); - } - keep_operands.push_back( - ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {undef_id})); - } else { - // Otherwise, the argument comes from a reachable block or from no block - // at all (meaning that it was defined in the global section of the - // program). In both cases, keep the argument intact. - keep_operands.push_back(phi->GetOperand(i)); - } - - keep_operands.push_back(phi->GetOperand(i + 1)); - - i += 2; - } - - phi->ReplaceOperands(keep_operands); -} - -void CFGCleanupPass::RemoveBlock(ir::Function::iterator* bi) { - auto& rm_block = **bi; - - // Remove instructions from the block. - rm_block.ForEachInst([&rm_block, this](ir::Instruction* inst) { - // Note that we do not kill the block label instruction here. The label - // instruction is needed to identify the block, which is needed by the - // removal of phi operands. - if (inst != rm_block.GetLabelInst()) { - KillNamesAndDecorates(inst); - get_def_use_mgr()->KillInst(inst); - } - }); - - // Remove the label instruction last. - auto label = rm_block.GetLabelInst(); - KillNamesAndDecorates(label); - get_def_use_mgr()->KillInst(label); - - *bi = bi->Erase(); -} - -bool CFGCleanupPass::RemoveUnreachableBlocks(ir::Function* func) { - bool modified = false; - - // Mark reachable all blocks reachable from the function's entry block. - std::unordered_set reachable_blocks; - std::unordered_set visited_blocks; - std::queue worklist; - reachable_blocks.insert(func->entry().get()); - - // Initially mark the function entry point as reachable. - worklist.push(func->entry().get()); - - auto mark_reachable = [&reachable_blocks, &visited_blocks, &worklist, - this](uint32_t label_id) { - auto successor = label2block_[label_id]; - if (visited_blocks.count(successor) == 0) { - reachable_blocks.insert(successor); - worklist.push(successor); - visited_blocks.insert(successor); - } - }; - - // Transitively mark all blocks reachable from the entry as reachable. - while (!worklist.empty()) { - ir::BasicBlock* block = worklist.front(); - worklist.pop(); - - // All the successors of a live block are also live. - block->ForEachSuccessorLabel(mark_reachable); - - // All the Merge and ContinueTarget blocks of a live block are also live. - block->ForMergeAndContinueLabel(mark_reachable); - } - - // Update operands of Phi nodes that reference unreachable blocks. - for (auto& block : *func) { - // If the block is about to be removed, don't bother updating its - // Phi instructions. - if (reachable_blocks.count(&block) == 0) { - continue; - } - - // If the block is reachable and has Phi instructions, remove all - // operands from its Phi instructions that reference unreachable blocks. - // If the block has no Phi instructions, this is a no-op. - block.ForEachPhiInst( - [&block, &reachable_blocks, this](ir::Instruction* phi) { - RemovePhiOperands(phi, reachable_blocks); - }); - } - - // Erase unreachable blocks. - for (auto ebi = func->begin(); ebi != func->end();) { - if (reachable_blocks.count(&*ebi) == 0) { - RemoveBlock(&ebi); - modified = true; - } else { - ++ebi; - } - } - - return modified; -} - -bool CFGCleanupPass::CFGCleanup(ir::Function* func) { - bool modified = false; - modified |= RemoveUnreachableBlocks(func); - return modified; -} - void CFGCleanupPass::Initialize(ir::Module* module) { InitializeProcessing(module); - - // Initialize block lookup map. - label2block_.clear(); - for (auto& fn : *module) { - for (auto& block : fn) { - label2block_[block.id()] = █ - - // Build a map between SSA names to the block they are defined in. - // TODO(dnovillo): This is expensive and unnecessary if ir::Instruction - // instances could figure out what basic block they belong to. Remove this - // once this is possible. - block.ForEachInst([this, &block](ir::Instruction* inst) { - uint32_t result_id = inst->result_id(); - if (result_id > 0) { - def_block_[result_id] = █ - } - }); - } - } + InitializeCFGCleanup(module); } Pass::Status CFGCleanupPass::Process(ir::Module* module) { diff --git a/source/opt/cfg_cleanup_pass.h b/source/opt/cfg_cleanup_pass.h index 46f799fcc..d26f16b78 100644 --- a/source/opt/cfg_cleanup_pass.h +++ b/source/opt/cfg_cleanup_pass.h @@ -29,32 +29,8 @@ class CFGCleanupPass : public MemPass { Status Process(ir::Module*) override; private: - // Call all the cleanup helper functions on |func|. - bool CFGCleanup(ir::Function* func); - - // Remove all the unreachable basic blocks in |func|. - bool RemoveUnreachableBlocks(ir::Function* func); - - // Remove the block pointed by the iterator |*bi|. This also removes - // all the instructions in the pointed-to block. - void RemoveBlock(ir::Function::iterator* bi); - // Initialize the pass. void Initialize(ir::Module* module); - - // Remove Phi operands in |phi| that are coming from blocks not in - // |reachable_blocks|. - void RemovePhiOperands(ir::Instruction* phi, - std::unordered_set reachable_blocks); - - // Map from block's label id to block. TODO(dnovillo): Basic blocks ought to - // have basic blocks in their pred/succ list. - std::unordered_map label2block_; - - // Map from an instruction result ID to the block that holds it. - // TODO(dnovillo): This would be unnecessary if ir::Instruction instances - // knew what basic block they belong to. - std::unordered_map def_block_; }; } // namespace opt diff --git a/source/opt/dead_branch_elim_pass.cpp b/source/opt/dead_branch_elim_pass.cpp index a15d96e41..92486fad7 100644 --- a/source/opt/dead_branch_elim_pass.cpp +++ b/source/opt/dead_branch_elim_pass.cpp @@ -105,13 +105,6 @@ void DeadBranchElimPass::AddBranchConditional(uint32_t condId, bp->AddInstruction(std::move(newBranchCond)); } -void DeadBranchElimPass::KillAllInsts(ir::BasicBlock* bp) { - bp->ForEachInst([this](ir::Instruction* ip) { - KillNamesAndDecorates(ip); - get_def_use_mgr()->KillInst(ip); - }); -} - bool DeadBranchElimPass::GetSelectionBranch(ir::BasicBlock* bp, ir::Instruction** branchInst, ir::Instruction** mergeInst, uint32_t *condId) { @@ -182,7 +175,7 @@ void DeadBranchElimPass::ComputeBackEdges( bool DeadBranchElimPass::EliminateDeadBranches(ir::Function* func) { // Traverse blocks in structured order std::list structuredOrder; - ComputeStructuredOrder(func, &structuredOrder); + ComputeStructuredOrder(func, &*func->begin(), &structuredOrder); ComputeBackEdges(structuredOrder); std::unordered_set elimBlocks; bool modified = false; diff --git a/source/opt/dead_branch_elim_pass.h b/source/opt/dead_branch_elim_pass.h index 0a688424e..12a2c383b 100644 --- a/source/opt/dead_branch_elim_pass.h +++ b/source/opt/dead_branch_elim_pass.h @@ -66,9 +66,6 @@ class DeadBranchElimPass : public MemPass { void AddBranchConditional(uint32_t condId, uint32_t trueLabId, uint32_t falseLabId, ir::BasicBlock* bp); - // Kill all instructions in block |bp|. - void KillAllInsts(ir::BasicBlock* bp); - // If block |bp| contains conditional branch or switch preceeded by an // OpSelctionMerge, return true and return branch and merge instructions // in |branchInst| and |mergeInst| and the conditional id in |condId|. diff --git a/source/opt/mem_pass.cpp b/source/opt/mem_pass.cpp index d332e4ab5..4dc8aa26c 100644 --- a/source/opt/mem_pass.cpp +++ b/source/opt/mem_pass.cpp @@ -157,6 +157,13 @@ void MemPass::KillNamesAndDecorates(ir::Instruction* inst) { KillNamesAndDecorates(rId); } +void MemPass::KillAllInsts(ir::BasicBlock* bp) { + bp->ForEachInst([this](ir::Instruction* ip) { + KillNamesAndDecorates(ip); + get_def_use_mgr()->KillInst(ip); + }); +} + bool MemPass::HasLoads(uint32_t varId) const { analysis::UseList* uses = get_def_use_mgr()->GetUses(varId); if (uses == nullptr) return false; @@ -259,12 +266,6 @@ bool MemPass::HasOnlySupportedRefs(uint32_t varId) { } void MemPass::InitSSARewrite(ir::Function* func) { - // Initialize function and block maps. - id2block_.clear(); - block2structured_succs_.clear(); - for (auto& fn : *get_module()) - for (auto& blk : fn) id2block_[blk.id()] = &blk; - // Clear collections. seen_target_vars_.clear(); seen_non_target_vars_.clear(); @@ -592,9 +593,6 @@ Pass::Status MemPass::InsertPhiInstructions(ir::Function* func) { if (!get_module()->HasCapability(SpvCapabilityShader)) return Status::SuccessWithoutChange; - // Collect all named and decorated ids. - FindNamedOrDecoratedIds(); - // Initialize the data structures used to insert Phi instructions. InitSSARewrite(func); @@ -602,7 +600,7 @@ Pass::Status MemPass::InsertPhiInstructions(ir::Function* func) { // simplest?) to make sure all predecessors blocks are processed before // a block itself. std::list structuredOrder; - ComputeStructuredOrder(func, &structuredOrder); + ComputeStructuredOrder(func, &pseudo_entry_block_, &structuredOrder); for (auto bi = structuredOrder.begin(); bi != structuredOrder.end(); ++bi) { // Skip pseudo entry block if (*bi == &pseudo_entry_block_) continue; @@ -660,6 +658,224 @@ Pass::Status MemPass::InsertPhiInstructions(ir::Function* func) { return Status::SuccessWithChange; } +// Remove all |phi| operands coming from unreachable blocks (i.e., blocks not in +// |reachable_blocks|). There are two types of removal that this function can +// perform: +// +// 1- Any operand that comes directly from an unreachable block is completely +// removed. Since the block is unreachable, the edge between the unreachable +// block and the block holding |phi| has been removed. +// +// 2- Any operand that comes via a live block and was defined at an unreachable +// block gets its value replaced with an OpUndef value. Since the argument +// was generated in an unreachable block, it no longer exists, so it cannot +// be referenced. However, since the value does not reach |phi| directly +// from the unreachable block, the operand cannot be removed from |phi|. +// Therefore, we replace the argument value with OpUndef. +// +// For example, in the switch() below, assume that we want to remove the +// argument with value %11 coming from block %41. +// +// [ ... ] +// %41 = OpLabel <--- Unreachable block +// %11 = OpLoad %int %y +// [ ... ] +// OpSelectionMerge %16 None +// OpSwitch %12 %16 10 %13 13 %14 18 %15 +// %13 = OpLabel +// OpBranch %16 +// %14 = OpLabel +// OpStore %outparm %int_14 +// OpBranch %16 +// %15 = OpLabel +// OpStore %outparm %int_15 +// OpBranch %16 +// %16 = OpLabel +// %30 = OpPhi %int %11 %41 %int_42 %13 %11 %14 %11 %15 +// +// Since %41 is now an unreachable block, the first operand of |phi| needs to +// be removed completely. But the operands (%11 %14) and (%11 %15) cannot be +// removed because %14 and %15 are reachable blocks. Since %11 no longer exist, +// in those arguments, we replace all references to %11 with an OpUndef value. +// This results in |phi| looking like: +// +// %50 = OpUndef %int +// [ ... ] +// %30 = OpPhi %int %int_42 %13 %50 %14 %50 %15 +void MemPass::RemovePhiOperands( + ir::Instruction* phi, + std::unordered_set reachable_blocks) { + std::vector keep_operands; + uint32_t type_id = 0; + // The id of an undefined value we've generated. + uint32_t undef_id = 0; + + // Traverse all the operands in |phi|. Build the new operand vector by adding + // all the original operands from |phi| except the unwanted ones. + for (uint32_t i = 0; i < phi->NumOperands();) { + if (i < 2) { + // The first two arguments are always preserved. + keep_operands.push_back(phi->GetOperand(i)); + ++i; + continue; + } + + // The remaining Phi arguments come in pairs. Index 'i' contains the + // variable id, index 'i + 1' is the originating block id. + assert(i % 2 == 0 && i < phi->NumOperands() - 1 && + "malformed Phi arguments"); + + ir::BasicBlock *in_block = label2block_[phi->GetSingleWordOperand(i + 1)]; + if (reachable_blocks.find(in_block) == reachable_blocks.end()) { + // If the incoming block is unreachable, remove both operands as this + // means that the |phi| has lost an incoming edge. + i += 2; + continue; + } + + // In all other cases, the operand must be kept but may need to be changed. + uint32_t arg_id = phi->GetSingleWordOperand(i); + ir::BasicBlock *def_block = def_block_[arg_id]; + if (def_block && + reachable_blocks.find(def_block_[arg_id]) == reachable_blocks.end()) { + // If the current |phi| argument was defined in an unreachable block, it + // means that this |phi| argument is no longer defined. Replace it with + // |undef_id|. + if (!undef_id) { + type_id = get_def_use_mgr()->GetDef(arg_id)->type_id(); + undef_id = Type2Undef(type_id); + } + keep_operands.push_back( + ir::Operand(spv_operand_type_t::SPV_OPERAND_TYPE_ID, {undef_id})); + } else { + // Otherwise, the argument comes from a reachable block or from no block + // at all (meaning that it was defined in the global section of the + // program). In both cases, keep the argument intact. + keep_operands.push_back(phi->GetOperand(i)); + } + + keep_operands.push_back(phi->GetOperand(i + 1)); + + i += 2; + } + + phi->ReplaceOperands(keep_operands); +} + +void MemPass::RemoveBlock(ir::Function::iterator* bi) { + auto& rm_block = **bi; + + // Remove instructions from the block. + rm_block.ForEachInst([&rm_block, this](ir::Instruction* inst) { + // Note that we do not kill the block label instruction here. The label + // instruction is needed to identify the block, which is needed by the + // removal of phi operands. + if (inst != rm_block.GetLabelInst()) { + KillNamesAndDecorates(inst); + get_def_use_mgr()->KillInst(inst); + } + }); + + // Remove the label instruction last. + auto label = rm_block.GetLabelInst(); + KillNamesAndDecorates(label); + get_def_use_mgr()->KillInst(label); + + *bi = bi->Erase(); +} + +bool MemPass::RemoveUnreachableBlocks(ir::Function* func) { + bool modified = false; + + // Mark reachable all blocks reachable from the function's entry block. + std::unordered_set reachable_blocks; + std::unordered_set visited_blocks; + std::queue worklist; + reachable_blocks.insert(func->entry().get()); + + // Initially mark the function entry point as reachable. + worklist.push(func->entry().get()); + + auto mark_reachable = [&reachable_blocks, &visited_blocks, &worklist, + this](uint32_t label_id) { + auto successor = label2block_[label_id]; + if (visited_blocks.count(successor) == 0) { + reachable_blocks.insert(successor); + worklist.push(successor); + visited_blocks.insert(successor); + } + }; + + // Transitively mark all blocks reachable from the entry as reachable. + while (!worklist.empty()) { + ir::BasicBlock* block = worklist.front(); + worklist.pop(); + + // All the successors of a live block are also live. + block->ForEachSuccessorLabel(mark_reachable); + + // All the Merge and ContinueTarget blocks of a live block are also live. + block->ForMergeAndContinueLabel(mark_reachable); + } + + // Update operands of Phi nodes that reference unreachable blocks. + for (auto& block : *func) { + // If the block is about to be removed, don't bother updating its + // Phi instructions. + if (reachable_blocks.count(&block) == 0) { + continue; + } + + // If the block is reachable and has Phi instructions, remove all + // operands from its Phi instructions that reference unreachable blocks. + // If the block has no Phi instructions, this is a no-op. + block.ForEachPhiInst( + [&block, &reachable_blocks, this](ir::Instruction* phi) { + RemovePhiOperands(phi, reachable_blocks); + }); + } + + // Erase unreachable blocks. + for (auto ebi = func->begin(); ebi != func->end();) { + if (reachable_blocks.count(&*ebi) == 0) { + RemoveBlock(&ebi); + modified = true; + } else { + ++ebi; + } + } + + return modified; +} + +bool MemPass::CFGCleanup(ir::Function* func) { + bool modified = false; + modified |= RemoveUnreachableBlocks(func); + return modified; +} + +void MemPass::InitializeCFGCleanup(ir::Module* module) { + // Initialize block lookup map. + label2block_.clear(); + for (auto& fn : *module) { + for (auto& block : fn) { + label2block_[block.id()] = █ + + // Build a map between SSA names to the block they are defined in. + // TODO(dnovillo): This is expensive and unnecessary if ir::Instruction + // instances could figure out what basic block they belong to. Remove this + // once this is possible. + block.ForEachInst([this, &block](ir::Instruction* inst) { + uint32_t result_id = inst->result_id(); + if (result_id > 0) { + def_block_[result_id] = █ + } + }); + } + } +} + + } // namespace opt } // namespace spvtools diff --git a/source/opt/mem_pass.h b/source/opt/mem_pass.h index a553bf0fe..b5bb72d2f 100644 --- a/source/opt/mem_pass.h +++ b/source/opt/mem_pass.h @@ -80,6 +80,12 @@ class MemPass : public Pass { // Kill all name and decorate ops using |id| void KillNamesAndDecorates(uint32_t id); + // Kill all instructions in block |bp|. + void KillAllInsts(ir::BasicBlock* bp); + + // Collect all named or decorated ids in module + void FindNamedOrDecoratedIds(); + // Return true if any instruction loads from |varId| bool HasLoads(uint32_t varId) const; @@ -103,6 +109,12 @@ class MemPass : public Pass { // |loadInst|. void ReplaceAndDeleteLoad(ir::Instruction* loadInst, uint32_t replId); + // Initialize CFG Cleanup variables + void InitializeCFGCleanup(ir::Module* module); + + // Call all the cleanup helper functions on |func|. + bool CFGCleanup(ir::Function* func); + // Return true if |op| is supported decorate. inline bool IsNonTypeDecorate(uint32_t op) const { return (op == SpvOpDecorate || op == SpvOpDecorateId); @@ -187,9 +199,17 @@ class MemPass : public Pass { // succeeding block in structured order. bool IsLiveAfter(uint32_t var_id, uint32_t label) const; - // Collect all named or decorated ids in module. - void FindNamedOrDecoratedIds(); + // Remove all the unreachable basic blocks in |func|. + bool RemoveUnreachableBlocks(ir::Function* func); + // Remove the block pointed by the iterator |*bi|. This also removes + // all the instructions in the pointed-to block. + void RemoveBlock(ir::Function::iterator* bi); + + // Remove Phi operands in |phi| that are coming from blocks not in + // |reachable_blocks|. + void RemovePhiOperands(ir::Instruction* phi, + std::unordered_set reachable_blocks); // named or decorated ids std::unordered_set named_or_decorated_ids_; @@ -212,6 +232,14 @@ class MemPass : public Pass { // patching of the value for the loop back-edge. std::unordered_set phis_to_patch_; + // Map from block's label id to block. TODO(dnovillo): Basic blocks ought to + // have basic blocks in their pred/succ list. + std::unordered_map label2block_; + + // Map from an instruction result ID to the block that holds it. + // TODO(dnovillo): This would be unnecessary if ir::Instruction instances + // knew what basic block they belong to. + std::unordered_map def_block_; }; } // namespace opt diff --git a/source/opt/pass.cpp b/source/opt/pass.cpp index 0e9064594..a651b9025 100644 --- a/source/opt/pass.cpp +++ b/source/opt/pass.cpp @@ -131,7 +131,7 @@ uint32_t Pass::GetPointeeTypeId(const ir::Instruction* ptrInst) const { return ptrTypeInst->GetSingleWordInOperand(kTypePointerTypeIdInIdx); } -void Pass::ComputeStructuredOrder(ir::Function* func, +void Pass::ComputeStructuredOrder(ir::Function* func, ir::BasicBlock* root, std::list* order) { // Compute structured successors and do DFS ComputeStructuredSuccessors(func); @@ -147,7 +147,7 @@ void Pass::ComputeStructuredOrder(ir::Function* func, order->push_front(const_cast(b)); }; spvtools::CFA::DepthFirstTraversal( - &pseudo_entry_block_, get_structured_successors, ignore_block, post_order, + root, get_structured_successors, ignore_block, post_order, ignore_edge); } @@ -157,7 +157,8 @@ void Pass::ComputeStructuredSuccessors(ir::Function *func) { // If no predecessors in function, make successor to pseudo entry if (label2preds_[blk.id()].size() == 0) block2structured_succs_[&pseudo_entry_block_].push_back(&blk); - // If header, make merge block first successor. + // If header, make merge block first successor and continue block second + // successor if there is one. uint32_t cbid; const uint32_t mbid = MergeBlockIdIfAny(blk, &cbid); if (mbid != 0) { diff --git a/source/opt/pass.h b/source/opt/pass.h index 5797fa981..96b3529f5 100644 --- a/source/opt/pass.h +++ b/source/opt/pass.h @@ -133,18 +133,19 @@ class Pass { // Compute structured successors for function |func|. A block's structured // successors are the blocks it branches to together with its declared merge - // block if it has one. When order matters, the merge block always appears - // first. This assures correct depth first search in the presence of early - // returns and kills. If the successor vector contain duplicates if the merge - // block, they are safely ignored by DFS. TODO(dnovillo): This belongs in a - // CFG class. + // block and continue block if it has them. When order matters, the merge + // block and continue block always appear first. This assures correct depth + // first search in the presence of early returns and kills. If the successor + // vector contain duplicates of the merge or continue blocks, they are safely + // ignored by DFS. TODO(dnovillo): This belongs in a CFG class. void ComputeStructuredSuccessors(ir::Function* func); - // Compute structured block order for |func| into |structuredOrder|. This - // order has the property that dominators come before all blocks they - // dominate and merge blocks come after all blocks that are in the control - // constructs of their header. TODO(dnovillo): This belongs in a CFG class. - void ComputeStructuredOrder(ir::Function* func, + // Compute structured block order into |structuredOrder| for |func| starting + // at |root|. This order has the property that dominators come before all + // blocks they dominate and merge blocks come after all blocks that are in + // the control constructs of their header. TODO(dnovillo): This belongs in + // a CFG class. + void ComputeStructuredOrder(ir::Function* func, ir::BasicBlock* root, std::list* order); // Return type id for |ptrInst|'s pointee diff --git a/test/opt/aggressive_dead_code_elim_test.cpp b/test/opt/aggressive_dead_code_elim_test.cpp index 8e8b407f6..a16eb47bc 100644 --- a/test/opt/aggressive_dead_code_elim_test.cpp +++ b/test/opt/aggressive_dead_code_elim_test.cpp @@ -1388,6 +1388,750 @@ OpFunctionEnd assembly, assembly, true, true); } +TEST_F(AggressiveDCETest, EliminateDeadIfThenElse) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float d; + // if (BaseColor.x == 0) + // d = BaseColor.y; + // else + // d = BaseColor.z; + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %d "d" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%uint_2 = OpConstant %uint 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%21 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%uint_2 = OpConstant %uint 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%21 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %7 +%22 = OpLabel +%d = OpVariable %_ptr_Function_float Function +%23 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%24 = OpLoad %float %23 +%25 = OpFOrdEqual %bool %24 %float_0 +OpSelectionMerge %26 None +OpBranchConditional %25 %27 %28 +%27 = OpLabel +%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%30 = OpLoad %float %29 +OpStore %d %30 +OpBranch %26 +%28 = OpLabel +%31 = OpAccessChain %_ptr_Input_float %BaseColor %uint_2 +%32 = OpLoad %float %31 +OpStore %d %32 +OpBranch %26 +%26 = OpLabel +OpStore %OutColor %21 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %7 +%22 = OpLabel +OpBranch %26 +%26 = OpLabel +OpStore %OutColor %21 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, + predefs_after + func_after, + true, true); +} + +TEST_F(AggressiveDCETest, EliminateDeadIfThen) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float d; + // if (BaseColor.x == 0) + // d = BaseColor.y; + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %d "d" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%20 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%20 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %7 +%21 = OpLabel +%d = OpVariable %_ptr_Function_float Function +%22 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%23 = OpLoad %float %22 +%24 = OpFOrdEqual %bool %23 %float_0 +OpSelectionMerge %25 None +OpBranchConditional %24 %26 %25 +%26 = OpLabel +%27 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%28 = OpLoad %float %27 +OpStore %d %28 +OpBranch %25 +%25 = OpLabel +OpStore %OutColor %20 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %7 +%21 = OpLabel +OpBranch %25 +%25 = OpLabel +OpStore %OutColor %20 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, + predefs_after + func_after, + true, true); +} + +TEST_F(AggressiveDCETest, EliminateDeadIfThenElseNested) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float d; + // if (BaseColor.x == 0) + // if (BaseColor.y == 0) + // d = 0.0; + // else + // d = 0.25; + // else + // if (BaseColor.y == 0) + // d = 0.5; + // else + // d = 0.75; + // OutColor = vec4(1.0,1.0,1.0,1.0); + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %d "d" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%uint_1 = OpConstant %uint 1 +%_ptr_Function_float = OpTypePointer Function %float +%float_0_25 = OpConstant %float 0.25 +%float_0_5 = OpConstant %float 0.5 +%float_0_75 = OpConstant %float 0.75 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%uint_1 = OpConstant %uint 1 +%_ptr_Function_float = OpTypePointer Function %float +%float_0_25 = OpConstant %float 0.25 +%float_0_5 = OpConstant %float 0.5 +%float_0_75 = OpConstant %float 0.75 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%float_1 = OpConstant %float 1 +%23 = OpConstantComposite %v4float %float_1 %float_1 %float_1 %float_1 +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %7 +%24 = OpLabel +%d = OpVariable %_ptr_Function_float Function +%25 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%26 = OpLoad %float %25 +%27 = OpFOrdEqual %bool %26 %float_0 +OpSelectionMerge %28 None +OpBranchConditional %27 %29 %30 +%29 = OpLabel +%31 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%32 = OpLoad %float %31 +%33 = OpFOrdEqual %bool %32 %float_0 +OpSelectionMerge %34 None +OpBranchConditional %33 %35 %36 +%35 = OpLabel +OpStore %d %float_0 +OpBranch %34 +%36 = OpLabel +OpStore %d %float_0_25 +OpBranch %34 +%34 = OpLabel +OpBranch %28 +%30 = OpLabel +%37 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%38 = OpLoad %float %37 +%39 = OpFOrdEqual %bool %38 %float_0 +OpSelectionMerge %40 None +OpBranchConditional %39 %41 %42 +%41 = OpLabel +OpStore %d %float_0_5 +OpBranch %40 +%42 = OpLabel +OpStore %d %float_0_75 +OpBranch %40 +%40 = OpLabel +OpBranch %28 +%28 = OpLabel +OpStore %OutColor %23 +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %7 +%24 = OpLabel +OpBranch %28 +%28 = OpLabel +OpStore %OutColor %23 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, + predefs_after + func_after, + true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateLiveIfThenElse) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float t; + // if (BaseColor.x == 0) + // t = BaseColor.y; + // else + // t = BaseColor.z; + // OutColor = vec4(t); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %t "t" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%uint_2 = OpConstant %uint 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %7 +%20 = OpLabel +%t = OpVariable %_ptr_Function_float Function +%21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%22 = OpLoad %float %21 +%23 = OpFOrdEqual %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %26 +%25 = OpLabel +%27 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%28 = OpLoad %float %27 +OpStore %t %28 +OpBranch %24 +%26 = OpLabel +%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_2 +%30 = OpLoad %float %29 +OpStore %t %30 +OpBranch %24 +%24 = OpLabel +%31 = OpLoad %float %t +%32 = OpCompositeConstruct %v4float %31 %31 %31 %31 +OpStore %OutColor %32 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateLiveIfThenElseNested) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float t; + // if (BaseColor.x == 0) + // if (BaseColor.y == 0) + // t = 0.0; + // else + // t = 0.25; + // else + // if (BaseColor.y == 0) + // t = 0.5; + // else + // t = 0.75; + // OutColor = vec4(t); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %t "t" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%uint_1 = OpConstant %uint 1 +%_ptr_Function_float = OpTypePointer Function %float +%float_0_25 = OpConstant %float 0.25 +%float_0_5 = OpConstant %float 0.5 +%float_0_75 = OpConstant %float 0.75 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %7 +%22 = OpLabel +%t = OpVariable %_ptr_Function_float Function +%23 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%24 = OpLoad %float %23 +%25 = OpFOrdEqual %bool %24 %float_0 +OpSelectionMerge %26 None +OpBranchConditional %25 %27 %28 +%27 = OpLabel +%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%30 = OpLoad %float %29 +%31 = OpFOrdEqual %bool %30 %float_0 +OpSelectionMerge %32 None +OpBranchConditional %31 %33 %34 +%33 = OpLabel +OpStore %t %float_0 +OpBranch %32 +%34 = OpLabel +OpStore %t %float_0_25 +OpBranch %32 +%32 = OpLabel +OpBranch %26 +%28 = OpLabel +%35 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%36 = OpLoad %float %35 +%37 = OpFOrdEqual %bool %36 %float_0 +OpSelectionMerge %38 None +OpBranchConditional %37 %39 %40 +%39 = OpLabel +OpStore %t %float_0_5 +OpBranch %38 +%40 = OpLabel +OpStore %t %float_0_75 +OpBranch %38 +%38 = OpLabel +OpBranch %26 +%26 = OpLabel +%41 = OpLoad %float %t +%42 = OpCompositeConstruct %v4float %41 %41 %41 %41 +OpStore %OutColor %42 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, NoEliminateIfWithPhi) { + // Note: Assembly hand-optimized from GLSL + // + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float t; + // if (BaseColor.x == 0) + // t = 0.0; + // else + // t = 1.0; + // OutColor = vec4(t); + // } + + const std::string assembly = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%6 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%float_1 = OpConstant %float 1 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +%main = OpFunction %void None %6 +%18 = OpLabel +%19 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%20 = OpLoad %float %19 +%21 = OpFOrdEqual %bool %20 %float_0 +OpSelectionMerge %22 None +OpBranchConditional %21 %23 %24 +%23 = OpLabel +OpBranch %22 +%24 = OpLabel +OpBranch %22 +%22 = OpLabel +%25 = OpPhi %float %float_0 %23 %float_1 %24 +%26 = OpCompositeConstruct %v4float %25 %25 %25 %25 +OpStore %OutColor %26 +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + assembly, assembly, true, true); +} + +TEST_F(AggressiveDCETest, EliminateEntireFunctionBody) { + // #version 450 + // + // layout(location = 0) in vec4 BaseColor; + // layout(location = 0) out vec4 OutColor; + // + // void main() + // { + // float d; + // if (BaseColor.x == 0) + // d = BaseColor.y; + // else + // d = BaseColor.z; + // } + + const std::string predefs_before = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %d "d" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%uint_2 = OpConstant %uint 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string predefs_after = + R"(OpCapability Shader +%1 = OpExtInstImport "GLSL.std.450" +OpMemoryModel Logical GLSL450 +OpEntryPoint Fragment %main "main" %BaseColor %OutColor +OpExecutionMode %main OriginUpperLeft +OpSource GLSL 450 +OpName %main "main" +OpName %BaseColor "BaseColor" +OpName %OutColor "OutColor" +OpDecorate %BaseColor Location 0 +OpDecorate %OutColor Location 0 +%void = OpTypeVoid +%7 = OpTypeFunction %void +%float = OpTypeFloat 32 +%v4float = OpTypeVector %float 4 +%_ptr_Input_v4float = OpTypePointer Input %v4float +%BaseColor = OpVariable %_ptr_Input_v4float Input +%uint = OpTypeInt 32 0 +%uint_0 = OpConstant %uint 0 +%_ptr_Input_float = OpTypePointer Input %float +%float_0 = OpConstant %float 0 +%bool = OpTypeBool +%_ptr_Function_float = OpTypePointer Function %float +%uint_1 = OpConstant %uint 1 +%uint_2 = OpConstant %uint 2 +%_ptr_Output_v4float = OpTypePointer Output %v4float +%OutColor = OpVariable %_ptr_Output_v4float Output +)"; + + const std::string func_before = + R"(%main = OpFunction %void None %7 +%20 = OpLabel +%d = OpVariable %_ptr_Function_float Function +%21 = OpAccessChain %_ptr_Input_float %BaseColor %uint_0 +%22 = OpLoad %float %21 +%23 = OpFOrdEqual %bool %22 %float_0 +OpSelectionMerge %24 None +OpBranchConditional %23 %25 %26 +%25 = OpLabel +%27 = OpAccessChain %_ptr_Input_float %BaseColor %uint_1 +%28 = OpLoad %float %27 +OpStore %d %28 +OpBranch %24 +%26 = OpLabel +%29 = OpAccessChain %_ptr_Input_float %BaseColor %uint_2 +%30 = OpLoad %float %29 +OpStore %d %30 +OpBranch %24 +%24 = OpLabel +OpReturn +OpFunctionEnd +)"; + + const std::string func_after = + R"(%main = OpFunction %void None %7 +%20 = OpLabel +OpBranch %24 +%24 = OpLabel +OpReturn +OpFunctionEnd +)"; + + SinglePassRunAndCheck( + predefs_before + func_before, + predefs_after + func_after, + true, true); +} + // TODO(greg-lunarg): Add tests to verify handling of these cases: //