Merge commit '596c92ae16ef500d0eceb1a4d4559576d1ace707' into dev-release

Change-Id: Ic1fc1f0afe8170eb952a369324af9b6b0e965115
diff --git a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
index 337ce31..8cb20a6 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -101,8 +101,9 @@
           LineNumberOptimizer.groupMethodsByRenamedName(appView, clazz);
       for (List<ProgramMethod> methods : overloads.values()) {
         if (methods.size() != 1) {
-          // Never use PC info for overloaded methods. They need distinct lines to disambiguate.
-          continue;
+          // Only use PC info for the first method in the set of overloaded methods.
+          // They need distinct lines to disambiguate.
+          LineNumberOptimizer.sortMethods(methods);
         }
         ProgramMethod method = methods.get(0);
         DexEncodedMethod definition = method.getDefinition();
diff --git a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
index 8c37d80..953e769 100644
--- a/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
+++ b/src/main/java/com/android/tools/r8/utils/AndroidApiLevel.java
@@ -92,6 +92,11 @@
     return getAndroidApiLevel(getLevel() + 1);
   }
 
+  public AndroidApiLevel verifyLevel(int expected) {
+    assert level == expected;
+    return this;
+  }
+
   public static List<AndroidApiLevel> getAndroidApiLevelsSorted() {
     return Arrays.asList(AndroidApiLevel.values());
   }
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 81cc3e0..2cf24ca 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -2726,12 +2726,16 @@
   // Debug entries may be dropped only if the source file content allows being omitted from
   // stack traces, or if the VM will report the source file even with a null valued debug info.
   public boolean allowDiscardingResidualDebugInfo() {
-    // TODO(b/146565491): We can drop debug info once fixed at a known min-api.
+    if (isGeneratingDex() && hasMinApi(AndroidApiLevel.MAIN.verifyLevel(37))) {
+      return true;
+    }
     return sourceFileProvider != null && sourceFileProvider.allowDiscardingSourceFile();
   }
 
   public boolean allowDiscardingResidualDebugInfo(ProgramMethod method) {
-    // TODO(b/146565491): We can drop debug info once fixed at a known min-api.
+    if (isGeneratingDex() && hasMinApi(AndroidApiLevel.MAIN.verifyLevel(37))) {
+      return true;
+    }
     DexString sourceFile = method.getHolder().getSourceFile();
     return sourceFile == null || sourceFile.equals(itemFactory.defaultSourceFileAttribute);
   }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
index 3dac364..65956c9 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/ClassPositionRemapper.java
@@ -46,6 +46,11 @@
       assert position.getOutlineCallee() == null;
       return new Pair<>(position, position);
     }
+
+    @Override
+    public void setNextOptimizedLineNumber(int nextOptimizedLineNumber) {
+      // Intentionally empty.
+    }
   }
 
   class OptimizingPositionRemapper implements AppPositionRemapper, ClassPositionRemapper {
@@ -96,6 +101,11 @@
         previousMethod = position.getMethod();
         return new Pair<>(position, newPosition);
       }
+
+      @Override
+      public void setNextOptimizedLineNumber(int nextOptimizedLineNumber) {
+        this.nextOptimizedLineNumber = nextOptimizedLineNumber;
+      }
     }
   }
 
@@ -214,6 +224,11 @@
           }
           return baseRemapper.createRemappedPosition(position);
         }
+
+        @Override
+        public void setNextOptimizedLineNumber(int nextOptimizedLineNumber) {
+          baseRemapper.setNextOptimizedLineNumber(nextOptimizedLineNumber);
+        }
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
index 760f493..f70bcef 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/DexPositionToPcMappedRangeMapper.java
@@ -42,10 +42,9 @@
     timing.begin("Pc mapper");
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
-    DexCode dexCode = method.getDefinition().getCode().asDexCode();
+    DexCode code = method.getDefinition().getCode().asDexCode();
     timing.begin("Convert to event based debug info");
-    EventBasedDebugInfo debugInfo =
-        getEventBasedDebugInfo(method.getDefinition(), dexCode, appView);
+    EventBasedDebugInfo debugInfo = getEventBasedDebugInfo(method.getDefinition(), code, appView);
     timing.end();
     IntBox firstDefaultEventPc = new IntBox(-1);
     Pair<Integer, Position> lastPosition = new Pair<>();
@@ -84,7 +83,7 @@
     timing.end();
 
     timing.begin("Flush");
-    int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(dexCode).getOffset();
+    int lastInstructionPc = DebugRepresentation.getLastExecutableInstruction(code).getOffset();
     if (lastPosition.getSecond() != null) {
       remapAndAddForPc(
           pcBasedDebugInfo,
@@ -95,11 +94,15 @@
           mappedPositions,
           timing);
     }
+    int nextOptimizedLineNumber = pcBasedDebugInfo.getPcEncoding(lastInstructionPc + 1);
+    assert mappedPositions.stream()
+        .allMatch(mappedPosition -> mappedPosition.getObfuscatedLine() < nextOptimizedLineNumber);
+    positionRemapper.setNextOptimizedLineNumber(nextOptimizedLineNumber);
     timing.end();
 
     assert !mappedPositions.isEmpty()
-        || dexCode.instructions.length == 1
-        || !dexCode.hasThrowingInstructions();
+        || code.instructions.length == 1
+        || !code.hasThrowingInstructions();
     timing.begin("Record pc mapping");
     pcBasedDebugInfo.recordPcMappingFor(method, pcEncodingCutoff);
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
index 7360c56..8043641 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/LineNumberOptimizer.java
@@ -30,6 +30,7 @@
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ObjectUtils;
 import com.android.tools.r8.utils.OriginalSourceFiles;
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.positions.MappedPositionToClassNameMapperBuilder.MappedPositionToClassNamingBuilder;
@@ -261,7 +262,9 @@
     }
     try (Timing t0 = timing.begin("Get mapped positions")) {
       int pcEncodingCutoff =
-          methods.size() == 1 ? representation.getDexPcEncodingCutoff(method) : -1;
+          ObjectUtils.identical(method, methods.get(0))
+              ? representation.getDexPcEncodingCutoff(method)
+              : -1;
       boolean canUseDexPc = pcEncodingCutoff > 0;
       List<MappedPosition> mappedPositions =
           positionToMappedRangeMapper.getMappedPositions(
@@ -333,9 +336,8 @@
     return 0;
   }
 
-  // Sort by startline, then DexEncodedMethod.slowCompare.
-  // Use startLine = 0 if no debuginfo.
-  private static void sortMethods(List<ProgramMethod> methods) {
+  public static void sortMethods(List<ProgramMethod> methods) {
+    // Sort by startline, then DexEncodedMethod.slowCompare. Use startLine = 0 if no debuginfo.
     methods.sort(
         (lhs, rhs) -> {
           int lhsStartLine = getMethodStartLine(lhs);
@@ -344,6 +346,22 @@
           if (startLineDiff != 0) return startLineDiff;
           return DexEncodedMethod.slowCompare(lhs.getDefinition(), rhs.getDefinition());
         });
+    // Insert the largest method first since we can use pc encoding for this method.
+    int largestIndex = -1;
+    int largestCode = -1;
+    for (int i = 0; i < methods.size(); i++) {
+      ProgramMethod method = methods.get(i);
+      if (method.getDefinition().hasCode() && method.getDefinition().getCode().isDexCode()) {
+        int codeSizeInBytes = method.getDefinition().getCode().asDexCode().codeSizeInBytes();
+        if (codeSizeInBytes > largestCode) {
+          largestIndex = i;
+          largestCode = codeSizeInBytes;
+        }
+      }
+    }
+    if (largestIndex > 0) {
+      Collections.swap(methods, 0, largestIndex);
+    }
   }
 
   public static IdentityHashMap<DexString, List<ProgramMethod>> groupMethodsByRenamedName(
diff --git a/src/main/java/com/android/tools/r8/utils/positions/MethodPositionRemapper.java b/src/main/java/com/android/tools/r8/utils/positions/MethodPositionRemapper.java
index 98e1b50..d4756b9 100644
--- a/src/main/java/com/android/tools/r8/utils/positions/MethodPositionRemapper.java
+++ b/src/main/java/com/android/tools/r8/utils/positions/MethodPositionRemapper.java
@@ -9,4 +9,6 @@
 public interface MethodPositionRemapper {
 
   Pair<Position, Position> createRemappedPosition(Position position);
+
+  void setNextOptimizedLineNumber(int nextOptimizedLineNumber);
 }
diff --git a/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyFirstOutlineTest.java b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyFirstOutlineTest.java
new file mode 100644
index 0000000..a5bfcd5
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debuginfo/EnsureNoDebugInfoEmittedForPcOnlyFirstOutlineTest.java
@@ -0,0 +1,136 @@
+// Copyright (c) 2025, 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.debuginfo;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class EnsureNoDebugInfoEmittedForPcOnlyFirstOutlineTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  @Test
+  public void testDebugInfo() throws Exception {
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepAllClassesRule()
+        .setMinApi(AndroidApiLevel.B)
+        .compile()
+        .apply(x -> System.out.println(x.getProguardMap()))
+        .inspect(inspector -> inspect(inspector, false, false));
+  }
+
+  @Test
+  public void testDebugInfoWithPcBasedEncoding() throws Exception {
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepAllClassesRule()
+        .addOptionsModification(options -> options.testing.forcePcBasedEncoding = true)
+        .setMinApi(AndroidApiLevel.B)
+        .compile()
+        .apply(x -> System.out.println(x.getProguardMap()))
+        .inspect(inspector -> inspect(inspector, false, true));
+  }
+
+  @Test
+  public void testNativePc() throws Exception {
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepAllClassesRule()
+        .setMinApi(AndroidApiLevel.ANDROID_PLATFORM_CONSTANT)
+        .compile()
+        .apply(x -> System.out.println(x.getProguardMap()))
+        .inspect(inspector -> inspect(inspector, true, false));
+  }
+
+  @Test
+  public void testNativePcWithPcBasedEncoding() throws Exception {
+    testForR8(Backend.DEX)
+        .addInnerClasses(getClass())
+        .addKeepAllClassesRule()
+        .addOptionsModification(options -> options.testing.forcePcBasedEncoding = true)
+        .setMinApi(AndroidApiLevel.ANDROID_PLATFORM_CONSTANT)
+        .compile()
+        .apply(x -> System.out.println(x.getProguardMap()))
+        .inspect(inspector -> inspect(inspector, true, true));
+  }
+
+  private void inspect(CodeInspector inspector, boolean nativePc, boolean pcBasedEncoding)
+      throws NoSuchMethodException {
+    ClassSubject clazz = inspector.clazz(Main.class);
+    DexCode first =
+        clazz.method(Main.class.getDeclaredMethod("foo")).getMethod().getCode().asDexCode();
+    if (nativePc) {
+      assertNull(first.getDebugInfo());
+    } else {
+      assertNotNull(first.getDebugInfo());
+    }
+
+    DexCode second =
+        clazz
+            .method(Main.class.getDeclaredMethod("foo", Object.class))
+            .getMethod()
+            .getCode()
+            .asDexCode();
+    assertNotNull(second.getDebugInfo());
+
+    DexCode third =
+        clazz
+            .method(Main.class.getDeclaredMethod("foo", String.class))
+            .getMethod()
+            .getCode()
+            .asDexCode();
+    assertNotNull(third.getDebugInfo());
+
+    if (nativePc) {
+      assertEquals(first.codeSizeInBytes(), second.getDebugInfo().getStartLine());
+      assertEquals(first.codeSizeInBytes() + 1, third.getDebugInfo().getStartLine());
+    } else {
+      assertEquals(1, first.getDebugInfo().getStartLine());
+      if (pcBasedEncoding) {
+        assertEquals(9, second.getDebugInfo().getStartLine());
+        assertEquals(10, third.getDebugInfo().getStartLine());
+      } else {
+        assertEquals(2, second.getDebugInfo().getStartLine());
+        assertEquals(3, third.getDebugInfo().getStartLine());
+      }
+    }
+  }
+
+  static class Main {
+
+    static void foo() {
+      System.out.println("foo");
+    }
+
+    static void foo(Object o) {
+      System.out.println("foo");
+    }
+
+    static void foo(String s) {
+      System.out.println("foo");
+    }
+  }
+}