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:
Steven Perron 2018-03-06 11:20:28 -05:00
parent 1ef6b19260
commit b3daa93b46
23 changed files with 1705 additions and 268 deletions

View File

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

View File

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

View File

@ -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;
}

View File

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

View File

@ -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*;

View File

@ -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;
}

View File

@ -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();
}

View File

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

View File

@ -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_;

View File

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

View File

@ -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)];

View File

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

View File

@ -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) {

View File

@ -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_;
}

View File

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

View File

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

View File

@ -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())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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