1 // Copyright (c) 2016, 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 package com.android.tools.r8.ir.code; 5 6 import com.android.tools.r8.errors.CompilationError; 7 import com.android.tools.r8.graph.DebugLocalInfo; 8 import com.android.tools.r8.ir.code.BasicBlock.EdgeType; 9 import com.android.tools.r8.ir.conversion.IRBuilder; 10 import com.android.tools.r8.utils.CfgPrinter; 11 import com.android.tools.r8.utils.ListUtils; 12 import com.android.tools.r8.utils.StringUtils; 13 import java.util.ArrayList; 14 import java.util.HashSet; 15 import java.util.List; 16 import java.util.Map; 17 import java.util.Set; 18 19 public class Phi extends Value { 20 21 private final BasicBlock block; 22 private final List<Value> operands = new ArrayList<>(); 23 24 // Trivial phis are eliminated during IR construction. When a trivial phi is eliminated 25 // we need to update all references to it. A phi can be referenced from phis, instructions 26 // and current definition mappings. This list contains the current definitions mappings that 27 // contain this phi. 28 private List<Map<Integer, Value>> definitionUsers = new ArrayList<>(); 29 30 // The computed out type is not always the same as 'this.type' because of the type 31 // confusion around null and constant zero. The null object can be used in a single 32 // context (if tests) and the single 0 can be used as null. A phi can therefore 33 // have either of the creation types 'single' and 'object' depending on the use that 34 // triggered the creation of the phi. We therefore have to delay the output type 35 // computation of the phi until all operands are known. 36 private MoveType outType = null; 37 Phi(int number, BasicBlock block, MoveType type, DebugLocalInfo local)38 public Phi(int number, BasicBlock block, MoveType type, DebugLocalInfo local) { 39 super(number, type, local == null ? null : new DebugInfo(local, null)); 40 this.block = block; 41 block.addPhi(this); 42 } 43 44 @Override isPhi()45 public boolean isPhi() { 46 return true; 47 } 48 49 @Override asPhi()50 public Phi asPhi() { 51 return this; 52 } 53 getBlock()54 public BasicBlock getBlock() { 55 return block; 56 } 57 addOperands(IRBuilder builder, int register)58 public void addOperands(IRBuilder builder, int register) { 59 // Phi operands are only filled in once to complete the phi. Some phis are incomplete for a 60 // period of time to break cycles. When the cycle has been resolved they are completed 61 // exactly once by adding the operands. 62 assert operands.isEmpty(); 63 boolean canBeNull = false; 64 if (block.getPredecessors().size() == 0) { 65 throwUndefinedValueError(); 66 } 67 for (BasicBlock pred : block.getPredecessors()) { 68 EdgeType edgeType = pred.getEdgeType(block); 69 // Since this read has been delayed we must provide the local info for the value. 70 Value operand = builder.readRegister(register, pred, edgeType, type, getLocalInfo()); 71 canBeNull |= operand.canBeNull(); 72 appendOperand(operand); 73 } 74 if (!canBeNull) { 75 markNeverNull(); 76 } 77 removeTrivialPhi(); 78 } 79 addOperands(List<Value> operands)80 public void addOperands(List<Value> operands) { 81 // Phi operands are only filled in once to complete the phi. Some phis are incomplete for a 82 // period of time to break cycles. When the cycle has been resolved they are completed 83 // exactly once by adding the operands. 84 assert this.operands.isEmpty(); 85 boolean canBeNull = false; 86 if (operands.size() == 0) { 87 throwUndefinedValueError(); 88 } 89 for (Value operand : operands) { 90 canBeNull |= operand.canBeNull(); 91 appendOperand(operand); 92 } 93 if (!canBeNull) { 94 markNeverNull(); 95 } 96 removeTrivialPhi(); 97 } 98 throwUndefinedValueError()99 private void throwUndefinedValueError() { 100 throw new CompilationError( 101 "Undefined value encountered during compilation. " 102 + "This is typically caused by invalid dex input that uses a register " 103 + "that is not define on all control-flow paths leading to the use."); 104 } 105 appendOperand(Value operand)106 private void appendOperand(Value operand) { 107 operands.add(operand); 108 operand.addPhiUser(this); 109 } 110 getOperand(int predIndex)111 public Value getOperand(int predIndex) { 112 return operands.get(predIndex); 113 } 114 getOperands()115 public List<Value> getOperands() { 116 return operands; 117 } 118 removeOperand(int index)119 public void removeOperand(int index) { 120 operands.get(index).removePhiUser(this); 121 operands.remove(index); 122 } 123 removeOperandsByIndex(List<Integer> operandsToRemove)124 public void removeOperandsByIndex(List<Integer> operandsToRemove) { 125 if (operandsToRemove.isEmpty()) { 126 return; 127 } 128 List<Value> copy = new ArrayList<>(operands); 129 operands.clear(); 130 int current = 0; 131 for (int i : operandsToRemove) { 132 operands.addAll(copy.subList(current, i)); 133 copy.get(i).removePhiUser(this); 134 current = i + 1; 135 } 136 operands.addAll(copy.subList(current, copy.size())); 137 } 138 replace(int predIndex, Value newValue)139 public void replace(int predIndex, Value newValue) { 140 Value current = operands.get(predIndex); 141 operands.set(predIndex, newValue); 142 newValue.addPhiUser(this); 143 current.removePhiUser(this); 144 } 145 146 // Removing the phi user from the current value leads to concurrent modification errors 147 // during trivial phi elimination. It is safe to not remove the phi user from current 148 // since current will be unreachable after trivial phi elimination. 149 // TODO(ager): can we unify the these replace methods and avoid the concurrent modification 150 // issue? replaceTrivialPhi(Value current, Value newValue)151 private void replaceTrivialPhi(Value current, Value newValue) { 152 for (int i = 0; i < operands.size(); i++) { 153 if (operands.get(i) == current) { 154 operands.set(i, newValue); 155 newValue.addPhiUser(this); 156 } 157 } 158 } 159 isTrivialPhi()160 public boolean isTrivialPhi() { 161 Value same = null; 162 for (Value op : operands) { 163 if (op == same || op == this) { 164 // Have only seen one value other than this. 165 continue; 166 } 167 if (same != null) { 168 // Merged at least two values and is therefore not trivial. 169 return false; 170 } 171 same = op; 172 } 173 return true; 174 } 175 removeTrivialPhi()176 public void removeTrivialPhi() { 177 Value same = null; 178 for (Value op : operands) { 179 if (op == same || op == this) { 180 // Have only seen one value other than this. 181 continue; 182 } 183 if (same != null) { 184 // Merged at least two values and is therefore not trivial. 185 assert !isTrivialPhi(); 186 return; 187 } 188 same = op; 189 } 190 assert isTrivialPhi(); 191 // Removing this phi, so get rid of it as a phi user from all of the operands to avoid 192 // recursively getting back here with the same phi. If the phi has itself as an operand 193 // that also removes the self-reference. 194 for (Value op : operands) { 195 op.removePhiUser(this); 196 } 197 // Replace this phi with the unique value in all users. 198 for (Instruction user : uniqueUsers()) { 199 user.replaceValue(this, same); 200 } 201 for (Phi user : uniquePhiUsers()) { 202 user.replaceTrivialPhi(this, same); 203 } 204 if (debugUsers() != null) { 205 for (Instruction user : debugUsers()) { 206 user.replaceDebugPhi(this, same); 207 } 208 } 209 // If IR construction is taking place, update the definition users. 210 if (definitionUsers != null) { 211 for (Map<Integer, Value> user : definitionUsers) { 212 for (Map.Entry<Integer, Value> entry : user.entrySet()) { 213 if (entry.getValue() == this) { 214 entry.setValue(same); 215 if (same.isPhi()) { 216 same.asPhi().addDefinitionsUser(user); 217 } 218 } 219 } 220 } 221 } 222 // Try to simplify phi users that might now have become trivial. 223 for (Phi user : uniquePhiUsers()) { 224 user.removeTrivialPhi(); 225 } 226 // Get rid of the phi itself. 227 block.removePhi(this); 228 } 229 printPhi()230 public String printPhi() { 231 StringBuilder builder = new StringBuilder(); 232 builder.append("v"); 233 builder.append(number); 234 builder.append(" <- phi"); 235 StringUtils.append(builder, ListUtils.map(operands, (Value operand) -> "v" + operand.number)); 236 return builder.toString(); 237 } 238 print(CfgPrinter printer)239 public void print(CfgPrinter printer) { 240 int uses = numberOfPhiUsers() + numberOfUsers(); 241 printer 242 .print("0 ") // bci 243 .append(uses) // use 244 .append(" v").append(number) // tid 245 .append(" Phi"); 246 for (Value operand : operands) { 247 printer.append(" v").append(operand.number); 248 } 249 } 250 addDefinitionsUser(Map<Integer, Value> currentDefinitions)251 public void addDefinitionsUser(Map<Integer, Value> currentDefinitions) { 252 definitionUsers.add(currentDefinitions); 253 } 254 removeDefinitionsUser(Map<Integer, Value> currentDefinitions)255 public void removeDefinitionsUser(Map<Integer, Value> currentDefinitions) { 256 definitionUsers.remove(currentDefinitions); 257 } 258 clearDefinitionsUsers()259 public void clearDefinitionsUsers() { 260 definitionUsers = null; 261 } 262 isSingleConstZero(Value value)263 private boolean isSingleConstZero(Value value) { 264 return value.definition != null && value.definition.isConstNumber() && 265 value.definition.asConstNumber().isZero() && 266 value.outType() == MoveType.SINGLE; 267 } 268 computeOutType(Set<Phi> active)269 private MoveType computeOutType(Set<Phi> active) { 270 if (outType != null) { 271 return outType; 272 } 273 active.add(this); 274 // Go through non-phi operands first to determine if we have an operand that dictates the type. 275 for (Value operand : operands) { 276 // Since a constant zero can be either an integer or an Object (null) we skip them 277 // when computing types and rely on other operands to specify the actual type. 278 if (!operand.isPhi() && !isSingleConstZero(operand)) { 279 return operand.outType(); 280 } 281 } 282 // We did not find a non-phi operand that dictates the type. Recurse on phi arguments. 283 for (Value operand : operands) { 284 if (operand.isPhi() && !active.contains(operand)) { 285 MoveType phiType = operand.asPhi().computeOutType(active); 286 // TODO(zerny): If we had a CONST_ZERO type element, we could often avoid going through 287 // all phis. We would only have to recurse until we got a non CONST_ZERO out type. 288 if (phiType != MoveType.SINGLE) { 289 return phiType; 290 } 291 } 292 } 293 // All operands were the constant zero or phis with out type SINGLE and the out type is either 294 // object or single depending on the use. Since all inputs have out type SINGLE it is safe to 295 // return MoveType.SINGLE here. 296 assert type == MoveType.SINGLE || type == MoveType.OBJECT; 297 return MoveType.SINGLE; 298 } 299 300 @Override outType()301 public MoveType outType() { 302 if (outType != null) { 303 return outType; 304 } 305 return computeOutType(new HashSet<>()); 306 } 307 308 @Override isConstant()309 public boolean isConstant() { 310 return false; 311 } 312 needsRegister()313 public boolean needsRegister() { 314 return true; 315 } 316 } 317