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"); + } + } +}