Roland Levillain | 72bceff | 2014-09-15 18:29:00 +0100 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | #include "dead_code_elimination.h" |
| 18 | |
Santiago Aboy Solanes | 78f3d3a | 2022-07-15 14:30:05 +0100 | [diff] [blame] | 19 | #include "android-base/logging.h" |
David Brazdil | d9c9037 | 2016-09-14 16:53:55 +0100 | [diff] [blame] | 20 | #include "base/array_ref.h" |
Roland Levillain | 72bceff | 2014-09-15 18:29:00 +0100 | [diff] [blame] | 21 | #include "base/bit_vector-inl.h" |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 22 | #include "base/logging.h" |
Vladimir Marko | 009d166 | 2017-10-10 13:21:15 +0100 | [diff] [blame] | 23 | #include "base/scoped_arena_allocator.h" |
| 24 | #include "base/scoped_arena_containers.h" |
Vladimir Marko | 2c45bc9 | 2016-10-25 16:54:12 +0100 | [diff] [blame] | 25 | #include "base/stl_util.h" |
Santiago Aboy Solanes | 78f3d3a | 2022-07-15 14:30:05 +0100 | [diff] [blame] | 26 | #include "optimizing/nodes.h" |
David Brazdil | 84daae5 | 2015-05-18 12:06:52 +0100 | [diff] [blame] | 27 | #include "ssa_phi_elimination.h" |
Roland Levillain | 72bceff | 2014-09-15 18:29:00 +0100 | [diff] [blame] | 28 | |
VladimĂr Marko | 434d968 | 2022-11-04 14:04:17 +0000 | [diff] [blame] | 29 | namespace art HIDDEN { |
Roland Levillain | 72bceff | 2014-09-15 18:29:00 +0100 | [diff] [blame] | 30 | |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 31 | static void MarkReachableBlocks(HGraph* graph, ArenaBitVector* visited) { |
Vladimir Marko | 009d166 | 2017-10-10 13:21:15 +0100 | [diff] [blame] | 32 | // Use local allocator for allocating memory. |
| 33 | ScopedArenaAllocator allocator(graph->GetArenaStack()); |
| 34 | |
| 35 | ScopedArenaVector<HBasicBlock*> worklist(allocator.Adapter(kArenaAllocDCE)); |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 36 | constexpr size_t kDefaultWorlistSize = 8; |
| 37 | worklist.reserve(kDefaultWorlistSize); |
| 38 | visited->SetBit(graph->GetEntryBlock()->GetBlockId()); |
| 39 | worklist.push_back(graph->GetEntryBlock()); |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 40 | |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 41 | while (!worklist.empty()) { |
| 42 | HBasicBlock* block = worklist.back(); |
| 43 | worklist.pop_back(); |
| 44 | int block_id = block->GetBlockId(); |
| 45 | DCHECK(visited->IsBitSet(block_id)); |
| 46 | |
| 47 | ArrayRef<HBasicBlock* const> live_successors(block->GetSuccessors()); |
| 48 | HInstruction* last_instruction = block->GetLastInstruction(); |
| 49 | if (last_instruction->IsIf()) { |
| 50 | HIf* if_instruction = last_instruction->AsIf(); |
| 51 | HInstruction* condition = if_instruction->InputAt(0); |
| 52 | if (condition->IsIntConstant()) { |
Roland Levillain | 1a65388 | 2016-03-18 18:05:57 +0000 | [diff] [blame] | 53 | if (condition->AsIntConstant()->IsTrue()) { |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 54 | live_successors = live_successors.SubArray(0u, 1u); |
| 55 | DCHECK_EQ(live_successors[0], if_instruction->IfTrueSuccessor()); |
| 56 | } else { |
Roland Levillain | 1a65388 | 2016-03-18 18:05:57 +0000 | [diff] [blame] | 57 | DCHECK(condition->AsIntConstant()->IsFalse()) << condition->AsIntConstant()->GetValue(); |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 58 | live_successors = live_successors.SubArray(1u, 1u); |
| 59 | DCHECK_EQ(live_successors[0], if_instruction->IfFalseSuccessor()); |
| 60 | } |
Mark Mendell | fe57faa | 2015-09-18 09:26:15 -0400 | [diff] [blame] | 61 | } |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 62 | } else if (last_instruction->IsPackedSwitch()) { |
| 63 | HPackedSwitch* switch_instruction = last_instruction->AsPackedSwitch(); |
| 64 | HInstruction* switch_input = switch_instruction->InputAt(0); |
| 65 | if (switch_input->IsIntConstant()) { |
| 66 | int32_t switch_value = switch_input->AsIntConstant()->GetValue(); |
| 67 | int32_t start_value = switch_instruction->GetStartValue(); |
Vladimir Marko | 430c4f5 | 2015-09-25 17:10:15 +0100 | [diff] [blame] | 68 | // Note: Though the spec forbids packed-switch values to wrap around, we leave |
| 69 | // that task to the verifier and use unsigned arithmetic with it's "modulo 2^32" |
| 70 | // semantics to check if the value is in range, wrapped or not. |
| 71 | uint32_t switch_index = |
| 72 | static_cast<uint32_t>(switch_value) - static_cast<uint32_t>(start_value); |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 73 | if (switch_index < switch_instruction->GetNumEntries()) { |
| 74 | live_successors = live_successors.SubArray(switch_index, 1u); |
Vladimir Marko | ec7802a | 2015-10-01 20:57:57 +0100 | [diff] [blame] | 75 | DCHECK_EQ(live_successors[0], block->GetSuccessors()[switch_index]); |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 76 | } else { |
| 77 | live_successors = live_successors.SubArray(switch_instruction->GetNumEntries(), 1u); |
| 78 | DCHECK_EQ(live_successors[0], switch_instruction->GetDefaultBlock()); |
| 79 | } |
Mark Mendell | fe57faa | 2015-09-18 09:26:15 -0400 | [diff] [blame] | 80 | } |
| 81 | } |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 82 | |
| 83 | for (HBasicBlock* successor : live_successors) { |
| 84 | // Add only those successors that have not been visited yet. |
| 85 | if (!visited->IsBitSet(successor->GetBlockId())) { |
| 86 | visited->SetBit(successor->GetBlockId()); |
| 87 | worklist.push_back(successor); |
| 88 | } |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 89 | } |
| 90 | } |
| 91 | } |
| 92 | |
| 93 | void HDeadCodeElimination::MaybeRecordDeadBlock(HBasicBlock* block) { |
| 94 | if (stats_ != nullptr) { |
| 95 | stats_->RecordStat(MethodCompilationStat::kRemovedDeadInstruction, |
| 96 | block->GetPhis().CountSize() + block->GetInstructions().CountSize()); |
| 97 | } |
| 98 | } |
| 99 | |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 100 | void HDeadCodeElimination::MaybeRecordSimplifyIf() { |
| 101 | if (stats_ != nullptr) { |
| 102 | stats_->RecordStat(MethodCompilationStat::kSimplifyIf); |
Nicolas Geoffray | 09aa147 | 2016-01-19 10:52:54 +0000 | [diff] [blame] | 103 | } |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 104 | } |
| 105 | |
| 106 | static bool HasInput(HCondition* instruction, HInstruction* input) { |
| 107 | return (instruction->InputAt(0) == input) || |
| 108 | (instruction->InputAt(1) == input); |
| 109 | } |
| 110 | |
| 111 | static bool HasEquality(IfCondition condition) { |
| 112 | switch (condition) { |
| 113 | case kCondEQ: |
| 114 | case kCondLE: |
| 115 | case kCondGE: |
| 116 | case kCondBE: |
| 117 | case kCondAE: |
| 118 | return true; |
| 119 | case kCondNE: |
| 120 | case kCondLT: |
| 121 | case kCondGT: |
| 122 | case kCondB: |
| 123 | case kCondA: |
| 124 | return false; |
| 125 | } |
| 126 | } |
| 127 | |
| 128 | static HConstant* Evaluate(HCondition* condition, HInstruction* left, HInstruction* right) { |
Vladimir Marko | 0ebe0d8 | 2017-09-21 22:50:39 +0100 | [diff] [blame] | 129 | if (left == right && !DataType::IsFloatingPointType(left->GetType())) { |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 130 | return condition->GetBlock()->GetGraph()->GetIntConstant( |
| 131 | HasEquality(condition->GetCondition()) ? 1 : 0); |
| 132 | } |
| 133 | |
| 134 | if (!left->IsConstant() || !right->IsConstant()) { |
| 135 | return nullptr; |
| 136 | } |
| 137 | |
| 138 | if (left->IsIntConstant()) { |
| 139 | return condition->Evaluate(left->AsIntConstant(), right->AsIntConstant()); |
| 140 | } else if (left->IsNullConstant()) { |
| 141 | return condition->Evaluate(left->AsNullConstant(), right->AsNullConstant()); |
| 142 | } else if (left->IsLongConstant()) { |
| 143 | return condition->Evaluate(left->AsLongConstant(), right->AsLongConstant()); |
| 144 | } else if (left->IsFloatConstant()) { |
| 145 | return condition->Evaluate(left->AsFloatConstant(), right->AsFloatConstant()); |
| 146 | } else { |
| 147 | DCHECK(left->IsDoubleConstant()); |
| 148 | return condition->Evaluate(left->AsDoubleConstant(), right->AsDoubleConstant()); |
| 149 | } |
| 150 | } |
| 151 | |
Aart Bik | 4c563ca | 2018-01-24 16:34:25 -0800 | [diff] [blame] | 152 | static bool RemoveNonNullControlDependences(HBasicBlock* block, HBasicBlock* throws) { |
| 153 | // Test for an if as last statement. |
| 154 | if (!block->EndsWithIf()) { |
| 155 | return false; |
| 156 | } |
| 157 | HIf* ifs = block->GetLastInstruction()->AsIf(); |
| 158 | // Find either: |
| 159 | // if obj == null |
| 160 | // throws |
| 161 | // else |
| 162 | // not_throws |
| 163 | // or: |
| 164 | // if obj != null |
| 165 | // not_throws |
| 166 | // else |
| 167 | // throws |
| 168 | HInstruction* cond = ifs->InputAt(0); |
| 169 | HBasicBlock* not_throws = nullptr; |
| 170 | if (throws == ifs->IfTrueSuccessor() && cond->IsEqual()) { |
| 171 | not_throws = ifs->IfFalseSuccessor(); |
| 172 | } else if (throws == ifs->IfFalseSuccessor() && cond->IsNotEqual()) { |
| 173 | not_throws = ifs->IfTrueSuccessor(); |
| 174 | } else { |
| 175 | return false; |
| 176 | } |
| 177 | DCHECK(cond->IsEqual() || cond->IsNotEqual()); |
| 178 | HInstruction* obj = cond->InputAt(1); |
| 179 | if (obj->IsNullConstant()) { |
| 180 | obj = cond->InputAt(0); |
| 181 | } else if (!cond->InputAt(0)->IsNullConstant()) { |
| 182 | return false; |
| 183 | } |
Santiago Aboy Solanes | e05bc3e | 2023-02-20 14:26:23 +0000 | [diff] [blame] | 184 | |
| 185 | // We can't create a BoundType for an object with an invalid RTI. |
| 186 | const ReferenceTypeInfo ti = obj->GetReferenceTypeInfo(); |
| 187 | if (!ti.IsValid()) { |
| 188 | return false; |
| 189 | } |
| 190 | |
Aart Bik | 4c563ca | 2018-01-24 16:34:25 -0800 | [diff] [blame] | 191 | // Scan all uses of obj and find null check under control dependence. |
| 192 | HBoundType* bound = nullptr; |
| 193 | const HUseList<HInstruction*>& uses = obj->GetUses(); |
| 194 | for (auto it = uses.begin(), end = uses.end(); it != end;) { |
| 195 | HInstruction* user = it->GetUser(); |
| 196 | ++it; // increment before possibly replacing |
| 197 | if (user->IsNullCheck()) { |
| 198 | HBasicBlock* user_block = user->GetBlock(); |
| 199 | if (user_block != block && |
| 200 | user_block != throws && |
| 201 | block->Dominates(user_block)) { |
| 202 | if (bound == nullptr) { |
Aart Bik | 4c563ca | 2018-01-24 16:34:25 -0800 | [diff] [blame] | 203 | bound = new (obj->GetBlock()->GetGraph()->GetAllocator()) HBoundType(obj); |
| 204 | bound->SetUpperBound(ti, /*can_be_null*/ false); |
| 205 | bound->SetReferenceTypeInfo(ti); |
| 206 | bound->SetCanBeNull(false); |
| 207 | not_throws->InsertInstructionBefore(bound, not_throws->GetFirstInstruction()); |
| 208 | } |
| 209 | user->ReplaceWith(bound); |
| 210 | user_block->RemoveInstruction(user); |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | return bound != nullptr; |
| 215 | } |
| 216 | |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 217 | // Simplify the pattern: |
| 218 | // |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 219 | // B1 |
| 220 | // / \ |
Santiago Aboy Solanes | cef72a6 | 2022-04-06 14:13:18 +0000 | [diff] [blame] | 221 | // | instr_1 |
| 222 | // | ... |
| 223 | // | instr_n |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 224 | // | foo() // always throws |
Santiago Aboy Solanes | 78f3d3a | 2022-07-15 14:30:05 +0100 | [diff] [blame] | 225 | // | instr_n+2 |
| 226 | // | ... |
| 227 | // | instr_n+m |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 228 | // \ goto B2 |
| 229 | // \ / |
| 230 | // B2 |
| 231 | // |
| 232 | // Into: |
| 233 | // |
| 234 | // B1 |
| 235 | // / \ |
Santiago Aboy Solanes | cef72a6 | 2022-04-06 14:13:18 +0000 | [diff] [blame] | 236 | // | instr_1 |
| 237 | // | ... |
| 238 | // | instr_n |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 239 | // | foo() |
| 240 | // | goto Exit |
| 241 | // | | |
| 242 | // B2 Exit |
| 243 | // |
| 244 | // Rationale: |
Santiago Aboy Solanes | a3bd09c | 2022-07-29 14:09:28 +0100 | [diff] [blame] | 245 | // Removal of the never taken edge to B2 may expose other optimization opportunities, such as code |
| 246 | // sinking. |
| 247 | // |
| 248 | // Note: The example above is a simple one that uses a `goto` but we could end the block with an If, |
| 249 | // for example. |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 250 | bool HDeadCodeElimination::SimplifyAlwaysThrows() { |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 251 | HBasicBlock* exit = graph_->GetExitBlock(); |
Santiago Aboy Solanes | 78f3d3a | 2022-07-15 14:30:05 +0100 | [diff] [blame] | 252 | if (!graph_->HasAlwaysThrowingInvokes() || exit == nullptr) { |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 253 | return false; |
| 254 | } |
| 255 | |
| 256 | bool rerun_dominance_and_loop_analysis = false; |
| 257 | |
| 258 | // Order does not matter, just pick one. |
| 259 | for (HBasicBlock* block : graph_->GetReversePostOrder()) { |
Santiago Aboy Solanes | a3bd09c | 2022-07-29 14:09:28 +0100 | [diff] [blame] | 260 | if (block->IsTryBlock()) { |
Santiago Aboy Solanes | cef72a6 | 2022-04-06 14:13:18 +0000 | [diff] [blame] | 261 | // We don't want to perform the simplify always throws optimizations for throws inside of |
Santiago Aboy Solanes | a3bd09c | 2022-07-29 14:09:28 +0100 | [diff] [blame] | 262 | // tries since those throws might not go to the exit block. |
Santiago Aboy Solanes | cef72a6 | 2022-04-06 14:13:18 +0000 | [diff] [blame] | 263 | continue; |
| 264 | } |
| 265 | |
Santiago Aboy Solanes | a3bd09c | 2022-07-29 14:09:28 +0100 | [diff] [blame] | 266 | // We iterate to find the first instruction that always throws. If two instructions always |
| 267 | // throw, the first one will throw and the second one will never be reached. |
| 268 | HInstruction* throwing_invoke = nullptr; |
| 269 | for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { |
| 270 | if (it.Current()->IsInvoke() && it.Current()->AsInvoke()->AlwaysThrows()) { |
| 271 | throwing_invoke = it.Current(); |
| 272 | break; |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 273 | } |
| 274 | } |
Santiago Aboy Solanes | a3bd09c | 2022-07-29 14:09:28 +0100 | [diff] [blame] | 275 | |
| 276 | if (throwing_invoke == nullptr) { |
| 277 | // No always-throwing instruction found. Continue with the rest of the blocks. |
| 278 | continue; |
| 279 | } |
| 280 | |
| 281 | // If we are already pointing at the exit block we could still remove the instructions |
| 282 | // between the always throwing instruction, and the exit block. If we have no other |
| 283 | // instructions, just continue since there's nothing to do. |
| 284 | if (block->GetSuccessors().size() == 1 && |
| 285 | block->GetSingleSuccessor() == exit && |
| 286 | block->GetLastInstruction()->GetPrevious() == throwing_invoke) { |
| 287 | continue; |
| 288 | } |
| 289 | |
| 290 | // We split the block at the throwing instruction, and the instructions after the throwing |
| 291 | // instructions will be disconnected from the graph after `block` points to the exit. |
| 292 | // `RemoveDeadBlocks` will take care of removing this new block and its instructions. |
| 293 | // Even though `SplitBefore` doesn't guarantee the graph to remain in SSA form, it is fine |
| 294 | // since we do not break it. |
| 295 | HBasicBlock* new_block = block->SplitBefore(throwing_invoke->GetNext(), |
| 296 | /* require_graph_not_in_ssa_form= */ false); |
| 297 | DCHECK_EQ(block->GetSingleSuccessor(), new_block); |
| 298 | block->ReplaceSuccessor(new_block, exit); |
| 299 | |
| 300 | rerun_dominance_and_loop_analysis = true; |
| 301 | MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyThrowingInvoke); |
| 302 | // Perform a quick follow up optimization on object != null control dependences |
| 303 | // that is much cheaper to perform now than in a later phase. |
| 304 | // If there are multiple predecessors, none may end with a HIf as required in |
| 305 | // RemoveNonNullControlDependences because we split critical edges. |
| 306 | if (block->GetPredecessors().size() == 1u && |
| 307 | RemoveNonNullControlDependences(block->GetSinglePredecessor(), block)) { |
| 308 | MaybeRecordStat(stats_, MethodCompilationStat::kRemovedNullCheck); |
| 309 | } |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 310 | } |
| 311 | |
| 312 | // We need to re-analyze the graph in order to run DCE afterwards. |
| 313 | if (rerun_dominance_and_loop_analysis) { |
| 314 | graph_->ClearLoopInformation(); |
| 315 | graph_->ClearDominanceInformation(); |
| 316 | graph_->BuildDominatorTree(); |
| 317 | return true; |
| 318 | } |
| 319 | return false; |
| 320 | } |
| 321 | |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 322 | bool HDeadCodeElimination::SimplifyIfs() { |
| 323 | bool simplified_one_or_more_ifs = false; |
| 324 | bool rerun_dominance_and_loop_analysis = false; |
| 325 | |
Santiago Aboy Solanes | 10d6870 | 2023-01-24 18:05:10 +0000 | [diff] [blame] | 326 | // Iterating in PostOrder it's better for MaybeAddPhi as it can add a Phi for multiple If |
| 327 | // instructions in a chain without updating the dominator chain. The branch redirection itself can |
| 328 | // work in PostOrder or ReversePostOrder without issues. |
| 329 | for (HBasicBlock* block : graph_->GetPostOrder()) { |
| 330 | if (block->IsCatchBlock()) { |
| 331 | // This simplification cannot be applied to catch blocks, because exception handler edges do |
| 332 | // not represent normal control flow. Though in theory this could still apply to normal |
| 333 | // control flow going directly to a catch block, we cannot support it at the moment because |
| 334 | // the catch Phi's inputs do not correspond to the catch block's predecessors, so we cannot |
| 335 | // identify which predecessor corresponds to a given statically evaluated input. |
| 336 | continue; |
| 337 | } |
| 338 | |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 339 | HInstruction* last = block->GetLastInstruction(); |
Santiago Aboy Solanes | 10d6870 | 2023-01-24 18:05:10 +0000 | [diff] [blame] | 340 | if (!last->IsIf()) { |
| 341 | continue; |
| 342 | } |
| 343 | |
| 344 | if (block->IsLoopHeader()) { |
| 345 | // We do not apply this optimization to loop headers as this could create irreducible loops. |
| 346 | continue; |
| 347 | } |
| 348 | |
| 349 | // We will add a Phi which allows the simplification to take place in cases where it wouldn't. |
| 350 | MaybeAddPhi(block); |
| 351 | |
| 352 | // TODO(solanes): Investigate support for multiple phis in `block`. We can potentially "push |
| 353 | // downwards" existing Phis into the true/false branches. For example, let's say we have another |
| 354 | // Phi: Phi(x1,x2,x3,x4,x5,x6). This could turn into Phi(x1,x2) in the true branch, Phi(x3,x4) |
| 355 | // in the false branch, and remain as Phi(x5,x6) in `block` (for edges that we couldn't |
| 356 | // redirect). We might even be able to remove some phis altogether as they will have only one |
| 357 | // value. |
| 358 | if (block->HasSinglePhi() && |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 359 | block->GetFirstPhi()->HasOnlyOneNonEnvironmentUse()) { |
Santiago Aboy Solanes | 10d6870 | 2023-01-24 18:05:10 +0000 | [diff] [blame] | 360 | HInstruction* first = block->GetFirstInstruction(); |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 361 | bool has_only_phi_and_if = (last == first) && (last->InputAt(0) == block->GetFirstPhi()); |
| 362 | bool has_only_phi_condition_and_if = |
| 363 | !has_only_phi_and_if && |
| 364 | first->IsCondition() && |
| 365 | HasInput(first->AsCondition(), block->GetFirstPhi()) && |
| 366 | (first->GetNext() == last) && |
| 367 | (last->InputAt(0) == first) && |
| 368 | first->HasOnlyOneNonEnvironmentUse(); |
| 369 | |
| 370 | if (has_only_phi_and_if || has_only_phi_condition_and_if) { |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 371 | HPhi* phi = block->GetFirstPhi()->AsPhi(); |
| 372 | bool phi_input_is_left = (first->InputAt(0) == phi); |
| 373 | |
| 374 | // Walk over all inputs of the phis and update the control flow of |
| 375 | // predecessors feeding constants to the phi. |
| 376 | // Note that phi->InputCount() may change inside the loop. |
| 377 | for (size_t i = 0; i < phi->InputCount();) { |
| 378 | HInstruction* input = phi->InputAt(i); |
| 379 | HInstruction* value_to_check = nullptr; |
| 380 | if (has_only_phi_and_if) { |
| 381 | if (input->IsIntConstant()) { |
| 382 | value_to_check = input; |
| 383 | } |
| 384 | } else { |
| 385 | DCHECK(has_only_phi_condition_and_if); |
| 386 | if (phi_input_is_left) { |
| 387 | value_to_check = Evaluate(first->AsCondition(), input, first->InputAt(1)); |
| 388 | } else { |
| 389 | value_to_check = Evaluate(first->AsCondition(), first->InputAt(0), input); |
| 390 | } |
| 391 | } |
| 392 | if (value_to_check == nullptr) { |
| 393 | // Could not evaluate to a constant, continue iterating over the inputs. |
| 394 | ++i; |
| 395 | } else { |
| 396 | HBasicBlock* predecessor_to_update = block->GetPredecessors()[i]; |
| 397 | HBasicBlock* successor_to_update = nullptr; |
| 398 | if (value_to_check->AsIntConstant()->IsTrue()) { |
| 399 | successor_to_update = last->AsIf()->IfTrueSuccessor(); |
| 400 | } else { |
| 401 | DCHECK(value_to_check->AsIntConstant()->IsFalse()) |
| 402 | << value_to_check->AsIntConstant()->GetValue(); |
| 403 | successor_to_update = last->AsIf()->IfFalseSuccessor(); |
| 404 | } |
| 405 | predecessor_to_update->ReplaceSuccessor(block, successor_to_update); |
| 406 | phi->RemoveInputAt(i); |
| 407 | simplified_one_or_more_ifs = true; |
| 408 | if (block->IsInLoop()) { |
| 409 | rerun_dominance_and_loop_analysis = true; |
| 410 | } |
| 411 | // For simplicity, don't create a dead block, let the dead code elimination |
| 412 | // pass deal with it. |
| 413 | if (phi->InputCount() == 1) { |
| 414 | break; |
| 415 | } |
| 416 | } |
| 417 | } |
| 418 | if (block->GetPredecessors().size() == 1) { |
| 419 | phi->ReplaceWith(phi->InputAt(0)); |
| 420 | block->RemovePhi(phi); |
| 421 | if (has_only_phi_condition_and_if) { |
| 422 | // Evaluate here (and not wait for a constant folding pass) to open |
| 423 | // more opportunities for DCE. |
| 424 | HInstruction* result = first->AsCondition()->TryStaticEvaluation(); |
| 425 | if (result != nullptr) { |
| 426 | first->ReplaceWith(result); |
| 427 | block->RemoveInstruction(first); |
| 428 | } |
| 429 | } |
| 430 | } |
| 431 | if (simplified_one_or_more_ifs) { |
| 432 | MaybeRecordSimplifyIf(); |
| 433 | } |
| 434 | } |
| 435 | } |
| 436 | } |
| 437 | // We need to re-analyze the graph in order to run DCE afterwards. |
| 438 | if (simplified_one_or_more_ifs) { |
| 439 | if (rerun_dominance_and_loop_analysis) { |
| 440 | graph_->ClearLoopInformation(); |
| 441 | graph_->ClearDominanceInformation(); |
| 442 | graph_->BuildDominatorTree(); |
| 443 | } else { |
| 444 | graph_->ClearDominanceInformation(); |
| 445 | // We have introduced critical edges, remove them. |
| 446 | graph_->SimplifyCFG(); |
| 447 | graph_->ComputeDominanceInformation(); |
| 448 | graph_->ComputeTryBlockInformation(); |
| 449 | } |
| 450 | } |
| 451 | |
| 452 | return simplified_one_or_more_ifs; |
| 453 | } |
| 454 | |
Santiago Aboy Solanes | 10d6870 | 2023-01-24 18:05:10 +0000 | [diff] [blame] | 455 | void HDeadCodeElimination::MaybeAddPhi(HBasicBlock* block) { |
| 456 | DCHECK(block->GetLastInstruction()->IsIf()); |
| 457 | HIf* if_instruction = block->GetLastInstruction()->AsIf(); |
| 458 | if (if_instruction->InputAt(0)->IsConstant()) { |
| 459 | // Constant values are handled in RemoveDeadBlocks. |
| 460 | return; |
| 461 | } |
| 462 | |
| 463 | if (block->GetNumberOfPredecessors() < 2u) { |
| 464 | // Nothing to redirect. |
| 465 | return; |
| 466 | } |
| 467 | |
| 468 | if (!block->GetPhis().IsEmpty()) { |
| 469 | // SimplifyIf doesn't currently work with multiple phis. Adding a phi here won't help that |
| 470 | // optimization. |
| 471 | return; |
| 472 | } |
| 473 | |
| 474 | HBasicBlock* dominator = block->GetDominator(); |
| 475 | if (!dominator->EndsWithIf()) { |
| 476 | return; |
| 477 | } |
| 478 | |
| 479 | HInstruction* input = if_instruction->InputAt(0); |
| 480 | HInstruction* dominator_input = dominator->GetLastInstruction()->AsIf()->InputAt(0); |
| 481 | const bool same_input = dominator_input == input; |
| 482 | if (!same_input) { |
| 483 | // Try to see if the dominator has the opposite input (e.g. if(cond) and if(!cond)). If that's |
| 484 | // the case, we can perform the optimization with the false and true branches reversed. |
| 485 | if (!dominator_input->IsCondition() || !input->IsCondition()) { |
| 486 | return; |
| 487 | } |
| 488 | |
| 489 | HCondition* block_cond = input->AsCondition(); |
| 490 | HCondition* dominator_cond = dominator_input->AsCondition(); |
| 491 | |
| 492 | if (block_cond->GetLeft() != dominator_cond->GetLeft() || |
| 493 | block_cond->GetRight() != dominator_cond->GetRight() || |
| 494 | block_cond->GetOppositeCondition() != dominator_cond->GetCondition()) { |
| 495 | return; |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | if (kIsDebugBuild) { |
| 500 | // `block`'s successors should have only one predecessor. Otherwise, we have a critical edge in |
| 501 | // the graph. |
| 502 | for (HBasicBlock* succ : block->GetSuccessors()) { |
| 503 | DCHECK_EQ(succ->GetNumberOfPredecessors(), 1u); |
| 504 | } |
| 505 | } |
| 506 | |
| 507 | const size_t pred_size = block->GetNumberOfPredecessors(); |
| 508 | HPhi* new_phi = new (graph_->GetAllocator()) |
| 509 | HPhi(graph_->GetAllocator(), kNoRegNumber, pred_size, DataType::Type::kInt32); |
| 510 | |
| 511 | for (size_t index = 0; index < pred_size; index++) { |
| 512 | HBasicBlock* pred = block->GetPredecessors()[index]; |
| 513 | const bool dominated_by_true = |
| 514 | dominator->GetLastInstruction()->AsIf()->IfTrueSuccessor()->Dominates(pred); |
| 515 | const bool dominated_by_false = |
| 516 | dominator->GetLastInstruction()->AsIf()->IfFalseSuccessor()->Dominates(pred); |
| 517 | if (dominated_by_true == dominated_by_false) { |
| 518 | // In this case, we can't know if we are coming from the true branch, or the false branch. It |
| 519 | // happens in cases like: |
| 520 | // 1 (outer if) |
| 521 | // / \ |
| 522 | // 2 3 (inner if) |
| 523 | // | / \ |
| 524 | // | 4 5 |
| 525 | // \/ | |
| 526 | // 6 | |
| 527 | // \ | |
| 528 | // 7 (has the same if(cond) as 1) |
| 529 | // | |
| 530 | // 8 |
| 531 | // `7` (which would be `block` in this example), and `6` will come from both the true path and |
| 532 | // the false path of `1`. We bumped into something similar in SelectGenerator. See |
| 533 | // HSelectGenerator::TryFixupDoubleDiamondPattern. |
| 534 | // TODO(solanes): Figure out if we can fix up the graph into a double diamond in a generic way |
| 535 | // so that DeadCodeElimination and SelectGenerator can take advantage of it. |
| 536 | |
| 537 | if (!same_input) { |
| 538 | // `1` and `7` having the opposite condition is a case we are missing. We could potentially |
| 539 | // add a BooleanNot instruction to be able to add the Phi, but it seems like overkill since |
| 540 | // this case is not that common. |
| 541 | return; |
| 542 | } |
| 543 | |
| 544 | // The Phi will have `0`, `1`, and `cond` as inputs. If SimplifyIf redirects 0s and 1s, we |
| 545 | // will end up with Phi(cond,...,cond) which will be replaced by `cond`. Effectively, we will |
| 546 | // redirect edges that we are able to redirect and the rest will remain as before (i.e. we |
| 547 | // won't have an extra Phi). |
| 548 | new_phi->SetRawInputAt(index, input); |
| 549 | } else { |
| 550 | // Redirect to either the true branch (1), or the false branch (0). |
| 551 | // Given that `dominated_by_true` is the exact opposite of `dominated_by_false`, |
| 552 | // `(same_input && dominated_by_true) || (!same_input && dominated_by_false)` is equivalent to |
| 553 | // `same_input == dominated_by_true`. |
| 554 | new_phi->SetRawInputAt( |
| 555 | index, |
| 556 | same_input == dominated_by_true ? graph_->GetIntConstant(1) : graph_->GetIntConstant(0)); |
| 557 | } |
| 558 | } |
| 559 | |
| 560 | block->AddPhi(new_phi); |
| 561 | if_instruction->ReplaceInput(new_phi, 0); |
| 562 | |
| 563 | // Remove the old input now, if possible. This allows the branch redirection in SimplifyIf to |
| 564 | // work without waiting for another pass of DCE. |
| 565 | if (input->IsDeadAndRemovable()) { |
| 566 | DCHECK(!same_input) |
| 567 | << " if both blocks have the same condition, it shouldn't be dead and removable since the " |
| 568 | << "dominator block's If instruction would be using that condition."; |
| 569 | input->GetBlock()->RemoveInstruction(input); |
| 570 | } |
| 571 | MaybeRecordStat(stats_, MethodCompilationStat::kSimplifyIfAddedPhi); |
| 572 | } |
| 573 | |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 574 | void HDeadCodeElimination::ConnectSuccessiveBlocks() { |
Vladimir Marko | 2c45bc9 | 2016-10-25 16:54:12 +0100 | [diff] [blame] | 575 | // Order does not matter. Skip the entry block by starting at index 1 in reverse post order. |
| 576 | for (size_t i = 1u, size = graph_->GetReversePostOrder().size(); i != size; ++i) { |
| 577 | HBasicBlock* block = graph_->GetReversePostOrder()[i]; |
| 578 | DCHECK(!block->IsEntryBlock()); |
| 579 | while (block->GetLastInstruction()->IsGoto()) { |
| 580 | HBasicBlock* successor = block->GetSingleSuccessor(); |
| 581 | if (successor->IsExitBlock() || successor->GetPredecessors().size() != 1u) { |
| 582 | break; |
| 583 | } |
| 584 | DCHECK_LT(i, IndexOfElement(graph_->GetReversePostOrder(), successor)); |
| 585 | block->MergeWith(successor); |
| 586 | --size; |
| 587 | DCHECK_EQ(size, graph_->GetReversePostOrder().size()); |
| 588 | DCHECK_EQ(block, graph_->GetReversePostOrder()[i]); |
| 589 | // Reiterate on this block in case it can be merged with its new successor. |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 590 | } |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 591 | } |
| 592 | } |
| 593 | |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 594 | struct HDeadCodeElimination::TryBelongingInformation { |
Santiago Aboy Solanes | 44ba423 | 2022-11-23 12:27:57 +0000 | [diff] [blame] | 595 | explicit TryBelongingInformation(ScopedArenaAllocator* allocator) |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 596 | : blocks_in_try(allocator->Adapter(kArenaAllocDCE)), |
| 597 | coalesced_try_entries(allocator->Adapter(kArenaAllocDCE)) {} |
| 598 | |
| 599 | // Which blocks belong in the try. |
| 600 | ScopedArenaSet<HBasicBlock*> blocks_in_try; |
| 601 | // Which other try entries are referencing this same try. |
| 602 | ScopedArenaSet<HBasicBlock*> coalesced_try_entries; |
| 603 | }; |
| 604 | |
| 605 | bool HDeadCodeElimination::CanPerformTryRemoval(const TryBelongingInformation& try_belonging_info) { |
| 606 | for (HBasicBlock* block : try_belonging_info.blocks_in_try) { |
| 607 | for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { |
| 608 | if (it.Current()->CanThrow()) { |
| 609 | return false; |
| 610 | } |
| 611 | } |
| 612 | } |
| 613 | return true; |
| 614 | } |
| 615 | |
| 616 | void HDeadCodeElimination::DisconnectHandlersAndUpdateTryBoundary( |
| 617 | HBasicBlock* block, |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 618 | /* out */ bool* any_block_in_loop) { |
| 619 | if (block->IsInLoop()) { |
| 620 | *any_block_in_loop = true; |
| 621 | } |
| 622 | |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 623 | // Disconnect the handlers. |
| 624 | while (block->GetSuccessors().size() > 1) { |
| 625 | HBasicBlock* handler = block->GetSuccessors()[1]; |
| 626 | DCHECK(handler->IsCatchBlock()); |
| 627 | block->RemoveSuccessor(handler); |
| 628 | handler->RemovePredecessor(block); |
| 629 | if (handler->IsInLoop()) { |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 630 | *any_block_in_loop = true; |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 631 | } |
| 632 | } |
| 633 | |
| 634 | // Change TryBoundary to Goto. |
| 635 | DCHECK(block->EndsWithTryBoundary()); |
| 636 | HInstruction* last = block->GetLastInstruction(); |
| 637 | block->RemoveInstruction(last); |
| 638 | block->AddInstruction(new (graph_->GetAllocator()) HGoto(last->GetDexPc())); |
| 639 | DCHECK_EQ(block->GetSuccessors().size(), 1u); |
| 640 | } |
| 641 | |
| 642 | void HDeadCodeElimination::RemoveTry(HBasicBlock* try_entry, |
| 643 | const TryBelongingInformation& try_belonging_info, |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 644 | /* out */ bool* any_block_in_loop) { |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 645 | // Update all try entries. |
| 646 | DCHECK(try_entry->EndsWithTryBoundary()); |
| 647 | DCHECK(try_entry->GetLastInstruction()->AsTryBoundary()->IsEntry()); |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 648 | DisconnectHandlersAndUpdateTryBoundary(try_entry, any_block_in_loop); |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 649 | |
| 650 | for (HBasicBlock* other_try_entry : try_belonging_info.coalesced_try_entries) { |
| 651 | DCHECK(other_try_entry->EndsWithTryBoundary()); |
| 652 | DCHECK(other_try_entry->GetLastInstruction()->AsTryBoundary()->IsEntry()); |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 653 | DisconnectHandlersAndUpdateTryBoundary(other_try_entry, any_block_in_loop); |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 654 | } |
| 655 | |
| 656 | // Update the blocks in the try. |
| 657 | for (HBasicBlock* block : try_belonging_info.blocks_in_try) { |
| 658 | // Update the try catch information since now the try doesn't exist. |
| 659 | block->SetTryCatchInformation(nullptr); |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 660 | if (block->IsInLoop()) { |
| 661 | *any_block_in_loop = true; |
| 662 | } |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 663 | |
| 664 | if (block->EndsWithTryBoundary()) { |
| 665 | // Try exits. |
| 666 | DCHECK(!block->GetLastInstruction()->AsTryBoundary()->IsEntry()); |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 667 | DisconnectHandlersAndUpdateTryBoundary(block, any_block_in_loop); |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 668 | |
| 669 | if (block->GetSingleSuccessor()->IsExitBlock()) { |
Santiago Aboy Solanes | e22e84b | 2022-11-28 11:49:16 +0000 | [diff] [blame] | 670 | // `block` used to be a single exit TryBoundary that got turned into a Goto. It |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 671 | // is now pointing to the exit which we don't allow. To fix it, we disconnect |
Santiago Aboy Solanes | e22e84b | 2022-11-28 11:49:16 +0000 | [diff] [blame] | 672 | // `block` from its predecessor and RemoveDeadBlocks will remove it from the |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 673 | // graph. |
| 674 | DCHECK(block->IsSingleGoto()); |
| 675 | HBasicBlock* predecessor = block->GetSinglePredecessor(); |
| 676 | predecessor->ReplaceSuccessor(block, graph_->GetExitBlock()); |
Santiago Aboy Solanes | e22e84b | 2022-11-28 11:49:16 +0000 | [diff] [blame] | 677 | |
| 678 | if (!block->GetDominatedBlocks().empty()) { |
| 679 | // Update domination tree if `block` dominates a block to keep the graph consistent. |
| 680 | DCHECK_EQ(block->GetDominatedBlocks().size(), 1u); |
| 681 | DCHECK_EQ(graph_->GetExitBlock()->GetDominator(), block); |
| 682 | predecessor->AddDominatedBlock(graph_->GetExitBlock()); |
| 683 | graph_->GetExitBlock()->SetDominator(predecessor); |
| 684 | block->RemoveDominatedBlock(graph_->GetExitBlock()); |
| 685 | } |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 686 | } |
| 687 | } |
| 688 | } |
| 689 | } |
| 690 | |
| 691 | bool HDeadCodeElimination::RemoveUnneededTries() { |
| 692 | if (!graph_->HasTryCatch()) { |
| 693 | return false; |
| 694 | } |
| 695 | |
| 696 | // Use local allocator for allocating memory. |
| 697 | ScopedArenaAllocator allocator(graph_->GetArenaStack()); |
| 698 | |
| 699 | // Collect which blocks are part of which try. |
| 700 | std::unordered_map<HBasicBlock*, TryBelongingInformation> tries; |
| 701 | for (HBasicBlock* block : graph_->GetReversePostOrderSkipEntryBlock()) { |
| 702 | if (block->IsTryBlock()) { |
| 703 | HBasicBlock* key = block->GetTryCatchInformation()->GetTryEntry().GetBlock(); |
| 704 | auto it = tries.find(key); |
| 705 | if (it == tries.end()) { |
| 706 | it = tries.insert({key, TryBelongingInformation(&allocator)}).first; |
| 707 | } |
| 708 | it->second.blocks_in_try.insert(block); |
| 709 | } |
| 710 | } |
| 711 | |
| 712 | // Deduplicate the tries which have different try entries but they are really the same try. |
| 713 | for (auto it = tries.begin(); it != tries.end(); it++) { |
| 714 | DCHECK(it->first->EndsWithTryBoundary()); |
| 715 | HTryBoundary* try_boundary = it->first->GetLastInstruction()->AsTryBoundary(); |
| 716 | for (auto other_it = next(it); other_it != tries.end(); /*other_it++ in the loop*/) { |
| 717 | DCHECK(other_it->first->EndsWithTryBoundary()); |
| 718 | HTryBoundary* other_try_boundary = other_it->first->GetLastInstruction()->AsTryBoundary(); |
| 719 | if (try_boundary->HasSameExceptionHandlersAs(*other_try_boundary)) { |
| 720 | // Merge the entries as they are really the same one. |
| 721 | // Block merging. |
| 722 | it->second.blocks_in_try.insert(other_it->second.blocks_in_try.begin(), |
| 723 | other_it->second.blocks_in_try.end()); |
| 724 | |
| 725 | // Add the coalesced try entry to update it too. |
| 726 | it->second.coalesced_try_entries.insert(other_it->first); |
| 727 | |
| 728 | // Erase the other entry. |
| 729 | other_it = tries.erase(other_it); |
| 730 | } else { |
| 731 | other_it++; |
| 732 | } |
| 733 | } |
| 734 | } |
| 735 | |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 736 | size_t removed_tries = 0; |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 737 | bool any_block_in_loop = false; |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 738 | |
| 739 | // Check which tries contain throwing instructions. |
| 740 | for (const auto& entry : tries) { |
| 741 | if (CanPerformTryRemoval(entry.second)) { |
| 742 | ++removed_tries; |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 743 | RemoveTry(entry.first, entry.second, &any_block_in_loop); |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 744 | } |
| 745 | } |
| 746 | |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 747 | if (removed_tries != 0) { |
| 748 | // We want to: |
| 749 | // 1) Update the dominance information |
| 750 | // 2) Remove catch block subtrees, if they are now unreachable. |
| 751 | // If we run the dominance recomputation without removing the code, those catch blocks will |
| 752 | // not be part of the post order and won't be removed. If we don't run the dominance |
| 753 | // recomputation, we risk RemoveDeadBlocks not running it and leaving the graph in an |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 754 | // inconsistent state. So, what we can do is run RemoveDeadBlocks and force a recomputation. |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 755 | // Note that we are not guaranteed to remove a catch block if we have nested try blocks: |
| 756 | // |
| 757 | // try { |
| 758 | // ... nothing can throw. TryBoundary A ... |
| 759 | // try { |
| 760 | // ... can throw. TryBoundary B... |
| 761 | // } catch (Error e) {} |
| 762 | // } catch (Exception e) {} |
| 763 | // |
| 764 | // In the example above, we can remove the TryBoundary A but the Exception catch cannot be |
| 765 | // removed as the TryBoundary B might still throw into that catch. TryBoundary A and B don't get |
| 766 | // coalesced since they have different catch handlers. |
| 767 | |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 768 | RemoveDeadBlocks(/* force_recomputation= */ true, any_block_in_loop); |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 769 | MaybeRecordStat(stats_, MethodCompilationStat::kRemovedTry, removed_tries); |
| 770 | return true; |
| 771 | } else { |
| 772 | return false; |
| 773 | } |
| 774 | } |
| 775 | |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 776 | bool HDeadCodeElimination::RemoveDeadBlocks(bool force_recomputation, |
| 777 | bool force_loop_recomputation) { |
| 778 | DCHECK_IMPLIES(force_loop_recomputation, force_recomputation); |
| 779 | |
Vladimir Marko | 009d166 | 2017-10-10 13:21:15 +0100 | [diff] [blame] | 780 | // Use local allocator for allocating memory. |
| 781 | ScopedArenaAllocator allocator(graph_->GetArenaStack()); |
| 782 | |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 783 | // Classify blocks as reachable/unreachable. |
Vladimir Marko | 009d166 | 2017-10-10 13:21:15 +0100 | [diff] [blame] | 784 | ArenaBitVector live_blocks(&allocator, graph_->GetBlocks().size(), false, kArenaAllocDCE); |
| 785 | live_blocks.ClearAllBits(); |
David Brazdil | a4b8c21 | 2015-05-07 09:59:30 +0100 | [diff] [blame] | 786 | |
Vladimir Marko | 211c211 | 2015-09-24 16:52:33 +0100 | [diff] [blame] | 787 | MarkReachableBlocks(graph_, &live_blocks); |
Nicolas Geoffray | 1f82ecc | 2015-06-24 12:20:24 +0100 | [diff] [blame] | 788 | bool removed_one_or_more_blocks = false; |
Nicolas Geoffray | 15bd228 | 2016-01-05 15:55:41 +0000 | [diff] [blame] | 789 | bool rerun_dominance_and_loop_analysis = false; |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 790 | |
David Brazdil | a4b8c21 | 2015-05-07 09:59:30 +0100 | [diff] [blame] | 791 | // Remove all dead blocks. Iterate in post order because removal needs the |
| 792 | // block's chain of dominators and nested loops need to be updated from the |
| 793 | // inside out. |
Vladimir Marko | 2c45bc9 | 2016-10-25 16:54:12 +0100 | [diff] [blame] | 794 | for (HBasicBlock* block : graph_->GetPostOrder()) { |
David Brazdil | a4b8c21 | 2015-05-07 09:59:30 +0100 | [diff] [blame] | 795 | int id = block->GetBlockId(); |
Nicolas Geoffray | 15bd228 | 2016-01-05 15:55:41 +0000 | [diff] [blame] | 796 | if (!live_blocks.IsBitSet(id)) { |
David Brazdil | 69a2804 | 2015-04-29 17:16:07 +0100 | [diff] [blame] | 797 | MaybeRecordDeadBlock(block); |
| 798 | block->DisconnectAndDelete(); |
Nicolas Geoffray | 1f82ecc | 2015-06-24 12:20:24 +0100 | [diff] [blame] | 799 | removed_one_or_more_blocks = true; |
Nicolas Geoffray | 15bd228 | 2016-01-05 15:55:41 +0000 | [diff] [blame] | 800 | if (block->IsInLoop()) { |
| 801 | rerun_dominance_and_loop_analysis = true; |
| 802 | } |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 803 | } |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 804 | } |
| 805 | |
Nicolas Geoffray | 1f82ecc | 2015-06-24 12:20:24 +0100 | [diff] [blame] | 806 | // If we removed at least one block, we need to recompute the full |
David Brazdil | 8a7c0fe | 2015-11-02 20:24:55 +0000 | [diff] [blame] | 807 | // dominator tree and try block membership. |
Santiago Aboy Solanes | fa55aa0 | 2022-11-29 11:42:20 +0000 | [diff] [blame] | 808 | if (removed_one_or_more_blocks || force_recomputation) { |
| 809 | if (rerun_dominance_and_loop_analysis || force_loop_recomputation) { |
Nicolas Geoffray | 15bd228 | 2016-01-05 15:55:41 +0000 | [diff] [blame] | 810 | graph_->ClearLoopInformation(); |
| 811 | graph_->ClearDominanceInformation(); |
| 812 | graph_->BuildDominatorTree(); |
| 813 | } else { |
| 814 | graph_->ClearDominanceInformation(); |
| 815 | graph_->ComputeDominanceInformation(); |
| 816 | graph_->ComputeTryBlockInformation(); |
| 817 | } |
Nicolas Geoffray | 1f82ecc | 2015-06-24 12:20:24 +0100 | [diff] [blame] | 818 | } |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 819 | return removed_one_or_more_blocks; |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 820 | } |
| 821 | |
| 822 | void HDeadCodeElimination::RemoveDeadInstructions() { |
Roland Levillain | 72bceff | 2014-09-15 18:29:00 +0100 | [diff] [blame] | 823 | // Process basic blocks in post-order in the dominator tree, so that |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 824 | // a dead instruction depending on another dead instruction is removed. |
Vladimir Marko | 2c45bc9 | 2016-10-25 16:54:12 +0100 | [diff] [blame] | 825 | for (HBasicBlock* block : graph_->GetPostOrder()) { |
Roland Levillain | 72bceff | 2014-09-15 18:29:00 +0100 | [diff] [blame] | 826 | // Traverse this block's instructions in backward order and remove |
| 827 | // the unused ones. |
| 828 | HBackwardInstructionIterator i(block->GetInstructions()); |
| 829 | // Skip the first iteration, as the last instruction of a block is |
| 830 | // a branching instruction. |
| 831 | DCHECK(i.Current()->IsControlFlow()); |
| 832 | for (i.Advance(); !i.Done(); i.Advance()) { |
| 833 | HInstruction* inst = i.Current(); |
| 834 | DCHECK(!inst->IsControlFlow()); |
Aart Bik | 482095d | 2016-10-10 15:39:10 -0700 | [diff] [blame] | 835 | if (inst->IsDeadAndRemovable()) { |
Roland Levillain | 72bceff | 2014-09-15 18:29:00 +0100 | [diff] [blame] | 836 | block->RemoveInstruction(inst); |
Igor Murashkin | 1e065a5 | 2017-08-09 13:20:34 -0700 | [diff] [blame] | 837 | MaybeRecordStat(stats_, MethodCompilationStat::kRemovedDeadInstruction); |
Roland Levillain | 72bceff | 2014-09-15 18:29:00 +0100 | [diff] [blame] | 838 | } |
| 839 | } |
| 840 | } |
| 841 | } |
| 842 | |
Santiago Aboy Solanes | 74da668 | 2022-12-16 19:28:47 +0000 | [diff] [blame] | 843 | void HDeadCodeElimination::UpdateGraphFlags() { |
| 844 | bool has_monitor_operations = false; |
| 845 | bool has_simd = false; |
| 846 | bool has_bounds_checks = false; |
| 847 | bool has_always_throwing_invokes = false; |
| 848 | |
| 849 | for (HBasicBlock* block : graph_->GetReversePostOrder()) { |
| 850 | for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) { |
| 851 | HInstruction* instruction = it.Current(); |
| 852 | if (instruction->IsMonitorOperation()) { |
| 853 | has_monitor_operations = true; |
| 854 | } else if (instruction->IsVecOperation()) { |
| 855 | has_simd = true; |
| 856 | } else if (instruction->IsBoundsCheck()) { |
| 857 | has_bounds_checks = true; |
| 858 | } else if (instruction->IsInvoke() && instruction->AsInvoke()->AlwaysThrows()) { |
| 859 | has_always_throwing_invokes = true; |
| 860 | } |
| 861 | } |
| 862 | } |
| 863 | |
| 864 | graph_->SetHasMonitorOperations(has_monitor_operations); |
| 865 | graph_->SetHasSIMD(has_simd); |
| 866 | graph_->SetHasBoundsChecks(has_bounds_checks); |
| 867 | graph_->SetHasAlwaysThrowingInvokes(has_always_throwing_invokes); |
| 868 | } |
| 869 | |
Aart Bik | 2477320 | 2018-04-26 10:28:51 -0700 | [diff] [blame] | 870 | bool HDeadCodeElimination::Run() { |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 871 | // Do not eliminate dead blocks if the graph has irreducible loops. We could |
| 872 | // support it, but that would require changes in our loop representation to handle |
| 873 | // multiple entry points. We decided it was not worth the complexity. |
| 874 | if (!graph_->HasIrreducibleLoops()) { |
| 875 | // Simplify graph to generate more dead block patterns. |
| 876 | ConnectSuccessiveBlocks(); |
| 877 | bool did_any_simplification = false; |
Aart Bik | a8b8e9b | 2018-01-09 11:01:02 -0800 | [diff] [blame] | 878 | did_any_simplification |= SimplifyAlwaysThrows(); |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 879 | did_any_simplification |= SimplifyIfs(); |
| 880 | did_any_simplification |= RemoveDeadBlocks(); |
Santiago Aboy Solanes | b2d364d | 2022-11-10 10:26:31 +0000 | [diff] [blame] | 881 | // We call RemoveDeadBlocks before RemoveUnneededTries to remove the dead blocks from the |
| 882 | // previous optimizations. Otherwise, we might detect that a try has throwing instructions but |
| 883 | // they are actually dead code. RemoveUnneededTryBoundary will call RemoveDeadBlocks again if |
| 884 | // needed. |
| 885 | did_any_simplification |= RemoveUnneededTries(); |
Nicolas Geoffray | dac9b19 | 2016-07-15 10:46:17 +0100 | [diff] [blame] | 886 | if (did_any_simplification) { |
| 887 | // Connect successive blocks created by dead branches. |
| 888 | ConnectSuccessiveBlocks(); |
| 889 | } |
| 890 | } |
David Brazdil | 84daae5 | 2015-05-18 12:06:52 +0100 | [diff] [blame] | 891 | SsaRedundantPhiElimination(graph_).Run(); |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 892 | RemoveDeadInstructions(); |
Santiago Aboy Solanes | 74da668 | 2022-12-16 19:28:47 +0000 | [diff] [blame] | 893 | UpdateGraphFlags(); |
Aart Bik | 2477320 | 2018-04-26 10:28:51 -0700 | [diff] [blame] | 894 | return true; |
David Brazdil | 2d7352b | 2015-04-20 14:52:42 +0100 | [diff] [blame] | 895 | } |
| 896 | |
Roland Levillain | 72bceff | 2014-09-15 18:29:00 +0100 | [diff] [blame] | 897 | } // namespace art |