blob: b4f47e3ad12e43685486fdf4e599aae4e49268a2 [file] [log] [blame]
// Copyright (c) 2017 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#ifndef LIBSPIRV_OPT_MERGE_RETURN_PASS_H_
#define LIBSPIRV_OPT_MERGE_RETURN_PASS_H_
#include "basic_block.h"
#include "function.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 MemPass {
public:
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::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);
// 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.
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
} // namespace spvtools
#endif // LIBSPIRV_OPT_MERGE_RETURN_PASS_H_