• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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