| // 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_ |