1 | // Copyright (c) 2016, the Dart project authors. Please see the AUTHORS file |
2 | // for details. All rights reserved. Use of this source code is governed by a |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "vm/compiler/backend/branch_optimizer.h" |
6 | |
7 | #include "vm/compiler/backend/flow_graph.h" |
8 | #include "vm/compiler/backend/il.h" |
9 | |
10 | namespace dart { |
11 | |
12 | // Returns true if the given phi has a single input use and |
13 | // is used in the environments either at the corresponding block entry or |
14 | // at the same instruction where input use is. |
15 | static bool PhiHasSingleUse(PhiInstr* phi, Value* use) { |
16 | if ((use->next_use() != NULL) || (phi->input_use_list() != use)) { |
17 | return false; |
18 | } |
19 | |
20 | BlockEntryInstr* block = phi->block(); |
21 | for (Value* env_use = phi->env_use_list(); env_use != NULL; |
22 | env_use = env_use->next_use()) { |
23 | if ((env_use->instruction() != block) && |
24 | (env_use->instruction() != use->instruction())) { |
25 | return false; |
26 | } |
27 | } |
28 | |
29 | return true; |
30 | } |
31 | |
32 | bool BranchSimplifier::Match(JoinEntryInstr* block) { |
33 | // Match the pattern of a branch on a comparison whose left operand is a |
34 | // phi from the same block, and whose right operand is a constant. |
35 | // |
36 | // Branch(Comparison(kind, Phi, Constant)) |
37 | // |
38 | // These are the branches produced by inlining in a test context. Also, |
39 | // the phi has no other uses so they can simply be eliminated. The block |
40 | // has no other phis and no instructions intervening between the phi and |
41 | // branch so the block can simply be eliminated. |
42 | BranchInstr* branch = block->last_instruction()->AsBranch(); |
43 | ASSERT(branch != NULL); |
44 | ComparisonInstr* comparison = branch->comparison(); |
45 | if (comparison->InputCount() != 2) { |
46 | return false; |
47 | } |
48 | if (comparison->CanDeoptimize() || comparison->MayThrow()) { |
49 | return false; |
50 | } |
51 | Value* left = comparison->left(); |
52 | PhiInstr* phi = left->definition()->AsPhi(); |
53 | Value* right = comparison->right(); |
54 | ConstantInstr* constant = |
55 | (right == NULL) ? NULL : right->definition()->AsConstant(); |
56 | return (phi != NULL) && (constant != NULL) && (phi->GetBlock() == block) && |
57 | PhiHasSingleUse(phi, left) && (block->next() == branch) && |
58 | (block->phis()->length() == 1); |
59 | } |
60 | |
61 | JoinEntryInstr* BranchSimplifier::ToJoinEntry(Zone* zone, |
62 | BlockEntryInstr* target) { |
63 | // Convert a target block into a join block. Branches will be duplicated |
64 | // so the former true and false targets become joins of the control flows |
65 | // from all the duplicated branches. |
66 | JoinEntryInstr* join = new (zone) |
67 | JoinEntryInstr(target->block_id(), target->try_index(), DeoptId::kNone); |
68 | join->InheritDeoptTarget(zone, target); |
69 | join->LinkTo(target->next()); |
70 | join->set_last_instruction(target->last_instruction()); |
71 | target->UnuseAllInputs(); |
72 | return join; |
73 | } |
74 | |
75 | TargetEntryInstr* BranchSimplifier::ToTargetEntry(Zone* zone, |
76 | BlockEntryInstr* target) { |
77 | auto replacement = new (zone) |
78 | TargetEntryInstr(target->block_id(), target->try_index(), DeoptId::kNone); |
79 | replacement->InheritDeoptTarget(zone, target); |
80 | replacement->LinkTo(target->next()); |
81 | replacement->set_last_instruction(target->last_instruction()); |
82 | target->UnuseAllInputs(); |
83 | return replacement; |
84 | } |
85 | |
86 | BranchInstr* BranchSimplifier::CloneBranch(Zone* zone, |
87 | BranchInstr* branch, |
88 | Value* new_left, |
89 | Value* new_right) { |
90 | ComparisonInstr* comparison = branch->comparison(); |
91 | ComparisonInstr* new_comparison = |
92 | comparison->CopyWithNewOperands(new_left, new_right); |
93 | BranchInstr* new_branch = |
94 | new (zone) BranchInstr(new_comparison, DeoptId::kNone); |
95 | return new_branch; |
96 | } |
97 | |
98 | void BranchSimplifier::Simplify(FlowGraph* flow_graph) { |
99 | // Optimize some branches that test the value of a phi. When it is safe |
100 | // to do so, push the branch to each of the predecessor blocks. This is |
101 | // an optimization when (a) it can avoid materializing a boolean object at |
102 | // the phi only to test its value, and (b) it can expose opportunities for |
103 | // constant propagation and unreachable code elimination. This |
104 | // optimization is intended to run after inlining which creates |
105 | // opportunities for optimization (a) and before constant folding which |
106 | // can perform optimization (b). |
107 | |
108 | // Begin with a worklist of join blocks ending in branches. They are |
109 | // candidates for the pattern below. |
110 | Zone* zone = flow_graph->zone(); |
111 | const GrowableArray<BlockEntryInstr*>& postorder = flow_graph->postorder(); |
112 | GrowableArray<BlockEntryInstr*> worklist(postorder.length()); |
113 | for (BlockIterator it(postorder); !it.Done(); it.Advance()) { |
114 | BlockEntryInstr* block = it.Current(); |
115 | if (block->IsJoinEntry() && block->last_instruction()->IsBranch()) { |
116 | worklist.Add(block); |
117 | } |
118 | } |
119 | |
120 | // Rewrite until no more instance of the pattern exists. |
121 | bool changed = false; |
122 | while (!worklist.is_empty()) { |
123 | // All blocks in the worklist are join blocks (ending with a branch). |
124 | JoinEntryInstr* block = worklist.RemoveLast()->AsJoinEntry(); |
125 | ASSERT(block != NULL); |
126 | |
127 | if (Match(block)) { |
128 | changed = true; |
129 | |
130 | // The branch will be copied and pushed to all the join's |
131 | // predecessors. Convert the true and false target blocks into join |
132 | // blocks to join the control flows from all of the true |
133 | // (respectively, false) targets of the copied branches. |
134 | // |
135 | // The converted join block will have no phis, so it cannot be another |
136 | // instance of the pattern. There is thus no need to add it to the |
137 | // worklist. |
138 | BranchInstr* branch = block->last_instruction()->AsBranch(); |
139 | ASSERT(branch != NULL); |
140 | JoinEntryInstr* join_true = ToJoinEntry(zone, branch->true_successor()); |
141 | JoinEntryInstr* join_false = ToJoinEntry(zone, branch->false_successor()); |
142 | |
143 | ComparisonInstr* comparison = branch->comparison(); |
144 | PhiInstr* phi = comparison->left()->definition()->AsPhi(); |
145 | ConstantInstr* constant = comparison->right()->definition()->AsConstant(); |
146 | ASSERT(constant != NULL); |
147 | // Copy the constant and branch and push it to all the predecessors. |
148 | for (intptr_t i = 0, count = block->PredecessorCount(); i < count; ++i) { |
149 | GotoInstr* old_goto = |
150 | block->PredecessorAt(i)->last_instruction()->AsGoto(); |
151 | ASSERT(old_goto != NULL); |
152 | |
153 | // Replace the goto in each predecessor with a rewritten branch, |
154 | // rewritten to use the corresponding phi input instead of the phi. |
155 | Value* new_left = phi->InputAt(i)->Copy(zone); |
156 | Value* new_right = new (zone) Value(constant); |
157 | BranchInstr* new_branch = |
158 | CloneBranch(zone, branch, new_left, new_right); |
159 | if (branch->env() == NULL) { |
160 | new_branch->InheritDeoptTarget(zone, old_goto); |
161 | } else { |
162 | // Take the environment from the branch if it has one. |
163 | new_branch->InheritDeoptTarget(zone, branch); |
164 | // InheritDeoptTarget gave the new branch's comparison the same |
165 | // deopt id that it gave the new branch. The id should be the |
166 | // deopt id of the original comparison. |
167 | new_branch->comparison()->SetDeoptId(*comparison); |
168 | // The phi can be used in the branch's environment. Rename such |
169 | // uses. |
170 | Definition* replacement = phi->InputAt(i)->definition(); |
171 | new_branch->ReplaceInEnvironment(phi, replacement); |
172 | } |
173 | |
174 | new_branch->InsertBefore(old_goto); |
175 | new_branch->set_next(NULL); // Detaching the goto from the graph. |
176 | old_goto->UnuseAllInputs(); |
177 | |
178 | // Update the predecessor block. We may have created another |
179 | // instance of the pattern so add it to the worklist if necessary. |
180 | BlockEntryInstr* branch_block = new_branch->GetBlock(); |
181 | branch_block->set_last_instruction(new_branch); |
182 | if (branch_block->IsJoinEntry()) worklist.Add(branch_block); |
183 | |
184 | // Connect the branch to the true and false joins, via empty target |
185 | // blocks. |
186 | TargetEntryInstr* true_target = new (zone) TargetEntryInstr( |
187 | flow_graph->max_block_id() + 1, block->try_index(), DeoptId::kNone); |
188 | true_target->InheritDeoptTarget(zone, join_true); |
189 | TargetEntryInstr* false_target = new (zone) TargetEntryInstr( |
190 | flow_graph->max_block_id() + 2, block->try_index(), DeoptId::kNone); |
191 | false_target->InheritDeoptTarget(zone, join_false); |
192 | flow_graph->set_max_block_id(flow_graph->max_block_id() + 2); |
193 | *new_branch->true_successor_address() = true_target; |
194 | *new_branch->false_successor_address() = false_target; |
195 | GotoInstr* goto_true = new (zone) GotoInstr(join_true, DeoptId::kNone); |
196 | goto_true->InheritDeoptTarget(zone, join_true); |
197 | true_target->LinkTo(goto_true); |
198 | true_target->set_last_instruction(goto_true); |
199 | GotoInstr* goto_false = |
200 | new (zone) GotoInstr(join_false, DeoptId::kNone); |
201 | goto_false->InheritDeoptTarget(zone, join_false); |
202 | false_target->LinkTo(goto_false); |
203 | false_target->set_last_instruction(goto_false); |
204 | } |
205 | // When all predecessors have been rewritten, the original block is |
206 | // unreachable from the graph. |
207 | phi->UnuseAllInputs(); |
208 | branch->UnuseAllInputs(); |
209 | block->UnuseAllInputs(); |
210 | ASSERT(!phi->HasUses()); |
211 | } |
212 | } |
213 | |
214 | if (changed) { |
215 | // We may have changed the block order and the dominator tree. |
216 | flow_graph->DiscoverBlocks(); |
217 | GrowableArray<BitVector*> dominance_frontier; |
218 | flow_graph->ComputeDominators(&dominance_frontier); |
219 | } |
220 | } |
221 | |
222 | static bool IsTrivialBlock(BlockEntryInstr* block, Definition* defn) { |
223 | return (block->IsTargetEntry() && (block->PredecessorCount() == 1)) && |
224 | ((block->next() == block->last_instruction()) || |
225 | ((block->next() == defn) && |
226 | (defn->next() == block->last_instruction()))); |
227 | } |
228 | |
229 | static void EliminateTrivialBlock(BlockEntryInstr* block, |
230 | Definition* instr, |
231 | IfThenElseInstr* before) { |
232 | block->UnuseAllInputs(); |
233 | block->last_instruction()->UnuseAllInputs(); |
234 | |
235 | if ((block->next() == instr) && |
236 | (instr->next() == block->last_instruction())) { |
237 | before->previous()->LinkTo(instr); |
238 | instr->LinkTo(before); |
239 | } |
240 | } |
241 | |
242 | void IfConverter::Simplify(FlowGraph* flow_graph) { |
243 | Zone* zone = flow_graph->zone(); |
244 | bool changed = false; |
245 | |
246 | const GrowableArray<BlockEntryInstr*>& postorder = flow_graph->postorder(); |
247 | for (BlockIterator it(postorder); !it.Done(); it.Advance()) { |
248 | BlockEntryInstr* block = it.Current(); |
249 | JoinEntryInstr* join = block->AsJoinEntry(); |
250 | |
251 | // Detect diamond control flow pattern which materializes a value depending |
252 | // on the result of the comparison: |
253 | // |
254 | // B_pred: |
255 | // ... |
256 | // Branch if COMP goto (B_pred1, B_pred2) |
257 | // B_pred1: -- trivial block that contains at most one definition |
258 | // v1 = Constant(...) |
259 | // goto B_block |
260 | // B_pred2: -- trivial block that contains at most one definition |
261 | // v2 = Constant(...) |
262 | // goto B_block |
263 | // B_block: |
264 | // v3 = phi(v1, v2) -- single phi |
265 | // |
266 | // and replace it with |
267 | // |
268 | // Ba: |
269 | // v3 = IfThenElse(COMP ? v1 : v2) |
270 | // |
271 | if ((join != NULL) && (join->phis() != NULL) && |
272 | (join->phis()->length() == 1) && (block->PredecessorCount() == 2)) { |
273 | BlockEntryInstr* pred1 = block->PredecessorAt(0); |
274 | BlockEntryInstr* pred2 = block->PredecessorAt(1); |
275 | |
276 | PhiInstr* phi = (*join->phis())[0]; |
277 | Value* v1 = phi->InputAt(0); |
278 | Value* v2 = phi->InputAt(1); |
279 | |
280 | if (IsTrivialBlock(pred1, v1->definition()) && |
281 | IsTrivialBlock(pred2, v2->definition()) && |
282 | (pred1->PredecessorAt(0) == pred2->PredecessorAt(0))) { |
283 | BlockEntryInstr* pred = pred1->PredecessorAt(0); |
284 | BranchInstr* branch = pred->last_instruction()->AsBranch(); |
285 | |
286 | if (branch == nullptr) { |
287 | // There is no "B_pred" block. |
288 | ASSERT(pred->last_instruction()->IsGraphEntry()); |
289 | continue; |
290 | } |
291 | |
292 | ComparisonInstr* comparison = branch->comparison(); |
293 | |
294 | // Check if the platform supports efficient branchless IfThenElseInstr |
295 | // for the given combination of comparison and values flowing from |
296 | // false and true paths. |
297 | if (IfThenElseInstr::Supports(comparison, v1, v2)) { |
298 | Value* if_true = (pred1 == branch->true_successor()) ? v1 : v2; |
299 | Value* if_false = (pred2 == branch->true_successor()) ? v1 : v2; |
300 | |
301 | ComparisonInstr* new_comparison = comparison->CopyWithNewOperands( |
302 | comparison->left()->Copy(zone), comparison->right()->Copy(zone)); |
303 | IfThenElseInstr* if_then_else = |
304 | new (zone) IfThenElseInstr(new_comparison, if_true->Copy(zone), |
305 | if_false->Copy(zone), DeoptId::kNone); |
306 | flow_graph->InsertBefore(branch, if_then_else, NULL, |
307 | FlowGraph::kValue); |
308 | |
309 | phi->ReplaceUsesWith(if_then_else); |
310 | |
311 | // Connect IfThenElseInstr to the first instruction in the merge block |
312 | // effectively eliminating diamond control flow. |
313 | // Current block as well as pred1 and pred2 blocks are no longer in |
314 | // the graph at this point. |
315 | if_then_else->LinkTo(join->next()); |
316 | pred->set_last_instruction(join->last_instruction()); |
317 | |
318 | // Resulting block must inherit block id from the eliminated current |
319 | // block to guarantee that ordering of phi operands in its successor |
320 | // stays consistent. |
321 | pred->set_block_id(block->block_id()); |
322 | |
323 | // If v1 and v2 were defined inside eliminated blocks pred1/pred2 |
324 | // move them out to the place before inserted IfThenElse instruction. |
325 | EliminateTrivialBlock(pred1, v1->definition(), if_then_else); |
326 | EliminateTrivialBlock(pred2, v2->definition(), if_then_else); |
327 | |
328 | // Update use lists to reflect changes in the graph. |
329 | phi->UnuseAllInputs(); |
330 | branch->UnuseAllInputs(); |
331 | block->UnuseAllInputs(); |
332 | |
333 | // The graph has changed. Recompute dominators and block orders after |
334 | // this pass is finished. |
335 | changed = true; |
336 | } |
337 | } |
338 | } |
339 | } |
340 | |
341 | if (changed) { |
342 | // We may have changed the block order and the dominator tree. |
343 | flow_graph->DiscoverBlocks(); |
344 | GrowableArray<BitVector*> dominance_frontier; |
345 | flow_graph->ComputeDominators(&dominance_frontier); |
346 | } |
347 | } |
348 | |
349 | } // namespace dart |
350 | |