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.conversion; 5 6 import static com.android.tools.r8.ir.conversion.JarSourceCode.getArrayElementType; 7 8 import com.android.tools.r8.graph.DebugLocalInfo; 9 import com.google.common.collect.HashMultimap; 10 import com.google.common.collect.ImmutableList; 11 import com.google.common.collect.Multimap; 12 import java.util.ArrayDeque; 13 import java.util.ArrayList; 14 import java.util.Arrays; 15 import java.util.Collection; 16 import java.util.Comparator; 17 import java.util.Deque; 18 import java.util.HashMap; 19 import java.util.List; 20 import java.util.Map; 21 import org.objectweb.asm.Type; 22 import org.objectweb.asm.tree.LabelNode; 23 import org.objectweb.asm.tree.LocalVariableNode; 24 25 /** 26 * Abstraction of the java bytecode state at a given control-flow point. 27 * 28 * The abstract state is defined by the abstract contents of locals and contents of the stack. 29 */ 30 public class JarState { 31 32 // Type representatives of "any object/array" using invalid names (only valid for asserting). 33 public static final Type REFERENCE_TYPE = Type.getObjectType("<any reference>"); 34 public static final Type OBJECT_TYPE = Type.getObjectType("<any object>"); 35 public static final Type ARRAY_TYPE = Type.getObjectType("[<any array>"); 36 37 // Type representative for the null value (non-existent but works for tracking the types here). 38 public static final Type NULL_TYPE = Type.getObjectType("<null>"); 39 40 // Type representative for an address type (used by JSR/RET). 41 public static final Type ADDR_TYPE = Type.getObjectType("<address>"); 42 43 // Typed mapping from a local slot or stack slot to a virtual register. 44 public static class Slot { 45 public final int register; 46 public final Type type; 47 48 @Override toString()49 public String toString() { 50 return "r" + register + ":" + type; 51 } 52 Slot(int register, Type type)53 public Slot(int register, Type type) { 54 assert type != REFERENCE_TYPE; 55 assert type != OBJECT_TYPE; 56 assert type != ARRAY_TYPE; 57 this.register = register; 58 this.type = type; 59 } 60 isCompatibleWith(Type other)61 public boolean isCompatibleWith(Type other) { 62 return isCompatible(type, other); 63 } 64 isCategory1()65 public boolean isCategory1() { 66 return isCategory1(type); 67 } 68 isCategory1(Type type)69 public static boolean isCategory1(Type type) { 70 return type != Type.LONG_TYPE && type != Type.DOUBLE_TYPE; 71 } 72 isCompatible(Type type, Type other)73 public static boolean isCompatible(Type type, Type other) { 74 assert type != REFERENCE_TYPE; 75 assert type != OBJECT_TYPE; 76 assert type != ARRAY_TYPE; 77 int sort = type.getSort(); 78 int otherSort = other.getSort(); 79 if (isReferenceCompatible(type, other)) { 80 return true; 81 } 82 // Integers are assumed compatible with any other 32-bit integral. 83 if (isIntCompatible(sort)) { 84 return isIntCompatible(otherSort); 85 } 86 if (isIntCompatible(otherSort)) { 87 return isIntCompatible(sort); 88 } 89 // In all other cases we require the two types to represent the same concrete type. 90 return type.equals(other); 91 } 92 isIntCompatible(int sort)93 private static boolean isIntCompatible(int sort) { 94 return Type.BOOLEAN <= sort && sort <= Type.INT; 95 } 96 isReferenceCompatible(Type type, Type other)97 private static boolean isReferenceCompatible(Type type, Type other) { 98 int sort = type.getSort(); 99 int otherSort = other.getSort(); 100 101 // Catch all matching. 102 if (other == REFERENCE_TYPE) { 103 return sort == Type.OBJECT || sort == Type.ARRAY; 104 } 105 if (other == OBJECT_TYPE) { 106 return sort == Type.OBJECT; 107 } 108 if (other == ARRAY_TYPE) { 109 return type == NULL_TYPE || sort == Type.ARRAY; 110 } 111 112 return (sort == Type.OBJECT && otherSort == Type.ARRAY) 113 || (sort == Type.ARRAY && otherSort == Type.OBJECT) 114 || (sort == Type.OBJECT && otherSort == Type.OBJECT) 115 || (sort == Type.ARRAY && otherSort == Type.ARRAY 116 && isReferenceCompatible(getArrayElementType(type), getArrayElementType(other))); 117 } 118 } 119 120 public static class Local { 121 final Slot slot; 122 final DebugLocalInfo info; 123 Local(Slot slot, DebugLocalInfo info)124 public Local(Slot slot, DebugLocalInfo info) { 125 this.slot = slot; 126 this.info = info; 127 } 128 } 129 130 // Immutable recording of the state (locals and stack should not be mutated). 131 private static class Snapshot { 132 public final Local[] locals; 133 public final ImmutableList<Slot> stack; 134 Snapshot(Local[] locals, ImmutableList<Slot> stack)135 public Snapshot(Local[] locals, ImmutableList<Slot> stack) { 136 this.locals = locals; 137 this.stack = stack; 138 } 139 140 @Override toString()141 public String toString() { 142 return "locals: " + localsToString(Arrays.asList(locals)) 143 + ", stack: " + stackToString(stack); 144 } 145 } 146 147 private final int startOfStack; 148 private int topOfStack; 149 150 // Locals are split into three parts based on types: 151 // 1) reference-type locals have registers in range: [0; localsSize[ 152 // 2) single-width locals have registers in range: [localsSize; 2*localsSize[ 153 // 3) wide-width locals have registers in range: [2*localsSize; 3*localsSize[ 154 // This ensures that we can insert debugging-ranges into the SSA graph (via DebugLocal{Start,End}) 155 // without conflating locals that are shared among different types. This issue arises because a 156 // debugging range can be larger than the definite-assignment scope of a local (eg, a local 157 // introduced in an unscoped switch case). To ensure that the SSA graph is valid we must introduce 158 // the local before inserting any DebugLocalReads (we do so in the method prelude, but that can 159 // potentially lead to phi functions merging locals of different move-types. Thus we allocate 160 // registers from the three distinct spaces. 161 private final int localsSize; 162 private final Local[] locals; 163 164 // Mapping from local-variable nodes to their canonical local info. 165 private final Map<LocalVariableNode, DebugLocalInfo> localVariables; 166 167 // Scope-points of all local variables for inserting debug scoping instructions. 168 private final Multimap<LabelNode, LocalVariableNode> localVariableStartPoints; 169 private final Multimap<LabelNode, LocalVariableNode> localVariableEndPoints; 170 171 172 private final Deque<Slot> stack = new ArrayDeque<>(); 173 174 private final Map<Integer, Snapshot> targetStates = new HashMap<>(); 175 176 JarState(int maxLocals, Map<LocalVariableNode, DebugLocalInfo> localVariables)177 public JarState(int maxLocals, Map<LocalVariableNode, DebugLocalInfo> localVariables) { 178 int localsRegistersSize = maxLocals * 3; 179 localsSize = maxLocals; 180 locals = new Local[localsRegistersSize]; 181 startOfStack = localsRegistersSize; 182 topOfStack = startOfStack; 183 this.localVariables = localVariables; 184 localVariableStartPoints = HashMultimap.create(); 185 localVariableEndPoints = HashMultimap.create(); 186 populateLocalTables(); 187 } 188 populateLocalTables()189 private void populateLocalTables() { 190 for (LocalVariableNode node : localVariables.keySet()) { 191 if (node.start != node.end) { 192 localVariableStartPoints.put(node.start, node); 193 localVariableEndPoints.put(node.end, node); 194 } 195 } 196 } 197 198 // Local variable procedures. 199 openLocals(LabelNode label)200 public List<Local> openLocals(LabelNode label) { 201 Collection<LocalVariableNode> nodes = localVariableStartPoints.get(label); 202 ArrayList<Local> locals = new ArrayList<>(nodes.size()); 203 for (LocalVariableNode node : nodes) { 204 locals.add(setLocal(node.index, Type.getType(node.desc), localVariables.get(node))); 205 } 206 // Sort to ensure deterministic instruction ordering (correctness is unaffected). 207 locals.sort(Comparator.comparingInt(local -> local.slot.register)); 208 return locals; 209 } 210 getLocalsToClose(LabelNode label)211 public List<Local> getLocalsToClose(LabelNode label) { 212 Collection<LocalVariableNode> nodes = localVariableEndPoints.get(label); 213 ArrayList<Local> locals = new ArrayList<>(nodes.size()); 214 for (LocalVariableNode node : nodes) { 215 Type type = Type.getType(node.desc); 216 int register = getLocalRegister(node.index, type); 217 Local local = getLocalForRegister(register); 218 assert local != null; 219 locals.add(local); 220 } 221 // Sort to ensure deterministic instruction ordering (correctness is unaffected). 222 locals.sort(Comparator.comparingInt(local -> local.slot.register)); 223 return locals; 224 } 225 closeLocals(List<Local> localsToClose)226 public void closeLocals(List<Local> localsToClose) { 227 for (Local local : localsToClose) { 228 assert local != null; 229 assert local == getLocalForRegister(local.slot.register); 230 setLocalForRegister(local.slot.register, local.slot.type, null); 231 } 232 } 233 getLocals()234 public ImmutableList<Local> getLocals() { 235 ImmutableList.Builder<Local> nonNullLocals = ImmutableList.builder(); 236 for (Local local : locals) { 237 if (local != null) { 238 nonNullLocals.add(local); 239 } 240 } 241 return nonNullLocals.build(); 242 } 243 getLocalRegister(int index, Type type)244 int getLocalRegister(int index, Type type) { 245 assert index < localsSize; 246 if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { 247 return index; 248 } 249 return Slot.isCategory1(type) ? index + localsSize : index + 2 * localsSize; 250 } 251 252 public DebugLocalInfo getLocalInfoForRegister(int register) { 253 if (register >= locals.length) { 254 return null; 255 } 256 Local local = getLocalForRegister(register); 257 return local == null ? null : local.info; 258 } 259 getLocalForRegister(int register)260 private Local getLocalForRegister(int register) { 261 return locals[register]; 262 } 263 getLocal(int index, Type type)264 private Local getLocal(int index, Type type) { 265 return getLocalForRegister(getLocalRegister(index, type)); 266 } 267 setLocal(int index, Type type, DebugLocalInfo info)268 private Local setLocal(int index, Type type, DebugLocalInfo info) { 269 return setLocalForRegister(getLocalRegister(index, type), type, info); 270 } 271 setLocalForRegister(int register, Type type, DebugLocalInfo info)272 private Local setLocalForRegister(int register, Type type, DebugLocalInfo info) { 273 Slot slot = new Slot(register, type); 274 Local local = new Local(slot, info); 275 locals[register] = local; 276 return local; 277 } 278 writeLocal(int index, Type type)279 public int writeLocal(int index, Type type) { 280 assert type != null; 281 Local local = getLocal(index, type); 282 assert local == null || local.info == null || local.slot.isCompatibleWith(type); 283 // We cannot assume consistency for writes because we do not have complete information about the 284 // scopes of locals. We assume the program to be verified and overwrite if the types mismatch. 285 if (local == null || (local.info == null && !local.slot.type.equals(type))) { 286 local = setLocal(index, type, null); 287 } 288 return local.slot.register; 289 } 290 readLocal(int index, Type type)291 public Slot readLocal(int index, Type type) { 292 Local local = getLocal(index, type); 293 assert local != null; 294 assert local.slot.isCompatibleWith(type); 295 return local.slot; 296 } 297 298 // Stack procedures. 299 push(Type type)300 public int push(Type type) { 301 assert type != null; 302 int top = topOfStack; 303 // For simplicity, every stack slot (and local variable) is wide (uses two registers). 304 topOfStack += 2; 305 Slot slot = new Slot(top, type); 306 stack.push(slot); 307 return top; 308 } 309 peek()310 public Slot peek() { 311 return stack.peek(); 312 } 313 peek(Type type)314 public Slot peek(Type type) { 315 Slot slot = stack.peek(); 316 assert slot.isCompatibleWith(type); 317 return slot; 318 } 319 pop()320 public Slot pop() { 321 assert topOfStack > startOfStack; 322 // For simplicity, every stack slot (and local variable) is wide (uses two registers). 323 topOfStack -= 2; 324 Slot slot = stack.pop(); 325 assert slot.type != null; 326 assert slot.register == topOfStack; 327 return slot; 328 } 329 pop(Type type)330 public Slot pop(Type type) { 331 Slot slot = pop(); 332 assert slot.isCompatibleWith(type); 333 return slot; 334 } 335 popReverse(int count)336 public Slot[] popReverse(int count) { 337 Slot[] slots = new Slot[count]; 338 for (int i = count - 1; i >= 0; i--) { 339 slots[i] = pop(); 340 } 341 return slots; 342 } 343 popReverse(int count, Type type)344 public Slot[] popReverse(int count, Type type) { 345 Slot[] slots = popReverse(count); 346 assert verifySlots(slots, type); 347 return slots; 348 } 349 350 // State procedures. 351 hasState(int offset)352 public boolean hasState(int offset) { 353 return targetStates.get(offset) != null; 354 } 355 restoreState(int offset)356 public void restoreState(int offset) { 357 Snapshot snapshot = targetStates.get(offset); 358 assert snapshot != null; 359 assert locals.length == snapshot.locals.length; 360 for (int i = 0; i < locals.length; i++) { 361 locals[i] = snapshot.locals[i]; 362 } 363 stack.clear(); 364 stack.addAll(snapshot.stack); 365 topOfStack = startOfStack + 2 * stack.size(); 366 } 367 recordStateForTarget(int target, JarSourceCode source)368 public boolean recordStateForTarget(int target, JarSourceCode source) { 369 return recordStateForTarget(target, locals.clone(), ImmutableList.copyOf(stack), source); 370 } 371 recordStateForExceptionalTarget(int target, JarSourceCode source)372 public boolean recordStateForExceptionalTarget(int target, JarSourceCode source) { 373 return recordStateForTarget(target, locals.clone(), ImmutableList.of(), source); 374 } 375 recordStateForTarget(int target, Local[] locals, ImmutableList<Slot> stack, JarSourceCode source)376 private boolean recordStateForTarget(int target, Local[] locals, ImmutableList<Slot> stack, 377 JarSourceCode source) { 378 if (!localVariables.isEmpty()) { 379 for (int i = 0; i < locals.length; i++) { 380 if (locals[i] != null) { 381 locals[i] = new Local(locals[i].slot, null); 382 } 383 } 384 // TODO(zerny): Precompute and sort the local ranges. 385 for (LocalVariableNode node : localVariables.keySet()) { 386 int startOffset = source.getOffset(node.start); 387 int endOffset = source.getOffset(node.end); 388 if (startOffset <= target && target < endOffset) { 389 int register = getLocalRegister(node.index, Type.getType(node.desc)); 390 Local local = locals[register]; 391 locals[register] = new Local(local.slot, localVariables.get(node)); 392 } 393 } 394 } 395 Snapshot snapshot = targetStates.get(target); 396 if (snapshot != null) { 397 Local[] newLocals = mergeLocals(snapshot.locals, locals); 398 ImmutableList<Slot> newStack = mergeStacks(snapshot.stack, stack); 399 if (newLocals != snapshot.locals || newStack != snapshot.stack) { 400 targetStates.put(target, new Snapshot(newLocals, newStack)); 401 return true; 402 } 403 // The snapshot is up to date - no new type information recoded. 404 return false; 405 } 406 targetStates.put(target, new Snapshot(locals, stack)); 407 return true; 408 } 409 mergeStacks( ImmutableList<Slot> currentStack, ImmutableList<Slot> newStack)410 private ImmutableList<Slot> mergeStacks( 411 ImmutableList<Slot> currentStack, ImmutableList<Slot> newStack) { 412 assert currentStack.size() == newStack.size(); 413 List<Slot> mergedStack = null; 414 for (int i = 0; i < currentStack.size(); i++) { 415 if (currentStack.get(i).type == JarState.NULL_TYPE && 416 newStack.get(i).type != JarState.NULL_TYPE) { 417 if (mergedStack == null) { 418 mergedStack = new ArrayList<>(); 419 mergedStack.addAll(currentStack.subList(0, i)); 420 } 421 mergedStack.add(newStack.get(i)); 422 } else if (mergedStack != null) { 423 assert currentStack.get(i).isCompatibleWith(newStack.get(i).type); 424 mergedStack.add(currentStack.get(i)); 425 } 426 } 427 return mergedStack != null ? ImmutableList.copyOf(mergedStack) : currentStack; 428 } 429 mergeLocals(Local[] currentLocals, Local[] newLocals)430 private Local[] mergeLocals(Local[] currentLocals, Local[] newLocals) { 431 assert currentLocals.length == newLocals.length; 432 Local[] mergedLocals = null; 433 for (int i = 0; i < currentLocals.length; i++) { 434 Local currentLocal = currentLocals[i]; 435 Local newLocal = newLocals[i]; 436 if (currentLocal == null || newLocal == null) { 437 continue; 438 } 439 // If this assert triggers we can get different debug information for the same local 440 // on different control-flow paths and we will have to merge them. 441 assert currentLocal.info == newLocal.info; 442 if (currentLocal.slot.type == JarState.NULL_TYPE && 443 newLocal.slot.type != JarState.NULL_TYPE) { 444 if (mergedLocals == null) { 445 mergedLocals = new Local[currentLocals.length]; 446 System.arraycopy(currentLocals, 0, mergedLocals, 0, i); 447 } 448 Slot newSlot = new Slot(newLocal.slot.register, newLocal.slot.type); 449 mergedLocals[i] = new Local(newSlot, newLocal.info); 450 } else if (mergedLocals != null) { 451 mergedLocals[i] = currentLocals[i]; 452 } 453 } 454 return mergedLocals != null ? mergedLocals : currentLocals; 455 } 456 verifyStack(List<Slot> stack, List<Slot> other)457 private static boolean verifyStack(List<Slot> stack, List<Slot> other) { 458 assert stack.size() == other.size(); 459 int i = 0; 460 for (Slot slot : stack) { 461 assert slot.isCompatibleWith(other.get(i++).type); 462 } 463 return true; 464 } 465 466 // Other helpers. 467 verifySlots(Slot[] slots, Type type)468 private static boolean verifySlots(Slot[] slots, Type type) { 469 for (Slot slot : slots) { 470 assert slot.isCompatibleWith(type); 471 } 472 return true; 473 } 474 475 // Printing helpers. 476 toString()477 public String toString() { 478 return "locals: " + localsToString(Arrays.asList(locals)) + ", stack: " + stackToString(stack); 479 } 480 stackToString(Collection<Slot> stack)481 public static String stackToString(Collection<Slot> stack) { 482 List<String> strings = new ArrayList<>(stack.size()); 483 for (Slot slot : stack) { 484 strings.add(slot.type.toString()); 485 } 486 StringBuilder builder = new StringBuilder("{ "); 487 for (int i = strings.size() - 1; i >= 0; i--) { 488 builder.append(strings.get(i)); 489 if (i > 0) { 490 builder.append(", "); 491 } 492 } 493 builder.append(" }"); 494 return builder.toString(); 495 } 496 localsToString(Collection<Local> locals)497 public static String localsToString(Collection<Local> locals) { 498 StringBuilder builder = new StringBuilder("{ "); 499 boolean first = true; 500 for (Local local : locals) { 501 if (!first) { 502 builder.append(", "); 503 } else { 504 first = false; 505 } 506 if (local == null) { 507 builder.append("_"); 508 } else if (local.info != null) { 509 builder.append(local.info); 510 } else { 511 builder.append(local.slot.type.toString()); 512 } 513 } 514 builder.append(" }"); 515 return builder.toString(); 516 } 517 } 518