• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2017, the R8 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 package com.android.tools.r8.ir.regalloc;
6 
7 import com.android.tools.r8.ir.code.BasicBlock;
8 import com.android.tools.r8.ir.code.IRCode;
9 import com.android.tools.r8.ir.code.Instruction;
10 import com.android.tools.r8.ir.code.InstructionListIterator;
11 import com.android.tools.r8.ir.code.MoveType;
12 import java.util.Collection;
13 import java.util.HashMap;
14 import java.util.Iterator;
15 import java.util.LinkedHashSet;
16 import java.util.Map;
17 import java.util.Set;
18 
19 /**
20  * A set of spill moves and functionality to schedule and insert them in the code.
21  */
22 class SpillMoveSet {
23   // Spill and restore moves on entry.
24   private final Map<Integer, Set<SpillMove>> instructionToInMoves = new HashMap<>();
25   // Spill and restore moves on exit.
26   private final Map<Integer, Set<SpillMove>> instructionToOutMoves = new HashMap<>();
27   // Phi moves.
28   private final Map<Integer, Set<SpillMove>> instructionToPhiMoves = new HashMap<>();
29   // The code into which to insert the moves.
30   private final IRCode code;
31   // The register allocator generating moves.
32   private LinearScanRegisterAllocator allocator;
33   // All registers below this number are arguments.
34   // TODO(ager): Get rid of this field, we should deal with arguments and other values that can
35   // be rematerialized differently.
36   private final int argumentRegisterLimit;
37   // Mapping from instruction numbers to the block that start with that instruction if any.
38   private final Map<Integer, BasicBlock> blockStartMap = new HashMap<>();
39   // The number of temporary registers used for parallel moves when scheduling the moves.
40   private int usedTempRegisters = 0;
41 
SpillMoveSet( LinearScanRegisterAllocator allocator, IRCode code, int argumentRegisterLimit)42   public SpillMoveSet(
43       LinearScanRegisterAllocator allocator, IRCode code, int argumentRegisterLimit) {
44     this.allocator = allocator;
45     this.code = code;
46     this.argumentRegisterLimit = argumentRegisterLimit;
47     for (BasicBlock block : code.blocks) {
48       blockStartMap.put(block.entry().getNumber(), block);
49     }
50   }
51 
52   /**
53    * Add a spill or restore move.
54    *
55    * <p>This is used between all interval splits. The move is only inserted if it is restoring
56    * from a spill slot at a position that is not at the start of a block. All block start
57    * moves are handled by resolution.
58    *
59    * @param i instruction number (gap number) for which to insert the move
60    * @param to interval representing the destination for the move
61    * @param from interval representating the source for the move
62    */
addSpillOrRestoreMove(int i, LiveIntervals to, LiveIntervals from)63   public void addSpillOrRestoreMove(int i, LiveIntervals to, LiveIntervals from) {
64     assert i % 2 == 1;
65     assert to.getSplitParent() == from.getSplitParent();
66     BasicBlock atEntryToBlock = blockStartMap.get(i + 1);
67     if (atEntryToBlock == null) {
68       addInMove(i, to, from);
69     }
70   }
71 
72   /**
73    * Add a resolution move. This deals with moves in order to transfer an SSA value to another
74    * register across basic block boundaries.
75    *
76    * @param i instruction number (gap number) for which to insert the move
77    * @param to interval representing the destination for the move
78    * @param from interval representing the source for the move
79    */
addInResolutionMove(int i, LiveIntervals to, LiveIntervals from)80   public void addInResolutionMove(int i, LiveIntervals to, LiveIntervals from) {
81     assert to.getSplitParent() == from.getSplitParent();
82     addInMove(i, to, from);
83   }
84 
addOutResolutionMove(int i, LiveIntervals to, LiveIntervals from)85   public void addOutResolutionMove(int i, LiveIntervals to, LiveIntervals from) {
86     assert to.getSplitParent() == from.getSplitParent();
87     addOutMove(i, to, from);
88   }
89 
90   /**
91    * Add a phi move to transfer an incoming SSA value to the SSA value in the destination block.
92    *
93    * @param i instruction number (gap number) for which to insert the move
94    * @param to interval representing the destination for the move
95    * @param from interval representing the source for the move
96    */
addPhiMove(int i, LiveIntervals to, LiveIntervals from)97   public void addPhiMove(int i, LiveIntervals to, LiveIntervals from) {
98     assert i % 2 == 1;
99     SpillMove move = new SpillMove(moveTypeForIntervals(to, from), to, from);
100     move.updateMaxNonSpilled();
101     instructionToPhiMoves.computeIfAbsent(i, (k) -> new LinkedHashSet<>()).add(move);
102   }
103 
addInMove(int i, LiveIntervals to, LiveIntervals from)104   private void addInMove(int i, LiveIntervals to, LiveIntervals from) {
105     assert i % 2 == 1;
106     instructionToInMoves.computeIfAbsent(i, (k) -> new LinkedHashSet<>()).add(
107         new SpillMove(moveTypeForIntervals(to, from), to, from));
108   }
109 
addOutMove(int i, LiveIntervals to, LiveIntervals from)110   private void addOutMove(int i, LiveIntervals to, LiveIntervals from) {
111     assert i % 2 == 1;
112     instructionToOutMoves.computeIfAbsent(i, (k) -> new LinkedHashSet<>()).add(
113         new SpillMove(moveTypeForIntervals(to, from), to, from));
114   }
115 
116   /**
117    * Schedule the moves added to this SpillMoveSet and insert them into the code.
118    *
119    * <p>Scheduling requires parallel move semantics for some of the moves. That can require
120    * the use of temporary registers to break cycles.
121    *
122    * @param tempRegister the first temporary register to use
123    * @return the number of temporary registers used
124    */
scheduleAndInsertMoves(int tempRegister)125   public int scheduleAndInsertMoves(int tempRegister) {
126     for (BasicBlock block : code.blocks) {
127       InstructionListIterator it = block.listIterator();
128       while (it.hasNext()) {
129         Instruction instruction = it.next();
130         int number = instruction.getNumber();
131         if (needsMovesBeforeInstruction(number)) {
132           // Move back so moves are inserted before the instruction.
133           it.previous();
134           scheduleMovesBeforeInstruction(tempRegister, number, it);
135           // Move past the instruction again.
136           it.next();
137         }
138       }
139     }
140     return usedTempRegisters;
141   }
142 
isArgumentRegister(int register)143   private boolean isArgumentRegister(int register) {
144     return register < argumentRegisterLimit;
145   }
146 
moveTypeForIntervals(LiveIntervals to, LiveIntervals from)147   private MoveType moveTypeForIntervals(LiveIntervals to, LiveIntervals from) {
148     MoveType toType = to.getType();
149     MoveType fromType = from.getType();
150     if (toType == MoveType.OBJECT || fromType == MoveType.OBJECT) {
151       assert fromType == MoveType.OBJECT || fromType == MoveType.SINGLE;
152       assert toType == MoveType.OBJECT || toType == MoveType.SINGLE;
153       return MoveType.OBJECT;
154     }
155     assert toType == fromType;
156     return toType;
157   }
158 
needsMovesBeforeInstruction(int i)159   private boolean needsMovesBeforeInstruction(int i) {
160     return instructionToOutMoves.containsKey(i - 1)
161         || instructionToInMoves.containsKey(i - 1)
162         || instructionToPhiMoves.containsKey(i - 1);
163   }
164 
getMoveWithSource(LiveIntervals src, Collection<SpillMove> moves)165   private SpillMove getMoveWithSource(LiveIntervals src, Collection<SpillMove> moves) {
166     for (SpillMove move : moves) {
167       if (move.from == src) {
168         return move;
169       }
170     }
171     return null;
172   }
173 
getMoveWritingSourceRegister(SpillMove inMove, Collection<SpillMove> moves)174   private SpillMove getMoveWritingSourceRegister(SpillMove inMove, Collection<SpillMove> moves) {
175     int srcRegister = inMove.from.getRegister();
176     int srcRegisters = inMove.type == MoveType.WIDE ? 2 : 1;
177     for (SpillMove move : moves) {
178       int dstRegister = move.to.getRegister();
179       int dstRegisters = move.type == MoveType.WIDE ? 2 : 1;
180       for (int s = 0; s < srcRegisters; s++) {
181         for (int d = 0; d < dstRegisters; d++) {
182           if ((dstRegister + d) == (srcRegister + s)) {
183             return move;
184           }
185         }
186       }
187     }
188     return null;
189   }
190 
191   // Shortcut move chains where we have a move in the in move set that moves to a
192   // location that is then moved in the out move set to its final destination.
193   //
194   // r1 <- r0 (in move set)
195   //
196   // r2 <- r1 (out move set)
197   //
198   // is replaced with
199   //
200   // r2 <- r0 (out move set)
201   //
202   // Care must be taken when there are other moves in the in move set that can interfere
203   // with the value. For example:
204   //
205   // r1 <- r0 (in move set)
206   // r0 <- r1 (in move set)
207   //
208   // r2 <- r1 (out move set)
209   //
210   // Additionally, if a phi move uses the destination of the in move it needs to stay.
211   //
212   // If such interference exists we don't rewrite the moves and parallel moves are generated
213   // to swap r1 and r0 on entry via a temporary register.
pruneParallelMoveSets( Set<SpillMove> inMoves, Set<SpillMove> outMoves, Set<SpillMove> phiMoves)214   private void pruneParallelMoveSets(
215       Set<SpillMove> inMoves, Set<SpillMove> outMoves, Set<SpillMove> phiMoves) {
216     Iterator<SpillMove> it = inMoves.iterator();
217     while (it.hasNext()) {
218       SpillMove inMove = it.next();
219       SpillMove outMove = getMoveWithSource(inMove.to, outMoves);
220       SpillMove blockingInMove = getMoveWritingSourceRegister(inMove, inMoves);
221       SpillMove blockingPhiMove = getMoveWithSource(inMove.to, phiMoves);
222       if (outMove != null && blockingInMove == null && blockingPhiMove == null) {
223         it.remove();
224         outMove.from = inMove.from;
225       }
226     }
227   }
228 
scheduleMovesBeforeInstruction( int tempRegister, int instruction, InstructionListIterator insertAt)229   private void scheduleMovesBeforeInstruction(
230       int tempRegister, int instruction, InstructionListIterator insertAt) {
231     // Spill and restore moves for the incoming edge.
232     Set<SpillMove> inMoves =
233         instructionToInMoves.computeIfAbsent(instruction - 1, (k) -> new LinkedHashSet<>());
234     removeArgumentRestores(inMoves);
235 
236     // Spill and restore moves for the outgoing edge.
237     Set<SpillMove> outMoves =
238         instructionToOutMoves.computeIfAbsent(instruction - 1, (k) -> new LinkedHashSet<>());
239     removeArgumentRestores(outMoves);
240 
241     // Get the phi moves for this instruction and schedule them with the out going spill moves.
242     Set<SpillMove> phiMoves =
243         instructionToPhiMoves.computeIfAbsent(instruction - 1, (k) -> new LinkedHashSet<>());
244 
245     // Remove/rewrite moves that we can guarantee will not be needed.
246     pruneParallelMoveSets(inMoves, outMoves, phiMoves);
247 
248     // Schedule out and phi moves together.
249     outMoves.addAll(phiMoves);
250 
251     // Perform parallel move scheduling independently for the in and out moves.
252     scheduleMoves(tempRegister, inMoves, insertAt);
253     scheduleMoves(tempRegister, outMoves, insertAt);
254   }
255 
256   // Remove restore moves that restore arguments. Since argument register reuse is
257   // disallowed at this point we know that argument registers do not change value and
258   // therefore we don't have to perform spill moves. Performing spill moves will also
259   // make art reject the code because it loses type information for the argument.
removeArgumentRestores(Set<SpillMove> moves)260   private void removeArgumentRestores(Set<SpillMove> moves) {
261     Iterator<SpillMove> moveIterator = moves.iterator();
262     while (moveIterator.hasNext()) {
263       SpillMove move = moveIterator.next();
264       if (isArgumentRegister(move.to.getRegister())) {
265         moveIterator.remove();
266       }
267     }
268   }
269 
scheduleMoves( int tempRegister, Collection<SpillMove> moves, InstructionListIterator insertAt)270   private void scheduleMoves(
271       int tempRegister, Collection<SpillMove> moves, InstructionListIterator insertAt) {
272     RegisterMoveScheduler scheduler =
273         new RegisterMoveScheduler(insertAt, tempRegister);
274     for (SpillMove move : moves) {
275       // Do not generate moves to spill a value that can be rematerialized.
276       if (move.to.isSpilledAndRematerializable(allocator)) {
277         continue;
278       }
279       // Use rematerialization when possible and otherwise generate moves.
280       if (move.from.isSpilledAndRematerializable(allocator)) {
281         scheduler.addMove(
282             new RegisterMove(move.to.getRegister(), move.type, move.from.getValue().definition));
283       } else if (move.to.getRegister() != move.from.getRegister()) {
284         scheduler.addMove(
285             new RegisterMove(move.to.getRegister(), move.from.getRegister(), move.type));
286       }
287     }
288     scheduler.schedule();
289     usedTempRegisters = Math.max(usedTempRegisters, scheduler.getUsedTempRegisters());
290   }
291 }
292