blob: 99bca1c6e08c36dad38ab93efe024c4ed7ecb1fc [file] [log] [blame]
// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.ir.conversion;
import com.android.tools.r8.errors.InvalidDebugInfoException;
import com.android.tools.r8.graph.DebugLocalInfo;
import com.android.tools.r8.graph.JarApplicationReader;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.Pair;
import com.google.common.base.Equivalence;
import com.google.common.collect.ImmutableList;
import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
import it.unimi.dsi.fastutil.ints.IntIterator;
import it.unimi.dsi.fastutil.ints.IntSet;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.LocalVariableNode;
/**
* Abstraction of the java bytecode state at a given control-flow point.
*
* The abstract state is defined by the abstract contents of locals and contents of the stack.
*/
public class JarState {
// Type representatives of "any object/array" using invalid names (only valid for asserting).
public static final Type REFERENCE_TYPE = Type.getObjectType("<any reference>");
public static final Type OBJECT_TYPE = Type.getObjectType("<any object>");
public static final Type ARRAY_TYPE = Type.getObjectType("[<any array>");
// Type representative for the null value (non-existent but works for tracking the types here).
public static final Type NULL_TYPE = Type.getObjectType("<null>");
// TODO(zerny): Define an internal Type wrapping ASM types so that we can define an actual value.
// Type representative for a value that may be either a boolean or a byte.
public static final Type BYTE_OR_BOOL_TYPE = null;
// Equivalence to canonicalize local-variable information.
private static class LocalNodeEquivalence extends Equivalence<LocalVariableNode> {
@Override
protected boolean doEquivalent(LocalVariableNode a, LocalVariableNode b) {
if (!a.name.equals(b.name) || !a.desc.equals(b.desc)) {
return false;
}
return (a.signature == null && b.signature == null)
|| (a.signature != null && a.signature.equals(b.signature));
}
@Override
protected int doHash(LocalVariableNode local) {
return 31 * local.name.hashCode()
+ 7 * local.desc.hashCode()
+ (local.signature == null ? 0 : local.signature.hashCode());
}
}
// Simple pairing of local-node with its debug info and ASM type.
private static class LocalNodeInfo {
final Type type;
final LocalVariableNode node;
final DebugLocalInfo info;
LocalNodeInfo(LocalVariableNode node, DebugLocalInfo info) {
this.node = node;
this.info = info;
type = Type.getType(node.desc);
}
}
// Collection of locals information at a program point.
private static class LocalsAtOffset {
// Note that we assume live is always a super-set of starts.
final List<LocalNodeInfo> live;
final List<LocalNodeInfo> starts;
final List<LocalNodeInfo> ends;
IdentityHashMap<DebugLocalInfo, DebugLocalInfo> liveInfosCache = null;
static final LocalsAtOffset EMPTY = new LocalsAtOffset();
LocalsAtOffset() {
live = Collections.emptyList();
starts = Collections.emptyList();
ends = Collections.emptyList();
}
LocalsAtOffset(LocalsAtOffset other) {
live = new ArrayList<>(other.live);
// Starts and ends are never shared.
starts = new ArrayList<>();
ends = new ArrayList<>();
}
void addStart(LocalVariableNode node, DebugLocalInfo info) {
starts.add(new LocalNodeInfo(node, info));
}
void addEnd(LocalVariableNode node, DebugLocalInfo info) {
ends.add(new LocalNodeInfo(node, info));
}
void addLive(LocalVariableNode node, DebugLocalInfo info) {
assert liveInfosCache == null;
live.add(new LocalNodeInfo(node, info));
}
boolean isLive(DebugLocalInfo info) {
if (live.size() < 10) {
for (LocalNodeInfo entry : live) {
if (entry.info == info) {
return true;
}
}
return false;
}
if (liveInfosCache == null) {
liveInfosCache = new IdentityHashMap<>(live.size());
for (LocalNodeInfo entry : live) {
liveInfosCache.put(entry.info, entry.info);
}
}
return liveInfosCache.containsKey(info);
}
}
// Typed mapping from a local slot or stack slot to a virtual register.
public static class Slot {
public final int register;
public final Type type;
@Override
public String toString() {
return "r" + register + ":" + type;
}
public Slot(int register, Type type) {
assert type != REFERENCE_TYPE;
assert type != OBJECT_TYPE;
assert type != ARRAY_TYPE;
this.register = register;
this.type = type;
}
public boolean isCompatibleWith(Type other) {
return isCompatible(type, other);
}
public boolean isCategory1() {
return isCategory1(type);
}
public Type getArrayElementType() {
assert type == NULL_TYPE || type == ARRAY_TYPE || type.getSort() == Type.ARRAY;
if (type == JarState.NULL_TYPE) {
return null;
}
return getArrayElementType(type);
}
public static boolean isCategory1(Type type) {
return type != Type.LONG_TYPE && type != Type.DOUBLE_TYPE;
}
public static boolean isCompatible(Type type, Type other) {
assert type != REFERENCE_TYPE;
assert type != OBJECT_TYPE;
assert type != ARRAY_TYPE;
if (type == BYTE_OR_BOOL_TYPE) {
type = Type.BYTE_TYPE;
}
if (other == BYTE_OR_BOOL_TYPE) {
other = Type.BYTE_TYPE;
}
int sort = type.getSort();
int otherSort = other.getSort();
if (isReferenceCompatible(type, other)) {
return true;
}
// Integers are assumed compatible with any other 32-bit integral.
if (isIntCompatible(sort)) {
return isIntCompatible(otherSort);
}
if (isIntCompatible(otherSort)) {
return isIntCompatible(sort);
}
// In all other cases we require the two types to represent the same concrete type.
return type.equals(other);
}
private static Type getArrayElementType(Type type) {
String desc = type.getDescriptor();
assert desc.charAt(0) == '[';
return Type.getType(desc.substring(1));
}
private static boolean isIntCompatible(int sort) {
return Type.BOOLEAN <= sort && sort <= Type.INT;
}
private static boolean isReferenceCompatible(Type type, Type other) {
int sort = type.getSort();
int otherSort = other.getSort();
// Catch all matching.
if (other == REFERENCE_TYPE) {
return sort == Type.OBJECT || sort == Type.ARRAY || sort == Type.METHOD;
}
if (other == OBJECT_TYPE) {
return sort == Type.OBJECT;
}
if (other == ARRAY_TYPE) {
return type == NULL_TYPE || sort == Type.ARRAY;
}
return (sort == Type.OBJECT && otherSort == Type.ARRAY)
|| (sort == Type.ARRAY && otherSort == Type.OBJECT)
|| (sort == Type.OBJECT && otherSort == Type.OBJECT)
|| (sort == Type.ARRAY && otherSort == Type.ARRAY);
}
}
public static class Local {
final Slot slot;
final DebugLocalInfo info;
public Local(Slot slot, DebugLocalInfo info) {
this.slot = slot;
this.info = info;
}
}
// Immutable recording of the state (locals and stack should not be mutated).
private static class Snapshot {
public final Local[] locals;
public final ImmutableList<Slot> stack;
public Snapshot(Local[] locals, ImmutableList<Slot> stack) {
this.locals = locals;
this.stack = stack;
}
@Override
public String toString() {
return "locals: " + localsToString(Arrays.asList(locals))
+ ", stack: " + stackToString(stack);
}
}
final int startOfStack;
private int topOfStack;
// Locals are split into three parts based on types:
// 1) reference-type locals have registers in range: [0; localsSize[
// 2) single-width locals have registers in range: [localsSize; 2*localsSize[
// 3) wide-width locals have registers in range: [2*localsSize; 3*localsSize[
// This ensures that we can insert debugging-ranges into the SSA graph (via DebugLocal{Start,End})
// without conflating locals that are shared among different types. This issue arises because a
// debugging range can be larger than the definite-assignment scope of a local (eg, a local
// introduced in an unscoped switch case). To ensure that the SSA graph is valid we must introduce
// the local before inserting any DebugLocalRead (we do so in the method prelude, but that can
// potentially lead to phi functions merging locals of different move-types. Thus we allocate
// registers from the three distinct spaces.
private final int localsSize;
private final Local[] locals;
// Equivalence on the position-independent parts of local-variable nodes.
private final LocalNodeEquivalence localNodeEquivalence = new LocalNodeEquivalence();
// Canonical local-variable info objects.
private final Map<Equivalence.Wrapper<LocalVariableNode>, DebugLocalInfo> canonicalLocalInfo;
// Active locals at each program point at which they change.
private final Int2ReferenceSortedMap<LocalsAtOffset> localsAtOffsetTable;
private final Deque<Slot> stack = new ArrayDeque<>();
private final Map<Integer, Snapshot> targetStates = new HashMap<>();
// Mode denoting that the state setup is done and we are now emitting IR.
// Concretely we treat all remaining byte-or-bool types as bytes (no actual type can flow there).
private boolean building = false;
public JarState(int maxLocals, List localNodes, JarSourceCode source, JarApplicationReader application) {
int localsRegistersSize = maxLocals * 3;
localsSize = maxLocals;
locals = new Local[localsRegistersSize];
startOfStack = localsRegistersSize;
topOfStack = startOfStack;
localsAtOffsetTable = new Int2ReferenceAVLTreeMap<>();
localsAtOffsetTable.put(-1, LocalsAtOffset.EMPTY);
if (localNodes.size() == 0) {
canonicalLocalInfo = Collections.emptyMap();
} else if (localNodes.size() == 1) {
LocalVariableNode local = (LocalVariableNode) localNodes.get(0);
DebugLocalInfo info = createLocalInfo(local, application);
canonicalLocalInfo = Collections.singletonMap(localNodeEquivalence.wrap(local), info);
populateLocalsAtTable(local, info, source);
} else {
canonicalLocalInfo = new HashMap<>(localNodes.size());
for (Object o : localNodes) {
LocalVariableNode node = (LocalVariableNode) o;
Equivalence.Wrapper<LocalVariableNode> wrapped = localNodeEquivalence.wrap(node);
DebugLocalInfo info = canonicalLocalInfo.get(wrapped);
if (info == null) {
info = createLocalInfo(node, application);
canonicalLocalInfo.put(wrapped, info);
}
populateLocalsAtTable(node, info, source);
}
}
}
private static DebugLocalInfo createLocalInfo(
LocalVariableNode node,
JarApplicationReader application) {
return new DebugLocalInfo(
application.getString(node.name),
application.getTypeFromDescriptor(node.desc),
node.signature == null ? null : application.getString(node.signature));
}
private void populateLocalsAtTable(
LocalVariableNode node, DebugLocalInfo info, JarSourceCode source) {
if (node.start == node.end) {
return;
}
int start = source.getOffset(node.start);
int end = source.getOffset(node.end);
// Ensure that there is an entry at the starting point of the local.
{
LocalsAtOffset atStart;
int lastOrStart = localsAtOffsetTable.headMap(start + 1).lastIntKey();
if (lastOrStart < start) {
atStart = new LocalsAtOffset(localsAtOffsetTable.get(lastOrStart));
localsAtOffsetTable.put(start, atStart);
} else {
atStart = localsAtOffsetTable.get(start);
}
atStart.addStart(node, info);
}
// Ensure there is an entry at the ending point of the local.
{
LocalsAtOffset atEnd;
int lastOrEnd = localsAtOffsetTable.headMap(end + 1).lastIntKey();
if (lastOrEnd < end) {
atEnd = new LocalsAtOffset(localsAtOffsetTable.get(lastOrEnd));
localsAtOffsetTable.put(end, atEnd);
} else {
atEnd = localsAtOffsetTable.get(end);
}
atEnd.addEnd(node, info);
}
// Add the local to all live entries in its range.
for (LocalsAtOffset entry : localsAtOffsetTable.subMap(start, end).values()) {
entry.addLive(node, info);
}
}
public List<Local> localsNotLiveAtAllSuccessors(IntSet successors) {
Local[] liveLocals = Arrays.copyOf(locals, locals.length);
List<Local> deadLocals = new ArrayList<>(locals.length);
IntIterator it = successors.iterator();
while (it.hasNext()) {
LocalsAtOffset localsAtOffset = getLocalsAt(it.nextInt());
for (int i = 0; i < liveLocals.length; i++) {
Local live = liveLocals[i];
if (live != null && live.info != null && !localsAtOffset.isLive(live.info)) {
deadLocals.add(live);
liveLocals[i] = null;
}
}
}
return deadLocals;
}
private LocalsAtOffset getLocalsAt(int offset) {
return offset < 0
? LocalsAtOffset.EMPTY
: localsAtOffsetTable.get(localsAtOffsetTable.headMap(offset + 1).lastIntKey());
}
public void setBuilding() {
assert stack.isEmpty();
building = true;
for (int i = 0; i < locals.length; i++) {
Local local = locals[i];
if (local != null && local.slot.type == BYTE_OR_BOOL_TYPE) {
locals[i] = new Local(new Slot(local.slot.register, Type.BYTE_TYPE), local.info);
}
}
for (Entry<Integer, Snapshot> entry : targetStates.entrySet()) {
Local[] locals = entry.getValue().locals;
for (int i = 0; i < locals.length; i++) {
Local local = locals[i];
if (local != null && local.slot.type == BYTE_OR_BOOL_TYPE) {
locals[i] = new Local(new Slot(local.slot.register, Type.BYTE_TYPE), local.info);
}
}
ImmutableList.Builder<Slot> builder = ImmutableList.builder();
boolean found = false;
for (Slot slot : entry.getValue().stack) {
if (slot.type == BYTE_OR_BOOL_TYPE) {
found = true;
builder.add(new Slot(slot.register, Type.BYTE_TYPE));
} else {
builder.add(slot);
}
}
if (found) {
entry.setValue(new Snapshot(locals, builder.build()));
}
}
}
// Local variable procedures.
private List<Pair<Integer, Type>> writes = new ArrayList<>();
private List<Local> localsToOpen = new ArrayList<>();
private List<Local> localsToClose = new ArrayList<>();
public void beginTransaction(int offset, boolean hasNextInstruction) {
getLocalsToClose(offset);
if (hasNextInstruction) {
getLocalsToOpen(offset);
} else {
assert localsToOpen.isEmpty();
localsToOpen.clear();
}
assert writes.isEmpty();
writes.clear();
}
public void beginTransactionSynthetic() {
assert localsToClose.isEmpty();
assert localsToOpen.isEmpty();
assert writes.isEmpty();
writes.clear();
}
public void endTransaction() {
closeLocals();
applyWrites();
openLocals();
}
public void beginTransactionAtBlockStart(int offset) {
// If there are locals closing at the start of a block, just ignore them,
// since we should have closed them at the end of the predecessor blocks.
assert localsToClose.isEmpty();
assert writes.isEmpty();
getLocalsToOpen(offset);
}
private void applyWrites() {
for (Pair<Integer, Type> write : writes) {
applyWriteLocal(write.getFirst(), write.getSecond());
}
writes.clear();
}
private void getLocalsToOpen(int offset) {
assert localsToOpen.isEmpty();
LocalsAtOffset localsAtOffset = localsAtOffsetTable.get(offset);
if (localsAtOffset == null) {
return;
}
for (LocalNodeInfo start : localsAtOffset.starts) {
int register = getLocalRegister(start.node.index, start.type);
Local existingLocal = getLocalForRegister(register);
assert existingLocal != null;
Local local = new Local(existingLocal.slot, start.info);
localsToOpen.add(local);
}
}
private void openLocals() {
for (Local local : localsToOpen) {
assert local != null;
openLocal(local);
}
localsToOpen.clear();
}
private void getLocalsToClose(int offset) {
assert localsToClose.isEmpty();
LocalsAtOffset localsAtOffset = localsAtOffsetTable.get(offset);
if (localsAtOffset == null) {
return;
}
for (LocalNodeInfo end : localsAtOffset.ends) {
int register = getLocalRegister(end.node.index, end.type);
Local local = getLocalForRegister(register);
assert local != null;
if (local.info != null) {
localsToClose.add(local);
}
}
}
private void closeLocals() {
for (Local localToClose : localsToClose) {
assert localToClose != null;
// Since the instruction preceding this point may have strongly updated the type,
// and the localsToClose list may have been generated before the preceding instruction,
// we cannot assert that localToClose == local at this point.
// We only set the info to null and leave the type as-is.
Local local = getLocalForRegister(localToClose.slot.register);
setLocalForRegister(local.slot.register, local.slot.type, null);
}
localsToClose.clear();
}
public List<Local> getLocalsToClose() {
return localsToClose;
}
public List<Local> getLocalsToOpen() {
return localsToOpen;
}
public ImmutableList<Local> getLocals() {
ImmutableList.Builder<Local> nonNullLocals = ImmutableList.builder();
for (Local local : locals) {
if (local != null) {
nonNullLocals.add(local);
}
}
return nonNullLocals.build();
}
int getLocalRegister(int index, Type type) {
assert index < localsSize;
if (type == BYTE_OR_BOOL_TYPE) {
assert Slot.isCategory1(type);
return index + localsSize;
}
if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) {
return index;
}
return Slot.isCategory1(type) ? index + localsSize : index + 2 * localsSize;
}
public DebugLocalInfo getIncomingLocalInfoForRegister(int register) {
if (register >= locals.length) {
return null;
}
Local local = getLocalForRegister(register);
return local == null ? null : local.info;
}
public DebugLocalInfo getOutgoingLocalInfoForRegister(int register) {
DebugLocalInfo local = getIncomingLocalInfoForRegister(register);
if (local != null && localsToClose != null) {
for (Local localToClose : localsToClose) {
if (localToClose.slot.register == register) {
local = null;
break;
}
}
}
if (local == null && localsToOpen != null) {
for (Local localToOpen : localsToOpen) {
if (localToOpen.slot.register == register) {
local = localToOpen.info;
break;
}
}
}
return local;
}
private Local getLocalForRegister(int register) {
return locals[register];
}
private Local getLocal(int index, Type type) {
return getLocalForRegister(getLocalRegister(index, type));
}
private Local setLocal(int index, Type type, DebugLocalInfo info) {
return setLocalForRegister(getLocalRegister(index, type), type, info);
}
private Local setLocalForRegister(int register, Type type, DebugLocalInfo info) {
Slot slot = new Slot(register, type);
Local local = new Local(slot, info);
locals[register] = local;
return local;
}
private void openLocal(Local localToOpen) {
int register = localToOpen.slot.register;
DebugLocalInfo info = localToOpen.info;
Local local = getLocalForRegister(register);
Type type = Type.getType(info.type.toDescriptorString());
if (!local.slot.isCompatibleWith(type)) {
throw new InvalidDebugInfoException(
"Attempt to define local of type " + prettyType(local.slot.type) + " as " + info);
}
// Only update local info; keep slot type intact.
locals[register] = new Local(local.slot, localToOpen.info);
}
public int writeLocal(int index, Type type) {
writes.add(new Pair<>(index, type));
return getLocalRegister(index, type);
}
private void applyWriteLocal(int index, Type type) {
assert nonNullType(type);
Local local = getLocal(index, type);
if (local != null && local.info != null && !local.slot.isCompatibleWith(type)) {
throw new InvalidDebugInfoException(
"Attempt to write value of type " + prettyType(type) + " to local " + local.info);
}
// We cannot assume consistency for writes because we do not have complete information about the
// scopes of locals. We assume the program to be verified and overwrite if the types mismatch.
if (local == null || !typeEquals(local.slot.type, type)) {
DebugLocalInfo info = local == null ? null : local.info;
setLocal(index, type, info);
}
}
public boolean typeEquals(Type type1, Type type2) {
return (type1 == BYTE_OR_BOOL_TYPE && type2 == BYTE_OR_BOOL_TYPE)
|| (type1 != null && type1.equals(type2));
}
public Slot readLocal(int index, Type type) {
Local local = getLocal(index, type);
assert local != null;
if (local.info != null && !local.slot.isCompatibleWith(type)) {
throw new InvalidDebugInfoException(
"Attempt to read value of type " + prettyType(type) + " from local " + local.info);
}
assert local.slot.isCompatibleWith(type);
return local.slot;
}
public boolean nonNullType(Type type) {
return type != null || !building;
}
// Stack procedures.
public int push(Type type) {
assert nonNullType(type);
int top = topOfStack;
// For simplicity, every stack slot (and local variable) is wide (uses two registers).
topOfStack += 2;
Slot slot = new Slot(top, type);
stack.push(slot);
return top;
}
public Slot peek() {
return stack.peek();
}
public Slot peek(Type type) {
Slot slot = stack.peek();
assert slot.isCompatibleWith(type);
return slot;
}
public Slot pop() {
assert topOfStack > startOfStack;
// For simplicity, every stack slot (and local variable) is wide (uses two registers).
topOfStack -= 2;
Slot slot = stack.pop();
assert nonNullType(slot.type);
assert slot.register == topOfStack;
return slot;
}
public Slot pop(Type type) {
Slot slot = pop();
assert slot.isCompatibleWith(type)
: "Tried to pop " + prettyType(slot.type) + " as " + prettyType(type);
return slot;
}
public Slot[] popReverse(int count) {
Slot[] slots = new Slot[count];
for (int i = count - 1; i >= 0; i--) {
slots[i] = pop();
}
return slots;
}
public Slot[] popReverse(int count, Type type) {
Slot[] slots = popReverse(count);
assert verifySlots(slots, type);
return slots;
}
// State procedures.
public boolean hasState(int offset) {
return targetStates.get(offset) != null;
}
public void restoreState(int offset) {
Snapshot snapshot = targetStates.get(offset);
assert snapshot != null;
assert locals.length == snapshot.locals.length;
System.arraycopy(snapshot.locals, 0, locals, 0, locals.length);
stack.clear();
stack.addAll(snapshot.stack);
topOfStack = startOfStack + 2 * stack.size();
}
public boolean recordStateForTarget(int target) {
return recordStateForTarget(target, locals.clone(), ImmutableList.copyOf(stack));
}
public boolean recordStateForExceptionalTarget(int target) {
return recordStateForTarget(
target,
locals.clone(),
ImmutableList.of(new Slot(startOfStack, JarSourceCode.THROWABLE_TYPE)));
}
private boolean recordStateForTarget(int target, Local[] locals, ImmutableList<Slot> stack) {
if (!canonicalLocalInfo.isEmpty()) {
for (int i = 0; i < locals.length; i++) {
if (locals[i] != null) {
locals[i] = new Local(locals[i].slot, null);
}
}
LocalsAtOffset localsAtOffset = getLocalsAt(target);
for (LocalNodeInfo live : localsAtOffset.live) {
int register = getLocalRegister(live.node.index, live.type);
Local local = locals[register];
locals[register] = new Local(local.slot, live.info);
}
}
Snapshot snapshot = targetStates.get(target);
if (snapshot != null) {
Local[] newLocals = mergeLocals(snapshot.locals, locals);
ImmutableList<Slot> newStack = mergeStacks(snapshot.stack, stack);
if (newLocals != snapshot.locals || newStack != snapshot.stack) {
targetStates.put(target, new Snapshot(newLocals, newStack));
return true;
}
// The snapshot is up to date - no new type information recoded.
return false;
}
targetStates.put(target, new Snapshot(locals, stack));
return true;
}
private boolean isRefinement(Type current, Type other) {
return (current == JarState.NULL_TYPE && other != JarState.NULL_TYPE)
|| (current == JarState.BYTE_OR_BOOL_TYPE && other != JarState.BYTE_OR_BOOL_TYPE);
}
private ImmutableList<Slot> mergeStacks(
ImmutableList<Slot> currentStack, ImmutableList<Slot> newStack) {
assert currentStack.size() == newStack.size();
List<Slot> mergedStack = null;
for (int i = 0; i < currentStack.size(); i++) {
if (isRefinement(currentStack.get(i).type, newStack.get(i).type)) {
if (mergedStack == null) {
mergedStack = new ArrayList<>();
mergedStack.addAll(currentStack.subList(0, i));
}
mergedStack.add(newStack.get(i));
} else if (mergedStack != null) {
assert currentStack.get(i).isCompatibleWith(newStack.get(i).type);
mergedStack.add(currentStack.get(i));
}
}
return mergedStack != null ? ImmutableList.copyOf(mergedStack) : currentStack;
}
private Local[] mergeLocals(Local[] currentLocals, Local[] newLocals) {
assert currentLocals.length == newLocals.length;
Local[] mergedLocals = null;
for (int i = 0; i < currentLocals.length; i++) {
Local currentLocal = currentLocals[i];
Local newLocal = newLocals[i];
if (currentLocal == null || newLocal == null) {
continue;
}
// If this assert triggers we can get different debug information for the same local
// on different control-flow paths and we will have to merge them.
assert currentLocal.info == newLocal.info;
if (isRefinement(currentLocal.slot.type, newLocal.slot.type)) {
if (mergedLocals == null) {
mergedLocals = new Local[currentLocals.length];
System.arraycopy(currentLocals, 0, mergedLocals, 0, i);
}
Slot newSlot = new Slot(newLocal.slot.register, newLocal.slot.type);
mergedLocals[i] = new Local(newSlot, newLocal.info);
} else if (mergedLocals != null) {
mergedLocals[i] = currentLocals[i];
}
}
return mergedLocals != null ? mergedLocals : currentLocals;
}
// Other helpers.
private static boolean verifySlots(Slot[] slots, Type type) {
for (Slot slot : slots) {
assert slot.isCompatibleWith(type);
}
return true;
}
// Printing helpers.
@Override
public String toString() {
return "locals: " + localsToString(Arrays.asList(locals)) + ", stack: " + stackToString(stack);
}
public static String stackToString(Collection<Slot> stack) {
List<String> strings = new ArrayList<>(stack.size());
for (Slot slot : stack) {
if (slot.type == BYTE_OR_BOOL_TYPE) {
strings.add("<byte|bool>");
} else {
strings.add(slot.type.toString());
}
}
StringBuilder builder = new StringBuilder("{ ");
for (int i = strings.size() - 1; i >= 0; i--) {
builder.append(strings.get(i));
if (i > 0) {
builder.append(", ");
}
}
builder.append(" }");
return builder.toString();
}
public static String localsToString(Collection<Local> locals) {
StringBuilder builder = new StringBuilder("{ ");
boolean first = true;
for (Local local : locals) {
if (!first) {
builder.append(", ");
} else {
first = false;
}
if (local == null) {
builder.append("_");
} else if (local.info != null) {
builder.append(local.info);
} else if (local.slot.type == BYTE_OR_BOOL_TYPE) {
builder.append("<byte|bool>");
} else {
builder.append(local.slot.type.toString());
}
}
builder.append(" }");
return builder.toString();
}
private String prettyType(Type type) {
if (type == BYTE_OR_BOOL_TYPE) {
return "<byte|bool>";
}
if (type == ARRAY_TYPE) {
return type.getElementType().getInternalName();
}
if (type == REFERENCE_TYPE || type == OBJECT_TYPE || type == NULL_TYPE) {
return type.getInternalName();
}
return DescriptorUtils.descriptorToJavaType(type.getDescriptor());
}
}