blob: d8ebac95a8b2eef21b74d487d5739c804c029e8a [file] [log] [blame]
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +00001/*
2 * Copyright (C) 2017 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 "code_sinking.h"
18
Vladimir Markoca6fff82017-10-03 14:49:14 +010019#include "base/arena_bit_vector.h"
20#include "base/bit_vector-inl.h"
21#include "base/scoped_arena_allocator.h"
22#include "base/scoped_arena_containers.h"
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +000023#include "common_dominator.h"
24#include "nodes.h"
25
26namespace art {
27
28void CodeSinking::Run() {
29 HBasicBlock* exit = graph_->GetExitBlock();
30 if (exit == nullptr) {
31 // Infinite loop, just bail.
32 return;
33 }
34 // TODO(ngeoffray): we do not profile branches yet, so use throw instructions
35 // as an indicator of an uncommon branch.
36 for (HBasicBlock* exit_predecessor : exit->GetPredecessors()) {
37 if (exit_predecessor->GetLastInstruction()->IsThrow()) {
38 SinkCodeToUncommonBranch(exit_predecessor);
39 }
40 }
41}
42
43static bool IsInterestingInstruction(HInstruction* instruction) {
44 // Instructions from the entry graph (for example constants) are never interesting to move.
45 if (instruction->GetBlock() == instruction->GetBlock()->GetGraph()->GetEntryBlock()) {
46 return false;
47 }
48 // We want to move moveable instructions that cannot throw, as well as
49 // heap stores and allocations.
50
51 // Volatile stores cannot be moved.
52 if (instruction->IsInstanceFieldSet()) {
53 if (instruction->AsInstanceFieldSet()->IsVolatile()) {
54 return false;
55 }
56 }
57
58 // Check allocations first, as they can throw, but it is safe to move them.
59 if (instruction->IsNewInstance() || instruction->IsNewArray()) {
60 return true;
61 }
62
Igor Murashkin79d8fa72017-04-18 09:37:23 -070063 // Check it is safe to move ConstructorFence.
64 // (Safe to move ConstructorFence for only protecting the new-instance but not for finals.)
65 if (instruction->IsConstructorFence()) {
66 HConstructorFence* ctor_fence = instruction->AsConstructorFence();
67
68 // A fence with "0" inputs is dead and should've been removed in a prior pass.
69 DCHECK_NE(0u, ctor_fence->InputCount());
70
Igor Murashkindd018df2017-08-09 10:38:31 -070071 // TODO: this should be simplified to 'return true' since it's
72 // potentially pessimizing any code sinking for inlined constructors with final fields.
73 // TODO: double check that if the final field assignments are not moved,
74 // then the fence is not moved either.
75
Igor Murashkin79d8fa72017-04-18 09:37:23 -070076 return ctor_fence->GetAssociatedAllocation() != nullptr;
77 }
78
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +000079 // All other instructions that can throw cannot be moved.
80 if (instruction->CanThrow()) {
81 return false;
82 }
83
84 // We can only store on local allocations. Other heap references can
85 // be escaping. Note that allocations can escape too, but we only move
86 // allocations if their users can move to, or are in the list of
87 // post dominated blocks.
88 if (instruction->IsInstanceFieldSet()) {
89 if (!instruction->InputAt(0)->IsNewInstance()) {
90 return false;
91 }
92 }
93
94 if (instruction->IsArraySet()) {
95 if (!instruction->InputAt(0)->IsNewArray()) {
96 return false;
97 }
98 }
99
100 // Heap accesses cannot go pass instructions that have memory side effects, which
101 // we are not tracking here. Note that the load/store elimination optimization
102 // runs before this optimization, and should have removed interesting ones.
103 // In theory, we could handle loads of local allocations, but this is currently
104 // hard to test, as LSE removes them.
105 if (instruction->IsStaticFieldGet() ||
106 instruction->IsInstanceFieldGet() ||
107 instruction->IsArrayGet()) {
108 return false;
109 }
110
111 if (instruction->IsInstanceFieldSet() ||
112 instruction->IsArraySet() ||
113 instruction->CanBeMoved()) {
114 return true;
115 }
116 return false;
117}
118
119static void AddInstruction(HInstruction* instruction,
120 const ArenaBitVector& processed_instructions,
121 const ArenaBitVector& discard_blocks,
Vladimir Markoca6fff82017-10-03 14:49:14 +0100122 ScopedArenaVector<HInstruction*>* worklist) {
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000123 // Add to the work list if the instruction is not in the list of blocks
124 // to discard, hasn't been already processed and is of interest.
125 if (!discard_blocks.IsBitSet(instruction->GetBlock()->GetBlockId()) &&
126 !processed_instructions.IsBitSet(instruction->GetId()) &&
127 IsInterestingInstruction(instruction)) {
128 worklist->push_back(instruction);
129 }
130}
131
132static void AddInputs(HInstruction* instruction,
133 const ArenaBitVector& processed_instructions,
134 const ArenaBitVector& discard_blocks,
Vladimir Markoca6fff82017-10-03 14:49:14 +0100135 ScopedArenaVector<HInstruction*>* worklist) {
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000136 for (HInstruction* input : instruction->GetInputs()) {
137 AddInstruction(input, processed_instructions, discard_blocks, worklist);
138 }
139}
140
141static void AddInputs(HBasicBlock* block,
142 const ArenaBitVector& processed_instructions,
143 const ArenaBitVector& discard_blocks,
Vladimir Markoca6fff82017-10-03 14:49:14 +0100144 ScopedArenaVector<HInstruction*>* worklist) {
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000145 for (HInstructionIterator it(block->GetPhis()); !it.Done(); it.Advance()) {
146 AddInputs(it.Current(), processed_instructions, discard_blocks, worklist);
147 }
148 for (HInstructionIterator it(block->GetInstructions()); !it.Done(); it.Advance()) {
149 AddInputs(it.Current(), processed_instructions, discard_blocks, worklist);
150 }
151}
152
153static bool ShouldFilterUse(HInstruction* instruction,
154 HInstruction* user,
155 const ArenaBitVector& post_dominated) {
156 if (instruction->IsNewInstance()) {
Igor Murashkin79d8fa72017-04-18 09:37:23 -0700157 return (user->IsInstanceFieldSet() || user->IsConstructorFence()) &&
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000158 (user->InputAt(0) == instruction) &&
159 !post_dominated.IsBitSet(user->GetBlock()->GetBlockId());
160 } else if (instruction->IsNewArray()) {
Igor Murashkin79d8fa72017-04-18 09:37:23 -0700161 return (user->IsArraySet() || user->IsConstructorFence()) &&
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000162 (user->InputAt(0) == instruction) &&
163 !post_dominated.IsBitSet(user->GetBlock()->GetBlockId());
164 }
165 return false;
166}
167
168
169// Find the ideal position for moving `instruction`. If `filter` is true,
170// we filter out store instructions to that instruction, which are processed
171// first in the step (3) of the sinking algorithm.
172// This method is tailored to the sinking algorithm, unlike
173// the generic HInstruction::MoveBeforeFirstUserAndOutOfLoops.
174static HInstruction* FindIdealPosition(HInstruction* instruction,
175 const ArenaBitVector& post_dominated,
176 bool filter = false) {
177 DCHECK(!instruction->IsPhi()); // Makes no sense for Phi.
178
179 // Find the target block.
180 CommonDominator finder(/* start_block */ nullptr);
181 for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
182 HInstruction* user = use.GetUser();
183 if (!(filter && ShouldFilterUse(instruction, user, post_dominated))) {
Nicolas Geoffray13445e72017-04-20 15:19:46 +0100184 HBasicBlock* block = user->GetBlock();
185 if (user->IsPhi()) {
186 // Special case phis by taking the incoming block for regular ones,
187 // or the dominator for catch phis.
188 block = user->AsPhi()->IsCatchPhi()
189 ? block->GetDominator()
190 : block->GetPredecessors()[use.GetIndex()];
191 }
192 finder.Update(block);
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000193 }
194 }
195 for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) {
196 DCHECK(!use.GetUser()->GetHolder()->IsPhi());
197 DCHECK(!filter || !ShouldFilterUse(instruction, use.GetUser()->GetHolder(), post_dominated));
198 finder.Update(use.GetUser()->GetHolder()->GetBlock());
199 }
200 HBasicBlock* target_block = finder.Get();
201 if (target_block == nullptr) {
202 // No user we can go next to? Likely a LSE or DCE limitation.
203 return nullptr;
204 }
205
206 // Move to the first dominator not in a loop, if we can.
207 while (target_block->IsInLoop()) {
208 if (!post_dominated.IsBitSet(target_block->GetDominator()->GetBlockId())) {
209 break;
210 }
211 target_block = target_block->GetDominator();
212 DCHECK(target_block != nullptr);
213 }
214
215 // Find insertion position. No need to filter anymore, as we have found a
216 // target block.
217 HInstruction* insert_pos = nullptr;
218 for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
219 if (use.GetUser()->GetBlock() == target_block &&
220 (insert_pos == nullptr || use.GetUser()->StrictlyDominates(insert_pos))) {
221 insert_pos = use.GetUser();
222 }
223 }
224 for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) {
225 HInstruction* user = use.GetUser()->GetHolder();
226 if (user->GetBlock() == target_block &&
227 (insert_pos == nullptr || user->StrictlyDominates(insert_pos))) {
228 insert_pos = user;
229 }
230 }
231 if (insert_pos == nullptr) {
232 // No user in `target_block`, insert before the control flow instruction.
233 insert_pos = target_block->GetLastInstruction();
234 DCHECK(insert_pos->IsControlFlow());
235 // Avoid splitting HCondition from HIf to prevent unnecessary materialization.
236 if (insert_pos->IsIf()) {
237 HInstruction* if_input = insert_pos->AsIf()->InputAt(0);
238 if (if_input == insert_pos->GetPrevious()) {
239 insert_pos = if_input;
240 }
241 }
242 }
243 DCHECK(!insert_pos->IsPhi());
244 return insert_pos;
245}
246
247
248void CodeSinking::SinkCodeToUncommonBranch(HBasicBlock* end_block) {
Vladimir Markoca6fff82017-10-03 14:49:14 +0100249 // Local allocator to discard data structures created below at the end of this optimization.
250 ScopedArenaAllocator allocator(graph_->GetArenaStack());
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000251
252 size_t number_of_instructions = graph_->GetCurrentInstructionId();
Vladimir Markoca6fff82017-10-03 14:49:14 +0100253 ScopedArenaVector<HInstruction*> worklist(allocator.Adapter(kArenaAllocMisc));
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000254 ArenaBitVector processed_instructions(&allocator, number_of_instructions, /* expandable */ false);
Vladimir Markoca6fff82017-10-03 14:49:14 +0100255 processed_instructions.ClearAllBits();
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000256 ArenaBitVector post_dominated(&allocator, graph_->GetBlocks().size(), /* expandable */ false);
Vladimir Markoca6fff82017-10-03 14:49:14 +0100257 post_dominated.ClearAllBits();
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000258 ArenaBitVector instructions_that_can_move(
259 &allocator, number_of_instructions, /* expandable */ false);
Vladimir Markoca6fff82017-10-03 14:49:14 +0100260 instructions_that_can_move.ClearAllBits();
261 ScopedArenaVector<HInstruction*> move_in_order(allocator.Adapter(kArenaAllocMisc));
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000262
263 // Step (1): Visit post order to get a subset of blocks post dominated by `end_block`.
264 // TODO(ngeoffray): Getting the full set of post-dominated shoud be done by
265 // computint the post dominator tree, but that could be too time consuming. Also,
266 // we should start the analysis from blocks dominated by an uncommon branch, but we
267 // don't profile branches yet.
268 bool found_block = false;
269 for (HBasicBlock* block : graph_->GetPostOrder()) {
270 if (block == end_block) {
271 found_block = true;
272 post_dominated.SetBit(block->GetBlockId());
273 } else if (found_block) {
274 bool is_post_dominated = true;
275 if (block->GetSuccessors().empty()) {
276 // We currently bail for loops.
277 is_post_dominated = false;
278 } else {
279 for (HBasicBlock* successor : block->GetSuccessors()) {
280 if (!post_dominated.IsBitSet(successor->GetBlockId())) {
281 is_post_dominated = false;
282 break;
283 }
284 }
285 }
286 if (is_post_dominated) {
287 post_dominated.SetBit(block->GetBlockId());
288 }
289 }
290 }
291
292 // Now that we have found a subset of post-dominated blocks, add to the worklist all inputs
293 // of instructions in these blocks that are not themselves in these blocks.
294 // Also find the common dominator of the found post dominated blocks, to help filtering
295 // out un-movable uses in step (2).
296 CommonDominator finder(end_block);
297 for (size_t i = 0, e = graph_->GetBlocks().size(); i < e; ++i) {
298 if (post_dominated.IsBitSet(i)) {
299 finder.Update(graph_->GetBlocks()[i]);
300 AddInputs(graph_->GetBlocks()[i], processed_instructions, post_dominated, &worklist);
301 }
302 }
303 HBasicBlock* common_dominator = finder.Get();
304
305 // Step (2): iterate over the worklist to find sinking candidates.
306 while (!worklist.empty()) {
307 HInstruction* instruction = worklist.back();
308 if (processed_instructions.IsBitSet(instruction->GetId())) {
309 // The instruction has already been processed, continue. This happens
310 // when the instruction is the input/user of multiple instructions.
311 worklist.pop_back();
312 continue;
313 }
314 bool all_users_in_post_dominated_blocks = true;
315 bool can_move = true;
316 // Check users of the instruction.
317 for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
318 HInstruction* user = use.GetUser();
319 if (!post_dominated.IsBitSet(user->GetBlock()->GetBlockId()) &&
320 !instructions_that_can_move.IsBitSet(user->GetId())) {
321 all_users_in_post_dominated_blocks = false;
322 // If we've already processed this user, or the user cannot be moved, or
323 // is not dominating the post dominated blocks, bail.
324 // TODO(ngeoffray): The domination check is an approximation. We should
325 // instead check if the dominated blocks post dominate the user's block,
326 // but we do not have post dominance information here.
327 if (processed_instructions.IsBitSet(user->GetId()) ||
328 !IsInterestingInstruction(user) ||
329 !user->GetBlock()->Dominates(common_dominator)) {
330 can_move = false;
331 break;
332 }
333 }
334 }
335
336 // Check environment users of the instruction. Some of these users require
337 // the instruction not to move.
338 if (all_users_in_post_dominated_blocks) {
339 for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) {
340 HEnvironment* environment = use.GetUser();
341 HInstruction* user = environment->GetHolder();
342 if (!post_dominated.IsBitSet(user->GetBlock()->GetBlockId())) {
343 if (graph_->IsDebuggable() ||
344 user->IsDeoptimize() ||
345 user->CanThrowIntoCatchBlock() ||
346 (user->IsSuspendCheck() && graph_->IsCompilingOsr())) {
347 can_move = false;
348 break;
349 }
350 }
351 }
352 }
353 if (!can_move) {
354 // Instruction cannot be moved, mark it as processed and remove it from the work
355 // list.
356 processed_instructions.SetBit(instruction->GetId());
357 worklist.pop_back();
358 } else if (all_users_in_post_dominated_blocks) {
359 // Instruction is a candidate for being sunk. Mark it as such, remove it from the
360 // work list, and add its inputs to the work list.
361 instructions_that_can_move.SetBit(instruction->GetId());
362 move_in_order.push_back(instruction);
363 processed_instructions.SetBit(instruction->GetId());
364 worklist.pop_back();
365 AddInputs(instruction, processed_instructions, post_dominated, &worklist);
366 // Drop the environment use not in the list of post-dominated block. This is
367 // to help step (3) of this optimization, when we start moving instructions
368 // closer to their use.
369 for (const HUseListNode<HEnvironment*>& use : instruction->GetEnvUses()) {
370 HEnvironment* environment = use.GetUser();
371 HInstruction* user = environment->GetHolder();
372 if (!post_dominated.IsBitSet(user->GetBlock()->GetBlockId())) {
373 environment->RemoveAsUserOfInput(use.GetIndex());
374 environment->SetRawEnvAt(use.GetIndex(), nullptr);
375 }
376 }
377 } else {
378 // The information we have on the users was not enough to decide whether the
379 // instruction could be moved.
380 // Add the users to the work list, and keep the instruction in the work list
381 // to process it again once all users have been processed.
382 for (const HUseListNode<HInstruction*>& use : instruction->GetUses()) {
383 AddInstruction(use.GetUser(), processed_instructions, post_dominated, &worklist);
384 }
385 }
386 }
387
388 // Make sure we process instructions in dominated order. This is required for heap
389 // stores.
390 std::sort(move_in_order.begin(), move_in_order.end(), [](HInstruction* a, HInstruction* b) {
391 return b->StrictlyDominates(a);
392 });
393
394 // Step (3): Try to move sinking candidates.
395 for (HInstruction* instruction : move_in_order) {
396 HInstruction* position = nullptr;
Igor Murashkin79d8fa72017-04-18 09:37:23 -0700397 if (instruction->IsArraySet()
398 || instruction->IsInstanceFieldSet()
399 || instruction->IsConstructorFence()) {
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000400 if (!instructions_that_can_move.IsBitSet(instruction->InputAt(0)->GetId())) {
401 // A store can trivially move, but it can safely do so only if the heap
402 // location it stores to can also move.
403 // TODO(ngeoffray): Handle allocation/store cycles by pruning these instructions
404 // from the set and all their inputs.
405 continue;
406 }
407 // Find the position of the instruction we're storing into, filtering out this
408 // store and all other stores to that instruction.
409 position = FindIdealPosition(instruction->InputAt(0), post_dominated, /* filter */ true);
410
411 // The position needs to be dominated by the store, in order for the store to move there.
412 if (position == nullptr || !instruction->GetBlock()->Dominates(position->GetBlock())) {
413 continue;
414 }
415 } else {
416 // Find the ideal position within the post dominated blocks.
417 position = FindIdealPosition(instruction, post_dominated);
418 if (position == nullptr) {
419 continue;
420 }
421 }
422 // Bail if we could not find a position in the post dominated blocks (for example,
423 // if there are multiple users whose common dominator is not in the list of
424 // post dominated blocks).
425 if (!post_dominated.IsBitSet(position->GetBlock()->GetBlockId())) {
426 continue;
427 }
Igor Murashkin1e065a52017-08-09 13:20:34 -0700428 MaybeRecordStat(stats_, MethodCompilationStat::kInstructionSunk);
Nicolas Geoffrayb813ca12017-02-16 22:08:29 +0000429 instruction->MoveBefore(position, /* ensure_safety */ false);
430 }
431}
432
433} // namespace art