Extract canonical positions related logic into standalone class.

This code, currently used only by DexSourceCode, will also be needed
by CfSourceCode in a follow-up CL.

Change-Id: I52ded36ee8d37f9b03c618a169acbedd5a9a8963
diff --git a/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
new file mode 100644
index 0000000..8db07f6
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/CanonicalPositions.java
@@ -0,0 +1,75 @@
+// Copyright (c) 2018, 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.ir.code;
+
+import com.android.tools.r8.graph.DexMethod;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Maintains a set of canonical positions. Also supports appending a new caller at the end of the
+ * caller chain of a Position.
+ */
+public class CanonicalPositions {
+  private final Position callerPosition;
+  private final boolean preserveCaller;
+  private final Map<Position, Position> canonicalPositions;
+  private final Position preamblePosition;
+
+  /**
+   * For callerPosition and preserveCaller see canonicalizeCallerPosition. initialCapacity will be
+   * passed to the HashMap constructor.
+   */
+  public CanonicalPositions(
+      Position callerPosition, boolean preserveCaller, int initialCapacity, DexMethod method) {
+    canonicalPositions = new HashMap<>(initialCapacity);
+    this.preserveCaller = preserveCaller;
+    this.callerPosition = callerPosition;
+    if (callerPosition != null) {
+      canonicalPositions.put(callerPosition, callerPosition);
+    }
+    preamblePosition =
+        callerPosition == null
+            ? Position.synthetic(0, method, null)
+            : new Position(0, null, method, callerPosition);
+    canonicalPositions.put(preamblePosition, preamblePosition);
+  }
+
+  public Position getPreamblePosition() {
+    return preamblePosition;
+  }
+
+  /**
+   * Update the internal set if this is the first occurence of the position's value and return
+   * canonical instance of position.
+   */
+  public Position getCanonical(Position position) {
+    Position canonical = canonicalPositions.putIfAbsent(position, position);
+    return canonical != null ? canonical : position;
+  }
+
+  /**
+   * Append callerPosition (supplied in constructor) to the end of caller's caller chain and return
+   * the canonical instance. Always returns null if preserveCaller (also supplied in constructor) is
+   * false.
+   */
+  public Position canonicalizeCallerPosition(Position caller) {
+    if (!preserveCaller) {
+      return null;
+    }
+
+    if (caller == null) {
+      return callerPosition;
+    }
+    if (caller.callerPosition == null && callerPosition == null) {
+      return getCanonical(caller);
+    }
+    Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
+    return getCanonical(
+        caller.isNone()
+            ? Position.noneWithMethod(caller.method, callerOfCaller)
+            : new Position(caller.line, caller.file, caller.method, callerOfCaller));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
index b59794b..e0c47da 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexSourceCode.java
@@ -40,6 +40,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.MethodAccessFlags;
+import com.android.tools.r8.ir.code.CanonicalPositions;
 import com.android.tools.r8.ir.code.CatchHandlers;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.ValueType;
@@ -68,25 +69,20 @@
   private Instruction currentDexInstruction = null;
 
   private Position currentPosition = null;
-  private Map<Position, Position> canonicalPositions = null;
+  private final CanonicalPositions canonicalPositions;
 
   private final List<ValueType> argumentTypes;
 
   private List<DexDebugEntry> debugEntries = null;
   // In case of inlining the position of the invoke in the caller.
-  private final Position callerPosition;
   private final DexMethod method;
-  private final boolean preserveCaller;
-  private final Position preamblePosition;
 
   public DexSourceCode(
       DexCode code, DexEncodedMethod method, Position callerPosition, boolean preserveCaller) {
     this.code = code;
     this.proto = method.method.proto;
     this.accessFlags = method.accessFlags;
-    this.callerPosition = callerPosition;
     this.method = method.method;
-    this.preserveCaller = preserveCaller;
 
     argumentTypes = computeArgumentTypes();
     DexDebugInfo info = code.getDebugInfo();
@@ -94,18 +90,11 @@
       debugEntries = info.computeEntries(method.method);
     }
     canonicalPositions =
-        new HashMap<>(
-            1
-                + (callerPosition == null ? 0 : 1)
-                + (debugEntries == null ? 0 : debugEntries.size()));
-    if (callerPosition != null) {
-      canonicalPositions.put(callerPosition, callerPosition);
-    }
-    preamblePosition =
-        callerPosition == null
-            ? Position.synthetic(0, this.method, null)
-            : new Position(0, null, this.method, callerPosition);
-    canonicalPositions.put(preamblePosition, preamblePosition);
+        new CanonicalPositions(
+            callerPosition,
+            preserveCaller,
+            1 + (callerPosition == null ? 0 : 1) + (debugEntries == null ? 0 : debugEntries.size()),
+            this.method);
   }
 
   @Override
@@ -148,7 +137,7 @@
 
   @Override
   public void buildPrelude(IRBuilder builder) {
-    currentPosition = preamblePosition;
+    currentPosition = canonicalPositions.getPreamblePosition();
     if (code.incomingRegisterSize == 0) {
       return;
     }
@@ -197,7 +186,9 @@
   @Override
   public Position getDebugPositionAtOffset(int offset) {
     DexDebugEntry entry = getDebugEntryAtOffset(offset);
-    return entry == null ? preamblePosition : getCanonicalPositionAppendCaller(entry);
+    return entry == null
+        ? canonicalPositions.getPreamblePosition()
+        : getCanonicalPositionAppendCaller(entry);
   }
 
   @Override
@@ -250,7 +241,7 @@
     int offset = instructionOffset(instructionIndex);
     DexDebugEntry entry = getDebugEntryAtOffset(offset);
     if (entry == null) {
-      currentPosition = preamblePosition;
+      currentPosition = canonicalPositions.getPreamblePosition();
     } else {
       currentPosition = getCanonicalPositionAppendCaller(entry);
       if (entry.lineEntry && entry.address == offset) {
@@ -259,39 +250,17 @@
     }
   }
 
-  private Position getCanonicalPosition(Position position) {
-    Position canonical = canonicalPositions.putIfAbsent(position, position);
-    return canonical != null ? canonical : position;
-  }
-
-  private Position canonicalizeCallerPosition(Position caller) {
-    // We are not supposed to get here from getCanonicalPositionAppendCaller if !preserveCaller.
-    assert preserveCaller;
-
-    if (caller == null) {
-      return callerPosition;
-    }
-    if (caller.callerPosition == null && callerPosition == null) {
-      return getCanonicalPosition(caller);
-    }
-    Position callerOfCaller = canonicalizeCallerPosition(caller.callerPosition);
-    return getCanonicalPosition(
-        caller.isNone()
-            ? Position.noneWithMethod(caller.method, callerOfCaller)
-            : new Position(caller.line, caller.file, caller.method, callerOfCaller));
-  }
-
   private Position getCanonicalPositionAppendCaller(DexDebugEntry entry) {
     // If this instruction has already been inlined then this.method must be the outermost caller.
     assert entry.callerPosition == null
         || entry.callerPosition.getOutermostCaller().method == method;
 
-    return getCanonicalPosition(
+    return canonicalPositions.getCanonical(
         new Position(
             entry.line,
             entry.sourceFile,
             entry.method,
-            preserveCaller ? canonicalizeCallerPosition(entry.callerPosition) : null));
+            canonicalPositions.canonicalizeCallerPosition(entry.callerPosition)));
   }
 
   @Override