Merge "Use ART default options"
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 6598ae4..0f0b38f 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -55,7 +55,6 @@
  */
 public final class D8 {
 
-  private static final String VERSION = "v0.2.0";
   private static final int STATUS_ERROR = 1;
 
   private D8() {}
@@ -111,7 +110,7 @@
       return;
     }
     if (command.isPrintVersion()) {
-      System.out.println("D8 " + VERSION);
+      System.out.println("D8 " + Version.LABEL);
       return;
     }
     run(command);
@@ -161,7 +160,7 @@
       return options.getMarker();
     }
     return new Marker(Tool.D8)
-        .put("version", VERSION)
+        .put("version", Version.LABEL)
         .put("min-api", options.minApiLevel);
   }
 
diff --git a/src/main/java/com/android/tools/r8/Disassemble.java b/src/main/java/com/android/tools/r8/Disassemble.java
index bb72d5d..a0770bc 100644
--- a/src/main/java/com/android/tools/r8/Disassemble.java
+++ b/src/main/java/com/android/tools/r8/Disassemble.java
@@ -144,7 +144,7 @@
       return;
     }
     if (command.isPrintVersion()) {
-      System.out.println("R8 v0.0.1");
+      System.out.println("R8 " + Version.LABEL);
       return;
     }
     R8.disassemble(command);
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index 44d523e..5a18b41 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -28,7 +28,6 @@
 import java.util.stream.Collectors;
 
 public class GenerateMainDexList {
-  private static final String VERSION = "v0.2.0";
   private final Timing timing = new Timing("maindex");
   private final InternalOptions options;
 
@@ -113,7 +112,7 @@
       return;
     }
     if (command.isPrintVersion()) {
-      System.out.println("MainDexListGenerator " + VERSION);
+      System.out.println("MainDexListGenerator " + Version.LABEL);
       return;
     }
     List<String> result = run(command);
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index b4c19ea..e8990f3 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -71,7 +71,6 @@
 
 public class R8 {
 
-  private static final String VERSION = "v0.2.0";
   private final Timing timing = new Timing("R8");
   private final InternalOptions options;
 
@@ -86,7 +85,7 @@
       return options.getMarker();
     }
     return new Marker(Tool.R8)
-        .put("version", VERSION)
+        .put("version", Version.LABEL)
         .put("min-api", options.minApiLevel);
   }
 
@@ -535,7 +534,7 @@
       return;
     }
     if (command.isPrintVersion()) {
-      System.out.println("R8 " + VERSION);
+      System.out.println("R8 " + Version.LABEL);
       return;
     }
     run(command);
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
new file mode 100644
index 0000000..ed19d71
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -0,0 +1,13 @@
+// Copyright (c) 2017, 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;
+
+public final class Version {
+
+  public static final String LABEL = "v0.2.0-dev";
+
+  private Version() {
+  }
+}
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 2134ccf..b6fbdee 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
@@ -638,6 +638,12 @@
     return currentDefinitions.get(register);
   }
 
+  public void replaceCurrentDefinitions(Value oldValue, Value newValue) {
+    assert oldValue.definition.getBlock() == this;
+    assert !oldValue.isUsed();
+    currentDefinitions.replaceAll((index, value) -> value == oldValue ? newValue : value);
+  }
+
   public void updateCurrentDefinition(int register, Value value, EdgeType readingEdge) {
     // If the reading/writing block is a catch successor, possibly update the on-throw value.
     if (isOnThrowValue(register, readingEdge)) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
index 9a8f8a5..d6063f3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugPosition.java
@@ -50,6 +50,15 @@
   }
 
   @Override
+  public boolean equals(Object other) {
+    if (other instanceof DebugPosition) {
+      DebugPosition o = (DebugPosition) other;
+      return line == o.line && file == o.file;
+    }
+    return false;
+  }
+
+  @Override
   public int maxInValueRegister() {
     throw new Unreachable();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
index 760a7ed..1390620 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCodeInstructionsIterator.java
@@ -51,4 +51,9 @@
   public void replaceCurrentInstruction(Instruction newInstruction) {
     instructionIterator.replaceCurrentInstruction(newInstruction);
   }
+
+  @Override
+  public void removeOrReplaceByNop() {
+    instructionIterator.removeOrReplaceByNop();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index de198c9..c110f2f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -86,6 +86,7 @@
     instruction.inValues.forEach(Value::clearUsersInfo);
     if (instruction.debugValues != null) {
       instruction.debugValues.forEach(Value::clearUsersInfo);
+      instruction.debugValues = null;
     }
   }
 
@@ -134,7 +135,9 @@
     assert value.getLocalInfo() != null;
     if (debugValues != null) {
       assert debugValues.contains(value);
-      debugValues.remove(value);
+      if (debugValues.remove(value)) {
+        value.removeDebugUser(this);
+      }
       return;
     }
     assert false;
@@ -142,6 +145,9 @@
 
   public void clearDebugValues() {
     if (debugValues != null) {
+      for (Value debugValue : debugValues) {
+        debugValue.removeDebugUser(this);
+      }
       debugValues.clear();
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
index 2cb8185..56d81a2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionIterator.java
@@ -31,4 +31,10 @@
    * @param instruction The instruction to add.
    */
   void add(Instruction instruction);
+
+  /**
+   * Safe removal function that will insert a Nop to take over the debug values if any are
+   * associated with the current instruction.
+   */
+  void removeOrReplaceByNop();
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Value.java b/src/main/java/com/android/tools/r8/ir/code/Value.java
index ffe3181..605eac3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Value.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Value.java
@@ -115,10 +115,17 @@
   }
 
   public void setLocalInfo(DebugLocalInfo local) {
+    assert local != null;
     assert debugData == null;
     debugData = new DebugData(local);
   }
 
+  public void clearLocalInfo() {
+    assert debugData.users.isEmpty();
+    assert debugData.phiUsers.isEmpty();
+    debugData = null;
+  }
+
   public List<Instruction> getDebugLocalStarts() {
     if (debugData == null) {
       return Collections.emptyList();
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index b35c1a0..cbd8941 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -48,8 +48,10 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
+import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -148,6 +150,11 @@
 
       // Populate the builder info objects.
       numberOfInstructions = 0;
+
+      // Remove redundant debug position instructions. They would otherwise materialize as
+      // unnecessary nops.
+      removeRedundantDebugPositions();
+
       ListIterator<BasicBlock> iterator = ir.listIterator();
       assert iterator.hasNext();
       BasicBlock block = iterator.next();
@@ -235,6 +242,26 @@
     return code;
   }
 
+  private void removeRedundantDebugPositions() {
+    ListIterator<BasicBlock> iterator = ir.listIterator();
+    DebugPosition lastMoveExceptionPosition = null;
+    while (iterator.hasNext()) {
+      BasicBlock next = iterator.next();
+      InstructionListIterator it = next.listIterator();
+      while (it.hasNext()) {
+        com.android.tools.r8.ir.code.Instruction instruction = it.next();
+        if (instruction.isDebugPosition()) {
+          if (instruction.asDebugPosition().equals(lastMoveExceptionPosition)) {
+            it.remove();
+          }
+          lastMoveExceptionPosition = null;
+        } else if (instruction.isMoveException()) {
+          lastMoveExceptionPosition = instruction.asMoveException().getPosition();
+        }
+      }
+    }
+  }
+
   // Rewrite ifs with offsets that are too large for the if encoding. The rewriting transforms:
   //
   //
@@ -316,7 +343,7 @@
     add(instruction, new FallThroughInfo(instruction));
   }
 
-  private boolean isNopInstruction(com.android.tools.r8.ir.code.Instruction instruction) {
+  private static boolean isNopInstruction(com.android.tools.r8.ir.code.Instruction instruction) {
     return instruction.isNop()
         || instruction.isDebugLocalsChange()
         || (instruction.isConstNumber() && !instruction.outValue().needsRegister());
@@ -813,7 +840,7 @@
       } else {
         size = 3;
       }
-      if (targetInfo.getIR().isReturn()) {
+      if (targetInfo.getIR().isReturn() && canTargetReturn(targetInfo.getIR().asReturn())) {
         // Set the size to the min of the size of the return and the size of the goto. When
         // adding instructions, we use the return if the computed size matches the size of the
         // return.
@@ -834,8 +861,9 @@
       Instruction dex;
       // Emit a return if the target is a return and the size of the return is the computed
       // size of this instruction.
-      if (targetInfo.getIR().isReturn() && size == targetInfo.getSize()) {
-        dex = targetInfo.getIR().asReturn().createDexInstruction(builder);
+      Return ret = targetInfo.getIR().asReturn();
+      if (ret != null && size == targetInfo.getSize() && canTargetReturn(ret)) {
+        dex = ret.createDexInstruction(builder);
       } else {
         switch (size) {
           case 1:
@@ -861,6 +889,18 @@
       dex.setOffset(getOffset()); // for better printing of the dex code.
       instructions.add(dex);
     }
+
+    private static boolean canTargetReturn(Return ret) {
+      InstructionListIterator it = ret.getBlock().listIterator(ret);
+      com.android.tools.r8.ir.code.Instruction prev = it.previous();
+      while (it.hasPrevious()) {
+        prev = it.previous();
+        if (!DexBuilder.isNopInstruction(prev)) {
+          break;
+        }
+      }
+      return !prev.isDebugPosition();
+    }
   }
 
   public static class IfInfo extends Info {
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 17b0e36..543f707 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
@@ -88,11 +88,6 @@
   }
 
   @Override
-  public boolean needsPrelude() {
-    return !accessFlags.isStatic() || !argumentTypes.isEmpty();
-  }
-
-  @Override
   public int instructionCount() {
     return code.instructions.length;
   }
@@ -143,11 +138,9 @@
   }
 
   @Override
-  public void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex) {
-  }
-
-  @Override
-  public void closedCurrentBlock() {
+  public void closingCurrentBlockWithFallthrough(
+      int fallthroughInstructionIndex, IRBuilder builder) {
+    // Intentionally empty.
   }
 
   @Override
@@ -206,7 +199,7 @@
     int offset = instructionOffset(instructionIndex);
     for (DexDebugEntry entry : debugEntries) {
       if (entry.address == offset) {
-        builder.updateCurrentDebugPosition(entry.line, entry.sourceFile);
+        builder.addDebugPosition(entry.line, entry.sourceFile);
         return;
       }
       if (entry.address > offset) {
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 245a75a..1b7a276 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
@@ -5,7 +5,6 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.ApiLevelException;
-import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.InternalCompilerError;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
@@ -60,6 +59,7 @@
 import com.android.tools.r8.ir.code.NewArrayEmpty;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Nop;
 import com.android.tools.r8.ir.code.Not;
 import com.android.tools.r8.ir.code.NumberConversion;
 import com.android.tools.r8.ir.code.NumericType;
@@ -269,8 +269,6 @@
 
   final private ValueNumberGenerator valueNumberGenerator;
 
-  private DebugPosition currentDebugPosition = null;
-
   private DexEncodedMethod method;
 
   // Source code to build IR from. Null if already built.
@@ -280,11 +278,9 @@
 
   private final InternalOptions options;
 
-  // Pending local changes.
+  // Pending local reads.
   private Value previousLocalValue = null;
-  private List<Value> debugLocalStarts = new ArrayList<>();
   private List<Value> debugLocalReads = new ArrayList<>();
-  private List<Value> debugLocalEnds = new ArrayList<>();
 
   private int nextBlockNumber = 0;
 
@@ -470,13 +466,12 @@
       // Build IR for each dex instruction in the block.
       for (int i = item.firstInstructionIndex; i < source.instructionCount(); ++i) {
         if (currentBlock == null) {
-          source.closedCurrentBlock();
           break;
         }
         BlockInfo info = targets.get(source.instructionOffset(i));
         if (info != null && info.block != currentBlock) {
+          source.closingCurrentBlockWithFallthrough(i, this);
           closeCurrentBlockWithFallThrough(info.block);
-          source.closedCurrentBlockWithFallthrough(i);
           addToWorklist(info.block, i);
           break;
         }
@@ -551,26 +546,13 @@
     addInstruction(new DebugLocalUninitialized(type, value));
   }
 
-  public void addDebugLocalStart(int register, DebugLocalInfo local) {
-    if (!options.debug) {
-      return;
-    }
-    assert local != null;
-    assert local == getCurrentLocal(register);
-    MoveType moveType = MoveType.fromDexType(local.type);
-    Value in = readRegisterIgnoreLocal(register, moveType);
-    if (in.isPhi() || in.getLocalInfo() != local) {
-      // We cannot shortcut if the local is defined by a phi as it could end up being trivial.
-      addDebugLocalWrite(moveType, register, in);
-    } else {
-      Value value = getLocalValue(register, local);
-      if (value != null) {
-        debugLocalStarts.add(value);
-      }
-    }
+  public void addDebugPosition(int line, DexString file) {
+    // Always add positions as they influence release builds (ie, stack traces).
+    addInstruction(new DebugPosition(line, file));
   }
 
   private void addDebugLocalWrite(MoveType type, int dest, Value in) {
+    assert options.debug;
     Value out = writeRegister(dest, type, ThrowingInfo.NO_THROW);
     DebugLocalWrite write = new DebugLocalWrite(out, in);
     assert !write.instructionTypeCanThrow();
@@ -578,17 +560,17 @@
   }
 
   private Value getLocalValue(int register, DebugLocalInfo local) {
+    assert options.debug;
     assert local != null;
     assert local == getCurrentLocal(register);
     MoveType moveType = MoveType.fromDexType(local.type);
+    return readRegisterIgnoreLocal(register, moveType);
+  }
+
+  private static boolean isValidFor(Value value, DebugLocalInfo local) {
     // Invalid debug-info may cause attempt to read a local that is not actually alive.
     // See b/37722432 and regression test {@code jasmin.InvalidDebugInfoTests::testInvalidInfoThrow}
-    Value in = readRegisterIgnoreLocal(register, moveType);
-    if (in.isUninitializedLocal() || in.getLocalInfo() != local) {
-      return null;
-    }
-    assert in.getLocalInfo() == local;
-    return in;
+    return !value.isUninitializedLocal() && value.getLocalInfo() == local;
   }
 
   public void addDebugLocalRead(int register, DebugLocalInfo local) {
@@ -596,18 +578,72 @@
       return;
     }
     Value value = getLocalValue(register, local);
-    if (value != null) {
+    if (isValidFor(value, local)) {
       debugLocalReads.add(value);
     }
   }
 
+  public void addDebugLocalStart(int register, DebugLocalInfo local) {
+    if (!options.debug) {
+      return;
+    }
+    Value value = getLocalValue(register, local);
+
+    // If the value is for a different local, introduce the new local. We cannot shortcut if the
+    // local is defined by a phi as it could end up being trivial.
+    if (value.isPhi() || value.getLocalInfo() != local) {
+      addDebugLocalWrite(MoveType.fromDexType(local.type), register, value);
+      return;
+    }
+
+    if (!isValidFor(value, local)) {
+      return;
+    }
+
+    // When inserting a start there are two possibilities:
+    // 1. The block is empty (eg, instructions from block entry until now materialized to nothing).
+    // 2. The block is non-empty (and the last instruction does not define the local to start).
+    if (currentBlock.getInstructions().isEmpty()) {
+      addInstruction(new Nop());
+    }
+    Instruction instruction = currentBlock.getInstructions().getLast();
+    assert instruction.outValue() != value;
+    instruction.addDebugValue(value);
+    value.addDebugLocalStart(instruction);
+  }
+
   public void addDebugLocalEnd(int register, DebugLocalInfo local) {
     if (!options.debug) {
       return;
     }
     Value value = getLocalValue(register, local);
-    if (value != null) {
-      debugLocalEnds.add(value);
+    if (!isValidFor(value, local)) {
+      return;
+    }
+
+    // When inserting an end there are three possibilities:
+    // 1. The block is empty (eg, instructions from block entry until now materialized to nothing).
+    // 2. The block has an instruction not defining the local being ended.
+    // 3. The block has an instruction defining the local being ended.
+    if (currentBlock.getInstructions().isEmpty()) {
+      addInstruction(new Nop());
+    }
+    Instruction instruction = currentBlock.getInstructions().getLast();
+    if (instruction.outValue() != value) {
+      instruction.addDebugValue(value);
+      value.addDebugLocalEnd(instruction);
+      return;
+    }
+    // In case 3. there are two cases:
+    // a. The defining instruction is a debug-write, in which case it should be removed.
+    // b. The defining instruction is overwriting the local value, in which case we de-associate it.
+    assert !instruction.outValue().isUsed();
+    if (instruction.isDebugLocalWrite()) {
+      DebugLocalWrite write = instruction.asDebugLocalWrite();
+      currentBlock.replaceCurrentDefinitions(value, write.src());
+      currentBlock.listIterator(write).removeOrReplaceByNop();
+    } else {
+      instruction.outValue().clearLocalInfo();
     }
   }
 
@@ -1146,6 +1182,13 @@
     assert out.getLocalInfo() == null;
     MoveException instruction = new MoveException(out);
     assert !instruction.instructionTypeCanThrow();
+    if (!currentBlock.getInstructions().isEmpty()
+        && currentBlock.getInstructions().getLast().isDebugPosition()) {
+      DebugPosition position = currentBlock.getInstructions().getLast().asDebugPosition();
+      position.moveDebugValues(instruction);
+      instruction.setPosition(position);
+      currentBlock.removeInstruction(position);
+    }
     if (!currentBlock.getInstructions().isEmpty()) {
       throw new CompilationError("Invalid MoveException instruction encountered. "
           + "The MoveException instruction is not the first instruction in the block in "
@@ -1621,8 +1664,7 @@
 
   // Private instruction helpers.
   private void addInstruction(Instruction ir) {
-    attachLocalChanges(ir);
-    flushCurrentDebugPosition(ir);
+    attachLocalValues(ir);
     currentBlock.add(ir);
     if (ir.instructionTypeCanThrow()) {
       assert source.verifyCurrentInstructionCanThrow();
@@ -1660,8 +1702,10 @@
     }
   }
 
-  private void attachLocalChanges(Instruction ir) {
+  private void attachLocalValues(Instruction ir) {
     if (!options.debug) {
+      assert previousLocalValue == null;
+      assert debugLocalReads.isEmpty();
       return;
     }
     // Add a use if this instruction is overwriting a previous value of the same local.
@@ -1669,24 +1713,12 @@
       assert ir.outValue() != null;
       ir.addDebugValue(previousLocalValue);
     }
+    // Add reads of locals if any are pending.
+    for (Value value : debugLocalReads) {
+      ir.addDebugValue(value);
+    }
     previousLocalValue = null;
-    if (debugLocalStarts.isEmpty() && debugLocalReads.isEmpty() && debugLocalEnds.isEmpty()) {
-      return;
-    }
-    for (Value debugLocalStart : debugLocalStarts) {
-      ir.addDebugValue(debugLocalStart);
-      debugLocalStart.addDebugLocalStart(ir);
-    }
-    for (Value debugLocalRead : debugLocalReads) {
-      ir.addDebugValue(debugLocalRead);
-    }
-    for (Value debugLocalEnd : debugLocalEnds) {
-      ir.addDebugValue(debugLocalEnd);
-      debugLocalEnd.addDebugLocalEnd(ir);
-    }
-    debugLocalStarts.clear();
     debugLocalReads.clear();
-    debugLocalEnds.clear();
   }
 
   // Package (ie, SourceCode accessed) helpers.
@@ -1787,7 +1819,6 @@
     // insert reads before closing the block. It is unclear if we can rely on a local-end to ensure
     // liveness in all blocks where the local should be live.
     assert currentBlock != null;
-    assert currentDebugPosition == null;
     currentBlock.close(this);
     setCurrentBlock(null);
     throwingInstructionInCurrentBlock = false;
@@ -2061,30 +2092,6 @@
     code.blocks = tracedBlocks;
   }
 
-  // Debug info helpers.
-
-  public void updateCurrentDebugPosition(int line, DexString file) {
-    // Stack-trace support requires position information in both debug and release mode.
-    flushCurrentDebugPosition(null);
-    currentDebugPosition = new DebugPosition(line, file);
-    attachLocalChanges(currentDebugPosition);
-  }
-
-  private void flushCurrentDebugPosition(Instruction instruction) {
-    if (currentDebugPosition != null) {
-      DebugPosition position = currentDebugPosition;
-      currentDebugPosition = null;
-      if (instruction != null && instruction.isMoveException()) {
-        // Set the position on the move-exception instruction.
-        // ART/DX associates the move-exception pc with the catch-declaration line.
-        // See debug.ExceptionTest.testStepOnCatch().
-        instruction.asMoveException().setPosition(position);
-      } else {
-        addInstruction(position);
-      }
-    }
-  }
-
   // Other stuff.
 
   boolean isIntegerType(NumericType type) {
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 569e554..e44ee80 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
@@ -257,11 +257,6 @@
   }
 
   @Override
-  public boolean needsPrelude() {
-    return isSynchronized() || actualArgumentCount() > 0 || !node.localVariables.isEmpty();
-  }
-
-  @Override
   public void buildPrelude(IRBuilder builder) {
     // Record types for arguments.
     Int2ReferenceMap<MoveType> argumentLocals = recordArgumentTypes();
@@ -418,26 +413,11 @@
 
         AbstractInsnNode insn = getInstruction(i);
         updateState(insn);
-
-        if (!isControlFlowInstruction(insn)) {
-          updateStateForLocalVariableEnd(insn);
-        }
       }
     }
     state.restoreState(0);
   }
 
-  private void updateStateForLocalVariableEnd(AbstractInsnNode insn) {
-    assert !isControlFlowInstruction(insn);
-    if (!(insn.getNext() instanceof LabelNode)) {
-      return;
-    }
-    // If the label is the end of any local-variable scopes end the locals.
-    LabelNode label = (LabelNode) insn.getNext();
-    List<Local> locals = state.getLocalsToClose(label);
-    state.closeLocals(locals);
-  }
-
   @Override
   public void buildPostlude(IRBuilder builder) {
     if (isSynchronized()) {
@@ -461,11 +441,14 @@
   }
 
   @Override
-  public void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex) {
-  }
-
-  @Override
-  public void closedCurrentBlock() {
+  public void closingCurrentBlockWithFallthrough(
+      int fallthroughInstructionIndex, IRBuilder builder) {
+    AbstractInsnNode insn = node.instructions.get(fallthroughInstructionIndex);
+    if (insn instanceof LabelNode) {
+      for (Local local : state.getLocalsToClose((LabelNode) insn)) {
+        builder.addDebugLocalEnd(local.slot.register, local.info);
+      }
+    }
   }
 
   @Override
@@ -488,12 +471,6 @@
       preInstructionState = state.toString();
     }
 
-    // Process local-variable end scopes if this instruction is not a control-flow instruction.
-    // For control-flow instructions, processing takes place before closing their respective blocks.
-    if (!isControlFlowInstruction(insn)) {
-      processLocalVariableEnd(insn, builder);
-    }
-
     build(insn, builder);
 
     if (Log.ENABLED && !(insn instanceof LineNumberNode)) {
@@ -1727,6 +1704,9 @@
   }
 
   private void updateState(LabelNode insn) {
+    // Close scope of locals ending at this point.
+    List<Local> locals = state.getLocalsToClose(insn);
+    state.closeLocals(locals);
     // Open the scope of locals starting at this point.
     if (insn != initialLabel) {
       state.openLocals(insn);
@@ -1834,20 +1814,6 @@
     }
   }
 
-  private void processLocalVariableEnd(AbstractInsnNode insn, IRBuilder builder) {
-    assert !isControlFlowInstruction(insn);
-    if (!(insn.getNext() instanceof LabelNode)) {
-      return;
-    }
-    // If the label is the end of any local-variable scopes end the locals.
-    LabelNode label = (LabelNode) insn.getNext();
-    List<Local> locals = state.getLocalsToClose(label);
-    for (Local local : locals) {
-      builder.addDebugLocalEnd(local.slot.register, local.info);
-    }
-    state.closeLocals(locals);
-  }
-
   private void processLocalVariablesAtControlEdge(AbstractInsnNode insn, IRBuilder builder) {
     assert isControlFlowInstruction(insn) && !isReturn(insn);
     int offset = getOffset(insn);
@@ -2743,6 +2709,13 @@
   }
 
   private void build(LabelNode insn, IRBuilder builder) {
+    // Close locals starting at this point.
+    List<Local> locals = state.getLocalsToClose(insn);
+    for (Local local : locals) {
+      builder.addDebugLocalEnd(local.slot.register, local.info);
+    }
+    state.closeLocals(locals);
+
     // Open the scope of locals starting at this point.
     if (insn != initialLabel) {
       for (Local local : state.openLocals(insn)) {
@@ -2846,7 +2819,7 @@
   }
 
   private void build(LineNumberNode insn, IRBuilder builder) {
-    builder.updateCurrentDebugPosition(insn.line, null);
+    builder.addDebugPosition(insn.line, null);
   }
 
   @Override
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 c6b83a2..828ff10 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
@@ -294,7 +294,9 @@
       int register = getLocalRegister(node.index, type);
       Local local = getLocalForRegister(register);
       assert local != null;
-      locals.add(local);
+      if (local.info != null) {
+        locals.add(local);
+      }
     }
     // Sort to ensure deterministic instruction ordering (correctness is unaffected).
     locals.sort(Comparator.comparingInt(local -> local.slot.register));
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
index 16e124a..3ed90fb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/SourceCode.java
@@ -22,10 +22,6 @@
   int instructionIndex(int instructionOffset);
   int instructionOffset(int instructionIndex);
 
-  // True if the method needs a special entry block (prelude) that is not a targetable block.
-  // This is the case for methods with arguments or that need additional synchronization support.
-  boolean needsPrelude();
-
   DebugLocalInfo getCurrentLocal(int register);
 
   DebugPosition getDebugPositionAtOffset(int offset);
@@ -41,8 +37,7 @@
    */
   int traceInstruction(int instructionIndex, IRBuilder builder);
 
-  void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex);
-  void closedCurrentBlock();
+  void closingCurrentBlockWithFallthrough(int fallthroughInstructionIndex, IRBuilder builder);
 
   // Setup and release resources used temporarily during trace/build.
   void setUp();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 20f272a..fbde02b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -91,7 +91,6 @@
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
-import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
@@ -1088,7 +1087,7 @@
             user -> user.isCheckCast()
                 && user.asCheckCast().getType().isSubtypeOf(checkCast.getType(), appInfo))) {
           checkCast.outValue().replaceUsers(checkCast.inValues().get(0));
-          it.remove();
+          it.removeOrReplaceByNop();
         }
       }
     }
@@ -1537,7 +1536,7 @@
 
     for (int i = 0; i < dominatorTree.getSortedBlocks().length; i++) {
       BasicBlock block = dominatorTree.getSortedBlocks()[i];
-      Iterator<Instruction> iterator = block.iterator();
+      InstructionListIterator iterator = block.listIterator();
       while (iterator.hasNext()) {
         Instruction instruction = iterator.next();
         if (instruction.isBinop()
@@ -1552,7 +1551,7 @@
                   shareCatchHandlers(instruction, candidate.definition)) {
                 instruction.outValue().replaceUsers(candidate);
                 eliminated = true;
-                iterator.remove();
+                iterator.removeOrReplaceByNop();
                 break;  // Don't try any more candidates.
               }
             }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
index 50f224c..ebf69b5 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DeadCodeRemover.java
@@ -120,7 +120,7 @@
       // All users will be removed for this instruction. Eagerly clear them so further inspection
       // of this instruction during dead code elimination will terminate here.
       outValue.clearUsers();
-      iterator.remove();
+      iterator.removeOrReplaceByNop();
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index ade7537..5bf6e8b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -788,11 +788,6 @@
     }
 
     @Override
-    public boolean needsPrelude() {
-      return outline.argumentCount() > 0;
-    }
-
-    @Override
     public int instructionCount() {
       return outline.templateInstructions.size() + 1;
     }
@@ -819,11 +814,8 @@
     }
 
     @Override
-    public void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex) {
-    }
-
-    @Override
-    public void closedCurrentBlock() {
+    public void closingCurrentBlockWithFallthrough(
+        int fallthroughInstructionIndex, IRBuilder builder) {
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
index 391b479..c8fc818 100644
--- a/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/synthetic/SingleBlockSourceCode.java
@@ -100,11 +100,6 @@
   protected abstract void prepareInstructions();
 
   @Override
-  public final boolean needsPrelude() {
-    return receiver != null || paramRegisters.length > 0;
-  }
-
-  @Override
   public final int instructionCount() {
     return constructors.size();
   }
@@ -130,11 +125,8 @@
   }
 
   @Override
-  public final void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex) {
-  }
-
-  @Override
-  public final void closedCurrentBlock() {
+  public final void closingCurrentBlockWithFallthrough(
+      int fallthroughInstructionIndex, IRBuilder builder) {
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 54ac455..91e0047 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -12,8 +12,10 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.OffOrAuto;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import it.unimi.dsi.fastutil.longs.LongArrayList;
 import it.unimi.dsi.fastutil.longs.LongList;
 import java.io.File;
@@ -269,6 +271,12 @@
     return step(StepKind.INTO, stepFilter);
   }
 
+  protected static List<Variable> getVisibleKotlinInlineVariables(
+      JUnit3Wrapper.DebuggeeState debuggeeState) {
+    return debuggeeState.getVisibleVariables().stream()
+        .filter(v -> v.getName().matches("^\\$i\\$f\\$.*$")).collect(Collectors.toList());
+  }
+
   public enum StepKind {
     INTO(StepDepth.INTO),
     OVER(StepDepth.OVER),
@@ -317,10 +325,7 @@
   }
 
   protected final JUnit3Wrapper.Command checkNoLocal(String localName) {
-    return inspect(t -> {
-      List<String> localNames = t.getLocalNames();
-      Assert.assertFalse("Unexpected local: " + localName, localNames.contains(localName));
-    });
+    return inspect(t -> t.checkNoLocal(localName));
   }
 
   protected final JUnit3Wrapper.Command checkNoLocal() {
@@ -508,8 +513,9 @@
               .getMethodSignature(debuggeeState.getLocation().classID,
                   debuggeeState.getLocation().methodID);
           System.out.println(String
-              .format("Suspended in %s#%s%s@0x%x", classSig, methodName, methodSig,
-                  Long.valueOf(debuggeeState.getLocation().index)));
+              .format("Suspended in %s#%s%s@0x%x (line=%d)", classSig, methodName, methodSig,
+                  Long.valueOf(debuggeeState.getLocation().index),
+                  Integer.valueOf(debuggeeState.getLineNumber())));
         }
 
         // Handle event.
@@ -553,6 +559,16 @@
       return new ArtTestOptions(debuggeePath);
     }
 
+    public void enqueueCommandFirst(Command command) {
+      commandsQueue.addFirst(command);
+    }
+
+    public void enqueueCommandsFirst(List<Command> commands) {
+      for (int i = commands.size() - 1; i >= 0; --i) {
+        enqueueCommandFirst(commands.get(i));
+      }
+    }
+
     //
     // Inspection
     //
@@ -570,6 +586,8 @@
 
       // Locals
 
+      List<Variable> getVisibleVariables();
+
       /**
        * Returns the names of all local variables visible at the current location
        */
@@ -579,6 +597,7 @@
        * Returns the values of all locals visible at the current location.
        */
       Map<String, Value> getLocalValues();
+      void checkNoLocal(String localName);
       void checkLocal(String localName);
       void checkLocal(String localName, Value expectedValue);
     }
@@ -622,7 +641,7 @@
             // Code indices are in ascending order.
             assert currentLineCodeIndex >= startCodeIndex;
             assert currentLineCodeIndex <= endCodeIndex;
-            assert currentLineCodeIndex > previousLineCodeIndex;
+            assert currentLineCodeIndex >= previousLineCodeIndex;
             previousLineCodeIndex = currentLineCodeIndex;
 
             if (location.index >= currentLineCodeIndex) {
@@ -650,13 +669,19 @@
           }
         }
 
-        public List<String> getLocalNames() {
-          Location location = getLocation();
-          return JUnit3Wrapper.getVariablesAt(mirror, location).stream()
-              .map(v -> v.getName())
+        @Override
+        public List<Variable> getVisibleVariables() {
+          // Get variable table and keep only variables visible at this location.
+          Location frameLocation = getLocation();
+          return getVariables(getMirror(), frameLocation.classID, frameLocation.methodID).stream()
+              .filter(v -> inScope(frameLocation.index, v))
               .collect(Collectors.toList());
         }
 
+        public List<String> getLocalNames() {
+          return getVisibleVariables().stream().map(v -> v.getName()).collect(Collectors.toList());
+        }
+
         @Override
         public Map<String, Value> getLocalValues() {
           return JUnit3Wrapper.getVariablesAt(mirror, location).stream()
@@ -685,6 +710,13 @@
               "line " + getLineNumber() + ": Expected local '" + localName + "' not present");
         }
 
+        @Override
+        public void checkNoLocal(String localName) {
+          Optional<Variable> localVar = JUnit3Wrapper
+              .getVariableAt(mirror, getLocation(), localName);
+          Assert.assertFalse("Unexpected local: " + localName, localVar.isPresent());
+        }
+
         public void checkLocal(String localName) {
           Optional<Variable> localVar = JUnit3Wrapper
               .getVariableAt(mirror, getLocation(), localName);
@@ -778,6 +810,10 @@
         return threadId;
       }
 
+      public int getFrameDepth() {
+        return frames.size();
+      }
+
       public FrameInspector getFrame(int index) {
         return frames.get(index);
       }
@@ -797,6 +833,11 @@
       }
 
       @Override
+      public void checkNoLocal(String localName) {
+        getTopFrame().checkNoLocal(localName);
+      }
+
+      @Override
       public void checkLocal(String localName) {
         getTopFrame().checkLocal(localName);
       }
@@ -846,6 +887,11 @@
         return getTopFrame().getMethodSignature();
       }
 
+      @Override
+      public List<Variable> getVisibleVariables() {
+        return getTopFrame().getVisibleVariables();
+      }
+
       public Value getStaticField(String className, String fieldName, String fieldSignature) {
         String classSignature = DescriptorUtils.javaTypeToDescriptor(className);
         byte typeTag = TypeTag.CLASS;
@@ -918,19 +964,19 @@
       }
     }
 
-    private static boolean inScope(long index, Variable var) {
-      long varStart = var.getCodeIndex();
-      long varEnd = varStart + var.getLength();
-      return index >= varStart && index < varEnd;
-    }
-
-    private static Optional<Variable> getVariableAt(VmMirror mirror, Location location,
+    public static Optional<Variable> getVariableAt(VmMirror mirror, Location location,
         String localName) {
       return getVariablesAt(mirror, location).stream()
           .filter(v -> localName.equals(v.getName()))
           .findFirst();
     }
 
+    protected static boolean inScope(long index, Variable var) {
+      long varStart = var.getCodeIndex();
+      long varEnd = varStart + var.getLength();
+      return index >= varStart && index < varEnd;
+    }
+
     private static List<Variable> getVariablesAt(VmMirror mirror, Location location) {
       // Get variable table and keep only variables visible at this location.
       return getVariables(mirror, location.classID, location.methodID).stream()
@@ -1057,10 +1103,9 @@
               wrapper.getMirror().clearEvent(JDWPConstants.EventKind.CLASS_PREPARE,
                   classPrepareRequestId);
 
-              // Breakpoint then resume. Note: we add them at the beginning of the queue (to be the
-              // next commands to be processed), thus they need to be pushed in reverse order.
-              wrapper.commandsQueue.addFirst(new JUnit3Wrapper.Command.RunCommand());
-              wrapper.commandsQueue.addFirst(BreakpointCommand.this);
+              // Breakpoint then resume.
+              wrapper.enqueueCommandsFirst(
+                  Arrays.asList(BreakpointCommand.this, new JUnit3Wrapper.Command.RunCommand()));
 
               // Set wrapper ready to process next command.
               wrapper.setState(State.ProcessCommand);
@@ -1260,7 +1305,7 @@
         }
         if (repeatStep) {
           // In order to repeat the step now, we need to add it at the beginning of the queue.
-          testBase.commandsQueue.addFirst(stepCommand);
+          testBase.enqueueCommandFirst(stepCommand);
         }
         super.handle(testBase);
       }
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
new file mode 100644
index 0000000..a0a9c3f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/KotlinDebugTestBase.java
@@ -0,0 +1,83 @@
+// Copyright (c) 2017, 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.debug;
+
+import java.util.Arrays;
+import java.util.List;
+import org.apache.harmony.jpda.tests.framework.jdwp.Frame.Variable;
+import org.apache.harmony.jpda.tests.framework.jdwp.Location;
+
+/**
+ * A specialization for Kotlin-based tests which provides extra commands.
+ */
+public abstract class KotlinDebugTestBase extends DebugTestBase {
+
+  protected final JUnit3Wrapper.Command kotlinStepOver() {
+    return testBaseBeforeStep -> {
+      final JUnit3Wrapper.DebuggeeState debuggeeStateBeforeStep = testBaseBeforeStep
+          .getDebuggeeState();
+      final int frameDepthBeforeStep = debuggeeStateBeforeStep.getFrameDepth();
+      final Location locationBeforeStep = debuggeeStateBeforeStep.getLocation();
+      final List<Variable> kotlinLvsBeforeStep = getVisibleKotlinInlineVariables(
+          debuggeeStateBeforeStep);
+
+      // This is the command that will be executed after the initial (normal) step over. If we
+      // reach an inlined location, this command will step until reaching a non-inlined location.
+      JUnit3Wrapper.Command commandAfterStep = testBaseAfterStep -> {
+        // Get the new debuggee state (previous one is stale).
+        JUnit3Wrapper.DebuggeeState debuggeeStateAfterStep = testBaseBeforeStep.getDebuggeeState();
+
+        // Are we in the same frame ?
+        final int frameDepthAfterStep = debuggeeStateAfterStep.getFrameDepth();
+        final Location locationAfterStep = debuggeeStateAfterStep.getLocation();
+        if (frameDepthBeforeStep == frameDepthAfterStep
+            && locationBeforeStep.classID == locationAfterStep.classID
+            && locationBeforeStep.methodID == locationAfterStep.methodID) {
+          // We remain in the same method. Do we step into an inlined section ?
+          List<Variable> kotlinLvsAfterStep = getVisibleKotlinInlineVariables(
+              debuggeeStateAfterStep);
+          if (kotlinLvsBeforeStep.isEmpty() && !kotlinLvsAfterStep.isEmpty()) {
+            assert kotlinLvsAfterStep.size() == 1;
+
+            // We're located in an inlined section. Instead of doing a classic step out, we must
+            // jump out of the inlined section.
+            Variable inlinedSectionLv = kotlinLvsAfterStep.get(0);
+            testBaseAfterStep.enqueueCommandFirst(stepUntilOutOfInlineScope(inlinedSectionLv));
+          }
+        }
+      };
+
+      // Step over then check whether we need to continue stepping.
+      testBaseBeforeStep.enqueueCommandsFirst(Arrays.asList(stepOver(), commandAfterStep));
+    };
+  }
+
+  protected final JUnit3Wrapper.Command kotlinStepOut() {
+    return wrapper -> {
+      final List<Variable> kotlinLvsBeforeStep = getVisibleKotlinInlineVariables(
+          wrapper.getDebuggeeState());
+
+      JUnit3Wrapper.Command nextCommand;
+      if (!kotlinLvsBeforeStep.isEmpty()) {
+        // We are in an inline section. We need to step until being out of inline scope.
+        assert kotlinLvsBeforeStep.size() == 1;
+        final Variable inlinedSectionLv = kotlinLvsBeforeStep.get(0);
+        nextCommand = stepUntilOutOfInlineScope(inlinedSectionLv);
+      } else {
+        nextCommand = stepOut();
+      }
+      wrapper.enqueueCommandFirst(nextCommand);
+    };
+  }
+
+  private JUnit3Wrapper.Command stepUntilOutOfInlineScope(Variable inlineScopeLv) {
+    return stepUntil(StepKind.OVER, StepLevel.LINE, debuggeeState -> {
+      boolean inInlineScope = JUnit3Wrapper
+          .inScope(debuggeeState.getLocation().index, inlineScopeLv);
+      return !inInlineScope;
+    });
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
index e62a17b..8e9d145 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinInlineTest.java
@@ -10,9 +10,9 @@
 import org.junit.Ignore;
 import org.junit.Test;
 
-public class KotlinInlineTest extends DebugTestBase {
+// TODO check double-depth inline (an inline in another inline)
+public class KotlinInlineTest extends KotlinDebugTestBase {
 
-  @Ignore("Requires kotlin-specific stepping behavior")
   @Test
   public void testStepOverInline() throws Throwable {
     String methodName = "singleInline";
@@ -26,7 +26,6 @@
           assertEquals(41, s.getLineNumber());
           s.checkLocal("this");
         }),
-        // TODO(shertz) stepping over must take kotlin inline range into account.
         stepOver(),
         inspect(s -> {
           assertEquals("KotlinInline", s.getClassName());
@@ -35,7 +34,7 @@
           assertEquals(42, s.getLineNumber());
           s.checkLocal("this");
         }),
-        stepOver(),
+        kotlinStepOver(),
         inspect(s -> {
           assertEquals("KotlinInline", s.getClassName());
           assertEquals(methodName, s.getMethodName());
@@ -46,7 +45,6 @@
         run());
   }
 
-  @Ignore("Requires kotlin-specific stepping behavior")
   @Test
   public void testStepIntoInline() throws Throwable {
     String methodName = "singleInline";
@@ -60,7 +58,14 @@
           assertEquals(41, s.getLineNumber());
           s.checkLocal("this");
         }),
-        // TODO(shertz) stepping over must take kotlin inline range into account.
+        stepOver(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(42, s.getLineNumber());
+          s.checkLocal("this");
+        }),
         stepInto(),
         inspect(s -> {
           assertEquals("KotlinInline", s.getClassName());
@@ -76,7 +81,6 @@
         run());
   }
 
-  @Ignore("Requires kotlin-specific stepping behavior")
   @Test
   public void testStepOutInline() throws Throwable {
     String methodName = "singleInline";
@@ -90,13 +94,20 @@
           assertEquals(41, s.getLineNumber());
           s.checkLocal("this");
         }),
-        // TODO(shertz) stepping out must take kotlin inline range into account.
+        stepOver(),
+        inspect(s -> {
+          assertEquals("KotlinInline", s.getClassName());
+          assertEquals(methodName, s.getMethodName());
+          assertEquals("KotlinInline.kt", s.getSourceFile());
+          assertEquals(42, s.getLineNumber());
+          s.checkLocal("this");
+        }),
         stepInto(),
         inspect(s -> {
           assertEquals("KotlinInline", s.getClassName());
           assertEquals(methodName, s.getMethodName());
         }),
-        stepOut(),
+        kotlinStepOut(),
         inspect(s -> {
           assertEquals("KotlinInline", s.getClassName());
           assertEquals(methodName, s.getMethodName());
diff --git a/src/test/java/com/android/tools/r8/debug/KotlinTest.java b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
index a6b57b8..2baa426 100644
--- a/src/test/java/com/android/tools/r8/debug/KotlinTest.java
+++ b/src/test/java/com/android/tools/r8/debug/KotlinTest.java
@@ -8,7 +8,7 @@
 import org.apache.harmony.jpda.tests.framework.jdwp.Value;
 import org.junit.Test;
 
-public class KotlinTest extends DebugTestBase {
+public class KotlinTest extends KotlinDebugTestBase {
 
   // TODO(shertz) simplify test
   // TODO(shertz) add more variables ?
diff --git a/src/test/java/com/android/tools/r8/debug/LocalsTest.java b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
index 35aeab2..9b0a888 100644
--- a/src/test/java/com/android/tools/r8/debug/LocalsTest.java
+++ b/src/test/java/com/android/tools/r8/debug/LocalsTest.java
@@ -20,50 +20,6 @@
   public static final String SOURCE_FILE = "Locals.java";
 
   @Test
-  // TODO(b/65402086) Remove @ignore when debug behavior will be fixed.
-  @Ignore
-  public void testLocalConstantBis() throws Throwable {
-    final String className = "Locals";
-    final String methodName = "localConstantBis";
-    runDebugTest(className,
-        breakpoint(className, methodName),
-        run(),
-        checkLine(SOURCE_FILE, 332),
-        checkNoLocal("result"),
-        stepOver(),
-        checkLine(SOURCE_FILE, 333),
-        checkLocal("result", Value.createInt(0)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 334),
-        checkLocal("result", Value.createInt(0)),
-        stepOver(),
-        checkLine(SOURCE_FILE, 338),
-        checkLocal("result", Value.createInt(1)),
-        run());
-  }
-
-  @Test
-  public void testLocalConstant() throws Throwable {
-    final String className = "Locals";
-    final String methodName = "localConstant";
-    runDebugTest(className,
-        breakpoint(className, methodName),
-        run(),
-        checkLine(SOURCE_FILE, 322),
-        checkNoLocal("result1"),
-        checkNoLocal("result2"),
-        stepOver(),
-        checkLine(SOURCE_FILE, 323),
-        checkNoLocal("result1"),
-        checkNoLocal("result2"),
-        stepOver(),
-        checkLine(SOURCE_FILE, 324),
-        checkLocal("result1"),
-        checkNoLocal("result2"),
-        run());
-  }
-
-  @Test
   public void testNoLocal() throws Throwable {
     final String className = "Locals";
     final String methodName = "noLocals";
@@ -610,4 +566,46 @@
         checkLine(SOURCE_FILE, 319),
         run());
   }
+
+  @Test
+  public void testLocalConstantBis() throws Throwable {
+    final String className = "Locals";
+    final String methodName = "localConstantBis";
+    runDebugTest(className,
+        breakpoint(className, methodName),
+        run(),
+        checkLine(SOURCE_FILE, 332),
+        checkNoLocal("result"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 333),
+        checkLocal("result", Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 334),
+        checkLocal("result", Value.createInt(0)),
+        stepOver(),
+        checkLine(SOURCE_FILE, 338),
+        checkLocal("result", Value.createInt(1)),
+        run());
+  }
+
+  @Test
+  public void testLocalConstant() throws Throwable {
+    final String className = "Locals";
+    final String methodName = "localConstant";
+    runDebugTest(className,
+        breakpoint(className, methodName),
+        run(),
+        checkLine(SOURCE_FILE, 322),
+        checkNoLocal("result1"),
+        checkNoLocal("result2"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 323),
+        checkNoLocal("result1"),
+        checkNoLocal("result2"),
+        stepOver(),
+        checkLine(SOURCE_FILE, 324),
+        checkLocal("result1"),
+        checkNoLocal("result2"),
+        run());
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
index 835474b..8c93a8a 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
 import com.google.common.collect.ImmutableList;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class DebugLocalTests extends JasminTestBase {
@@ -117,7 +116,6 @@
   }
 
   @Test
-  @Ignore("b/65430598")
   public void testNoLocalInfoOnStack() throws Exception {
     JasminBuilder builder = new JasminBuilder();
     JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
@@ -489,11 +487,11 @@
         "L3_ORIGINAL:",  // This is where javac normally ends t.
         ".line 7",
         "  iload 0",
-        "L3:",           // Moved L3 here to end t on the switch instruction.
         "lookupswitch",
         "  0: L4",
         "  100: L5",
         "  default: L6",
+        "L3:",           // Moved L3 here to end t on the switch instruction.
         "L4:",
         ".line 9",
         "  iconst_0",
@@ -595,7 +593,6 @@
         "L3_ORIGINAL:",  // This is where javac normally ends t.
         ".line 7",
         "  iload 0",
-        "L3:",           // Moved L3 here to end t on the switch instruction.
         "lookupswitch",
         "  0: L4",
         "  1: L5",
@@ -604,6 +601,7 @@
         "  101: L8",
         "  102: L9",
         "  default: L10",
+        "L3:",           // Moved L3 here to end t on the switch instruction.
         "L4:",
         ".line 9",
         "  iconst_0",
@@ -663,4 +661,64 @@
     info.checkLineHasExactLocals(11, "x", "int");
     info.checkLineHasExactLocals(13, "x", "int");
   }
+
+  @Test
+  public void testLocalEndAfterLine() throws Exception {
+    JasminBuilder builder = new JasminBuilder();
+    JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+    MethodSignature foo = clazz.addStaticMethod("foo", ImmutableList.of("I"), "I",
+        ".limit stack 2",
+        ".limit locals 2",
+        ".var 0 is x I from L0 to L4",
+        ".var 1 is t I from L1 to L3",
+        "L0:",
+        ".line 1",
+        "  iload 0",
+        "  iconst_1",
+        "  iadd",
+        "  istore 1",
+        "L1:",
+        ".line 2",
+        "  iload 1",
+        "  istore 0",
+        "L2:",
+        ".line 3",
+        "  iload 0",
+        "  iload 0",
+        "  iadd",
+        "  istore 0",
+        ".line 4",
+        "  iload 0",
+        "L3:", // This should not end t on line 4!
+        ".line 5",
+        "  ireturn",
+        "L4:"
+    );
+
+    clazz.addMainMethod(
+        ".limit stack 2",
+        ".limit locals 1",
+        "  getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "  ldc 1",
+        "  invokestatic Test/foo(I)I",
+        "  invokevirtual java/io/PrintStream/print(I)V",
+        "  return");
+
+    String expected = "4";
+    String javaResult = runOnJava(builder, clazz.name);
+    assertEquals(expected, javaResult);
+
+    AndroidApp jasminApp = builder.build();
+    AndroidApp d8App = ToolHelper.runD8(jasminApp);
+    String artResult = runOnArt(d8App, clazz.name);
+    assertEquals(expected, artResult);
+
+    DebugInfoInspector info = new DebugInfoInspector(d8App, clazz.name, foo);
+    info.checkStartLine(1);
+    info.checkLineHasExactLocals(1, "x", "int");
+    info.checkLineHasExactLocals(2, "x", "int", "t", "int");
+    info.checkLineHasExactLocals(3, "x", "int", "t", "int");
+    info.checkLineHasExactLocals(4, "x", "int", "t", "int");
+    info.checkLineHasExactLocals(5, "x", "int");
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 4122416..a316969 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -622,11 +622,6 @@
     }
 
     @Override
-    public boolean needsPrelude() {
-      return false;
-    }
-
-    @Override
     public DebugLocalInfo getCurrentLocal(int register) {
       return null;
     }
@@ -637,16 +632,12 @@
     }
 
     @Override
-    public void closedCurrentBlockWithFallthrough(int fallthroughInstructionIndex) {
+    public void closingCurrentBlockWithFallthrough(
+        int fallthroughInstructionIndex, IRBuilder builder) {
       throw new Unreachable();
     }
 
     @Override
-    public void closedCurrentBlock() {
-      // Intentionally empty.
-    }
-
-    @Override
     public void setUp() {
       // Intentionally empty.
     }
diff --git a/tools/archive.py b/tools/archive.py
index ee1053d..3d4523b 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -6,6 +6,7 @@
 import d8
 import os
 import r8
+import subprocess
 import sys
 import utils
 
@@ -21,27 +22,58 @@
         'Version mismatch: \n%s\n%s' % (d8_version, r8_version))
   return d8_version.split()[1]
 
-def GetStorageDestination(storage_prefix, version, file_name):
-  return '%s%s/raw/%s/%s' % (storage_prefix, ARCHIVE_BUCKET, version, file_name)
+def GetGitBranches():
+  return subprocess.check_output(['git', 'show', '-s', '--pretty=%d', 'HEAD'])
 
-def GetUploadDestination(version, file_name):
-  return GetStorageDestination('gs://', version, file_name)
+def GetGitHash():
+  return subprocess.check_output(['git', 'rev-parse', 'HEAD']).strip()
 
-def GetUrl(version, file_name):
+def IsMaster(version):
+  branches = subprocess.check_output(['git', 'show', '-s', '--pretty=%d',
+                                      'HEAD'])
+  if not version.endswith('-dev'):
+    # Sanity check, we don't want to archive on top of release builds EVER
+    # Note that even though we branch, we never push the bots to build the same
+    # commit as master on a branch since we always change the version to
+    # not have dev (or we crash here :-)).
+    if 'origin/master' in branches:
+      raise Exception('We are seeing origin/master in a commit that '
+                      'don\'t have -dev in version')
+    return False;
+  if not 'origin/master' in branches:
+      raise Exception('We are not seeing origin/master '
+                      'in a commit that have -dev in version')
+  return True;
+
+def GetStorageDestination(storage_prefix, version, file_name, is_master):
+  # We archive master commits under raw/master instead of directly under raw
+  archive_dir = 'raw/master' if is_master else 'raw'
+  return '%s%s/%s/%s/%s' % (storage_prefix, ARCHIVE_BUCKET, archive_dir,
+                            version, file_name)
+
+def GetUploadDestination(version, file_name, is_master):
+  return GetStorageDestination('gs://', version, file_name, is_master)
+
+def GetUrl(version, file_name, is_master):
   return GetStorageDestination('http://storage.googleapis.com/',
-                               version,
-                               file_name)
+                               version, file_name, is_master)
 
 def Main():
   if not 'BUILDBOT_BUILDERNAME' in os.environ:
     raise Exception('You are not a bot, don\'t archive builds')
   version = GetVersion()
+  is_master = IsMaster(version)
+  if is_master:
+    # On master we use the git hash to archive with
+    print 'On master, using git hash for archiving'
+    version = GetGitHash()
+
   for jar in [utils.D8_JAR, utils.R8_JAR]:
     file_name = os.path.basename(jar)
-    destination = GetUploadDestination(version, file_name)
+    destination = GetUploadDestination(version, file_name, is_master)
     print('Uploading %s to %s' % (jar, destination))
     utils.upload_file_to_cloud_storage(jar, destination)
-    print('File available at: %s' % GetUrl(version, file_name))
+    print('File available at: %s' % GetUrl(version, file_name, is_master))
 
 if __name__ == '__main__':
   sys.exit(Main())
diff --git a/tools/checkout_aosp.py b/tools/checkout_aosp.py
new file mode 100755
index 0000000..d7208ab
--- /dev/null
+++ b/tools/checkout_aosp.py
@@ -0,0 +1,55 @@
+#!/usr/bin/env python
+# Copyright (c) 2017, 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.
+
+from os.path import basename, join
+from shutil import copy2
+from subprocess import check_call
+import argparse
+import multiprocessing
+import os
+import sys
+
+import utils
+
+AOSP_MANIFEST_XML = join(utils.REPO_ROOT, 'third_party',
+  'aosp_manifest.xml')
+AOSP_MANIFEST_URL = 'https://android.googlesource.com/platform/manifest'
+
+J_DEFAULT = multiprocessing.cpu_count() - 2
+
+# Checkout AOSP source to the specified direcotry using the speficied manifest.
+def checkout_aosp(aosp_root, manifest_xml, concurrency):
+  manifests_dir = join(aosp_root, '.repo', 'manifests')
+  utils.makedirs_if_needed(manifests_dir)
+
+  copy2(manifest_xml, manifests_dir)
+  check_call(['repo', 'init', '-u', AOSP_MANIFEST_URL, '-m',
+    basename(manifest_xml), '--depth=1'], cwd = aosp_root)
+
+  check_call(['repo', 'sync', '-dq', '-j' + concurrency], cwd = aosp_root)
+
+def parse_arguments():
+  parser = argparse.ArgumentParser(
+      description = 'Checkout the AOSP source tree.')
+  parser.add_argument('--aosp-root',
+                      help='Root of the AOSP checkout. ' +
+                           'Defaults to current working directory.',
+                      default=os.getcwd())
+  parser.add_argument('--manifest',
+                      help='Manifest to use for the checkout. ' +
+                           'Defaults to ' + AOSP_MANIFEST_XML + '.',
+                      default=AOSP_MANIFEST_XML)
+  parser.add_argument('-j',
+                      help='Projects to fetch simultaneously. ' +
+                           'Defaults to ' + str(J_DEFAULT) + '.',
+                      default=str(J_DEFAULT))
+  return parser.parse_args()
+
+def Main():
+  args = parse_arguments()
+  checkout_aosp(args.aosp_root, args.manifest, args.j)
+
+if __name__ == '__main__':
+  sys.exit(Main())
diff --git a/tools/test_android_cts.py b/tools/test_android_cts.py
index 887e3ae..ccb762b 100755
--- a/tools/test_android_cts.py
+++ b/tools/test_android_cts.py
@@ -30,6 +30,7 @@
 import sys
 import time
 
+import checkout_aosp
 import gradle
 import utils
 
@@ -53,7 +54,8 @@
 CTS_TRADEFED = join(OUT_CTS,
   'host/linux-x86/cts/android-cts/tools/cts-tradefed')
 
-J_OPTION = '-j8'
+J_DEFAULT = '8'
+J_OPTION = '-j' + J_DEFAULT
 
 EXIT_FAILURE = 1
 
@@ -142,17 +144,6 @@
   if counter > 0:
     print('Removed {} dex files.'.format(counter))
 
-def checkout_aosp():
-  # checkout AOSP source
-  manifests_dir = join(AOSP_ROOT, '.repo', 'manifests')
-  utils.makedirs_if_needed(manifests_dir)
-
-  copy2(AOSP_MANIFEST_XML, manifests_dir)
-  check_call(['repo', 'init', '-u', AOSP_MANIFEST_URL, '-m',
-    'aosp_manifest.xml', '--depth=1'], cwd = AOSP_ROOT)
-
-  check_call(['repo', 'sync', '-dq', J_OPTION], cwd = AOSP_ROOT)
-
 def Main():
   args = parse_arguments()
 
@@ -179,7 +170,7 @@
 
   setup_and_clean(args.tool == 'd8', args.clean_dex)
 
-  checkout_aosp()
+  checkout_aosp.checkout_aosp(AOSP_ROOT, AOSP_MANIFEST_XML, J_DEFAULT)
 
   # activate OUT_CTS and build Android CTS
   # AOSP has no clean way to set the output directory.