Annotate all instructions with position information.

This addresses several outstanding issues: blocks can now be reordered without
issue, if positions are present all instructions will be annotated, in
particular move-exceptions for synchornized blocks. This change requires that
all IR tranformations properly transfer position information for each
instruction.

Bug: 65618023
Bug: 65567013
Bug: 65474153
Change-Id: I9ded6003c2b349e738f50e7be58f35536bc5b08b
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
index a5a63e3..c52d12c 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -5,11 +5,13 @@
 
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
+import com.android.tools.r8.graph.DexEncodedMethod.DebugPositionRange;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.DebugLocalsChange;
-import com.android.tools.r8.ir.code.DebugPosition;
+import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.MoveException;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.utils.InternalOptions;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
@@ -32,6 +34,7 @@
 
   private final DexEncodedMethod method;
   private final DexItemFactory factory;
+  private final InternalOptions options;
 
   // In order list of non-this argument locals.
   private ArrayList<DebugLocalInfo> arguments;
@@ -48,8 +51,7 @@
 
   // State of pc, line, file and locals in the emitted event stream.
   private int emittedPc = NO_PC_INFO;
-  private int emittedLine = NO_LINE_INFO;
-  private DexString emittedFile = null;
+  private Position emittedPosition = Position.none();
   private Int2ReferenceMap<DebugLocalInfo> emittedLocals;
 
   // Emitted events.
@@ -58,37 +60,65 @@
   // Initial known line for the method.
   private int startLine = NO_LINE_INFO;
 
-  public DexDebugEventBuilder(DexEncodedMethod method, DexItemFactory factory) {
-    this.method = method;
-    this.factory = factory;
+  // True if running in debug-mode with input code that contains line information, otherwise false.
+  private boolean hasDebugPositions;
+
+  private DexEncodedMethod.DebugPositionRangeList.Builder debugPositionListBuilder =
+      new DexEncodedMethod.DebugPositionRangeList.Builder();
+
+  public DexDebugEventBuilder(IRCode code, InternalOptions options) {
+    this.method = code.method;
+    this.factory = options.itemFactory;
+    this.options = options;
+    hasDebugPositions = code.hasDebugPositions;
   }
 
   /** Add events at pc for instruction. */
-  public void add(int pc, Instruction instruction) {
+  public void add(int pc, int postPc, Instruction instruction) {
+    boolean isBlockEntry = instruction.getBlock().entry() == instruction;
+    boolean isBlockExit = instruction.getBlock().exit() == instruction;
+
     // Initialize locals state on block entry.
-    if (instruction.getBlock().entry() == instruction) {
+    if (isBlockEntry) {
       updateBlockEntry(instruction);
     }
     assert pendingLocals != null;
 
-    if (instruction.isDebugPosition()) {
-      emitDebugPosition(pc, instruction.asDebugPosition());
-    } else if (instruction.isMoveException()) {
-      MoveException move = instruction.asMoveException();
-      if (move.getPosition() != null) {
-        emitDebugPosition(pc, move.getPosition());
-      }
-    } else if (instruction.isArgument()) {
+    Position position = instruction.getPosition();
+    boolean pcAdvancing = pc != postPc;
+
+    // In release mode we can only check that all throwing instructions have positions.
+    // See IRCode's isConsistentGraph and computeAllThrowingInstructionsHavePositions.
+
+    // In debug mode check that all non-nop instructions have positions.
+    assert startLine == NO_LINE_INFO || !hasDebugPositions || !pcAdvancing || position.isSome()
+        : "PC-advancing instruction " + instruction + " expected to have an associated position.";
+
+    // In any mode check that nop instructions have no position info.
+    assert pcAdvancing || position.isNone()
+        : "Nop instruction " + instruction + " must never have an associated position.";
+
+    if (instruction.isArgument()) {
       startArgument(instruction.asArgument());
     } else if (instruction.isDebugLocalsChange()) {
       updateLocals(instruction.asDebugLocalsChange());
-    } else if (instruction.getBlock().exit() == instruction) {
+    }
+
+    if (!position.isNone() && !position.equals(emittedPosition)) {
+      if (options.debug || instruction.instructionInstanceCanThrow()) {
+        emitDebugPosition(pc, position);
+      }
+    }
+
+    if (!isBlockExit && emittedPc != pc && pcAdvancing) {
+      // For non-exit / pc-advancing instructions emit any pending changes.
+      emitLocalChanges(pc);
+    }
+
+    if (isBlockExit) {
       // If this is the end of the block clear out the pending state.
       pendingLocals = null;
       pendingLocalChanges = false;
-    } else if (pc != emittedPc && !instruction.isDebugLocalRead()) {
-      // For non-exit / pc-advancing instructions emit any pending changes once possible.
-      emitLocalChanges(pc);
     }
   }
 
@@ -110,6 +140,10 @@
     return new DexDebugInfo(startLine, params, events.toArray(new DexDebugEvent[events.size()]));
   }
 
+  public List<DebugPositionRange> buildPositionRanges() {
+    return debugPositionListBuilder.build();
+  }
+
   private void updateBlockEntry(Instruction instruction) {
     assert pendingLocals == null;
     assert !pendingLocalChanges;
@@ -156,14 +190,7 @@
 
   private void updateLocals(DebugLocalsChange change) {
     pendingLocalChanges = true;
-    for (Entry<DebugLocalInfo> end : change.getEnding().int2ReferenceEntrySet()) {
-      assert pendingLocals.get(end.getIntKey()) == end.getValue();
-      pendingLocals.remove(end.getIntKey());
-    }
-    for (Entry<DebugLocalInfo> start : change.getStarting().int2ReferenceEntrySet()) {
-      assert !pendingLocals.containsKey(start.getIntKey());
-      pendingLocals.put(start.getIntKey(), start.getValue());
-    }
+    change.apply(pendingLocals);
   }
 
   private boolean localsChanged() {
@@ -174,32 +201,22 @@
     return pendingLocalChanges;
   }
 
-  private void emitDebugPosition(int pc, DebugPosition position) {
-    emitDebugPosition(pc, position.line, position.file);
-  }
-
-  private void emitDebugPosition(int pc, int line, DexString file) {
-    if (emittedPc == pc) {
-      // The pc is unchanged then this must be the same position as the previous and we do nothing.
-      // If this assert fails, we have likely forgotten to insert a "nop" instruction between two
-      // successive yet distinct debug positions.
-      assert emittedLine == line && emittedFile == file;
-      return;
-    }
-    boolean changedLocals = localsChanged();
-    if (!changedLocals && emittedLine == line && emittedFile == file) {
-      return;
-    }
+  private void emitDebugPosition(int pc, Position position) {
+    assert !position.equals(emittedPosition);
     if (startLine == NO_LINE_INFO) {
-      assert emittedLine == NO_LINE_INFO;
-      startLine = line;
-      emittedLine = line;
+      if (position.synthetic) {
+        // Ignore synthetic positions prior to any actual position.
+        return;
+      }
+      assert emittedPosition.isNone();
+      startLine = position.line;
+      emittedPosition = position;
     }
-    emitAdvancementEvents(emittedPc, emittedLine, emittedFile, pc, line, file, events, factory);
+    debugPositionListBuilder.add(position.line, position.line);
+    emitAdvancementEvents(emittedPc, emittedPosition, pc, position, events, factory);
     emittedPc = pc;
-    emittedLine = line;
-    emittedFile = file;
-    if (changedLocals) {
+    emittedPosition = position;
+    if (localsChanged()) {
       emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
       assert localsEqual(emittedLocals, pendingLocals);
     }
@@ -209,8 +226,7 @@
   private void emitLocalChanges(int pc) {
     // If pc advanced since the locals changed and locals indeed have changed, emit the changes.
     if (localsChanged()) {
-      emitAdvancementEvents(
-          emittedPc, emittedLine, emittedFile, pc, emittedLine, emittedFile, events, factory);
+      emitAdvancementEvents(emittedPc, emittedPosition, pc, emittedPosition, events, factory);
       emittedPc = pc;
       emitLocalChangeEvents(emittedLocals, pendingLocals, lastKnownLocals, events, factory);
       pendingLocalChanges = false;
@@ -220,19 +236,19 @@
 
   private static void emitAdvancementEvents(
       int previousPc,
-      int previousLine,
-      DexString previousFile,
+      Position previousPosition,
       int nextPc,
-      int nextLine,
-      DexString nextFile,
+      Position nextPosition,
       List<DexDebugEvent> events,
       DexItemFactory factory) {
     assert previousPc != nextPc;
     int pcDelta = previousPc == NO_PC_INFO ? nextPc : nextPc - previousPc;
-    int lineDelta = nextLine == NO_LINE_INFO ? 0 : nextLine - previousLine;
+    assert !previousPosition.isNone() || nextPosition.isNone();
+    assert nextPosition.isNone() || nextPosition.line >= 0;
+    int lineDelta = nextPosition.isNone() ? 0 : nextPosition.line - previousPosition.line;
     assert pcDelta >= 0;
-    if (nextFile != previousFile) {
-      events.add(factory.createSetFile(nextFile));
+    if (nextPosition.file != previousPosition.file) {
+      events.add(factory.createSetFile(nextPosition.file));
     }
     if (lineDelta < Constants.DBG_LINE_BASE
         || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index a541f5a..882bd99 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -273,8 +273,11 @@
   }
 
   public void setCode(
-      IRCode ir, RegisterAllocator registerAllocator, DexItemFactory dexItemFactory) {
-    final DexBuilder builder = new DexBuilder(ir, registerAllocator, dexItemFactory);
+      IRCode ir,
+      RegisterAllocator registerAllocator,
+      DexItemFactory dexItemFactory,
+      InternalOptions options) {
+    final DexBuilder builder = new DexBuilder(ir, registerAllocator, dexItemFactory, options);
     code = builder.build(method.getArity());
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Add.java b/src/main/java/com/android/tools/r8/ir/code/Add.java
index ba11abb..1577968 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Add.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Add.java
@@ -73,7 +73,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asAdd().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/And.java b/src/main/java/com/android/tools/r8/ir/code/And.java
index a6e71ae..0c0714e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/And.java
+++ b/src/main/java/com/android/tools/r8/ir/code/And.java
@@ -69,7 +69,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asAnd().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Argument.java b/src/main/java/com/android/tools/r8/ir/code/Argument.java
index fbf6658..0cc3962 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Argument.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Argument.java
@@ -18,6 +18,12 @@
   public Argument(Value outValue) {
     super(outValue);
     outValue.markAsArgument();
+    super.setPosition(Position.none());
+  }
+
+  @Override
+  public void setPosition(Position position) {
+    // Arguments never have positional information as they never materialize to actual instructions.
   }
 
   @Override
@@ -45,7 +51,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     assert other.isArgument();
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
index e787e59..ee1db81 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayGet.java
@@ -83,7 +83,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asArrayGet().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
index 88325a4..0596c1d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayLength.java
@@ -72,7 +72,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     assert other.isArrayLength();
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
index 11694a3..32b03f4 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ArrayPut.java
@@ -120,7 +120,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asArrayPut().type == type;
   }
 
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 2324f8e..243e6d5 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
@@ -770,6 +770,10 @@
     return "";
   }
 
+  private static int digits(int number) {
+    return (int) Math.ceil(Math.log10(number + 1));
+  }
+
   public String toDetailedString() {
     StringBuilder builder = new StringBuilder();
     builder.append("block ");
@@ -813,10 +817,26 @@
       StringUtils.append(builder, localsAtEntry.int2ReferenceEntrySet(), ", ", BraceType.NONE);
       builder.append('\n');
     }
+    int lineColumn = 0;
+    int numberColumn = 0;
     for (Instruction instruction : instructions) {
-      StringUtils.appendLeftPadded(builder, Integer.toString(instruction.getNumber()), 6);
+      lineColumn = Math.max(lineColumn, instruction.getPosition().toString().length());
+      numberColumn = Math.max(numberColumn, digits(instruction.getNumber()));
+    }
+    Position currentPosition = null;
+    for (Instruction instruction : instructions) {
+      if (lineColumn > 0) {
+        String line = "";
+        if (!instruction.getPosition().equals(currentPosition)) {
+          currentPosition = instruction.getPosition();
+          line = currentPosition.toString();
+        }
+        StringUtils.appendLeftPadded(builder, line, lineColumn + 1);
+        builder.append(": ");
+      }
+      StringUtils.appendLeftPadded(builder, "" + instruction.getNumber(), numberColumn + 1);
       builder.append(": ");
-      StringUtils.appendRightPadded(builder, instruction.toString(), 20);
+      builder.append(instruction.toString());
       if (DebugLocalInfo.PRINT_LEVEL != PrintLevel.NONE) {
         List<Value> localEnds = new ArrayList<>(instruction.getDebugValues().size());
         List<Value> localStarts = new ArrayList<>(instruction.getDebugValues().size());
@@ -1009,6 +1029,27 @@
     return instructions.size() == 1 && exit().isGoto();
   }
 
+  // Find the final target from this goto block. Returns null if the goto chain is cyclic.
+  public BasicBlock endOfGotoChain() {
+    BasicBlock hare = this;
+    BasicBlock tortuous = this;
+    boolean advance = false;
+    while (hare.isTrivialGoto()) {
+      hare = hare.exit().asGoto().getTarget();
+      tortuous = advance ? tortuous.exit().asGoto().getTarget() : tortuous;
+      advance = !advance;
+      if (hare == tortuous) {
+        return null;
+      }
+    }
+    return hare;
+  }
+
+  public Position getPosition() {
+    BasicBlock block = endOfGotoChain();
+    return block != null ? block.entry().getPosition() : Position.none();
+  }
+
   public boolean hasOneNormalExit() {
     return successors.size() == 1 && exit().isGoto();
   }
@@ -1199,11 +1240,10 @@
     List<BasicBlock> predecessors = this.getPredecessors();
     boolean hasMoveException = entry().isMoveException();
     MoveException move = null;
-    DebugPosition position = null;
+    Position position = entry().getPosition();
     if (hasMoveException) {
       // Remove the move-exception instruction.
       move = entry().asMoveException();
-      position = move.getPosition();
       assert move.getDebugValues().isEmpty();
       getInstructions().remove(0);
     }
@@ -1222,9 +1262,7 @@
         values.add(value);
         MoveException newMove = new MoveException(value);
         newBlock.add(newMove);
-        if (position != null) {
-          newMove.setPosition(new DebugPosition(position.line, position.file));
-        }
+        newMove.setPosition(position);
       }
       newBlock.add(new Goto());
       newBlock.close(null);
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 7d61783..41db4d9 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -17,6 +17,7 @@
   protected final BasicBlock block;
   protected final ListIterator<Instruction> listIterator;
   protected Instruction current;
+  protected Position position = null;
 
   protected BasicBlockInstructionIterator(BasicBlock block) {
     this.block = block;
@@ -65,6 +66,11 @@
     return listIterator.previousIndex();
   }
 
+  @Override
+  public void setInsertionPosition(Position position) {
+    this.position = position;
+  }
+
   /**
    * Adds an instruction to the block. The instruction will be added just before the current
    * cursor position.
@@ -77,6 +83,9 @@
   public void add(Instruction instruction) {
     instruction.setBlock(block);
     assert instruction.getBlock() == block;
+    if (position != null) {
+      instruction.setPosition(position);
+    }
     listIterator.add(instruction);
   }
 
@@ -162,6 +171,7 @@
     }
     current.moveDebugValues(newInstruction);
     newInstruction.setBlock(block);
+    newInstruction.setPosition(current.getPosition());
     listIterator.remove();
     listIterator.add(newInstruction);
     current.clearBlock();
@@ -332,6 +342,11 @@
     assert invokeBlock.getInstructions().size() == 2;
     assert invokeBlock.getInstructions().getFirst().isInvoke();
 
+    // Invalidate position-on-throwing-instructions property if it does not hold for the inlinee.
+    if (!inlinee.doAllThrowingInstructionsHavePositions()) {
+      code.setAllThrowingInstructionsHavePositions(false);
+    }
+
     // Split the invoke instruction into a separate block.
     Invoke invoke = invokeBlock.getInstructions().getFirst().asInvoke();
     BasicBlock invokePredecessor = invokeBlock.getPredecessors().get(0);
@@ -349,6 +364,7 @@
         Value receiverValue = arguments.get(0);
         Value value = code.createValue(MoveType.OBJECT);
         castInstruction = new CheckCast(value, invokeValue, downcast);
+        castInstruction.setPosition(invoke.getPosition());
         receiverValue.replaceUsers(value);
       } else {
         arguments.get(i).replaceUsers(invoke.inValues().get(i));
@@ -474,6 +490,8 @@
       }
       newReturn = new Return(value, value.outType());
     }
+    // The newly constructed return will be eliminated as part of inlining so we set position none.
+    newReturn.setPosition(Position.none());
     newExitBlock.add(newReturn);
     for (BasicBlock exitBlock : normalExits) {
       InstructionListIterator it = exitBlock.listIterator(exitBlock.getInstructions().size());
diff --git a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
index 0c0797f..b6c52df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CheckCast.java
@@ -58,7 +58,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asCheckCast().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Cmp.java b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
index aeb0a51..09c1dd6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Cmp.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Cmp.java
@@ -115,7 +115,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asCmp().bias == bias;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
index efde56a..c43ce44 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstClass.java
@@ -50,7 +50,7 @@
     return true;
   }
 
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asConstClass().clazz == clazz;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
index ef5a025..2397027 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstNumber.java
@@ -95,6 +95,7 @@
   @Override
   public void buildDex(DexBuilder builder) {
     if (!dest().needsRegister()) {
+      forceSetPosition(Position.none());
       builder.addNop(this);
       return;
     }
@@ -169,7 +170,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (preciseTypeUnknown()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index 124ff54..9d869aa 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -33,7 +33,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asConstString().value == value;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
index f0eb4a5..1b7aaf6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalRead.java
@@ -32,7 +32,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
index d3d5553..6833568 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalWrite.java
@@ -38,7 +38,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     assert other.isDebugLocalWrite();
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
index f579fd6..a973ed7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
+++ b/src/main/java/com/android/tools/r8/ir/code/DebugLocalsChange.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
 
 public class DebugLocalsChange extends Instruction {
 
@@ -24,6 +25,12 @@
     assert !ending.isEmpty() || !starting.isEmpty();
     this.ending = ending;
     this.starting = starting;
+    super.setPosition(Position.none());
+  }
+
+  @Override
+  public void setPosition(Position position) {
+    throw new Unreachable();
   }
 
   public Int2ReferenceMap<DebugLocalInfo> getEnding() {
@@ -50,7 +57,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     assert other.isDebugLocalsChange();
     DebugLocalsChange o = (DebugLocalsChange) other;
     return DebugLocalInfo.localsInfoMapsEqual(ending, o.ending)
@@ -92,4 +99,15 @@
   public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
     return Constraint.ALWAYS;
   }
+
+  public void apply(Int2ReferenceMap<DebugLocalInfo> locals) {
+    for (Entry<DebugLocalInfo> end : getEnding().int2ReferenceEntrySet()) {
+      assert locals.get(end.getIntKey()) == end.getValue();
+      locals.remove(end.getIntKey());
+    }
+    for (Entry<DebugLocalInfo> start : getStarting().int2ReferenceEntrySet()) {
+      assert !locals.containsKey(start.getIntKey());
+      locals.put(start.getIntKey(), start.getValue());
+    }
+  }
 }
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 d6063f3..48cc8e6 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
@@ -5,7 +5,6 @@
 
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
-import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
@@ -13,13 +12,8 @@
 
 public class DebugPosition extends Instruction {
 
-  public final int line;
-  public final DexString file;
-
-  public DebugPosition(int line, DexString file) {
+  public DebugPosition() {
     super(null);
-    this.line = line;
-    this.file = file;
   }
 
   @Override
@@ -38,9 +32,9 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     assert other.isDebugPosition();
-    return false;
+    return true;
   }
 
   @Override
@@ -50,15 +44,6 @@
   }
 
   @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();
   }
@@ -69,26 +54,12 @@
   }
 
   @Override
-  public boolean canBeDeadCode(IRCode code, InternalOptions options) {
-    return false;
-  }
-
-  @Override
-  public String toString() {
-    StringBuilder builder = new StringBuilder(super.toString());
-    printLineInfo(builder);
-    return builder.toString();
-  }
-
-  public void printLineInfo(StringBuilder builder) {
-    if (file != null) {
-      builder.append(file).append(":");
-    }
-    builder.append(line);
-  }
-
-  @Override
   public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
     return Constraint.ALWAYS;
   }
+
+  @Override
+  public boolean canBeDeadCode(IRCode code, InternalOptions options) {
+    return false;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Div.java b/src/main/java/com/android/tools/r8/ir/code/Div.java
index 7dc5e75..69541c1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Div.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Div.java
@@ -77,7 +77,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asDiv().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Goto.java b/src/main/java/com/android/tools/r8/ir/code/Goto.java
index 5ca9199..f88020d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Goto.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Goto.java
@@ -11,13 +11,20 @@
 
   public Goto() {
     super(null);
+    super.setPosition(Position.none());
   }
 
   public Goto(BasicBlock block) {
-    super(null);
+    this();
     setBlock(block);
   }
 
+  @Override
+  public void setPosition(Position position) {
+    // In general goto's do not signify program points only transitions, so we avoid
+    // associating them with positional information.
+  }
+
   public BasicBlock getTarget() {
     assert getBlock().exit() == this;
     List<BasicBlock> successors = getBlock().getSuccessors();
@@ -66,7 +73,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asGoto().getTarget() == getTarget();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index d9fdaf2..8346c7a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -31,13 +31,22 @@
   private boolean numbered = false;
   private int nextInstructionNumber = 0;
 
+  // Initial value indicating if the code does have actual positions on all throwing instructions.
+  // If this is the case, which holds for javac code, then we want to ensure that it remains so.
+  private boolean allThrowingInstructionsHavePositions;
+
+  public final boolean hasDebugPositions;
+
   public IRCode(
       DexEncodedMethod method,
       LinkedList<BasicBlock> blocks,
-      ValueNumberGenerator valueNumberGenerator) {
+      ValueNumberGenerator valueNumberGenerator,
+      boolean hasDebugPositions) {
     this.method = method;
     this.blocks = blocks;
     this.valueNumberGenerator = valueNumberGenerator;
+    this.hasDebugPositions = hasDebugPositions;
+    allThrowingInstructionsHavePositions = computeAllThrowingInstructionsHavePositions();
   }
 
   /**
@@ -167,6 +176,7 @@
     assert consistentPredecessorSuccessors();
     assert consistentCatchHandlers();
     assert consistentBlockInstructions();
+    assert !allThrowingInstructionsHavePositions || computeAllThrowingInstructionsHavePositions();
     return true;
   }
 
@@ -308,6 +318,7 @@
   private boolean consistentBlockInstructions() {
     for (BasicBlock block : blocks) {
       for (Instruction instruction : block.getInstructions()) {
+        assert instruction.getPosition() != null;
         assert instruction.getBlock() == block;
       }
     }
@@ -431,4 +442,23 @@
     Value newValue = createValue(from.outType());
     return new ConstNumber(ConstType.fromMoveType(from.outType()), newValue, 0);
   }
+
+  public boolean doAllThrowingInstructionsHavePositions() {
+    return allThrowingInstructionsHavePositions;
+  }
+
+  public void setAllThrowingInstructionsHavePositions(boolean value) {
+    this.allThrowingInstructionsHavePositions = value;
+  }
+
+  private boolean computeAllThrowingInstructionsHavePositions() {
+    InstructionIterator it = instructionIterator();
+    while (it.hasNext()) {
+      Instruction instruction = it.next();
+      if (instruction.instructionTypeCanThrow() && instruction.getPosition().isNone()) {
+        return false;
+      }
+    }
+    return true;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/If.java b/src/main/java/com/android/tools/r8/ir/code/If.java
index 3a068f6..d2e16f6 100644
--- a/src/main/java/com/android/tools/r8/ir/code/If.java
+++ b/src/main/java/com/android/tools/r8/ir/code/If.java
@@ -144,7 +144,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     If o = other.asIf();
     return o.getTrueTarget() == getTrueTarget()
         && o.fallthroughBlock() == fallthroughBlock()
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 1d9e853..b286544 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -83,7 +83,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     InstanceGet o = other.asInstanceGet();
     return o.field == field && o.type == type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
index afa18e5..5662a9a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceOf.java
@@ -54,7 +54,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asInstanceOf().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
index 2642de1..0866205 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstancePut.java
@@ -73,7 +73,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     InstancePut o = other.asInstancePut();
     return o.field == field && o.type == type;
   }
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 24c3925..72d348c 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
@@ -27,6 +27,7 @@
   private BasicBlock block = null;
   private int number = -1;
   private Set<Value> debugValues = null;
+  private Position position = null;
 
   protected Instruction(Value outValue) {
     setOutValue(outValue);
@@ -46,6 +47,20 @@
     setOutValue(outValue);
   }
 
+  public final Position getPosition() {
+    assert position != null;
+    return position;
+  }
+
+  public void setPosition(Position position) {
+    assert this.position == null;
+    this.position = position;
+  }
+
+  public final void forceSetPosition(Position position) {
+    this.position = position;
+  }
+
   public List<Value> inValues() {
     return inValues;
   }
@@ -244,11 +259,16 @@
   }
 
   /**
-   * Compare equality of two class-equivalent instructions modulo their values.
+   * Compare equality of two class-equivalent instructions modulo their values and positions.
    *
    * <p>It is a precondition to this method that this.getClass() == other.getClass().
    */
-  public abstract boolean identicalNonValueParts(Instruction other);
+  public abstract boolean identicalNonValueNonPositionParts(Instruction other);
+
+  public boolean identicalNonValueParts(Instruction other) {
+    assert getClass() == other.getClass();
+    return position.equals(other.position) && identicalNonValueNonPositionParts(other);
+  }
 
   public abstract int compareNonValueParts(Instruction other);
 
@@ -264,7 +284,7 @@
     } else {
       ConstNumber aNum = a.getConstInstruction().asConstNumber();
       ConstNumber bNum = b.getConstInstruction().asConstNumber();
-      if (!aNum.identicalNonValueParts(bNum)) {
+      if (!aNum.identicalNonValueNonPositionParts(bNum)) {
         return false;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 92491c3..56a3e1f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -59,6 +59,10 @@
     return null;
   }
 
+  default void setInsertionPosition(Position position) {
+    // Intentionally empty.
+  }
+
   /**
    * Safe removal function that will insert a DebugLocalRead to take over the debug values if any
    * are associated with the current instruction.
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
index f597b2c..e0e816b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeCustom.java
@@ -72,7 +72,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.isInvokeCustom() && callSite == other.asInvokeCustom().callSite;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
index cc23ef5..a56f840 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeDirect.java
@@ -70,11 +70,11 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokeDirect()) {
       return false;
     }
-    return super.identicalNonValueParts(other);
+    return super.identicalNonValueNonPositionParts(other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
index a639ba1..d9be255 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeInterface.java
@@ -57,11 +57,11 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokeInterface()) {
       return false;
     }
-    return super.identicalNonValueParts(other);
+    return super.identicalNonValueNonPositionParts(other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
index 70201f0..cd6497d 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeMethod.java
@@ -28,7 +28,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return method == other.asInvokeMethod().getInvokedMethod();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
index 0049baa..5251e20 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeNewArray.java
@@ -70,7 +70,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokeNewArray()) {
       return false;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
index 5eacaa9..126bfd2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokePolymorphic.java
@@ -70,11 +70,12 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokePolymorphic()) {
       return false;
     }
-    return proto.equals(((InvokePolymorphic) other).proto) && super.identicalNonValueParts(other);
+    return proto.equals(((InvokePolymorphic) other).proto)
+        && super.identicalNonValueNonPositionParts(other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 6654c16..4d9c67a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -59,11 +59,11 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokeStatic()) {
       return false;
     }
-    return super.identicalNonValueParts(other);
+    return super.identicalNonValueNonPositionParts(other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
index b3d5fe4..428be6f 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeSuper.java
@@ -58,11 +58,11 @@
     addInvokeAndMoveResult(instruction, builder);
   }
 
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokeSuper()) {
       return false;
     }
-    return super.identicalNonValueParts(other);
+    return super.identicalNonValueNonPositionParts(other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 34793c2..c5cfd50 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -57,11 +57,11 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isInvokeVirtual()) {
       return false;
     }
-    return super.identicalNonValueParts(other);
+    return super.identicalNonValueNonPositionParts(other);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/Monitor.java b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
index 3c0c062..98a83c7 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Monitor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Monitor.java
@@ -50,7 +50,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asMonitor().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Move.java b/src/main/java/com/android/tools/r8/ir/code/Move.java
index 277c63c..08d8bf2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Move.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Move.java
@@ -43,7 +43,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     assert other.isMove();
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/MoveException.java b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
index 61ed928..0f72182 100644
--- a/src/main/java/com/android/tools/r8/ir/code/MoveException.java
+++ b/src/main/java/com/android/tools/r8/ir/code/MoveException.java
@@ -12,8 +12,6 @@
 
 public class MoveException extends Instruction {
 
-  private DebugPosition position = null;
-
   public MoveException(Value dest) {
     super(dest);
   }
@@ -22,15 +20,6 @@
     return outValue;
   }
 
-  public DebugPosition getPosition() {
-    return position;
-  }
-
-  public void setPosition(DebugPosition position) {
-    assert this.position == null;
-    this.position = position;
-  }
-
   @Override
   public void buildDex(DexBuilder builder) {
     int dest = builder.allocatedRegister(dest(), getNumber());
@@ -49,7 +38,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     assert other.isMoveException();
     return true;
   }
@@ -76,18 +65,6 @@
   }
 
   @Override
-  public String toString() {
-    if (position != null) {
-      StringBuilder builder = new StringBuilder(super.toString());
-      builder.append("(DebugPosition ");
-      position.printLineInfo(builder);
-      builder.append(')');
-      return builder.toString();
-    }
-    return super.toString();
-  }
-
-  @Override
   public Constraint inliningConstraint(AppInfoWithSubtyping info, DexType holder) {
     // TODO(64432527): Revisit this constraint.
     return Constraint.NEVER;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Mul.java b/src/main/java/com/android/tools/r8/ir/code/Mul.java
index 67aca89..42c7f09 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Mul.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Mul.java
@@ -79,7 +79,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asMul().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Neg.java b/src/main/java/com/android/tools/r8/ir/code/Neg.java
index f858d70..dc0be38 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Neg.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Neg.java
@@ -50,7 +50,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asNeg().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
index 17a08da..1e77217 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayEmpty.java
@@ -52,7 +52,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asNewArrayEmpty().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
index aed765e..4b01c4c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewArrayFilledData.java
@@ -42,7 +42,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     NewArrayFilledData o = other.asNewArrayFilledData();
     return o.element_width == element_width
         && o.size == size
diff --git a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
index 8d1671d..434b415 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NewInstance.java
@@ -36,7 +36,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asNewInstance().clazz == clazz;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Not.java b/src/main/java/com/android/tools/r8/ir/code/Not.java
index 4c42b80..98e2207 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Not.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Not.java
@@ -56,7 +56,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asNot().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
index 23cbb69..67cb816 100644
--- a/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
+++ b/src/main/java/com/android/tools/r8/ir/code/NumberConversion.java
@@ -114,7 +114,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     NumberConversion o = other.asNumberConversion();
     return o.from == from && o.to == to;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Or.java b/src/main/java/com/android/tools/r8/ir/code/Or.java
index bfa69f4..24b8995 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Or.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Or.java
@@ -68,7 +68,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asOr().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Position.java b/src/main/java/com/android/tools/r8/ir/code/Position.java
new file mode 100644
index 0000000..a8312fb
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -0,0 +1,67 @@
+// 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.ir.code;
+
+import com.android.tools.r8.graph.DexString;
+
+public class Position {
+
+  private static final Position NO_POSITION = new Position(-1, null, false);
+
+  public final int line;
+  public final DexString file;
+  public final boolean synthetic;
+
+  public Position(int line, DexString file) {
+    this(line, file, false);
+    assert line >= 0;
+  }
+
+  private Position(int line, DexString file, boolean synthetic) {
+    this.line = line;
+    this.file = file;
+    this.synthetic = synthetic;
+  }
+
+  public static Position synthetic(int line) {
+    return new Position(line, null, true);
+  }
+
+  public static Position none() {
+    return NO_POSITION;
+  }
+
+  public boolean isNone() {
+    return this == NO_POSITION;
+  }
+
+  public boolean isSome() {
+    return this != NO_POSITION;
+  }
+
+  @Override
+  public boolean equals(Object other) {
+    if (this == other) {
+      return true;
+    }
+    if (other instanceof Position) {
+      Position o = (Position) other;
+      return !isNone() && line == o.line && file == o.file;
+    }
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    if (isNone()) {
+      return "--";
+    }
+    StringBuilder builder = new StringBuilder();
+    if (file != null) {
+      builder.append(file).append(":");
+    }
+    builder.append(line);
+    return builder.toString();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/Rem.java b/src/main/java/com/android/tools/r8/ir/code/Rem.java
index a257109..7232012 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Rem.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Rem.java
@@ -77,7 +77,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asRem().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Return.java b/src/main/java/com/android/tools/r8/ir/code/Return.java
index ba2fcb0..e9311e3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Return.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Return.java
@@ -71,7 +71,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (isReturnVoid()) {
       return other.asReturn().isReturnVoid();
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shl.java b/src/main/java/com/android/tools/r8/ir/code/Shl.java
index 8236891..09c7d61 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shl.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shl.java
@@ -68,7 +68,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asShl().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Shr.java b/src/main/java/com/android/tools/r8/ir/code/Shr.java
index e4bd97c..aef09b3 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Shr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Shr.java
@@ -68,7 +68,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asShr().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index bb79d5e..5075e7a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -77,7 +77,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     StaticGet o = other.asStaticGet();
     return o.field == field && o.type == type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index 6736d7e..8a55361 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -79,7 +79,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     StaticPut o = other.asStaticPut();
     return o.field == field && o.type == type;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Sub.java b/src/main/java/com/android/tools/r8/ir/code/Sub.java
index 8470070..7bb1107 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Sub.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Sub.java
@@ -80,7 +80,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asSub().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Switch.java b/src/main/java/com/android/tools/r8/ir/code/Switch.java
index 08ab002..e962d90 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Switch.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Switch.java
@@ -140,7 +140,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     assert other.isSwitch();
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Throw.java b/src/main/java/com/android/tools/r8/ir/code/Throw.java
index 2f9f9f7..def92eb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Throw.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Throw.java
@@ -41,7 +41,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     assert other.isThrow();
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Ushr.java b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
index d90ce4a..c0500b1 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Ushr.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Ushr.java
@@ -68,7 +68,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asUshr().type == type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/Xor.java b/src/main/java/com/android/tools/r8/ir/code/Xor.java
index dfe47bc..779f307 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Xor.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Xor.java
@@ -68,7 +68,7 @@
   }
 
   @Override
-  public boolean identicalNonValueParts(Instruction other) {
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
     return other.asXor().type == type;
   }
 
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 0d96df7..7a8895d 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
@@ -33,12 +33,12 @@
 import com.android.tools.r8.code.Nop;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexCode.Try;
 import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEventBuilder;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -52,17 +52,21 @@
 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.Position;
 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;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceMaps;
+import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -81,6 +85,8 @@
 
   private final DexItemFactory dexItemFactory;
 
+  private final InternalOptions options;
+
   // List of information about switch payloads that have to be created at the end of the
   // dex code.
   private final List<SwitchPayloadInfo> switchPayloadInfos = new ArrayList<>();
@@ -109,13 +115,18 @@
 
   BasicBlock nextBlock;
 
-  public DexBuilder(IRCode ir, RegisterAllocator registerAllocator, DexItemFactory dexItemFactory) {
+  public DexBuilder(
+      IRCode ir,
+      RegisterAllocator registerAllocator,
+      DexItemFactory dexItemFactory,
+      InternalOptions options) {
     assert ir != null;
     assert registerAllocator != null;
     assert dexItemFactory != null;
     this.ir = ir;
     this.registerAllocator = registerAllocator;
     this.dexItemFactory = dexItemFactory;
+    this.options = options;
   }
 
   private void reset() {
@@ -149,13 +160,13 @@
       // Reset the state of the builder to start from scratch.
       reset();
 
-      // Populate the builder info objects.
-      numberOfInstructions = 0;
-
       // Remove redundant debug position instructions. They would otherwise materialize as
       // unnecessary nops.
       removeRedundantDebugPositions();
 
+      // Populate the builder info objects.
+      numberOfInstructions = 0;
+
       ListIterator<BasicBlock> iterator = ir.listIterator();
       assert iterator.hasNext();
       BasicBlock block = iterator.next();
@@ -177,22 +188,16 @@
     } while (!ifsNeedingRewrite.isEmpty());
 
     // Build instructions.
-    DexDebugEventBuilder debugEventBuilder = new DexDebugEventBuilder(ir.method, dexItemFactory);
+    DexDebugEventBuilder debugEventBuilder = new DexDebugEventBuilder(ir, options);
     List<Instruction> dexInstructions = new ArrayList<>(numberOfInstructions);
     int instructionOffset = 0;
     InstructionIterator instructionIterator = ir.instructionIterator();
-    DexEncodedMethod.DebugPositionRangeList.Builder debugPositionListBuilder =
-        new DexEncodedMethod.DebugPositionRangeList.Builder();
     while (instructionIterator.hasNext()) {
       com.android.tools.r8.ir.code.Instruction ir = instructionIterator.next();
-      if (ir.isDebugPosition()) {
-        int line = ir.asDebugPosition().line;
-        debugPositionListBuilder.add(line, line);
-      }
       Info info = getInfo(ir);
       int previousInstructionCount = dexInstructions.size();
       info.addInstructions(this, dexInstructions);
-      debugEventBuilder.add(instructionOffset, ir);
+      int instructionStartOffset = instructionOffset;
       if (previousInstructionCount < dexInstructions.size()) {
         while (previousInstructionCount < dexInstructions.size()) {
           Instruction instruction = dexInstructions.get(previousInstructionCount++);
@@ -200,9 +205,10 @@
           instructionOffset += instruction.getSize();
         }
       }
+      debugEventBuilder.add(instructionStartOffset, instructionOffset, ir);
     }
 
-    ir.method.debugPositionRangeList = debugPositionListBuilder.build();
+    ir.method.debugPositionRangeList = debugEventBuilder.buildPositionRanges();
 
     // Compute switch payloads.
     for (SwitchPayloadInfo switchPayloadInfo : switchPayloadInfos) {
@@ -251,21 +257,114 @@
     return code;
   }
 
+  private static boolean verifyNopHasNoPosition(
+      com.android.tools.r8.ir.code.Instruction instruction, ListIterator<BasicBlock> blocks) {
+    BasicBlock nextBlock = null;
+    if (blocks.hasNext()) {
+      nextBlock = blocks.next();
+      blocks.previous();
+    }
+    return verifyNopHasNoPosition(instruction, nextBlock);
+  }
+
+  private static boolean verifyNopHasNoPosition(
+      com.android.tools.r8.ir.code.Instruction instruction, BasicBlock nextBlock) {
+    if (isNopInstruction(instruction, nextBlock)) {
+      assert instruction.getPosition().isNone();
+    }
+    return true;
+  }
+
+  // Eliminates unneeded debug positions.
+  //
+  // After this pass all instructions that don't materialize to an actual DEX instruction will have
+  // Position.none(). If any other instruction has a non-none position then all other instructions
+  // that do materialize to a DEX instruction (eg, non-fallthrough gotos) will have a non-none
+  // position.
+  //
+  // Remaining debug positions indicate two successive lines without intermediate instructions.
+  // For these we must emit a nop instruction to ensure they don't share the same pc.
   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();
+    if (!ir.hasDebugPositions) {
+      return;
+    }
+    Int2ReferenceMap[] localsMap = new Int2ReferenceMap[instructionToInfo.length];
+    // Scan forwards removing debug positions equal to the previous instruction position.
+    {
+      Int2ReferenceMap<DebugLocalInfo> locals = Int2ReferenceMaps.emptyMap();
+      Position previous = Position.none();
+      ListIterator<BasicBlock> blockIterator = ir.listIterator();
+      BasicBlock previousBlock = null;
+      while (blockIterator.hasNext()) {
+        BasicBlock block = blockIterator.next();
+        if (previousBlock != null
+            && previousBlock.exit().isGoto()
+            && !isNopInstruction(previousBlock.exit(), block)) {
+          assert previousBlock.exit().getPosition().isNone()
+              || previousBlock.exit().getPosition().equals(previous);
+          previousBlock.exit().forceSetPosition(previous);
+        }
+        InstructionListIterator instructionIterator = block.listIterator();
+        if (block.getLocalsAtEntry() != null && !locals.equals(block.getLocalsAtEntry())) {
+          locals = new Int2ReferenceOpenHashMap<>(block.getLocalsAtEntry());
+        }
+        while (instructionIterator.hasNext()) {
+          com.android.tools.r8.ir.code.Instruction instruction = instructionIterator.next();
+          if (instruction.isDebugPosition() && previous.equals(instruction.getPosition())) {
+            instructionIterator.remove();
+          } else if (instruction.isConstNumber() && !instruction.outValue().needsRegister()) {
+            instruction.forceSetPosition(Position.none());
+          } else if (instruction.getPosition().isSome()) {
+            assert verifyNopHasNoPosition(instruction, blockIterator);
+            previous = instruction.getPosition();
           }
-          lastMoveExceptionPosition = null;
-        } else if (instruction.isMoveException()) {
-          lastMoveExceptionPosition = instruction.asMoveException().getPosition();
+          if (instruction.isDebugLocalsChange()) {
+            locals = new Int2ReferenceOpenHashMap<>(locals);
+            instruction.asDebugLocalsChange().apply(locals);
+          }
+          localsMap[instructionNumberToIndex(instruction.getNumber())] = locals;
+        }
+        previousBlock = block;
+      }
+      if (previousBlock != null && previousBlock.exit().isGoto()) {
+        // If the last block ends in a goto it cannot be a fallthrough/nop.
+        assert previousBlock.exit().getPosition().isNone();
+        previousBlock.exit().forceSetPosition(previous);
+      }
+    }
+    // Scan backwards removing debug positions equal to the following instruction position.
+    {
+      ListIterator<BasicBlock> blocks = ir.blocks.listIterator(ir.blocks.size());
+      BasicBlock block = null;
+      BasicBlock nextBlock;
+      com.android.tools.r8.ir.code.Instruction next = null;
+      Int2ReferenceMap nextLocals = null;
+      while (blocks.hasPrevious()) {
+        nextBlock = block;
+        block = blocks.previous();
+        InstructionListIterator instructions = block.listIterator(block.getInstructions().size());
+        while (instructions.hasPrevious()) {
+          com.android.tools.r8.ir.code.Instruction instruction = instructions.previous();
+          int index = instructionNumberToIndex(instruction.getNumber());
+          if (instruction.isDebugPosition() && localsMap[index].equals(nextLocals)) {
+            Position nextPosition = next.getPosition();
+            Position thisPosition = instruction.getPosition();
+            if (nextPosition.isNone()) {
+              next.forceSetPosition(thisPosition);
+              instructions.remove();
+            } else if (nextPosition.equals(thisPosition)) {
+              instructions.remove();
+            } else {
+              next = instruction;
+            }
+          } else {
+            assert verifyNopHasNoPosition(instruction, nextBlock);
+            if (!isNopInstruction(instruction, nextBlock)) {
+              next = instruction;
+              nextLocals = localsMap[index];
+              assert nextLocals != null;
+            }
+          }
         }
       }
     }
@@ -349,39 +448,22 @@
   }
 
   public void addNop(com.android.tools.r8.ir.code.Instruction instruction) {
+    assert instruction.getPosition().isNone();
     add(instruction, new FallThroughInfo(instruction));
   }
 
-  private static boolean isNopInstruction(com.android.tools.r8.ir.code.Instruction instruction) {
-    return instruction.isDebugLocalsChange()
-        || (instruction.isConstNumber() && !instruction.outValue().needsRegister());
+  private static boolean isNopInstruction(
+      com.android.tools.r8.ir.code.Instruction instruction, BasicBlock nextBlock) {
+    return instruction.isArgument()
+        || instruction.isDebugLocalsChange()
+        || (instruction.isConstNumber() && !instruction.outValue().needsRegister())
+        || instruction.isGoto() && instruction.asGoto().getTarget() == nextBlock;
   }
 
   public void addDebugPosition(DebugPosition position) {
-    BasicBlock block = position.getBlock();
-    int blockIndex = ir.blocks.indexOf(block);
-    Iterator<com.android.tools.r8.ir.code.Instruction> iterator = block.listIterator(position);
-
-    com.android.tools.r8.ir.code.Instruction next = null;
-    while (next == null) {
-      next = iterator.next();
-      while (isNopInstruction(next)) {
-        next = iterator.next();
-      }
-      if (next.isGoto()) {
-        ++blockIndex;
-        BasicBlock nextBlock = blockIndex < ir.blocks.size() ? ir.blocks.get(blockIndex) : null;
-        if (next.asGoto().getTarget() == nextBlock) {
-          iterator = nextBlock.iterator();
-          next = null;
-        }
-      }
-    }
-    if (next.isDebugPosition() && !position.equals(next.asDebugPosition())) {
-      add(position, new FixedSizeInfo(position, new Nop()));
-    } else {
-      addNop(position);
-    }
+    // Remaining debug positions always require we emit an actual nop instruction.
+    // See removeRedundantDebugPositions.
+    add(position, new FixedSizeInfo(position, new Nop()));
   }
 
   public void add(com.android.tools.r8.ir.code.Instruction ir, Instruction dex) {
@@ -411,30 +493,13 @@
   }
 
   public void addReturn(Return ret, Instruction dex) {
-    if (nextBlock != null) {
-      Return followingRet = nextBlock.exit().asReturn();
-      if (nextBlock.getInstructions().size() == 1
-          && followingRet != null
-          && ret.getReturnType() == followingRet.getReturnType()) {
-        if (ret.isReturnVoid() && followingRet.isReturnVoid()) {
-          addNop(ret);
-          return;
-        }
-        if (!ret.isReturnVoid()
-            && !followingRet.isReturnVoid()
-            && ret.returnValue().outType() == followingRet.returnValue().outType()) {
-          int thisRegister = registerAllocator.getRegisterForValue(
-              ret.returnValue(), ret.getNumber());
-          int otherRegister = registerAllocator.getRegisterForValue(
-              followingRet.returnValue(), followingRet.getNumber());
-          if (thisRegister == otherRegister) {
-            addNop(ret);
-            return;
-          }
-        }
-      }
+    if (nextBlock != null
+        && ret.identicalAfterRegisterAllocation(nextBlock.entry(), registerAllocator)) {
+      ret.forceSetPosition(Position.none());
+      addNop(ret);
+    } else {
+      add(ret, dex);
     }
-    add(ret, dex);
   }
 
   private void add(com.android.tools.r8.ir.code.Instruction ir, Info info) {
@@ -889,7 +954,7 @@
       } else {
         size = 3;
       }
-      if (targetInfo.getIR().isReturn() && canTargetReturn(targetInfo.getIR().asReturn())) {
+      if (targetInfo.getIR().isReturn() && targetInfo.getIR().getPosition().isNone()) {
         // 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.
@@ -909,7 +974,7 @@
       // Emit a return if the target is a return and the size of the return is the computed
       // size of this instruction.
       Return ret = targetInfo.getIR().asReturn();
-      if (ret != null && size == targetInfo.getSize() && canTargetReturn(ret)) {
+      if (ret != null && size == targetInfo.getSize() && ret.getPosition().isNone()) {
         Instruction dex = ret.createDexInstruction(builder);
         dex.setOffset(getOffset()); // for better printing of the dex code.
         instructions.add(dex);
@@ -950,18 +1015,6 @@
         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 543f707..a71f326 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
@@ -41,8 +41,8 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.CatchHandlers;
-import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.MoveType;
+import com.android.tools.r8.ir.code.Position;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -67,6 +67,9 @@
   private CatchHandlers<Integer> currentCatchHandlers = null;
   private Instruction currentDexInstruction = null;
 
+  private Position currentPosition = null;
+  private Map<Position, Position> canonicalPositions = null;
+
   private final List<MoveType> argumentTypes;
 
   private List<DexDebugEntry> debugEntries = null;
@@ -79,6 +82,7 @@
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
       debugEntries = info.computeEntries();
+      canonicalPositions = new HashMap<>(debugEntries.size());
     }
   }
 
@@ -116,6 +120,7 @@
 
   @Override
   public void buildPrelude(IRBuilder builder) {
+    currentPosition = Position.none();
     if (code.incomingRegisterSize == 0) {
       return;
     }
@@ -146,7 +151,7 @@
   @Override
   public void buildInstruction(IRBuilder builder, int instructionIndex) throws ApiLevelException {
     updateCurrentCatchHandlers(instructionIndex);
-    emitDebugPosition(instructionIndex, builder);
+    updateDebugPosition(instructionIndex, builder);
     currentDexInstruction = code.instructions[instructionIndex];
     currentDexInstruction.buildIR(builder);
   }
@@ -163,11 +168,16 @@
   }
 
   @Override
-  public DebugPosition getDebugPositionAtOffset(int offset) {
+  public Position getDebugPositionAtOffset(int offset) {
     throw new Unreachable();
   }
 
   @Override
+  public Position getCurrentPosition() {
+    return currentPosition;
+  }
+
+  @Override
   public boolean verifyCurrentInstructionCanThrow() {
     return currentDexInstruction.canThrow();
   }
@@ -192,22 +202,32 @@
     }
   }
 
-  private void emitDebugPosition(int instructionIndex, IRBuilder builder) {
+  private void updateDebugPosition(int instructionIndex, IRBuilder builder) {
     if (debugEntries == null || debugEntries.isEmpty()) {
       return;
     }
+    DexDebugEntry current = null;
     int offset = instructionOffset(instructionIndex);
     for (DexDebugEntry entry : debugEntries) {
-      if (entry.address == offset) {
-        builder.addDebugPosition(entry.line, entry.sourceFile);
-        return;
-      }
       if (entry.address > offset) {
-        return;
+        break;
+      }
+      current = entry;
+    }
+    if (current == null) {
+      currentPosition = Position.none();
+    } else {
+      currentPosition = getCanonicalPosition(current);
+      if (current.address == offset) {
+        builder.addDebugPosition(currentPosition);
       }
     }
   }
 
+  private Position getCanonicalPosition(DexDebugEntry entry) {
+    return canonicalPositions.computeIfAbsent(new Position(entry.line, entry.sourceFile), p -> p);
+  }
+
   @Override
   public void clear() {
     switchPayloadResolver.clear();
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 c84adcb..e9f94e3 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
@@ -66,6 +66,7 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Or;
 import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Shl;
@@ -375,6 +376,9 @@
     // but before handle-exit (which does not maintain predecessor counts).
     assert verifyFilledPredecessors();
 
+    // Insert debug positions so all position changes are marked by an explicit instruction.
+    boolean hasDebugPositions = insertDebugPositions();
+
     // Clear all reaching definitions to free up memory (and avoid invalid use).
     for (BasicBlock block : blocks) {
       block.clearCurrentDefinitions();
@@ -389,7 +393,7 @@
     splitCriticalEdges();
 
     // Package up the IR code.
-    IRCode ir = new IRCode(method, blocks, valueNumberGenerator);
+    IRCode ir = new IRCode(method, blocks, valueNumberGenerator, hasDebugPositions);
 
     if (options.testing.invertConditionals) {
       invertConditionalsForTesting(ir);
@@ -408,6 +412,41 @@
     return ir;
   }
 
+  private boolean insertDebugPositions() {
+    boolean hasDebugPositions = false;
+    if (!options.debug) {
+      return hasDebugPositions;
+    }
+    for (BasicBlock block : blocks) {
+      InstructionListIterator it = block.listIterator();
+      Position current = null;
+      while (it.hasNext()) {
+        Instruction instruction = it.next();
+        Position position = instruction.getPosition();
+        if (instruction.isMoveException()) {
+          assert current == null;
+          current = position;
+          hasDebugPositions = hasDebugPositions || position.isSome();
+        } else if (instruction.isDebugPosition()) {
+          hasDebugPositions = true;
+          if (position.equals(current)) {
+            it.removeOrReplaceByDebugLocalRead();
+          } else {
+            current = position;
+          }
+        } else if (position.isSome() && !position.equals(current)) {
+          DebugPosition positionChange = new DebugPosition();
+          positionChange.setPosition(position);
+          it.previous();
+          it.add(positionChange);
+          it.next();
+          current = position;
+        }
+      }
+    }
+    return hasDebugPositions;
+  }
+
   private void clearCanonicalizationMaps() {
     intConstants = null;
     longConstants = null;
@@ -484,10 +523,12 @@
     assert moveExceptionDest >= 0;
     int targetIndex = source.instructionIndex(moveExceptionItem.targetOffset);
     Value out = writeRegister(moveExceptionDest, MoveType.OBJECT, ThrowingInfo.NO_THROW, null);
+    Position position = source.getDebugPositionAtOffset(moveExceptionItem.targetOffset);
     MoveException moveException = new MoveException(out);
-    moveException.setPosition(source.getDebugPositionAtOffset(moveExceptionItem.targetOffset));
+    moveException.setPosition(position);
     currentBlock.add(moveException);
-    currentBlock.add(new Goto());
+    Goto exit = new Goto();
+    currentBlock.add(exit);
     BasicBlock targetBlock = getTarget(moveExceptionItem.targetOffset);
     currentBlock.link(targetBlock);
     addToWorklist(targetBlock, targetIndex);
@@ -544,11 +585,6 @@
     addInstruction(new DebugLocalUninitialized(type, 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);
@@ -645,6 +681,13 @@
     }
   }
 
+  public void addDebugPosition(Position position) {
+    if (options.debug) {
+      assert source.getCurrentPosition().equals(position);
+      addInstruction(new DebugPosition());
+    }
+  }
+
   public void addAdd(NumericType type, int dest, int left, int right) {
     Value in1 = readNumericRegister(left, type);
     Value in2 = readNumericRegister(right, type);
@@ -766,6 +809,7 @@
           }
         }
         it.add(instruction);
+        instruction.setPosition(Position.none());
       } else {
         add(instruction);
       }
@@ -1193,12 +1237,13 @@
     assert !out.hasLocalInfo();
     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().size() == 1 && currentBlock.entry().isDebugPosition()) {
+      InstructionListIterator it = currentBlock.listIterator();
+      Instruction entry = it.next();
+      assert entry.getPosition().equals(source.getCurrentPosition());
+      attachLocalValues(instruction);
+      it.replaceCurrentInstruction(instruction);
+      return;
     }
     if (!currentBlock.getInstructions().isEmpty()) {
       throw new CompilationError("Invalid MoveException instruction encountered. "
@@ -1265,14 +1310,18 @@
 
   public void addReturn(MoveType type, int value) {
     Value in = readRegister(value, type);
-    source.buildPostlude(this);
-    addInstruction(new Return(in, type));
-    closeCurrentBlock();
+    addReturn(new Return(in, type));
   }
 
   public void addReturn() {
+    addReturn(new Return());
+  }
+
+  private void addReturn(Return ret) {
+    // Attach the live locals to the return instruction to avoid a local change on monitor exit.
+    attachLocalValues(ret);
     source.buildPostlude(this);
-    addInstruction(new Return());
+    addInstruction(ret);
     closeCurrentBlock();
   }
 
@@ -1675,6 +1724,11 @@
 
   // Private instruction helpers.
   private void addInstruction(Instruction ir) {
+    addInstruction(ir, source.getCurrentPosition());
+  }
+
+  private void addInstruction(Instruction ir, Position position) {
+    ir.setPosition(position);
     attachLocalValues(ir);
     currentBlock.add(ir);
     if (ir.instructionTypeCanThrow()) {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 184087a..e83774d 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -412,7 +412,7 @@
     }
     assert code.isConsistentSSA();
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
-    method.setCode(code, registerAllocator, appInfo.dexItemFactory);
+    method.setCode(code, registerAllocator, appInfo.dexItemFactory, options);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
           method.toSourceString(), logCode(options, method));
@@ -577,13 +577,14 @@
     // Insert code to log arguments if requested.
     if (options.methodMatchesLogArgumentsFilter(method)) {
       codeRewriter.logArgumentTypes(method, code);
+      assert code.isConsistentSSA();
     }
 
     printMethod(code, "Optimized IR (SSA)");
 
     // Perform register allocation.
     RegisterAllocator registerAllocator = performRegisterAllocation(code, method);
-    method.setCode(code, registerAllocator, appInfo.dexItemFactory);
+    method.setCode(code, registerAllocator, appInfo.dexItemFactory, options);
     updateHighestSortingStrings(method);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Resulting dex code for %s:\n%s",
@@ -615,13 +616,6 @@
     // Always perform dead code elimination before register allocation. The register allocator
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
     DeadCodeRemover.removeDeadCode(code, codeRewriter, options);
-    if (!options.debug) {
-      // Remove unneeded debug positions before register allocation to avoid to process useless
-      // instructions. These is safe because the register allocator can not generate new
-      // instructions that will throw exceptions and thus apply removedUnneededDebugPositions before
-      // register allocator will produce the same result.
-      CodeRewriter.removedUnneededDebugPositions(code);
-    }
     LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(code, options);
     registerAllocator.allocateRegisters(options.debug);
     printMethod(code, "After register allocation (non-SSA)");
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 a4203a4..2e14085 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
@@ -23,13 +23,13 @@
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Cmp.Bias;
 import com.android.tools.r8.ir.code.ConstType;
-import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.If;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.MemberType;
 import com.android.tools.r8.ir.code.Monitor;
 import com.android.tools.r8.ir.code.MoveType;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.IRBuilder.BlockInfo;
 import com.android.tools.r8.ir.conversion.JarState.Local;
 import com.android.tools.r8.ir.conversion.JarState.Slot;
@@ -45,6 +45,7 @@
 import java.util.Arrays;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
@@ -173,6 +174,15 @@
   // State to signal that the code currently being emitted is part of synchronization prelude/exits.
   private boolean generatingMethodSynchronization = false;
 
+  // Current position associated with the current instruction during building.
+  private Position currentPosition;
+
+  // Canonicalized positions to lower memory usage.
+  private Int2ReferenceMap<Position> canonicalPositions = new Int2ReferenceOpenHashMap<>();
+
+  // Cooked position to indicate positions in synthesized code (ie, for synchronization).
+  private Position syntheticPosition = null;
+
   public JarSourceCode(DexType clazz, MethodNode node, JarApplicationReader application) {
     assert node != null;
     assert node.desc != null;
@@ -303,6 +313,8 @@
       locals = state.openLocals(initialLabel);
     }
 
+    currentPosition = Position.none();
+
     // Build the actual argument instructions now that type and debug information is known
     // for arguments.
     buildArgumentInstructions(builder);
@@ -431,6 +443,7 @@
   private void buildExceptionalPostlude(IRBuilder builder) {
     assert isSynchronized();
     generatingMethodSynchronization = true;
+    currentPosition = getSyntheticPosition();
     buildMonitorExit(builder);
     builder.addThrow(getMoveExceptionRegister());
     generatingMethodSynchronization = false;
@@ -472,6 +485,12 @@
       preInstructionState = state.toString();
     }
 
+    // Don't include line changes when processing a label. Doing so will end up emitting local
+    // writes after the line has changed and thus causing locals to become visible too late.
+    currentPosition =
+        getDebugPositionAtOffset(
+            insn instanceof LabelNode ? instructionIndex - 1 : instructionIndex);
+
     build(insn, builder);
 
     if (Log.ENABLED && !(insn instanceof LineNumberNode)) {
@@ -2826,24 +2845,65 @@
   }
 
   private void build(LineNumberNode insn, IRBuilder builder) {
-    builder.addDebugPosition(insn.line, null);
+    currentPosition = getCanonicalPosition(insn.line);
+    builder.addDebugPosition(currentPosition);
   }
 
   @Override
-  public DebugPosition getDebugPositionAtOffset(int offset) {
+  public Position getDebugPositionAtOffset(int offset) {
+    if (offset == EXCEPTIONAL_SYNC_EXIT_OFFSET) {
+      return getSyntheticPosition();
+    }
     int index = instructionIndex(offset);
     if (index < 0 || instructionCount() <= index) {
-      return null;
+      return Position.none();
     }
     AbstractInsnNode insn = node.instructions.get(index);
     if (insn instanceof LabelNode) {
       insn = insn.getNext();
     }
-    if (insn instanceof LineNumberNode) {
-      LineNumberNode line = (LineNumberNode) insn;
-      return new DebugPosition(line.line, null);
+    while (insn != null && !(insn instanceof LineNumberNode)) {
+      insn = insn.getPrevious();
     }
-    return null;
+    if (insn != null) {
+      LineNumberNode line = (LineNumberNode) insn;
+      return getCanonicalPosition(line.line);
+    }
+    return Position.none();
+  }
+
+  @Override
+  public Position getCurrentPosition() {
+    return currentPosition;
+  }
+
+  private Position getCanonicalPosition(int line) {
+    return canonicalPositions.computeIfAbsent(line, l -> new Position(l, null));
+  }
+
+  // If we need to emit a synthetic position for exceptional monitor exits, we try to cook up a
+  // position that is not actually a valid program position, so as not to incorrectly position the
+  // user on an exit that is not the actual exit being taken. Our heuristic for this is that if the
+  // method has at least two positions we use the first position minus one as the synthetic exit.
+  // If the method only has one position it is safe to just use that position.
+  private Position getSyntheticPosition() {
+    if (syntheticPosition == null) {
+      int min = Integer.MAX_VALUE;
+      int max = Integer.MIN_VALUE;
+      for (Iterator it = node.instructions.iterator(); it.hasNext(); ) {
+        Object insn = it.next();
+        if (insn instanceof LineNumberNode) {
+          LineNumberNode lineNode = (LineNumberNode) insn;
+          min = Math.min(min, lineNode.line);
+          max = Math.max(max, lineNode.line);
+        }
+      }
+      syntheticPosition =
+          (min == Integer.MAX_VALUE)
+              ? Position.none()
+              : Position.synthetic(min < max ? min - 1 : min);
+    }
+    return syntheticPosition;
   }
 
   // Printing helpers.
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 71de2b6..ab3efdb 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -111,6 +111,7 @@
                   newValue,
                   newInvoke.outValue(),
                   graphLense.lookupType(invokedMethod.proto.returnType, method));
+              cast.setPosition(current.getPosition());
               iterator.add(cast);
               // If the current block has catch handlers split the check cast into its own block.
               if (newInvoke.getBlock().hasCatchHandlers()) {
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 3ed90fb..13d1551 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
@@ -7,7 +7,7 @@
 import com.android.tools.r8.ApiLevelException;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.code.CatchHandlers;
-import com.android.tools.r8.ir.code.DebugPosition;
+import com.android.tools.r8.ir.code.Position;
 
 /**
  * Abstraction of the input/source code for the IRBuilder.
@@ -24,7 +24,9 @@
 
   DebugLocalInfo getCurrentLocal(int register);
 
-  DebugPosition getDebugPositionAtOffset(int offset);
+  Position getCurrentPosition();
+
+  Position getDebugPositionAtOffset(int offset);
 
   /**
    * Trace block structure of the source-program.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index 388c72a..acac84a 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -315,6 +315,7 @@
     InvokeDirect constructorCall = new InvokeDirect(
         lambdaClass.constructor, null /* no return value */, arguments);
     instructions.add(constructorCall);
+    constructorCall.setPosition(newInstance.getPosition());
 
     // If we don't have catch handlers we are done.
     if (!constructorCall.getBlock().hasCatchHandlers()) {
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 7cb186f..94cbccb 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
@@ -57,6 +57,7 @@
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
@@ -100,9 +101,6 @@
 
 public class CodeRewriter {
 
-  private static final int UNKNOWN_CAN_THROW = 0;
-  private static final int CAN_THROW = 1;
-  private static final int CANNOT_THROW = 2;
   private static final int MAX_FILL_ARRAY_SIZE = 8 * Constants.KILOBYTE;
   // This constant was determined by experimentation.
   private static final int STOP_SHARED_CONSTANT_THRESHOLD = 50;
@@ -117,105 +115,6 @@
     this.libraryMethodsReturningReceiver = libraryMethodsReturningReceiver;
   }
 
-  /**
-   * Removes all debug positions that are not needed to maintain proper stack trace information.
-   * If a debug position is followed by another debug position and no instructions between the two
-   * can throw then it is unneeded (in a release build).
-   * If a block with a position has (normal) outgoing edges, this property depends on the
-   * possibility of the successors throwing before the next debug position is hit.
-   */
-  public static boolean removedUnneededDebugPositions(IRCode code) {
-    computeThrowsColorForAllBlocks(code);
-    for (BasicBlock block : code.blocks) {
-      InstructionListIterator iterator = block.listIterator();
-      while (iterator.hasNext()) {
-        Instruction instruction = iterator.next();
-        if (instruction.isDebugPosition()
-            && getThrowsColorForBlock(block, iterator.nextIndex()) == CANNOT_THROW) {
-          iterator.remove();
-        }
-      }
-    }
-    return true;
-  }
-
-  private static void computeThrowsColorForAllBlocks(IRCode code) {
-    // First pass colors blocks in reverse topological order, based on the instructions.
-    code.clearMarks();
-    List<BasicBlock> blocks = code.blocks;
-    ArrayList<BasicBlock> worklist = new ArrayList<>();
-    for (int i = blocks.size() - 1; i >= 0; i--) {
-      BasicBlock block = blocks.get(i);
-      // Mark the block as not-throwing if no successor implies otherwise.
-      // This ensures that a loop back to this block will be seen as non-throwing.
-      block.setColor(CANNOT_THROW);
-      int color = getThrowsColorForBlock(block, 0);
-      block.setColor(color);
-      if (color == UNKNOWN_CAN_THROW) {
-        worklist.add(block);
-      }
-    }
-    // A fixed point then ensures that we propagate the color backwards over normal edges.
-    ArrayList<BasicBlock> remaining = new ArrayList<>(worklist.size());
-    while (!worklist.isEmpty()) {
-      ImmutableList<BasicBlock> work = new ImmutableList.Builder<BasicBlock>()
-          .addAll(worklist)
-          .addAll(remaining)
-          .build();
-      worklist.clear();
-      remaining.clear();
-      for (BasicBlock block : work) {
-        if (!block.hasColor(UNKNOWN_CAN_THROW)) {
-          continue;
-        }
-        block.setColor(CANNOT_THROW);
-        int color = getThrowsColorForSuccessors(block);
-        block.setColor(color);
-        if (color == UNKNOWN_CAN_THROW) {
-          remaining.add(block);
-        } else {
-          for (BasicBlock predecessor : block.getNormalPredecessors()) {
-            if (predecessor.hasColor(UNKNOWN_CAN_THROW)) {
-              worklist.add(predecessor);
-            }
-          }
-        }
-      }
-    }
-    // Any remaining set of blocks represents a cycle of blocks containing no throwing instructions.
-    for (BasicBlock block : remaining) {
-      assert !block.canThrow();
-      block.setColor(CANNOT_THROW);
-    }
-  }
-
-  private static int getThrowsColorForBlock(BasicBlock block, int index) {
-    InstructionListIterator iterator = block.listIterator(index);
-    while (iterator.hasNext()) {
-      Instruction instruction = iterator.next();
-      if (instruction.isDebugPosition()) {
-        return CANNOT_THROW;
-      }
-      if (instruction.instructionTypeCanThrow()) {
-        return CAN_THROW;
-      }
-    }
-    return getThrowsColorForSuccessors(block);
-  }
-
-  private static int getThrowsColorForSuccessors(BasicBlock block) {
-    int color = CANNOT_THROW;
-    for (BasicBlock successor : block.getNormalSuccessors()) {
-      if (successor.hasColor(CAN_THROW)) {
-        return CAN_THROW;
-      }
-      if (successor.hasColor(UNKNOWN_CAN_THROW)) {
-        color = UNKNOWN_CAN_THROW;
-      }
-    }
-    return color;
-  }
-
   private static boolean removedTrivialGotos(IRCode code) {
     ListIterator<BasicBlock> iterator = code.listIterator();
     assert iterator.hasNext();
@@ -237,31 +136,6 @@
     return true;
   }
 
-  private static BasicBlock endOfGotoChain(BasicBlock block) {
-    block.mark();
-    BasicBlock target = block;
-    while (target.isTrivialGoto()) {
-      BasicBlock nextTarget = target.exit().asGoto().getTarget();
-      if (nextTarget.isMarked()) {
-        clearTrivialGotoMarks(block);
-        return nextTarget;
-      }
-      nextTarget.mark();
-      target = nextTarget;
-    }
-    clearTrivialGotoMarks(block);
-    return target;
-  }
-
-  private static void clearTrivialGotoMarks(BasicBlock block) {
-    while (block.isMarked()) {
-      block.clearMark();
-      if (block.isTrivialGoto()) {
-        block = block.exit().asGoto().getTarget();
-      }
-    }
-  }
-
   private static void collapsTrivialGoto(
       BasicBlock block, BasicBlock nextBlock, List<BasicBlock> blocksToRemove) {
 
@@ -270,7 +144,7 @@
       return;
     }
 
-    BasicBlock target = endOfGotoChain(block);
+    BasicBlock target = block.endOfGotoChain();
 
     boolean needed = false;
     if (target != nextBlock) {
@@ -284,7 +158,7 @@
 
     // This implies we are in a loop of GOTOs. In that case, we will iteratively remove each trival
     // GOTO one-by-one until the above base case (one block targeting itself) is left.
-    if (target == block) {
+    if (target == null) {
       target = block.exit().asGoto().getTarget();
     }
 
@@ -307,10 +181,10 @@
   private static void collapsIfTrueTarget(BasicBlock block) {
     If insn = block.exit().asIf();
     BasicBlock target = insn.getTrueTarget();
-    BasicBlock newTarget = endOfGotoChain(target);
+    BasicBlock newTarget = target.endOfGotoChain();
     BasicBlock fallthrough = insn.fallthroughBlock();
-    BasicBlock newFallthrough = endOfGotoChain(fallthrough);
-    if (target != newTarget) {
+    BasicBlock newFallthrough = fallthrough.endOfGotoChain();
+    if (newTarget != null && target != newTarget) {
       insn.getBlock().replaceSuccessor(target, newTarget);
       target.getPredecessors().remove(block);
       if (!newTarget.getPredecessors().contains(block)) {
@@ -335,8 +209,8 @@
     for (int j = 0; j < insn.targetBlockIndices().length; j++) {
       BasicBlock target = insn.targetBlock(j);
       if (target != fallthroughBlock) {
-        BasicBlock newTarget = endOfGotoChain(target);
-        if (target != newTarget && !replacedBlocks.contains(target)) {
+        BasicBlock newTarget = target.endOfGotoChain();
+        if (newTarget != null && target != newTarget && !replacedBlocks.contains(target)) {
           insn.getBlock().replaceSuccessor(target, newTarget);
           target.getPredecessors().remove(block);
           if (!newTarget.getPredecessors().contains(block)) {
@@ -351,6 +225,11 @@
   // TODO(sgjesse); Move this somewhere else, and reuse it for some of the other switch rewritings.
   public abstract static class InstructionBuilder<T> {
     protected int blockNumber;
+    protected final Position position;
+
+    protected InstructionBuilder(Position position) {
+      this.position = position;
+    }
 
     public abstract T self();
 
@@ -365,6 +244,10 @@
     private Int2ObjectSortedMap<BasicBlock> keyToTarget = new Int2ObjectAVLTreeMap<>();
     private BasicBlock fallthrough;
 
+    public SwitchBuilder(Position position) {
+      super(position);
+    }
+
     public SwitchBuilder self() {
       return this;
     }
@@ -406,6 +289,7 @@
       Integer fallthroughIndex =
           targetToSuccessorIndex.computeIfAbsent(fallthrough, b -> targetToSuccessorIndex.size());
       Switch newSwitch = new Switch(value, keys, targetBlockIndices, fallthroughIndex);
+      newSwitch.setPosition(position);
       BasicBlock newSwitchBlock = BasicBlock.createSwitchBlock(blockNumber, newSwitch);
       for (BasicBlock successor : targetToSuccessorIndex.keySet()) {
         newSwitchBlock.link(successor);
@@ -421,7 +305,8 @@
     private BasicBlock target;
     private BasicBlock fallthrough;
 
-    public IfBuilder(IRCode code) {
+    public IfBuilder(Position position, IRCode code) {
+      super(position);
       this.code = code;
     }
 
@@ -456,12 +341,14 @@
       BasicBlock ifBlock;
       if (right != 0) {
         ConstNumber rightConst = code.createIntConstant(right);
+        rightConst.setPosition(position);
         newIf = new If(Type.EQ, ImmutableList.of(left, rightConst.dest()));
         ifBlock = BasicBlock.createIfBlock(blockNumber, newIf, rightConst);
       } else {
         newIf = new If(Type.EQ, left);
         ifBlock = BasicBlock.createIfBlock(blockNumber, newIf);
       }
+      newIf.setPosition(position);
       ifBlock.link(target);
       ifBlock.link(fallthrough);
       return ifBlock;
@@ -477,6 +364,8 @@
       InstructionListIterator iterator, Switch theSwitch,
       List<IntList> switches, IntList keysToRemove) {
 
+    Position position = theSwitch.getPosition();
+
     // Extract the information from the switch before removing it.
     Int2ReferenceSortedMap<BasicBlock> keyToTarget = theSwitch.getKeyToTargetMap();
 
@@ -503,7 +392,7 @@
 
     // Build the switch-blocks backwards, to always have the fallthrough block in hand.
     for (int i = switches.size() - 1; i >= 0; i--) {
-      SwitchBuilder switchBuilder = new SwitchBuilder();
+      SwitchBuilder switchBuilder = new SwitchBuilder(position);
       switchBuilder.setValue(theSwitch.value());
       IntList keys = switches.get(i);
       for (int j = 0; j < keys.size(); j++) {
@@ -522,7 +411,7 @@
     for (int i = keysToRemove.size() - 1; i >= 0; i--) {
       int key = keysToRemove.getInt(i);
       BasicBlock peeledOffTarget = keyToTarget.get(key);
-      IfBuilder ifBuilder = new IfBuilder(code);
+      IfBuilder ifBuilder = new IfBuilder(position, code);
       ifBuilder
           .setLeft(theSwitch.value())
           .setRight(key)
@@ -561,6 +450,7 @@
               iterator.replaceCurrentInstruction(new If(Type.EQ, theSwitch.value()));
             } else {
               ConstNumber labelConst = code.createIntConstant(theSwitch.getFirstKey());
+              labelConst.setPosition(theSwitch.getPosition());
               iterator.previous();
               iterator.add(labelConst);
               Instruction dummy = iterator.next();
@@ -1155,6 +1045,7 @@
               if (newNumber == null) {
                 newNumber = ConstNumber.copyOf(code, definition);
                 it.add(newNumber);
+                newNumber.setPosition(current.getPosition());
                 oldToNew.put(definition, newNumber);
               }
               invoke.inValues().set(i, newNumber.outValue());
@@ -1242,6 +1133,7 @@
             assert constantValue.numberOfUsers() == constantValue.numberOfAllUsers();
             for (Instruction user : constantValue.uniqueUsers()) {
               ConstNumber newCstNum = ConstNumber.copyOf(code, constNumber);
+              newCstNum.setPosition(user.getPosition());
               InstructionListIterator iterator = user.getBlock().listIterator(user);
               iterator.previous();
               iterator.add(newCstNum);
@@ -1273,7 +1165,9 @@
         i.inValues().contains(instruction.outValue())
         || i.isJumpInstruction()
         || (hasCatchHandlers && i.instructionTypeCanThrow()));
-    insertAt.previous();
+    Instruction next = insertAt.previous();
+    instruction.forceSetPosition(
+        next.isGoto() ? next.asGoto().getTarget().getPosition() : next.getPosition());
     insertAt.add(instruction);
   }
 
@@ -1406,6 +1300,7 @@
           }
           InvokeNewArray invoke = new InvokeNewArray(
               dexItemFactory.stringArrayType, newArray.outValue(), stringValues);
+          invoke.setPosition(newArray.getPosition());
           it.detach();
           for (Value value : newArray.inValues()) {
             value.removeUser(newArray);
@@ -1425,6 +1320,7 @@
           int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
           NewArrayFilledData fillArray = new NewArrayFilledData(
               newArray.outValue(), elementSize, arraySize, contents);
+          fillArray.setPosition(newArray.getPosition());
           it.add(fillArray);
         }
         storesToRemoveForArray.put(newArray.outValue(), size);
@@ -1465,13 +1361,25 @@
     if (from.getBlock() != to.getBlock()) {
       return true;
     }
+    if (from.getPosition().isSome()
+        && to.getPosition().isSome()
+        && !from.getPosition().equals(to.getPosition())) {
+      return true;
+    }
     InstructionListIterator iterator = from.getBlock().listIterator(from);
+    Position position = null;
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
+      if (position == null) {
+        if (instruction.getPosition().isSome()) {
+          position = instruction.getPosition();
+        }
+      } else if (instruction.getPosition().isSome()
+          && !position.equals(instruction.getPosition())) {
+        return true;
+      }
       if (instruction == to) {
         return false;
-      } else if (instruction.isDebugPosition()) {
-        return true;
       }
     }
     throw new Unreachable();
@@ -1530,11 +1438,12 @@
     }
   }
 
-  private static class ExpressionEquivalence extends Equivalence<Instruction> {
+  private static class CSEExpressionEquivalence extends Equivalence<Instruction> {
 
     @Override
     protected boolean doEquivalent(Instruction a, Instruction b) {
-      if (a.getClass() != b.getClass() || !a.identicalNonValueParts(b)) {
+      // Note that we don't consider positions because CSE can at most remove an instruction.
+      if (a.getClass() != b.getClass() || !a.identicalNonValueNonPositionParts(b)) {
         return false;
       }
       // For commutative binary operations any order of in-values are equal.
@@ -1596,7 +1505,7 @@
   public void commonSubexpressionElimination(IRCode code) {
     final ListMultimap<Wrapper<Instruction>, Value> instructionToValue = ArrayListMultimap.create();
     final DominatorTree dominatorTree = new DominatorTree(code);
-    final ExpressionEquivalence equivalence = new ExpressionEquivalence();
+    final CSEExpressionEquivalence equivalence = new CSEExpressionEquivalence();
 
     for (int i = 0; i < dominatorTree.getSortedBlocks().length; i++) {
       BasicBlock block = dominatorTree.getSortedBlocks()[i];
@@ -1760,6 +1669,8 @@
                 Instruction newInstruction = new Xor(NumericType.INT, newOutValue, testValue,
                     trueNumber.isIntegerOne() ? trueValue : falseValue);
                 newInstruction.setBlock(phi.getBlock());
+                // The xor is replacing a phi so it does not have an actual position.
+                newInstruction.setPosition(phi.getBlock().getPosition());
                 phi.getBlock().getInstructions().add(0, newInstruction);
                 deadPhis++;
               }
@@ -1882,6 +1793,7 @@
 
             // First insert the constant value *before* the current instruction.
             ConstNumber zero = code.createIntConstant(0);
+            zero.setPosition(current.getPosition());
             assert iterator.hasPrevious();
             iterator.previous();
             iterator.add(zero);
@@ -1936,6 +1848,10 @@
     BasicBlock block = code.blocks.getFirst();
     InstructionListIterator iterator = block.listIterator();
 
+    // Attach some synthetic position to all inserted code.
+    Position position = Position.synthetic(1);
+    iterator.setInsertionPosition(position);
+
     // Split arguments into their own block.
     iterator.nextUntil(instruction -> !instruction.isArgument());
     iterator.previous();
@@ -1992,6 +1908,7 @@
         // Insert "if (argument != null) ...".
         successor = block.unlinkSingleSuccessor();
         If theIf = new If(If.Type.NE, argument);
+        theIf.setPosition(position);
         BasicBlock ifBlock = BasicBlock.createIfBlock(code.blocks.size(), theIf);
         code.blocks.add(ifBlock);
         // Fallthrough block must be added right after the if.
@@ -2009,8 +1926,10 @@
 
         // Fill code into the blocks.
         iterator = isNullBlock.listIterator();
+        iterator.setInsertionPosition(position);
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, nul)));
         iterator = isNotNullBlock.listIterator();
+        iterator.setInsertionPosition(position);
         value = code.createValue(MoveType.OBJECT);
         iterator.add(new InvokeVirtual(dexItemFactory.objectMethods.getClass, value,
             ImmutableList.of(arguments.get(i))));
@@ -2018,6 +1937,7 @@
       }
 
       iterator = eol.listIterator();
+      iterator.setInsertionPosition(position);
       if (i == arguments.size() - 1) {
         iterator.add(new InvokeVirtual(printLn, null, ImmutableList.of(out, closeParenthesis)));
       } else {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 241106c..12f1cf2 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -110,6 +110,7 @@
         assert replacement.outValue() != null;
         current.outValue().replaceUsers(replacement.outValue());
       }
+      replacement.setPosition(current.getPosition());
       iterator.add(replacement);
     }
   }
@@ -174,6 +175,7 @@
               Instruction knownConstReturn =
                   new ConstNumber(ConstType.fromMoveType(moveType), value, constant);
               invoke.outValue().replaceUsers(value);
+              knownConstReturn.setPosition(invoke.getPosition());
               iterator.add(knownConstReturn);
             }
           }
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 20c54f3..49dcb45 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
@@ -28,7 +28,6 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.BasicBlock.ThrowingInfo;
 import com.android.tools.r8.ir.code.CatchHandlers;
-import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.Div;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -40,6 +39,7 @@
 import com.android.tools.r8.ir.code.MoveType;
 import com.android.tools.r8.ir.code.Mul;
 import com.android.tools.r8.ir.code.NewInstance;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Rem;
 import com.android.tools.r8.ir.code.Sub;
 import com.android.tools.r8.ir.code.Value;
@@ -154,7 +154,8 @@
       for (int i = 0; i < instructions0.size(); i++) {
         Instruction i0 = instructions0.get(i);
         Instruction i1 = instructions1.get(i);
-        if (i0.getClass() != i1.getClass() || !i0.identicalNonValueParts(i1)) {
+        // Note that we don't consider positions as this optimization already breaks stack traces.
+        if (i0.getClass() != i1.getClass() || !i0.identicalNonValueNonPositionParts(i1)) {
           return false;
         }
         if ((i0.outValue() != null) != (i1.outValue() != null)) {
@@ -701,6 +702,7 @@
         List<Value> in = new ArrayList<>();
         returnValue = null;
         argumentsMapIndex = 0;
+        Position position = Position.none();
         { // Scope for 'instructions'.
           List<Instruction> instructions = getInstructionArray();
           for (int i = start; i < end; i++) {
@@ -709,7 +711,9 @@
               // Leave any const instructions.
               continue;
             }
-
+            if (position.isNone()) {
+              position = current.getPosition();
+            }
             // Prepare to remove the instruction.
             List<Value> inValues = orderedInValues(current, returnValue);
             for (int j = 0; j < inValues.size(); j++) {
@@ -737,6 +741,12 @@
         }
         Invoke outlineInvoke = new InvokeStatic(m, returnValue, in);
         outlineInvoke.setBlock(block);
+        outlineInvoke.setPosition(position);
+        if (position.isNone() && code.doAllThrowingInstructionsHavePositions()) {
+          // We have introduced a static invoke, but non of the outlines instructions could throw
+          // and none had a position. The code no longer has the previous property.
+          code.setAllThrowingInstructionsHavePositions(false);
+        }
         InstructionListIterator endIterator = block.listIterator(end - 1);
         Instruction instructionBeforeEnd = endIterator.next();
         invalidateInstructionArray(); // Because we're about to modify the original linked list.
@@ -948,11 +958,16 @@
     }
 
     @Override
-    public DebugPosition getDebugPositionAtOffset(int offset) {
+    public Position getDebugPositionAtOffset(int offset) {
       throw new Unreachable();
     }
 
     @Override
+    public Position getCurrentPosition() {
+      return Position.none();
+    }
+
+    @Override
     public boolean verifyCurrentInstructionCanThrow() {
       // TODO(sgjesse): Check more here?
       return true;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index e6916b8..39f622c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -101,6 +101,7 @@
             commonSuffixSize =
                 Math.min(commonSuffixSize, sharedSuffixSize(firstPred, pred, allocator));
           }
+          // Don't share a suffix that is just a single goto or return instruction.
           if (commonSuffixSize <= 1) {
             continue;
           }
@@ -198,9 +199,7 @@
       Instruction i0 = it0.previous();
       Instruction i1 = it1.previous();
       if (!i0.identicalAfterRegisterAllocation(i1, allocator)) {
-        // If the shared suffix follows a debug position at least one instruction must remain
-        // unshared to ensure the debug position is at a different pc than the shared suffix.
-        return i0.isDebugPosition() || i1.isDebugPosition() ? suffixSize - 1 : suffixSize;
+        return suffixSize;
       }
       suffixSize++;
     }
@@ -287,8 +286,9 @@
             }
             int outRegister = allocator.getRegisterForValue(outValue, instructionNumber);
             ConstNumber numberInRegister = registerToNumber.get(outRegister);
-            if (numberInRegister != null && numberInRegister.identicalNonValueParts(current)) {
+            if (numberInRegister != null && numberInRegister.identicalNonValueNonPositionParts(current)) {
               // This instruction is not needed, the same constant is already in this register.
+              // We don't consider the positions of the two (non-throwing) instructions.
               iterator.remove();
             } else {
               // Insert the current constant in the mapping. Make sure to clobber the second
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 7a2dc4a..21e6773 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -291,14 +291,18 @@
 
     for (BasicBlock block : blocks) {
       ListIterator<Instruction> instructionIterator = block.listIterator();
-      // Close ranges up-to and including the first instruction. Ends are exclusive so the range is
-      // closed at entry.
+      // Close ranges up-to but excluding the first instruction. Ends are exclusive but the values
+      // are live upon entering the first instruction.
       int entryIndex = block.entry().getNumber();
+      if (block.entry().isMoveException()) {
+        // Close locals at a move exception since they close as part of the exceptional transfer.
+        entryIndex++;
+      }
       {
         ListIterator<LocalRange> it = openRanges.listIterator(0);
         while (it.hasNext()) {
           LocalRange openRange = it.next();
-          if (openRange.end <= entryIndex) {
+          if (openRange.end < entryIndex) {
             it.remove();
             assert currentLocals.get(openRange.register) == openRange.local;
             currentLocals.remove(openRange.register);
@@ -308,8 +312,9 @@
       // Open ranges up-to but excluding the first instruction. Starts are inclusive but entry is
       // prior to the first instruction.
       while (nextStartingRange != null && nextStartingRange.start < entryIndex) {
-        // If the range is live at this index open it.
-        if (entryIndex < nextStartingRange.end) {
+        // If the range is live at this index open it. Again the end is inclusive here because the
+        // instruction is live at block entry if it is live at entry to the first instruction.
+        if (entryIndex <= nextStartingRange.end) {
           openRanges.add(nextStartingRange);
           assert !currentLocals.containsKey(nextStartingRange.register);
           currentLocals.put(nextStartingRange.register, nextStartingRange.local);
@@ -341,10 +346,10 @@
         ListIterator<LocalRange> it = openRanges.listIterator(0);
         Int2ReferenceMap<DebugLocalInfo> ending = new Int2ReferenceOpenHashMap<>();
         Int2ReferenceMap<DebugLocalInfo> starting = new Int2ReferenceOpenHashMap<>();
-        int endPositionCorrection = instruction.isDebugPosition() ? 1 : 0;
         while (it.hasNext()) {
           LocalRange openRange = it.next();
-          if (openRange.end <= index - endPositionCorrection) {
+          // Any local change is inserted after the instruction so end is inclusive.
+          if (openRange.end <= index) {
             it.remove();
             assert currentLocals.get(openRange.register) == openRange.local;
             currentLocals.remove(openRange.register);
@@ -378,13 +383,7 @@
         if (localsChanged && instruction.getBlock().exit() != instruction) {
           DebugLocalsChange change = createLocalsChange(ending, starting);
           if (change != null) {
-            if (instruction.isDebugPosition()) {
-              instructionIterator.previous();
-              instructionIterator.add(change);
-              instructionIterator.next();
-            } else {
-              instructionIterator.add(change);
-            }
+            instructionIterator.add(change);
           }
         }
         localsChanged = false;
@@ -2057,6 +2056,7 @@
           newArgument = createValue(argument.outType());
           Move move = new Move(newArgument, argument);
           move.setBlock(invoke.getBlock());
+          move.setPosition(invoke.getPosition());
           replaceArgument(invoke, i, newArgument);
           insertAt.add(move);
         }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index a669737..5d7606d 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.MoveType;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import java.util.ArrayList;
 import java.util.Deque;
@@ -30,12 +31,20 @@
   private int usedTempRegisters = 0;
   // Location at which to insert the scheduled moves.
   private final InstructionListIterator insertAt;
+  // Debug position associated with insertion point.
+  private final Position position;
   // The first available temporary register.
   private final int tempRegister;
 
-  public RegisterMoveScheduler(InstructionListIterator insertAt, int tempRegister) {
+  public RegisterMoveScheduler(
+      InstructionListIterator insertAt, int tempRegister, Position position) {
     this.insertAt = insertAt;
     this.tempRegister = tempRegister;
+    this.position = position;
+  }
+
+  public RegisterMoveScheduler(InstructionListIterator insertAt, int tempRegister) {
+    this(insertAt, tempRegister, Position.none());
   }
 
   public void addMove(RegisterMove move) {
@@ -133,6 +142,7 @@
       Value from = new FixedRegisterValue(move.type, valueMap.get(move.src));
       instruction = new Move(to, from);
     }
+    instruction.setPosition(position);
     insertAt.add(instruction);
     return move.dst;
 
@@ -152,7 +162,9 @@
       // of generating a new one.
       Value to = new FixedRegisterValue(moveWithSrc.type, tempRegister + usedTempRegisters);
       Value from = new FixedRegisterValue(moveWithSrc.type, valueMap.get(moveWithSrc.src));
-      insertAt.add(new Move(to, from));
+      Move instruction = new Move(to, from);
+      instruction.setPosition(position);
+      insertAt.add(instruction);
       valueMap.put(moveWithSrc.src, tempRegister + usedTempRegisters);
       usedTempRegisters += moveWithSrc.type == MoveType.WIDE ? 2 : 1;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index cd1d880..9154eff 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.MoveType;
+import com.android.tools.r8.ir.code.Position;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
@@ -226,6 +227,19 @@
 
   private void scheduleMovesBeforeInstruction(
       int tempRegister, int instruction, InstructionListIterator insertAt) {
+
+    Position position;
+    if (insertAt.hasPrevious() && insertAt.peekPrevious().isMoveException()) {
+      position = insertAt.peekPrevious().getPosition();
+    } else {
+      Instruction next = insertAt.peekNext();
+      assert next.getNumber() == instruction;
+      position = next.getPosition();
+      if (position.isNone() && next.isGoto()) {
+        position = next.asGoto().getTarget().getPosition();
+      }
+    }
+
     // Spill and restore moves for the incoming edge.
     Set<SpillMove> inMoves =
         instructionToInMoves.computeIfAbsent(instruction - 1, (k) -> new LinkedHashSet<>());
@@ -247,8 +261,8 @@
     outMoves.addAll(phiMoves);
 
     // Perform parallel move scheduling independently for the in and out moves.
-    scheduleMoves(tempRegister, inMoves, insertAt);
-    scheduleMoves(tempRegister, outMoves, insertAt);
+    scheduleMoves(tempRegister, inMoves, insertAt, position);
+    scheduleMoves(tempRegister, outMoves, insertAt, position);
   }
 
   // Remove restore moves that restore arguments. Since argument register reuse is
@@ -270,9 +284,8 @@
   }
 
   private void scheduleMoves(
-      int tempRegister, Collection<SpillMove> moves, InstructionListIterator insertAt) {
-    RegisterMoveScheduler scheduler =
-        new RegisterMoveScheduler(insertAt, tempRegister);
+      int tempRegister, Set<SpillMove> moves, InstructionListIterator insertAt, Position position) {
+    RegisterMoveScheduler scheduler = new RegisterMoveScheduler(insertAt, tempRegister, position);
     for (SpillMove move : moves) {
       // Do not generate moves to spill a value that can be rematerialized.
       if (move.to.isSpilledAndRematerializable(allocator)) {
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 c8fc818..637cbac 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
@@ -13,8 +13,8 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.Argument;
 import com.android.tools.r8.ir.code.CatchHandlers;
-import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.MoveType;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
@@ -196,11 +196,16 @@
   }
 
   @Override
-  public DebugPosition getDebugPositionAtOffset(int offset) {
+  public Position getDebugPositionAtOffset(int offset) {
     throw new Unreachable();
   }
 
   @Override
+  public Position getCurrentPosition() {
+    return Position.none();
+  }
+
+  @Override
   public final boolean verifyCurrentInstructionCanThrow() {
     return true;
   }
diff --git a/src/test/debugTestResources/FinallyBlock.java b/src/test/debugTestResources/FinallyBlock.java
new file mode 100644
index 0000000..4b30391
--- /dev/null
+++ b/src/test/debugTestResources/FinallyBlock.java
@@ -0,0 +1,37 @@
+// 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.
+
+public class FinallyBlock {
+
+  public static int finallyBlock(Throwable obj) throws Throwable {
+    int x = 21;
+    try {
+      if (obj != null) {
+        throw obj;
+      }
+    } catch (AssertionError e) {
+      x = e.getMessage().length() + 1;
+    } catch (RuntimeException e) {
+      x = e.getMessage().length() + 2;
+    } finally {
+      x *= 2;
+    }
+    return x;
+  }
+
+  private static int callFinallyBlock(Throwable obj) {
+    try {
+      return finallyBlock(obj);
+    } catch (Throwable e) {
+      return -1;
+    }
+  }
+
+  public static void main(String[] args) throws Throwable {
+    System.out.println(callFinallyBlock(null));
+    System.out.println(callFinallyBlock(new AssertionError("assert error")));
+    System.out.println(callFinallyBlock(new RuntimeException("runtime error")));
+    System.out.println(callFinallyBlock(new Throwable("throwable")));
+  }
+}
diff --git a/src/test/debugTestResources/SharedCode.java b/src/test/debugTestResources/SharedCode.java
new file mode 100644
index 0000000..f88fdbd
--- /dev/null
+++ b/src/test/debugTestResources/SharedCode.java
@@ -0,0 +1,25 @@
+// 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.
+
+public class SharedCode {
+
+  public static int sharedIf(int x) {
+    if (x == 0) {
+      doit(1); doit(2); doit(1); doit(2);
+    } else {
+      doit(1); doit(2); doit(1); doit(2);
+    }
+    return x;
+  }
+
+
+  public static void doit(int x) {
+    // nothing to do really.
+  }
+
+  public static void main(String[] args) {
+    System.out.println(sharedIf(0));
+    System.out.println(sharedIf(1));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java b/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java
index 6f6b7d0..7081921 100644
--- a/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java
+++ b/src/test/java/com/android/tools/r8/debug/BlockReorderingTest.java
@@ -4,7 +4,6 @@
 package com.android.tools.r8.debug;
 
 import org.junit.BeforeClass;
-import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -23,7 +22,6 @@
   }
 
   @Test
-  @Ignore("b/65618023")
   public void testConditionalReturn() throws Throwable {
     final String method = "conditionalReturn";
     runDebugTest(CLASS,
@@ -39,7 +37,6 @@
   }
 
   @Test
-  @Ignore("b/65618023")
   public void testInvertConditionalReturn() throws Throwable {
     final String method = "invertConditionalReturn";
     runDebugTest(CLASS,
@@ -55,7 +52,6 @@
   }
 
   @Test
-  @Ignore("b/65618023")
   public void testFallthroughReturn() throws Throwable {
     final String method = "fallthroughReturn";
     runDebugTest(CLASS,
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 ec32bd3..6686635 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -11,6 +11,8 @@
 import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
 import com.android.tools.r8.ToolHelper.DexVm;
 import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import com.android.tools.r8.shaking.ProguardRuleParserException;
 import com.android.tools.r8.utils.AndroidApiLevel;
@@ -88,9 +90,16 @@
     ART
   }
 
+  enum DexCompilerKind {
+    DX,
+    D8
+  }
+
   // Set to JAVA to run tests with java
   private static final RuntimeKind RUNTIME_KIND = RuntimeKind.ART;
 
+  private static final DexCompilerKind DEX_COMPILER_KIND = DexCompilerKind.D8;
+
   // Set to true to enable verbose logs
   private static final boolean DEBUG_TESTS = false;
 
@@ -123,35 +132,63 @@
       Consumer<InternalOptions> optionsConsumer,
       Consumer<ProguardConfiguration.Builder> pgConsumer)
       throws Exception {
-    // Convert jar to dex with d8 with debug info
-    jdwpDexD8 = compileToDex(null, JDWP_JAR);
     // TODO(zerny): supply a set of compilers to run with.
-    debuggeeDexD8 = compileToDex(optionsConsumer, DEBUGGEE_JAR);
+    // Compile the debuggee code first so potential compilation errors or debugging breakpoints hit
+    // here first.
+    debuggeeDexD8 = compileToDex(DEBUGGEE_JAR, optionsConsumer);
     debuggeeDexR8 = compileToDexViaR8(optionsConsumer, pgConsumer, DEBUGGEE_JAR);
-    debuggeeJava8DexD8 = compileToDex(options -> {
+    debuggeeJava8DexD8 = compileToDex(DEBUGGEE_JAVA8_JAR, options -> {
       // Enable desugaring for preN runtimes
       options.interfaceMethodDesugaring = OffOrAuto.Auto;
       if (optionsConsumer != null) {
         optionsConsumer.accept(options);
       }
-    }, DEBUGGEE_JAVA8_JAR);
-    debuggeeKotlinDexD8 = compileToDex(optionsConsumer, DEBUGGEE_KOTLIN_JAR);
+    });
+    debuggeeKotlinDexD8 = compileToDex(DEBUGGEE_KOTLIN_JAR, optionsConsumer);
+    // Convert jar to dex with d8 with debug info
+    jdwpDexD8 = compileToDex(JDWP_JAR, null);
   }
 
-  static Path compileToDex(Consumer<InternalOptions> optionsConsumer, Path jarToCompile)
+  protected static Path compileToDex(Path jarToCompile, Consumer<InternalOptions> optionsConsumer)
+      throws IOException, CompilationException {
+    return compileToDex(DEX_COMPILER_KIND, jarToCompile, optionsConsumer);
+  }
+
+  static Path compileToDex(
+      DexCompilerKind compiler, Path jarToCompile, Consumer<InternalOptions> optionsConsumer)
       throws IOException, CompilationException {
     int minSdk = ToolHelper.getMinApiLevelForDexVm(ToolHelper.getDexVm());
     assert jarToCompile.toFile().exists();
     Path dexOutputDir = temp.newFolder().toPath();
-    ToolHelper.runD8(
-        D8Command.builder()
-            .addProgramFiles(jarToCompile)
-            .setOutputPath(dexOutputDir)
-            .setMinApiLevel(minSdk)
-            .setMode(CompilationMode.DEBUG)
-            .addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(minSdk)))
-            .build(),
-        optionsConsumer);
+    switch (compiler) {
+      case D8:
+        {
+          ToolHelper.runD8(
+              D8Command.builder()
+                  .addProgramFiles(jarToCompile)
+                  .setOutputPath(dexOutputDir)
+                  .setMinApiLevel(minSdk)
+                  .setMode(CompilationMode.DEBUG)
+                  .addLibraryFiles(Paths.get(ToolHelper.getAndroidJar(minSdk)))
+                  .build(),
+              optionsConsumer);
+          break;
+        }
+      case DX:
+        {
+          ProcessResult result =
+              ToolHelper.runDX(
+                  new String[] {
+                    "--output=" + dexOutputDir,
+                    "--min-sdk-version=" + minSdk,
+                    jarToCompile.toString()
+                  });
+          Assert.assertEquals(result.stderr, 0, result.exitCode);
+          break;
+        }
+      default:
+        throw new Unreachable();
+    }
     return dexOutputDir.resolve("classes.dex");
   }
 
diff --git a/src/test/java/com/android/tools/r8/debug/FinallyBlockTest.java b/src/test/java/com/android/tools/r8/debug/FinallyBlockTest.java
new file mode 100644
index 0000000..83a0486
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/FinallyBlockTest.java
@@ -0,0 +1,79 @@
+// 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 org.junit.Test;
+
+/** Test single stepping behaviour of synchronized blocks. */
+public class FinallyBlockTest extends DebugTestBase {
+
+  public static final String CLASS = "FinallyBlock";
+  public static final String FILE = "FinallyBlock.java";
+
+  @Test
+  public void testEmptyBlock() throws Throwable {
+    final String method = "finallyBlock";
+    runDebugTest(CLASS,
+        breakpoint(CLASS, method),
+        run(),
+        checkLine(FILE, 8),
+        stepOver(),
+        checkLine(FILE, 10),
+        stepOver(),
+        checkLine(FILE, 18),
+        stepOver(),
+        checkLine(FILE, 19),
+        stepOver(),
+        checkLine(FILE, 20),
+        stepOver(),
+        checkLine(FILE, 25), // return in callFinallyBlock
+        run(),
+        checkLine(FILE, 8),
+        stepOver(),
+        checkLine(FILE, 10),
+        stepOver(),
+        checkLine(FILE, 11),
+        stepOver(),
+        checkLine(FILE, 13), // catch AE
+        stepOver(),
+        checkLine(FILE, 14),
+        stepOver(),
+        checkLine(FILE, 18),
+        stepOver(),
+        checkLine(FILE, 19),
+        stepOver(),
+        checkLine(FILE, 20),
+        stepOver(),
+        checkLine(FILE, 25), // return in callFinallyBlock
+        run(),
+        checkLine(FILE, 8),
+        stepOver(),
+        checkLine(FILE, 10),
+        stepOver(),
+        checkLine(FILE, 11),
+        stepOver(),
+        checkLine(FILE, 15), // catch RE
+        stepOver(),
+        checkLine(FILE, 16),
+        stepOver(),
+        checkLine(FILE, 18),
+        stepOver(),
+        checkLine(FILE, 19),
+        stepOver(),
+        checkLine(FILE, 20),
+        stepOver(),
+        checkLine(FILE, 25), // return in callFinallyBlock
+        run(),
+        checkLine(FILE, 8),
+        stepOver(),
+        checkLine(FILE, 10),
+        stepOver(),
+        checkLine(FILE, 11), // throw without catch
+        stepOver(),
+        checkLine(FILE, 18), // finally
+        stepOver(),
+        checkLine(FILE, 26), // catch in callFinallyBlock
+        run());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java b/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java
index 1b29472..2b0084d 100644
--- a/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java
+++ b/src/test/java/com/android/tools/r8/debug/JasminDebugTest.java
@@ -30,23 +30,20 @@
     final String className = "UselessCheckCast";
     final String sourcefile = className + ".j";
     final String methodName = "test";
-    runDebugTest(getExtraPaths(getBuilderForUselessCheckcast(className, methodName)),
+    List<Path> paths = getExtraPaths(getBuilderForUselessCheckcast(className, methodName));
+    runDebugTest(paths,
         className,
         breakpoint(className, methodName),
         run(),
-        checkLine(sourcefile, 8),
+        checkLine(sourcefile, 1),
         stepOver(),
-        checkLine(sourcefile, 9),
-        stepOver(),
-        checkLine(sourcefile, 10),
-        stepOver(),
-        checkLine(sourcefile, 12),
+        checkLine(sourcefile, 2),
         checkLocal("local"),
         stepOver(),
-        checkLine(sourcefile, 14),
+        checkLine(sourcefile, 3),
         checkNoLocal("local"),
         stepOver(),
-        checkLine(sourcefile, 15),
+        checkLine(sourcefile, 4),
         run());
   }
 
@@ -58,13 +55,17 @@
         ".limit stack 1",
         ".limit locals 3",
         ".var 1 is local Ljava/lang/Object; from Label1 to Label2",
+        ".line 1",
         " aload 0",
         " dup",
         " astore 1",
         " Label1:",
+        ".line 2",
         " checkcast " + testClassName,
         " Label2:",
+        ".line 3",
         " checkcast " + testClassName,
+        ".line 4",
         "return");
 
     clazz.addMainMethod(
@@ -84,14 +85,14 @@
 
     for (ClassBuilder clazz : classes) {
       ClassFile file = new ClassFile();
-      file.readJasmin(new StringReader(clazz.toString()), clazz.name, true);
+      file.readJasmin(new StringReader(clazz.toString()), clazz.name, false);
       Path path = out.toPath().resolve(clazz.name + ".class");
       Files.createDirectories(path.getParent());
       file.write(new FileOutputStream(path.toFile()));
       if (isRunningJava()) {
         extraPaths.add(path);
       } else {
-        extraPaths.add(compileToDex(null, path));
+        extraPaths.add(compileToDex(path, null));
       }
     }
 
diff --git a/src/test/java/com/android/tools/r8/debug/SharedCodeTest.java b/src/test/java/com/android/tools/r8/debug/SharedCodeTest.java
new file mode 100644
index 0000000..d4149e8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/SharedCodeTest.java
@@ -0,0 +1,36 @@
+// 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 org.junit.Test;
+
+public class SharedCodeTest extends DebugTestBase {
+
+  public static final String CLASS = "SharedCode";
+  public static final String FILE = "SharedCode.java";
+
+  @Test
+  public void testSharedIf() throws Throwable {
+    final String methodName = "sharedIf";
+    runDebugTest(CLASS,
+        breakpoint(CLASS, methodName),
+        run(),
+        checkMethod(CLASS, methodName),
+        checkLine(FILE, 8),
+        stepOver(),
+        checkLine(FILE, 9),
+        stepOver(),
+        checkLine(FILE, 13),
+        run(),
+        checkMethod(CLASS, methodName),
+        checkLine(FILE, 8),
+        stepOver(),
+        checkLine(FILE, 11),
+        stepOver(),
+        checkLine(FILE, 13),
+        run());
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
index 42f0174..fa6aa47 100644
--- a/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
+++ b/src/test/java/com/android/tools/r8/debug/SynchronizedBlockTest.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.debug;
 
-import org.junit.Ignore;
 import org.junit.Test;
 
 /**
@@ -71,7 +70,6 @@
   }
 
   @Test
-  @Ignore("b/65567013")
   public void testThrowingBlock() throws Throwable {
     final String method = "throwingBlock";
     runDebugTest(CLASS,
@@ -79,22 +77,27 @@
         run(),
         checkLine(FILE, 25),
         checkLocal("obj"),
+        checkNoLocal("x"),
         stepOver(),
         checkLine(FILE, 26),
         checkLocal("obj"),
         checkLocal("x"),
+        checkNoLocal("y"),
         stepOver(),
         checkLine(FILE, 27),
         checkLocal("obj"),
         checkLocal("x"),
+        checkNoLocal("y"),
         stepOver(),
         checkLine(FILE, 28), // synchronized block end
         checkLocal("obj"),
         checkLocal("x"),
+        checkNoLocal("y"),
         stepOver(),
         checkLine(FILE, 31), // catch handler
         checkLocal("obj"),
         checkNoLocal("x"),
+        checkNoLocal("y"),
         stepOver(),
         run());
   }
@@ -153,7 +156,6 @@
   }
 
   @Test
-  @Ignore("b/65567013")
   public void testNestedThrowingBlock() throws Throwable {
     final String method = "nestedThrowingBlock";
     runDebugTest(CLASS,
diff --git a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
index c911d37..7351206 100644
--- a/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
+++ b/src/test/java/com/android/tools/r8/ir/SplitBlockTest.java
@@ -19,6 +19,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.MoveType;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.smali.SmaliTestBase;
@@ -373,6 +374,8 @@
       iterator.previous();
       iterator.add(constInstruction);
       iterator.add(addInstruction);
+      addInstruction.setPosition(Position.none());
+      constInstruction.setPosition(Position.none());
     }
     // Run code and check result (code in the test object is updated).
     String result = test.run();
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 6eebf6b..6969c2d 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.ir.code.ConstType;
 import com.android.tools.r8.ir.code.MoveType;
 import com.android.tools.r8.ir.code.NumericType;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import org.junit.Test;
 
@@ -54,7 +55,9 @@
     ConstNumber const2 = new ConstNumber(ConstType.INT, value2, 2);
     Value value3 = new Value(2, MoveType.SINGLE, null);
     Add add0 = new Add(NumericType.INT, value3, value0, value1);
+    add0.setPosition(Position.none());
     Add add1 = new Add(NumericType.INT, value3, value0, value2);
+    add1.setPosition(Position.none());
     value0.computeNeedsRegister();
     assertTrue(value0.needsRegister());
     value1.computeNeedsRegister();
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 8c93a8a..2511d79 100644
--- a/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
+++ b/src/test/java/com/android/tools/r8/jasmin/DebugLocalTests.java
@@ -196,21 +196,23 @@
         "  default: CaseDefault",
 
         "Case1:",
+        ".line 3",
         "  ldc 42",
         "  istore 1",
         "LabelYStart:",
-        ".line 3",
+        ".line 4",
         "  invokestatic Test/ensureLine()V",
         "  goto AfterSwitch",
 
         "CaseDefault:",
+        ".line 5",
         "  ldc -42",
         "  istore 1",
-        ".line 4",
+        ".line 6",
         "  invokestatic Test/ensureLine()V",
 
         "AfterSwitch:",
-        ".line 5",
+        ".line 7",
         "  iload 1",
         "  ireturn",
         "LabelYEnd:",
@@ -246,9 +248,9 @@
     info.checkStartLine(1);
     info.checkLineHasExactLocals(1, "param", "int");
     info.checkLineHasExactLocals(2, "param", "int", "x", "int");
-    info.checkLineHasExactLocals(3, "param", "int", "y", "int");
     info.checkLineHasExactLocals(4, "param", "int", "y", "int");
-    info.checkLineHasExactLocals(5, "param", "int", "y", "int");
+    info.checkLineHasExactLocals(6, "param", "int", "y", "int");
+    info.checkLineHasExactLocals(7, "param", "int", "y", "int");
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index ad625cc..c609701 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -52,7 +52,7 @@
     File out = temp.newFolder("classes");
     for (ClassBuilder clazz : builder.getClasses()) {
       ClassFile file = new ClassFile();
-      file.readJasmin(new StringReader(clazz.toString()), clazz.name, true);
+      file.readJasmin(new StringReader(clazz.toString()), clazz.name, false);
       Path path = out.toPath().resolve(clazz.name + ".class");
       Files.createDirectories(path.getParent());
       file.write(new FileOutputStream(path.toFile()));
@@ -110,7 +110,7 @@
   private ProcessResult runDx(JasminBuilder builder, File classes, Path dex) throws Exception {
     for (ClassBuilder clazz : builder.getClasses()) {
       ClassFile file = new ClassFile();
-      file.readJasmin(new StringReader(clazz.toString()), clazz.name, true);
+      file.readJasmin(new StringReader(clazz.toString()), clazz.name, false);
       file.write(new FileOutputStream(classes.toPath().resolve(clazz.name + ".class").toFile()));
     }
     List<String> args = new ArrayList<>();
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 15cd3b6..c289c8e 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -37,8 +37,8 @@
 import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.ir.code.CatchHandlers;
-import com.android.tools.r8.ir.code.DebugPosition;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.SourceCode;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
@@ -593,7 +593,7 @@
                 code);
         IRCode ir = code.buildIR(method, options);
         RegisterAllocator allocator = new LinearScanRegisterAllocator(ir, options);
-        method.setCode(ir, allocator, factory);
+        method.setCode(ir, allocator, factory, options);
         directMethods[i] = method;
       }
       builder.addProgramClass(
@@ -705,11 +705,16 @@
     }
 
     @Override
-    public DebugPosition getDebugPositionAtOffset(int offset) {
+    public Position getDebugPositionAtOffset(int offset) {
       throw new Unreachable();
     }
 
     @Override
+    public Position getCurrentPosition() {
+      return Position.none();
+    }
+
+    @Override
     public boolean verifyRegister(int register) {
       throw new Unreachable();
     }