Merge "Complete the control-flow graph construction as part of tracing instructions."
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 8bb31d2..2bef174 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -694,7 +694,7 @@
       StringBuilder builder, List<BasicBlock> list, Function<BasicBlock, String> postfix) {
     if (list.size() > 0) {
       for (BasicBlock block : list) {
-        builder.append(block.getNumber());
+        builder.append(block.number >= 0 ? block.number : "<unknown>");
         builder.append(postfix.apply(block));
         builder.append(" ");
       }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index 7e47ed2..b0ab940 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -277,7 +277,7 @@
       // Check that we don't ever have instructions that can throw and have targets.
       assert !dex.canThrow();
       for (int relativeOffset : targets) {
-        builder.ensureSuccessorBlock(offset + relativeOffset);
+        builder.ensureNormalSuccessorBlock(offset, offset + relativeOffset);
       }
       return true;
     }
@@ -297,7 +297,7 @@
         builder.ensureBlockWithoutEnqueuing(tryRangeStartAddress);
         // Edge to exceptional successors.
         for (Integer handlerOffset : getUniqueTryHandlerOffsets(tryRange)) {
-          builder.ensureSuccessorBlock(handlerOffset);
+          builder.ensureExceptionalSuccessorBlock(offset, handlerOffset);
         }
         // If the following instruction is a move-result include it in this (the invokes) block.
         if (index + 1 < code.instructions.length && isMoveResult(code.instructions[index + 1])) {
@@ -307,7 +307,7 @@
         }
         // Edge to normal successor if any (fallthrough).
         if (!(dex instanceof Throw)) {
-          builder.ensureSuccessorBlock(dex.getOffset() + dex.getSize());
+          builder.ensureNormalSuccessorBlock(offset, dex.getOffset() + dex.getSize());
         }
         return true;
       }
@@ -319,9 +319,9 @@
       switchPayloadResolver.addPayloadUser(dex);
 
       for (int target : switchPayloadResolver.absoluteTargets(dex)) {
-        builder.ensureSuccessorBlock(target);
+        builder.ensureNormalSuccessorBlock(offset, target);
       }
-      builder.ensureSuccessorBlock(offset + dex.getSize());
+      builder.ensureNormalSuccessorBlock(offset, offset + dex.getSize());
       return true;
     }
     // TODO(zerny): Remove this from block computation.
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 bd8a469..b3d5266 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
@@ -83,7 +83,14 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.StringUtils.BraceType;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
+import it.unimi.dsi.fastutil.ints.IntArraySet;
+import it.unimi.dsi.fastutil.ints.IntIterator;
+import it.unimi.dsi.fastutil.ints.IntSet;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedList;
@@ -101,6 +108,8 @@
  */
 public class IRBuilder {
 
+  private static final int INITIAL_BLOCK_OFFSET = -1;
+
   // SSA construction uses a worklist of basic blocks reachable from the entry and their
   // instruction offsets.
   private static class WorklistItem {
@@ -158,8 +167,64 @@
     }
   }
 
+  private static class BlockInfo {
+    BasicBlock block = new BasicBlock();
+    IntSet normalPredecessors = new IntArraySet();
+    IntSet normalSuccessors = new IntArraySet();
+    IntSet exceptionalPredecessors = new IntArraySet();
+    IntSet exceptionalSuccessors = new IntArraySet();
+
+    void addNormalPredecessor(int offset) {
+      normalPredecessors.add(offset);
+    }
+
+    void addNormalSuccessor(int offset) {
+      normalSuccessors.add(offset);
+    }
+
+    void replaceNormalPredecessor(int existing, int replacement) {
+      normalPredecessors.remove(existing);
+      normalPredecessors.add(replacement);
+    }
+
+    void addExceptionalPredecessor(int offset) {
+      exceptionalPredecessors.add(offset);
+    }
+
+    void addExceptionalSuccessor(int offset) {
+      exceptionalSuccessors.add(offset);
+    }
+
+    int predecessorCount() {
+      return normalPredecessors.size() + exceptionalPredecessors.size();
+    }
+
+    BlockInfo split(
+        int blockStartOffset, int fallthroughOffset, Int2ReferenceMap<BlockInfo> targets) {
+      BlockInfo fallthroughInfo = new BlockInfo();
+      fallthroughInfo.normalPredecessors = new IntArraySet(Collections.singleton(blockStartOffset));
+      fallthroughInfo.block.incrementUnfilledPredecessorCount();
+      // Move all normal successors to the fallthrough block.
+      IntIterator normalSuccessorIterator = normalSuccessors.iterator();
+      while (normalSuccessorIterator.hasNext()) {
+        BlockInfo normalSuccessor = targets.get(normalSuccessorIterator.nextInt());
+        normalSuccessor.replaceNormalPredecessor(blockStartOffset, fallthroughOffset);
+      }
+      fallthroughInfo.normalSuccessors = normalSuccessors;
+      normalSuccessors = new IntArraySet(Collections.singleton(fallthroughOffset));
+      // Copy all exceptional successors to the fallthrough block.
+      IntIterator exceptionalSuccessorIterator = fallthroughInfo.exceptionalSuccessors.iterator();
+      while (exceptionalSuccessorIterator.hasNext()) {
+        BlockInfo exceptionalSuccessor = targets.get(exceptionalSuccessorIterator.nextInt());
+        exceptionalSuccessor.addExceptionalPredecessor(fallthroughOffset);
+      }
+      fallthroughInfo.exceptionalSuccessors = new IntArraySet(this.exceptionalSuccessors);
+      return fallthroughInfo;
+    }
+  }
+
   // Mapping from instruction offsets to basic-block targets.
-  private final Map<Integer, BasicBlock> targets = new HashMap<>();
+  private final Int2ReferenceSortedMap<BlockInfo> targets = new Int2ReferenceAVLTreeMap<>();
 
   // Worklist of reachable blocks.
   private final Queue<Integer> traceBlocksWorklist = new LinkedList<>();
@@ -239,13 +304,8 @@
     assert source != null;
     source.setUp();
 
-    // Create entry block.
-    setCurrentBlock(new BasicBlock());
-
-    // If the method needs a prelude, the entry block must never be the target of other blocks.
-    if (!source.needsPrelude()) {
-      targets.put(0, currentBlock);
-    }
+    // Create entry block (at a non-targetable address).
+    targets.put(INITIAL_BLOCK_OFFSET, new BlockInfo());
 
     // Process reachable code paths starting from instruction 0.
     processedInstructions = new boolean[source.instructionCount()];
@@ -267,8 +327,8 @@
         // If the next instruction starts a block, fall through to it.
         if (index + 1 < source.instructionCount()) {
           int nextOffset = source.instructionOffset(index + 1);
-          if (getTarget(nextOffset) != null) {
-            ensureSuccessorBlock(nextOffset);
+          if (targets.get(nextOffset) != null) {
+            ensureNormalSuccessorBlock(startOfBlockOffset, nextOffset);
             break;
           }
         }
@@ -276,6 +336,7 @@
     }
     processedInstructions = null;
 
+    setCurrentBlock(targets.get(INITIAL_BLOCK_OFFSET).block);
     source.buildPrelude(this);
 
     // Process normal blocks reachable from the entry block using a worklist of reachable
@@ -340,11 +401,36 @@
 
   private boolean verifyFilledPredecessors() {
     for (BasicBlock block : blocks) {
-      assert block.verifyFilledPredecessors();
+      assert verifyFilledPredecessors(block);
     }
     return true;
   }
 
+  private boolean verifyFilledPredecessors(BasicBlock block) {
+    assert block.verifyFilledPredecessors();
+    // TODO(zerny): Consider moving the validation of the initial control-flow graph to after its
+    // construction and prior to building the IR.
+    for (BlockInfo info : targets.values()) {
+      if (info != null && info.block == block) {
+        assert info.predecessorCount() == block.getPredecessors().size();
+        assert info.normalSuccessors.size() == block.getNormalSucessors().size();
+        if (block.hasCatchHandlers()) {
+          assert info.exceptionalSuccessors.size()
+              == block.getCatchHandlers().getUniqueTargets().size();
+        } else {
+          assert !block.canThrow()
+              || info.exceptionalSuccessors.isEmpty()
+              || (info.exceptionalSuccessors.size() == 1
+                  && info.exceptionalSuccessors.iterator().nextInt() < 0);
+        }
+        return true;
+      }
+    }
+    // There are places where we add in new blocks that we do not represent in the initial CFG.
+    // TODO(zerny): Should we maintain the initial CFG after instruction building?
+    return true;
+  }
+
   private int processWorklist(int blockNumber) {
     for (WorklistItem item = ssaWorklist.poll(); item != null; item = ssaWorklist.poll()) {
       if (item.block.isFilled()) {
@@ -359,11 +445,11 @@
           source.closedCurrentBlock();
           break;
         }
-        BasicBlock block = getTarget(source.instructionOffset(i));
-        if (block != null && block != currentBlock) {
-          closeCurrentBlockWithFallThrough(block);
+        BlockInfo info = targets.get(source.instructionOffset(i));
+        if (info != null && info.block != currentBlock) {
+          closeCurrentBlockWithFallThrough(info.block);
           source.closedCurrentBlockWithFallthrough(i);
-          addToWorklist(block, i);
+          addToWorklist(info.block, i);
           break;
         }
         source.buildInstruction(this, i);
@@ -1151,7 +1237,8 @@
       // If this was a packed switch with only fallthrough cases we can make it a goto.
       // Oddly, this does happen.
       if (numberOfFallthroughs == numberOfTargets) {
-        targets.get(fallthroughOffset).decrementUnfilledPredecessorCount(numberOfFallthroughs);
+        BlockInfo info = targets.get(fallthroughOffset);
+        info.block.decrementUnfilledPredecessorCount(numberOfFallthroughs);
         addGoto(fallthroughOffset);
         return;
       }
@@ -1161,7 +1248,8 @@
       int bytesSaved = packedSwitchPayloadSize - sparseSwitchPayloadSize;
       // Perform the rewrite if we can reduce the payload size by more than 20%.
       if (bytesSaved > (packedSwitchPayloadSize / 5)) {
-        targets.get(fallthroughOffset).decrementUnfilledPredecessorCount(numberOfFallthroughs);
+        BlockInfo info = targets.get(fallthroughOffset);
+        info.block.decrementUnfilledPredecessorCount(numberOfFallthroughs);
         int nextCaseIndex = 0;
         int currentKey = keys[0];
         keys = new int[numberOfSparseTargets];
@@ -1458,10 +1546,16 @@
     BasicBlock block = new BasicBlock();
     blocks.add(block);
     block.incrementUnfilledPredecessorCount();
-    for (Integer offset : source.getCurrentCatchHandlers().getUniqueTargets()) {
-      BasicBlock target = getTarget(offset);
-      assert !target.isSealed();
-      target.incrementUnfilledPredecessorCount();
+    int freshOffset = INITIAL_BLOCK_OFFSET - 1;
+    while (targets.containsKey(freshOffset)) {
+      freshOffset--;
+    }
+    targets.put(freshOffset, null);
+    for (int offset : source.getCurrentCatchHandlers().getUniqueTargets()) {
+      BlockInfo target = targets.get(offset);
+      assert !target.block.isSealed();
+      target.block.incrementUnfilledPredecessorCount();
+      target.addExceptionalPredecessor(freshOffset);
     }
     addInstruction(new Goto());
     currentBlock.link(block);
@@ -1499,21 +1593,32 @@
   // Package (ie, SourceCode accessed) helpers.
 
   // Ensure there is a block starting at offset.
-  BasicBlock ensureBlockWithoutEnqueuing(int offset) {
-    BasicBlock block = targets.get(offset);
-    if (block == null) {
-      block = new BasicBlock();
-      targets.put(offset, block);
+  BlockInfo ensureBlockWithoutEnqueuing(int offset) {
+    assert offset != INITIAL_BLOCK_OFFSET;
+    BlockInfo info = targets.get(offset);
+    if (info == null) {
       // If this is a processed instruction, the block split and it has a fall-through predecessor.
       if (offset >= 0 && isOffsetProcessed(offset)) {
-        block.incrementUnfilledPredecessorCount();
+        int blockStartOffset = getBlockStartOffset(offset);
+        BlockInfo existing = targets.get(blockStartOffset);
+        info = existing.split(blockStartOffset, offset, targets);
+      } else {
+        info = new BlockInfo();
       }
+      targets.put(offset, info);
     }
-    return block;
+    return info;
+  }
+
+  private int getBlockStartOffset(int offset) {
+    if (targets.containsKey(offset)) {
+      return offset;
+    }
+    return targets.headMap(offset).lastIntKey();
   }
 
   // Ensure there is a block starting at offset and add it to the work-list if it needs processing.
-  private BasicBlock ensureBlock(int offset) {
+  private BlockInfo ensureBlock(int offset) {
     // We don't enqueue negative targets (these are special blocks, eg, an argument prelude).
     if (offset >= 0 && !isOffsetProcessed(offset)) {
       traceBlocksWorklist.add(offset);
@@ -1550,16 +1655,32 @@
   }
 
   // Ensure there is a block at offset and add a predecessor to it.
-  BasicBlock ensureSuccessorBlock(int offset) {
-    BasicBlock block = ensureBlock(offset);
-    block.incrementUnfilledPredecessorCount();
-    return block;
+  private void ensureSuccessorBlock(int sourceOffset, int targetOffset, boolean normal) {
+    BlockInfo targetInfo = ensureBlock(targetOffset);
+    int sourceStartOffset = getBlockStartOffset(sourceOffset);
+    BlockInfo sourceInfo = targets.get(sourceStartOffset);
+    if (normal) {
+      sourceInfo.addNormalSuccessor(targetOffset);
+      targetInfo.addNormalPredecessor(sourceStartOffset);
+    } else {
+      sourceInfo.addExceptionalSuccessor(targetOffset);
+      targetInfo.addExceptionalPredecessor(sourceStartOffset);
+    }
+    targetInfo.block.incrementUnfilledPredecessorCount();
+  }
+
+  void ensureNormalSuccessorBlock(int sourceOffset, int targetOffset) {
+    ensureSuccessorBlock(sourceOffset, targetOffset, true);
+  }
+
+  void ensureExceptionalSuccessorBlock(int sourceOffset, int targetOffset) {
+    ensureSuccessorBlock(sourceOffset, targetOffset, false);
   }
 
   // Private block helpers.
 
   private BasicBlock getTarget(int offset) {
-    return targets.get(offset);
+    return targets.get(offset).block;
   }
 
   private void closeCurrentBlock() {
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 5647878..1d8f18d 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
@@ -66,7 +66,6 @@
 import org.objectweb.asm.util.Textifier;
 import org.objectweb.asm.util.TraceMethodVisitor;
 
-
 public class JarSourceCode implements SourceCode {
 
   // Try-catch block wrapper containing resolved offsets.
@@ -144,7 +143,7 @@
   // thus that the monitor-entry prelude (part of the argument block which must not have a try-catch
   // successor) is not joined with the first instruction block (which likely will have a try-catch
   // successor).
-  private static final int EXCEPTIONAL_SYNC_EXIT_OFFSET = -1;
+  private static final int EXCEPTIONAL_SYNC_EXIT_OFFSET = -2;
   private static final TryCatchBlock EXCEPTIONAL_SYNC_EXIT =
       new TryCatchBlock(EXCEPTIONAL_SYNC_EXIT_OFFSET, 0, Integer.MAX_VALUE, null);
 
@@ -534,7 +533,7 @@
     if (targets != NO_TARGETS) {
       assert !canThrow(insn);
       for (int target : targets) {
-        builder.ensureSuccessorBlock(target);
+        builder.ensureNormalSuccessorBlock(index, target);
       }
       return true;
     }
@@ -549,12 +548,12 @@
           int handler = tryCatchBlock.getHandler();
           if (!seenHandlerOffsets.contains(handler)) {
             seenHandlerOffsets.add(handler);
-            builder.ensureSuccessorBlock(handler);
+            builder.ensureExceptionalSuccessorBlock(index, handler);
           }
         }
         // Edge to normal successor if any (fallthrough).
         if (!isThrow(insn)) {
-          builder.ensureSuccessorBlock(getOffset(insn.getNext()));
+          builder.ensureNormalSuccessorBlock(index, getOffset(insn.getNext()));
         }
         return true;
       }