mirror of
https://github.com/KhronosGroup/SPIRV-Tools
synced 2024-11-29 14:31:04 +00:00
Change merge return pass to handle structured cfg.
We are seeing shaders that have multiple returns in a functions. These functions must get inlined for legalization purposes; however, the inliner does not know how to inline functions that have multiple returns. The solution we will go with it to improve the merge return pass to handle structured control flow. Note that the merge return pass will assume the cfg has been cleanedup by dead branch elimination. Fixes #857.
This commit is contained in:
parent
1ef6b19260
commit
b3daa93b46
@ -453,16 +453,19 @@ Optimizer::PassToken CreateCFGCleanupPass();
|
||||
// that are not referenced.
|
||||
Optimizer::PassToken CreateDeadVariableEliminationPass();
|
||||
|
||||
// Create merge return pass.
|
||||
// This pass replaces all returns with unconditional branches to a new block
|
||||
// containing a return. If necessary, this new block will contain a PHI node to
|
||||
// select the correct return value.
|
||||
// create merge return pass.
|
||||
// changes functions that have multiple return statements so they have a single
|
||||
// return statement.
|
||||
//
|
||||
// This pass does not consider unreachable code, nor does it perform any other
|
||||
// optimizations.
|
||||
// for structured control flow it is assumed that the only unreachable blocks in
|
||||
// the function are trivial merge and continue blocks.
|
||||
//
|
||||
// This pass does not currently support structured control flow. It bails out if
|
||||
// the shader capability is detected.
|
||||
// a trivial merge block contains the label and an opunreachable instructions,
|
||||
// nothing else. a trivial continue block contain a label and an opbranch to
|
||||
// the header, nothing else.
|
||||
//
|
||||
// these conditions are guaranteed to be met after running dead-branch
|
||||
// elimination.
|
||||
Optimizer::PassToken CreateMergeReturnPass();
|
||||
|
||||
// Create value numbering pass.
|
||||
|
@ -199,5 +199,24 @@ std::ostream& operator<<(std::ostream& str, const BasicBlock& block) {
|
||||
return str;
|
||||
}
|
||||
|
||||
BasicBlock* BasicBlock::SplitBasicBlock(IRContext* context, uint32_t label_id,
|
||||
iterator iter) {
|
||||
assert(!insts_.empty());
|
||||
|
||||
BasicBlock* new_block = new BasicBlock(MakeUnique<Instruction>(
|
||||
context, SpvOpLabel, 0, label_id, std::initializer_list<ir::Operand>{}));
|
||||
|
||||
new_block->insts_.Splice(new_block->end(), &insts_, iter, end());
|
||||
new_block->SetParent(GetParent());
|
||||
|
||||
if (context->AreAnalysesValid(ir::IRContext::kAnalysisInstrToBlockMapping)) {
|
||||
new_block->ForEachInst([new_block, context](ir::Instruction* inst) {
|
||||
context->set_instr_block(inst, new_block);
|
||||
});
|
||||
}
|
||||
|
||||
return new_block;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
} // namespace spvtools
|
||||
|
@ -175,6 +175,12 @@ class BasicBlock {
|
||||
// indicated by |killLabel|.
|
||||
void KillAllInsts(bool killLabel);
|
||||
|
||||
// Splits this basic block into two. Returns a new basic block with label
|
||||
// |labelId| containing the instructions from |iter| onwards. Instructions
|
||||
// prior to |iter| remain in this basic block.
|
||||
BasicBlock* SplitBasicBlock(IRContext* context, uint32_t label_id,
|
||||
iterator iter);
|
||||
|
||||
private:
|
||||
// The enclosing function.
|
||||
Function* function_;
|
||||
@ -256,9 +262,16 @@ inline void BasicBlock::ForEachInst(
|
||||
|
||||
inline bool BasicBlock::WhileEachPhiInst(
|
||||
const std::function<bool(Instruction*)>& f, bool run_on_debug_line_insts) {
|
||||
for (auto& inst : insts_) {
|
||||
if (inst.opcode() != SpvOpPhi) break;
|
||||
if (!inst.WhileEachInst(f, run_on_debug_line_insts)) return false;
|
||||
if (insts_.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Instruction* inst = &insts_.front();
|
||||
while (inst != nullptr) {
|
||||
Instruction* next_instruction = inst->NextNode();
|
||||
if (inst->opcode() != SpvOpPhi) break;
|
||||
if (!inst->WhileEachInst(f, run_on_debug_line_insts)) return false;
|
||||
inst = next_instruction;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
#include "cfg.h"
|
||||
#include "cfa.h"
|
||||
#include "ir_builder.h"
|
||||
#include "ir_context.h"
|
||||
#include "module.h"
|
||||
|
||||
@ -113,15 +114,17 @@ void CFG::ComputeStructuredSuccessors(ir::Function* func) {
|
||||
// successor if there is one.
|
||||
uint32_t mbid = blk.MergeBlockIdIfAny();
|
||||
if (mbid != 0) {
|
||||
block2structured_succs_[&blk].push_back(id2block_[mbid]);
|
||||
block2structured_succs_[&blk].push_back(block(mbid));
|
||||
uint32_t cbid = blk.ContinueBlockIdIfAny();
|
||||
if (cbid != 0) block2structured_succs_[&blk].push_back(id2block_[cbid]);
|
||||
if (cbid != 0) {
|
||||
block2structured_succs_[&blk].push_back(block(cbid));
|
||||
}
|
||||
}
|
||||
|
||||
// Add true successors.
|
||||
const auto& const_blk = blk;
|
||||
const_blk.ForEachSuccessorLabel([&blk, this](const uint32_t sbid) {
|
||||
block2structured_succs_[&blk].push_back(id2block_[sbid]);
|
||||
block2structured_succs_[&blk].push_back(block(sbid));
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -139,5 +142,171 @@ void CFG::ComputePostOrderTraversal(BasicBlock* bb, vector<BasicBlock*>* order,
|
||||
order->push_back(bb);
|
||||
}
|
||||
|
||||
BasicBlock* CFG::SplitLoopHeader(ir::BasicBlock* bb) {
|
||||
assert(bb->GetLoopMergeInst() && "Expecting bb to be the header of a loop.");
|
||||
|
||||
Function* fn = bb->GetParent();
|
||||
IRContext* context = fn->context();
|
||||
|
||||
// Find the insertion point for the new bb.
|
||||
Function::iterator header_it = std::find_if(
|
||||
fn->begin(), fn->end(),
|
||||
[bb](BasicBlock& block_in_func) { return &block_in_func == bb; });
|
||||
assert(header_it != fn->end());
|
||||
|
||||
const std::vector<uint32_t>& pred = label2preds_[bb->id()];
|
||||
// Find the back edge
|
||||
ir::BasicBlock* latch_block = nullptr;
|
||||
{
|
||||
Function::iterator latch_block_iter = header_it;
|
||||
while (++latch_block_iter != fn->end()) {
|
||||
// If blocks are in the proper order, then the only branch that appears
|
||||
// after the header is the latch.
|
||||
if (std::find(pred.begin(), pred.end(), latch_block_iter->id()) !=
|
||||
pred.end()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(latch_block_iter != fn->end() && "Could not find the latch.");
|
||||
latch_block = &*latch_block_iter;
|
||||
}
|
||||
|
||||
RemoveSuccessorEdges(bb);
|
||||
|
||||
// Create the new header bb basic bb.
|
||||
// Leave the phi instructions behind.
|
||||
auto iter = bb->begin();
|
||||
while (iter->opcode() == SpvOpPhi) {
|
||||
++iter;
|
||||
}
|
||||
|
||||
std::unique_ptr<ir::BasicBlock> newBlock(
|
||||
bb->SplitBasicBlock(context, context->TakeNextId(), iter));
|
||||
|
||||
// Insert the new bb in the correct position
|
||||
auto insert_pos = header_it;
|
||||
++insert_pos;
|
||||
ir::BasicBlock* new_header = &*insert_pos.InsertBefore(std::move(newBlock));
|
||||
new_header->SetParent(fn);
|
||||
uint32_t new_header_id = new_header->id();
|
||||
context->AnalyzeDefUse(new_header->GetLabelInst());
|
||||
|
||||
// Update cfg
|
||||
RegisterBlock(new_header);
|
||||
|
||||
// Update bb mappings.
|
||||
context->set_instr_block(new_header->GetLabelInst(), new_header);
|
||||
new_header->ForEachInst([new_header, context](ir::Instruction* inst) {
|
||||
context->set_instr_block(inst, new_header);
|
||||
});
|
||||
|
||||
// Adjust the OpPhi instructions as needed.
|
||||
bb->ForEachPhiInst([latch_block, bb, new_header, context](Instruction* phi) {
|
||||
std::vector<uint32_t> preheader_phi_ops;
|
||||
std::vector<Operand> header_phi_ops;
|
||||
|
||||
// Idendify where the original inputs to original OpPhi belong: header or
|
||||
// preheader.
|
||||
for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) {
|
||||
uint32_t def_id = phi->GetSingleWordInOperand(i);
|
||||
uint32_t branch_id = phi->GetSingleWordInOperand(i + 1);
|
||||
if (branch_id == latch_block->id()) {
|
||||
header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {def_id}});
|
||||
header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {branch_id}});
|
||||
} else {
|
||||
preheader_phi_ops.push_back(def_id);
|
||||
preheader_phi_ops.push_back(branch_id);
|
||||
}
|
||||
}
|
||||
|
||||
// Create a phi instruction if and only if the preheader_phi_ops has more
|
||||
// than one pair.
|
||||
if (preheader_phi_ops.size() > 2) {
|
||||
opt::InstructionBuilder builder(
|
||||
context, &*bb->begin(),
|
||||
ir::IRContext::kAnalysisDefUse |
|
||||
ir::IRContext::kAnalysisInstrToBlockMapping);
|
||||
|
||||
ir::Instruction* new_phi =
|
||||
builder.AddPhi(phi->type_id(), preheader_phi_ops);
|
||||
|
||||
// Add the OpPhi to the header bb.
|
||||
header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {new_phi->result_id()}});
|
||||
header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {bb->id()}});
|
||||
} else {
|
||||
// An OpPhi with a single entry is just a copy. In this case use the same
|
||||
// instruction in the new header.
|
||||
header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {preheader_phi_ops[0]}});
|
||||
header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {bb->id()}});
|
||||
}
|
||||
|
||||
phi->RemoveFromList();
|
||||
std::unique_ptr<ir::Instruction> phi_owner(phi);
|
||||
phi->SetInOperands(std::move(header_phi_ops));
|
||||
new_header->begin()->InsertBefore(std::move(phi_owner));
|
||||
context->set_instr_block(phi, new_header);
|
||||
context->AnalyzeUses(phi);
|
||||
});
|
||||
|
||||
// Add a branch to the new header.
|
||||
opt::InstructionBuilder branch_builder(
|
||||
context, bb,
|
||||
ir::IRContext::kAnalysisDefUse |
|
||||
ir::IRContext::kAnalysisInstrToBlockMapping);
|
||||
bb->AddInstruction(MakeUnique<ir::Instruction>(
|
||||
context, SpvOpBranch, 0, 0,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {new_header->id()}}}));
|
||||
context->AnalyzeUses(bb->terminator());
|
||||
context->set_instr_block(bb->terminator(), bb);
|
||||
label2preds_[new_header->id()].push_back(bb->id());
|
||||
|
||||
// Update the latch to branch to the new header.
|
||||
latch_block->ForEachSuccessorLabel([bb, new_header_id](uint32_t* id) {
|
||||
if (*id == bb->id()) {
|
||||
*id = new_header_id;
|
||||
}
|
||||
});
|
||||
ir::Instruction* latch_branch = latch_block->terminator();
|
||||
context->AnalyzeUses(latch_branch);
|
||||
label2preds_[new_header->id()].push_back(latch_block->id());
|
||||
|
||||
auto& block_preds = label2preds_[bb->id()];
|
||||
auto latch_pos =
|
||||
std::find(block_preds.begin(), block_preds.end(), latch_block->id());
|
||||
assert(latch_pos != block_preds.end() && "The cfg was invalid.");
|
||||
block_preds.erase(latch_pos);
|
||||
|
||||
// Update the loop descriptors
|
||||
if (context->AreAnalysesValid(ir::IRContext::kAnalysisLoopAnalysis)) {
|
||||
LoopDescriptor* loop_desc = context->GetLoopDescriptor(bb->GetParent());
|
||||
Loop* loop = (*loop_desc)[bb->id()];
|
||||
|
||||
loop->AddBasicBlock(new_header_id);
|
||||
loop->SetHeaderBlock(new_header);
|
||||
loop_desc->SetBasicBlockToLoop(new_header_id, loop);
|
||||
|
||||
loop->RemoveBasicBlock(bb->id());
|
||||
loop->SetPreHeaderBlock(bb);
|
||||
|
||||
Loop* parent_loop = loop->GetParent();
|
||||
if (parent_loop != nullptr) {
|
||||
parent_loop->AddBasicBlock(bb->id());
|
||||
loop_desc->SetBasicBlockToLoop(bb->id(), parent_loop);
|
||||
} else {
|
||||
loop_desc->SetBasicBlockToLoop(bb->id(), nullptr);
|
||||
}
|
||||
}
|
||||
return new_header;
|
||||
}
|
||||
|
||||
unordered_set<BasicBlock*> CFG::FindReachableBlocks(BasicBlock* start) {
|
||||
std::unordered_set<BasicBlock*> reachable_blocks;
|
||||
ForEachBlockInReversePostOrder(start, [&reachable_blocks](BasicBlock* bb) {
|
||||
reachable_blocks.insert(bb);
|
||||
});
|
||||
return reachable_blocks;
|
||||
}
|
||||
|
||||
} // namespace ir
|
||||
} // namespace spvtools
|
||||
|
@ -35,6 +35,7 @@ class CFG {
|
||||
// Return the list of predecesors for basic block with label |blkid|.
|
||||
// TODO(dnovillo): Move this to ir::BasicBlock.
|
||||
const std::vector<uint32_t>& preds(uint32_t blk_id) const {
|
||||
assert(label2preds_.count(blk_id));
|
||||
return label2preds_.at(blk_id);
|
||||
}
|
||||
|
||||
@ -88,8 +89,7 @@ class CFG {
|
||||
void ForgetBlock(const ir::BasicBlock* blk) {
|
||||
id2block_.erase(blk->id());
|
||||
label2preds_.erase(blk->id());
|
||||
blk->ForEachSuccessorLabel(
|
||||
[blk, this](uint32_t succ_id) { RemoveEdge(blk->id(), succ_id); });
|
||||
RemoveSuccessorEdges(blk);
|
||||
}
|
||||
|
||||
void RemoveEdge(uint32_t pred_blk_id, uint32_t succ_blk_id) {
|
||||
@ -113,6 +113,21 @@ class CFG {
|
||||
// the basic block id |blk_id|.
|
||||
void RemoveNonExistingEdges(uint32_t blk_id);
|
||||
|
||||
// Remove all edges that leave |bb|.
|
||||
void RemoveSuccessorEdges(const ir::BasicBlock* bb) {
|
||||
bb->ForEachSuccessorLabel(
|
||||
[bb, this](uint32_t succ_id) { RemoveEdge(bb->id(), succ_id); });
|
||||
}
|
||||
|
||||
// Divides |block| into two basic blocks. The first block will have the same
|
||||
// id as |block| and will become a preheader for the loop. The other block
|
||||
// becomes is a new block that will be the new loop header.
|
||||
//
|
||||
// Returns a pointer to the new loop header.
|
||||
BasicBlock* SplitLoopHeader(ir::BasicBlock* bb);
|
||||
|
||||
std::unordered_set<BasicBlock*> FindReachableBlocks(BasicBlock* start);
|
||||
|
||||
private:
|
||||
using cbb_ptr = const ir::BasicBlock*;
|
||||
|
||||
|
@ -14,6 +14,8 @@
|
||||
|
||||
#include "def_use_manager.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include "log.h"
|
||||
#include "reflect.h"
|
||||
|
||||
@ -267,6 +269,20 @@ bool operator==(const DefUseManager& lhs, const DefUseManager& rhs) {
|
||||
}
|
||||
|
||||
if (lhs.id_to_users_ != rhs.id_to_users_) {
|
||||
for (auto p : lhs.id_to_users_) {
|
||||
if (rhs.id_to_users_.count(p) == 0) {
|
||||
std::cerr << p.first->PrettyPrint() << std::endl;
|
||||
std::cerr << p.second->PrettyPrint() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
for (auto p : rhs.id_to_users_) {
|
||||
if (lhs.id_to_users_.count(p) == 0) {
|
||||
std::cerr << p.first->PrettyPrint() << std::endl;
|
||||
std::cerr << p.second->PrettyPrint() << std::endl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -196,8 +196,6 @@ void BasicBlockSuccessorHelper<BBType>::CreateSuccessorMap(
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Technically, this is not needed, but it unifies
|
||||
// the handling of dominator and postdom tree later on.
|
||||
successors_[dummy_start_node].push_back(f.entry().get());
|
||||
predecessors_[f.entry().get()].push_back(
|
||||
const_cast<BasicBlock*>(dummy_start_node));
|
||||
@ -211,6 +209,13 @@ void BasicBlockSuccessorHelper<BBType>::CreateSuccessorMap(
|
||||
predecessors_[succ].push_back(&bb);
|
||||
});
|
||||
}
|
||||
/*
|
||||
for (BasicBlock& bb : f) {
|
||||
if (predecessors_[&bb].empty()) {
|
||||
predecessors_[&bb].push_back(const_cast<BasicBlock*>(dummy_start_node));
|
||||
successors_[dummy_start_node].push_back(&bb);
|
||||
}
|
||||
} */
|
||||
}
|
||||
}
|
||||
|
||||
@ -357,7 +362,6 @@ void DominatorTree::InitializeTree(const ir::Function* f, const ir::CFG& cfg) {
|
||||
first->parent_ = second;
|
||||
second->children_.push_back(first);
|
||||
}
|
||||
|
||||
ResetDFNumbering();
|
||||
}
|
||||
|
||||
|
@ -14,32 +14,29 @@
|
||||
|
||||
#include "function.h"
|
||||
|
||||
#include "make_unique.h"
|
||||
|
||||
#include <ostream>
|
||||
#include <iostream>
|
||||
|
||||
namespace spvtools {
|
||||
namespace ir {
|
||||
|
||||
Function* Function::Clone(IRContext* context) const {
|
||||
Function* Function::Clone(IRContext* ctx) const {
|
||||
Function* clone =
|
||||
new Function(std::unique_ptr<Instruction>(DefInst().Clone(context)));
|
||||
new Function(std::unique_ptr<Instruction>(DefInst().Clone(ctx)));
|
||||
clone->params_.reserve(params_.size());
|
||||
ForEachParam(
|
||||
[clone, context](const Instruction* inst) {
|
||||
clone->AddParameter(std::unique_ptr<Instruction>(inst->Clone(context)));
|
||||
[clone, ctx](const Instruction* inst) {
|
||||
clone->AddParameter(std::unique_ptr<Instruction>(inst->Clone(ctx)));
|
||||
},
|
||||
true);
|
||||
|
||||
clone->blocks_.reserve(blocks_.size());
|
||||
for (const auto& b : blocks_) {
|
||||
std::unique_ptr<BasicBlock> bb(b->Clone(context));
|
||||
std::unique_ptr<BasicBlock> bb(b->Clone(ctx));
|
||||
bb->SetParent(clone);
|
||||
clone->AddBasicBlock(std::move(bb));
|
||||
}
|
||||
|
||||
clone->SetFunctionEnd(
|
||||
std::unique_ptr<Instruction>(EndInst()->Clone(context)));
|
||||
clone->SetFunctionEnd(std::unique_ptr<Instruction>(EndInst()->Clone(ctx)));
|
||||
return clone;
|
||||
}
|
||||
|
||||
@ -77,6 +74,20 @@ void Function::ForEachParam(const std::function<void(const Instruction*)>& f,
|
||||
->ForEachInst(f, run_on_debug_line_insts);
|
||||
}
|
||||
|
||||
BasicBlock* Function::InsertBasicBlockAfter(
|
||||
std::unique_ptr<BasicBlock>&& new_block, BasicBlock* position) {
|
||||
for (auto bb_iter = begin(); bb_iter != end(); ++bb_iter) {
|
||||
if (&*bb_iter == position) {
|
||||
new_block->SetParent(this);
|
||||
++bb_iter;
|
||||
bb_iter = bb_iter.InsertBefore(std::move(new_block));
|
||||
return &*bb_iter;
|
||||
}
|
||||
}
|
||||
assert(false && "Could not find insertion point.");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& str, const Function& func) {
|
||||
func.ForEachInst([&str](const ir::Instruction* inst) {
|
||||
str << *inst;
|
||||
|
@ -27,6 +27,7 @@
|
||||
namespace spvtools {
|
||||
namespace ir {
|
||||
|
||||
class CFG;
|
||||
class IRContext;
|
||||
class Module;
|
||||
|
||||
@ -103,6 +104,12 @@ class Function {
|
||||
void ForEachParam(const std::function<void(const Instruction*)>& f,
|
||||
bool run_on_debug_line_insts = false) const;
|
||||
|
||||
// Returns the context of the current function.
|
||||
IRContext* context() const { return def_inst_->context(); }
|
||||
|
||||
BasicBlock* InsertBasicBlockAfter(std::unique_ptr<ir::BasicBlock>&& new_block,
|
||||
BasicBlock* position);
|
||||
|
||||
private:
|
||||
// The enclosing module.
|
||||
Module* module_;
|
||||
|
@ -128,7 +128,7 @@ bool IfConversion::CheckBlock(ir::BasicBlock* block,
|
||||
// for this block. If there is no common dominator, then we cannot transform
|
||||
// any phi in this basic block.
|
||||
*common = dominators->CommonDominator(inc0, inc1);
|
||||
if (!*common) return false;
|
||||
if (!*common || cfg()->IsPseudoEntryBlock(*common)) return false;
|
||||
ir::Instruction* branch = (*common)->terminator();
|
||||
if (branch->opcode() != SpvOpBranchConditional) return false;
|
||||
|
||||
|
@ -216,6 +216,11 @@ void InlinePass::GenInlineCode(
|
||||
// Post-call same-block op ids
|
||||
std::unordered_map<uint32_t, uint32_t> postCallSB;
|
||||
|
||||
// Invalidate the def-use chains. They are not kept up to date while
|
||||
// inlining. However, certain calls try to keep them up-to-date if they are
|
||||
// valid. These operations can fail.
|
||||
context()->InvalidateAnalyses(ir::IRContext::kAnalysisDefUse);
|
||||
|
||||
ir::Function* calleeFn = id2function_[call_inst_itr->GetSingleWordOperand(
|
||||
kSpvFunctionCallFunctionId)];
|
||||
|
||||
|
@ -195,17 +195,25 @@ bool IRContext::IsConsistent() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (AreAnalysesValid(kAnalysisInstrToBlockMapping)) {
|
||||
for (auto& func : *module()) {
|
||||
for (auto& block : func) {
|
||||
if (!block.WhileEachInst([this, &block](ir::Instruction* inst) {
|
||||
if (get_instr_block(inst) != &block) return false;
|
||||
if (get_instr_block(inst) != &block) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!CheckCFG()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -577,5 +585,56 @@ opt::PostDominatorAnalysis* IRContext::GetPostDominatorAnalysis(
|
||||
return &post_dominator_trees_[f];
|
||||
}
|
||||
|
||||
bool ir::IRContext::CheckCFG() {
|
||||
std::unordered_map<uint32_t, std::vector<uint32_t>> real_preds;
|
||||
if (!AreAnalysesValid(kAnalysisCFG)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (ir::Function& function : *module()) {
|
||||
for (const auto& bb : function) {
|
||||
bb.ForEachSuccessorLabel([&bb, &real_preds](const uint32_t lab_id) {
|
||||
real_preds[lab_id].push_back(bb.id());
|
||||
});
|
||||
}
|
||||
|
||||
for (auto& bb : function) {
|
||||
std::vector<uint32_t> preds = cfg()->preds(bb.id());
|
||||
std::vector<uint32_t> real = real_preds[bb.id()];
|
||||
std::sort(preds.begin(), preds.end());
|
||||
std::sort(real.begin(), real.end());
|
||||
|
||||
bool same = true;
|
||||
if (preds.size() != real.size()) {
|
||||
same = false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < real.size() && same; i++) {
|
||||
if (preds[i] != real[i]) {
|
||||
same = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!same) {
|
||||
std::cerr << "Predecessors for " << bb.id() << " are different:\n";
|
||||
|
||||
std::cerr << "Real:";
|
||||
for (uint32_t i : real) {
|
||||
std::cerr << ' ' << i;
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
|
||||
std::cerr << "Recorded:";
|
||||
for (uint32_t i : preds) {
|
||||
std::cerr << ' ' << i;
|
||||
}
|
||||
std::cerr << std::endl;
|
||||
}
|
||||
if (!same) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} // namespace ir
|
||||
} // namespace spvtools
|
||||
|
@ -479,6 +479,10 @@ class IRContext {
|
||||
// Remove |inst| from |id_to_name_| if it is in map.
|
||||
void RemoveFromIdToName(const Instruction* inst);
|
||||
|
||||
// Returns true if it is suppose to be valid but it is incorrect. Returns
|
||||
// true if the cfg is invalidated.
|
||||
bool CheckCFG();
|
||||
|
||||
// The SPIR-V syntax context containing grammar tables for opcodes and
|
||||
// operands.
|
||||
spv_context syntax_context_;
|
||||
@ -733,10 +737,16 @@ void IRContext::AddAnnotationInst(std::unique_ptr<Instruction>&& a) {
|
||||
|
||||
void IRContext::AddType(std::unique_ptr<Instruction>&& t) {
|
||||
module()->AddType(std::move(t));
|
||||
if (AreAnalysesValid(kAnalysisDefUse)) {
|
||||
get_def_use_mgr()->AnalyzeInstDef(&*(--types_values_end()));
|
||||
}
|
||||
}
|
||||
|
||||
void IRContext::AddGlobalValue(std::unique_ptr<Instruction>&& v) {
|
||||
module()->AddGlobalValue(std::move(v));
|
||||
if (AreAnalysesValid(kAnalysisDefUse)) {
|
||||
get_def_use_mgr()->AnalyzeInstDef(&*(--types_values_end()));
|
||||
}
|
||||
}
|
||||
|
||||
void IRContext::AddFunction(std::unique_ptr<Function>&& f) {
|
||||
|
@ -256,98 +256,8 @@ bool Loop::IsBasicBlockInLoopSlow(const BasicBlock* bb) {
|
||||
BasicBlock* Loop::GetOrCreatePreHeaderBlock() {
|
||||
if (loop_preheader_) return loop_preheader_;
|
||||
|
||||
Function* fn = loop_header_->GetParent();
|
||||
// Find the insertion point for the preheader.
|
||||
Function::iterator header_it =
|
||||
std::find_if(fn->begin(), fn->end(),
|
||||
[this](BasicBlock& bb) { return &bb == loop_header_; });
|
||||
assert(header_it != fn->end());
|
||||
|
||||
// Create the preheader basic block.
|
||||
loop_preheader_ = &*header_it.InsertBefore(std::unique_ptr<ir::BasicBlock>(
|
||||
new ir::BasicBlock(std::unique_ptr<ir::Instruction>(new ir::Instruction(
|
||||
context_, SpvOpLabel, 0, context_->TakeNextId(), {})))));
|
||||
loop_preheader_->SetParent(fn);
|
||||
uint32_t loop_preheader_id = loop_preheader_->id();
|
||||
|
||||
// Redirect the branches and patch the phi:
|
||||
// - For each phi instruction in the header:
|
||||
// - If the header has only 1 out-of-loop incoming branch:
|
||||
// - Change the incomning branch to be the preheader.
|
||||
// - If the header has more than 1 out-of-loop incoming branch:
|
||||
// - Create a new phi in the preheader, gathering all out-of-loops
|
||||
// incoming values;
|
||||
// - Patch the header phi instruction to use the preheader phi
|
||||
// instruction;
|
||||
// - Redirect all edges coming from outside the loop to the preheader.
|
||||
opt::InstructionBuilder builder(
|
||||
context_, loop_preheader_,
|
||||
ir::IRContext::kAnalysisDefUse |
|
||||
ir::IRContext::kAnalysisInstrToBlockMapping);
|
||||
// Patch all the phi instructions.
|
||||
loop_header_->ForEachPhiInst([&builder, this](Instruction* phi) {
|
||||
std::vector<uint32_t> preheader_phi_ops;
|
||||
std::vector<uint32_t> header_phi_ops;
|
||||
for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) {
|
||||
uint32_t def_id = phi->GetSingleWordInOperand(i);
|
||||
uint32_t branch_id = phi->GetSingleWordInOperand(i + 1);
|
||||
if (IsInsideLoop(branch_id)) {
|
||||
header_phi_ops.push_back(def_id);
|
||||
header_phi_ops.push_back(branch_id);
|
||||
} else {
|
||||
preheader_phi_ops.push_back(def_id);
|
||||
preheader_phi_ops.push_back(branch_id);
|
||||
}
|
||||
}
|
||||
|
||||
Instruction* preheader_insn_def = nullptr;
|
||||
// Create a phi instruction if and only if the preheader_phi_ops has more
|
||||
// than one pair.
|
||||
if (preheader_phi_ops.size() > 2)
|
||||
preheader_insn_def = builder.AddPhi(phi->type_id(), preheader_phi_ops);
|
||||
else
|
||||
preheader_insn_def =
|
||||
context_->get_def_use_mgr()->GetDef(preheader_phi_ops[0]);
|
||||
// Build the new incoming edge.
|
||||
header_phi_ops.push_back(preheader_insn_def->result_id());
|
||||
header_phi_ops.push_back(loop_preheader_->id());
|
||||
// Rewrite operands of the header's phi instruction.
|
||||
uint32_t idx = 0;
|
||||
for (; idx < header_phi_ops.size(); idx++)
|
||||
phi->SetInOperand(idx, {header_phi_ops[idx]});
|
||||
// Remove extra operands, from last to first (more efficient).
|
||||
for (uint32_t j = phi->NumInOperands() - 1; j >= idx; j--)
|
||||
phi->RemoveInOperand(j);
|
||||
});
|
||||
// Branch from the preheader to the header.
|
||||
builder.AddBranch(loop_header_->id());
|
||||
|
||||
// Redirect all out of loop branches to the header to the preheader.
|
||||
CFG* cfg = context_->cfg();
|
||||
cfg->RegisterBlock(loop_preheader_);
|
||||
for (uint32_t pred_id : cfg->preds(loop_header_->id())) {
|
||||
if (pred_id == loop_preheader_->id()) continue;
|
||||
if (IsInsideLoop(pred_id)) continue;
|
||||
BasicBlock* pred = cfg->block(pred_id);
|
||||
pred->ForEachSuccessorLabel([this, loop_preheader_id](uint32_t* id) {
|
||||
if (*id == loop_header_->id()) *id = loop_preheader_id;
|
||||
});
|
||||
cfg->AddEdge(pred_id, loop_preheader_id);
|
||||
}
|
||||
// Delete predecessors that are no longer predecessors of the loop header.
|
||||
cfg->RemoveNonExistingEdges(loop_header_->id());
|
||||
// Update the loop descriptors.
|
||||
if (HasParent()) {
|
||||
GetParent()->AddBasicBlock(loop_preheader_);
|
||||
context_->GetLoopDescriptor(fn)->SetBasicBlockToLoop(loop_preheader_->id(),
|
||||
GetParent());
|
||||
}
|
||||
|
||||
context_->InvalidateAnalysesExceptFor(
|
||||
builder.GetPreservedAnalysis() |
|
||||
ir::IRContext::Analysis::kAnalysisLoopAnalysis |
|
||||
ir::IRContext::kAnalysisCFG);
|
||||
|
||||
loop_header_ = cfg->SplitLoopHeader(loop_header_);
|
||||
return loop_preheader_;
|
||||
}
|
||||
|
||||
|
@ -15,7 +15,10 @@
|
||||
#include "merge_return_pass.h"
|
||||
|
||||
#include "instruction.h"
|
||||
#include "ir_builder.h"
|
||||
#include "ir_context.h"
|
||||
#include "make_unique.h"
|
||||
#include "reflect.h"
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
@ -23,110 +26,635 @@ namespace opt {
|
||||
Pass::Status MergeReturnPass::Process(ir::IRContext* irContext) {
|
||||
InitializeProcessing(irContext);
|
||||
|
||||
// TODO (alanbaker): Support structured control flow. Bail out in the
|
||||
// meantime.
|
||||
if (context()->get_feature_mgr()->HasCapability(SpvCapabilityShader))
|
||||
return Status::SuccessWithoutChange;
|
||||
|
||||
bool modified = false;
|
||||
bool is_shader =
|
||||
context()->get_feature_mgr()->HasCapability(SpvCapabilityShader);
|
||||
for (auto& function : *get_module()) {
|
||||
std::vector<ir::BasicBlock*> returnBlocks = CollectReturnBlocks(&function);
|
||||
modified |= MergeReturnBlocks(&function, returnBlocks);
|
||||
std::vector<ir::BasicBlock*> return_blocks = CollectReturnBlocks(&function);
|
||||
if (return_blocks.size() <= 1) continue;
|
||||
|
||||
function_ = &function;
|
||||
return_flag_ = nullptr;
|
||||
return_value_ = nullptr;
|
||||
final_return_block_ = nullptr;
|
||||
|
||||
modified = true;
|
||||
if (is_shader) {
|
||||
ProcessStructured(&function, return_blocks);
|
||||
} else {
|
||||
MergeReturnBlocks(&function, return_blocks);
|
||||
}
|
||||
}
|
||||
|
||||
return modified ? Status::SuccessWithChange : Status::SuccessWithoutChange;
|
||||
}
|
||||
|
||||
void MergeReturnPass::ProcessStructured(
|
||||
ir::Function* function, const std::vector<ir::BasicBlock*>& return_blocks) {
|
||||
std::list<ir::BasicBlock*> order;
|
||||
cfg()->ComputeStructuredOrder(function, &*function->begin(), &order);
|
||||
|
||||
// Create the new return block
|
||||
CreateReturnBlock();
|
||||
|
||||
// Create the return
|
||||
CreateReturn(final_return_block_);
|
||||
|
||||
cfg()->RegisterBlock(final_return_block_);
|
||||
|
||||
state_.clear();
|
||||
state_.emplace_back(nullptr, nullptr);
|
||||
for (auto block : order) {
|
||||
if (cfg()->IsPseudoEntryBlock(block) || cfg()->IsPseudoExitBlock(block)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto blockId = block->GetLabelInst()->result_id();
|
||||
if (blockId == CurrentState().CurrentMergeId()) {
|
||||
// Pop the current state as we've hit the merge
|
||||
state_.pop_back();
|
||||
}
|
||||
|
||||
ProcessStructuredBlock(block);
|
||||
|
||||
// Generate state for next block
|
||||
if (ir::Instruction* mergeInst = block->GetMergeInst()) {
|
||||
ir::Instruction* loopMergeInst = block->GetLoopMergeInst();
|
||||
if (!loopMergeInst) loopMergeInst = state_.back().LoopMergeInst();
|
||||
state_.emplace_back(loopMergeInst, mergeInst);
|
||||
}
|
||||
}
|
||||
|
||||
// Predicate successors of the original return blocks as necessary.
|
||||
PredicateBlocks(return_blocks);
|
||||
|
||||
// We have not kept the dominator tree up-to-date.
|
||||
// Invalidate it at this point to make sure it will be rebuilt.
|
||||
context()->RemoveDominatorAnalysis(function);
|
||||
AddNewPhiNodes();
|
||||
}
|
||||
|
||||
void MergeReturnPass::CreateReturnBlock() {
|
||||
// Create a label for the new return block
|
||||
std::unique_ptr<ir::Instruction> return_label(
|
||||
new ir::Instruction(context(), SpvOpLabel, 0u, TakeNextId(), {}));
|
||||
|
||||
// Create the new basic block
|
||||
std::unique_ptr<ir::BasicBlock> return_block(
|
||||
new ir::BasicBlock(std::move(return_label)));
|
||||
function_->AddBasicBlock(std::move(return_block));
|
||||
final_return_block_ = &*(--function_->end());
|
||||
context()->AnalyzeDefUse(final_return_block_->GetLabelInst());
|
||||
context()->set_instr_block(final_return_block_->GetLabelInst(),
|
||||
final_return_block_);
|
||||
final_return_block_->SetParent(function_);
|
||||
}
|
||||
|
||||
void MergeReturnPass::CreateReturn(ir::BasicBlock* block) {
|
||||
AddReturnValue();
|
||||
|
||||
if (return_value_) {
|
||||
// Load and return the final return value
|
||||
uint32_t loadId = TakeNextId();
|
||||
block->AddInstruction(MakeUnique<ir::Instruction>(
|
||||
context(), SpvOpLoad, function_->type_id(), loadId,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {return_value_->result_id()}}}));
|
||||
ir::Instruction* var_inst = block->terminator();
|
||||
context()->AnalyzeDefUse(var_inst);
|
||||
context()->set_instr_block(var_inst, block);
|
||||
|
||||
block->AddInstruction(MakeUnique<ir::Instruction>(
|
||||
context(), SpvOpReturnValue, 0, 0,
|
||||
std::initializer_list<ir::Operand>{{SPV_OPERAND_TYPE_ID, {loadId}}}));
|
||||
context()->AnalyzeDefUse(block->terminator());
|
||||
context()->set_instr_block(block->terminator(), block);
|
||||
} else {
|
||||
block->AddInstruction(MakeUnique<ir::Instruction>(context(), SpvOpReturn));
|
||||
context()->AnalyzeDefUse(block->terminator());
|
||||
context()->set_instr_block(block->terminator(), block);
|
||||
}
|
||||
}
|
||||
|
||||
void MergeReturnPass::ProcessStructuredBlock(ir::BasicBlock* block) {
|
||||
SpvOp tail_opcode = block->tail()->opcode();
|
||||
if (tail_opcode == SpvOpReturn || tail_opcode == SpvOpReturnValue) {
|
||||
if (!return_flag_) {
|
||||
AddReturnFlag();
|
||||
}
|
||||
}
|
||||
|
||||
if (tail_opcode == SpvOpReturn || tail_opcode == SpvOpReturnValue ||
|
||||
tail_opcode == SpvOpUnreachable) {
|
||||
if (CurrentState().InLoop()) {
|
||||
// Can always break out of innermost loop
|
||||
BranchToBlock(block, CurrentState().LoopMergeId());
|
||||
} else if (CurrentState().InStructuredFlow()) {
|
||||
BranchToBlock(block, CurrentState().CurrentMergeId());
|
||||
} else {
|
||||
BranchToBlock(block, final_return_block_->id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MergeReturnPass::BranchToBlock(ir::BasicBlock* block, uint32_t target) {
|
||||
if (block->tail()->opcode() == SpvOpReturn ||
|
||||
block->tail()->opcode() == SpvOpReturnValue) {
|
||||
RecordReturned(block);
|
||||
RecordReturnValue(block);
|
||||
}
|
||||
|
||||
// Fix up existing phi nodes.
|
||||
//
|
||||
// A new edge is being added from |block| to |target|, so go through
|
||||
// |target|'s phi nodes add an undef incoming value for |block|.
|
||||
ir::BasicBlock* target_block = context()->get_instr_block(target);
|
||||
target_block->ForEachPhiInst([this, block](ir::Instruction* inst) {
|
||||
uint32_t undefId = Type2Undef(inst->type_id());
|
||||
inst->AddOperand({SPV_OPERAND_TYPE_ID, {undefId}});
|
||||
inst->AddOperand({SPV_OPERAND_TYPE_ID, {block->id()}});
|
||||
context()->UpdateDefUse(inst);
|
||||
});
|
||||
|
||||
const auto& target_pred = cfg()->preds(target);
|
||||
if (target_pred.size() == 1) {
|
||||
MarkForNewPhiNodes(target_block,
|
||||
context()->get_instr_block(target_pred[0]));
|
||||
}
|
||||
|
||||
ir::Instruction* return_inst = block->terminator();
|
||||
return_inst->SetOpcode(SpvOpBranch);
|
||||
return_inst->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {target}}});
|
||||
context()->get_def_use_mgr()->AnalyzeInstDefUse(return_inst);
|
||||
cfg()->AddEdge(block->id(), target);
|
||||
}
|
||||
|
||||
void MergeReturnPass::CreatePhiNodesForInst(ir::BasicBlock* merge_block,
|
||||
uint32_t predecessor,
|
||||
ir::Instruction& inst) {
|
||||
opt::DominatorAnalysis* dom_tree =
|
||||
context()->GetDominatorAnalysis(merge_block->GetParent(), *cfg());
|
||||
ir::BasicBlock* inst_bb = context()->get_instr_block(&inst);
|
||||
|
||||
if (inst.result_id() != 0) {
|
||||
std::vector<ir::Instruction*> users_to_update;
|
||||
context()->get_def_use_mgr()->ForEachUser(
|
||||
&inst,
|
||||
[&users_to_update, &dom_tree, inst_bb, this](ir::Instruction* user) {
|
||||
if (!dom_tree->Dominates(inst_bb, context()->get_instr_block(user))) {
|
||||
users_to_update.push_back(user);
|
||||
}
|
||||
});
|
||||
|
||||
if (users_to_update.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// There is at least one values that needs to be replaced.
|
||||
// First create the OpPhi instruction.
|
||||
InstructionBuilder builder(context(), &*merge_block->begin(),
|
||||
ir::IRContext::kAnalysisDefUse);
|
||||
uint32_t undef_id = Type2Undef(inst.type_id());
|
||||
std::vector<uint32_t> phi_operands;
|
||||
|
||||
// Add the operands for the defining instructions.
|
||||
phi_operands.push_back(inst.result_id());
|
||||
phi_operands.push_back(predecessor);
|
||||
|
||||
// Add undef from all other blocks.
|
||||
std::vector<uint32_t> preds = cfg()->preds(merge_block->id());
|
||||
for (uint32_t pred_id : preds) {
|
||||
if (pred_id != predecessor) {
|
||||
phi_operands.push_back(undef_id);
|
||||
phi_operands.push_back(pred_id);
|
||||
}
|
||||
}
|
||||
|
||||
ir::Instruction* new_phi = builder.AddPhi(inst.type_id(), phi_operands);
|
||||
uint32_t result_of_phi = new_phi->result_id();
|
||||
|
||||
// Update all of the users to use the result of the new OpPhi.
|
||||
for (ir::Instruction* user : users_to_update) {
|
||||
user->ForEachInId([&inst, result_of_phi](uint32_t* id) {
|
||||
if (*id == inst.result_id()) {
|
||||
*id = result_of_phi;
|
||||
}
|
||||
});
|
||||
context()->AnalyzeUses(user);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MergeReturnPass::PredicateBlocks(
|
||||
const std::vector<ir::BasicBlock*>& return_blocks) {
|
||||
// The CFG is being modified as the function proceeds so avoid caching
|
||||
// successors.
|
||||
std::vector<ir::BasicBlock*> stack;
|
||||
auto add_successors = [this, &stack](ir::BasicBlock* block) {
|
||||
const ir::BasicBlock* const_block =
|
||||
const_cast<const ir::BasicBlock*>(block);
|
||||
const_block->ForEachSuccessorLabel([this, &stack](const uint32_t idx) {
|
||||
stack.push_back(context()->get_instr_block(idx));
|
||||
});
|
||||
};
|
||||
|
||||
std::unordered_set<ir::BasicBlock*> seen;
|
||||
std::unordered_set<ir::BasicBlock*> predicated;
|
||||
for (auto b : return_blocks) {
|
||||
seen.clear();
|
||||
add_successors(b);
|
||||
|
||||
while (!stack.empty()) {
|
||||
ir::BasicBlock* block = stack.back();
|
||||
assert(block);
|
||||
stack.pop_back();
|
||||
|
||||
if (block == b) continue;
|
||||
if (block == final_return_block_) continue;
|
||||
if (!seen.insert(block).second) continue;
|
||||
if (!predicated.insert(block).second) continue;
|
||||
|
||||
// Skip structured subgraphs.
|
||||
ir::BasicBlock* next = block;
|
||||
while (next->GetMergeInst()) {
|
||||
next = context()->get_instr_block(next->MergeBlockIdIfAny());
|
||||
}
|
||||
add_successors(next);
|
||||
PredicateBlock(block, next, &predicated);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MergeReturnPass::RequiresPredication(
|
||||
const ir::BasicBlock* block, const ir::BasicBlock* tail_block) const {
|
||||
// This is intentionally conservative.
|
||||
// TODO(alanbaker): re-visit this when more performance data is available.
|
||||
if (block != tail_block) return true;
|
||||
|
||||
bool requires_predicate = false;
|
||||
block->ForEachInst([&requires_predicate](const ir::Instruction* inst) {
|
||||
if (inst->opcode() != SpvOpPhi && inst->opcode() != SpvOpLabel &&
|
||||
!ir::IsTerminatorInst(inst->opcode())) {
|
||||
requires_predicate = true;
|
||||
}
|
||||
});
|
||||
return requires_predicate;
|
||||
}
|
||||
|
||||
void MergeReturnPass::PredicateBlock(
|
||||
ir::BasicBlock* block, ir::BasicBlock* tail_block,
|
||||
std::unordered_set<ir::BasicBlock*>* predicated) {
|
||||
if (!RequiresPredication(block, tail_block)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure the cfg is build here. If we don't then it becomes very hard to
|
||||
// know which new blocks need to be updated.
|
||||
context()->BuildInvalidAnalyses(ir::IRContext::kAnalysisCFG);
|
||||
|
||||
// When predicating, be aware of whether this block is a header block, a merge
|
||||
// block or both.
|
||||
//
|
||||
// If this block is a merge block, ensure the appropriate header stays
|
||||
// up-to-date with any changes (i.e. points to the pre-header).
|
||||
//
|
||||
// If this block is a header block, predicate the entire structured subgraph.
|
||||
// This can act recursively.
|
||||
|
||||
// If |block| is a loop head, then the back edge must jump to the original
|
||||
// code, not the new header.
|
||||
if (block->GetLoopMergeInst()) {
|
||||
cfg()->SplitLoopHeader(block);
|
||||
}
|
||||
|
||||
// Leave the phi instructions behind.
|
||||
auto iter = block->begin();
|
||||
while (iter->opcode() == SpvOpPhi) {
|
||||
++iter;
|
||||
}
|
||||
|
||||
// Forget about the edges leaving block. They will be removed.
|
||||
cfg()->RemoveSuccessorEdges(block);
|
||||
|
||||
std::unique_ptr<ir::BasicBlock> new_block(
|
||||
block->SplitBasicBlock(context(), TakeNextId(), iter));
|
||||
ir::BasicBlock* old_body =
|
||||
function_->InsertBasicBlockAfter(std::move(new_block), block);
|
||||
predicated->insert(old_body);
|
||||
|
||||
if (tail_block == block) {
|
||||
tail_block = old_body;
|
||||
}
|
||||
|
||||
const ir::BasicBlock* const_old_body =
|
||||
static_cast<const ir::BasicBlock*>(old_body);
|
||||
const_old_body->ForEachSuccessorLabel(
|
||||
[old_body, block, this](const uint32_t label) {
|
||||
ir::BasicBlock* target_bb = context()->get_instr_block(label);
|
||||
if (MarkedSinglePred(target_bb) == block) {
|
||||
MarkForNewPhiNodes(target_bb, old_body);
|
||||
}
|
||||
});
|
||||
|
||||
std::unique_ptr<ir::BasicBlock> new_merge_block(new ir::BasicBlock(
|
||||
MakeUnique<ir::Instruction>(context(), SpvOpLabel, 0, TakeNextId(),
|
||||
std::initializer_list<ir::Operand>{})));
|
||||
|
||||
ir::BasicBlock* new_merge =
|
||||
function_->InsertBasicBlockAfter(std::move(new_merge_block), tail_block);
|
||||
predicated->insert(new_merge);
|
||||
new_merge->SetParent(function_);
|
||||
|
||||
// Register the new labels.
|
||||
get_def_use_mgr()->AnalyzeInstDef(old_body->GetLabelInst());
|
||||
context()->set_instr_block(old_body->GetLabelInst(), old_body);
|
||||
get_def_use_mgr()->AnalyzeInstDef(new_merge->GetLabelInst());
|
||||
context()->set_instr_block(new_merge->GetLabelInst(), new_merge);
|
||||
|
||||
// Move the tail branch into the new merge and fix the mapping. If a single
|
||||
// block is being predicated then its branch was moved to the old body
|
||||
// previously.
|
||||
std::unique_ptr<ir::Instruction> inst;
|
||||
ir::Instruction* i = tail_block->terminator();
|
||||
cfg()->RemoveSuccessorEdges(tail_block);
|
||||
get_def_use_mgr()->ClearInst(i);
|
||||
inst.reset(std::move(i));
|
||||
inst->RemoveFromList();
|
||||
new_merge->end().InsertBefore(std::move(inst));
|
||||
get_def_use_mgr()->AnalyzeInstUse(new_merge->terminator());
|
||||
context()->set_instr_block(new_merge->terminator(), new_merge);
|
||||
|
||||
// Add a branch to the new merge. If we jumped multiple blocks, the branch is
|
||||
// added to tail_block, otherwise the branch belongs in old_body.
|
||||
tail_block->AddInstruction(MakeUnique<ir::Instruction>(
|
||||
context(), SpvOpBranch, 0, 0,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {new_merge->id()}}}));
|
||||
get_def_use_mgr()->AnalyzeInstUse(tail_block->terminator());
|
||||
context()->set_instr_block(tail_block->terminator(), tail_block);
|
||||
|
||||
// Within the new header we need the following:
|
||||
// 1. Load of the return status flag
|
||||
// 2. Declare the merge block
|
||||
// 3. Branch to new merge (true) or old body (false)
|
||||
|
||||
// 1. Load of the return status flag
|
||||
analysis::Bool bool_type;
|
||||
uint32_t bool_id = context()->get_type_mgr()->GetId(&bool_type);
|
||||
assert(bool_id != 0);
|
||||
uint32_t load_id = TakeNextId();
|
||||
block->AddInstruction(MakeUnique<ir::Instruction>(
|
||||
context(), SpvOpLoad, bool_id, load_id,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {return_flag_->result_id()}}}));
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(block->terminator());
|
||||
context()->set_instr_block(block->terminator(), block);
|
||||
|
||||
// 2. Declare the merge block
|
||||
block->AddInstruction(
|
||||
MakeUnique<ir::Instruction>(context(), SpvOpSelectionMerge, 0, 0,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {new_merge->id()}},
|
||||
{SPV_OPERAND_TYPE_SELECTION_CONTROL,
|
||||
{SpvSelectionControlMaskNone}}}));
|
||||
get_def_use_mgr()->AnalyzeInstUse(block->terminator());
|
||||
context()->set_instr_block(block->terminator(), block);
|
||||
|
||||
// 3. Branch to new merge (true) or old body (false)
|
||||
block->AddInstruction(MakeUnique<ir::Instruction>(
|
||||
context(), SpvOpBranchConditional, 0, 0,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {load_id}},
|
||||
{SPV_OPERAND_TYPE_ID, {new_merge->id()}},
|
||||
{SPV_OPERAND_TYPE_ID, {old_body->id()}}}));
|
||||
get_def_use_mgr()->AnalyzeInstUse(block->terminator());
|
||||
context()->set_instr_block(block->terminator(), block);
|
||||
|
||||
assert(old_body->begin() != old_body->end());
|
||||
assert(block->begin() != block->end());
|
||||
assert(new_merge->begin() != new_merge->end());
|
||||
|
||||
// Update the cfg
|
||||
cfg()->AddEdges(block);
|
||||
cfg()->RegisterBlock(old_body);
|
||||
if (old_body != tail_block) {
|
||||
cfg()->AddEdges(tail_block);
|
||||
}
|
||||
cfg()->RegisterBlock(new_merge);
|
||||
MarkForNewPhiNodes(new_merge, tail_block);
|
||||
}
|
||||
|
||||
void MergeReturnPass::RecordReturned(ir::BasicBlock* block) {
|
||||
if (block->tail()->opcode() != SpvOpReturn &&
|
||||
block->tail()->opcode() != SpvOpReturnValue)
|
||||
return;
|
||||
|
||||
assert(return_flag_ && "Did not generate the return flag variable.");
|
||||
|
||||
if (!constant_true_) {
|
||||
analysis::Bool temp;
|
||||
const analysis::Bool* bool_type =
|
||||
context()->get_type_mgr()->GetRegisteredType(&temp)->AsBool();
|
||||
|
||||
analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
|
||||
const analysis::Constant* true_const =
|
||||
const_mgr->GetConstant(bool_type, {true});
|
||||
constant_true_ = const_mgr->GetDefiningInstruction(true_const);
|
||||
context()->UpdateDefUse(constant_true_);
|
||||
}
|
||||
|
||||
std::unique_ptr<ir::Instruction> return_store(new ir::Instruction(
|
||||
context(), SpvOpStore, 0, 0,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {return_flag_->result_id()}},
|
||||
{SPV_OPERAND_TYPE_ID, {constant_true_->result_id()}}}));
|
||||
|
||||
ir::Instruction* store_inst =
|
||||
&*block->tail().InsertBefore(std::move(return_store));
|
||||
context()->set_instr_block(store_inst, block);
|
||||
context()->AnalyzeDefUse(store_inst);
|
||||
}
|
||||
|
||||
void MergeReturnPass::RecordReturnValue(ir::BasicBlock* block) {
|
||||
auto terminator = *block->tail();
|
||||
if (terminator.opcode() != SpvOpReturnValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(return_value_ &&
|
||||
"Did not generate the variable to hold the return value.");
|
||||
|
||||
std::unique_ptr<ir::Instruction> value_store(new ir::Instruction(
|
||||
context(), SpvOpStore, 0, 0,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_ID, {return_value_->result_id()}},
|
||||
{SPV_OPERAND_TYPE_ID, {terminator.GetSingleWordInOperand(0u)}}}));
|
||||
|
||||
ir::Instruction* store_inst =
|
||||
&*block->tail().InsertBefore(std::move(value_store));
|
||||
context()->set_instr_block(store_inst, block);
|
||||
context()->AnalyzeDefUse(store_inst);
|
||||
}
|
||||
|
||||
void MergeReturnPass::AddReturnValue() {
|
||||
if (return_value_) return;
|
||||
|
||||
uint32_t return_type_id = function_->type_id();
|
||||
if (get_def_use_mgr()->GetDef(return_type_id)->opcode() == SpvOpTypeVoid)
|
||||
return;
|
||||
|
||||
uint32_t return_ptr_type = context()->get_type_mgr()->FindPointerToType(
|
||||
return_type_id, SpvStorageClassFunction);
|
||||
|
||||
uint32_t var_id = TakeNextId();
|
||||
std::unique_ptr<ir::Instruction> returnValue(new ir::Instruction(
|
||||
context(), SpvOpVariable, return_ptr_type, var_id,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}}}));
|
||||
|
||||
auto insert_iter = function_->begin()->begin();
|
||||
insert_iter.InsertBefore(std::move(returnValue));
|
||||
ir::BasicBlock* entry_block = &*function_->begin();
|
||||
return_value_ = &*entry_block->begin();
|
||||
context()->AnalyzeDefUse(return_value_);
|
||||
context()->set_instr_block(return_value_, entry_block);
|
||||
}
|
||||
|
||||
void MergeReturnPass::AddReturnFlag() {
|
||||
if (return_flag_) return;
|
||||
|
||||
analysis::TypeManager* type_mgr = context()->get_type_mgr();
|
||||
analysis::ConstantManager* const_mgr = context()->get_constant_mgr();
|
||||
|
||||
analysis::Bool temp;
|
||||
uint32_t bool_id = type_mgr->GetTypeInstruction(&temp);
|
||||
analysis::Bool* bool_type = type_mgr->GetType(bool_id)->AsBool();
|
||||
|
||||
const analysis::Constant* false_const =
|
||||
const_mgr->GetConstant(bool_type, {false});
|
||||
uint32_t const_false_id =
|
||||
const_mgr->GetDefiningInstruction(false_const)->result_id();
|
||||
|
||||
uint32_t bool_ptr_id =
|
||||
type_mgr->FindPointerToType(bool_id, SpvStorageClassFunction);
|
||||
|
||||
uint32_t var_id = TakeNextId();
|
||||
std::unique_ptr<ir::Instruction> returnFlag(new ir::Instruction(
|
||||
context(), SpvOpVariable, bool_ptr_id, var_id,
|
||||
std::initializer_list<ir::Operand>{
|
||||
{SPV_OPERAND_TYPE_STORAGE_CLASS, {SpvStorageClassFunction}},
|
||||
{SPV_OPERAND_TYPE_ID, {const_false_id}}}));
|
||||
|
||||
auto insert_iter = function_->begin()->begin();
|
||||
|
||||
insert_iter.InsertBefore(std::move(returnFlag));
|
||||
ir::BasicBlock* entry_block = &*function_->begin();
|
||||
return_flag_ = &*entry_block->begin();
|
||||
context()->AnalyzeDefUse(return_flag_);
|
||||
context()->set_instr_block(return_flag_, entry_block);
|
||||
}
|
||||
|
||||
std::vector<ir::BasicBlock*> MergeReturnPass::CollectReturnBlocks(
|
||||
ir::Function* function) {
|
||||
std::vector<ir::BasicBlock*> returnBlocks;
|
||||
std::vector<ir::BasicBlock*> return_blocks;
|
||||
for (auto& block : *function) {
|
||||
ir::Instruction& terminator = *block.tail();
|
||||
if (terminator.opcode() == SpvOpReturn ||
|
||||
terminator.opcode() == SpvOpReturnValue) {
|
||||
returnBlocks.push_back(&block);
|
||||
return_blocks.push_back(&block);
|
||||
}
|
||||
}
|
||||
|
||||
return returnBlocks;
|
||||
return return_blocks;
|
||||
}
|
||||
|
||||
bool MergeReturnPass::MergeReturnBlocks(
|
||||
ir::Function* function, const std::vector<ir::BasicBlock*>& returnBlocks) {
|
||||
if (returnBlocks.size() <= 1) {
|
||||
void MergeReturnPass::MergeReturnBlocks(
|
||||
ir::Function* function, const std::vector<ir::BasicBlock*>& return_blocks) {
|
||||
if (return_blocks.size() <= 1) {
|
||||
// No work to do.
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<ir::Instruction*> uses_to_update;
|
||||
|
||||
// Create a label for the new return block
|
||||
std::unique_ptr<ir::Instruction> returnLabel(
|
||||
new ir::Instruction(context(), SpvOpLabel, 0u, TakeNextId(), {}));
|
||||
uint32_t returnId = returnLabel->result_id();
|
||||
|
||||
// Create the new basic block.
|
||||
std::unique_ptr<ir::BasicBlock> returnBlock(
|
||||
new ir::BasicBlock(std::move(returnLabel)));
|
||||
function->AddBasicBlock(std::move(returnBlock));
|
||||
ir::Function::iterator retBlockIter = --function->end();
|
||||
|
||||
// Register the definition of the return and mark it to update its uses.
|
||||
get_def_use_mgr()->AnalyzeInstDef(retBlockIter->GetLabelInst());
|
||||
uses_to_update.push_back(retBlockIter->GetLabelInst());
|
||||
|
||||
CreateReturnBlock();
|
||||
uint32_t return_id = final_return_block_->id();
|
||||
auto ret_block_iter = --function->end();
|
||||
// Create the PHI for the merged block (if necessary).
|
||||
// Create new return.
|
||||
std::vector<ir::Operand> phiOps;
|
||||
for (auto block : returnBlocks) {
|
||||
std::vector<ir::Operand> phi_ops;
|
||||
for (auto block : return_blocks) {
|
||||
if (block->tail()->opcode() == SpvOpReturnValue) {
|
||||
phiOps.push_back(
|
||||
phi_ops.push_back(
|
||||
{SPV_OPERAND_TYPE_ID, {block->tail()->GetSingleWordInOperand(0u)}});
|
||||
phiOps.push_back({SPV_OPERAND_TYPE_ID, {block->id()}});
|
||||
phi_ops.push_back({SPV_OPERAND_TYPE_ID, {block->id()}});
|
||||
}
|
||||
}
|
||||
|
||||
if (!phiOps.empty()) {
|
||||
if (!phi_ops.empty()) {
|
||||
// Need a PHI node to select the correct return value.
|
||||
uint32_t phiResultId = TakeNextId();
|
||||
uint32_t phiTypeId = function->type_id();
|
||||
std::unique_ptr<ir::Instruction> phiInst(new ir::Instruction(
|
||||
context(), SpvOpPhi, phiTypeId, phiResultId, phiOps));
|
||||
retBlockIter->AddInstruction(std::move(phiInst));
|
||||
ir::BasicBlock::iterator phiIter = retBlockIter->tail();
|
||||
uint32_t phi_result_id = TakeNextId();
|
||||
uint32_t phi_type_id = function->type_id();
|
||||
std::unique_ptr<ir::Instruction> phi_inst(new ir::Instruction(
|
||||
context(), SpvOpPhi, phi_type_id, phi_result_id, phi_ops));
|
||||
ret_block_iter->AddInstruction(std::move(phi_inst));
|
||||
ir::BasicBlock::iterator phiIter = ret_block_iter->tail();
|
||||
|
||||
std::unique_ptr<ir::Instruction> returnInst(
|
||||
std::unique_ptr<ir::Instruction> return_inst(
|
||||
new ir::Instruction(context(), SpvOpReturnValue, 0u, 0u,
|
||||
{{SPV_OPERAND_TYPE_ID, {phiResultId}}}));
|
||||
retBlockIter->AddInstruction(std::move(returnInst));
|
||||
ir::BasicBlock::iterator ret = retBlockIter->tail();
|
||||
{{SPV_OPERAND_TYPE_ID, {phi_result_id}}}));
|
||||
ret_block_iter->AddInstruction(std::move(return_inst));
|
||||
ir::BasicBlock::iterator ret = ret_block_iter->tail();
|
||||
|
||||
// Register the phi def and mark instructions for use updates.
|
||||
get_def_use_mgr()->AnalyzeInstDef(&*phiIter);
|
||||
uses_to_update.push_back(&*ret);
|
||||
uses_to_update.push_back(&*phiIter);
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(&*phiIter);
|
||||
get_def_use_mgr()->AnalyzeInstDef(&*ret);
|
||||
} else {
|
||||
std::unique_ptr<ir::Instruction> returnInst(
|
||||
std::unique_ptr<ir::Instruction> return_inst(
|
||||
new ir::Instruction(context(), SpvOpReturn));
|
||||
retBlockIter->AddInstruction(std::move(returnInst));
|
||||
ret_block_iter->AddInstruction(std::move(return_inst));
|
||||
}
|
||||
|
||||
// Replace returns with branches
|
||||
for (auto block : returnBlocks) {
|
||||
context()->KillInst(&*block->tail());
|
||||
std::unique_ptr<ir::Instruction> new_instruction(
|
||||
new ir::Instruction(context(), SpvOpBranch, 0,
|
||||
0, {{SPV_OPERAND_TYPE_ID, {returnId}}}));
|
||||
block->AddInstruction(std::move(new_instruction));
|
||||
uses_to_update.push_back(&*block->tail());
|
||||
uses_to_update.push_back(block->GetLabelInst());
|
||||
for (auto block : return_blocks) {
|
||||
context()->ForgetUses(block->terminator());
|
||||
block->tail()->SetOpcode(SpvOpBranch);
|
||||
block->tail()->ReplaceOperands({{SPV_OPERAND_TYPE_ID, {return_id}}});
|
||||
get_def_use_mgr()->AnalyzeInstUse(block->terminator());
|
||||
get_def_use_mgr()->AnalyzeInstUse(block->GetLabelInst());
|
||||
}
|
||||
|
||||
for (auto& inst : uses_to_update) {
|
||||
context()->AnalyzeUses(inst);
|
||||
}
|
||||
get_def_use_mgr()->AnalyzeInstDefUse(ret_block_iter->GetLabelInst());
|
||||
}
|
||||
|
||||
return true;
|
||||
void MergeReturnPass::AddNewPhiNodes() {
|
||||
opt::DominatorAnalysis* dom_tree =
|
||||
context()->GetDominatorAnalysis(function_, *cfg());
|
||||
std::list<ir::BasicBlock*> order;
|
||||
cfg()->ComputeStructuredOrder(function_, &*function_->begin(), &order);
|
||||
|
||||
for (ir::BasicBlock* bb : order) {
|
||||
AddNewPhiNodes(bb, new_merge_nodes_[bb],
|
||||
dom_tree->ImmediateDominator(bb)->id());
|
||||
}
|
||||
}
|
||||
|
||||
void MergeReturnPass::AddNewPhiNodes(ir::BasicBlock* bb, ir::BasicBlock* pred,
|
||||
uint32_t header_id) {
|
||||
opt::DominatorAnalysis* dom_tree =
|
||||
context()->GetDominatorAnalysis(function_, *cfg());
|
||||
// Insert as a stopping point. We do not have to add anything in the block or
|
||||
// above because the header dominates |bb|.
|
||||
|
||||
ir::BasicBlock* current_bb = pred;
|
||||
while (current_bb != nullptr && current_bb->id() != header_id) {
|
||||
for (ir::Instruction& inst : *current_bb) {
|
||||
CreatePhiNodesForInst(bb, pred->id(), inst);
|
||||
}
|
||||
current_bb = dom_tree->ImmediateDominator(current_bb);
|
||||
}
|
||||
}
|
||||
|
||||
void MergeReturnPass::MarkForNewPhiNodes(ir::BasicBlock* block,
|
||||
ir::BasicBlock* single_original_pred) {
|
||||
new_merge_nodes_[block] = single_original_pred;
|
||||
}
|
||||
|
||||
} // namespace opt
|
||||
|
@ -17,36 +17,288 @@
|
||||
|
||||
#include "basic_block.h"
|
||||
#include "function.h"
|
||||
#include "pass.h"
|
||||
#include "mem_pass.h"
|
||||
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace spvtools {
|
||||
namespace opt {
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Handling Structured Control Flow:
|
||||
*
|
||||
* Structured control flow guarantees that the CFG will reconverge at a given
|
||||
* point (the merge block). Within structured control flow, all blocks must be
|
||||
* post-dominated by the merge block, except return blocks and break blocks.
|
||||
* A break block is a block that branches to the innermost loop's merge block.
|
||||
*
|
||||
* Beyond this, we further assume that all unreachable blocks have been
|
||||
* cleanedup. This means that the only unreachable blocks are those necessary
|
||||
* for valid structured control flow.
|
||||
*
|
||||
* Algorithm:
|
||||
*
|
||||
* If a return is encountered, it should record that: i) the function has
|
||||
* "returned" and ii) the value of the return. The return should be replaced
|
||||
* with a branch. If current block is not within structured control flow, this
|
||||
* is the final return. This block should branch to the new return block (its
|
||||
* direct successor). If the current block is within structured control flow,
|
||||
* the branch destination should be the innermost loop's merge (if it exists)
|
||||
* or the merge block of the immediate structured control flow. If the merge
|
||||
* block produces any live values it will need to be predicated. While the merge
|
||||
* is nested in structured control flow, the predication path should branch to
|
||||
* the next best merge block available. Once structured control flow has been
|
||||
* exited, remaining blocks must be predicated with new structured control flow
|
||||
* (OpSelectionMerge). These should be nested correctly in case of straight line
|
||||
* branching to reach the final return block.
|
||||
*
|
||||
* In the final return block, the return value should be loaded and returned.
|
||||
* Memory promotion passes should be able to promote the newly introduced
|
||||
* variables ("has returned" and "return value").
|
||||
*
|
||||
* Predicating the Final Merge:
|
||||
*
|
||||
* At each merge block predication needs to be introduced (optimization: only if
|
||||
* that block produces value live beyond it). This needs to be done carefully.
|
||||
* The merge block should be split into multiple blocks.
|
||||
*
|
||||
* 1 (header)
|
||||
* / \
|
||||
* (ret) 2 3 (merge)
|
||||
*
|
||||
* ||
|
||||
* \/
|
||||
*
|
||||
* 1 (header)
|
||||
* / \
|
||||
* 2 |
|
||||
* \ /
|
||||
* 3 (merge for 1, new header)
|
||||
* / \
|
||||
* | 3 (old body)
|
||||
* \ /
|
||||
* (ret) 4 (new merge)
|
||||
*
|
||||
* In the above (simple) example, the return originally in |2| is passed through
|
||||
* the merge. That merge is predicated such that the old body of the block is
|
||||
* the else branch. The branch condition is based on the value of the "has
|
||||
* returned" variable. In more complicated examples (blocks between |1| and
|
||||
* |3|), the SSA would need to fixed up due the newly reconvergent path at the
|
||||
* merge for |1|. Assuming |3| originally was also a return block, the old body
|
||||
* of |3| should also store the return value for that case. The return value in
|
||||
* |4| just requires loading the return value variable.
|
||||
*
|
||||
******************************************************************************/
|
||||
|
||||
// Documented in optimizer.hpp
|
||||
class MergeReturnPass : public Pass {
|
||||
class MergeReturnPass : public MemPass {
|
||||
public:
|
||||
MergeReturnPass() = default;
|
||||
const char* name() const override { return "merge-return-pass"; }
|
||||
MergeReturnPass()
|
||||
: function_(nullptr),
|
||||
return_flag_(nullptr),
|
||||
return_value_(nullptr),
|
||||
constant_true_(nullptr),
|
||||
final_return_block_(nullptr) {}
|
||||
|
||||
const char* name() const override { return "merge-return"; }
|
||||
Status Process(ir::IRContext*) override;
|
||||
|
||||
ir::IRContext::Analysis GetPreservedAnalyses() override {
|
||||
return ir::IRContext::kAnalysisDefUse;
|
||||
// return ir::IRContext::kAnalysisDefUse;
|
||||
return ir::IRContext::kAnalysisNone;
|
||||
}
|
||||
|
||||
private:
|
||||
// This class is used to store the a loop merge instruction and a selection
|
||||
// merge instruction. The intended use is that is represent the inner most
|
||||
// contain selection construct and the inner most loop construct.
|
||||
class StructuredControlState {
|
||||
public:
|
||||
StructuredControlState(ir::Instruction* loop, ir::Instruction* merge)
|
||||
: loop_merge_(loop), current_merge_(merge) {}
|
||||
|
||||
StructuredControlState(const StructuredControlState&) = default;
|
||||
|
||||
bool InLoop() const { return loop_merge_; }
|
||||
bool InStructuredFlow() const { return CurrentMergeId() != 0; }
|
||||
|
||||
uint32_t CurrentMergeId() const {
|
||||
return current_merge_ ? current_merge_->GetSingleWordInOperand(0u) : 0u;
|
||||
}
|
||||
|
||||
uint32_t CurrentMergeHeader() const {
|
||||
return current_merge_ ? current_merge_->context()
|
||||
->get_instr_block(current_merge_)
|
||||
->id()
|
||||
: 0;
|
||||
}
|
||||
|
||||
uint32_t LoopMergeId() const {
|
||||
return loop_merge_ ? loop_merge_->GetSingleWordInOperand(0u) : 0u;
|
||||
}
|
||||
|
||||
uint32_t CurrentLoopHeader() const {
|
||||
return loop_merge_
|
||||
? loop_merge_->context()->get_instr_block(loop_merge_)->id()
|
||||
: 0;
|
||||
}
|
||||
|
||||
ir::Instruction* LoopMergeInst() const { return loop_merge_; }
|
||||
|
||||
private:
|
||||
ir::Instruction* loop_merge_;
|
||||
ir::Instruction* current_merge_;
|
||||
};
|
||||
|
||||
// Returns all BasicBlocks terminated by OpReturn or OpReturnValue in
|
||||
// |function|.
|
||||
std::vector<ir::BasicBlock*> CollectReturnBlocks(ir::Function* function);
|
||||
|
||||
// Returns |true| if returns were merged, |false| otherwise.
|
||||
//
|
||||
// Creates a new basic block with a single return. If |function| returns a
|
||||
// value, a phi node is created to select the correct value to return.
|
||||
// Replaces old returns with an unconditional branch to the new block.
|
||||
bool MergeReturnBlocks(ir::Function* function,
|
||||
void MergeReturnBlocks(ir::Function* function,
|
||||
const std::vector<ir::BasicBlock*>& returnBlocks);
|
||||
|
||||
// Merges the return instruction in |function| so that it has a single return
|
||||
// statement. It is assumed that |function| has structured control flow, and
|
||||
// that |return_blocks| is a list of all of the basic blocks in |function|
|
||||
// that have a return.
|
||||
void ProcessStructured(ir::Function* function,
|
||||
const std::vector<ir::BasicBlock*>& return_blocks);
|
||||
|
||||
// Changes an OpReturn* or OpUnreachable instruction at the end of |block|
|
||||
// into a store to |return_flag_|, a store to |return_value_| (if necessary),
|
||||
// and a branch to the appropriate merge block.
|
||||
//
|
||||
// Is is assumed that |AddReturnValue| have already been called to created the
|
||||
// variable to store a return value if there is one.
|
||||
//
|
||||
// Note this will break the semantics. To fix this, PredicateBlock will have
|
||||
// to be called on the merge block the branch targets.
|
||||
void ProcessStructuredBlock(ir::BasicBlock* block);
|
||||
|
||||
// Creates a variable used to store whether or not the control flow has
|
||||
// traversed a block that used to have a return. A pointer to the instruction
|
||||
// declaring the variable is stored in |return_flag_|.
|
||||
void AddReturnFlag();
|
||||
|
||||
// Creates the variable used to store the return value when passing through
|
||||
// a block that use to contain an OpReturnValue.
|
||||
void AddReturnValue();
|
||||
|
||||
// Adds a store that stores true to |return_flag_| immediately before the
|
||||
// terminator of |block|. It is assumed that |AddReturnFlag| has already been
|
||||
// called.
|
||||
void RecordReturned(ir::BasicBlock* block);
|
||||
|
||||
// Adds an instruction that stores the value being returned in the
|
||||
// OpReturnValue in |block|. The value is stored to |return_value_|, and the
|
||||
// store is placed before the OpReturnValue.
|
||||
//
|
||||
// If |block| does not contain an OpReturnValue, then this function has no
|
||||
// effect. If |block| contains an OpReturnValue, then |AddReturnValue| must
|
||||
// have already been called to create the variable to store to.
|
||||
void RecordReturnValue(ir::BasicBlock* block);
|
||||
|
||||
// Adds an unconditional branch in |block| that branches to |target|. It also
|
||||
// adds stores to |return_flag_| and |return_value_| as needed.
|
||||
// |AddReturnFlag| and |AddReturnValue| must have already been called.
|
||||
void BranchToBlock(ir::BasicBlock* block, uint32_t target);
|
||||
|
||||
// Returns true if we need to pridicate |block| where |tail_block| is the
|
||||
// merge point. (See |PredicateBlocks|). There is no need to predicate if
|
||||
// there is no code that could be executed.
|
||||
bool RequiresPredication(const ir::BasicBlock* block,
|
||||
const ir::BasicBlock* tail_block) const;
|
||||
|
||||
// For every basic block that is reachable from a basic block in
|
||||
// |return_blocks|, extra code is added to jump around any code that should
|
||||
// not be executed because the original code would have already returned. This
|
||||
// involves adding new selections constructs to jump around these
|
||||
// instructions.
|
||||
void PredicateBlocks(const std::vector<ir::BasicBlock*>& return_blocks);
|
||||
|
||||
// Add the predication code (see |PredicateBlocks|) to |tail_block| if it
|
||||
// requires predication. |tail_block| and any new blocks that are known to
|
||||
// not require predication will be added to |predicated|.
|
||||
void PredicateBlock(ir::BasicBlock* block, ir::BasicBlock* tail_block,
|
||||
std::unordered_set<ir::BasicBlock*>* predicated);
|
||||
|
||||
// Add an |OpReturn| or |OpReturnValue| to the end of |block|. If an
|
||||
// |OpReturnValue| is needed, the return value is loaded from |return_value_|.
|
||||
void CreateReturn(ir::BasicBlock* block);
|
||||
|
||||
// Creates a block at the end of the function that will become the single
|
||||
// return block at the end of the pass.
|
||||
void CreateReturnBlock();
|
||||
|
||||
// Creates a Phi node in |merge_block| for the result of |inst| coming from
|
||||
// |predecessor|. Any uses of the result of |inst| that are no longer
|
||||
// dominated by |inst|, are replaced with the result of the new |OpPhi|
|
||||
// instruction.
|
||||
void CreatePhiNodesForInst(ir::BasicBlock* merge_block, uint32_t predecessor,
|
||||
ir::Instruction& inst);
|
||||
|
||||
// Traverse the nodes in |new_merge_nodes_|, and adds the OpPhi instructions
|
||||
// that are needed to make the code correct. It is assumed that at this point
|
||||
// there are no unreachable blocks in the control flow graph.
|
||||
void AddNewPhiNodes();
|
||||
|
||||
// Creates any new phi nodes that are needed in |bb| now that |pred| is no
|
||||
// longer the only block that preceedes |bb|. |header_id| is the id of the
|
||||
// basic block for the loop or selection construct that merges at |bb|.
|
||||
void AddNewPhiNodes(ir::BasicBlock* bb, ir::BasicBlock* pred,
|
||||
uint32_t header_id);
|
||||
|
||||
// Saves |block| to a list of basic block that will require OpPhi nodes to be
|
||||
// added by calling |AddNewPhiNodes|. It is assumed that |block| used to have
|
||||
// a single predecessor, |single_original_pred|, but now has more.
|
||||
void MarkForNewPhiNodes(ir::BasicBlock* block,
|
||||
ir::BasicBlock* single_original_pred);
|
||||
|
||||
// Return the original single predcessor of |block| if it was flagged as
|
||||
// having a single predecessor. |nullptr| is returned otherwise.
|
||||
ir::BasicBlock* MarkedSinglePred(ir::BasicBlock* block) {
|
||||
auto it = new_merge_nodes_.find(block);
|
||||
if (it != new_merge_nodes_.end()) {
|
||||
return it->second;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
StructuredControlState& CurrentState() { return state_.back(); }
|
||||
|
||||
// A stack used to keep track of the innermost contain loop and selection
|
||||
// constructs.
|
||||
std::vector<StructuredControlState> state_;
|
||||
|
||||
// The current function being transformed.
|
||||
ir::Function* function_;
|
||||
|
||||
// The |OpVariable| instruction defining a boolean variable used to keep track
|
||||
// of whether or not the function is trying to return.
|
||||
ir::Instruction* return_flag_;
|
||||
|
||||
// The |OpVariable| instruction defining a variabled to used to keep track of
|
||||
// the value that was returned when passing through a block that use to
|
||||
// contain an |OpReturnValue|.
|
||||
ir::Instruction* return_value_;
|
||||
|
||||
// The instruction defining the boolean constant true.
|
||||
ir::Instruction* constant_true_;
|
||||
|
||||
// The basic block that is suppose to become the contain the only return value
|
||||
// after processing the current function.
|
||||
ir::BasicBlock* final_return_block_;
|
||||
// This map contains the set of nodes that use to have a single predcessor,
|
||||
// but now have more. They will need new OpPhi nodes. For each of the nodes,
|
||||
// it is mapped to it original single predcessor. It is assumed there are no
|
||||
// values that will need a phi on the new edges.
|
||||
std::unordered_map<ir::BasicBlock*, ir::BasicBlock*> new_merge_nodes_;
|
||||
};
|
||||
|
||||
} // namespace opt
|
||||
|
@ -90,8 +90,12 @@ Optimizer& Optimizer::RegisterPass(PassToken&& p) {
|
||||
// or enable more copy propagation.
|
||||
Optimizer& Optimizer::RegisterLegalizationPasses() {
|
||||
return
|
||||
// Make sure uses and definitions are in the same function.
|
||||
RegisterPass(CreateInlineExhaustivePass())
|
||||
// Remove unreachable block so that merge return works.
|
||||
RegisterPass(CreateDeadBranchElimPass())
|
||||
// Merge the returns so we can inline.
|
||||
.RegisterPass(CreateMergeReturnPass())
|
||||
// Make sure uses and definitions are in the same function.
|
||||
.RegisterPass(CreateInlineExhaustivePass())
|
||||
// Make private variable function scope
|
||||
.RegisterPass(CreateEliminateDeadFunctionsPass())
|
||||
.RegisterPass(CreatePrivateToLocalPass())
|
||||
|
@ -491,6 +491,11 @@ void TypeManager::RegisterType(uint32_t id, const Type& type) {
|
||||
}
|
||||
}
|
||||
|
||||
Type* TypeManager::GetRegisteredType(const Type* type) {
|
||||
uint32_t id = GetTypeInstruction(type);
|
||||
return GetType(id);
|
||||
}
|
||||
|
||||
Type* TypeManager::RecordIfTypeDefinition(
|
||||
const spvtools::ir::Instruction& inst) {
|
||||
if (!spvtools::ir::IsTypeInst(inst.opcode())) return nullptr;
|
||||
|
@ -122,6 +122,8 @@ class TypeManager {
|
||||
// unchanged.
|
||||
void RegisterType(uint32_t id, const Type& type);
|
||||
|
||||
Type* GetRegisteredType(const Type* type);
|
||||
|
||||
// Removes knowledge of |id| from the manager.
|
||||
//
|
||||
// If |id| is an ambiguous type the multiple ids may be registered to |id|'s
|
||||
|
@ -181,6 +181,12 @@ class IntrusiveList {
|
||||
const NodeType& front() const;
|
||||
const NodeType& back() const;
|
||||
|
||||
// Transfers [|first|, |last|) from |other| into the list at |where|.
|
||||
//
|
||||
// If |other| is |this|, no change is made.
|
||||
void Splice(iterator where, IntrusiveList<NodeType>* other, iterator first,
|
||||
iterator last);
|
||||
|
||||
protected:
|
||||
// Doing a deep copy of the list does not make sense if the list does not own
|
||||
// the data. It is not clear who will own the newly created data. Making
|
||||
@ -307,6 +313,29 @@ const NodeType& IntrusiveList<NodeType>::back() const {
|
||||
return *node;
|
||||
}
|
||||
|
||||
template <class NodeType>
|
||||
void IntrusiveList<NodeType>::Splice(iterator where,
|
||||
IntrusiveList<NodeType>* other,
|
||||
iterator first, iterator last) {
|
||||
if (first == last) return;
|
||||
if (other == this) return;
|
||||
|
||||
NodeType* first_prev = first.node_->previous_node_;
|
||||
NodeType* where_next = where.node_->next_node_;
|
||||
|
||||
// Attach first.
|
||||
where.node_->next_node_ = first.node_;
|
||||
first.node_->previous_node_ = where.node_;
|
||||
|
||||
// Attach last.
|
||||
where_next->previous_node_ = last.node_->previous_node_;
|
||||
last.node_->previous_node_->next_node_ = where_next;
|
||||
|
||||
// Fixup other.
|
||||
first_prev->next_node_ = last.node_;
|
||||
last.node_->previous_node_ = first_prev;
|
||||
}
|
||||
|
||||
template <class NodeType>
|
||||
void IntrusiveList<NodeType>::Check(NodeType* start) {
|
||||
int sentinel_count = 0;
|
||||
|
@ -46,8 +46,9 @@ void main(){
|
||||
}
|
||||
}
|
||||
*/
|
||||
#ifdef SPIRV_EFFCEE
|
||||
TEST_F(PassClassTest, HoistWithoutPreheader) {
|
||||
const std::string before_hoist = R"(OpCapability Shader
|
||||
const std::string text = R"(OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main"
|
||||
@ -69,6 +70,7 @@ OpName %main "main"
|
||||
OpBranch %14
|
||||
%14 = OpLabel
|
||||
%15 = OpPhi %int %int_0 %13 %16 %17
|
||||
; CHECK: OpLoopMerge [[preheader:%\w+]]
|
||||
OpLoopMerge %25 %17 None
|
||||
OpBranch %19
|
||||
%19 = OpLabel
|
||||
@ -85,6 +87,16 @@ OpBranch %17
|
||||
%17 = OpLabel
|
||||
%16 = OpIAdd %int %15 %int_1
|
||||
OpBranch %14
|
||||
; Check that we hoisted the code to the preheader
|
||||
; CHECK: [[preheader]] = OpLabel
|
||||
; CHECK-NEXT: OpPhi
|
||||
; CHECK-NEXT: OpPhi
|
||||
; CHECK-NEXT: OpIAdd
|
||||
; CHECK-NEXT: OpBranch [[header:%\w+]]
|
||||
; CHECK: [[header]] = OpLabel
|
||||
; CHECK-NEXT: OpPhi
|
||||
; CHECK-NEXT: OpPhi
|
||||
; CHECK: OpLoopMerge
|
||||
%25 = OpLabel
|
||||
%26 = OpPhi %int %int_0 %24 %int_0 %19 %27 %28
|
||||
%29 = OpPhi %int %int_0 %24 %int_0 %19 %30 %28
|
||||
@ -104,68 +116,8 @@ OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after_hoist = R"(OpCapability Shader
|
||||
%1 = OpExtInstImport "GLSL.std.450"
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint Fragment %main "main"
|
||||
OpExecutionMode %main OriginUpperLeft
|
||||
OpSource GLSL 440
|
||||
OpName %main "main"
|
||||
%void = OpTypeVoid
|
||||
%4 = OpTypeFunction %void
|
||||
%int = OpTypeInt 32 1
|
||||
%_ptr_Function_int = OpTypePointer Function %int
|
||||
%int_1 = OpConstant %int 1
|
||||
%int_2 = OpConstant %int 2
|
||||
%int_0 = OpConstant %int 0
|
||||
%int_10 = OpConstant %int 10
|
||||
%bool = OpTypeBool
|
||||
%int_5 = OpConstant %int 5
|
||||
%main = OpFunction %void None %4
|
||||
%13 = OpLabel
|
||||
OpBranch %14
|
||||
%14 = OpLabel
|
||||
%15 = OpPhi %int %int_0 %13 %16 %17
|
||||
OpLoopMerge %18 %17 None
|
||||
OpBranch %19
|
||||
%19 = OpLabel
|
||||
%20 = OpSLessThan %bool %15 %int_10
|
||||
OpBranchConditional %20 %21 %34
|
||||
%21 = OpLabel
|
||||
%22 = OpIEqual %bool %15 %int_5
|
||||
OpSelectionMerge %23 None
|
||||
OpBranchConditional %22 %24 %23
|
||||
%24 = OpLabel
|
||||
OpBranch %34
|
||||
%23 = OpLabel
|
||||
OpBranch %17
|
||||
%17 = OpLabel
|
||||
%16 = OpIAdd %int %15 %int_1
|
||||
OpBranch %14
|
||||
%34 = OpLabel
|
||||
%35 = OpPhi %int %int_0 %24 %int_0 %19
|
||||
%36 = OpPhi %int %int_0 %24 %int_0 %19
|
||||
%26 = OpIAdd %int %int_1 %int_2
|
||||
OpBranch %18
|
||||
%18 = OpLabel
|
||||
%25 = OpPhi %int %26 %27 %35 %34
|
||||
%28 = OpPhi %int %29 %27 %36 %34
|
||||
OpLoopMerge %30 %27 None
|
||||
OpBranch %31
|
||||
%31 = OpLabel
|
||||
%32 = OpSLessThan %bool %28 %int_10
|
||||
OpBranchConditional %32 %33 %30
|
||||
%33 = OpLabel
|
||||
OpBranch %27
|
||||
%27 = OpLabel
|
||||
%29 = OpIAdd %int %28 %int_1
|
||||
OpBranch %18
|
||||
%30 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndCheck<opt::LICMPass>(before_hoist, after_hoist, true);
|
||||
SinglePassRunAndMatch<opt::LICMPass>(text, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
@ -257,9 +257,24 @@ OpFunctionEnd
|
||||
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
|
||||
}
|
||||
|
||||
TEST_F(MergeReturnPassTest, StructuredControlFlowNOP) {
|
||||
#ifdef SPIRV_EFFCEE
|
||||
TEST_F(MergeReturnPassTest, StructuredControlFlowWithUnreachableMerge) {
|
||||
const std::string before =
|
||||
R"(OpCapability Addresses
|
||||
R"(
|
||||
; CHECK: [[false:%\w+]] = OpConstantFalse
|
||||
; CHECK: [[true:%\w+]] = OpConstantTrue
|
||||
; CHECK: OpFunction
|
||||
; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
|
||||
; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
|
||||
; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
|
||||
; CHECK: [[if_lab]] = OpLabel
|
||||
; CHECK-Next: OpStore [[var]] [[true]]
|
||||
; CHECK-Next: OpBranch
|
||||
; CHECK: [[then_lab]] = OpLabel
|
||||
; CHECK-Next: OpStore [[var]] [[true]]
|
||||
; CHECK-Next: OpBranch [[merge_lab]]
|
||||
; CHECK: OpReturn
|
||||
OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
@ -281,11 +296,411 @@ OpUnreachable
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after = before;
|
||||
SinglePassRunAndMatch<opt::MergeReturnPass>(before, false);
|
||||
}
|
||||
|
||||
TEST_F(MergeReturnPassTest, StructuredControlFlowAddPhi) {
|
||||
const std::string before =
|
||||
R"(
|
||||
; CHECK: [[false:%\w+]] = OpConstantFalse
|
||||
; CHECK: [[true:%\w+]] = OpConstantTrue
|
||||
; CHECK: OpFunction
|
||||
; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
|
||||
; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
|
||||
; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
|
||||
; CHECK: [[if_lab]] = OpLabel
|
||||
; CHECK-NEXT: [[add:%\w+]] = OpIAdd [[type:%\w+]]
|
||||
; CHECK-Next: OpStore [[var]] [[true]]
|
||||
; CHECK-Next: OpBranch
|
||||
; CHECK: [[then_lab]] = OpLabel
|
||||
; CHECK-Next: OpStore [[var]] [[true]]
|
||||
; CHECK-Next: OpBranch [[merge_lab]]
|
||||
; CHECK: [[merge_lab]] = OpLabel
|
||||
; CHECK-NEXT: [[phi:%\w+]] = OpPhi [[type]] [[add]] [[if_lab]] [[undef:%\w+]] [[then_lab]]
|
||||
; CHECK: OpIAdd [[type]] [[phi]] [[phi]]
|
||||
; CHECK: OpReturn
|
||||
OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %6 "simple_shader"
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeBool
|
||||
%int = OpTypeInt 32 0
|
||||
%int_0 = OpConstant %int 0
|
||||
%4 = OpConstantFalse %3
|
||||
%1 = OpTypeFunction %2
|
||||
%6 = OpFunction %2 None %1
|
||||
%7 = OpLabel
|
||||
OpSelectionMerge %10 None
|
||||
OpBranchConditional %4 %8 %9
|
||||
%8 = OpLabel
|
||||
%11 = OpIAdd %int %int_0 %int_0
|
||||
OpBranch %10
|
||||
%9 = OpLabel
|
||||
OpReturn
|
||||
%10 = OpLabel
|
||||
%12 = OpIAdd %int %11 %11
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndMatch<opt::MergeReturnPass>(before, false);
|
||||
}
|
||||
#endif
|
||||
|
||||
TEST_F(MergeReturnPassTest, StructuredControlFlowBothMergeAndHeader) {
|
||||
const std::string before =
|
||||
R"(OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "simple_shader"
|
||||
%2 = OpTypeVoid
|
||||
%3 = OpTypeBool
|
||||
%4 = OpTypeInt 32 0
|
||||
%5 = OpConstant %4 0
|
||||
%6 = OpConstantFalse %3
|
||||
%7 = OpTypeFunction %2
|
||||
%1 = OpFunction %2 None %7
|
||||
%8 = OpLabel
|
||||
OpSelectionMerge %9 None
|
||||
OpBranchConditional %6 %10 %11
|
||||
%10 = OpLabel
|
||||
OpReturn
|
||||
%11 = OpLabel
|
||||
OpBranch %9
|
||||
%9 = OpLabel
|
||||
OpLoopMerge %12 %13 None
|
||||
OpBranch %13
|
||||
%13 = OpLabel
|
||||
%14 = OpIAdd %4 %5 %5
|
||||
OpBranchConditional %6 %9 %12
|
||||
%12 = OpLabel
|
||||
%15 = OpIAdd %4 %14 %14
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after =
|
||||
R"(OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "simple_shader"
|
||||
%void = OpTypeVoid
|
||||
%bool = OpTypeBool
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%false = OpConstantFalse %bool
|
||||
%7 = OpTypeFunction %void
|
||||
%_ptr_Function_bool = OpTypePointer Function %bool
|
||||
%true = OpConstantTrue %bool
|
||||
%1 = OpFunction %void None %7
|
||||
%8 = OpLabel
|
||||
%18 = OpVariable %_ptr_Function_bool Function %false
|
||||
OpSelectionMerge %9 None
|
||||
OpBranchConditional %false %10 %11
|
||||
%10 = OpLabel
|
||||
OpStore %18 %true
|
||||
OpBranch %9
|
||||
%11 = OpLabel
|
||||
OpBranch %9
|
||||
%9 = OpLabel
|
||||
%23 = OpLoad %bool %18
|
||||
OpSelectionMerge %22 None
|
||||
OpBranchConditional %23 %22 %21
|
||||
%21 = OpLabel
|
||||
OpBranch %20
|
||||
%20 = OpLabel
|
||||
OpLoopMerge %12 %13 None
|
||||
OpBranch %13
|
||||
%13 = OpLabel
|
||||
%14 = OpIAdd %uint %uint_0 %uint_0
|
||||
OpBranchConditional %false %20 %12
|
||||
%12 = OpLabel
|
||||
%15 = OpIAdd %uint %14 %14
|
||||
OpStore %18 %true
|
||||
OpBranch %22
|
||||
%22 = OpLabel
|
||||
OpBranch %16
|
||||
%16 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
|
||||
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
|
||||
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
|
||||
}
|
||||
|
||||
TEST_F(MergeReturnPassTest, NestedSelectionMerge) {
|
||||
const std::string before =
|
||||
R"(
|
||||
OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "simple_shader"
|
||||
%void = OpTypeVoid
|
||||
%bool = OpTypeBool
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%false = OpConstantFalse %bool
|
||||
%7 = OpTypeFunction %void
|
||||
%1 = OpFunction %void None %7
|
||||
%8 = OpLabel
|
||||
OpSelectionMerge %9 None
|
||||
OpBranchConditional %false %10 %11
|
||||
%10 = OpLabel
|
||||
OpReturn
|
||||
%11 = OpLabel
|
||||
OpSelectionMerge %12 None
|
||||
OpBranchConditional %false %14 %15
|
||||
%14 = OpLabel
|
||||
%16 = OpIAdd %uint %uint_0 %uint_0
|
||||
OpBranch %12
|
||||
%15 = OpLabel
|
||||
OpReturn
|
||||
%12 = OpLabel
|
||||
OpBranch %9
|
||||
%9 = OpLabel
|
||||
%17 = OpIAdd %uint %16 %16
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after =
|
||||
R"(OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "simple_shader"
|
||||
%void = OpTypeVoid
|
||||
%bool = OpTypeBool
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%false = OpConstantFalse %bool
|
||||
%7 = OpTypeFunction %void
|
||||
%_ptr_Function_bool = OpTypePointer Function %bool
|
||||
%true = OpConstantTrue %bool
|
||||
%24 = OpUndef %uint
|
||||
%1 = OpFunction %void None %7
|
||||
%8 = OpLabel
|
||||
%19 = OpVariable %_ptr_Function_bool Function %false
|
||||
OpSelectionMerge %9 None
|
||||
OpBranchConditional %false %10 %11
|
||||
%10 = OpLabel
|
||||
OpStore %19 %true
|
||||
OpBranch %9
|
||||
%11 = OpLabel
|
||||
OpSelectionMerge %12 None
|
||||
OpBranchConditional %false %13 %14
|
||||
%13 = OpLabel
|
||||
%15 = OpIAdd %uint %uint_0 %uint_0
|
||||
OpBranch %12
|
||||
%14 = OpLabel
|
||||
OpStore %19 %true
|
||||
OpBranch %12
|
||||
%12 = OpLabel
|
||||
%25 = OpPhi %uint %15 %13 %24 %14
|
||||
OpBranch %9
|
||||
%9 = OpLabel
|
||||
%26 = OpPhi %uint %25 %12 %24 %10
|
||||
%23 = OpLoad %bool %19
|
||||
OpSelectionMerge %22 None
|
||||
OpBranchConditional %23 %22 %21
|
||||
%21 = OpLabel
|
||||
%16 = OpIAdd %uint %26 %26
|
||||
OpStore %19 %true
|
||||
OpBranch %22
|
||||
%22 = OpLabel
|
||||
OpBranch %17
|
||||
%17 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
|
||||
}
|
||||
|
||||
// This is essentially the same as NestedSelectionMerge, except
|
||||
// the order of the first branch is changed. This is to make sure things
|
||||
// work even if the order of the traversals change.
|
||||
TEST_F(MergeReturnPassTest, NestedSelectionMerge2) {
|
||||
const std::string before =
|
||||
R"(
|
||||
OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "simple_shader"
|
||||
%void = OpTypeVoid
|
||||
%bool = OpTypeBool
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%false = OpConstantFalse %bool
|
||||
%7 = OpTypeFunction %void
|
||||
%1 = OpFunction %void None %7
|
||||
%8 = OpLabel
|
||||
OpSelectionMerge %9 None
|
||||
OpBranchConditional %false %10 %11
|
||||
%11 = OpLabel
|
||||
OpReturn
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %12 None
|
||||
OpBranchConditional %false %14 %15
|
||||
%14 = OpLabel
|
||||
%16 = OpIAdd %uint %uint_0 %uint_0
|
||||
OpBranch %12
|
||||
%15 = OpLabel
|
||||
OpReturn
|
||||
%12 = OpLabel
|
||||
OpBranch %9
|
||||
%9 = OpLabel
|
||||
%17 = OpIAdd %uint %16 %16
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after =
|
||||
R"(OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "simple_shader"
|
||||
%void = OpTypeVoid
|
||||
%bool = OpTypeBool
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%false = OpConstantFalse %bool
|
||||
%7 = OpTypeFunction %void
|
||||
%_ptr_Function_bool = OpTypePointer Function %bool
|
||||
%true = OpConstantTrue %bool
|
||||
%24 = OpUndef %uint
|
||||
%1 = OpFunction %void None %7
|
||||
%8 = OpLabel
|
||||
%19 = OpVariable %_ptr_Function_bool Function %false
|
||||
OpSelectionMerge %9 None
|
||||
OpBranchConditional %false %10 %11
|
||||
%11 = OpLabel
|
||||
OpStore %19 %true
|
||||
OpBranch %9
|
||||
%10 = OpLabel
|
||||
OpSelectionMerge %12 None
|
||||
OpBranchConditional %false %13 %14
|
||||
%13 = OpLabel
|
||||
%15 = OpIAdd %uint %uint_0 %uint_0
|
||||
OpBranch %12
|
||||
%14 = OpLabel
|
||||
OpStore %19 %true
|
||||
OpBranch %12
|
||||
%12 = OpLabel
|
||||
%25 = OpPhi %uint %15 %13 %24 %14
|
||||
OpBranch %9
|
||||
%9 = OpLabel
|
||||
%26 = OpPhi %uint %25 %12 %24 %11
|
||||
%23 = OpLoad %bool %19
|
||||
OpSelectionMerge %22 None
|
||||
OpBranchConditional %23 %22 %21
|
||||
%21 = OpLabel
|
||||
%16 = OpIAdd %uint %26 %26
|
||||
OpStore %19 %true
|
||||
OpBranch %22
|
||||
%22 = OpLabel
|
||||
OpBranch %17
|
||||
%17 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
|
||||
}
|
||||
|
||||
TEST_F(MergeReturnPassTest, NestedSelectionMerge3) {
|
||||
const std::string before =
|
||||
R"(
|
||||
OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "simple_shader"
|
||||
%void = OpTypeVoid
|
||||
%bool = OpTypeBool
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%false = OpConstantFalse %bool
|
||||
%7 = OpTypeFunction %void
|
||||
%1 = OpFunction %void None %7
|
||||
%8 = OpLabel
|
||||
OpSelectionMerge %9 None
|
||||
OpBranchConditional %false %10 %11
|
||||
%11 = OpLabel
|
||||
OpReturn
|
||||
%10 = OpLabel
|
||||
%16 = OpIAdd %uint %uint_0 %uint_0
|
||||
OpSelectionMerge %12 None
|
||||
OpBranchConditional %false %14 %15
|
||||
%14 = OpLabel
|
||||
OpBranch %12
|
||||
%15 = OpLabel
|
||||
OpReturn
|
||||
%12 = OpLabel
|
||||
OpBranch %9
|
||||
%9 = OpLabel
|
||||
%17 = OpIAdd %uint %16 %16
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
const std::string after =
|
||||
R"(OpCapability Addresses
|
||||
OpCapability Shader
|
||||
OpCapability Linkage
|
||||
OpMemoryModel Logical GLSL450
|
||||
OpEntryPoint GLCompute %1 "simple_shader"
|
||||
%void = OpTypeVoid
|
||||
%bool = OpTypeBool
|
||||
%uint = OpTypeInt 32 0
|
||||
%uint_0 = OpConstant %uint 0
|
||||
%false = OpConstantFalse %bool
|
||||
%7 = OpTypeFunction %void
|
||||
%_ptr_Function_bool = OpTypePointer Function %bool
|
||||
%true = OpConstantTrue %bool
|
||||
%24 = OpUndef %uint
|
||||
%1 = OpFunction %void None %7
|
||||
%8 = OpLabel
|
||||
%19 = OpVariable %_ptr_Function_bool Function %false
|
||||
OpSelectionMerge %9 None
|
||||
OpBranchConditional %false %10 %11
|
||||
%11 = OpLabel
|
||||
OpStore %19 %true
|
||||
OpBranch %9
|
||||
%10 = OpLabel
|
||||
%12 = OpIAdd %uint %uint_0 %uint_0
|
||||
OpSelectionMerge %13 None
|
||||
OpBranchConditional %false %14 %15
|
||||
%14 = OpLabel
|
||||
OpBranch %13
|
||||
%15 = OpLabel
|
||||
OpStore %19 %true
|
||||
OpBranch %13
|
||||
%13 = OpLabel
|
||||
OpBranch %9
|
||||
%9 = OpLabel
|
||||
%25 = OpPhi %uint %12 %13 %24 %11
|
||||
%23 = OpLoad %bool %19
|
||||
OpSelectionMerge %22 None
|
||||
OpBranchConditional %23 %22 %21
|
||||
%21 = OpLabel
|
||||
%16 = OpIAdd %uint %25 %25
|
||||
OpStore %19 %true
|
||||
OpBranch %22
|
||||
%22 = OpLabel
|
||||
OpBranch %17
|
||||
%17 = OpLabel
|
||||
OpReturn
|
||||
OpFunctionEnd
|
||||
)";
|
||||
|
||||
SinglePassRunAndCheck<opt::MergeReturnPass>(before, after, false, true);
|
||||
}
|
||||
} // anonymous namespace
|
||||
|
@ -182,10 +182,19 @@ Options (in lexicographical order):
|
||||
first as its only predecessor. Performed only on entry point
|
||||
call tree functions.
|
||||
--merge-return
|
||||
Replace all return instructions with unconditional branches to
|
||||
a new basic block containing an unified return.
|
||||
This pass does not currently support structured control flow. It
|
||||
makes no changes if the shader capability is detected.
|
||||
Changes functions that have multiple return statements so they
|
||||
have a single return statement.
|
||||
|
||||
For structured control flow it is assumed that the only
|
||||
unreachable blocks in the function are trivial merge and continue
|
||||
blocks.
|
||||
|
||||
A trivial merge block contains the label and an OpUnreachable
|
||||
instructions, nothing else. A trivial continue block contain a
|
||||
label and an OpBranch to the header, nothing else.
|
||||
|
||||
These conditions are guaranteed to be met after running
|
||||
dead-branch elimination.
|
||||
--local-redundancy-elimination
|
||||
Looks for instructions in the same basic block that compute the
|
||||
same value, and deletes the redundant ones.
|
||||
|
Loading…
Reference in New Issue
Block a user