Maintain per-file PC encoding cutoffs.

Bug: b/231903117
Change-Id: I22bb038f292639b14e950d17485d8e91547412f9
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 173ddd1..667e5b0 100644
--- a/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
+++ b/src/main/java/com/android/tools/r8/debuginfo/DebugRepresentation.java
@@ -30,14 +30,17 @@
 
 public class DebugRepresentation {
 
+  public static final int NO_PC_ENCODING = -1;
+  public static final int ALWAYS_PC_ENCODING = Integer.MAX_VALUE;
+
   public interface DebugRepresentationPredicate {
 
-    boolean useDexPcEncoding(DexProgramClass holder, DexEncodedMethod method);
+    int getDexPcEncodingCutoff(DexProgramClass holder, DexEncodedMethod method);
   }
 
   public static DebugRepresentationPredicate none(InternalOptions options) {
     assert !options.canUseDexPc2PcAsDebugInformation();
-    return (holder, method) -> false;
+    return (holder, method) -> NO_PC_ENCODING;
   }
 
   public static DebugRepresentationPredicate fromFiles(
@@ -45,8 +48,8 @@
     if (!options.canUseDexPc2PcAsDebugInformation()) {
       return none(options);
     }
-    if (options.canUseNativeDexPcInsteadOfDebugInfo() || options.testing.forcePcBasedEncoding) {
-      return (holder, method) -> true;
+    if (options.canUseNativeDexPcInsteadOfDebugInfo()) {
+      return (holder, method) -> ALWAYS_PC_ENCODING;
     }
     // TODO(b/220999985): Avoid the need to maintain a class-to-file map.
     Map<DexProgramClass, VirtualFile> classMapping = new IdentityHashMap<>();
@@ -55,11 +58,11 @@
     }
     return (holder, method) -> {
       if (!isPcCandidate(method)) {
-        return false;
+        return NO_PC_ENCODING;
       }
       VirtualFile file = classMapping.get(holder);
       DebugRepresentation cutoffs = file.getDebugRepresentation();
-      return cutoffs.usesPcEncoding(method);
+      return cutoffs.getDexPcEncodingCutoff(method);
     };
   }
 
@@ -72,8 +75,7 @@
   public static void computeForFile(
       VirtualFile file, GraphLens graphLens, NamingLens namingLens, InternalOptions options) {
     if (!options.canUseDexPc2PcAsDebugInformation()
-        || options.canUseNativeDexPcInsteadOfDebugInfo()
-        || options.testing.forcePcBasedEncoding) {
+        || options.canUseNativeDexPcInsteadOfDebugInfo()) {
       return;
     }
     // First collect all of the per-pc costs
@@ -106,26 +108,28 @@
       }
     }
     // Second compute the cost of converting to a pc encoding.
-    paramCountToCosts.forEach((ignored, summary) -> summary.computeConversionCosts());
+    paramCountToCosts.forEach((ignored, summary) -> summary.computeConversionCosts(options));
     // The result is stored on the virtual files for thread safety.
     // TODO(b/220999985): Consider just passing this to the line number optimizer once fixed.
     file.setDebugRepresentation(new DebugRepresentation(paramCountToCosts));
   }
 
-  private boolean usesPcEncoding(DexEncodedMethod method) {
+  private int getDexPcEncodingCutoff(DexEncodedMethod method) {
     DexCode code = method.getCode().asDexCode();
     DexDebugInfo debugInfo = code.getDebugInfo();
     int paramCount = debugInfo.getParameterCount();
     CostSummary conversionInfo = paramToInfo.get(paramCount);
-    if (conversionInfo.cutoff < 0) {
-      return false;
+    if (conversionInfo == null || conversionInfo.cutoff < 0) {
+      // We expect all methods calling this to have computed conversion info.
+      assert conversionInfo != null;
+      return NO_PC_ENCODING;
     }
     Instruction lastInstruction = getLastExecutableInstruction(code);
     if (lastInstruction == null) {
-      return false;
+      return NO_PC_ENCODING;
     }
     int maxPc = lastInstruction.getOffset();
-    return maxPc <= conversionInfo.cutoff;
+    return maxPc <= conversionInfo.cutoff ? conversionInfo.cutoff : NO_PC_ENCODING;
   }
 
   @Override
@@ -189,7 +193,8 @@
       maxPc = Math.max(maxPc, pc);
     }
 
-    private void computeConversionCosts() {
+    private void computeConversionCosts(InternalOptions options) {
+      boolean forcePcBasedEncoding = options.testing.forcePcBasedEncoding;
       assert !pcToCost.isEmpty();
       // Point at which it is estimated that conversion to PC-encoding is viable.
       int currentConvertedPc = -1;
@@ -212,7 +217,7 @@
         // If the estimated cost is larger we convert. The order here could be either way as
         // both the normal cost and converted cost are estimates. Canonicalization could reduce
         // the former and compaction could reduce the latter.
-        if (normalOutstandingCost > costToConvert) {
+        if (forcePcBasedEncoding || normalOutstandingCost > costToConvert) {
           normalConvertedCost += normalOutstandingCost;
           normalOutstandingCost = 0;
           currentConvertedPc = currentPc;
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 78f612a..e1d4ce4 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -76,10 +76,9 @@
 import it.unimi.dsi.fastutil.ints.Int2IntArrayMap;
 import it.unimi.dsi.fastutil.ints.Int2IntLinkedOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
-import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
 import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
-import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceMap;
+import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -360,12 +359,10 @@
 
   private interface PcBasedDebugInfoRecorder {
     /** Callback to record a code object with a given max instruction PC and parameter count. */
-    void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount);
+    void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc);
 
     /** Callback to record a code object with only a single "line". */
-    void recordSingleLineFor(DexCode code, int parameterCount);
-
-    void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc);
+    void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc);
 
     /**
      * Install the correct debug info objects.
@@ -378,11 +375,33 @@
 
   private static class Pc2PcMappingSupport implements PcBasedDebugInfoRecorder {
 
-    // Some DEX VMs require matching parameter count in methods and debug info.
-    // Record the max pc for each parameter count so we can share the param count objects.
-    private Int2IntMap paramToMaxPc = new Int2IntOpenHashMap();
+    private static class UpdateInfo {
+      final DexCode code;
+      final int paramCount;
+      final int maxEncodingPc;
 
-    private final List<Pair<Integer, DexCode>> codesToUpdate = new ArrayList<>();
+      public UpdateInfo(DexCode code, int paramCount, int maxEncodingPc) {
+        this.code = code;
+        this.paramCount = paramCount;
+        this.maxEncodingPc = maxEncodingPc;
+      }
+
+      // Used as key when building the shared debug info map.
+      // Only param and max-pc are part of the key.
+
+      @Override
+      public boolean equals(Object o) {
+        UpdateInfo that = (UpdateInfo) o;
+        return paramCount == that.paramCount && maxEncodingPc != that.maxEncodingPc;
+      }
+
+      @Override
+      public int hashCode() {
+        return Objects.hash(paramCount, maxEncodingPc);
+      }
+    }
+
+    private final List<UpdateInfo> codesToUpdate = new ArrayList<>();
 
     // We can only drop single-line debug info if it is OK to lose the source-file info.
     // This list is null if we must retain single-line entries.
@@ -393,46 +412,29 @@
     }
 
     @Override
-    public void recordPcMappingFor(DexCode code, int lastInstructionPc, int parameterCount) {
-      codesToUpdate.add(new Pair<>(parameterCount, code));
-      int existing = paramToMaxPc.getOrDefault(parameterCount, -1);
-      if (existing < lastInstructionPc) {
-        paramToMaxPc.put(parameterCount, lastInstructionPc);
-      }
+    public void recordPcMappingFor(DexCode code, int parameterCount, int maxEncodingPc) {
+      codesToUpdate.add(new UpdateInfo(code, parameterCount, maxEncodingPc));
     }
 
     @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount) {
+    public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
       if (singleLineCodesToClear != null) {
         singleLineCodesToClear.add(code);
         return;
       }
-      int lastInstructionPc = ArrayUtils.last(code.instructions).getOffset();
-      recordPcMappingFor(code, lastInstructionPc, parameterCount);
-    }
-
-    @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) {
-      if (singleLineCodesToClear != null) {
-        singleLineCodesToClear.add(code);
-        return;
-      }
-      recordPcMappingFor(code, lastInstructionPc, parameterCount);
+      recordPcMappingFor(code, parameterCount, maxEncodingPc);
     }
 
     @Override
     public void updateDebugInfoInCodeObjects() {
-      Int2ReferenceMap<DexDebugInfo> debugInfos =
-          new Int2ReferenceOpenHashMap<>(paramToMaxPc.size());
+      Object2ReferenceMap<UpdateInfo, DexDebugInfo> debugInfos =
+          new Object2ReferenceOpenHashMap<>();
       codesToUpdate.forEach(
           entry -> {
-            int parameterCount = entry.getFirst();
-            DexCode code = entry.getSecond();
             DexDebugInfo debugInfo =
                 debugInfos.computeIfAbsent(
-                    parameterCount,
-                    key -> buildPc2PcDebugInfo(paramToMaxPc.get(key), parameterCount));
-            code.setDebugInfo(debugInfo);
+                    entry, key -> buildPc2PcDebugInfo(key.maxEncodingPc, key.paramCount));
+            entry.code.setDebugInfo(debugInfo);
           });
       if (singleLineCodesToClear != null) {
         singleLineCodesToClear.forEach(c -> c.setDebugInfo(null));
@@ -447,23 +449,18 @@
   private static class NativePcSupport implements PcBasedDebugInfoRecorder {
 
     @Override
-    public void recordPcMappingFor(DexCode code, int lastInstructionPc, int length) {
+    public void recordPcMappingFor(DexCode code, int length, int maxEncodingPc) {
       // Strip the info in full as the runtime will emit the PC directly.
       code.setDebugInfo(null);
     }
 
     @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount) {
+    public void recordSingleLineFor(DexCode code, int parameterCount, int maxEncodingPc) {
       // Strip the info at once as it does not conflict with any PC mapping update.
       code.setDebugInfo(null);
     }
 
     @Override
-    public void recordSingleLineFor(DexCode code, int parameterCount, int lastInstructionPc) {
-      recordSingleLineFor(code, parameterCount);
-    }
-
-    @Override
     public void updateDebugInfoInCodeObjects() {
       // Already null out the info so nothing to do.
     }
@@ -572,14 +569,15 @@
           kotlinRemapper.currentMethod = definition;
           List<MappedPosition> mappedPositions;
           Code code = definition.getCode();
-          boolean canUseDexPc =
-              methods.size() == 1 && representation.useDexPcEncoding(clazz, definition);
+          int pcEncodingCutoff =
+              methods.size() == 1 ? representation.getDexPcEncodingCutoff(clazz, definition) : -1;
+          boolean canUseDexPc = pcEncodingCutoff > 0;
           if (code != null) {
             if (code.isDexCode() && doesContainPositions(code.asDexCode())) {
               if (canUseDexPc) {
                 mappedPositions =
                     optimizeDexCodePositionsForPc(
-                        definition, appView, kotlinRemapper, pcBasedDebugInfo);
+                        definition, appView, kotlinRemapper, pcBasedDebugInfo, pcEncodingCutoff);
               } else {
                 mappedPositions =
                     optimizeDexCodePositions(
@@ -751,7 +749,7 @@
               && definition.getCode().asDexCode().getDebugInfo()
                   == DexDebugInfoForSingleLineMethod.getInstance()) {
             pcBasedDebugInfo.recordSingleLineFor(
-                definition.getCode().asDexCode(), method.getParameters().size());
+                definition.getCode().asDexCode(), method.getParameters().size(), pcEncodingCutoff);
           }
         } // for each method of the group
       } // for each method group, grouped by name
@@ -1133,7 +1131,8 @@
       DexEncodedMethod method,
       AppView<?> appView,
       PositionRemapper positionRemapper,
-      PcBasedDebugInfoRecorder debugInfoProvider) {
+      PcBasedDebugInfoRecorder debugInfoProvider,
+      int pcEncodingCutoff) {
     List<MappedPosition> mappedPositions = new ArrayList<>();
     // Do the actual processing for each method.
     DexCode dexCode = method.getCode().asDexCode();
@@ -1191,9 +1190,9 @@
         && !mappedPositions.get(0).isOutlineCaller()) {
       dexCode.setDebugInfo(DexDebugInfoForSingleLineMethod.getInstance());
       debugInfoProvider.recordSingleLineFor(
-          dexCode, method.getParameters().size(), lastInstructionPc);
+          dexCode, method.getParameters().size(), pcEncodingCutoff);
     } else {
-      debugInfoProvider.recordPcMappingFor(dexCode, lastInstructionPc, debugInfo.parameters.length);
+      debugInfoProvider.recordPcMappingFor(dexCode, debugInfo.parameters.length, pcEncodingCutoff);
     }
     return mappedPositions;
   }