Add line numbers to output of '--print-mapping'.

Bug:
Change-Id: I181011af558adb0d1feef7fe598df76b0d584603
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 11d9a23..7766a94 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -37,6 +37,10 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.utils.InternalOptions;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.ListIterator;
 
 public class DexEncodedMethod extends KeyedDexItem<DexMethod> {
 
@@ -88,6 +92,100 @@
   private CompilationState compilationState = CompilationState.NOT_PROCESSED;
   private OptimizationInfo optimizationInfo = DefaultOptimizationInfo.DEFAULT;
 
+  // Encodes a mapping from a range of original to emitted line numbers:
+  // (originalFirst, originalFirst + length) -> (emittedFirst, emittedFirst + length)
+  public static class DebugPositionRange {
+    public final int originalFirst;
+    public final int emittedFirst;
+    private final int length; // 0 for a single line.
+
+    // The original source line numbers are compressed to a tight range
+    public DebugPositionRange(int originalFirst, int emittedFirst, int length) {
+      assert originalFirst >= 0 && emittedFirst >= 0 && length >= 0;
+      this.originalFirst = originalFirst;
+      this.length = length;
+      this.emittedFirst = emittedFirst;
+    }
+
+    public int getOriginalLast() {
+      return originalFirst + length;
+    }
+
+    public int getEmittedLast() {
+      return emittedFirst + length;
+    }
+  }
+
+  public static class DebugPositionRangeList {
+    // Build sorted list of DebugPositionRange objects (sorted by emitted position) from single
+    // line-to-line mappings
+    public static class Builder {
+      private static class Mapping implements Comparable<Mapping> {
+        public int original;
+        public int emitted;
+
+        Mapping(int original, int emitted) {
+          this.original = original;
+          this.emitted = emitted;
+        }
+
+        @Override
+        public int compareTo(Mapping rhs) {
+          if (emitted == rhs.emitted) {
+            return original - rhs.original;
+          } else {
+            return emitted - rhs.emitted;
+          }
+        }
+      };
+
+      public void add(int original, int emitted) {
+        list.add(new Mapping(original, emitted));
+      }
+
+      public List<DebugPositionRange> build() {
+        if (list.isEmpty()) {
+          return Collections.<DebugPositionRange>emptyList();
+        }
+
+        Collections.sort(list);
+
+        List<DebugPositionRange> result = new ArrayList<>();
+
+        ListIterator<Mapping> iterator = list.listIterator();
+        Mapping pendingMapping = iterator.next();
+        int pendingLength = 0;
+        while (iterator.hasNext()) {
+          Mapping mapping = iterator.next();
+          // Try to merge with last interval.
+          int originalDiff = mapping.original - (pendingMapping.original + pendingLength);
+          int emittedDiff = mapping.emitted - (pendingMapping.emitted + pendingLength);
+          if (originalDiff == 0 && emittedDiff == 0) {
+            // Skip duplicates.
+            continue;
+          }
+          assert emittedDiff > 0;
+          if (originalDiff == emittedDiff) {
+            pendingLength = mapping.original - pendingMapping.original;
+          } else {
+            result.add(
+                new DebugPositionRange(
+                    pendingMapping.original, pendingMapping.emitted, pendingLength));
+            pendingMapping = mapping;
+            pendingLength = 0;
+          }
+        }
+        result.add(
+            new DebugPositionRange(pendingMapping.original, pendingMapping.emitted, pendingLength));
+        return Collections.unmodifiableList(result);
+      }
+
+      private List<Mapping> list = new ArrayList<>();
+    }
+  }
+
+  public List<DebugPositionRange> debugPositionRangeList = null;
+
   public DexEncodedMethod(DexMethod method, DexAccessFlags accessFlags,
       DexAnnotationSet annotations, DexAnnotationSetRefList parameterAnnotations, Code code) {
     this.method = method;
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 923dbf1..3bdeefa 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -38,6 +38,7 @@
 import com.android.tools.r8.graph.DexCode.TryHandler;
 import com.android.tools.r8.graph.DexCode.TryHandler.TypeAddrPair;
 import com.android.tools.r8.graph.DexDebugEventBuilder;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
@@ -180,8 +181,14 @@
     List<Instruction> dexInstructions = new ArrayList<>(numberOfInstructions);
     int instructionOffset = 0;
     InstructionIterator instructionIterator = ir.instructionIterator();
+    DexEncodedMethod.DebugPositionRangeList.Builder debugPositionListBuilder =
+        new DexEncodedMethod.DebugPositionRangeList.Builder();
     while (instructionIterator.hasNext()) {
       com.android.tools.r8.ir.code.Instruction ir = instructionIterator.next();
+      if (ir.isDebugPosition()) {
+        int line = ir.asDebugPosition().line;
+        debugPositionListBuilder.add(line, line);
+      }
       Info info = getInfo(ir);
       int previousInstructionCount = dexInstructions.size();
       info.addInstructions(this, dexInstructions);
@@ -195,6 +202,8 @@
       }
     }
 
+    ir.method.debugPositionRangeList = debugPositionListBuilder.build();
+
     // Compute switch payloads.
     for (SwitchPayloadInfo switchPayloadInfo : switchPayloadInfos) {
       // Align payloads at even addresses.
diff --git a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
index 14dd50e..e9ec08c 100644
--- a/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
+++ b/src/main/java/com/android/tools/r8/naming/MinifiedNameMapPrinter.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexEncodedMethod.DebugPositionRange;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -84,16 +85,38 @@
     }
   }
 
+  private void writeMethod(
+      MethodSignature signature,
+      String renamed,
+      PrintStream out,
+      DexEncodedMethod.DebugPositionRange range) {
+    out.print("    ");
+    if (range != null) {
+      out.printf("%d:%d:", range.emittedFirst, range.getEmittedLast());
+    }
+    out.print(signature);
+    if (range != null && range.originalFirst != range.emittedFirst) {
+      out.printf(":%d:%d", range.originalFirst, range.getOriginalLast());
+    }
+    out.print(" -> ");
+    out.println(renamed);
+  }
+
   private void write(DexEncodedMethod[] methods, PrintStream out) {
     for (DexEncodedMethod encodedMethod : methods) {
       DexMethod method = encodedMethod.method;
       DexString renamed = namingLens.lookupName(method);
       if (renamed != method.name) {
         MethodSignature signature = MethodSignature.fromDexMethod(method);
-        out.print("    ");
-        out.print(signature);
-        out.print(" -> ");
-        out.println(renamed.toSourceString());
+        String renamedSourceString = renamed.toSourceString();
+        if (encodedMethod.debugPositionRangeList == null
+            || encodedMethod.debugPositionRangeList.isEmpty()) {
+          writeMethod(signature, renamedSourceString, out, null);
+        } else {
+          for (DebugPositionRange range : encodedMethod.debugPositionRangeList) {
+            writeMethod(signature, renamedSourceString, out, range);
+          }
+        }
       }
     }
   }