Add dummy loop in merge-return. (#1896)

The current implementation of merge return can create bad, but correct,
code.  When it is not in a loop construct, it will insert a lot of
extra branch around code.  The potentially large number of branches are
bad.  At the same time, it can separate code store to variables from
its uses hiding the fact that the store dominates the load.

This hurts the later analysis because the compiler thinks that multiple
values can reach a load, when there is really only 1.  This poorer
analysis leads to missed optimizations.

The solution is to create a dummy loop around the entire body of the
function, then we can break from that loop with a single branch.  Also
only new merge nodes would be those at the end of loops meaning that
most analysies will not be hurt.

Remove dead code for cases that are no longer possible.

It seems like some drivers expect there the be an OpSelectionMerge
before conditional branches, even if they are not strictly needed.
So we add them.
This commit is contained in:
Steven Perron 2018-09-18 08:52:47 -04:00 committed by GitHub
parent 5f599e700e
commit 7075c49923
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 299 additions and 532 deletions

View File

@ -212,8 +212,11 @@ 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<Operand>{}));
std::unique_ptr<BasicBlock> new_block_temp =
MakeUnique<BasicBlock>(MakeUnique<Instruction>(
context, SpvOpLabel, 0, label_id, std::initializer_list<Operand>{}));
BasicBlock* new_block = new_block_temp.get();
function_->InsertBasicBlockAfter(std::move(new_block_temp), this);
new_block->insts_.Splice(new_block->end(), &insts_, iter, end());
new_block->SetParent(GetParent());

View File

@ -198,7 +198,8 @@ class BasicBlock {
// 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.
// prior to |iter| remain in this basic block. The new block will be added
// to the function immediately after the original block.
BasicBlock* SplitBasicBlock(IRContext* context, uint32_t label_id,
iterator iter);

View File

@ -197,14 +197,9 @@ BasicBlock* CFG::SplitLoopHeader(BasicBlock* bb) {
++iter;
}
std::unique_ptr<BasicBlock> newBlock(
bb->SplitBasicBlock(context, context->TakeNextId(), iter));
BasicBlock* new_header =
bb->SplitBasicBlock(context, context->TakeNextId(), iter);
// Insert the new bb in the correct position
auto insert_pos = header_it;
++insert_pos;
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());

View File

@ -78,8 +78,13 @@ class CFG {
BasicBlock* bb, const std::function<void(BasicBlock*)>& f);
// Registers |blk| as a basic block in the cfg, this also updates the
// predecessor lists of each successor of |blk|.
// predecessor lists of each successor of |blk|. |blk| must have a terminator
// instruction at the end of the block.
void RegisterBlock(BasicBlock* blk) {
assert(blk->begin() != blk->end() &&
"Basic blocks must have a terminator before registering.");
assert(blk->tail()->IsBlockTerminator() &&
"Basic blocks must have a terminator before registering.");
uint32_t blk_id = blk->id();
id2block_[blk_id] = blk;
AddEdges(blk);

View File

@ -71,6 +71,20 @@ class InstructionBuilder {
return AddInstruction(std::move(new_branch_merge));
}
// Creates a new loop merge instruction.
// The id |merge_id| is the basic block id of the merge block.
// |continue_id| is the id of the continue block.
// |loop_control| are the loop control flags to be added to the instruction.
Instruction* AddLoopMerge(uint32_t merge_id, uint32_t continue_id,
uint32_t loop_control = SpvLoopControlMaskNone) {
std::unique_ptr<Instruction> new_branch_merge(new Instruction(
GetContext(), SpvOpLoopMerge, 0, 0,
{{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {merge_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_ID, {continue_id}},
{spv_operand_type_t::SPV_OPERAND_TYPE_LOOP_CONTROL, {loop_control}}}));
return AddInstruction(std::move(new_branch_merge));
}
// Creates a new branch instruction to |label_id|.
// Note that the user must make sure the final basic block is
// well formed.

View File

@ -53,21 +53,16 @@ Pass::Status MergeReturnPass::Process() {
void MergeReturnPass::ProcessStructured(
Function* function, const std::vector<BasicBlock*>& return_blocks) {
AddDummyLoopAroundFunction();
std::list<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)) {
if (cfg()->IsPseudoEntryBlock(block) || cfg()->IsPseudoExitBlock(block) ||
block == final_return_block_) {
continue;
}
@ -175,14 +170,8 @@ void MergeReturnPass::ProcessStructuredBlock(BasicBlock* block) {
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());
}
assert(CurrentState().InLoop() && "Should be in the dummy loop.");
BranchToBlock(block, CurrentState().LoopMergeId());
}
}
@ -311,206 +300,16 @@ void MergeReturnPass::PredicateBlocks(
if (!predicated->insert(block).second) break;
// Skip structured subgraphs.
BasicBlock* next = nullptr;
if (state->InLoop()) {
next = context()->get_instr_block(state->LoopMergeId());
while (state->LoopMergeId() == next->id()) {
state++;
}
BreakFromConstruct(block, next, predicated, order);
} else if (false && state->InStructuredFlow()) {
// TODO(#1861): This is disabled until drivers are fixed to accept
// conditional exits from a selection construct. Reenable tests when
// this code is turned back on.
next = context()->get_instr_block(state->CurrentMergeId());
assert(state->InLoop() && "Should be in the dummy loop at the very least.");
next = context()->get_instr_block(state->LoopMergeId());
while (state->LoopMergeId() == next->id()) {
state++;
BreakFromConstruct(block, next, predicated, order);
} else {
BasicBlock* tail = block;
while (tail->GetMergeInst()) {
tail = context()->get_instr_block(tail->MergeBlockIdIfAny());
}
// Must find |next| (the successor of |tail|) before predicating the
// block because, if |block| == |tail|, then |tail| will have multiple
// successors.
next = nullptr;
const_cast<const BasicBlock*>(tail)->ForEachSuccessorLabel(
[this, &next](const uint32_t idx) {
BasicBlock* succ_block = context()->get_instr_block(idx);
assert(next == nullptr &&
"Found block with multiple successors and no merge "
"instruction.");
next = succ_block;
});
PredicateBlock(block, tail, predicated, order);
}
BreakFromConstruct(block, next, predicated, order);
block = next;
}
}
bool MergeReturnPass::RequiresPredication(const BasicBlock* block,
const 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 Instruction* inst) {
if (inst->opcode() != SpvOpPhi && inst->opcode() != SpvOpLabel &&
!IsTerminatorInst(inst->opcode())) {
requires_predicate = true;
}
});
return requires_predicate;
}
void MergeReturnPass::PredicateBlock(
BasicBlock* block, BasicBlock* tail_block,
std::unordered_set<BasicBlock*>* predicated,
std::list<BasicBlock*>* order) {
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(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 header, 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<BasicBlock> new_block(
block->SplitBasicBlock(context(), TakeNextId(), iter));
BasicBlock* old_body =
function_->InsertBasicBlockAfter(std::move(new_block), block);
predicated->insert(old_body);
// Update |order| so old_block will be traversed.
InsertAfterElement(block, old_body, order);
if (tail_block == block) {
tail_block = old_body;
}
const BasicBlock* const_old_body = static_cast<const BasicBlock*>(old_body);
const_old_body->ForEachSuccessorLabel(
[old_body, block, this](const uint32_t label) {
BasicBlock* target_bb = context()->get_instr_block(label);
if (MarkedSinglePred(target_bb) == block) {
MarkForNewPhiNodes(target_bb, old_body);
}
});
std::unique_ptr<BasicBlock> new_merge_block(new BasicBlock(
MakeUnique<Instruction>(context(), SpvOpLabel, 0, TakeNextId(),
std::initializer_list<Operand>{})));
BasicBlock* new_merge =
function_->InsertBasicBlockAfter(std::move(new_merge_block), tail_block);
predicated->insert(new_merge);
new_merge->SetParent(function_);
// Update |order| so old_block will be traversed.
InsertAfterElement(tail_block, new_merge, order);
// Register the new label.
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<Instruction> inst;
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<Instruction>(context(), SpvOpBranch, 0, 0,
std::initializer_list<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<Instruction>(
context(), SpvOpLoad, bool_id, load_id,
std::initializer_list<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<Instruction>(
context(), SpvOpSelectionMerge, 0, 0,
std::initializer_list<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<Instruction>(
context(), SpvOpBranchConditional, 0, 0,
std::initializer_list<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::BreakFromConstruct(
BasicBlock* block, BasicBlock* merge_block,
std::unordered_set<BasicBlock*>* predicated,
@ -543,10 +342,7 @@ void MergeReturnPass::BreakFromConstruct(
// Forget about the edges leaving block. They will be removed.
cfg()->RemoveSuccessorEdges(block);
std::unique_ptr<BasicBlock> new_block(
block->SplitBasicBlock(context(), TakeNextId(), iter));
BasicBlock* old_body =
function_->InsertBasicBlockAfter(std::move(new_block), block);
BasicBlock* old_body = block->SplitBasicBlock(context(), TakeNextId(), iter);
predicated->insert(old_body);
// Update |order| so old_block will be traversed.
@ -560,26 +356,19 @@ void MergeReturnPass::BreakFromConstruct(
// Sine we are branching to the merge block of the current construct, there is
// no need for an OpSelectionMerge.
InstructionBuilder builder(
context(), block,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
// 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<Instruction>(
context(), SpvOpLoad, bool_id, load_id,
std::initializer_list<Operand>{
{SPV_OPERAND_TYPE_ID, {return_flag_->result_id()}}}));
get_def_use_mgr()->AnalyzeInstDefUse(block->terminator());
context()->set_instr_block(block->terminator(), block);
uint32_t load_id =
builder.AddLoad(bool_id, return_flag_->result_id())->result_id();
// 2. Branch to |merge_block| (true) or |old_body| (false)
block->AddInstruction(MakeUnique<Instruction>(
context(), SpvOpBranchConditional, 0, 0,
std::initializer_list<Operand>{{SPV_OPERAND_TYPE_ID, {load_id}},
{SPV_OPERAND_TYPE_ID, {merge_block->id()}},
{SPV_OPERAND_TYPE_ID, {old_body->id()}}}));
get_def_use_mgr()->AnalyzeInstUse(block->terminator());
context()->set_instr_block(block->terminator(), block);
builder.AddConditionalBranch(load_id, merge_block->id(), old_body->id(),
old_body->id());
// Update the cfg
cfg()->AddEdges(block);
@ -786,8 +575,10 @@ void MergeReturnPass::AddNewPhiNodes() {
cfg()->ComputeStructuredOrder(function_, &*function_->begin(), &order);
for (BasicBlock* bb : order) {
AddNewPhiNodes(bb, new_merge_nodes_[bb],
dom_tree->ImmediateDominator(bb)->id());
BasicBlock* dominator = dom_tree->ImmediateDominator(bb);
if (dominator) {
AddNewPhiNodes(bb, new_merge_nodes_[bb], dominator->id());
}
}
}
@ -820,5 +611,102 @@ void MergeReturnPass::InsertAfterElement(BasicBlock* element,
list->insert(pos, new_element);
}
void MergeReturnPass::AddDummyLoopAroundFunction() {
CreateReturnBlock();
CreateReturn(final_return_block_);
if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) {
cfg()->RegisterBlock(final_return_block_);
}
CreateDummyLoop(final_return_block_);
}
BasicBlock* MergeReturnPass::CreateContinueTarget(uint32_t header_label_id) {
std::unique_ptr<Instruction> label(
new Instruction(context(), SpvOpLabel, 0u, TakeNextId(), {}));
// Create the new basic block
std::unique_ptr<BasicBlock> block(new BasicBlock(std::move(label)));
// Insert the new block just before the return block
auto pos = function_->end();
assert(pos != function_->begin());
pos--;
assert(pos != function_->begin());
assert(&*pos == final_return_block_);
auto new_block = &*pos.InsertBefore(std::move(block));
new_block->SetParent(function_);
context()->AnalyzeDefUse(new_block->GetLabelInst());
context()->set_instr_block(new_block->GetLabelInst(), new_block);
InstructionBuilder builder(
context(), new_block,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
builder.AddBranch(header_label_id);
if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) {
cfg()->RegisterBlock(new_block);
}
return new_block;
}
void MergeReturnPass::CreateDummyLoop(BasicBlock* merge_target) {
std::unique_ptr<Instruction> label(
new Instruction(context(), SpvOpLabel, 0u, TakeNextId(), {}));
// Create the new basic block
std::unique_ptr<BasicBlock> block(new BasicBlock(std::move(label)));
// Insert the new block before any code is run. We have to split the entry
// block to make sure the OpVariable instructions remain in the entry block.
BasicBlock* start_block = &*function_->begin();
auto split_pos = start_block->begin();
while (split_pos->opcode() == SpvOpVariable) {
++split_pos;
}
BasicBlock* old_block =
start_block->SplitBasicBlock(context(), TakeNextId(), split_pos);
// The new block must be inserted after the entry block. We cannot make the
// entry block the header for the dummy loop because it is not valid to have a
// branch to the entry block, and the continue target must branch back to the
// loop header.
auto pos = function_->begin();
pos++;
BasicBlock* header_block = &*pos.InsertBefore(std::move(block));
context()->AnalyzeDefUse(header_block->GetLabelInst());
header_block->SetParent(function_);
// We have to create the continue block before OpLoopMerge instruction.
// Otherwise the def-use manager will compalain that there is a use without a
// definition.
uint32_t continue_target = CreateContinueTarget(header_block->id())->id();
// Add the code the the header block.
InstructionBuilder builder(
context(), header_block,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
builder.AddLoopMerge(merge_target->id(), continue_target);
builder.AddBranch(old_block->id());
// Fix up the entry block by adding a branch to the loop header.
InstructionBuilder builder2(
context(), start_block,
IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping);
builder2.AddBranch(header_block->id());
if (context()->AreAnalysesValid(IRContext::kAnalysisCFG)) {
cfg()->RegisterBlock(old_block);
cfg()->RegisterBlock(header_block);
cfg()->AddEdges(start_block);
}
}
} // namespace opt
} // namespace spvtools

View File

@ -30,13 +30,13 @@ namespace opt {
*
* Handling Structured Control Flow:
*
* Structured control flow guarantees that the CFG will reconverge at a given
* Structured control flow guarantees that the CFG will converge 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
* cleaned up. This means that the only unreachable blocks are those necessary
* for valid structured control flow.
*
* Algorithm:
@ -46,14 +46,13 @@ namespace opt {
* 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.
* the branch destination should be the innermost loop's merge. This loop will
* always exist because a dummy loop is added around the entire function.
* 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 merge block of the inner-most loop it is contained in.
*Once structured control flow has been exited, it will be at the merge of the
*dummy loop, with will simply return.
*
* In the final return block, the return value should be loaded and returned.
* Memory promotion passes should be able to promote the newly introduced
@ -65,31 +64,29 @@ namespace opt {
* that block produces value live beyond it). This needs to be done carefully.
* The merge block should be split into multiple blocks.
*
* 1 (header)
* 1 (loop header)
* / \
* (ret) 2 3 (merge)
*
* ||
* \/
*
* 1 (header)
* / \
* 2 |
* \ /
* 3 (merge for 1, new header)
* / \
* | 3 (old body)
* \ /
* (ret) 4 (new merge)
* 0 (dummy loop header)
* |
* 1 (loop header)
* / \
* 2 | (merge)
* \ /
* 3' (merge)
* / \
* | 3 (original code in 3)
* \ /
* (ret) 4 (dummy loop 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.
* returned" variable.
*
******************************************************************************/
@ -107,7 +104,6 @@ class MergeReturnPass : public MemPass {
Status Process() override;
IRContext::Analysis GetPreservedAnalyses() override {
// return IRContext::kAnalysisDefUse;
return IRContext::kAnalysisNone;
}
@ -209,12 +205,6 @@ class MergeReturnPass : public MemPass {
// |AddReturnFlag| and |AddReturnValue| must have already been called.
void BranchToBlock(BasicBlock* block, uint32_t target);
// Returns true if we need to predicate |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 BasicBlock* block,
const BasicBlock* tail_block) const;
// For every basic block that is reachable from |return_block|, 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
@ -236,16 +226,6 @@ class MergeReturnPass : public MemPass {
std::unordered_set<BasicBlock*>* predicated,
std::list<BasicBlock*>* order);
// 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|.
//
// If new blocks that are created will be added to |order|. This way a call
// can traverse these new block in structured order.
void PredicateBlock(BasicBlock* block, BasicBlock* tail_block,
std::unordered_set<BasicBlock*>* predicated,
std::list<BasicBlock*>* order);
// 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(BasicBlock* block);
@ -300,6 +280,20 @@ class MergeReturnPass : public MemPass {
void InsertAfterElement(BasicBlock* element, BasicBlock* new_element,
std::list<BasicBlock*>* list);
// Creates a single iteration loop around all of the exectuable code of the
// current function and returns after the loop is done. Sets
// |final_return_block_|.
void AddDummyLoopAroundFunction();
// Creates a new basic block that branches to |header_label_id|. Returns the
// new basic block. The block will be the second last basic block in the
// function.
BasicBlock* CreateContinueTarget(uint32_t header_label_id);
// Creates a loop around the executable code of the function with
// |merge_target| as the merge node.
void CreateDummyLoop(BasicBlock* merge_target);
// A stack used to keep track of the innermost contain loop and selection
// constructs.
std::vector<StructuredControlState> state_;

View File

@ -265,15 +265,19 @@ TEST_F(MergeReturnPassTest, StructuredControlFlowWithUnreachableMerge) {
; CHECK: [[true:%\w+]] = OpConstantTrue
; CHECK: OpFunction
; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
; CHECK: OpLoopMerge [[return_block:%\w+]]
; 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-NEXT: OpBranch [[return_block]]
; CHECK: [[then_lab]] = OpLabel
; CHECK-NEXT: OpStore [[var]] [[true]]
; CHECK-NEXT: OpBranch [[merge_lab]]
; CHECK: OpReturn
; CHECK-NEXT: OpBranch [[return_block]]
; CHECK: [[merge_lab]] = OpLabel
; CHECK-NEXT: OpBranch [[return_block]]
; CHECK: [[return_block]] = OpLabel
; CHECK-NEXT: OpReturn
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
@ -307,6 +311,7 @@ TEST_F(MergeReturnPassTest, StructuredControlFlowAddPhi) {
; CHECK: [[true:%\w+]] = OpConstantTrue
; CHECK: OpFunction
; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
; CHECK: OpLoopMerge [[dummy_loop_merge:%\w+]]
; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
; CHECK: [[if_lab]] = OpLabel
@ -314,11 +319,10 @@ TEST_F(MergeReturnPassTest, StructuredControlFlowAddPhi) {
; CHECK-NEXT: OpBranch
; CHECK: [[then_lab]] = OpLabel
; CHECK-NEXT: OpStore [[var]] [[true]]
; CHECK-NEXT: OpBranch [[merge_lab]]
; CHECK-NEXT: OpBranch [[dummy_loop_merge]]
; CHECK: [[merge_lab]] = OpLabel
; CHECK-NEXT: [[phi:%\w+]] = OpPhi [[type]] [[add]] [[if_lab]] [[undef:%\w+]] [[then_lab]]
; CHECK: OpIAdd [[type]] [[phi]] [[phi]]
; CHECK: OpReturn
; CHECK: [[dummy_loop_merge]] = OpLabel
; CHECK-NEXT: OpReturn
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
@ -357,6 +361,7 @@ TEST_F(MergeReturnPassTest, StructuredControlDecoration) {
; CHECK: [[true:%\w+]] = OpConstantTrue
; CHECK: OpFunction
; CHECK: [[var:%\w+]] = OpVariable [[:%\w+]] Function [[false]]
; CHECK: OpLoopMerge [[return_block:%\w+]]
; CHECK: OpSelectionMerge [[merge_lab:%\w+]]
; CHECK: OpBranchConditional [[cond:%\w+]] [[if_lab:%\w+]] [[then_lab:%\w+]]
; CHECK: [[if_lab]] = OpLabel
@ -364,9 +369,12 @@ TEST_F(MergeReturnPassTest, StructuredControlDecoration) {
; CHECK-NEXT: OpBranch
; CHECK: [[then_lab]] = OpLabel
; CHECK-NEXT: OpStore [[var]] [[true]]
; CHECK-NEXT: OpBranch [[merge_lab]]
; CHECK-NEXT: OpBranch [[return_block]]
; CHECK: [[merge_lab]] = OpLabel
; CHECK: OpReturn
; CHECK-NEXT: OpStore [[var]] [[true]]
; CHECK-NEXT: OpBranch [[return_block]]
; CHECK: [[return_block]] = OpLabel
; CHECK-NEXT: OpReturn
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
@ -396,69 +404,18 @@ OpFunctionEnd
SinglePassRunAndMatch<MergeReturnPass>(before, false);
}
TEST_F(MergeReturnPassTest, StructuredControlDecoration2) {
const std::string before =
R"(
; CHECK: OpDecorate [[dec_id:%\w+]] RelaxedPrecision
; 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: [[dec_id]] = OpIAdd [[type:%\w+]]
; 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]] [[dec_id]] [[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"
OpDecorate %11 RelaxedPrecision
%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<MergeReturnPass>(before, false);
}
TEST_F(MergeReturnPassTest, SplitBlockUsedInPhi) {
const std::string before =
R"(
; CHECK: OpFunction
; CHECK-NEXT: OpLabel
; CHECK: OpSelectionMerge [[merge1:%\w+]] None
; CHECK: [[merge1]] = OpLabel
; CHECK: OpBranchConditional %{{\w+}} %{{\w+}} [[old_merge:%\w+]]
; CHECK: [[old_merge]] = OpLabel
; CHECK-NEXT: OpSelectionMerge [[merge2:%\w+]]
; CHECK-NEXT: OpBranchConditional %false [[side_node:%\w+]] [[merge2]]
; CHECK: [[merge2]] = OpLabel
; CHECK-NEXT: OpPhi %bool %false [[old_merge]] %true [[side_node]]
; CHECK: OpLoopMerge [[dummy_loop_merge:%\w+]]
; CHECK: OpLoopMerge [[loop_merge:%\w+]]
; CHECK: [[loop_merge]] = OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[dummy_loop_merge]] [[old_code_path:%\w+]]
; CHECK: [[old_code_path:%\w+]] = OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[side_node:%\w+]] [[phi_block:%\w+]]
; CHECK: [[phi_block]] = OpLabel
; CHECK-NEXT: OpPhi %bool %false [[side_node]] %true [[old_code_path]]
OpCapability Addresses
OpCapability Shader
OpCapability Linkage
@ -471,17 +428,19 @@ TEST_F(MergeReturnPassTest, SplitBlockUsedInPhi) {
%6 = OpTypeFunction %void
%1 = OpFunction %void None %6
%7 = OpLabel
OpSelectionMerge %8 None
OpBranchConditional %false %9 %8
OpLoopMerge %merge %cont None
OpBranchConditional %false %9 %merge
%9 = OpLabel
OpReturn
%8 = OpLabel
OpSelectionMerge %10 None
OpBranchConditional %false %11 %10
%11 = OpLabel
OpBranch %10
%10 = OpLabel
%12 = OpPhi %bool %false %8 %true %11
%cont = OpLabel
OpBranch %7
%merge = OpLabel
OpSelectionMerge %merge2 None
OpBranchConditional %false %if %merge2
%if = OpLabel
OpBranch %merge2
%merge2 = OpLabel
%12 = OpPhi %bool %false %if %true %merge
OpReturn
OpFunctionEnd
)";
@ -560,36 +519,59 @@ TEST_F(MergeReturnPassTest, UpdateOrderWhenPredicating) {
#endif
TEST_F(MergeReturnPassTest, StructuredControlFlowBothMergeAndHeader) {
const std::string before =
R"(OpCapability Addresses
const std::string test =
R"(
; CHECK: OpFunction
; CHECK: [[ret_flag:%\w+]] = OpVariable %_ptr_Function_bool Function %false
; CHECK: OpLoopMerge [[dummy_loop_merge:%\w+]]
; CHECK: OpLoopMerge [[loop1_merge:%\w+]] {{%\w+}}
; CHECK-NEXT: OpBranchConditional {{%\w+}} [[if_lab:%\w+]] {{%\w+}}
; CHECK: [[if_lab]] = OpLabel
; CHECK: OpStore [[ret_flag]] %true
; CHECK-NEXT: OpBranch [[loop1_merge]]
; CHECK: [[loop1_merge]] = OpLabel
; CHECK-NEXT: [[ld:%\w+]] = OpLoad %bool [[ret_flag]]
; CHECK-NOT: OpLabel
; CHECK: OpBranchConditional [[ld]] [[dummy_loop_merge]] [[empty_block:%\w+]]
; CHECK: [[empty_block]] = OpLabel
; CHECK-NEXT: OpBranch [[loop2:%\w+]]
; CHECK: [[loop2]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpLoopMerge
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
%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 %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
OpLoopMerge %10 %11 None
OpBranchConditional %false %12 %13
%12 = OpLabel
%15 = OpIAdd %4 %14 %14
OpReturn
%13 = OpLabel
OpBranch %10
%11 = OpLabel
OpBranch %9
%10 = OpLabel
OpLoopMerge %14 %15 None
OpBranch %15
%15 = OpLabel
%16 = OpIAdd %uint %uint_0 %uint_0
OpBranchConditional %false %10 %14
%14 = OpLabel
%17 = OpIAdd %uint %16 %16
OpReturn
OpFunctionEnd
)";
const std::string after =
@ -639,7 +621,7 @@ OpReturn
OpFunctionEnd
)";
SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
SinglePassRunAndMatch<MergeReturnPass>(test, false);
}
// TODO(#1861): Reenable these test when the breaks from selection constructs
@ -927,8 +909,27 @@ OpFunctionEnd
*/
TEST_F(MergeReturnPassTest, NestedLoopMerge) {
const std::string before =
R"( OpCapability SampledBuffer
const std::string test =
R"(
; CHECK: OpFunction
; CHECK: OpLoopMerge [[dummy_loop_merge:%\w+]]
; CHECK: OpLoopMerge [[outer_loop_merge:%\w+]]
; CHECK: OpLoopMerge [[inner_loop_merge:%\w+]]
; CHECK: OpSelectionMerge
; CHECK-NEXT: OpBranchConditional %true [[early_exit_block:%\w+]]
; CHECK: [[early_exit_block]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpBranch [[inner_loop_merge]]
; CHECK: [[inner_loop_merge]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[outer_loop_merge]]
; CHECK: [[outer_loop_merge]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpBranchConditional {{%\w+}} [[dummy_loop_merge]]
; CHECK: [[dummy_loop_merge]] = OpLabel
; CHECK-NOT: OpLabel
; CHECK: OpReturn
OpCapability SampledBuffer
OpCapability StorageImageExtendedFormats
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
@ -936,7 +937,6 @@ TEST_F(MergeReturnPassTest, NestedLoopMerge) {
OpEntryPoint GLCompute %2 "CS"
OpExecutionMode %2 LocalSize 8 8 1
OpSource HLSL 600
OpName %function "function"
%uint = OpTypeInt 32 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
@ -946,29 +946,18 @@ TEST_F(MergeReturnPassTest, NestedLoopMerge) {
%bool = OpTypeBool
%true = OpConstantTrue %bool
%_ptr_Function_uint = OpTypePointer Function %uint
%_struct_13 = OpTypeStruct %v3uint %v3uint %v3uint %uint %uint %uint %uint %uint %uint
%2 = OpFunction %void None %6
%14 = OpLabel
%15 = OpFunctionCall %void %function
OpReturn
OpFunctionEnd
%function = OpFunction %void None %6
%16 = OpLabel
%17 = OpVariable %_ptr_Function_uint Function
%18 = OpVariable %_ptr_Function_uint Function
OpStore %17 %uint_0
OpBranch %19
%19 = OpLabel
%20 = OpLoad %uint %17
%20 = OpPhi %uint %uint_0 %2 %34 %23
%21 = OpULessThan %bool %20 %uint_1
OpLoopMerge %22 %23 DontUnroll
OpBranchConditional %21 %24 %22
%24 = OpLabel
OpStore %18 %uint_1
OpBranch %25
%25 = OpLabel
%26 = OpLoad %uint %18
%27 = OpINotEqual %bool %26 %uint_0
%27 = OpINotEqual %bool %uint_1 %uint_0
OpLoopMerge %28 %29 DontUnroll
OpBranchConditional %27 %30 %28
%30 = OpLabel
@ -977,110 +966,32 @@ TEST_F(MergeReturnPassTest, NestedLoopMerge) {
%32 = OpLabel
OpReturn
%31 = OpLabel
OpStore %18 %uint_1
OpBranch %29
%29 = OpLabel
OpBranch %25
%28 = OpLabel
OpBranch %23
%23 = OpLabel
%33 = OpLoad %uint %17
%34 = OpIAdd %uint %33 %uint_1
OpStore %17 %34
%34 = OpIAdd %uint %20 %uint_1
OpBranch %19
%22 = OpLabel
OpReturn
OpFunctionEnd
)";
const std::string after =
R"(OpCapability SampledBuffer
OpCapability StorageImageExtendedFormats
OpCapability Shader
%1 = OpExtInstImport "GLSL.std.450"
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %2 "CS"
OpExecutionMode %2 LocalSize 8 8 1
OpSource HLSL 600
OpName %function "function"
%uint = OpTypeInt 32 0
%void = OpTypeVoid
%6 = OpTypeFunction %void
%uint_0 = OpConstant %uint 0
%uint_1 = OpConstant %uint 1
%v3uint = OpTypeVector %uint 3
%bool = OpTypeBool
%true = OpConstantTrue %bool
%_ptr_Function_uint = OpTypePointer Function %uint
%_struct_13 = OpTypeStruct %v3uint %v3uint %v3uint %uint %uint %uint %uint %uint %uint
%false = OpConstantFalse %bool
%_ptr_Function_bool = OpTypePointer Function %bool
%2 = OpFunction %void None %6
%14 = OpLabel
%15 = OpFunctionCall %void %function
OpReturn
OpFunctionEnd
%function = OpFunction %void None %6
%16 = OpLabel
%38 = OpVariable %_ptr_Function_bool Function %false
%17 = OpVariable %_ptr_Function_uint Function
%18 = OpVariable %_ptr_Function_uint Function
OpStore %17 %uint_0
OpBranch %19
%19 = OpLabel
%20 = OpLoad %uint %17
%21 = OpULessThan %bool %20 %uint_1
OpLoopMerge %22 %23 DontUnroll
OpBranchConditional %21 %24 %22
%24 = OpLabel
OpStore %18 %uint_1
OpBranch %25
%25 = OpLabel
%26 = OpLoad %uint %18
%27 = OpINotEqual %bool %26 %uint_0
OpLoopMerge %28 %29 DontUnroll
OpBranchConditional %27 %30 %28
%30 = OpLabel
OpSelectionMerge %31 None
OpBranchConditional %true %32 %31
%32 = OpLabel
OpStore %38 %true
OpBranch %28
%31 = OpLabel
OpStore %18 %uint_1
OpBranch %29
%29 = OpLabel
OpBranch %25
%28 = OpLabel
%40 = OpLoad %bool %38
OpBranchConditional %40 %22 %39
%39 = OpLabel
OpBranch %23
%23 = OpLabel
%33 = OpLoad %uint %17
%34 = OpIAdd %uint %33 %uint_1
OpStore %17 %34
OpBranch %19
%22 = OpLabel
%43 = OpLoad %bool %38
OpSelectionMerge %42 None
OpBranchConditional %43 %42 %41
%41 = OpLabel
OpStore %38 %true
OpBranch %42
%42 = OpLabel
OpBranch %35
%35 = OpLabel
OpReturn
OpFunctionEnd
)";
SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
SinglePassRunAndMatch<MergeReturnPass>(test, false);
}
TEST_F(MergeReturnPassTest, ReturnValueDecoration) {
const std::string before =
R"(OpCapability Linkage
const std::string test =
R"(
; CHECK: OpDecorate [[func:%\w+]] RelaxedPrecision
; CHECK: OpDecorate [[ret_val:%\w+]] RelaxedPrecision
; CHECK: [[func]] = OpFunction
; CHECK-NEXT: OpLabel
; CHECK-NOT: OpLabel
; CHECK: [[ret_val]] = OpVariable
OpCapability Linkage
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %11 "simple_shader"
@ -1107,51 +1018,7 @@ OpReturnValue %5
OpFunctionEnd
)";
const std::string after =
R"(OpCapability Linkage
OpCapability Shader
OpMemoryModel Logical GLSL450
OpEntryPoint GLCompute %11 "simple_shader"
OpDecorate %7 RelaxedPrecision
OpDecorate %17 RelaxedPrecision
OpDecorate %18 RelaxedPrecision
%12 = OpTypeVoid
%1 = OpTypeInt 32 0
%2 = OpTypeBool
%3 = OpConstantFalse %2
%4 = OpConstant %1 0
%5 = OpConstant %1 1
%6 = OpTypeFunction %1
%13 = OpTypeFunction %12
%16 = OpTypePointer Function %1
%19 = OpTypePointer Function %2
%21 = OpConstantTrue %2
%11 = OpFunction %12 None %13
%14 = OpLabel
OpReturn
OpFunctionEnd
%7 = OpFunction %1 None %6
%8 = OpLabel
%20 = OpVariable %19 Function %3
%17 = OpVariable %16 Function
OpBranchConditional %3 %9 %10
%9 = OpLabel
OpStore %20 %21
OpStore %17 %4
OpBranch %15
%10 = OpLabel
OpStore %20 %21
OpStore %17 %5
OpBranch %15
%15 = OpLabel
%18 = OpLoad %1 %17
OpReturnValue %18
OpFunctionEnd
)";
SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
SinglePassRunAndCheck<MergeReturnPass>(before, after, false, true);
SinglePassRunAndMatch<MergeReturnPass>(test, false);
}
} // namespace