/src/spirv-tools/source/opt/loop_utils.cpp
Line | Count | Source |
1 | | // Copyright (c) 2018 Google LLC. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // http://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include <algorithm> |
16 | | #include <memory> |
17 | | #include <unordered_map> |
18 | | #include <unordered_set> |
19 | | #include <utility> |
20 | | #include <vector> |
21 | | |
22 | | #include "source/cfa.h" |
23 | | #include "source/opt/cfg.h" |
24 | | #include "source/opt/ir_builder.h" |
25 | | #include "source/opt/ir_context.h" |
26 | | #include "source/opt/loop_descriptor.h" |
27 | | #include "source/opt/loop_utils.h" |
28 | | |
29 | | namespace spvtools { |
30 | | namespace opt { |
31 | | namespace { |
32 | | // Return true if |bb| is dominated by at least one block in |exits| |
33 | | inline bool DominatesAnExit(BasicBlock* bb, |
34 | | const std::unordered_set<BasicBlock*>& exits, |
35 | 0 | const DominatorTree& dom_tree) { |
36 | 0 | for (BasicBlock* e_bb : exits) |
37 | 0 | if (dom_tree.Dominates(bb, e_bb)) return true; |
38 | 0 | return false; |
39 | 0 | } |
40 | | |
41 | | // Utility class to rewrite out-of-loop uses of an in-loop definition in terms |
42 | | // of phi instructions to achieve a LCSSA form. |
43 | | // For a given definition, the class user registers phi instructions using that |
44 | | // definition in all loop exit blocks by which the definition escapes. |
45 | | // Then, when rewriting a use of the definition, the rewriter walks the |
46 | | // paths from the use the loop exits. At each step, it will insert a phi |
47 | | // instruction to merge the incoming value according to exit blocks definition. |
48 | | class LCSSARewriter { |
49 | | public: |
50 | | LCSSARewriter(IRContext* context, const DominatorTree& dom_tree, |
51 | | const std::unordered_set<BasicBlock*>& exit_bb, |
52 | | BasicBlock* merge_block) |
53 | 0 | : context_(context), |
54 | 0 | cfg_(context_->cfg()), |
55 | 0 | dom_tree_(dom_tree), |
56 | 0 | exit_bb_(exit_bb), |
57 | 0 | merge_block_id_(merge_block ? merge_block->id() : 0) {} |
58 | | |
59 | | struct UseRewriter { |
60 | | explicit UseRewriter(LCSSARewriter* base, const Instruction& def_insn) |
61 | 0 | : base_(base), def_insn_(def_insn) {} |
62 | | // Rewrites the use of |def_insn_| by the instruction |user| at the index |
63 | | // |operand_index| in terms of phi instruction. This recursively builds new |
64 | | // phi instructions from |user| to the loop exit blocks' phis. The use of |
65 | | // |def_insn_| in |user| is replaced by the relevant phi instruction at the |
66 | | // end of the operation. |
67 | | // It is assumed that |user| does not dominates any of the loop exit basic |
68 | | // block. This operation does not update the def/use manager, instead it |
69 | | // records what needs to be updated. The actual update is performed by |
70 | | // UpdateManagers. |
71 | 0 | bool RewriteUse(BasicBlock* bb, Instruction* user, uint32_t operand_index) { |
72 | 0 | assert( |
73 | 0 | (user->opcode() != spv::Op::OpPhi || bb != GetParent(user)) && |
74 | 0 | "The root basic block must be the incoming edge if |user| is a phi " |
75 | 0 | "instruction"); |
76 | 0 | assert((user->opcode() == spv::Op::OpPhi || bb == GetParent(user)) && |
77 | 0 | "The root basic block must be the instruction parent if |user| is " |
78 | 0 | "not " |
79 | 0 | "phi instruction"); |
80 | | |
81 | 0 | Instruction* new_def = GetOrBuildIncoming(bb->id()); |
82 | 0 | if (!new_def) { |
83 | 0 | return false; |
84 | 0 | } |
85 | | |
86 | 0 | user->SetOperand(operand_index, {new_def->result_id()}); |
87 | 0 | rewritten_.insert(user); |
88 | 0 | return true; |
89 | 0 | } |
90 | | |
91 | | // In-place update of some managers (avoid full invalidation). |
92 | 0 | inline void UpdateManagers() { |
93 | 0 | analysis::DefUseManager* def_use_mgr = base_->context_->get_def_use_mgr(); |
94 | | // Register all new definitions. |
95 | 0 | for (Instruction* insn : rewritten_) { |
96 | 0 | def_use_mgr->AnalyzeInstDef(insn); |
97 | 0 | } |
98 | | // Register all new uses. |
99 | 0 | for (Instruction* insn : rewritten_) { |
100 | 0 | def_use_mgr->AnalyzeInstUse(insn); |
101 | 0 | } |
102 | 0 | } |
103 | | |
104 | | private: |
105 | | // Return the basic block that |instr| belongs to. |
106 | 0 | BasicBlock* GetParent(Instruction* instr) { |
107 | 0 | return base_->context_->get_instr_block(instr); |
108 | 0 | } |
109 | | |
110 | | // Builds a phi instruction for the basic block |bb|. The function assumes |
111 | | // that |defining_blocks| contains the list of basic block that define the |
112 | | // usable value for each predecessor of |bb|. |
113 | | inline Instruction* CreatePhiInstruction( |
114 | 0 | BasicBlock* bb, const std::vector<uint32_t>& defining_blocks) { |
115 | 0 | std::vector<uint32_t> incomings; |
116 | 0 | const std::vector<uint32_t>& bb_preds = base_->cfg_->preds(bb->id()); |
117 | 0 | assert(bb_preds.size() == defining_blocks.size()); |
118 | 0 | for (size_t i = 0; i < bb_preds.size(); i++) { |
119 | 0 | incomings.push_back( |
120 | 0 | GetOrBuildIncoming(defining_blocks[i])->result_id()); |
121 | 0 | incomings.push_back(bb_preds[i]); |
122 | 0 | } |
123 | 0 | InstructionBuilder builder(base_->context_, &*bb->begin(), |
124 | 0 | IRContext::kAnalysisInstrToBlockMapping); |
125 | 0 | Instruction* incoming_phi = |
126 | 0 | builder.AddPhi(def_insn_.type_id(), incomings); |
127 | 0 | if (!incoming_phi) { |
128 | 0 | return nullptr; |
129 | 0 | } |
130 | | |
131 | 0 | rewritten_.insert(incoming_phi); |
132 | 0 | return incoming_phi; |
133 | 0 | } |
134 | | |
135 | | // Builds a phi instruction for the basic block |bb|, all incoming values |
136 | | // will be |value|. |
137 | | inline Instruction* CreatePhiInstruction(BasicBlock* bb, |
138 | 0 | const Instruction& value) { |
139 | 0 | std::vector<uint32_t> incomings; |
140 | 0 | const std::vector<uint32_t>& bb_preds = base_->cfg_->preds(bb->id()); |
141 | 0 | for (size_t i = 0; i < bb_preds.size(); i++) { |
142 | 0 | incomings.push_back(value.result_id()); |
143 | 0 | incomings.push_back(bb_preds[i]); |
144 | 0 | } |
145 | 0 | InstructionBuilder builder(base_->context_, &*bb->begin(), |
146 | 0 | IRContext::kAnalysisInstrToBlockMapping); |
147 | 0 | Instruction* incoming_phi = |
148 | 0 | builder.AddPhi(def_insn_.type_id(), incomings); |
149 | 0 | if (!incoming_phi) { |
150 | 0 | return nullptr; |
151 | 0 | } |
152 | | |
153 | 0 | rewritten_.insert(incoming_phi); |
154 | 0 | return incoming_phi; |
155 | 0 | } |
156 | | |
157 | | // Return the new def to use for the basic block |bb_id|. |
158 | | // If |bb_id| does not have a suitable def to use then we: |
159 | | // - return the common def used by all predecessors; |
160 | | // - if there is no common def, then we build a new phi instr at the |
161 | | // beginning of |bb_id| and return this new instruction. |
162 | 0 | Instruction* GetOrBuildIncoming(uint32_t bb_id) { |
163 | 0 | assert(base_->cfg_->block(bb_id) != nullptr && "Unknown basic block"); |
164 | | |
165 | 0 | Instruction*& incoming_phi = bb_to_phi_[bb_id]; |
166 | 0 | if (incoming_phi) { |
167 | 0 | return incoming_phi; |
168 | 0 | } |
169 | | |
170 | 0 | BasicBlock* bb = &*base_->cfg_->block(bb_id); |
171 | | // If this is an exit basic block, look if there already is an eligible |
172 | | // phi instruction. An eligible phi has |def_insn_| as all incoming |
173 | | // values. |
174 | 0 | if (base_->exit_bb_.count(bb)) { |
175 | | // Look if there is an eligible phi in this block. |
176 | 0 | if (!bb->WhileEachPhiInst([&incoming_phi, this](Instruction* phi) { |
177 | 0 | for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) { |
178 | 0 | if (phi->GetSingleWordInOperand(i) != def_insn_.result_id()) |
179 | 0 | return true; |
180 | 0 | } |
181 | 0 | incoming_phi = phi; |
182 | 0 | rewritten_.insert(incoming_phi); |
183 | 0 | return false; |
184 | 0 | })) { |
185 | 0 | return incoming_phi; |
186 | 0 | } |
187 | 0 | incoming_phi = CreatePhiInstruction(bb, def_insn_); |
188 | 0 | return incoming_phi; |
189 | 0 | } |
190 | | |
191 | | // Get the block that defines the value to use for each predecessor. |
192 | | // If the vector has 1 value, then it means that this block does not need |
193 | | // to build a phi instruction unless |bb_id| is the loop merge block. |
194 | 0 | const std::vector<uint32_t>& defining_blocks = |
195 | 0 | base_->GetDefiningBlocks(bb_id); |
196 | | |
197 | | // Special case for structured loops: merge block might be different from |
198 | | // the exit block set. To maintain structured properties it will ease |
199 | | // transformations if the merge block also holds a phi instruction like |
200 | | // the exit ones. |
201 | 0 | if (defining_blocks.size() > 1 || bb_id == base_->merge_block_id_) { |
202 | 0 | if (defining_blocks.size() > 1) { |
203 | 0 | incoming_phi = CreatePhiInstruction(bb, defining_blocks); |
204 | 0 | } else { |
205 | 0 | assert(bb_id == base_->merge_block_id_); |
206 | 0 | incoming_phi = |
207 | 0 | CreatePhiInstruction(bb, *GetOrBuildIncoming(defining_blocks[0])); |
208 | 0 | } |
209 | 0 | } else { |
210 | 0 | incoming_phi = GetOrBuildIncoming(defining_blocks[0]); |
211 | 0 | } |
212 | | |
213 | 0 | return incoming_phi; |
214 | 0 | } |
215 | | |
216 | | LCSSARewriter* base_; |
217 | | const Instruction& def_insn_; |
218 | | std::unordered_map<uint32_t, Instruction*> bb_to_phi_; |
219 | | std::unordered_set<Instruction*> rewritten_; |
220 | | }; |
221 | | |
222 | | private: |
223 | | // Return the new def to use for the basic block |bb_id|. |
224 | | // If |bb_id| does not have a suitable def to use then we: |
225 | | // - return the common def used by all predecessors; |
226 | | // - if there is no common def, then we build a new phi instr at the |
227 | | // beginning of |bb_id| and return this new instruction. |
228 | 0 | const std::vector<uint32_t>& GetDefiningBlocks(uint32_t bb_id) { |
229 | 0 | assert(cfg_->block(bb_id) != nullptr && "Unknown basic block"); |
230 | 0 | std::vector<uint32_t>& defining_blocks = bb_to_defining_blocks_[bb_id]; |
231 | |
|
232 | 0 | if (defining_blocks.size()) return defining_blocks; |
233 | | |
234 | | // Check if one of the loop exit basic block dominates |bb_id|. |
235 | 0 | for (const BasicBlock* e_bb : exit_bb_) { |
236 | 0 | if (dom_tree_.Dominates(e_bb->id(), bb_id)) { |
237 | 0 | defining_blocks.push_back(e_bb->id()); |
238 | 0 | return defining_blocks; |
239 | 0 | } |
240 | 0 | } |
241 | | |
242 | | // Process parents, they will returns their suitable blocks. |
243 | | // If they are all the same, this means this basic block is dominated by a |
244 | | // common block, so we won't need to build a phi instruction. |
245 | 0 | for (uint32_t pred_id : cfg_->preds(bb_id)) { |
246 | 0 | const std::vector<uint32_t>& pred_blocks = GetDefiningBlocks(pred_id); |
247 | 0 | if (pred_blocks.size() == 1) |
248 | 0 | defining_blocks.push_back(pred_blocks[0]); |
249 | 0 | else |
250 | 0 | defining_blocks.push_back(pred_id); |
251 | 0 | } |
252 | 0 | assert(defining_blocks.size()); |
253 | 0 | if (std::all_of(defining_blocks.begin(), defining_blocks.end(), |
254 | 0 | [&defining_blocks](uint32_t id) { |
255 | 0 | return id == defining_blocks[0]; |
256 | 0 | })) { |
257 | | // No need for a phi. |
258 | 0 | defining_blocks.resize(1); |
259 | 0 | } |
260 | |
|
261 | 0 | return defining_blocks; |
262 | 0 | } |
263 | | |
264 | | IRContext* context_; |
265 | | CFG* cfg_; |
266 | | const DominatorTree& dom_tree_; |
267 | | const std::unordered_set<BasicBlock*>& exit_bb_; |
268 | | uint32_t merge_block_id_; |
269 | | // This map represent the set of known paths. For each key, the vector |
270 | | // represent the set of blocks holding the definition to be used to build the |
271 | | // phi instruction. |
272 | | // If the vector has 0 value, then the path is unknown yet, and must be built. |
273 | | // If the vector has 1 value, then the value defined by that basic block |
274 | | // should be used. |
275 | | // If the vector has more than 1 value, then a phi node must be created, the |
276 | | // basic block ordering is the same as the predecessor ordering. |
277 | | std::unordered_map<uint32_t, std::vector<uint32_t>> bb_to_defining_blocks_; |
278 | | }; |
279 | | |
280 | | // Make the set |blocks| closed SSA. The set is closed SSA if all the uses |
281 | | // outside the set are phi instructions in exiting basic block set (hold by |
282 | | // |lcssa_rewriter|). |
283 | | inline bool MakeSetClosedSSA(IRContext* context, Function* function, |
284 | | const std::unordered_set<uint32_t>& blocks, |
285 | | const std::unordered_set<BasicBlock*>& exit_bb, |
286 | 0 | LCSSARewriter* lcssa_rewriter) { |
287 | 0 | CFG& cfg = *context->cfg(); |
288 | 0 | DominatorTree& dom_tree = |
289 | 0 | context->GetDominatorAnalysis(function)->GetDomTree(); |
290 | 0 | analysis::DefUseManager* def_use_manager = context->get_def_use_mgr(); |
291 | |
|
292 | 0 | for (uint32_t bb_id : blocks) { |
293 | 0 | BasicBlock* bb = cfg.block(bb_id); |
294 | | // If bb does not dominate an exit block, then it cannot have escaping defs. |
295 | 0 | if (!DominatesAnExit(bb, exit_bb, dom_tree)) continue; |
296 | 0 | for (Instruction& inst : *bb) { |
297 | 0 | LCSSARewriter::UseRewriter rewriter(lcssa_rewriter, inst); |
298 | 0 | bool success = def_use_manager->WhileEachUse( |
299 | 0 | &inst, [&blocks, &rewriter, &exit_bb, context]( |
300 | 0 | Instruction* use, uint32_t operand_index) { |
301 | 0 | BasicBlock* use_parent = context->get_instr_block(use); |
302 | 0 | assert(use_parent); |
303 | 0 | if (blocks.count(use_parent->id())) return true; |
304 | | |
305 | 0 | if (use->opcode() == spv::Op::OpPhi) { |
306 | | // If the use is a Phi instruction and the incoming block is |
307 | | // coming from the loop, then that's consistent with LCSSA form. |
308 | 0 | if (exit_bb.count(use_parent)) { |
309 | 0 | return true; |
310 | 0 | } else { |
311 | | // That's not an exit block, but the user is a phi instruction. |
312 | | // Consider the incoming branch only. |
313 | 0 | use_parent = context->get_instr_block( |
314 | 0 | use->GetSingleWordOperand(operand_index + 1)); |
315 | 0 | } |
316 | 0 | } |
317 | | // Rewrite the use. Note that this call does not invalidate the |
318 | | // def/use manager. So this operation is safe. |
319 | 0 | return rewriter.RewriteUse(use_parent, use, operand_index); |
320 | 0 | }); |
321 | 0 | if (!success) { |
322 | 0 | return false; |
323 | 0 | } |
324 | 0 | rewriter.UpdateManagers(); |
325 | 0 | } |
326 | 0 | } |
327 | 0 | return true; |
328 | 0 | } |
329 | | |
330 | | } // namespace |
331 | | |
332 | 0 | bool LoopUtils::CreateLoopDedicatedExits() { |
333 | 0 | Function* function = loop_->GetHeaderBlock()->GetParent(); |
334 | 0 | LoopDescriptor& loop_desc = *context_->GetLoopDescriptor(function); |
335 | 0 | CFG& cfg = *context_->cfg(); |
336 | 0 | analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); |
337 | |
|
338 | 0 | const IRContext::Analysis PreservedAnalyses = |
339 | 0 | IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping; |
340 | | |
341 | | // Gathers the set of basic block that are not in this loop and have at least |
342 | | // one predecessor in the loop and one not in the loop. |
343 | 0 | std::unordered_set<uint32_t> exit_bb_set; |
344 | 0 | loop_->GetExitBlocks(&exit_bb_set); |
345 | |
|
346 | 0 | std::unordered_set<BasicBlock*> new_loop_exits; |
347 | 0 | bool made_change = false; |
348 | | // For each block, we create a new one that gathers all branches from |
349 | | // the loop and fall into the block. |
350 | 0 | for (uint32_t non_dedicate_id : exit_bb_set) { |
351 | 0 | BasicBlock* non_dedicate = cfg.block(non_dedicate_id); |
352 | 0 | const std::vector<uint32_t>& bb_pred = cfg.preds(non_dedicate_id); |
353 | | // Ignore the block if all the predecessors are in the loop. |
354 | 0 | if (std::all_of(bb_pred.begin(), bb_pred.end(), |
355 | 0 | [this](uint32_t id) { return loop_->IsInsideLoop(id); })) { |
356 | 0 | new_loop_exits.insert(non_dedicate); |
357 | 0 | continue; |
358 | 0 | } |
359 | | |
360 | 0 | made_change = true; |
361 | 0 | Function::iterator insert_pt = function->begin(); |
362 | 0 | for (; insert_pt != function->end() && &*insert_pt != non_dedicate; |
363 | 0 | ++insert_pt) { |
364 | 0 | } |
365 | 0 | assert(insert_pt != function->end() && "Basic Block not found"); |
366 | | |
367 | | // Create the dedicate exit basic block. |
368 | 0 | uint32_t exit_id = context_->TakeNextId(); |
369 | 0 | if (exit_id == 0) { |
370 | 0 | return false; |
371 | 0 | } |
372 | 0 | BasicBlock& exit = *insert_pt.InsertBefore( |
373 | 0 | std::unique_ptr<BasicBlock>(new BasicBlock(std::unique_ptr<Instruction>( |
374 | 0 | new Instruction(context_, spv::Op::OpLabel, 0, exit_id, {}))))); |
375 | 0 | exit.SetParent(function); |
376 | | |
377 | | // Redirect in loop predecessors to |exit| block. |
378 | 0 | for (uint32_t exit_pred_id : bb_pred) { |
379 | 0 | if (loop_->IsInsideLoop(exit_pred_id)) { |
380 | 0 | BasicBlock* pred_block = cfg.block(exit_pred_id); |
381 | 0 | pred_block->ForEachSuccessorLabel([non_dedicate, &exit](uint32_t* id) { |
382 | 0 | if (*id == non_dedicate->id()) *id = exit.id(); |
383 | 0 | }); |
384 | | // Update the CFG. |
385 | | // |non_dedicate|'s predecessor list will be updated at the end of the |
386 | | // loop. |
387 | 0 | cfg.RegisterBlock(pred_block); |
388 | 0 | } |
389 | 0 | } |
390 | | |
391 | | // Register the label to the def/use manager, requires for the phi patching. |
392 | 0 | def_use_mgr->AnalyzeInstDefUse(exit.GetLabelInst()); |
393 | 0 | context_->set_instr_block(exit.GetLabelInst(), &exit); |
394 | |
|
395 | 0 | InstructionBuilder builder(context_, &exit, PreservedAnalyses); |
396 | | // Now jump from our dedicate basic block to the old exit. |
397 | | // We also reset the insert point so all instructions are inserted before |
398 | | // the branch. |
399 | 0 | builder.SetInsertPoint(builder.AddBranch(non_dedicate->id())); |
400 | 0 | bool succeeded = non_dedicate->WhileEachPhiInst( |
401 | 0 | [&builder, &exit, def_use_mgr, this](Instruction* phi) { |
402 | | // New phi operands for this instruction. |
403 | 0 | std::vector<uint32_t> new_phi_op; |
404 | | // Phi operands for the dedicated exit block. |
405 | 0 | std::vector<uint32_t> exit_phi_op; |
406 | 0 | for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) { |
407 | 0 | uint32_t def_id = phi->GetSingleWordInOperand(i); |
408 | 0 | uint32_t incoming_id = phi->GetSingleWordInOperand(i + 1); |
409 | 0 | if (loop_->IsInsideLoop(incoming_id)) { |
410 | 0 | exit_phi_op.push_back(def_id); |
411 | 0 | exit_phi_op.push_back(incoming_id); |
412 | 0 | } else { |
413 | 0 | new_phi_op.push_back(def_id); |
414 | 0 | new_phi_op.push_back(incoming_id); |
415 | 0 | } |
416 | 0 | } |
417 | | |
418 | | // Build the new phi instruction dedicated exit block. |
419 | 0 | Instruction* exit_phi = builder.AddPhi(phi->type_id(), exit_phi_op); |
420 | 0 | if (!exit_phi) { |
421 | 0 | return false; |
422 | 0 | } |
423 | | // Build the new incoming branch. |
424 | 0 | new_phi_op.push_back(exit_phi->result_id()); |
425 | 0 | new_phi_op.push_back(exit.id()); |
426 | | // Rewrite operands. |
427 | 0 | uint32_t idx = 0; |
428 | 0 | for (; idx < new_phi_op.size(); idx++) |
429 | 0 | phi->SetInOperand(idx, {new_phi_op[idx]}); |
430 | | // Remove extra operands, from last to first (more efficient). |
431 | 0 | for (uint32_t j = phi->NumInOperands() - 1; j >= idx; j--) |
432 | 0 | phi->RemoveInOperand(j); |
433 | | // Update the def/use manager for this |phi|. |
434 | 0 | def_use_mgr->AnalyzeInstUse(phi); |
435 | 0 | return true; |
436 | 0 | }); |
437 | 0 | if (!succeeded) return false; |
438 | | // Update the CFG. |
439 | 0 | cfg.RegisterBlock(&exit); |
440 | 0 | cfg.RemoveNonExistingEdges(non_dedicate->id()); |
441 | 0 | new_loop_exits.insert(&exit); |
442 | | // If non_dedicate is in a loop, add the new dedicated exit in that loop. |
443 | 0 | if (Loop* parent_loop = loop_desc[non_dedicate]) |
444 | 0 | parent_loop->AddBasicBlock(&exit); |
445 | 0 | } |
446 | | |
447 | 0 | if (new_loop_exits.size() == 1) { |
448 | 0 | loop_->SetMergeBlock(*new_loop_exits.begin()); |
449 | 0 | } |
450 | |
|
451 | 0 | if (made_change) { |
452 | 0 | context_->InvalidateAnalysesExceptFor( |
453 | 0 | PreservedAnalyses | IRContext::kAnalysisCFG | |
454 | 0 | IRContext::Analysis::kAnalysisLoopAnalysis); |
455 | 0 | } |
456 | 0 | return true; |
457 | 0 | } |
458 | | |
459 | 0 | bool LoopUtils::MakeLoopClosedSSA() { |
460 | 0 | if (!CreateLoopDedicatedExits()) { |
461 | 0 | return false; |
462 | 0 | } |
463 | | |
464 | 0 | Function* function = loop_->GetHeaderBlock()->GetParent(); |
465 | 0 | CFG& cfg = *context_->cfg(); |
466 | 0 | DominatorTree& dom_tree = |
467 | 0 | context_->GetDominatorAnalysis(function)->GetDomTree(); |
468 | |
|
469 | 0 | std::unordered_set<BasicBlock*> exit_bb; |
470 | 0 | { |
471 | 0 | std::unordered_set<uint32_t> exit_bb_id; |
472 | 0 | loop_->GetExitBlocks(&exit_bb_id); |
473 | 0 | for (uint32_t bb_id : exit_bb_id) { |
474 | 0 | exit_bb.insert(cfg.block(bb_id)); |
475 | 0 | } |
476 | 0 | } |
477 | |
|
478 | 0 | LCSSARewriter lcssa_rewriter(context_, dom_tree, exit_bb, |
479 | 0 | loop_->GetMergeBlock()); |
480 | 0 | if (!MakeSetClosedSSA(context_, function, loop_->GetBlocks(), exit_bb, |
481 | 0 | &lcssa_rewriter)) { |
482 | 0 | return false; |
483 | 0 | } |
484 | | |
485 | | // Make sure all defs post-dominated by the merge block have their last use no |
486 | | // further than the merge block. |
487 | 0 | if (loop_->GetMergeBlock()) { |
488 | 0 | std::unordered_set<uint32_t> merging_bb_id; |
489 | 0 | loop_->GetMergingBlocks(&merging_bb_id); |
490 | 0 | merging_bb_id.erase(loop_->GetMergeBlock()->id()); |
491 | | // Reset the exit set, now only the merge block is the exit. |
492 | 0 | exit_bb.clear(); |
493 | 0 | exit_bb.insert(loop_->GetMergeBlock()); |
494 | | // LCSSARewriter is reusable here only because it forces the creation of a |
495 | | // phi instruction in the merge block. |
496 | 0 | if (!MakeSetClosedSSA(context_, function, merging_bb_id, exit_bb, |
497 | 0 | &lcssa_rewriter)) { |
498 | 0 | return false; |
499 | 0 | } |
500 | 0 | } |
501 | | |
502 | 0 | context_->InvalidateAnalysesExceptFor( |
503 | 0 | IRContext::Analysis::kAnalysisCFG | |
504 | 0 | IRContext::Analysis::kAnalysisDominatorAnalysis | |
505 | 0 | IRContext::Analysis::kAnalysisLoopAnalysis); |
506 | 0 | return true; |
507 | 0 | } |
508 | | |
509 | 0 | Loop* LoopUtils::CloneLoop(LoopCloningResult* cloning_result) const { |
510 | | // Compute the structured order of the loop basic blocks and store it in the |
511 | | // vector ordered_loop_blocks. |
512 | 0 | std::vector<BasicBlock*> ordered_loop_blocks; |
513 | 0 | loop_->ComputeLoopStructuredOrder(&ordered_loop_blocks); |
514 | | |
515 | | // Clone the loop. |
516 | 0 | return CloneLoop(cloning_result, ordered_loop_blocks); |
517 | 0 | } |
518 | | |
519 | 0 | Loop* LoopUtils::CloneAndAttachLoopToHeader(LoopCloningResult* cloning_result) { |
520 | | // Clone the loop. |
521 | 0 | Loop* cloned_loop = CloneLoop(cloning_result); |
522 | 0 | if (!cloned_loop) { |
523 | 0 | return nullptr; |
524 | 0 | } |
525 | | |
526 | | // Create a new exit block/label for the new loop. |
527 | 0 | uint32_t new_label_id = context_->TakeNextId(); |
528 | 0 | if (new_label_id == 0) { |
529 | 0 | return nullptr; |
530 | 0 | } |
531 | 0 | std::unique_ptr<Instruction> new_label{ |
532 | 0 | new Instruction(context_, spv::Op::OpLabel, 0, new_label_id, {})}; |
533 | 0 | std::unique_ptr<BasicBlock> new_exit_bb{new BasicBlock(std::move(new_label))}; |
534 | 0 | new_exit_bb->SetParent(loop_->GetMergeBlock()->GetParent()); |
535 | | |
536 | | // Create an unconditional branch to the header block. |
537 | 0 | InstructionBuilder builder{context_, new_exit_bb.get()}; |
538 | 0 | builder.AddBranch(loop_->GetHeaderBlock()->id()); |
539 | | |
540 | | // Save the ids of the new and old merge block. |
541 | 0 | const uint32_t old_merge_block = loop_->GetMergeBlock()->id(); |
542 | 0 | const uint32_t new_merge_block = new_exit_bb->id(); |
543 | | |
544 | | // Replace the uses of the old merge block in the new loop with the new merge |
545 | | // block. |
546 | 0 | for (std::unique_ptr<BasicBlock>& basic_block : cloning_result->cloned_bb_) { |
547 | 0 | for (Instruction& inst : *basic_block) { |
548 | | // For each operand in each instruction check if it is using the old merge |
549 | | // block and change it to be the new merge block. |
550 | 0 | auto replace_merge_use = [old_merge_block, |
551 | 0 | new_merge_block](uint32_t* id) { |
552 | 0 | if (*id == old_merge_block) *id = new_merge_block; |
553 | 0 | }; |
554 | 0 | inst.ForEachInOperand(replace_merge_use); |
555 | 0 | } |
556 | 0 | } |
557 | |
|
558 | 0 | const uint32_t old_header = loop_->GetHeaderBlock()->id(); |
559 | 0 | const uint32_t new_header = cloned_loop->GetHeaderBlock()->id(); |
560 | 0 | analysis::DefUseManager* def_use = context_->get_def_use_mgr(); |
561 | |
|
562 | 0 | def_use->ForEachUse(old_header, |
563 | 0 | [new_header, this](Instruction* inst, uint32_t operand) { |
564 | 0 | if (!this->loop_->IsInsideLoop(inst)) |
565 | 0 | inst->SetOperand(operand, {new_header}); |
566 | 0 | }); |
567 | |
|
568 | 0 | BasicBlock* pre_header = loop_->GetOrCreatePreHeaderBlock(); |
569 | 0 | if (!pre_header) { |
570 | 0 | return nullptr; |
571 | 0 | } |
572 | 0 | def_use->ForEachUse( |
573 | 0 | pre_header->id(), |
574 | 0 | [new_merge_block, this](Instruction* inst, uint32_t operand) { |
575 | 0 | if (this->loop_->IsInsideLoop(inst)) |
576 | 0 | inst->SetOperand(operand, {new_merge_block}); |
577 | 0 | }); |
578 | 0 | cloned_loop->SetMergeBlock(new_exit_bb.get()); |
579 | |
|
580 | 0 | cloned_loop->SetPreHeaderBlock(loop_->GetPreHeaderBlock()); |
581 | | |
582 | | // Add the new block into the cloned instructions. |
583 | 0 | cloning_result->cloned_bb_.push_back(std::move(new_exit_bb)); |
584 | |
|
585 | 0 | return cloned_loop; |
586 | 0 | } |
587 | | |
588 | | Loop* LoopUtils::CloneLoop( |
589 | | LoopCloningResult* cloning_result, |
590 | 0 | const std::vector<BasicBlock*>& ordered_loop_blocks) const { |
591 | 0 | analysis::DefUseManager* def_use_mgr = context_->get_def_use_mgr(); |
592 | |
|
593 | 0 | std::unique_ptr<Loop> new_loop = MakeUnique<Loop>(context_); |
594 | |
|
595 | 0 | CFG& cfg = *context_->cfg(); |
596 | | |
597 | | // Clone and place blocks in a SPIR-V compliant order (dominators first). |
598 | 0 | for (BasicBlock* old_bb : ordered_loop_blocks) { |
599 | | // For each basic block in the loop, we clone it and register the mapping |
600 | | // between old and new ids. |
601 | 0 | BasicBlock* new_bb = old_bb->Clone(context_); |
602 | 0 | if (!new_bb) return nullptr; |
603 | 0 | new_bb->SetParent(&function_); |
604 | 0 | uint32_t new_label_id = context_->TakeNextId(); |
605 | 0 | if (new_label_id == 0) { |
606 | 0 | return nullptr; |
607 | 0 | } |
608 | 0 | new_bb->GetLabelInst()->SetResultId(new_label_id); |
609 | 0 | def_use_mgr->AnalyzeInstDef(new_bb->GetLabelInst()); |
610 | 0 | context_->set_instr_block(new_bb->GetLabelInst(), new_bb); |
611 | 0 | cloning_result->cloned_bb_.emplace_back(new_bb); |
612 | |
|
613 | 0 | cloning_result->old_to_new_bb_[old_bb->id()] = new_bb; |
614 | 0 | cloning_result->new_to_old_bb_[new_bb->id()] = old_bb; |
615 | 0 | cloning_result->value_map_[old_bb->id()] = new_bb->id(); |
616 | |
|
617 | 0 | if (loop_->IsInsideLoop(old_bb)) new_loop->AddBasicBlock(new_bb); |
618 | |
|
619 | 0 | for (auto new_inst = new_bb->begin(), old_inst = old_bb->begin(); |
620 | 0 | new_inst != new_bb->end(); ++new_inst, ++old_inst) { |
621 | 0 | cloning_result->ptr_map_[&*new_inst] = &*old_inst; |
622 | 0 | if (new_inst->HasResultId()) { |
623 | 0 | uint32_t new_result_id = context_->TakeNextId(); |
624 | 0 | if (new_result_id == 0) { |
625 | 0 | return nullptr; |
626 | 0 | } |
627 | 0 | new_inst->SetResultId(new_result_id); |
628 | 0 | cloning_result->value_map_[old_inst->result_id()] = |
629 | 0 | new_inst->result_id(); |
630 | | |
631 | | // Only look at the defs for now, uses are not updated yet. |
632 | 0 | def_use_mgr->AnalyzeInstDef(&*new_inst); |
633 | 0 | } |
634 | 0 | } |
635 | 0 | } |
636 | | |
637 | | // All instructions (including all labels) have been cloned, |
638 | | // remap instruction operands id with the new ones. |
639 | 0 | for (std::unique_ptr<BasicBlock>& bb_ref : cloning_result->cloned_bb_) { |
640 | 0 | BasicBlock* bb = bb_ref.get(); |
641 | |
|
642 | 0 | for (Instruction& insn : *bb) { |
643 | 0 | insn.ForEachInId([cloning_result](uint32_t* old_id) { |
644 | | // If the operand is defined in the loop, remap the id. |
645 | 0 | auto id_it = cloning_result->value_map_.find(*old_id); |
646 | 0 | if (id_it != cloning_result->value_map_.end()) { |
647 | 0 | *old_id = id_it->second; |
648 | 0 | } |
649 | 0 | }); |
650 | | // Only look at what the instruction uses. All defs are register, so all |
651 | | // should be fine now. |
652 | 0 | def_use_mgr->AnalyzeInstUse(&insn); |
653 | 0 | context_->set_instr_block(&insn, bb); |
654 | 0 | } |
655 | 0 | cfg.RegisterBlock(bb); |
656 | 0 | } |
657 | |
|
658 | 0 | PopulateLoopNest(new_loop.get(), *cloning_result); |
659 | |
|
660 | 0 | return new_loop.release(); |
661 | 0 | } |
662 | | |
663 | | void LoopUtils::PopulateLoopNest( |
664 | 0 | Loop* new_loop, const LoopCloningResult& cloning_result) const { |
665 | 0 | std::unordered_map<Loop*, Loop*> loop_mapping; |
666 | 0 | loop_mapping[loop_] = new_loop; |
667 | |
|
668 | 0 | if (loop_->HasParent()) loop_->GetParent()->AddNestedLoop(new_loop); |
669 | 0 | PopulateLoopDesc(new_loop, loop_, cloning_result); |
670 | |
|
671 | 0 | for (Loop& sub_loop : |
672 | 0 | make_range(++TreeDFIterator<Loop>(loop_), TreeDFIterator<Loop>())) { |
673 | 0 | Loop* cloned = new Loop(context_); |
674 | 0 | if (Loop* parent = loop_mapping[sub_loop.GetParent()]) |
675 | 0 | parent->AddNestedLoop(cloned); |
676 | 0 | loop_mapping[&sub_loop] = cloned; |
677 | 0 | PopulateLoopDesc(cloned, &sub_loop, cloning_result); |
678 | 0 | } |
679 | |
|
680 | 0 | loop_desc_->AddLoopNest(std::unique_ptr<Loop>(new_loop)); |
681 | 0 | } |
682 | | |
683 | | // Populates |new_loop| descriptor according to |old_loop|'s one. |
684 | | void LoopUtils::PopulateLoopDesc( |
685 | | Loop* new_loop, Loop* old_loop, |
686 | 0 | const LoopCloningResult& cloning_result) const { |
687 | 0 | for (uint32_t bb_id : old_loop->GetBlocks()) { |
688 | 0 | BasicBlock* bb = cloning_result.old_to_new_bb_.at(bb_id); |
689 | 0 | new_loop->AddBasicBlock(bb); |
690 | 0 | } |
691 | 0 | new_loop->SetHeaderBlock( |
692 | 0 | cloning_result.old_to_new_bb_.at(old_loop->GetHeaderBlock()->id())); |
693 | 0 | if (old_loop->GetLatchBlock()) |
694 | 0 | new_loop->SetLatchBlock( |
695 | 0 | cloning_result.old_to_new_bb_.at(old_loop->GetLatchBlock()->id())); |
696 | 0 | if (old_loop->GetContinueBlock()) |
697 | 0 | new_loop->SetContinueBlock( |
698 | 0 | cloning_result.old_to_new_bb_.at(old_loop->GetContinueBlock()->id())); |
699 | 0 | if (old_loop->GetMergeBlock()) { |
700 | 0 | auto it = |
701 | 0 | cloning_result.old_to_new_bb_.find(old_loop->GetMergeBlock()->id()); |
702 | 0 | BasicBlock* bb = it != cloning_result.old_to_new_bb_.end() |
703 | 0 | ? it->second |
704 | 0 | : old_loop->GetMergeBlock(); |
705 | 0 | new_loop->SetMergeBlock(bb); |
706 | 0 | } |
707 | 0 | if (old_loop->GetPreHeaderBlock()) { |
708 | 0 | auto it = |
709 | 0 | cloning_result.old_to_new_bb_.find(old_loop->GetPreHeaderBlock()->id()); |
710 | 0 | if (it != cloning_result.old_to_new_bb_.end()) { |
711 | 0 | new_loop->SetPreHeaderBlock(it->second); |
712 | 0 | } |
713 | 0 | } |
714 | 0 | } |
715 | | |
716 | | // Class to gather some metrics about a region of interest. |
717 | 0 | void CodeMetrics::Analyze(const Loop& loop) { |
718 | 0 | CFG& cfg = *loop.GetContext()->cfg(); |
719 | |
|
720 | 0 | roi_size_ = 0; |
721 | 0 | block_sizes_.clear(); |
722 | |
|
723 | 0 | for (uint32_t id : loop.GetBlocks()) { |
724 | 0 | const BasicBlock* bb = cfg.block(id); |
725 | 0 | size_t bb_size = 0; |
726 | 0 | bb->ForEachInst([&bb_size](const Instruction* insn) { |
727 | 0 | if (insn->opcode() == spv::Op::OpLabel) return; |
728 | 0 | if (insn->IsNop()) return; |
729 | 0 | if (insn->opcode() == spv::Op::OpPhi) return; |
730 | 0 | bb_size++; |
731 | 0 | }); |
732 | 0 | block_sizes_[bb->id()] = bb_size; |
733 | 0 | roi_size_ += bb_size; |
734 | 0 | } |
735 | 0 | } |
736 | | |
737 | | } // namespace opt |
738 | | } // namespace spvtools |