1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337
|
// 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 <unordered_map>
#include <unordered_set>
#include <vector>
#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) {}
StructuredControlState(const StructuredControlState&) = default;
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<BasicBlock*> 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<BasicBlock*>& 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<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(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<BasicBlock*>* pSet,
std::list<BasicBlock*>* 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<BasicBlock*>* predicated,
std::list<BasicBlock*>* 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 immidiate 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<BasicBlock*>* list);
// Creates a single case switch around all of the exectuable 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_|.
void 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.
void 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<StructuredControlState> 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<BasicBlock*, Instruction*> 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<BasicBlock*, std::set<uint32_t>> 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<uint32_t> return_blocks_;
};
} // namespace opt
} // namespace spvtools
#endif // SOURCE_OPT_MERGE_RETURN_PASS_H_
|