Introduce list of nested Positions to preserve callers for inlining.

Bug:
Change-Id: I2b97da816ee900ea3652646557f4ed77af53b445
diff --git a/src/main/java/com/android/tools/r8/graph/DexCode.java b/src/main/java/com/android/tools/r8/graph/DexCode.java
index a05299c..083c865 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.DexSourceCode;
 import com.android.tools.r8.ir.conversion.IRBuilder;
@@ -161,16 +162,18 @@
   @Override
   public IRCode buildIR(DexEncodedMethod encodedMethod, InternalOptions options)
       throws ApiLevelException {
-    DexSourceCode source = new DexSourceCode(this, encodedMethod);
+    DexSourceCode source = new DexSourceCode(this, encodedMethod, null);
     IRBuilder builder = new IRBuilder(encodedMethod, source, options);
     return builder.build();
   }
 
   public IRCode buildIR(
       DexEncodedMethod encodedMethod,
-      InternalOptions options, ValueNumberGenerator valueNumberGenerator)
+      InternalOptions options,
+      ValueNumberGenerator valueNumberGenerator,
+      Position callerPosition)
       throws ApiLevelException {
-    DexSourceCode source = new DexSourceCode(this, encodedMethod);
+    DexSourceCode source = new DexSourceCode(this, encodedMethod, callerPosition);
     IRBuilder builder = new IRBuilder(encodedMethod, source, options, valueNumberGenerator);
     return builder.build();
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
index f083794..908372a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntry.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableMap;
 import java.util.SortedSet;
@@ -16,19 +17,27 @@
   public final boolean prologueEnd;
   public final boolean epilogueBegin;
   public final ImmutableMap<Integer, DebugLocalInfo> locals;
+  public final DexMethod method;
+  public final Position callerPosition;
 
-  public DexDebugEntry(int address,
+  public DexDebugEntry(
+      int address,
       int line,
       DexString sourceFile,
       boolean prologueEnd,
       boolean epilogueBegin,
-      ImmutableMap<Integer, DebugLocalInfo> locals) {
+      ImmutableMap<Integer, DebugLocalInfo> locals,
+      DexMethod method,
+      Position callerPosition) {
     this.address = address;
     this.line = line;
     this.sourceFile = sourceFile;
     this.prologueEnd = prologueEnd;
     this.epilogueBegin = epilogueBegin;
     this.locals = locals;
+    this.method = method;
+    assert method != null;
+    this.callerPosition = callerPosition;
   }
 
   @Override
@@ -46,6 +55,10 @@
     if (sourceFile != null) {
       builder.append(", file ").append(sourceFile);
     }
+    if (callerPosition != null) {
+      builder.append(", method ").append(method);
+      builder.append(" <-(").append(callerPosition).append(")");
+    }
     if (prologueEnd) {
       builder.append(", prologue_end = true");
     }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
index c51e6e3..6ca2268 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEntryBuilder.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
 import com.google.common.collect.ImmutableMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceArrayMap;
@@ -43,10 +44,13 @@
   private int currentPc = 0;
   private int currentLine;
   private DexString currentFile = null;
+  private DexMethod currentMethod = null;
+  private Position currentCallerPosition = null;
   private boolean prologueEnd = false;
   private boolean epilogueBegin = false;
   private final Map<Integer, LocalEntry> locals = new HashMap<>();
   private final Int2ReferenceMap<DebugLocalInfo> arguments = new Int2ReferenceArrayMap<>();
+  private final DexMethod method;
 
   // Delayed construction of an entry. Is finalized once locals information has been collected.
   private DexDebugEntry pending = null;
@@ -57,11 +61,16 @@
   // Resulting debug entries.
   private List<DexDebugEntry> entries = new ArrayList<>();
 
-  public DexDebugEntryBuilder(int startLine) {
+  public DexDebugEntryBuilder(int startLine, DexMethod method) {
     currentLine = startLine;
+    this.method = method;
+    assert this.method != null;
+    currentMethod = method;
   }
 
   public DexDebugEntryBuilder(DexEncodedMethod method, DexItemFactory factory) {
+    this.method = method.method;
+    assert this.method != null;
     DexCode code = method.getCode().asDexCode();
     DexDebugInfo info = code.getDebugInfo();
     int argumentRegister = code.registerSize - code.incomingRegisterSize;
@@ -81,6 +90,7 @@
       argumentRegister += ValueType.fromDexType(types[i]).requiredRegisters();
     }
     currentLine = info.startLine;
+    currentMethod = this.method;
     for (DexDebugEvent event : info.events) {
       event.addToBuilder(this);
     }
@@ -94,6 +104,13 @@
     currentFile = file;
   }
 
+  public void setInlineFrame(DexMethod callee, Position caller) {
+    assert (caller == null && callee == method)
+        || (caller != null && caller.getOutermostCaller().method == method);
+    currentMethod = callee;
+    currentCallerPosition = caller;
+  }
+
   public void advancePC(int pcDelta) {
     assert pcDelta >= 0;
     currentPc += pcDelta;
@@ -133,15 +150,29 @@
     assert pcDelta >= 0;
     if (pending != null) {
       // Local changes contribute to the pending position entry.
-      entries.add(new DexDebugEntry(
-          pending.address, pending.line, pending.sourceFile,
-          pending.prologueEnd, pending.epilogueBegin,
-          getLocals()));
+      entries.add(
+          new DexDebugEntry(
+              pending.address,
+              pending.line,
+              pending.sourceFile,
+              pending.prologueEnd,
+              pending.epilogueBegin,
+              getLocals(),
+              pending.method,
+              pending.callerPosition));
     }
     currentPc += pcDelta;
     currentLine += lineDelta;
-    pending = new DexDebugEntry(
-        currentPc, currentLine, currentFile, prologueEnd, epilogueBegin, null);
+    pending =
+        new DexDebugEntry(
+            currentPc,
+            currentLine,
+            currentFile,
+            prologueEnd,
+            epilogueBegin,
+            null,
+            currentMethod,
+            currentCallerPosition);
     prologueEnd = false;
     epilogueBegin = false;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
index 8d64cf4..724c10b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -7,6 +7,8 @@
 import com.android.tools.r8.dex.DebugBytecodeWriter;
 import com.android.tools.r8.dex.IndexedItemCollection;
 import com.android.tools.r8.dex.MixedSectionCollection;
+import com.android.tools.r8.ir.code.Position;
+import java.util.Objects;
 
 abstract public class DexDebugEvent extends DexItem {
 
@@ -379,6 +381,47 @@
     }
   }
 
+  public static class SetInlineFrame extends DexDebugEvent {
+
+    final DexMethod callee;
+    final Position caller;
+
+    SetInlineFrame(DexMethod callee, Position caller) {
+      assert callee != null;
+      this.callee = callee;
+      this.caller = caller;
+    }
+
+    @Override
+    public void writeOn(DebugBytecodeWriter writer, ObjectToOffsetMapping mapping) {
+      // CallerPosition will not be written.
+    }
+
+    @Override
+    public void addToBuilder(DexDebugEntryBuilder builder) {
+      builder.setInlineFrame(callee, caller);
+    }
+
+    @Override
+    public String toString() {
+      return String.format("SET_INLINE_FRAME %s %s", callee, caller);
+    }
+
+    @Override
+    public int hashCode() {
+      return 31 * callee.hashCode() + Objects.hashCode(caller);
+    }
+
+    @Override
+    public boolean equals(Object other) {
+      if (!(other instanceof SetInlineFrame)) {
+        return false;
+      }
+      SetInlineFrame o = (SetInlineFrame) other;
+      return callee == o.callee && Objects.equals(caller, o.caller);
+    }
+  }
+
   public static class Default extends DexDebugEvent {
 
     final int value;
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 c52d12c..1c71b9b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEventBuilder.java
@@ -210,7 +210,7 @@
       }
       assert emittedPosition.isNone();
       startLine = position.line;
-      emittedPosition = position;
+      emittedPosition = new Position(position.line, null, method.method, null);
     }
     debugPositionListBuilder.add(position.line, position.line);
     emitAdvancementEvents(emittedPc, emittedPosition, pc, position, events, factory);
@@ -250,6 +250,10 @@
     if (nextPosition.file != previousPosition.file) {
       events.add(factory.createSetFile(nextPosition.file));
     }
+    if (nextPosition.method != previousPosition.method
+        || nextPosition.callerPosition != previousPosition.callerPosition) {
+      events.add(factory.createSetInlineFrame(nextPosition.method, nextPosition.callerPosition));
+    }
     if (lineDelta < Constants.DBG_LINE_BASE
         || lineDelta - Constants.DBG_LINE_BASE >= Constants.DBG_LINE_RANGE) {
       events.add(factory.createAdvanceLine(lineDelta));
diff --git a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
index ae669a2..99d1227 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugInfo.java
@@ -24,8 +24,8 @@
     hashCode();
   }
 
-  public List<DexDebugEntry> computeEntries() {
-    DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine);
+  public List<DexDebugEntry> computeEntries(DexMethod method) {
+    DexDebugEntryBuilder builder = new DexDebugEntryBuilder(startLine, method);
     for (DexDebugEvent event : events) {
       event.addToBuilder(builder);
     }
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 c8932be..8fbcbce 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -24,6 +24,7 @@
 import com.android.tools.r8.dex.MixedSectionCollection;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Invoke;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.ir.conversion.DexBuilder;
@@ -273,7 +274,15 @@
       throws ApiLevelException {
     return code == null
         ? null
-        : code.asDexCode().buildIR(this, options, valueNumberGenerator);
+        : code.asDexCode().buildIR(this, options, valueNumberGenerator, null);
+  }
+
+  public IRCode buildIR(
+      InternalOptions options, ValueNumberGenerator valueNumberGenerator, Position callerPosition)
+      throws ApiLevelException {
+    return code == null
+        ? null
+        : code.asDexCode().buildIR(this, options, valueNumberGenerator, callerPosition);
   }
 
   public void setCode(Code code) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index a7d44ba..4ca9e94 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -12,8 +12,10 @@
 import com.android.tools.r8.graph.DexDebugEvent.RestartLocal;
 import com.android.tools.r8.graph.DexDebugEvent.SetEpilogueBegin;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
+import com.android.tools.r8.graph.DexDebugEvent.SetInlineFrame;
 import com.android.tools.r8.graph.DexDebugEvent.SetPrologueEnd;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.naming.NamingLens;
 import com.google.common.collect.ImmutableSet;
 import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
@@ -47,6 +49,7 @@
   private final SetEpilogueBegin setEpilogueBegin = new SetEpilogueBegin();
   private final SetPrologueEnd setPrologueEnd = new SetPrologueEnd();
   private final Map<DexString, SetFile> setFiles = new HashMap<>();
+  private final Map<SetInlineFrame, SetInlineFrame> setInlineFrames = new HashMap<>();
 
   boolean sorted = false;
 
@@ -452,6 +455,13 @@
     }
   }
 
+  // TODO(tamaskenez) b/69024229 Measure if canonicalization is worth it.
+  public SetInlineFrame createSetInlineFrame(DexMethod callee, Position caller) {
+    synchronized (setInlineFrames) {
+      return setInlineFrames.computeIfAbsent(new SetInlineFrame(callee, caller), p -> p);
+    }
+  }
+
   public boolean isConstructor(DexMethod method) {
     return method.name == constructorMethodName;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/JarCode.java b/src/main/java/com/android/tools/r8/graph/JarCode.java
index 44bc529..ce2de41 100644
--- a/src/main/java/com/android/tools/r8/graph/JarCode.java
+++ b/src/main/java/com/android/tools/r8/graph/JarCode.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.Resource.Origin;
 import com.android.tools.r8.errors.InvalidDebugInfoException;
 import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueNumberGenerator;
 import com.android.tools.r8.ir.conversion.IRBuilder;
 import com.android.tools.r8.ir.conversion.JarSourceCode;
@@ -94,39 +95,49 @@
       throws ApiLevelException {
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, options, null)
-        : internalBuild(encodedMethod, options, null);
+        ? internalBuildWithLocals(encodedMethod, options, null, null)
+        : internalBuild(encodedMethod, options, null, null);
   }
 
   public IRCode buildIR(
-      DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator generator,
+      Position callerPosition)
       throws ApiLevelException {
     assert generator != null;
     triggerDelayedParsingIfNeccessary();
     return options.debug
-        ? internalBuildWithLocals(encodedMethod, options, generator)
-        : internalBuild(encodedMethod, options, generator);
+        ? internalBuildWithLocals(encodedMethod, options, generator, callerPosition)
+        : internalBuild(encodedMethod, options, generator, callerPosition);
   }
 
   private IRCode internalBuildWithLocals(
-      DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator generator,
+      Position callerPosition)
       throws ApiLevelException {
     try {
-      return internalBuild(encodedMethod, options, generator);
+      return internalBuild(encodedMethod, options, generator, callerPosition);
     } catch (InvalidDebugInfoException e) {
       options.warningInvalidDebugInfo(encodedMethod, origin, e);
       node.localVariables.clear();
-      return internalBuild(encodedMethod, options, generator);
+      return internalBuild(encodedMethod, options, generator, callerPosition);
     }
   }
 
   private IRCode internalBuild(
-      DexEncodedMethod encodedMethod, InternalOptions options, ValueNumberGenerator generator)
+      DexEncodedMethod encodedMethod,
+      InternalOptions options,
+      ValueNumberGenerator generator,
+      Position callerPosition)
       throws ApiLevelException {
     if (!options.debug) {
       node.localVariables.clear();
     }
-    JarSourceCode source = new JarSourceCode(clazz, node, application);
+    JarSourceCode source =
+        new JarSourceCode(clazz, node, application, encodedMethod.method, callerPosition);
     IRBuilder builder =
         (generator == null)
             ? new IRBuilder(encodedMethod, source, options)
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
index 08298bb..9b6ac1b 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Position.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Position.java
@@ -3,42 +3,75 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import java.util.Objects;
 
 public class Position {
 
-  private static final Position NO_POSITION = new Position(-1, null, false);
+  private static final Position NO_POSITION = new Position(-1, null, false, null, null);
 
   public final int line;
   public final DexString file;
   public final boolean synthetic;
 
-  public Position(int line, DexString file) {
-    this(line, file, false);
+  // If there's no inlining, callerPosition is null.
+  //
+  // For an inlined instruction its Position contains the inlinee's line and method and
+  // callerPosition is the position of the invoke instruction in the caller.
+
+  public final DexMethod method;
+  public final Position callerPosition;
+
+  public Position(int line, DexString file, DexMethod method, Position callerPosition) {
+    this(line, file, false, method, callerPosition);
     assert line >= 0;
   }
 
-  private Position(int line, DexString file, boolean synthetic) {
+  private Position(
+      int line, DexString file, boolean synthetic, DexMethod method, Position callerPosition) {
     this.line = line;
     this.file = file;
     this.synthetic = synthetic;
+    this.method = method;
+    this.callerPosition = callerPosition;
+    assert callerPosition == null || callerPosition.method != null;
+    assert line == -1 || method != null; // It's NO_POSITION or must have valid method.
   }
 
-  public static Position synthetic(int line) {
-    return new Position(line, null, true);
+  public static Position synthetic(int line, DexMethod method, Position callerPosition) {
+    assert line >= 0;
+    return new Position(line, null, true, method, callerPosition);
   }
 
   public static Position none() {
     return NO_POSITION;
   }
 
+  // This factory method is used by the Inliner to create Positions when the caller has no valid
+  // positions. Since the callee still may have valid positions we need a non-null Position to set
+  // it as the caller of the inlined Positions.
+  public static Position noneWithMethod(DexMethod method, Position callerPosition) {
+    assert method != null;
+    return new Position(-1, null, false, method, callerPosition);
+  }
+
   public boolean isNone() {
-    return this == NO_POSITION;
+    return line == -1;
   }
 
   public boolean isSome() {
-    return this != NO_POSITION;
+    return !isNone();
+  }
+
+  // Follow the linked list of callerPositions and return the last.
+  // Return this if no inliner.
+  public Position getOutermostCaller() {
+    Position lastPosition = this;
+    while (lastPosition.callerPosition != null) {
+      lastPosition = lastPosition.callerPosition;
+    }
+    return lastPosition;
   }
 
   @Override
@@ -48,7 +81,11 @@
     }
     if (other instanceof Position) {
       Position o = (Position) other;
-      return !isNone() && line == o.line && file == o.file;
+      return !isNone()
+          && line == o.line
+          && file == o.file
+          && method == o.method
+          && Objects.equals(callerPosition, o.callerPosition);
     }
     return false;
   }
@@ -58,11 +95,12 @@
     int result = line;
     result = 31 * result + Objects.hashCode(file);
     result = 31 * result + (synthetic ? 1 : 0);
+    result = 31 * result + Objects.hashCode(method);
+    result = 31 * result + Objects.hashCode(callerPosition);
     return result;
   }
 
-  @Override
-  public String toString() {
+  private String toString(boolean forceMethod) {
     if (isNone()) {
       return "--";
     }
@@ -70,7 +108,18 @@
     if (file != null) {
       builder.append(file).append(":");
     }
-    builder.append(line);
+    if (method != null && (forceMethod || callerPosition != null)) {
+      builder.append("[").append(method).append("]");
+    }
+    builder.append("#").append(line);
+    if (callerPosition != null) {
+      builder.append(" <- ").append(callerPosition.toString(true));
+    }
     return builder.toString();
   }
+
+  @Override
+  public String toString() {
+    return toString(false);
+  }
 }
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 e353a70..eb055f2 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
@@ -37,6 +37,7 @@
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
@@ -73,17 +74,28 @@
   private final List<ValueType> argumentTypes;
 
   private List<DexDebugEntry> debugEntries = null;
+  private Position callerPosition; // In case of inlining the position of the invoke in the caller.
+  private final DexMethod method;
 
-  public DexSourceCode(DexCode code, DexEncodedMethod method) {
+  public DexSourceCode(DexCode code, DexEncodedMethod method, Position callerPosition) {
     this.code = code;
     this.proto = method.method.proto;
     this.accessFlags = method.accessFlags;
     argumentTypes = computeArgumentTypes();
     DexDebugInfo info = code.getDebugInfo();
     if (info != null) {
-      debugEntries = info.computeEntries();
+      debugEntries = info.computeEntries(method.method);
       canonicalPositions = new HashMap<>(debugEntries.size());
     }
+    if (info != null && callerPosition != null) {
+      // Canonicalize callerPosition
+      this.callerPosition = callerPosition;
+      canonicalPositions.put(callerPosition, callerPosition);
+    } else {
+      this.callerPosition = null;
+    }
+
+    this.method = method.method;
   }
 
   @Override
@@ -217,15 +229,44 @@
     if (current == null) {
       currentPosition = Position.none();
     } else {
-      currentPosition = getCanonicalPosition(current);
+      currentPosition = getCanonicalPositionAppendCaller(current);
       if (current.address == offset) {
         builder.addDebugPosition(currentPosition);
       }
     }
   }
 
-  private Position getCanonicalPosition(DexDebugEntry entry) {
-    return canonicalPositions.computeIfAbsent(new Position(entry.line, entry.sourceFile), p -> p);
+  private Position getCanonicalPosition(Position position) {
+    Position canonical = canonicalPositions.putIfAbsent(position, position);
+    return canonical != null ? canonical : position;
+  }
+
+  private Position canonicalizeCallerPosition(Position caller) {
+    if (caller == null) {
+      return callerPosition;
+    }
+    if (caller.callerPosition == null && callerPosition == null) {
+      return getCanonicalPosition(caller);
+    }
+    Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
+    return getCanonicalPosition(
+        caller.isNone()
+            ? Position.noneWithMethod(caller.method, callerOfCaller)
+            : new Position(caller.line, caller.file, caller.method, callerOfCaller));
+  }
+
+  private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
+    // If this instruction has already been inlined then this.method must be the outermost caller.
+    assert (entry.callerPosition == null && entry.method == method)
+        || (entry.callerPosition != null
+            && entry.callerPosition.getOutermostCaller().method == method);
+
+    return getCanonicalPosition(
+        new Position(
+            entry.line,
+            entry.sourceFile,
+            entry.method,
+            canonicalizeCallerPosition(entry.callerPosition)));
   }
 
   @Override
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 04a2685..700d9da 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
@@ -181,12 +181,22 @@
   // Cooked position to indicate positions in synthesized code (ie, for synchronization).
   private Position syntheticPosition = null;
 
-  public JarSourceCode(DexType clazz, MethodNode node, JarApplicationReader application) {
+  private final DexMethod method;
+  private final Position callerPosition;
+
+  public JarSourceCode(
+      DexType clazz,
+      MethodNode node,
+      JarApplicationReader application,
+      DexMethod method,
+      Position callerPosition) {
     assert node != null;
     assert node.desc != null;
     this.node = node;
     this.application = application;
+    this.method = method;
     this.clazz = clazz;
+    this.callerPosition = callerPosition;
     parameterTypes = Arrays.asList(Type.getArgumentTypes(node.desc));
     state = new JarState(node.maxLocals, node.localVariables, this, application);
     AbstractInsnNode first = node.instructions.getFirst();
@@ -2840,7 +2850,8 @@
   }
 
   private Position getCanonicalPosition(int line) {
-    return canonicalPositions.computeIfAbsent(line, l -> new Position(l, null));
+    return canonicalPositions.computeIfAbsent(
+        line, l -> new Position(l, null, method, callerPosition));
   }
 
   // If we need to emit a synthetic position for exceptional monitor exits, we try to cook up a
@@ -2862,8 +2873,8 @@
       }
       syntheticPosition =
           (min == Integer.MAX_VALUE)
-              ? Position.none()
-              : Position.synthetic(min < max ? min - 1 : min);
+              ? Position.noneWithMethod(method, callerPosition)
+              : Position.synthetic(min < max ? min - 1 : min, method, callerPosition);
     }
     return syntheticPosition;
   }
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 3c7fe40..5386163 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
@@ -1986,7 +1986,7 @@
     InstructionListIterator iterator = block.listIterator();
 
     // Attach some synthetic position to all inserted code.
-    Position position = Position.synthetic(1);
+    Position position = Position.synthetic(1, method.method, null);
     iterator.setInsertionPosition(position);
 
     // Split arguments into their own block.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index b15d254..cd51b25 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeMethod;
+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.ir.conversion.CallSiteInformation;
@@ -231,18 +232,23 @@
       return reason != Reason.SIMPLE;
     }
 
-    public IRCode buildIR(ValueNumberGenerator generator, AppInfoWithSubtyping appInfo,
-        GraphLense graphLense, InternalOptions options) throws ApiLevelException {
+    public IRCode buildIR(
+        ValueNumberGenerator generator,
+        AppInfoWithSubtyping appInfo,
+        GraphLense graphLense,
+        InternalOptions options,
+        Position callerPosition)
+        throws ApiLevelException {
       if (target.isProcessed()) {
         assert target.getCode().isDexCode();
-        return target.buildIR(options, generator);
+        return target.buildIR(options, generator, callerPosition);
       } else {
         // Build the IR for a yet not processed method, and perform minimal IR processing.
         IRCode code;
         if (target.getCode().isJarCode()) {
-          code = target.getCode().asJarCode().buildIR(target, options, generator);
+          code = target.getCode().asJarCode().buildIR(target, options, generator, callerPosition);
         } else {
-          code = target.getCode().asDexCode().buildIR(target, options, generator);
+          code = target.getCode().asDexCode().buildIR(target, options, generator, callerPosition);
         }
         new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
         return code;
@@ -359,8 +365,16 @@
           InlineAction result = invoke.computeInlining(oracle);
           if (result != null) {
             DexEncodedMethod target = result.target;
-            IRCode inlinee = result
-                .buildIR(code.valueNumberGenerator, appInfo, graphLense, options);
+            Position invokePosition = invoke.getPosition();
+            if (invokePosition.method == null) {
+              assert invokePosition.isNone();
+              invokePosition = Position.noneWithMethod(method.method, null);
+            }
+            assert invokePosition.getOutermostCaller().method == method.method;
+
+            IRCode inlinee =
+                result.buildIR(
+                    code.valueNumberGenerator, appInfo, graphLense, options, invokePosition);
             if (inlinee != null) {
               // TODO(64432527): Get rid of this additional check by improved inlining.
               if (block.hasCatchHandlers() && inlinee.computeNormalExitBlocks().isEmpty()) {