mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-26 05:10:05 +00:00
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:
parent
632e2068f3
commit
94bec26afe
@ -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*>{};
|
||||
|
@ -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_;
|
||||
|
||||
|
@ -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()] = █
|
||||
|
||||
// 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) {
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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|.
|
||||
|
@ -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()] = █
|
||||
|
||||
// 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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user