Aart Bik | 281c681 | 2016-08-26 11:31:48 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2016 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 "loop_optimization.h" |
| 18 | |
| 19 | #include "base/arena_containers.h" |
| 20 | #include "induction_var_range.h" |
| 21 | #include "ssa_liveness_analysis.h" |
| 22 | #include "nodes.h" |
| 23 | |
| 24 | namespace art { |
| 25 | |
| 26 | // TODO: Generalize to cycles, as found by induction analysis? |
| 27 | static bool IsPhiAddSub(HPhi* phi, /*out*/ HInstruction** addsub_out) { |
| 28 | HInputsRef inputs = phi->GetInputs(); |
| 29 | if (inputs.size() == 2 && (inputs[1]->IsAdd() || inputs[1]->IsSub())) { |
| 30 | HInstruction* addsub = inputs[1]; |
| 31 | if (addsub->InputAt(0) == phi || addsub->InputAt(1) == phi) { |
| 32 | if (addsub->GetUses().HasExactlyOneElement()) { |
| 33 | *addsub_out = addsub; |
| 34 | return true; |
| 35 | } |
| 36 | } |
| 37 | } |
| 38 | return false; |
| 39 | } |
| 40 | |
| 41 | static bool IsOnlyUsedAfterLoop(const HLoopInformation& loop_info, |
| 42 | HPhi* phi, HInstruction* addsub) { |
| 43 | for (const HUseListNode<HInstruction*>& use : phi->GetUses()) { |
| 44 | if (use.GetUser() != addsub) { |
| 45 | HLoopInformation* other_loop_info = use.GetUser()->GetBlock()->GetLoopInformation(); |
| 46 | if (other_loop_info != nullptr && other_loop_info->IsIn(loop_info)) { |
| 47 | return false; |
| 48 | } |
| 49 | } |
| 50 | } |
| 51 | return true; |
| 52 | } |
| 53 | |
| 54 | // Find: phi: Phi(init, addsub) |
| 55 | // s: SuspendCheck |
| 56 | // c: Condition(phi, bound) |
| 57 | // i: If(c) |
| 58 | // TODO: Find a less pattern matching approach? |
| 59 | static bool IsEmptyHeader(HBasicBlock* block, /*out*/ HInstruction** addsub) { |
| 60 | HInstruction* phi = block->GetFirstPhi(); |
| 61 | if (phi != nullptr && phi->GetNext() == nullptr && IsPhiAddSub(phi->AsPhi(), addsub)) { |
| 62 | HInstruction* s = block->GetFirstInstruction(); |
| 63 | if (s != nullptr && s->IsSuspendCheck()) { |
| 64 | HInstruction* c = s->GetNext(); |
| 65 | if (c != nullptr && c->IsCondition() && c->GetUses().HasExactlyOneElement()) { |
| 66 | HInstruction* i = c->GetNext(); |
| 67 | if (i != nullptr && i->IsIf() && i->InputAt(0) == c) { |
| 68 | // Check that phi is only used inside loop as expected. |
| 69 | for (const HUseListNode<HInstruction*>& use : phi->GetUses()) { |
| 70 | if (use.GetUser() != *addsub && use.GetUser() != c) { |
| 71 | return false; |
| 72 | } |
| 73 | } |
| 74 | return true; |
| 75 | } |
| 76 | } |
| 77 | } |
| 78 | } |
| 79 | return false; |
| 80 | } |
| 81 | |
| 82 | static bool IsEmptyBody(HBasicBlock* block, HInstruction* addsub) { |
| 83 | HInstruction* phi = block->GetFirstPhi(); |
| 84 | HInstruction* i = block->GetFirstInstruction(); |
| 85 | return phi == nullptr && i == addsub && i->GetNext() != nullptr && i->GetNext()->IsGoto(); |
| 86 | } |
| 87 | |
| 88 | static HBasicBlock* TryRemovePreHeader(HBasicBlock* preheader, HBasicBlock* entry_block) { |
| 89 | if (preheader->GetPredecessors().size() == 1) { |
| 90 | HBasicBlock* entry = preheader->GetSinglePredecessor(); |
| 91 | HInstruction* anchor = entry->GetLastInstruction(); |
| 92 | // If the pre-header has a single predecessor we can remove it too if |
| 93 | // either the pre-header just contains a goto, or if the predecessor |
| 94 | // is not the entry block so we can push instructions backward |
| 95 | // (moving computation into the entry block is too dangerous!). |
| 96 | if (preheader->GetFirstInstruction() == nullptr || |
| 97 | preheader->GetFirstInstruction()->IsGoto() || |
| 98 | (entry != entry_block && anchor->IsGoto())) { |
| 99 | // Push non-goto statements backward to empty the pre-header. |
| 100 | for (HInstructionIterator it(preheader->GetInstructions()); !it.Done(); it.Advance()) { |
| 101 | HInstruction* instruction = it.Current(); |
| 102 | if (!instruction->IsGoto()) { |
| 103 | if (!instruction->CanBeMoved()) { |
| 104 | return nullptr; // pushing failed to move all |
| 105 | } |
| 106 | it.Current()->MoveBefore(anchor); |
| 107 | } |
| 108 | } |
| 109 | return entry; |
| 110 | } |
| 111 | } |
| 112 | return nullptr; |
| 113 | } |
| 114 | |
| 115 | static void RemoveFromCycle(HInstruction* instruction) { |
| 116 | // A bit more elaborate than the usual instruction removal, |
| 117 | // since there may be a cycle in the use structure. |
| 118 | instruction->RemoveAsUserOfAllInputs(); |
| 119 | instruction->RemoveEnvironmentUsers(); |
| 120 | instruction->GetBlock()->RemoveInstructionOrPhi(instruction, /*ensure_safety=*/ false); |
| 121 | } |
| 122 | |
| 123 | // |
| 124 | // Class methods. |
| 125 | // |
| 126 | |
| 127 | HLoopOptimization::HLoopOptimization(HGraph* graph, |
| 128 | HInductionVarAnalysis* induction_analysis) |
| 129 | : HOptimization(graph, kLoopOptimizationPassName), |
| 130 | induction_range_(induction_analysis), |
| 131 | loop_allocator_(graph_->GetArena()->GetArenaPool()), // phase-local allocator on global pool |
| 132 | top_loop_(nullptr), |
| 133 | last_loop_(nullptr) { |
| 134 | } |
| 135 | |
| 136 | void HLoopOptimization::Run() { |
| 137 | // Well-behaved loops only. |
| 138 | // TODO: make this less of a sledgehammer. |
| 139 | if (graph_-> HasTryCatch() || graph_->HasIrreducibleLoops()) { |
| 140 | return; |
| 141 | } |
| 142 | |
| 143 | // Build the linear order. This step enables building a loop hierarchy that |
| 144 | // properly reflects the outer-inner and previous-next relation. |
| 145 | graph_->Linearize(); |
| 146 | // Build the loop hierarchy. |
| 147 | for (HLinearOrderIterator it_graph(*graph_); !it_graph.Done(); it_graph.Advance()) { |
| 148 | HBasicBlock* block = it_graph.Current(); |
| 149 | if (block->IsLoopHeader()) { |
| 150 | AddLoop(block->GetLoopInformation()); |
| 151 | } |
| 152 | } |
| 153 | if (top_loop_ == nullptr) { |
| 154 | return; // no loops |
| 155 | } |
| 156 | // Traverse the loop hierarchy inner-to-outer and optimize. |
| 157 | TraverseLoopsInnerToOuter(top_loop_); |
| 158 | } |
| 159 | |
| 160 | void HLoopOptimization::AddLoop(HLoopInformation* loop_info) { |
| 161 | DCHECK(loop_info != nullptr); |
| 162 | LoopNode* node = new (&loop_allocator_) LoopNode(loop_info); // phase-local allocator |
| 163 | if (last_loop_ == nullptr) { |
| 164 | // First loop. |
| 165 | DCHECK(top_loop_ == nullptr); |
| 166 | last_loop_ = top_loop_ = node; |
| 167 | } else if (loop_info->IsIn(*last_loop_->loop_info)) { |
| 168 | // Inner loop. |
| 169 | node->outer = last_loop_; |
| 170 | DCHECK(last_loop_->inner == nullptr); |
| 171 | last_loop_ = last_loop_->inner = node; |
| 172 | } else { |
| 173 | // Subsequent loop. |
| 174 | while (last_loop_->outer != nullptr && !loop_info->IsIn(*last_loop_->outer->loop_info)) { |
| 175 | last_loop_ = last_loop_->outer; |
| 176 | } |
| 177 | node->outer = last_loop_->outer; |
| 178 | node->previous = last_loop_; |
| 179 | DCHECK(last_loop_->next == nullptr); |
| 180 | last_loop_ = last_loop_->next = node; |
| 181 | } |
| 182 | } |
| 183 | |
| 184 | void HLoopOptimization::RemoveLoop(LoopNode* node) { |
| 185 | DCHECK(node != nullptr); |
| 186 | // TODO: implement when needed (for current set of optimizations, we don't |
| 187 | // need to keep recorded loop hierarchy up to date, but as we get different |
| 188 | // traversal, we may want to remove the node from the hierarchy here. |
| 189 | } |
| 190 | |
| 191 | void HLoopOptimization::TraverseLoopsInnerToOuter(LoopNode* node) { |
| 192 | for ( ; node != nullptr; node = node->next) { |
| 193 | if (node->inner != nullptr) { |
| 194 | TraverseLoopsInnerToOuter(node->inner); |
| 195 | } |
| 196 | // Visit loop after its inner loops have been visited. |
| 197 | SimplifyInduction(node); |
| 198 | RemoveIfEmptyLoop(node); |
| 199 | } |
| 200 | } |
| 201 | |
| 202 | void HLoopOptimization::SimplifyInduction(LoopNode* node) { |
| 203 | HBasicBlock* header = node->loop_info->GetHeader(); |
| 204 | HBasicBlock* preheader = node->loop_info->GetPreHeader(); |
| 205 | // Scan the phis in the header to find opportunities to optimize induction. |
| 206 | for (HInstructionIterator it(header->GetPhis()); !it.Done(); it.Advance()) { |
| 207 | HPhi* phi = it.Current()->AsPhi(); |
| 208 | HInstruction* addsub = nullptr; |
| 209 | // Find phi-add/sub cycle. |
| 210 | if (IsPhiAddSub(phi, &addsub)) { |
| 211 | // Simple case, the induction is only used by itself. Although redundant, |
| 212 | // later phases do not easily detect this property. Thus, eliminate here. |
| 213 | // Example: for (int i = 0; x != null; i++) { .... no i .... } |
| 214 | if (phi->GetUses().HasExactlyOneElement()) { |
| 215 | // Remove the cycle, including all uses. Even environment uses can be removed, |
| 216 | // since these computations have no effect at all. |
| 217 | RemoveFromCycle(phi); // removes environment uses too |
| 218 | RemoveFromCycle(addsub); |
| 219 | continue; |
| 220 | } |
| 221 | // Closed form case. Only the last value of the induction is needed. Remove all |
| 222 | // overhead from the loop, and replace subsequent uses with the last value. |
| 223 | // Example: for (int i = 0; i < 10; i++, k++) { .... no k .... } return k; |
| 224 | if (IsOnlyUsedAfterLoop(*node->loop_info, phi, addsub) && |
| 225 | induction_range_.CanGenerateLastValue(phi)) { |
| 226 | HInstruction* last = induction_range_.GenerateLastValue(phi, graph_, preheader); |
| 227 | // Remove the cycle, replacing all uses. Even environment uses can consume the final |
| 228 | // value, since any first real use is outside the loop (although this may imply |
| 229 | // that deopting may look "ahead" a bit on the phi value). |
| 230 | ReplaceAllUses(phi, last, addsub); |
| 231 | RemoveFromCycle(phi); // removes environment uses too |
| 232 | RemoveFromCycle(addsub); |
| 233 | } |
| 234 | } |
| 235 | } |
| 236 | } |
| 237 | |
| 238 | void HLoopOptimization::RemoveIfEmptyLoop(LoopNode* node) { |
| 239 | HBasicBlock* header = node->loop_info->GetHeader(); |
| 240 | HBasicBlock* preheader = node->loop_info->GetPreHeader(); |
| 241 | // Ensure there is only a single loop-body (besides the header). |
| 242 | HBasicBlock* body = nullptr; |
| 243 | for (HBlocksInLoopIterator it(*node->loop_info); !it.Done(); it.Advance()) { |
| 244 | if (it.Current() != header) { |
| 245 | if (body != nullptr) { |
| 246 | return; |
| 247 | } |
| 248 | body = it.Current(); |
| 249 | } |
| 250 | } |
| 251 | // Ensure there is only a single exit point. |
| 252 | if (header->GetSuccessors().size() != 2) { |
| 253 | return; |
| 254 | } |
| 255 | HBasicBlock* exit = (header->GetSuccessors()[0] == body) |
| 256 | ? header->GetSuccessors()[1] |
| 257 | : header->GetSuccessors()[0]; |
| 258 | // Ensure exit can only be reached by exiting loop (this seems typically the |
| 259 | // case anyway, and simplifies code generation below; TODO: perhaps relax?). |
| 260 | if (exit->GetPredecessors().size() != 1) { |
| 261 | return; |
| 262 | } |
| 263 | // Detect an empty loop: no side effects other than plain iteration. |
| 264 | HInstruction* addsub = nullptr; |
| 265 | if (IsEmptyHeader(header, &addsub) && IsEmptyBody(body, addsub)) { |
| 266 | HBasicBlock* entry = TryRemovePreHeader(preheader, graph_->GetEntryBlock()); |
| 267 | body->DisconnectAndDelete(); |
| 268 | exit->RemovePredecessor(header); |
| 269 | header->RemoveSuccessor(exit); |
| 270 | header->ClearDominanceInformation(); |
| 271 | header->SetDominator(preheader); // needed by next disconnect. |
| 272 | header->DisconnectAndDelete(); |
| 273 | // If allowed, remove preheader too, which may expose next outer empty loop |
| 274 | // Otherwise, link preheader directly to exit to restore the flow graph. |
| 275 | if (entry != nullptr) { |
| 276 | entry->ReplaceSuccessor(preheader, exit); |
| 277 | entry->AddDominatedBlock(exit); |
| 278 | exit->SetDominator(entry); |
| 279 | preheader->DisconnectAndDelete(); |
| 280 | } else { |
| 281 | preheader->AddSuccessor(exit); |
| 282 | preheader->AddInstruction(new (graph_->GetArena()) HGoto()); // global allocator |
| 283 | preheader->AddDominatedBlock(exit); |
| 284 | exit->SetDominator(preheader); |
| 285 | } |
| 286 | // Update hierarchy. |
| 287 | RemoveLoop(node); |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | void HLoopOptimization::ReplaceAllUses(HInstruction* instruction, |
| 292 | HInstruction* replacement, |
| 293 | HInstruction* exclusion) { |
| 294 | const HUseList<HInstruction*>& uses = instruction->GetUses(); |
| 295 | for (auto it = uses.begin(), end = uses.end(); it != end;) { |
| 296 | HInstruction* user = it->GetUser(); |
| 297 | size_t index = it->GetIndex(); |
| 298 | ++it; // increment before replacing |
| 299 | if (user != exclusion) { |
| 300 | user->ReplaceInput(replacement, index); |
| 301 | induction_range_.Replace(user, instruction, replacement); // update induction |
| 302 | } |
| 303 | } |
| 304 | const HUseList<HEnvironment*>& env_uses = instruction->GetEnvUses(); |
| 305 | for (auto it = env_uses.begin(), end = env_uses.end(); it != end;) { |
| 306 | HEnvironment* user = it->GetUser(); |
| 307 | size_t index = it->GetIndex(); |
| 308 | ++it; // increment before replacing |
| 309 | if (user->GetHolder() != exclusion) { |
| 310 | user->RemoveAsUserOfInput(index); |
| 311 | user->SetRawEnvAt(index, replacement); |
| 312 | replacement->AddEnvUseAt(user, index); |
| 313 | } |
| 314 | } |
| 315 | } |
| 316 | |
| 317 | } // namespace art |