Support inlining of outline positions

Bug: b/335083074
Change-Id: I61b2688b177f970a8d927e5e203823b3a8107a6f
diff --git a/src/main/java/com/android/tools/r8/graph/Code.java b/src/main/java/com/android/tools/r8/graph/Code.java
index b4913b1..3e23b32 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -13,10 +13,12 @@
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.NumberGenerator;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.ir.code.Position.OutlineCallerPosition;
 import com.android.tools.r8.ir.code.Position.PositionBuilder;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions;
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.MutableMethodConversionOptions;
 import com.android.tools.r8.lightir.LirCode;
+import com.android.tools.r8.utils.Int2StructuralItemArrayMap;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import java.util.function.Consumer;
@@ -219,16 +221,48 @@
     if (!outermostCallee.isOutline() && !outermostCallee.isRemoveInnerFramesIfThrowingNpe()) {
       return calleePosition.replacePosition(outermostCallee, callerPosition);
     }
+    // If the position is inlining an outline then both frames are to be replaced by the
+    // translation of the line positions.
+    if (callerPosition.isOutlineCaller() && outermostCallee.isOutline()) {
+      OutlineCallerPosition outlineCaller = callerPosition.asOutlineCaller();
+      Int2StructuralItemArrayMap<Position> translation = outlineCaller.getOutlinePositions();
+      // Map the synthetic line found in the outline to the actual position as encoded in the
+      // outline-caller position table. Note that the outline may have more lines than the table
+      // if multiple outline instructions translate to the same original caller positions.
+      int outlineLine = outermostCallee.getLine();
+      Position translatedPosition = null;
+      for (int i = 0; i <= outlineLine; i++) {
+        Position result = translation.lookup(i);
+        if (result != null) {
+          translatedPosition = result;
+        }
+      }
+      assert translatedPosition != null;
+      // If the caller has outer frames compose them with the translated position.
+      if (callerPosition.hasCallerPosition()) {
+        translatedPosition =
+            translatedPosition.withOutermostCallerPosition(callerPosition.getCallerPosition());
+      }
+      // If the outline has additional inner frames append them as inner frames on the translation.
+      if (calleePosition.hasCallerPosition()) {
+        translatedPosition = calleePosition.replacePosition(outermostCallee, translatedPosition);
+      }
+      return translatedPosition;
+    }
 
     assert !callerPosition.isOutline();
-    assert !callerPosition.hasCallerPosition();
     // Copy the callee frame to ensure transfer of the outline key if present.
     PositionBuilder<?, ?> newCallerBuilder =
         outermostCallee.builderWithCopy().setMethod(callerPosition.getMethod());
+    // Transfer the callers outer frames if any.
+    if (callerPosition.hasCallerPosition()) {
+      newCallerBuilder.setCallerPosition(callerPosition.getCallerPosition());
+    }
     // If the callee is an outline, the line must be that of the outline to maintain the positions.
     if (outermostCallee.isOutline()) {
       // This does not implement inlining an outline. The cases this hits should always be a full
       // "move as inlining" to be correct.
+      assert !callerPosition.isOutlineCaller();
       assert callerPosition.isD8R8Synthesized();
       assert callerPosition.getLine() == 0;
     } else {