Merge "Nit: shorten long lines in PG config parser."
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
index 258d8e6..2dceeab 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRBuilder.java
@@ -213,6 +213,13 @@
       return normalPredecessors.size() + exceptionalPredecessors.size();
     }
 
+    IntSet allSuccessors() {
+      IntSet all = new IntArraySet(normalSuccessors.size() + exceptionalSuccessors.size());
+      all.addAll(normalSuccessors);
+      all.addAll(exceptionalSuccessors);
+      return all;
+    }
+
     BlockInfo split(
         int blockStartOffset, int fallthroughOffset, Int2ReferenceMap<BlockInfo> targets) {
       BlockInfo fallthroughInfo = new BlockInfo();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
index 64513c3..1862277 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarSourceCode.java
@@ -189,7 +189,7 @@
     this.application = application;
     this.clazz = clazz;
     parameterTypes = Arrays.asList(Type.getArgumentTypes(node.desc));
-    state = new JarState(node.maxLocals, node.localVariables, application);
+    state = new JarState(node.maxLocals, node.localVariables, this, application);
     AbstractInsnNode first = node.instructions.getFirst();
     initialLabel = first instanceof LabelNode ? (LabelNode) first : null;
   }
@@ -286,7 +286,7 @@
     // Add debug information for all locals at the initial label.
     List<Local> locals = null;
     if (initialLabel != null) {
-      locals = state.openLocals(initialLabel);
+      locals = state.openLocals(getOffset(initialLabel));
     }
 
     currentPosition = Position.none();
@@ -374,7 +374,7 @@
       entry = CFG.get(0);
     }
     worklist.add(new JarStateWorklistItem(entry, 0));
-    state.recordStateForTarget(0, this);
+    state.recordStateForTarget(0);
     for (JarStateWorklistItem item = worklist.poll(); item != null; item = worklist.poll()) {
       state.restoreState(item.instructionIndex);
       // Iterate each of the instructions in the block to compute the outgoing JarState.
@@ -384,14 +384,14 @@
         // that changed to the worklist.
         if (i == instructionCount() || (i != item.instructionIndex && CFG.containsKey(i))) {
           item.blockInfo.normalSuccessors.iterator().forEachRemaining(offset -> {
-            if (state.recordStateForTarget(offset, this)) {
+            if (state.recordStateForTarget(offset)) {
               if (offset >= 0) {
                 worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
               }
             }
           });
           item.blockInfo.exceptionalSuccessors.iterator().forEachRemaining(offset -> {
-            if (state.recordStateForExceptionalTarget(offset, this)) {
+            if (state.recordStateForExceptionalTarget(offset)) {
               if (offset >= 0) {
                 worklist.add(new JarStateWorklistItem(CFG.get(offset.intValue()), offset));
               }
@@ -435,7 +435,7 @@
       int fallthroughInstructionIndex, IRBuilder builder) {
     AbstractInsnNode insn = node.instructions.get(fallthroughInstructionIndex);
     if (insn instanceof LabelNode) {
-      for (Local local : state.getLocalsToClose((LabelNode) insn)) {
+      for (Local local : state.getLocalsToClose(getOffset(insn))) {
         builder.addDebugLocalEnd(local.slot.register, local.info);
       }
     }
@@ -1700,12 +1700,13 @@
   }
 
   private void updateState(LabelNode insn) {
+    int offset = getOffset(insn);
     // Close scope of locals ending at this point.
-    List<Local> locals = state.getLocalsToClose(insn);
+    List<Local> locals = state.getLocalsToClose(offset);
     state.closeLocals(locals);
     // Open the scope of locals starting at this point.
     if (insn != initialLabel) {
-      state.openLocals(insn);
+      state.openLocals(offset);
     }
   }
 
@@ -1818,27 +1819,11 @@
     int blockOffset = builder.getCFG().headMap(offset).lastIntKey();
     BlockInfo blockInfo = builder.getCFG().get(blockOffset);
     // Read all locals that are not live on all successors to ensure liveness.
-    for (Local local : state.getLocals()) {
-      if (!liveAtAllSuccessors(blockInfo, local)) {
-        builder.addDebugLocalRead(local.slot.register, local.info);
-      }
+    for (Local local : state.localsNotLiveAtAllSuccessors(blockInfo.allSuccessors())) {
+      builder.addDebugLocalRead(local.slot.register, local.info);
     }
   }
 
-  private boolean liveAtAllSuccessors(BlockInfo blockInfo, Local local) {
-    for (int successor : blockInfo.normalSuccessors) {
-      if (!state.localLiveAt(local, successor, this)) {
-        return false;
-      }
-    }
-    for (int successor : blockInfo.exceptionalSuccessors) {
-      if (!state.localLiveAt(local, successor, this)) {
-        return false;
-      }
-    }
-    return true;
-  }
-
   private void processLocalVariablesAtExit(AbstractInsnNode insn, IRBuilder builder) {
     assert isReturn(insn) || isThrow(insn);
     // Read all locals live at exit to ensure liveness.
@@ -2706,8 +2691,9 @@
   }
 
   private void build(LabelNode insn, IRBuilder builder) {
+    int offset = getOffset(insn);
     // Close locals starting at this point.
-    List<Local> locals = state.getLocalsToClose(insn);
+    List<Local> locals = state.getLocalsToClose(offset);
     for (Local local : locals) {
       builder.addDebugLocalEnd(local.slot.register, local.info);
     }
@@ -2715,7 +2701,7 @@
 
     // Open the scope of locals starting at this point.
     if (insn != initialLabel) {
-      for (Local local : state.openLocals(insn)) {
+      for (Local local : state.openLocals(offset)) {
         builder.addDebugLocalStart(local.slot.register, local.info);
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
index 67f6c93..af09854 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/JarState.java
@@ -8,22 +8,23 @@
 import com.android.tools.r8.graph.JarApplicationReader;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.base.Equivalence;
-import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Multimap;
+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.Comparator;
 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.LabelNode;
 import org.objectweb.asm.tree.LocalVariableNode;
 
 /**
@@ -65,6 +66,75 @@
     }
   }
 
+  // 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;
@@ -205,14 +275,14 @@
   private final int localsSize;
   private final Local[] locals;
 
-  // Mapping from local-variable nodes to their canonical local info.
-  private final Map<Equivalence.Wrapper<LocalVariableNode>, DebugLocalInfo> localVariables;
+  // Equivalence on the position-independent parts of local-variable nodes.
   private final LocalNodeEquivalence localNodeEquivalence = new LocalNodeEquivalence();
 
-  // Scope-points of all local variables for inserting debug scoping instructions.
-  private final Multimap<LabelNode, LocalVariableNode> localVariableStartPoints;
-  private final Multimap<LabelNode, LocalVariableNode> localVariableEndPoints;
+  // 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<>();
 
@@ -222,40 +292,34 @@
   // Concretely we treat all remaining byte-or-bool types as bytes (no actual type can flow there).
   private boolean building = false;
 
-  // TODO(zerny): Precompute the necessary local structures for faster access and remove this.
-  private final List localNodes;
-
-  public JarState(int maxLocals, List localNodes, JarApplicationReader application) {
+  public JarState(int maxLocals, List localNodes, JarSourceCode source, JarApplicationReader application) {
     int localsRegistersSize = maxLocals * 3;
     localsSize = maxLocals;
     locals = new Local[localsRegistersSize];
     startOfStack = localsRegistersSize;
     topOfStack = startOfStack;
-    this.localNodes = localNodes;
-    localVariables = computeLocals(localNodes, application);
-    localVariableStartPoints = HashMultimap.create();
-    localVariableEndPoints = HashMultimap.create();
-    populateLocalTables(localNodes);
-  }
-
-  private Map<Equivalence.Wrapper<LocalVariableNode>, DebugLocalInfo> computeLocals(
-      List localNodes, JarApplicationReader application) {
-    int size = localNodes.size();
-    if (size == 0) {
-      return Collections.emptyMap();
-    }
-    if (size == 1) {
+    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);
-      return Collections.singletonMap(
-          localNodeEquivalence.wrap(local), createLocalInfo(local, application));
+      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);
+      }
     }
-    Map<Equivalence.Wrapper<LocalVariableNode>, DebugLocalInfo> locals = new HashMap<>(size);
-    for (Object o : localNodes) {
-      LocalVariableNode node = (LocalVariableNode) o;
-      Equivalence.Wrapper<LocalVariableNode> wrapped = localNodeEquivalence.wrap(node);
-      locals.computeIfAbsent(wrapped, w -> createLocalInfo(w.get(), application));
-    }
-    return locals;
   }
 
   private static DebugLocalInfo createLocalInfo(
@@ -267,36 +331,64 @@
         node.signature == null ? null : application.getString(node.signature));
   }
 
-  private void populateLocalTables(List localNodes) {
-    for (Object object : localNodes) {
-      LocalVariableNode node = (LocalVariableNode) object;
-      if (node.start != node.end) {
-        localVariableStartPoints.put(node.start, node);
-        localVariableEndPoints.put(node.end, node);
+  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 boolean localLiveAt(Local local, int offset, JarSourceCode source) {
-    // TODO(zerny): Precompute and sort the local ranges.
-    for (Object object : localNodes) {
-      LocalVariableNode node = (LocalVariableNode) object;
-      DebugLocalInfo nodeInfo = localVariables.get(localNodeEquivalence.wrap(node));
-      if (nodeInfo != local.info) {
-        continue;
-      }
-      Type type = Type.getType(node.desc);
-      int register = getLocalRegister(node.index, type);
-      if (register != local.slot.register) {
-        continue;
-      }
-      int startOffset = source.getOffset(node.start);
-      int endOffset = source.getOffset(node.end);
-      if (offset < startOffset || endOffset <= offset) {
-        return false;
+  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 true;
+    return deadLocals;
+  }
+
+  private LocalsAtOffset getLocalsAt(int offset) {
+    return offset < 0
+        ? LocalsAtOffset.EMPTY
+        : localsAtOffsetTable.get(localsAtOffsetTable.headMap(offset + 1).lastIntKey());
   }
 
   public void setBuilding() {
@@ -334,32 +426,32 @@
 
   // Local variable procedures.
 
-  public List<Local> openLocals(LabelNode label) {
-    Collection<LocalVariableNode> nodes = localVariableStartPoints.get(label);
-    ArrayList<Local> locals = new ArrayList<>(nodes.size());
-    for (LocalVariableNode node : nodes) {
-      DebugLocalInfo info = localVariables.get(localNodeEquivalence.wrap(node));
-      locals.add(setLocalInfo(node.index, Type.getType(node.desc), info));
+  public List<Local> openLocals(int offset) {
+    LocalsAtOffset localsAtOffset = localsAtOffsetTable.get(offset);
+    if (localsAtOffset == null) {
+      return Collections.emptyList();
     }
-    // Sort to ensure deterministic instruction ordering (correctness is unaffected).
-    locals.sort(Comparator.comparingInt(local -> local.slot.register));
+    ArrayList<Local> locals = new ArrayList<>(localsAtOffset.starts.size());
+    for (LocalNodeInfo start : localsAtOffset.starts) {
+      locals.add(setLocalInfo(start.node.index, start.type, start.info));
+    }
     return locals;
   }
 
-  public List<Local> getLocalsToClose(LabelNode label) {
-    Collection<LocalVariableNode> nodes = localVariableEndPoints.get(label);
-    ArrayList<Local> locals = new ArrayList<>(nodes.size());
-    for (LocalVariableNode node : nodes) {
-      Type type = Type.getType(node.desc);
-      int register = getLocalRegister(node.index, type);
+  public List<Local> getLocalsToClose(int offset) {
+    LocalsAtOffset localsAtOffset = localsAtOffsetTable.get(offset);
+    if (localsAtOffset == null) {
+      return Collections.emptyList();
+    }
+    ArrayList<Local> locals = new ArrayList<>(localsAtOffset.ends.size());
+    for (LocalNodeInfo end : localsAtOffset.ends) {
+      int register = getLocalRegister(end.node.index, end.type);
       Local local = getLocalForRegister(register);
       assert local != null;
       if (local.info != null) {
         locals.add(local);
       }
     }
-    // Sort to ensure deterministic instruction ordering (correctness is unaffected).
-    locals.sort(Comparator.comparingInt(local -> local.slot.register));
     return locals;
   }
 
@@ -541,36 +633,29 @@
     topOfStack = startOfStack + 2 * stack.size();
   }
 
-  public boolean recordStateForTarget(int target, JarSourceCode source) {
-    return recordStateForTarget(target, locals.clone(), ImmutableList.copyOf(stack), source);
+  public boolean recordStateForTarget(int target) {
+    return recordStateForTarget(target, locals.clone(), ImmutableList.copyOf(stack));
   }
 
-  public boolean recordStateForExceptionalTarget(int target, JarSourceCode source) {
-    return recordStateForTarget(target,
+  public boolean recordStateForExceptionalTarget(int target) {
+    return recordStateForTarget(
+        target,
         locals.clone(),
-        ImmutableList.of(new Slot(startOfStack, JarSourceCode.THROWABLE_TYPE)),
-        source);
+        ImmutableList.of(new Slot(startOfStack, JarSourceCode.THROWABLE_TYPE)));
   }
 
-  private boolean recordStateForTarget(int target, Local[] locals, ImmutableList<Slot> stack,
-      JarSourceCode source) {
-    if (!localVariables.isEmpty()) {
+  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);
         }
       }
-      // TODO(zerny): Precompute and sort the local ranges.
-      for (Object object : localNodes) {
-        LocalVariableNode node = (LocalVariableNode) object;
-        int startOffset = source.getOffset(node.start);
-        int endOffset = source.getOffset(node.end);
-        if (startOffset <= target && target < endOffset) {
-          int register = getLocalRegister(node.index, Type.getType(node.desc));
-          Local local = locals[register];
-          DebugLocalInfo info = localVariables.get(localNodeEquivalence.wrap(node));
-          locals[register] = new Local(local.slot, info);
-        }
+      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);