Merge "Allow larger line ranges in map files for class file output"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 734e421..4f472c6 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;
@@ -634,12 +633,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();
+  }
+}