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.
This commit is contained in:
GregF 2017-10-17 16:33:43 -06:00 committed by David Neto
parent 632e2068f3
commit 94bec26afe
11 changed files with 1241 additions and 305 deletions

View File

@ -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 <stack>
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<ir::BasicBlock*>& structuredOrder) {
block2headerMerge_.clear();
block2headerBranch_.clear();
std::stack<ir::Instruction*> currentMergeInst;
std::stack<ir::Instruction*> currentBranchInst;
std::stack<uint32_t> 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<ir::Instruction> 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<ir::BasicBlock*> 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<bool> assume_branches_live;
std::stack<uint32_t> 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<ir::Instruction*>{};

View File

@ -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<ir::BasicBlock*>& 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<ir::Instruction*> worklist_;
// Map from block to the branch instruction in the header of the most
// immediate controlling structured if.
std::unordered_map<ir::BasicBlock*, ir::Instruction*> block2headerBranch_;
// Map from block to the merge instruction in the header of the most
// immediate controlling structured if.
std::unordered_map<ir::BasicBlock*, ir::Instruction*> block2headerMerge_;
// Map from instruction containing block
std::unordered_map<ir::Instruction*, ir::BasicBlock*> inst2block_;
// Map from block's label id to block.
std::unordered_map<uint32_t, ir::BasicBlock*> id2block_;
// Map from block to its structured successor blocks. See
// ComputeStructuredSuccessors() for definition.
std::unordered_map<const ir::BasicBlock*, std::vector<ir::BasicBlock*>>
block2structured_succs_;
// Store instructions to variables of private storage
std::vector<ir::Instruction*> private_stores_;

View File

@ -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<ir::BasicBlock*> reachable_blocks) {
std::vector<ir::Operand> 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<ir::BasicBlock*> reachable_blocks;
std::unordered_set<ir::BasicBlock*> visited_blocks;
std::queue<ir::BasicBlock*> 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()] = &block;
// 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] = &block;
}
});
}
}
InitializeCFGCleanup(module);
}
Pass::Status CFGCleanupPass::Process(ir::Module* module) {

View File

@ -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<ir::BasicBlock*> 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<uint32_t, ir::BasicBlock*> 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<uint32_t, ir::BasicBlock*> def_block_;
};
} // namespace opt

View File

@ -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<ir::BasicBlock*> structuredOrder;
ComputeStructuredOrder(func, &structuredOrder);
ComputeStructuredOrder(func, &*func->begin(), &structuredOrder);
ComputeBackEdges(structuredOrder);
std::unordered_set<ir::BasicBlock*> elimBlocks;
bool modified = false;

View File

@ -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|.

View File

@ -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<ir::BasicBlock*> 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<ir::BasicBlock*> reachable_blocks) {
std::vector<ir::Operand> 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<ir::BasicBlock*> reachable_blocks;
std::unordered_set<ir::BasicBlock*> visited_blocks;
std::queue<ir::BasicBlock*> 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()] = &block;
// 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] = &block;
}
});
}
}
}
} // namespace opt
} // namespace spvtools

View File

@ -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<ir::BasicBlock*> reachable_blocks);
// named or decorated ids
std::unordered_set<uint32_t> named_or_decorated_ids_;
@ -212,6 +232,14 @@ class MemPass : public Pass {
// patching of the value for the loop back-edge.
std::unordered_set<uint32_t> 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<uint32_t, ir::BasicBlock*> 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<uint32_t, ir::BasicBlock*> def_block_;
};
} // namespace opt

View File

@ -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<ir::BasicBlock*>* order) {
// Compute structured successors and do DFS
ComputeStructuredSuccessors(func);
@ -147,7 +147,7 @@ void Pass::ComputeStructuredOrder(ir::Function* func,
order->push_front(const_cast<ir::BasicBlock*>(b));
};
spvtools::CFA<ir::BasicBlock>::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) {

View File

@ -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<ir::BasicBlock*>* order);
// Return type id for |ptrInst|'s pointee

View File

@ -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<opt::AggressiveDCEPass>(
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<opt::AggressiveDCEPass>(
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<opt::AggressiveDCEPass>(
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<opt::AggressiveDCEPass>(
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<opt::AggressiveDCEPass>(
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<opt::AggressiveDCEPass>(
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<opt::AggressiveDCEPass>(
predefs_before + func_before,
predefs_after + func_after,
true, true);
}
// TODO(greg-lunarg): Add tests to verify handling of these cases:
//