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