/src/spirv-tools/source/opt/cfg.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) 2017 Google Inc. |
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 "source/opt/cfg.h" |
16 | | |
17 | | #include <memory> |
18 | | #include <utility> |
19 | | |
20 | | #include "source/cfa.h" |
21 | | #include "source/opt/ir_builder.h" |
22 | | #include "source/opt/ir_context.h" |
23 | | #include "source/opt/module.h" |
24 | | |
25 | | namespace spvtools { |
26 | | namespace opt { |
27 | | namespace { |
28 | | |
29 | | using cbb_ptr = const opt::BasicBlock*; |
30 | | |
31 | | // Universal Limit of ResultID + 1 |
32 | | constexpr int kMaxResultId = 0x400000; |
33 | | |
34 | | } // namespace |
35 | | |
36 | | CFG::CFG(Module* module) |
37 | | : module_(module), |
38 | | pseudo_entry_block_(std::unique_ptr<Instruction>( |
39 | | new Instruction(module->context(), spv::Op::OpLabel, 0, 0, {}))), |
40 | | pseudo_exit_block_(std::unique_ptr<Instruction>(new Instruction( |
41 | 51.0k | module->context(), spv::Op::OpLabel, 0, kMaxResultId, {}))) { |
42 | 76.9k | for (auto& fn : *module) { |
43 | 9.28M | for (auto& blk : fn) { |
44 | 9.28M | RegisterBlock(&blk); |
45 | 9.28M | } |
46 | 76.9k | } |
47 | 51.0k | } |
48 | | |
49 | 9.30M | void CFG::AddEdges(BasicBlock* blk) { |
50 | 9.30M | uint32_t blk_id = blk->id(); |
51 | | // Force the creation of an entry, not all basic block have predecessors |
52 | | // (such as the entry blocks and some unreachables). |
53 | 9.30M | label2preds_[blk_id]; |
54 | 9.30M | const auto* const_blk = blk; |
55 | 9.30M | const_blk->ForEachSuccessorLabel( |
56 | 9.85M | [blk_id, this](const uint32_t succ_id) { AddEdge(blk_id, succ_id); }); |
57 | 9.30M | } |
58 | | |
59 | 0 | void CFG::RemoveNonExistingEdges(uint32_t blk_id) { |
60 | 0 | std::vector<uint32_t> updated_pred_list; |
61 | 0 | for (uint32_t id : preds(blk_id)) { |
62 | 0 | const BasicBlock* pred_blk = block(id); |
63 | 0 | bool has_branch = false; |
64 | 0 | pred_blk->ForEachSuccessorLabel([&has_branch, blk_id](uint32_t succ) { |
65 | 0 | if (succ == blk_id) { |
66 | 0 | has_branch = true; |
67 | 0 | } |
68 | 0 | }); |
69 | 0 | if (has_branch) updated_pred_list.push_back(id); |
70 | 0 | } |
71 | |
|
72 | 0 | label2preds_.at(blk_id) = std::move(updated_pred_list); |
73 | 0 | } |
74 | | |
75 | | void CFG::ComputeStructuredOrder(Function* func, BasicBlock* root, |
76 | 115k | std::list<BasicBlock*>* order) { |
77 | 115k | ComputeStructuredOrder(func, root, nullptr, order); |
78 | 115k | } |
79 | | |
80 | | void CFG::ComputeStructuredOrder(Function* func, BasicBlock* root, |
81 | | BasicBlock* end, |
82 | 118k | std::list<BasicBlock*>* order) { |
83 | 118k | assert(module_->context()->get_feature_mgr()->HasCapability( |
84 | 118k | spv::Capability::Shader) && |
85 | 118k | "This only works on structured control flow"); |
86 | | |
87 | | // Compute structured successors and do DFS. |
88 | 118k | ComputeStructuredSuccessors(func); |
89 | 11.7M | auto ignore_block = [](cbb_ptr) {}; |
90 | 25.8M | auto terminal = [end](cbb_ptr bb) { return bb == end; }; |
91 | | |
92 | 37.6M | auto get_structured_successors = [this](const BasicBlock* b) { |
93 | 37.6M | return &(block2structured_succs_[b]); |
94 | 37.6M | }; |
95 | | |
96 | | // TODO(greg-lunarg): Get rid of const_cast by making moving const |
97 | | // out of the cfa.h prototypes and into the invoking code. |
98 | 11.7M | auto post_order = [&](cbb_ptr b) { |
99 | 11.7M | order->push_front(const_cast<BasicBlock*>(b)); |
100 | 11.7M | }; |
101 | 118k | CFA<BasicBlock>::DepthFirstTraversal(root, get_structured_successors, |
102 | 118k | ignore_block, post_order, terminal); |
103 | 118k | } |
104 | | |
105 | | void CFG::ForEachBlockInPostOrder(BasicBlock* bb, |
106 | 3.36k | const std::function<void(BasicBlock*)>& f) { |
107 | 3.36k | std::vector<BasicBlock*> po; |
108 | 3.36k | std::unordered_set<BasicBlock*> seen; |
109 | 3.36k | ComputePostOrderTraversal(bb, &po, &seen); |
110 | | |
111 | 55.2k | for (BasicBlock* current_bb : po) { |
112 | 55.2k | if (!IsPseudoExitBlock(current_bb) && !IsPseudoEntryBlock(current_bb)) { |
113 | 55.2k | f(current_bb); |
114 | 55.2k | } |
115 | 55.2k | } |
116 | 3.36k | } |
117 | | |
118 | | void CFG::ForEachBlockInReversePostOrder( |
119 | 24.2k | BasicBlock* bb, const std::function<void(BasicBlock*)>& f) { |
120 | 5.23M | WhileEachBlockInReversePostOrder(bb, [f](BasicBlock* b) { |
121 | 5.23M | f(b); |
122 | 5.23M | return true; |
123 | 5.23M | }); |
124 | 24.2k | } |
125 | | |
126 | | bool CFG::WhileEachBlockInReversePostOrder( |
127 | 40.4k | BasicBlock* bb, const std::function<bool(BasicBlock*)>& f) { |
128 | 40.4k | std::vector<BasicBlock*> po; |
129 | 40.4k | std::unordered_set<BasicBlock*> seen; |
130 | 40.4k | ComputePostOrderTraversal(bb, &po, &seen); |
131 | | |
132 | 5.65M | for (auto current_bb = po.rbegin(); current_bb != po.rend(); ++current_bb) { |
133 | 5.61M | if (!IsPseudoExitBlock(*current_bb) && !IsPseudoEntryBlock(*current_bb)) { |
134 | 5.61M | if (!f(*current_bb)) { |
135 | 0 | return false; |
136 | 0 | } |
137 | 5.61M | } |
138 | 5.61M | } |
139 | 40.4k | return true; |
140 | 40.4k | } |
141 | | |
142 | 118k | void CFG::ComputeStructuredSuccessors(Function* func) { |
143 | 118k | block2structured_succs_.clear(); |
144 | 11.7M | for (auto& blk : *func) { |
145 | | // If no predecessors in function, make successor to pseudo entry. |
146 | 11.7M | if (label2preds_[blk.id()].size() == 0) |
147 | 324k | block2structured_succs_[&pseudo_entry_block_].push_back(&blk); |
148 | | |
149 | | // If header, make merge block first successor and continue block second |
150 | | // successor if there is one. |
151 | 11.7M | uint32_t mbid = blk.MergeBlockIdIfAny(); |
152 | 11.7M | if (mbid != 0) { |
153 | 1.20M | block2structured_succs_[&blk].push_back(block(mbid)); |
154 | 1.20M | uint32_t cbid = blk.ContinueBlockIdIfAny(); |
155 | 1.20M | if (cbid != 0) { |
156 | 289k | block2structured_succs_[&blk].push_back(block(cbid)); |
157 | 289k | } |
158 | 1.20M | } |
159 | | |
160 | | // Add true successors. |
161 | 11.7M | const auto& const_blk = blk; |
162 | 12.6M | const_blk.ForEachSuccessorLabel([&blk, this](const uint32_t sbid) { |
163 | 12.6M | block2structured_succs_[&blk].push_back(block(sbid)); |
164 | 12.6M | }); |
165 | 11.7M | } |
166 | 118k | } |
167 | | |
168 | | void CFG::ComputePostOrderTraversal(BasicBlock* bb, |
169 | | std::vector<BasicBlock*>* order, |
170 | 43.8k | std::unordered_set<BasicBlock*>* seen) { |
171 | 43.8k | std::vector<BasicBlock*> stack; |
172 | 43.8k | stack.push_back(bb); |
173 | 11.3M | while (!stack.empty()) { |
174 | 11.3M | bb = stack.back(); |
175 | 11.3M | seen->insert(bb); |
176 | 11.3M | static_cast<const BasicBlock*>(bb)->WhileEachSuccessorLabel( |
177 | 11.8M | [&seen, &stack, this](const uint32_t sbid) { |
178 | 11.8M | BasicBlock* succ_bb = id2block_[sbid]; |
179 | 11.8M | if (!seen->count(succ_bb)) { |
180 | 5.63M | stack.push_back(succ_bb); |
181 | 5.63M | return false; |
182 | 5.63M | } |
183 | 6.20M | return true; |
184 | 11.8M | }); |
185 | 11.3M | if (stack.back() == bb) { |
186 | 5.67M | order->push_back(bb); |
187 | 5.67M | stack.pop_back(); |
188 | 5.67M | } |
189 | 11.3M | } |
190 | 43.8k | } |
191 | | |
192 | 0 | BasicBlock* CFG::SplitLoopHeader(BasicBlock* bb) { |
193 | 0 | assert(bb->GetLoopMergeInst() && "Expecting bb to be the header of a loop."); |
194 | | |
195 | 0 | Function* fn = bb->GetParent(); |
196 | 0 | IRContext* context = module_->context(); |
197 | | |
198 | | // Get the new header id up front. If we are out of ids, then we cannot split |
199 | | // the loop. |
200 | 0 | uint32_t new_header_id = context->TakeNextId(); |
201 | 0 | if (new_header_id == 0) { |
202 | 0 | return nullptr; |
203 | 0 | } |
204 | | |
205 | | // Find the insertion point for the new bb. |
206 | 0 | Function::iterator header_it = std::find_if( |
207 | 0 | fn->begin(), fn->end(), |
208 | 0 | [bb](BasicBlock& block_in_func) { return &block_in_func == bb; }); |
209 | 0 | assert(header_it != fn->end()); |
210 | | |
211 | 0 | const std::vector<uint32_t>& pred = preds(bb->id()); |
212 | | // Find the back edge |
213 | 0 | BasicBlock* latch_block = nullptr; |
214 | 0 | Function::iterator latch_block_iter = header_it; |
215 | 0 | for (; latch_block_iter != fn->end(); ++latch_block_iter) { |
216 | | // If blocks are in the proper order, then the only branch that appears |
217 | | // after the header is the latch. |
218 | 0 | if (std::find(pred.begin(), pred.end(), latch_block_iter->id()) != |
219 | 0 | pred.end()) { |
220 | 0 | break; |
221 | 0 | } |
222 | 0 | } |
223 | 0 | assert(latch_block_iter != fn->end() && "Could not find the latch."); |
224 | 0 | latch_block = &*latch_block_iter; |
225 | |
|
226 | 0 | RemoveSuccessorEdges(bb); |
227 | | |
228 | | // Create the new header bb basic bb. |
229 | | // Leave the phi instructions behind. |
230 | 0 | auto iter = bb->begin(); |
231 | 0 | while (iter->opcode() == spv::Op::OpPhi) { |
232 | 0 | ++iter; |
233 | 0 | } |
234 | |
|
235 | 0 | BasicBlock* new_header = bb->SplitBasicBlock(context, new_header_id, iter); |
236 | 0 | context->AnalyzeDefUse(new_header->GetLabelInst()); |
237 | | |
238 | | // Update cfg |
239 | 0 | RegisterBlock(new_header); |
240 | | |
241 | | // Update bb mappings. |
242 | 0 | context->set_instr_block(new_header->GetLabelInst(), new_header); |
243 | 0 | new_header->ForEachInst([new_header, context](Instruction* inst) { |
244 | 0 | context->set_instr_block(inst, new_header); |
245 | 0 | }); |
246 | | |
247 | | // If |bb| was the latch block, the branch back to the header is not in |
248 | | // |new_header|. |
249 | 0 | if (latch_block == bb) { |
250 | 0 | if (new_header->ContinueBlockId() == bb->id()) { |
251 | 0 | new_header->GetLoopMergeInst()->SetInOperand(1, {new_header_id}); |
252 | 0 | } |
253 | 0 | latch_block = new_header; |
254 | 0 | } |
255 | | |
256 | | // Adjust the OpPhi instructions as needed. |
257 | 0 | bb->ForEachPhiInst([latch_block, bb, new_header, context](Instruction* phi) { |
258 | 0 | std::vector<uint32_t> preheader_phi_ops; |
259 | 0 | std::vector<Operand> header_phi_ops; |
260 | | |
261 | | // Identify where the original inputs to original OpPhi belong: header or |
262 | | // preheader. |
263 | 0 | for (uint32_t i = 0; i < phi->NumInOperands(); i += 2) { |
264 | 0 | uint32_t def_id = phi->GetSingleWordInOperand(i); |
265 | 0 | uint32_t branch_id = phi->GetSingleWordInOperand(i + 1); |
266 | 0 | if (branch_id == latch_block->id()) { |
267 | 0 | header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {def_id}}); |
268 | 0 | header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {branch_id}}); |
269 | 0 | } else { |
270 | 0 | preheader_phi_ops.push_back(def_id); |
271 | 0 | preheader_phi_ops.push_back(branch_id); |
272 | 0 | } |
273 | 0 | } |
274 | | |
275 | | // Create a phi instruction if and only if the preheader_phi_ops has more |
276 | | // than one pair. |
277 | 0 | if (preheader_phi_ops.size() > 2) { |
278 | 0 | InstructionBuilder builder( |
279 | 0 | context, &*bb->begin(), |
280 | 0 | IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); |
281 | |
|
282 | 0 | Instruction* new_phi = builder.AddPhi(phi->type_id(), preheader_phi_ops); |
283 | | |
284 | | // Add the OpPhi to the header bb. |
285 | 0 | header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {new_phi->result_id()}}); |
286 | 0 | header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {bb->id()}}); |
287 | 0 | } else { |
288 | | // An OpPhi with a single entry is just a copy. In this case use the same |
289 | | // instruction in the new header. |
290 | 0 | header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {preheader_phi_ops[0]}}); |
291 | 0 | header_phi_ops.push_back({SPV_OPERAND_TYPE_ID, {bb->id()}}); |
292 | 0 | } |
293 | |
|
294 | 0 | phi->RemoveFromList(); |
295 | 0 | std::unique_ptr<Instruction> phi_owner(phi); |
296 | 0 | phi->SetInOperands(std::move(header_phi_ops)); |
297 | 0 | new_header->begin()->InsertBefore(std::move(phi_owner)); |
298 | 0 | context->set_instr_block(phi, new_header); |
299 | 0 | context->AnalyzeUses(phi); |
300 | 0 | }); |
301 | | |
302 | | // Add a branch to the new header. |
303 | 0 | InstructionBuilder branch_builder( |
304 | 0 | context, bb, |
305 | 0 | IRContext::kAnalysisDefUse | IRContext::kAnalysisInstrToBlockMapping); |
306 | 0 | bb->AddInstruction( |
307 | 0 | MakeUnique<Instruction>(context, spv::Op::OpBranch, 0, 0, |
308 | 0 | std::initializer_list<Operand>{ |
309 | 0 | {SPV_OPERAND_TYPE_ID, {new_header->id()}}})); |
310 | 0 | context->AnalyzeUses(bb->terminator()); |
311 | 0 | context->set_instr_block(bb->terminator(), bb); |
312 | 0 | label2preds_[new_header->id()].push_back(bb->id()); |
313 | | |
314 | | // Update the latch to branch to the new header. |
315 | 0 | latch_block->ForEachSuccessorLabel([bb, new_header_id](uint32_t* id) { |
316 | 0 | if (*id == bb->id()) { |
317 | 0 | *id = new_header_id; |
318 | 0 | } |
319 | 0 | }); |
320 | 0 | Instruction* latch_branch = latch_block->terminator(); |
321 | 0 | context->AnalyzeUses(latch_branch); |
322 | 0 | label2preds_[new_header->id()].push_back(latch_block->id()); |
323 | |
|
324 | 0 | auto& block_preds = label2preds_[bb->id()]; |
325 | 0 | auto latch_pos = |
326 | 0 | std::find(block_preds.begin(), block_preds.end(), latch_block->id()); |
327 | 0 | assert(latch_pos != block_preds.end() && "The cfg was invalid."); |
328 | 0 | block_preds.erase(latch_pos); |
329 | | |
330 | | // Update the loop descriptors |
331 | 0 | if (context->AreAnalysesValid(IRContext::kAnalysisLoopAnalysis)) { |
332 | 0 | LoopDescriptor* loop_desc = context->GetLoopDescriptor(bb->GetParent()); |
333 | 0 | Loop* loop = (*loop_desc)[bb->id()]; |
334 | |
|
335 | 0 | loop->AddBasicBlock(new_header_id); |
336 | 0 | loop->SetHeaderBlock(new_header); |
337 | 0 | loop_desc->SetBasicBlockToLoop(new_header_id, loop); |
338 | |
|
339 | 0 | loop->RemoveBasicBlock(bb->id()); |
340 | 0 | loop->SetPreHeaderBlock(bb); |
341 | |
|
342 | 0 | Loop* parent_loop = loop->GetParent(); |
343 | 0 | if (parent_loop != nullptr) { |
344 | 0 | parent_loop->AddBasicBlock(bb->id()); |
345 | 0 | loop_desc->SetBasicBlockToLoop(bb->id(), parent_loop); |
346 | 0 | } else { |
347 | 0 | loop_desc->SetBasicBlockToLoop(bb->id(), nullptr); |
348 | 0 | } |
349 | 0 | } |
350 | 0 | return new_header; |
351 | 0 | } |
352 | | |
353 | | } // namespace opt |
354 | | } // namespace spvtools |