// 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 SOURCE_OPT_MERGE_RETURN_PASS_H_ #define SOURCE_OPT_MERGE_RETURN_PASS_H_ #include #include #include #include "source/opt/basic_block.h" #include "source/opt/function.h" #include "source/opt/mem_pass.h" namespace spvtools { namespace opt { /******************************************************************************* * * Handling Structured Control Flow: * * 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 a containing construct's merge * block. * * Beyond this, we further assume that all unreachable blocks have been * cleaned up. 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 construct's merge. This * merge will always exist because a single case switch 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 * (or switch if no loop) it is contained in. Once structured control flow has * been exited, it will be at the merge of the single case switch, which 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 * 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 (loop header) * / \ * (ret) 2 3 (merge) * * || * \/ * * 0 (single case switch header) * | * 1 (loop header) * / \ * 2 | (merge) * \ / * 3' (merge) * / \ * | 3 (original code in 3) * \ / * (ret) 4 (single case switch merge) * * In the above (simple) example, the return originally in |2| is passed through * the loop 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. * ******************************************************************************/ // 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() override; IRContext::Analysis GetPreservedAnalyses() override { return IRContext::kAnalysisConstants | IRContext::kAnalysisTypes; } private: // This class is used to store the a break merge instruction and a current // merge instruction. The intended use is to keep track of the block to // break to and the current innermost control flow construct merge block. class StructuredControlState { public: StructuredControlState(Instruction* break_merge, Instruction* merge) : break_merge_(break_merge), current_merge_(merge) {} bool InBreakable() const { return break_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 BreakMergeId() const { return break_merge_ ? break_merge_->GetSingleWordInOperand(0u) : 0u; } Instruction* BreakMergeInst() const { return break_merge_; } private: Instruction* break_merge_; Instruction* current_merge_; }; // Returns all BasicBlocks terminated by OpReturn or OpReturnValue in // |function|. std::vector CollectReturnBlocks(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(Function* function, const std::vector& returnBlocks); // Generate and push new control flow state if |block| contains a merge. void GenerateState(BasicBlock* block); // 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. bool ProcessStructured(Function* function, const std::vector& 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(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(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(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(BasicBlock* block, uint32_t target); // 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 // selections constructs to jump around these instructions. // // If new blocks that are created will be added to |order|. This way a call // can traverse these new block in structured order. // // Returns true if successful. bool PredicateBlocks(BasicBlock* return_block, std::unordered_set* pSet, std::list* order); // Add a conditional branch at the start of |block| that either jumps to // the merge block of |break_merge_inst| or the original code in |block| // depending on the value in |return_flag_|. The continue target in // |break_merge_inst| will be updated if needed. // // If new blocks that are created will be added to |order|. This way a call // can traverse these new block in structured order. // // Returns true if successful. bool BreakFromConstruct(BasicBlock* block, std::unordered_set* predicated, std::list* order, Instruction* break_merge_inst); // 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); // 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|. // 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(BasicBlock* merge_block, Instruction& inst); // Add new phi nodes for any id that no longer dominate all of it uses. A phi // node is added to a block |bb| for an id if the id is defined between the // original immediate dominator of |bb| and its new immediate dominator. 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|. |AddNewPhiNodes| must // have already been called on the original dominators of |bb|. void AddNewPhiNodes(BasicBlock* bb); // Records the terminator of immediate dominator for every basic block in // |function|. void RecordImmediateDominators(Function* function); // Modifies existing OpPhi instruction in |target| block to account for the // new edge from |new_source|. The value for that edge will be an Undef. // // The CFG must not include the edge from |new_source| to |target| yet. void UpdatePhiNodes(BasicBlock* new_source, BasicBlock* target); StructuredControlState& CurrentState() { return state_.back(); } // Inserts |new_element| into |list| after the first occurrence of |element|. // |element| must be in |list| at least once. void InsertAfterElement(BasicBlock* element, BasicBlock* new_element, std::list* list); // Creates a single case switch around all of the executable code of the // current function where the switch and case value are both zero and the // default is the merge block. Returns after the switch is executed. Sets // |final_return_block_|. bool AddSingleCaseSwitchAroundFunction(); // 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 one case switch around the executable code of the function with // |merge_target| as the merge node. bool CreateSingleCaseSwitch(BasicBlock* merge_target); // Returns true if |function| has an unreachable block that is not a continue // target that simply branches back to the header, or a merge block containing // 1 instruction which is OpUnreachable. bool HasNontrivialUnreachableBlocks(Function* function); // A stack used to keep track of the break and current control flow construct // merge blocks. std::vector state_; // The current function being transformed. Function* function_; // The |OpVariable| instruction defining a boolean variable used to keep track // of whether or not the function is trying to return. 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|. Instruction* return_value_; // The instruction defining the boolean constant true. Instruction* constant_true_; // The basic block that is suppose to become the contain the only return value // after processing the current function. BasicBlock* final_return_block_; // This is a map from a node to its original immediate dominator identified by // the terminator if that block. We use the terminator because the block we // want may change if the block is split. std::unordered_map original_dominator_; // A map from a basic block, bb, to the set of basic blocks which represent // the new edges that reach |bb|. std::unordered_map> new_edges_; // Contains all return blocks that are merged. This is set is populated while // processing structured blocks and used to properly construct OpPhi // instructions. std::unordered_set return_blocks_; }; } // namespace opt } // namespace spvtools #endif // SOURCE_OPT_MERGE_RETURN_PASS_H_