| 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 | |