Account for preamble position when reprocessing ir

Bug: b/263457890
Change-Id: I4721c611e6f5475d2cf18092b53520c12d8469c1
diff --git a/src/main/java/com/android/tools/r8/graph/CfCode.java b/src/main/java/com/android/tools/r8/graph/CfCode.java
index fff7a6e..82e4b48 100644
--- a/src/main/java/com/android/tools/r8/graph/CfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/CfCode.java
@@ -399,6 +399,23 @@
     return true;
   }
 
+  public Position getPreamblePosition() {
+    Position preamble = null;
+    for (CfInstruction instruction : instructions) {
+      if (instruction.isLabel()) {
+        continue;
+      }
+      if (instruction.isPosition()) {
+        Position candidate = instruction.asPosition().getPosition();
+        if (candidate.getLine() == 0) {
+          preamble = candidate;
+        }
+      }
+      break;
+    }
+    return preamble;
+  }
+
   private static class PrunePreambleMethodVisitor extends MethodVisitor {
 
     private final AppView<?> appView;
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 bc0c72b..c70e226 100644
--- a/src/main/java/com/android/tools/r8/graph/Code.java
+++ b/src/main/java/com/android/tools/r8/graph/Code.java
@@ -187,7 +187,7 @@
     throw new Unreachable();
   }
 
-  public Position newInlineePosition(
+  public static Position newInlineePosition(
       Position callerPosition, Position oldPosition, boolean isCalleeD8R8Synthesized) {
     Position outermostCaller = oldPosition.getOutermostCaller();
     if (!isCalleeD8R8Synthesized) {
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 5dcc091..c7e251e 100644
--- a/src/main/java/com/android/tools/r8/graph/DexCode.java
+++ b/src/main/java/com/android/tools/r8/graph/DexCode.java
@@ -3,6 +3,8 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
+import static com.android.tools.r8.graph.DexDebugEventBuilder.addDefaultEventWithAdvancePcIfNecessary;
+import static com.android.tools.r8.utils.DexDebugUtils.computePreamblePosition;
 import static com.android.tools.r8.utils.DexDebugUtils.verifySetPositionFramesFollowedByDefaultEvent;
 
 import com.android.tools.r8.dex.CodeToKeep;
@@ -15,6 +17,7 @@
 import com.android.tools.r8.dex.code.DexReturnVoid;
 import com.android.tools.r8.dex.code.DexSwitchPayload;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
+import com.android.tools.r8.graph.DexDebugEvent.AdvanceLine;
 import com.android.tools.r8.graph.DexDebugEvent.Default;
 import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugEvent.StartLocal;
@@ -35,7 +38,7 @@
 import com.android.tools.r8.ir.conversion.MethodConversionOptions.ThrowingMethodConversionOptions;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.ArrayUtils;
-import com.android.tools.r8.utils.IntBox;
+import com.android.tools.r8.utils.DexDebugUtils.PositionInfo;
 import com.android.tools.r8.utils.RetracerForCodePrinting;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.structural.Equatable;
@@ -47,11 +50,13 @@
 import com.google.common.base.Strings;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import java.nio.ShortBuffer;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
@@ -297,7 +302,8 @@
 
   private DexDebugInfo debugInfoAsInlining(
       DexMethod caller, DexMethod callee, boolean isCalleeD8R8Synthesized, DexItemFactory factory) {
-    Position callerPosition = SyntheticPosition.builder().setLine(0).setMethod(caller).build();
+    Position callerPosition =
+        SyntheticPosition.builder().setLine(0).setMethod(caller).setIsD8R8Synthesized(true).build();
     EventBasedDebugInfo eventBasedInfo = DexDebugInfo.convertToEventBased(this, factory);
     if (eventBasedInfo == null) {
       // If the method has no debug info we generate a preamble position to denote the inlining.
@@ -318,55 +324,78 @@
             factory.createPositionFrame(preamblePosition), factory.zeroChangeDefaultEvent
           });
     }
-    // The inline position should match the first actual callee position, so either its actual line
-    // at first instruction or it is a synthetic preamble.
-    int lineAtPcZero = findLineAtPcZero(callee, eventBasedInfo);
-    PositionBuilder<?, ?> frameBuilder =
-        lineAtPcZero == -1
-            ? SyntheticPosition.builder().setLine(0)
-            : SourcePosition.builder().setLine(lineAtPcZero);
+    // At this point we know we had existing debug information:
+    // 1) There is an already existing SET_POSITION_FRAME before a default event and the default
+    //    event sets a position for PC 0
+    //    => Nothing to do except append caller.
+    // 2) There is no SET_POSITION_FRAME before a default event and a default event covers PC 0.
+    //    => Insert a SET_POSITION_FRAME
+    // 3) There is a SET_POSITION_FRAME and no default event setting a position for PC 0.
+    //    => Insert a default event and potentially advance line.
+    // 4) There is no SET_POSITION_FRAME and no default event setting a position for PC 0..
+    //    => Insert a SET_POSITION_FRAME and a default event and potentially advance line.
+    PositionInfo positionInfo = computePreamblePosition(callee, eventBasedInfo);
     DexDebugEvent[] oldEvents = eventBasedInfo.events;
-    DexDebugEvent[] newEvents = new DexDebugEvent[oldEvents.length + 1];
-    int i = 0;
-    newEvents[i++] =
-        new SetPositionFrame(
-            isCalleeD8R8Synthesized
-                ? callerPosition
-                : frameBuilder.setMethod(callee).setCallerPosition(callerPosition).build());
+    boolean adjustStartPosition =
+        !positionInfo.hasLinePositionAtPcZero() && debugInfo.getStartLine() > 0;
+    List<DexDebugEvent> newEvents =
+        new ArrayList<>(
+            oldEvents.length
+                + (positionInfo.hasFramePosition() ? 0 : 1)
+                + (positionInfo.hasLinePositionAtPcZero() ? 0 : 1)
+                + (adjustStartPosition ? 1 : 0)); // Potentially an advance line.
+    if (!positionInfo.hasFramePosition()) {
+      PositionBuilder<?, ?> calleePositionBuilder =
+          isCalleeD8R8Synthesized ? SyntheticPosition.builder() : SourcePosition.builder();
+      newEvents.add(
+          factory.createPositionFrame(
+              newInlineePosition(
+                  callerPosition,
+                  calleePositionBuilder
+                      .setLine(
+                          positionInfo.hasLinePositionAtPcZero()
+                              ? positionInfo.getLinePositionAtPcZero()
+                              : 0)
+                      .setMethod(callee)
+                      .setIsD8R8Synthesized(isCalleeD8R8Synthesized)
+                      .build(),
+                  isCalleeD8R8Synthesized)));
+    }
+    if (!positionInfo.hasLinePositionAtPcZero()) {
+      newEvents.add(factory.zeroChangeDefaultEvent);
+    }
     for (DexDebugEvent event : oldEvents) {
-      if (event instanceof SetPositionFrame) {
-        SetPositionFrame oldFrame = (SetPositionFrame) event;
+      if (event.isAdvanceLine() && adjustStartPosition) {
+        AdvanceLine advanceLine = event.asAdvanceLine();
+        newEvents.add(factory.createAdvanceLine(debugInfo.getStartLine() + advanceLine.delta));
+        adjustStartPosition = false;
+      } else if (event.isDefaultEvent() && adjustStartPosition) {
+        Default oldDefaultEvent = event.asDefaultEvent();
+        addDefaultEventWithAdvancePcIfNecessary(
+            oldDefaultEvent.getLineDelta() + debugInfo.getStartLine(),
+            oldDefaultEvent.getPCDelta(),
+            newEvents,
+            factory);
+        adjustStartPosition = false;
+      } else if (event.isPositionFrame()) {
+        SetPositionFrame oldFrame = event.asSetPositionFrame();
         assert oldFrame.getPosition() != null;
-        newEvents[i++] =
+        newEvents.add(
             new SetPositionFrame(
                 newInlineePosition(
-                    callerPosition, oldFrame.getPosition(), isCalleeD8R8Synthesized));
+                    callerPosition, oldFrame.getPosition(), isCalleeD8R8Synthesized)));
       } else {
-        newEvents[i++] = event;
+        newEvents.add(event);
       }
     }
-    return new EventBasedDebugInfo(eventBasedInfo.startLine, eventBasedInfo.parameters, newEvents);
-  }
-
-  private static int findLineAtPcZero(DexMethod method, EventBasedDebugInfo debugInfo) {
-    IntBox lineAtPcZero = new IntBox(-1);
-    DexDebugPositionState visitor =
-        new DexDebugPositionState(debugInfo.startLine, method) {
-          @Override
-          public void visit(Default defaultEvent) {
-            super.visit(defaultEvent);
-            if (getCurrentPc() == 0) {
-              lineAtPcZero.set(getCurrentLine());
-            }
-          }
-        };
-    for (DexDebugEvent event : debugInfo.events) {
-      event.accept(visitor);
-      if (visitor.getCurrentPc() > 0) {
-        break;
-      }
+    if (adjustStartPosition) {
+      // This only happens if we have no default event and the debug start line is > 0.
+      newEvents.add(factory.createAdvanceLine(debugInfo.getStartLine()));
     }
-    return lineAtPcZero.get();
+    return new EventBasedDebugInfo(
+        positionInfo.hasLinePositionAtPcZero() ? eventBasedInfo.getStartLine() : 0,
+        eventBasedInfo.parameters,
+        newEvents.toArray(DexDebugEvent.EMPTY_ARRAY));
   }
 
   public static int getLargestPrefix(DexItemFactory factory, DexString name) {
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 cd157e3..737df40 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDebugEvent.java
@@ -97,10 +97,22 @@
     return false;
   }
 
+  public boolean isAdvanceLine() {
+    return false;
+  }
+
   public SetPositionFrame asSetPositionFrame() {
     return null;
   }
 
+  public Default asDefaultEvent() {
+    return null;
+  }
+
+  public AdvanceLine asAdvanceLine() {
+    return null;
+  }
+
   public static class AdvancePC extends DexDebugEvent {
 
     public final int delta;
@@ -267,6 +279,16 @@
     }
 
     @Override
+    public boolean isAdvanceLine() {
+      return true;
+    }
+
+    @Override
+    public AdvanceLine asAdvanceLine() {
+      return this;
+    }
+
+    @Override
     public void internalWriteOn(
         DebugBytecodeWriter writer, ObjectToOffsetMapping mapping, GraphLens graphLens) {
       writer.putByte(Constants.DBG_ADVANCE_LINE);
@@ -648,6 +670,11 @@
     }
 
     @Override
+    public Default asDefaultEvent() {
+      return this;
+    }
+
+    @Override
     boolean isWritableEvent() {
       return true;
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
index 85467b7..bde37fd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.ir.code.Position.SourcePosition;
 import com.android.tools.r8.ir.code.Position.SyntheticPosition;
@@ -28,26 +29,23 @@
       Position callerPosition,
       int expectedPositionsCount,
       DexMethod method,
-      boolean methodIsSynthesized) {
+      boolean methodIsSynthesized,
+      Position preamblePosition) {
     canonicalPositions =
         new HashMap<>(1 + (callerPosition == null ? 0 : 1) + expectedPositionsCount);
+    if (preamblePosition == null) {
+      preamblePosition = SyntheticPosition.builder().setLine(0).setMethod(method).build();
+    }
     if (callerPosition != null) {
       this.callerPosition = getCanonical(callerPosition);
       isCompilerSynthesizedInlinee = methodIsSynthesized;
-      preamblePosition =
-          methodIsSynthesized
-              ? callerPosition
-              : getCanonical(
-                  SourcePosition.builder()
-                      .setLine(0)
-                      .setMethod(method)
-                      .setCallerPosition(callerPosition)
-                      .build());
+      this.preamblePosition =
+          getCanonical(
+              Code.newInlineePosition(callerPosition, preamblePosition, methodIsSynthesized));
     } else {
       this.callerPosition = null;
       isCompilerSynthesizedInlinee = false;
-      preamblePosition =
-          getCanonical(SyntheticPosition.builder().setLine(0).setMethod(method).build());
+      this.preamblePosition = getCanonical(preamblePosition);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
index bffd1fe..60d5807 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfSourceCode.java
@@ -252,7 +252,8 @@
             callerPosition,
             cfPositionCount,
             originalMethod,
-            method.getDefinition().isD8R8Synthesized());
+            method.getDefinition().isD8R8Synthesized(),
+            code.getPreamblePosition());
     internalOutputMode = appView.options().getInternalOutputMode();
 
     needsGeneratedMethodSynchronization =
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 3242779..229fcfc 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
@@ -44,6 +44,7 @@
 import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.utils.DexDebugUtils;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
@@ -94,7 +95,8 @@
             callerPosition,
             debugEntries == null ? 0 : debugEntries.size(),
             originalMethod,
-            method.getDefinition().isD8R8Synthesized());
+            method.getDefinition().isD8R8Synthesized(),
+            DexDebugUtils.computePreamblePosition(originalMethod, info).getFramePosition());
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/utils/DexDebugUtils.java b/src/main/java/com/android/tools/r8/utils/DexDebugUtils.java
index 1dc89fd..adfda77 100644
--- a/src/main/java/com/android/tools/r8/utils/DexDebugUtils.java
+++ b/src/main/java/com/android/tools/r8/utils/DexDebugUtils.java
@@ -5,7 +5,13 @@
 package com.android.tools.r8.utils;
 
 import com.android.tools.r8.graph.DexDebugEvent;
+import com.android.tools.r8.graph.DexDebugEvent.SetPositionFrame;
 import com.android.tools.r8.graph.DexDebugInfo;
+import com.android.tools.r8.graph.DexDebugInfo.EventBasedDebugInfo;
+import com.android.tools.r8.graph.DexDebugPositionState;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.ir.code.Position;
+import com.android.tools.r8.utils.DexDebugUtils.PositionInfo.PositionInfoBuilder;
 import java.util.List;
 
 public class DexDebugUtils {
@@ -29,4 +35,81 @@
     }
     return true;
   }
+
+  public static PositionInfo computePreamblePosition(
+      DexMethod method, EventBasedDebugInfo debugInfo) {
+    if (debugInfo == null) {
+      return PositionInfo.builder().build();
+    }
+    Box<Position> existingPositionFrame = new Box<>();
+    DexDebugPositionState visitor =
+        new DexDebugPositionState(debugInfo.startLine, method) {
+          @Override
+          public void visit(SetPositionFrame setPositionFrame) {
+            super.visit(setPositionFrame);
+            existingPositionFrame.set(setPositionFrame.getPosition());
+          }
+        };
+    PositionInfoBuilder builder = PositionInfo.builder();
+    for (DexDebugEvent event : debugInfo.events) {
+      event.accept(visitor);
+      if (visitor.getCurrentPc() > 0) {
+        break;
+      }
+      if (event.isDefaultEvent()) {
+        builder.setLinePositionAtPcZero(visitor.getCurrentLine());
+        builder.setFramePosition(existingPositionFrame.get());
+      }
+    }
+    return builder.build();
+  }
+
+  public static class PositionInfo {
+
+    private final Position framePosition;
+    private final int linePositionAtPcZero;
+
+    private PositionInfo(Position framePosition, int linePositionAtPcZero) {
+      this.framePosition = framePosition;
+      this.linePositionAtPcZero = linePositionAtPcZero;
+    }
+
+    public boolean hasFramePosition() {
+      return framePosition != null;
+    }
+
+    public boolean hasLinePositionAtPcZero() {
+      return linePositionAtPcZero > -1;
+    }
+
+    public Position getFramePosition() {
+      return framePosition;
+    }
+
+    public int getLinePositionAtPcZero() {
+      return linePositionAtPcZero;
+    }
+
+    public static PositionInfoBuilder builder() {
+      return new PositionInfoBuilder();
+    }
+
+    public static class PositionInfoBuilder {
+
+      private Position framePosition;
+      private int linePositionAtPcZero = -1;
+
+      public void setFramePosition(Position position) {
+        this.framePosition = position;
+      }
+
+      public void setLinePositionAtPcZero(int linePositionAtPcZero) {
+        this.linePositionAtPcZero = linePositionAtPcZero;
+      }
+
+      public PositionInfo build() {
+        return new PositionInfo(framePosition, linePositionAtPcZero);
+      }
+    }
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
index a660a37..a6a2a06 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToNoPcMappedRangeMapper.java
@@ -87,9 +87,6 @@
     private final List<MappedPosition> mappedPositions;
     private final PositionRemapper positionRemapper;
     private final List<DexDebugEvent> processedEvents;
-    private final DexItemFactory factory;
-
-    private final DexMethod startMethod;
 
     // Keep track of what PC has been emitted.
     private int emittedPc = 0;
@@ -109,8 +106,6 @@
       this.mappedPositions = mappedPositions;
       this.positionRemapper = positionRemapper;
       this.processedEvents = processedEvents;
-      this.factory = factory;
-      this.startMethod = method;
     }
 
     // Force the current PC to emitted.
@@ -124,9 +119,6 @@
     // A default event denotes a line table entry and must always be emitted. Remap its line.
     @Override
     public void visit(Default defaultEvent) {
-      if (hasPreamblePosition(defaultEvent)) {
-        emitPreamblePosition();
-      }
       super.visit(defaultEvent);
       assert getCurrentLine() >= 0;
       Position position = getPosition();
@@ -139,13 +131,6 @@
       emittedPc = getCurrentPc();
     }
 
-    private boolean hasPreamblePosition(Default defaultEvent) {
-      return getCurrentPc() == 0
-          && defaultEvent.getPCDelta() > 0
-          && currentPosition != null
-          && currentPosition.getLine() != getCurrentLine();
-    }
-
     // Non-materializing events use super, ie, AdvancePC, AdvanceLine and SetInlineFrame.
 
     // Materializing events are just amended to the stream.
@@ -184,17 +169,6 @@
       flushPc();
       processedEvents.add(restartLocal);
     }
-
-    public void emitPreamblePosition() {
-      if (currentPosition == null || positionEventEmitter.didEmitLineEvents()) {
-        return;
-      }
-      Position mappedPosition =
-          PositionUtils.remapAndAdd(currentPosition, positionRemapper, mappedPositions);
-      processedEvents.add(factory.createPositionFrame(mappedPosition));
-      currentPosition = null;
-      currentMethod = startMethod;
-    }
   }
 
   private final AppView<?> appView;
@@ -206,7 +180,7 @@
   }
 
   public List<MappedPosition> optimizeDexCodePositions(
-      ProgramMethod method, PositionRemapper positionRemapper, boolean hasOverloads) {
+      ProgramMethod method, PositionRemapper positionRemapper) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     DexApplication application = appView.appInfo().app();
@@ -237,11 +211,6 @@
       event.accept(visitor);
     }
 
-    // We still need to emit a preamble if we did not materialize any other instructions.
-    if (mappedPositions.isEmpty()) {
-      visitor.emitPreamblePosition();
-    }
-
     EventBasedDebugInfo optimizedDebugInfo =
         new EventBasedDebugInfo(
             positionEventEmitter.didEmitLineEvents() ? positionEventEmitter.getStartLine() : 0,
diff --git a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
index 914b174..2ada4e3 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/PositionToMappedRangeMapper.java
@@ -57,7 +57,7 @@
         int pcEncodingCutoff) {
       return canUseDexPc
           ? pcMapper.optimizeDexCodePositionsForPc(method, positionRemapper, pcEncodingCutoff)
-          : noPcMapper.optimizeDexCodePositions(method, positionRemapper, hasOverloads);
+          : noPcMapper.optimizeDexCodePositions(method, positionRemapper);
     }
 
     @Override