Merge "One step closer to actual JVM field resolution."
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
new file mode 100644
index 0000000..adf82dd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -0,0 +1,364 @@
+// Copyright (c) 2017, 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.cf;
+
+import static com.android.tools.r8.dex.Constants.U16BIT_MAX;
+import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
+import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER;
+
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.ir.code.BasicBlock;
+import com.android.tools.r8.ir.code.IRCode;
+import com.android.tools.r8.ir.code.Instruction;
+import com.android.tools.r8.ir.code.InstructionIterator;
+import com.android.tools.r8.ir.code.InstructionListIterator;
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.StackValue;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.regalloc.LiveIntervals;
+import com.android.tools.r8.ir.regalloc.LiveIntervalsUse;
+import com.android.tools.r8.ir.regalloc.LiveRange;
+import com.android.tools.r8.ir.regalloc.RegisterAllocator;
+import com.android.tools.r8.utils.InternalOptions;
+import it.unimi.dsi.fastutil.ints.IntArrayList;
+import it.unimi.dsi.fastutil.ints.IntList;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.NavigableSet;
+import java.util.PriorityQueue;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Allocator for assigning local-indexes to non-stack values.
+ *
+ * <p>This is mostly a very simple variant of {@link
+ * com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator} since for CF there are no register
+ * constraints and thus no need for spilling.
+ */
+public class CfRegisterAllocator implements RegisterAllocator {
+
+  private final IRCode code;
+  private final InternalOptions options;
+
+  // Mapping from basic blocks to the set of values live at entry to that basic block.
+  private Map<BasicBlock, Set<Value>> liveAtEntrySets;
+
+  // List of all top-level live intervals for all SSA values.
+  private final List<LiveIntervals> liveIntervals = new ArrayList<>();
+
+  // List of active intervals.
+  private final List<LiveIntervals> active = new LinkedList<>();
+
+  // List of intervals where the current instruction falls into one of their live range holes.
+  private final List<LiveIntervals> inactive = new LinkedList<>();
+
+  // List of intervals that no register has been allocated to sorted by first live range.
+  private final PriorityQueue<LiveIntervals> unhandled = new PriorityQueue<>();
+
+  // The set of registers that are free for allocation.
+  private final NavigableSet<Integer> freeRegisters = new TreeSet<>();
+
+  private int nextUnusedRegisterNumber = 0;
+
+  private int maxRegisterNumber = 0;
+
+  public CfRegisterAllocator(IRCode code, InternalOptions options) {
+    this.code = code;
+    this.options = options;
+  }
+
+  @Override
+  public int registersUsed() {
+    return maxRegisterNumber + 1;
+  }
+
+  @Override
+  public int getRegisterForValue(Value value, int instructionNumber) {
+    return getRegisterForValue(value);
+  }
+
+  public int getRegisterForValue(Value value) {
+    if (value instanceof FixedLocalValue) {
+      return ((FixedLocalValue) value).getRegister(this);
+    }
+    assert !value.getLiveIntervals().hasSplits();
+    return value.getLiveIntervals().getRegister();
+  }
+
+  @Override
+  public boolean argumentValueUsesHighRegister(Value value, int instructionNumber) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public void allocateRegisters(boolean debug) {
+    assert options.debug == debug;
+    allocateRegisters();
+  }
+
+  public void allocateRegisters() {
+    computeNeedsRegister();
+    computeLivenessInformation();
+    performLinearScan();
+  }
+
+  private void computeNeedsRegister() {
+    InstructionIterator it = code.instructionIterator();
+    while (it.hasNext()) {
+      Instruction next = it.next();
+      Value outValue = next.outValue();
+      if (outValue != null) {
+        outValue.setNeedsRegister(!(outValue instanceof StackValue));
+      }
+    }
+  }
+
+  private void computeLivenessInformation() {
+    code.numberInstructions();
+    liveAtEntrySets = code.computeLiveAtEntrySets();
+    computeLiveRanges();
+  }
+
+  private void computeLiveRanges() {
+    for (BasicBlock block : code.topologicallySortedBlocks()) {
+      Set<Value> live = new HashSet<>();
+      List<BasicBlock> successors = block.getSuccessors();
+      Set<Value> phiOperands = new HashSet<>();
+      for (BasicBlock successor : successors) {
+        live.addAll(liveAtEntrySets.get(successor));
+        int predIndex = successor.getPredecessors().indexOf(block);
+        for (Phi phi : successor.getPhis()) {
+          live.remove(phi);
+          phiOperands.add(phi.getOperand(predIndex));
+          assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+          phiOperands.addAll(phi.getDebugValues());
+        }
+      }
+      live.addAll(phiOperands);
+      List<Instruction> instructions = block.getInstructions();
+      for (Value value : live) {
+        int end = block.entry().getNumber() + instructions.size() * INSTRUCTION_NUMBER_DELTA;
+        // Make sure that phi operands do not overlap the phi live range. The phi operand is
+        // not live until the next instruction, but only until the gap before the next instruction
+        // where the phi value takes over.
+        if (phiOperands.contains(value)) {
+          end--;
+        }
+        addLiveRange(value, block, end);
+      }
+      InstructionListIterator it = block.listIterator(block.getInstructions().size());
+      while (it.hasPrevious()) {
+        Instruction instruction = it.previous();
+        int number = instruction.getNumber();
+        Value definition = instruction.outValue();
+        if (definition != null) {
+          // For instructions that define values which have no use create a live range covering
+          // the instruction. This will typically be instructions that can have side effects even
+          // if their output is not used.
+          if (!definition.isUsed()) {
+            addLiveRange(definition, block, number + INSTRUCTION_NUMBER_DELTA);
+            assert instruction.isArgument()
+                : "Arguments should be the only potentially unused local in CF";
+          }
+          live.remove(definition);
+        }
+        for (Value use : instruction.inValues()) {
+          if (use.needsRegister()) {
+            if (!live.contains(use)) {
+              live.add(use);
+              addLiveRange(use, block, number);
+            }
+            LiveIntervals useIntervals = use.getLiveIntervals();
+            useIntervals.addUse(new LiveIntervalsUse(number, U16BIT_MAX /* unconstrained */));
+          }
+        }
+        if (options.debug) {
+          for (Value use : instruction.getDebugValues()) {
+            assert use.needsRegister();
+            if (!live.contains(use)) {
+              live.add(use);
+              addLiveRange(use, block, number);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  private void addLiveRange(Value value, BasicBlock block, int end) {
+    int firstInstructionInBlock = block.entry().getNumber();
+    int instructionsSize = block.getInstructions().size() * INSTRUCTION_NUMBER_DELTA;
+    int lastInstructionInBlock =
+        firstInstructionInBlock + instructionsSize - INSTRUCTION_NUMBER_DELTA;
+    int instructionNumber;
+    if (value.isPhi()) {
+      instructionNumber = firstInstructionInBlock;
+    } else {
+      Instruction instruction = value.definition;
+      instructionNumber = instruction.getNumber();
+    }
+    if (value.getLiveIntervals() == null) {
+      Value current = value.getStartOfConsecutive();
+      LiveIntervals intervals = new LiveIntervals(current);
+      while (true) {
+        liveIntervals.add(intervals);
+        Value next = current.getNextConsecutive();
+        if (next == null) {
+          break;
+        }
+        LiveIntervals nextIntervals = new LiveIntervals(next);
+        intervals.link(nextIntervals);
+        current = next;
+        intervals = nextIntervals;
+      }
+    }
+    LiveIntervals intervals = value.getLiveIntervals();
+    if (firstInstructionInBlock <= instructionNumber
+        && instructionNumber <= lastInstructionInBlock) {
+      if (value.isPhi()) {
+        // Phis need to interfere with spill restore moves inserted before the instruction because
+        // the phi value is defined on the inflowing edge.
+        instructionNumber--;
+      }
+      intervals.addRange(new LiveRange(instructionNumber, end));
+      if (!value.isPhi()) {
+        intervals.addUse(new LiveIntervalsUse(instructionNumber, U16BIT_MAX /* unconstrained */));
+      }
+    } else {
+      intervals.addRange(new LiveRange(firstInstructionInBlock - 1, end));
+    }
+  }
+
+  private void performLinearScan() {
+    unhandled.addAll(liveIntervals);
+    while (!unhandled.isEmpty()) {
+      LiveIntervals unhandledInterval = unhandled.poll();
+      int start = unhandledInterval.getStart();
+      {
+        // Check for active intervals that expired or became inactive.
+        Iterator<LiveIntervals> it = active.iterator();
+        while (it.hasNext()) {
+          LiveIntervals activeIntervals = it.next();
+          if (start >= activeIntervals.getEnd()) {
+            it.remove();
+            freeRegistersForIntervals(activeIntervals);
+          } else if (!activeIntervals.overlapsPosition(start)) {
+            assert activeIntervals.getRegister() != NO_REGISTER;
+            it.remove();
+            inactive.add(activeIntervals);
+            freeRegistersForIntervals(activeIntervals);
+          }
+        }
+      }
+      IntList reactivedBeforeEnd = new IntArrayList(inactive.size());
+      {
+        // Check for inactive intervals that expired or become active.
+        Iterator<LiveIntervals> it = inactive.iterator();
+        while (it.hasNext()) {
+          LiveIntervals inactiveIntervals = it.next();
+          if (start >= inactiveIntervals.getEnd()) {
+            it.remove();
+          } else if (inactiveIntervals.overlapsPosition(start)) {
+            it.remove();
+            assert inactiveIntervals.getRegister() != NO_REGISTER;
+            active.add(inactiveIntervals);
+            takeRegistersForIntervals(inactiveIntervals);
+          } else if (inactiveIntervals.overlaps(unhandledInterval)) {
+            reactivedBeforeEnd.add(inactiveIntervals.getRegister());
+            takeRegistersForIntervals(inactiveIntervals);
+          }
+        }
+      }
+
+      // Perform the actual allocation.
+      assignRegisterToUnhandledInterval(
+          unhandledInterval, getNextFreeRegister(unhandledInterval.getType().isWide()));
+
+      // Add back the potentially free registers from the inactive set.
+      freeRegisters.addAll(reactivedBeforeEnd);
+    }
+  }
+
+  // Note that getNextFreeRegister will not take the register before it is committed to an interval.
+  private int getNextFreeRegister(boolean isWide) {
+    if (!freeRegisters.isEmpty()) {
+      if (isWide) {
+        for (Integer register : freeRegisters) {
+          if (freeRegisters.contains(register + 1) || nextUnusedRegisterNumber == register + 1) {
+            return register;
+          }
+        }
+        return nextUnusedRegisterNumber;
+      }
+      return freeRegisters.first();
+    }
+    return nextUnusedRegisterNumber;
+  }
+
+  private void freeRegistersForIntervals(LiveIntervals intervals) {
+    int register = intervals.getRegister();
+    freeRegisters.add(register);
+    if (intervals.getType().isWide()) {
+      freeRegisters.add(register + 1);
+    }
+  }
+
+  private void takeRegistersForIntervals(LiveIntervals intervals) {
+    int register = intervals.getRegister();
+    freeRegisters.remove(register);
+    if (intervals.getType().isWide()) {
+      freeRegisters.remove(register + 1);
+    }
+  }
+
+  private void assignRegisterToUnhandledInterval(LiveIntervals unhandledInterval, int register) {
+    assignRegister(unhandledInterval, register);
+    takeRegistersForIntervals(unhandledInterval);
+    updateRegisterState(register, unhandledInterval.getType().isWide());
+    active.add(unhandledInterval);
+  }
+
+  private void updateRegisterState(int register, boolean needsRegisterPair) {
+    int maxRegister = register + (needsRegisterPair ? 1 : 0);
+    if (maxRegister >= nextUnusedRegisterNumber) {
+      nextUnusedRegisterNumber = maxRegister + 1;
+    }
+    maxRegisterNumber = Math.max(maxRegisterNumber, maxRegister);
+  }
+
+  private void assignRegister(LiveIntervals intervals, int register) {
+    intervals.setRegister(register);
+  }
+
+  public Collection<Value> getLocalsAtPosition(int blockEntryInstruction) {
+    List<Value> values = new ArrayList<>(registersUsed());
+    for (LiveIntervals intervals : liveIntervals) {
+      addLiveValueFromIntervals(blockEntryInstruction, intervals, values);
+    }
+    return values;
+  }
+
+  private static void addLiveValueFromIntervals(
+      int position, LiveIntervals intervals, List<Value> values) {
+    if (intervals.overlapsPosition(position)) {
+      for (LiveRange range : intervals.getRanges()) {
+        if (range.start <= position && position < range.end) {
+          values.add(intervals.getValue());
+          return;
+        }
+      }
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java b/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java
new file mode 100644
index 0000000..56ab3cd
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/cf/FixedLocalValue.java
@@ -0,0 +1,38 @@
+// Copyright (c) 2017, 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.cf;
+
+import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.Value;
+
+/**
+ * Value that represents a shared physical location defined by the phi value.
+ *
+ * <p>This value is introduced to represent the store instructions used to unify the location of
+ * in-flowing values to phi's. After introducing this fixed location the graph is no longer in SSA
+ * since the fixed location signifies a place that can be written to from multiple places.
+ */
+public class FixedLocalValue extends Value {
+
+  private final Phi phi;
+
+  public FixedLocalValue(Phi phi) {
+    super(phi.getNumber(), phi.outType(), phi.getLocalInfo());
+    this.phi = phi;
+  }
+
+  public int getRegister(CfRegisterAllocator allocator) {
+    return allocator.getRegisterForValue(phi, -1);
+  }
+
+  @Override
+  public boolean isConstant() {
+    return false;
+  }
+
+  @Override
+  public String toString() {
+    return "fixed:v" + phi.getNumber();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
index 6242e9b..bb76dc7 100644
--- a/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/LoadStoreHelper.java
@@ -21,7 +21,6 @@
 import com.android.tools.r8.ir.code.Store;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
-import com.android.tools.r8.ir.conversion.CfBuilder.FixedLocal;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
@@ -37,22 +36,6 @@
   }
 
   public void insertLoadsAndStores() {
-    // Insert phi stores in all predecessors.
-    for (BasicBlock block : code.blocks) {
-      if (!block.getPhis().isEmpty()) {
-        for (int predIndex = 0; predIndex < block.getPredecessors().size(); predIndex++) {
-          BasicBlock pred = block.getPredecessors().get(predIndex);
-          List<Phi> phis = block.getPhis();
-          List<Value> values = new ArrayList<>(phis.size());
-          for (Phi phi : phis) {
-            values.add(phi.getOperand(predIndex));
-          }
-          InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
-          it.previous();
-          movePhis(phis, values, it);
-        }
-      }
-    }
     // Insert per-instruction loads and stores.
     for (BasicBlock block : code.blocks) {
       InstructionListIterator it = block.listIterator();
@@ -63,6 +46,29 @@
     }
   }
 
+  public void insertPhiMoves(CfRegisterAllocator allocator) {
+    // Insert phi stores in all predecessors.
+    for (BasicBlock block : code.blocks) {
+      if (!block.getPhis().isEmpty()) {
+        for (int predIndex = 0; predIndex < block.getPredecessors().size(); predIndex++) {
+          BasicBlock pred = block.getPredecessors().get(predIndex);
+          List<Phi> phis = block.getPhis();
+          List<PhiMove> moves = new ArrayList<>(phis.size());
+          for (Phi phi : phis) {
+            Value value = phi.getOperand(predIndex);
+            if (allocator.getRegisterForValue(phi) != allocator.getRegisterForValue(value)) {
+              moves.add(new PhiMove(phi, value));
+            }
+          }
+          InstructionListIterator it = pred.listIterator(pred.getInstructions().size());
+          it.previous();
+          movePhis(moves, it);
+        }
+      }
+    }
+    code.blocks.forEach(BasicBlock::clearUserInfo);
+  }
+
   private StackValue createStackValue(Value value, int height) {
     if (value.outType().isObject()) {
       return StackValue.forObjectType(types.get(value), height);
@@ -111,24 +117,32 @@
     add(new Pop(newOutValue), instruction, it);
   }
 
-  public void movePhis(List<Phi> phis, List<Value> values, InstructionListIterator it) {
+  private static class PhiMove {
+    final Phi phi;
+    final Value operand;
+
+    public PhiMove(Phi phi, Value operand) {
+      this.phi = phi;
+      this.operand = operand;
+    }
+  }
+
+  private void movePhis(List<PhiMove> moves, InstructionListIterator it) {
     // TODO(zerny): Accounting for non-interfering phis would lower the max stack size.
     int topOfStack = 0;
-    List<StackValue> temps = new ArrayList<>(phis.size());
-    for (int i = 0; i < phis.size(); i++) {
-      Phi phi = phis.get(i);
-      Value value = values.get(i);
-      StackValue tmp = createStackValue(phi, topOfStack++);
-      add(load(tmp, value), phi.getBlock(), Position.none(), it);
+    List<StackValue> temps = new ArrayList<>(moves.size());
+    for (PhiMove move : moves) {
+      StackValue tmp = createStackValue(move.phi, topOfStack++);
+      add(load(tmp, move.operand), move.phi.getBlock(), Position.none(), it);
       temps.add(tmp);
-      value.removePhiUser(phi);
+      move.operand.removePhiUser(move.phi);
     }
-    for (int i = phis.size() - 1; i >= 0; i--) {
-      Phi phi = phis.get(i);
+    for (int i = moves.size() - 1; i >= 0; i--) {
+      PhiMove move = moves.get(i);
       StackValue tmp = temps.get(i);
-      FixedLocal out = new FixedLocal(phi);
-      add(new Store(out, tmp), phi.getBlock(), Position.none(), it);
-      phi.replaceUsers(out);
+      FixedLocalValue out = new FixedLocalValue(move.phi);
+      add(new Store(out, tmp), move.phi.getBlock(), Position.none(), it);
+      move.phi.replaceUsers(out);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
index 2789252..500cff3 100644
--- a/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
+++ b/src/main/java/com/android/tools/r8/compatdx/CompatDx.java
@@ -321,7 +321,6 @@
   }
 
   private static void run(String[] args) throws DxUsageMessage, IOException, CompilationException {
-    System.out.println("CompatDx " + String.join(" ", args));
     DxCompatOptions dexArgs = DxCompatOptions.parse(args);
     if (dexArgs.help) {
       printHelpOn(System.out);
@@ -348,11 +347,11 @@
       throw new DxUsageMessage("No input files specified");
     }
 
-    if (!Log.ENABLED && (!dexArgs.noWarning || dexArgs.debug || dexArgs.verbose)) {
+    if (!Log.ENABLED && dexArgs.debug) {
       System.out.println("Warning: logging is not enabled for this build.");
     }
 
-    if (dexArgs.dump) {
+    if (dexArgs.dump && dexArgs.verbose) {
       System.out.println("Warning: dump is not supported");
     }
 
@@ -377,11 +376,11 @@
       }
     }
 
-    if (dexArgs.dumpTo != null) {
+    if (dexArgs.dumpTo != null && dexArgs.verbose) {
       System.out.println("dump-to file not yet supported");
     }
 
-    if (dexArgs.positions == PositionInfo.NONE) {
+    if (dexArgs.positions == PositionInfo.NONE && dexArgs.verbose) {
       System.out.println("Warning: no support for positions none.");
     }
 
@@ -393,7 +392,7 @@
       throw new Unimplemented("incremental merge not supported yet");
     }
 
-    if (dexArgs.forceJumbo) {
+    if (dexArgs.forceJumbo && dexArgs.verbose) {
       System.out.println(
           "Warning: no support for forcing jumbo-strings.\n"
               + "Strings will only use jumbo-string indexing if necessary.\n"
@@ -401,7 +400,7 @@
               + "supports correct handling of jumbo-strings (eg, D8/R8 does).");
     }
 
-    if (dexArgs.noOptimize) {
+    if (dexArgs.noOptimize && dexArgs.verbose) {
       System.out.println("Warning: no support for not optimizing");
     }
 
@@ -413,7 +412,7 @@
       throw new Unimplemented("no support for dont-optimize-method list");
     }
 
-    if (dexArgs.statistics) {
+    if (dexArgs.statistics && dexArgs.verbose) {
       System.out.println("Warning: no support for printing statistics");
     }
 
@@ -426,16 +425,20 @@
     }
 
     if (dexArgs.noStrict) {
-      System.out.println("Warning: conservative main-dex list not yet supported");
+      if (dexArgs.verbose) {
+        System.out.println("Warning: conservative main-dex list not yet supported");
+      }
     } else {
-      System.out.println("Warning: strict name checking not yet supported");
+      if (dexArgs.verbose) {
+        System.out.println("Warning: strict name checking not yet supported");
+      }
     }
 
-    if (dexArgs.minimalMainDex) {
+    if (dexArgs.minimalMainDex && dexArgs.verbose) {
       System.out.println("Warning: minimal main-dex support is not yet supported");
     }
 
-    if (dexArgs.maxIndexNumber != 0) {
+    if (dexArgs.maxIndexNumber != 0 && dexArgs.verbose) {
       System.out.println("Warning: internal maximum-index setting is not supported");
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 934d3db..3ec6b1a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -7,22 +7,29 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.utils.CfgPrinter;
 import com.google.common.collect.ImmutableList;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
+import java.util.Map;
+import java.util.Queue;
 import java.util.Set;
 import java.util.stream.Collectors;
 
 public class IRCode {
 
+  // When numbering instructions we number instructions only with even numbers. This allows us to
+  // use odd instruction numbers for the insertion of moves during spilling.
+  public static final int INSTRUCTION_NUMBER_DELTA = 2;
+
   public final DexEncodedMethod method;
 
   public LinkedList<BasicBlock> blocks;
@@ -49,6 +56,66 @@
     allThrowingInstructionsHavePositions = computeAllThrowingInstructionsHavePositions();
   }
 
+  /**
+   * Compute the set of live values at the entry to each block using a backwards data-flow analysis.
+   */
+  public Map<BasicBlock, Set<Value>> computeLiveAtEntrySets() {
+    Map<BasicBlock, Set<Value>> liveAtEntrySets = new IdentityHashMap<>();
+    Queue<BasicBlock> worklist = new ArrayDeque<>();
+    // Since this is a backwards data-flow analysis we process the blocks in reverse
+    // topological order to reduce the number of iterations.
+    BasicBlock[] sorted = topologicallySortedBlocks();
+    for (int i = sorted.length - 1; i >= 0; i--) {
+      worklist.add(sorted[i]);
+    }
+    while (!worklist.isEmpty()) {
+      BasicBlock block = worklist.poll();
+      Set<Value> live = new HashSet<>();
+      for (BasicBlock succ : block.getSuccessors()) {
+        Set<Value> succLiveAtEntry = liveAtEntrySets.get(succ);
+        if (succLiveAtEntry != null) {
+          live.addAll(succLiveAtEntry);
+        }
+        int predIndex = succ.getPredecessors().indexOf(block);
+        for (Phi phi : succ.getPhis()) {
+          live.add(phi.getOperand(predIndex));
+          assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
+          live.addAll(phi.getDebugValues());
+        }
+      }
+      ListIterator<Instruction> iterator =
+          block.getInstructions().listIterator(block.getInstructions().size());
+      while (iterator.hasPrevious()) {
+        Instruction instruction = iterator.previous();
+        if (instruction.outValue() != null) {
+          live.remove(instruction.outValue());
+        }
+        for (Value use : instruction.inValues()) {
+          if (use.needsRegister()) {
+            live.add(use);
+          }
+        }
+        assert instruction.getDebugValues().stream().allMatch(Value::needsRegister);
+        live.addAll(instruction.getDebugValues());
+      }
+      for (Phi phi : block.getPhis()) {
+        live.remove(phi);
+      }
+      Set<Value> previousLiveAtEntry = liveAtEntrySets.put(block, live);
+      // If the live-at-entry set changed, add the predecessors to the worklist if they are not
+      // already there.
+      if (previousLiveAtEntry == null || !previousLiveAtEntry.equals(live)) {
+        for (BasicBlock pred : block.getPredecessors()) {
+          if (!worklist.contains(pred)) {
+            worklist.add(pred);
+          }
+        }
+      }
+    }
+    assert liveAtEntrySets.get(sorted[0]).size() == 0;
+    return liveAtEntrySets;
+  }
+
   public void splitCriticalEdges() {
     List<BasicBlock> newBlocks = new ArrayList<>();
     int nextBlockNumber = getHighestBlockNumber() + 1;
@@ -438,7 +505,7 @@
     for (BasicBlock block : blocks) {
       for (Instruction instruction : block.getInstructions()) {
         instruction.setNumber(nextInstructionNumber);
-        nextInstructionNumber += LinearScanRegisterAllocator.INSTRUCTION_NUMBER_DELTA;
+        nextInstructionNumber += INSTRUCTION_NUMBER_DELTA;
       }
     }
     return blocks;
@@ -450,7 +517,7 @@
       Instruction i = it.next();
       if (i.getNumber() == -1) {
         i.setNumber(nextInstructionNumber);
-        nextInstructionNumber += LinearScanRegisterAllocator.INSTRUCTION_NUMBER_DELTA;
+        nextInstructionNumber += INSTRUCTION_NUMBER_DELTA;
       }
     }
     return nextInstructionNumber;
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValue.java b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
index 65c4d3f..94909db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
@@ -38,6 +38,16 @@
   }
 
   @Override
+  public boolean needsRegister() {
+    return false;
+  }
+
+  @Override
+  public void setNeedsRegister(boolean value) {
+    assert !value;
+  }
+
+  @Override
   public String toString() {
     return "s" + height;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Store.java b/src/main/java/com/android/tools/r8/ir/code/Store.java
index 7bdf7ad..05c7c79 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Store.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Store.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.code;
 
+import com.android.tools.r8.cf.FixedLocalValue;
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfStore;
@@ -10,7 +11,6 @@
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.conversion.CfBuilder;
-import com.android.tools.r8.ir.conversion.CfBuilder.FixedLocal;
 import com.android.tools.r8.ir.optimize.Inliner.Constraint;
 import com.android.tools.r8.utils.InternalOptions;
 
@@ -72,6 +72,6 @@
 
   @Override
   public boolean canBeDeadCode(IRCode code, InternalOptions options) {
-    return !(outValue instanceof FixedLocal);
+    return !(outValue instanceof FixedLocalValue);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
index d4cc49c..0ad6c6c 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CfBuilder.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.conversion;
 
+import com.android.tools.r8.cf.CfRegisterAllocator;
 import com.android.tools.r8.cf.LoadStoreHelper;
 import com.android.tools.r8.cf.TypeVerificationHelper;
 import com.android.tools.r8.cf.code.CfFrame;
@@ -24,7 +25,6 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Load;
-import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StackValue;
 import com.android.tools.r8.ir.code.Store;
 import com.android.tools.r8.ir.code.Value;
@@ -33,8 +33,6 @@
 import com.android.tools.r8.utils.InternalOptions;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
-import it.unimi.dsi.fastutil.objects.Reference2IntMap;
-import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -50,39 +48,10 @@
   private final DexEncodedMethod method;
   private final IRCode code;
 
-  private int maxLocals = -1;
-
   private Map<Value, DexType> types;
-  private Reference2IntMap<Value> registers;
   private Map<BasicBlock, CfLabel> labels;
   private List<CfInstruction> instructions;
-
-  /**
-   * Value that represents a shared physical location defined by the phi value.
-   *
-   * This value is introduced to represent the store instructions used to unify the location of
-   * in-flowing values to phi's. After introducing this fixed location the graph is no longer in
-   * SSA since the fixed location signifies a place that can be written to from multiple places.
-   */
-  public static class FixedLocal extends Value {
-
-    private final Phi phi;
-
-    public FixedLocal(Phi phi) {
-      super(phi.getNumber(), phi.outType(), phi.getLocalInfo());
-      this.phi = phi;
-    }
-
-    @Override
-    public boolean isConstant() {
-      return false;
-    }
-
-    @Override
-    public String toString() {
-      return "fixed:v" + phi.getNumber();
-    }
-  }
+  private CfRegisterAllocator registerAllocator;
 
   // Internal abstraction of the stack values and height.
   private static class Stack {
@@ -115,10 +84,14 @@
     try {
       types = new TypeVerificationHelper(code, factory).computeVerificationTypes();
       splitExceptionalBlocks();
-      new LoadStoreHelper(code, types).insertLoadsAndStores();
+      LoadStoreHelper loadStoreHelper = new LoadStoreHelper(code, types);
+      loadStoreHelper.insertLoadsAndStores();
       DeadCodeRemover.removeDeadCode(code, rewriter, options);
       removeUnneededLoadsAndStores();
-      allocateLocalRegisters();
+      registerAllocator = new CfRegisterAllocator(code, options);
+      registerAllocator.allocateRegisters();
+      loadStoreHelper.insertPhiMoves(registerAllocator);
+      CodeRewriter.collapsTrivialGotos(method, code);
       // TODO(zerny): Compute debug info.
       CfCode code = buildCfCode();
       return code;
@@ -193,29 +166,6 @@
     }
   }
 
-  private void allocateLocalRegisters() {
-    // TODO(zerny): Allocate locals based on live ranges.
-    InstructionIterator it = code.instructionIterator();
-    registers = new Reference2IntOpenHashMap<>();
-    int nextFreeRegister = 0;
-    while (it.hasNext()) {
-      Instruction instruction = it.next();
-      Value outValue = instruction.outValue();
-      if (outValue instanceof FixedLocal) {
-        // Phi stores are marked by a "fixed-local" value which share the same local index.
-        FixedLocal fixed = (FixedLocal) outValue;
-        if (!registers.containsKey(fixed.phi)) {
-          registers.put(fixed.phi, nextFreeRegister);
-          nextFreeRegister += fixed.requiredRegisters();
-        }
-      } else if (outValue != null && !(outValue instanceof StackValue)) {
-        registers.put(instruction.outValue(), nextFreeRegister);
-        nextFreeRegister += instruction.outValue().requiredRegisters();
-      }
-    }
-    maxLocals = nextFreeRegister;
-  }
-
   private CfCode buildCfCode() {
     Stack stack = new Stack();
     List<CfTryCatch> tryCatchRanges = new ArrayList<>();
@@ -253,13 +203,14 @@
         assert stack.isEmpty();
         instructions.add(getLabel(nextBlock));
         if (nextBlock.getPredecessors().size() > 1) {
-          addFrame(Collections.emptyList(), types.keySet());
+          addFrame(nextBlock, Collections.emptyList());
         }
       }
       block = nextBlock;
     } while (block != null);
     assert stack.isEmpty();
-    return new CfCode(stack.maxHeight, maxLocals, instructions, tryCatchRanges);
+    return new CfCode(
+        stack.maxHeight, registerAllocator.registersUsed(), instructions, tryCatchRanges);
   }
 
   private void buildCfInstructions(BasicBlock block, boolean fallthrough, Stack stack) {
@@ -285,9 +236,10 @@
     }
   }
 
-  private void addFrame(Collection<StackValue> stack, Collection<Value> locals) {
+  private void addFrame(BasicBlock block, Collection<StackValue> stack) {
     // TODO(zerny): Support having values on the stack on control-edges.
     assert stack.isEmpty();
+    Collection<Value> locals = registerAllocator.getLocalsAtPosition(block.entry().getNumber());
     Int2ReferenceSortedMap<DexType> mapping = new Int2ReferenceAVLTreeMap<>();
     for (Value local : locals) {
       DexType type;
@@ -323,12 +275,7 @@
   }
 
   public int getLocalRegister(Value value) {
-    if (value instanceof FixedLocal) {
-      // Phi stores are marked by a "fixed-local" value which share the same local index.
-      FixedLocal fixed = (FixedLocal) value;
-      return registers.getInt(fixed.phi);
-    }
-    return registers.getInt(value);
+    return registerAllocator.getRegisterForValue(value);
   }
 
   public void add(CfInstruction instruction) {
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 43a91f3..928d3c1 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
@@ -57,7 +57,6 @@
 import com.android.tools.r8.ir.code.Return;
 import com.android.tools.r8.ir.code.Switch;
 import com.android.tools.r8.ir.code.Value;
-import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.utils.InternalOptions;
 import com.google.common.collect.BiMap;
@@ -510,7 +509,7 @@
   }
 
   private static int instructionNumberToIndex(int instructionNumber) {
-    return instructionNumber / LinearScanRegisterAllocator.INSTRUCTION_NUMBER_DELTA;
+    return instructionNumber / IRCode.INSTRUCTION_NUMBER_DELTA;
   }
 
   // Helper used by the info objects.
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index 55ecb45..b982b52 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.ir.regalloc;
 
-import com.android.tools.r8.code.MoveType;
+import static com.android.tools.r8.ir.code.IRCode.INSTRUCTION_NUMBER_DELTA;
+import static com.android.tools.r8.ir.regalloc.LiveIntervals.NO_REGISTER;
+
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.code.Add;
@@ -41,14 +43,12 @@
 import it.unimi.dsi.fastutil.ints.IntSet;
 import java.util.ArrayList;
 import java.util.HashSet;
-import java.util.IdentityHashMap;
 import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.PriorityQueue;
-import java.util.Queue;
 import java.util.Set;
 import java.util.TreeSet;
 
@@ -67,7 +67,7 @@
  * </ul>
  */
 public class LinearScanRegisterAllocator implements RegisterAllocator {
-  public static final int NO_REGISTER = Integer.MIN_VALUE;
+
   public static final int REGISTER_CANDIDATE_NOT_FOUND = -1;
   public static final int MIN_CONSTANT_FREE_FOR_POSITIONS = 5;
 
@@ -105,9 +105,6 @@
     }
   }
 
-  // When numbering instructions we number instructions only with even numbers. This allows us to
-  // use odd instruction numbers for the insertion of moves during spilling.
-  public static final int INSTRUCTION_NUMBER_DELTA = 2;
   // The max register number that will fit in any dex instruction encoding.
   private static final int MAX_SMALL_REGISTER = Constants.U4BIT_MAX;
   // We put one sentinel in front of the argument chain and one after the argument chain.
@@ -121,7 +118,7 @@
   private final InternalOptions options;
 
   // Mapping from basic blocks to the set of values live at entry to that basic block.
-  private Map<BasicBlock, Set<Value>> liveAtEntrySets = new IdentityHashMap<>();
+  private Map<BasicBlock, Set<Value>> liveAtEntrySets;
   // The sentinel value starting the chain of linked argument values.
   private Value preArgumentSentinelValue = null;
 
@@ -488,7 +485,7 @@
     boolean unused = intervals.isSpilledAndRematerializable(this);
     if (!unused) {
       used.add(realRegisterNumberFromAllocated(intervals.getRegister()));
-      if (intervals.getType() == MoveType.WIDE) {
+      if (intervals.getType().isWide()) {
         used.add(realRegisterNumberFromAllocated(intervals.getRegister() + 1));
       }
     }
@@ -535,7 +532,7 @@
 
   private BasicBlock[] computeLivenessInformation() {
     BasicBlock[] blocks = code.numberInstructions();
-    computeLiveAtEntrySets();
+    liveAtEntrySets = code.computeLiveAtEntrySets();
     computeLiveRanges();
     return blocks;
   }
@@ -933,7 +930,7 @@
     for (int i = 0; i < numberOfRegister; i++) {
       assert current != null;
       current.setRegister(firstRegister + i);
-      if (current.getType() == MoveType.WIDE) {
+      if (current.getType().isWide()) {
         assert i < numberOfRegister;
         i++;
       }
@@ -974,7 +971,7 @@
   private int getSpillRegister(LiveIntervals intervals) {
     int registerNumber = nextUnusedRegisterNumber++;
     maxRegisterNumber = registerNumber;
-    if (intervals.getType() == MoveType.WIDE) {
+    if (intervals.getType().isWide()) {
       nextUnusedRegisterNumber++;
       maxRegisterNumber++;
     }
@@ -1817,65 +1814,6 @@
     }
   }
 
-  /**
-   * Compute the set of live values at the entry to each block using a backwards data-flow
-   * analysis.
-   */
-  private void computeLiveAtEntrySets() {
-    Queue<BasicBlock> worklist = new LinkedList<>();
-    // Since this is a backwards data-flow analysis we process the blocks in reverse
-    // topological order to reduce the number of iterations.
-    BasicBlock[] sorted = code.topologicallySortedBlocks();
-    for (int i = sorted.length - 1; i >= 0; i--) {
-      worklist.add(sorted[i]);
-    }
-    while (!worklist.isEmpty()) {
-      BasicBlock block = worklist.poll();
-      Set<Value> live = new HashSet<>();
-      for (BasicBlock succ : block.getSuccessors()) {
-        Set<Value> succLiveAtEntry = liveAtEntrySets.get(succ);
-        if (succLiveAtEntry != null) {
-          live.addAll(succLiveAtEntry);
-        }
-        int predIndex = succ.getPredecessors().indexOf(block);
-        for (Phi phi : succ.getPhis()) {
-          live.add(phi.getOperand(predIndex));
-          assert phi.getDebugValues().stream().allMatch(Value::needsRegister);
-          live.addAll(phi.getDebugValues());
-        }
-      }
-      ListIterator<Instruction> iterator =
-          block.getInstructions().listIterator(block.getInstructions().size());
-      while (iterator.hasPrevious()) {
-        Instruction instruction = iterator.previous();
-        if (instruction.outValue() != null) {
-          live.remove(instruction.outValue());
-        }
-        for (Value use : instruction.inValues()) {
-          if (use.needsRegister()) {
-            live.add(use);
-          }
-        }
-        assert instruction.getDebugValues().stream().allMatch(Value::needsRegister);
-        live.addAll(instruction.getDebugValues());
-      }
-      for (Phi phi : block.getPhis()) {
-        live.remove(phi);
-      }
-      Set<Value> previousLiveAtEntry = liveAtEntrySets.put(block, live);
-      // If the live at entry set changed for this block at the predecessors to the worklist if
-      // they are not already there.
-      if (previousLiveAtEntry == null || !previousLiveAtEntry.equals(live)) {
-        for (BasicBlock pred : block.getPredecessors()) {
-          if (!worklist.contains(pred)) {
-            worklist.add(pred);
-          }
-        }
-      }
-    }
-    assert liveAtEntrySets.get(sorted[0]).size() == 0;
-  }
-
   private void addLiveRange(Value v, BasicBlock b, int end) {
     int firstInstructionInBlock = b.entry().getNumber();
     int instructionsSize = b.getInstructions().size() * INSTRUCTION_NUMBER_DELTA;
@@ -2187,7 +2125,7 @@
   private void freeRegistersForIntervals(LiveIntervals intervals) {
     int register = intervals.getRegister();
     freeRegisters.add(register);
-    if (intervals.getType() == MoveType.WIDE) {
+    if (intervals.getType().isWide()) {
       freeRegisters.add(register + 1);
     }
   }
@@ -2195,7 +2133,7 @@
   private void takeRegistersForIntervals(LiveIntervals intervals) {
     int register = intervals.getRegister();
     freeRegisters.remove(register);
-    if (intervals.getType() == MoveType.WIDE) {
+    if (intervals.getType().isWide()) {
       freeRegisters.remove(register + 1);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
index 4745a81..4c68c4a 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveIntervals.java
@@ -4,11 +4,11 @@
 package com.android.tools.r8.ir.regalloc;
 
 import static com.android.tools.r8.dex.Constants.U16BIT_MAX;
-import static com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator.NO_REGISTER;
 
 import com.android.tools.r8.code.MoveType;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.ir.code.ValueType;
 import com.android.tools.r8.utils.CfgPrinter;
 import java.util.ArrayList;
 import java.util.Iterator;
@@ -17,8 +17,9 @@
 
 public class LiveIntervals implements Comparable<LiveIntervals> {
 
+  public static final int NO_REGISTER = Integer.MIN_VALUE;
+
   private final Value value;
-  private final MoveType type;
   private LiveIntervals nextConsecutive;
   private LiveIntervals previousConsecutive;
   private LiveIntervals splitParent;
@@ -39,18 +40,16 @@
   // conservatively determine if it is safe to use rematerialization for this value.
   private int maxNonSpilledRegister = NO_REGISTER;
 
-  LiveIntervals(Value value) {
+  public LiveIntervals(Value value) {
     this.value = value;
-    this.type = MoveType.fromValueType(value.outType());
     usedInMonitorOperations = value.usedInMonitorOperation();
     splitParent = this;
     value.setLiveIntervals(this);
   }
 
-  LiveIntervals(LiveIntervals splitParent) {
+  public LiveIntervals(LiveIntervals splitParent) {
     this.splitParent = splitParent;
     value = splitParent.value;
-    type = splitParent.type;
     usedInMonitorOperations = splitParent.usedInMonitorOperations;
   }
 
@@ -62,16 +61,20 @@
     return position % 2 == 1 ? position : position - 1;
   }
 
-  public MoveType getType() {
-    return type;
-  }
-
   public Value getValue() {
     return value;
   }
 
+  public ValueType getType() {
+    return value.outType();
+  }
+
+  public MoveType getMoveType() {
+    return MoveType.fromValueType(getType());
+  }
+
   public int requiredRegisters() {
-    return type == MoveType.WIDE ? 2 : 1;
+    return getType().requiredRegisters();
   }
 
   public void setHint(LiveIntervals intervals) {
@@ -294,7 +297,7 @@
   }
 
   public boolean usesRegister(int n) {
-    if (register == n || (getType() == MoveType.WIDE && register + 1 == n)) {
+    if (register == n || (getType().isWide() && register + 1 == n)) {
       return true;
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LiveRange.java b/src/main/java/com/android/tools/r8/ir/regalloc/LiveRange.java
index 0d7bb80..711c400 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LiveRange.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LiveRange.java
@@ -3,7 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.regalloc;
 
-class LiveRange {
+public class LiveRange {
 
   public final static LiveRange INFINITE = new LiveRange(0, Integer.MAX_VALUE);
 
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
index dd857fc..8724d43 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMove.java
@@ -24,7 +24,7 @@
 
   public RegisterMove(int dst, MoveType type, Instruction definition) {
     this.dst = dst;
-    this.src = LinearScanRegisterAllocator.NO_REGISTER;
+    this.src = LiveIntervals.NO_REGISTER;
     this.type = type;
     assert definition.isConstInstruction() || definition.isArgument();
     this.definition = definition;
@@ -40,7 +40,7 @@
 
   public boolean isBlocked(Set<RegisterMove> moveSet, Map<Integer, Integer> valueMap) {
     for (RegisterMove move : moveSet) {
-      if (move.src == LinearScanRegisterAllocator.NO_REGISTER) {
+      if (move.src == LiveIntervals.NO_REGISTER) {
         continue;
       }
       if (move != this) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
index 9a22d76..32bdca4 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterMoveScheduler.java
@@ -50,7 +50,7 @@
 
   public void addMove(RegisterMove move) {
     moveSet.add(move);
-    if (move.src != LinearScanRegisterAllocator.NO_REGISTER) {
+    if (move.src != LiveIntervals.NO_REGISTER) {
       valueMap.put(move.src, move.src);
     }
     valueMap.put(move.dst, move.dst);
@@ -80,7 +80,7 @@
         Integer generatedDest = createMove(move);
         // Update the value map with the information that dest can be used instead of
         // src starting now.
-        if (move.src != LinearScanRegisterAllocator.NO_REGISTER) {
+        if (move.src != LiveIntervals.NO_REGISTER) {
           valueMap.put(move.src, generatedDest);
         }
         // Iterate and find the moves that were blocked because they need to write to
@@ -110,9 +110,9 @@
 
   private List<RegisterMove> findMovesWithSrc(int src, MoveType type) {
     List<RegisterMove> result = new ArrayList<>();
-    assert src != LinearScanRegisterAllocator.NO_REGISTER;
+    assert src != LiveIntervals.NO_REGISTER;
     for (RegisterMove move : moveSet) {
-      if (move.src == LinearScanRegisterAllocator.NO_REGISTER) {
+      if (move.src == LiveIntervals.NO_REGISTER) {
         continue;
       }
       int moveSrc = valueMap.get(move.src);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java
index 70354fe..ebc95ec 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMove.java
@@ -20,8 +20,8 @@
     this.type = type;
     this.to = to;
     this.from = from;
-    assert to.getRegister() != LinearScanRegisterAllocator.NO_REGISTER;
-    assert from.getRegister() != LinearScanRegisterAllocator.NO_REGISTER;
+    assert to.getRegister() != LiveIntervals.NO_REGISTER;
+    assert from.getRegister() != LiveIntervals.NO_REGISTER;
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index e466904..979b128 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -144,8 +144,8 @@
   }
 
   private MoveType moveTypeForIntervals(LiveIntervals to, LiveIntervals from) {
-    MoveType toType = to.getType();
-    MoveType fromType = from.getType();
+    MoveType toType = to.getMoveType();
+    MoveType fromType = from.getMoveType();
     if (toType == MoveType.OBJECT || fromType == MoveType.OBJECT) {
       assert fromType == MoveType.OBJECT || fromType == MoveType.SINGLE;
       assert toType == MoveType.OBJECT || toType == MoveType.SINGLE;