Allow larger line ranges in map files for class file output

For class file output allow the line number optimization to increase
the optimized line numbers with any positive delta. This makes the map
files about 30% smaller.

For dex files don't allow this, as even though the map files does get
smaller, the dex size increases due to less canonicalization of debug
info objects.

Bug: 113198295
Change-Id: Ifbc037be9ad58f442c37b4296fd43d8161f4590c
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 6c3e47e..2d19d9d 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -63,7 +63,6 @@
 import com.android.tools.r8.utils.ExceptionUtils;
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions;
-import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.android.tools.r8.utils.LineNumberOptimizer;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.SelfRetraceTest;
@@ -632,12 +631,7 @@
       // When line number optimization is turned off the identity mapping for line numbers is
       // used. We still run the line number optimizer to collect line numbers and inline frame
       // information for the mapping file.
-      ClassNameMapper classNameMapper =
-          LineNumberOptimizer.run(
-              application,
-              appView.graphLense(),
-              namingLens,
-              options.lineNumberOptimization == LineNumberOptimization.OFF);
+      ClassNameMapper classNameMapper = LineNumberOptimizer.run(appView, application, namingLens);
       timing.end();
       proguardMapSupplier = ProguardMapSupplier.fromClassNameMapper(classNameMapper, options);
 
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 f5a4b81..427341f 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -5,6 +5,8 @@
 
 import com.android.tools.r8.cf.code.CfInstruction;
 import com.android.tools.r8.cf.code.CfPosition;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexApplication;
@@ -39,6 +41,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.base.Suppliers;
 import java.util.ArrayList;
 import java.util.IdentityHashMap;
@@ -66,13 +69,33 @@
   }
 
   private static class OptimizingPositionRemapper implements PositionRemapper {
-    private int nextLineNumber = 1;
+    private final int maxLineDelta;
+    private DexMethod previousMethod = null;
+    private int previousSourceLine = -1;
+    private int nextOptimizedLineNumber = 1;
+
+    OptimizingPositionRemapper(InternalOptions options) {
+      // TODO(113198295): For dex using "Constants.DBG_LINE_RANGE + Constants.DBG_LINE_BASE"
+      // instead of 1 creates a ~30% smaller map file but the dex files gets larger due to reduced
+      // debug info canonicalization.
+      maxLineDelta = options.isGeneratingClassFiles() ? Integer.MAX_VALUE : 1;
+    }
 
     @Override
     public Position createRemappedPosition(
         int line, DexString file, DexMethod method, Position callerPosition) {
-      Position newPosition = new Position(nextLineNumber, file, method, null);
-      ++nextLineNumber;
+      assert method != null;
+      if (previousMethod == method) {
+        assert previousSourceLine >= 0;
+        if (line > previousSourceLine && line - previousSourceLine <= maxLineDelta) {
+            nextOptimizedLineNumber += (line - previousSourceLine) - 1;
+        }
+      }
+
+      Position newPosition = new Position(nextOptimizedLineNumber, file, method, null);
+      ++nextOptimizedLineNumber;
+      previousSourceLine = line;
+      previousMethod = method;
       return newPosition;
     }
   }
@@ -138,10 +161,9 @@
   }
 
   public static ClassNameMapper run(
+      AppView<AppInfoWithSubtyping> appView,
       DexApplication application,
-      GraphLense graphLense,
-      NamingLens namingLens,
-      boolean identityMapping) {
+      NamingLens namingLens) {
     ClassNameMapper.Builder classNameMapperBuilder = ClassNameMapper.builder();
     // Collect which files contain which classes that need to have their line numbers optimized.
     for (DexProgramClass clazz : application.classes()) {
@@ -151,7 +173,7 @@
       // At this point we don't know if we really need to add this class to the builder.
       // It depends on whether any methods/fields are renamed or some methods contain positions.
       // Create a supplier which creates a new, cached ClassNaming.Builder on-demand.
-      DexType originalType = graphLense.getOriginalType(clazz.type);
+      DexType originalType = appView.graphLense().getOriginalType(clazz.type);
       DexString renamedClassName = namingLens.lookupDescriptor(clazz.getType());
       Supplier<ClassNaming.Builder> onDemandClassNamingBuilder =
           Suppliers.memoize(
@@ -164,7 +186,7 @@
       addClassToClassNaming(originalType, renamedClassName, onDemandClassNamingBuilder);
 
       // First transfer renamed fields to classNamingBuilder.
-      addFieldsToClassNaming(graphLense, namingLens, clazz, onDemandClassNamingBuilder);
+      addFieldsToClassNaming(appView.graphLense(), namingLens, clazz, onDemandClassNamingBuilder);
 
       // Then process the methods, ordered by renamed name.
       List<DexString> renamedMethodNames = new ArrayList<>(methodsByRenamedName.keySet());
@@ -179,8 +201,12 @@
           sortMethods(methods);
         }
 
+        boolean identityMapping =
+            appView.options().lineNumberOptimization == LineNumberOptimization.OFF;
         PositionRemapper positionRemapper =
-            identityMapping ? new IdentityPositionRemapper() : new OptimizingPositionRemapper();
+            identityMapping
+                ? new IdentityPositionRemapper()
+                : new OptimizingPositionRemapper(appView.options());
 
         for (DexEncodedMethod method : methods) {
           List<MappedPosition> mappedPositions = new ArrayList<>();
@@ -194,7 +220,7 @@
             }
           }
 
-          DexMethod originalMethod = graphLense.getOriginalMethodSignature(method.method);
+          DexMethod originalMethod = appView.graphLense().getOriginalMethodSignature(method.method);
           MethodSignature originalSignature =
               MethodSignature.fromDexMethod(originalMethod, originalMethod.holder != clazz.type);
 
@@ -217,7 +243,7 @@
           signatures.put(originalMethod, originalSignature);
           Function<DexMethod, MethodSignature> getOriginalMethodSignature =
               m -> {
-                DexMethod original = graphLense.getOriginalMethodSignature(m);
+                DexMethod original = appView.graphLense().getOriginalMethodSignature(m);
                 return signatures.computeIfAbsent(
                     original,
                     key ->
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/LineDeltaTest.java b/src/test/java/com/android/tools/r8/naming/retrace/LineDeltaTest.java
new file mode 100644
index 0000000..4e64e7e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/retrace/LineDeltaTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2019, 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.naming.retrace;
+
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.ForceInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+
+public class LineDeltaTest extends TestBase {
+  public String runTest(Backend backend) throws Exception {
+    return testForR8(backend)
+        .enableInliningAnnotations()
+        .addProgramClasses(LineDeltaTestClass.class)
+        .addKeepMainRule(LineDeltaTestClass.class)
+        .addKeepRules("-keepattributes LineNumberTable")
+        .run(LineDeltaTestClass.class)
+        .assertSuccessWithOutput(StringUtils.lines(
+            "In test1() - 1",
+            "In test1() - 2",
+            "In test1() - 3",
+            "In test1() - 4",
+            "In test2() - 1",
+            "In test2() - 2",
+            "In test2() - 3",
+            "In test2() - 4"
+        ))
+        .proguardMap();
+  }
+
+  private long mapLines(String map) {
+    return StringUtils.splitLines(map).stream().filter(line -> !line.startsWith("#")).count();
+  }
+
+  @Test
+  public void testDex() throws Exception {
+    assertEquals(17, mapLines(runTest(Backend.DEX)));
+  }
+
+  @Test
+  public void testCf() throws Exception {
+    assertEquals(5, mapLines(runTest(Backend.CF)));
+  }
+}
+
+class LineDeltaTestClass {
+  @ForceInline
+  static void test1() {
+    System.out.println("In test1() - 1");
+    // One line comment.
+    System.out.println("In test1() - 2");
+    // Two line comments.
+    //
+    System.out.println("In test1() - 3");
+    // Four line comments.
+    //
+    //
+    //
+    System.out.println("In test1() - 4");
+  }
+
+  @ForceInline
+  static void test2() {
+    System.out.println("In test2() - 1");
+    // Seven line comments.
+    //
+    //
+    //
+    //
+    //
+    //
+    System.out.println("In test2() - 2");
+    // Eight line comments.
+    //
+    //
+    //
+    //
+    //
+    //
+    //
+    System.out.println("In test2() - 3");
+    // Nine line comments.
+    //
+    //
+    //
+    //
+    //
+    //
+    //
+    //
+    System.out.println("In test2() - 4");
+  }
+
+  public static void main(String[] args) {
+    test1();
+    test2();
+  }
+}