Merge "BasicBlock bugfix: Remember to update Phi.definitionUsers"
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index c2cfe42..ae43001 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -97,6 +97,11 @@
   }
 
   @Override
+  public InternalOptions getOptions() {
+    return options;
+  }
+
+  @Override
   public void allocateRegisters(boolean debug) {
     assert options.debug == debug;
     allocateRegisters();
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index f751669..c2f21ac 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -26,8 +26,13 @@
   public Binop(NumericType type, Value dest, Value left, Value right) {
     super(dest);
     this.type = type;
-    addInValue(left);
-    addInValue(right);
+    if (isCommutative() && (!right.isConstNumber() && left.isConstNumber())) {
+      addInValue(right);
+      addInValue(left);
+    } else {
+      addInValue(left);
+      addInValue(right);
+    }
   }
 
   public NumericType getNumericType() {
@@ -53,7 +58,8 @@
       return ((leftRegister == destRegister) ||
           (isCommutative() && rightRegister == destRegister)) &&
           leftRegister <= U4BIT_MAX &&
-          rightRegister <= U4BIT_MAX;
+          rightRegister <= U4BIT_MAX &&
+          !(allocator.getOptions().canHaveMul2AddrBug() && isMul());
     }
     return false;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index a54208d..67a4783 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1549,8 +1549,7 @@
     insertAt.nextUntil(i ->
         i.inValues().contains(instruction.outValue())
         || i.isJumpInstruction()
-        || (hasCatchHandlers && i.instructionTypeCanThrow())
-        || (options.canHaveBoundsCheckEliminationBug() && i.isArrayLength()));
+        || (hasCatchHandlers && i.instructionTypeCanThrow()));
     Instruction next = insertAt.previous();
     instruction.forceSetPosition(
         next.isGoto() ? next.asGoto().getTarget().getPosition() : next.getPosition());
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 dbce627..8ad7a32 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
@@ -298,10 +298,11 @@
     ranges.sort(LocalRange::compareTo);
 
     // At each instruction compute the changes to live locals.
-    boolean localsChanged = false;
     LinkedList<LocalRange> openRanges = new LinkedList<>();
     Iterator<LocalRange> rangeIterator = ranges.iterator();
     LocalRange nextStartingRange = rangeIterator.next();
+    Int2ReferenceMap<DebugLocalInfo> ending = new Int2ReferenceOpenHashMap<>();
+    Int2ReferenceMap<DebugLocalInfo> starting = new Int2ReferenceOpenHashMap<>();
 
     for (BasicBlock block : blocks) {
       // Skip past all spill moves to obtain the instruction number of the actual first instruction.
@@ -347,42 +348,52 @@
           instruction.clearDebugValues();
           instructionIterator.remove();
         }
-        if (isSpillInstruction(instruction)) {
+        if (!instructionIterator.hasNext()) {
+          break;
+        }
+        Instruction nextInstruction = instructionIterator.peekNext();
+        if (isSpillInstruction(nextInstruction)) {
+          // No need to insert a DebugLocalsChange instruction before a spill instruction.
           continue;
         }
-        int index = instruction.getNumber();
+        int index = nextInstruction.getNumber();
         ListIterator<LocalRange> it = openRanges.listIterator(0);
-        Int2ReferenceMap<DebugLocalInfo> ending = new Int2ReferenceOpenHashMap<>();
-        Int2ReferenceMap<DebugLocalInfo> starting = new Int2ReferenceOpenHashMap<>();
         while (it.hasNext()) {
           LocalRange openRange = it.next();
-          // Any local change is inserted after the instruction so end is inclusive.
-          if (openRange.end <= index) {
+          // Close ranges up-to but excluding the first instruction.
+          if (!isLocalLiveAtInstruction(nextInstruction, openRange)) {
             it.remove();
             assert currentLocals.get(openRange.register) == openRange.local;
             currentLocals.remove(openRange.register);
-            localsChanged = true;
             ending.put(openRange.register, openRange.local);
           }
         }
-        while (nextStartingRange != null && nextStartingRange.start <= index) {
+        while (nextStartingRange != null && nextStartingRange.start < index) {
           // If the range is live at this index open it.
-          if (index < nextStartingRange.end) {
+          if (isLocalLiveAtInstruction(nextInstruction, nextStartingRange)) {
             openRanges.add(nextStartingRange);
             assert !currentLocals.containsKey(nextStartingRange.register);
             currentLocals.put(nextStartingRange.register, nextStartingRange.local);
             starting.put(nextStartingRange.register, nextStartingRange.local);
-            localsChanged = true;
           }
           nextStartingRange = rangeIterator.hasNext() ? rangeIterator.next() : null;
         }
-        if (localsChanged && instruction.getBlock().exit() != instruction) {
-          DebugLocalsChange change = createLocalsChange(ending, starting);
-          if (change != null) {
-            instructionIterator.add(change);
+        // Compute the final change in locals and insert it before nextInstruction.
+        boolean localsChanged = !ending.isEmpty() || !starting.isEmpty();
+        if (localsChanged) {
+          boolean skipChange =
+              nextInstruction == nextInstruction.getBlock().exit() && nextInstruction.isGoto();
+          if (!skipChange) {
+            DebugLocalsChange change = createLocalsChange(ending, starting);
+            if (change != null) {
+              // Insert the DebugLocalsChange instruction before nextInstruction.
+              instructionIterator.add(change);
+            }
           }
+          // Create new maps for the next DebugLocalsChange instruction.
+          ending = new Int2ReferenceOpenHashMap<>();
+          starting = new Int2ReferenceOpenHashMap<>();
         }
-        localsChanged = false;
       }
     }
   }
@@ -391,11 +402,6 @@
     return isLocalLiveAtInstruction(instruction, range.start, range.end, range.value);
   }
 
-  public static boolean isLocalLiveAtInstruction(
-      Instruction instruction, LiveRange range, Value value) {
-    return isLocalLiveAtInstruction(instruction, range.start, range.end, value);
-  }
-
   private static boolean isLocalLiveAtInstruction(
       Instruction instruction, int start, int end, Value value) {
     int number = instruction.getNumber();
@@ -584,6 +590,11 @@
     return getRegisterForValue(value, instructionNumber);
   }
 
+  @Override
+  public InternalOptions getOptions() {
+    return options;
+  }
+
   private ImmutableList<BasicBlock> computeLivenessInformation() {
     ImmutableList<BasicBlock> blocks = code.numberInstructions();
     liveAtEntrySets = code.computeLiveAtEntrySets();
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index 79ba8db..8fcc6b0 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.regalloc;
 
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.InternalOptions;
 
 public interface RegisterAllocator {
   void allocateRegisters(boolean debug);
@@ -11,4 +12,5 @@
   int getRegisterForValue(Value value, int instructionNumber);
   boolean argumentValueUsesHighRegister(Value value, int instructionNumber);
   int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber);
+  InternalOptions getOptions();
 }
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 2dbc015..3dc151d 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
@@ -296,8 +296,18 @@
         scheduler.addMove(
             new RegisterMove(move.to.getRegister(), move.type, move.from.getValue().definition));
       } else if (move.to.getRegister() != move.from.getRegister()) {
-        scheduler.addMove(
-            new RegisterMove(move.to.getRegister(), move.from.getRegister(), move.type));
+        // In case the runtime might have a bound-check elimination bug we make sure to define all
+        // indexing constants with an actual const instruction rather than a move. This appears to
+        // avoid a bug where the index variable could end up being uninitialized.
+        if (code.options.canHaveBoundsCheckEliminationBug()
+            && move.from.getValue().isConstNumber()
+            && move.type == MoveType.SINGLE) {
+          scheduler.addMove(
+              new RegisterMove(move.to.getRegister(), move.type, move.from.getValue().definition));
+        } else {
+          scheduler.addMove(
+              new RegisterMove(move.to.getRegister(), move.from.getRegister(), move.type));
+        }
       }
     }
     scheduler.schedule();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index a8ff665..4099912 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -488,18 +488,16 @@
   // Some Lollipop versions of Art found in the wild perform invalid bounds
   // check elimination. There is a fast path of loops and a slow path.
   // The bailout to the slow path is performed too early and therefore
-  // the loop variable might not be defined in the slow path code leading
+  // the array-index variable might not be defined in the slow path code leading
   // to use of undefined registers as indices into arrays. The result
   // is ArrayIndexOutOfBounds exceptions.
   //
-  // In an attempt to help these Art VMs get the loop variable initialized
-  // early, we do not lower constants past array-length instructions when
-  // building for Lollipop or below.
+  // In an attempt to help these Art VMs, all single-width constants are initialized and not moved.
   //
   // There is no guarantee that this works, but it does make the problem
   // disappear on the one known instance of this problem.
   //
-  // See b/69364976.
+  // See b/69364976 and b/77996377.
   public boolean canHaveBoundsCheckEliminationBug() {
     return minApiLevel <= AndroidApiLevel.L.getLevel();
   }
@@ -558,4 +556,26 @@
   public boolean canHaveCmpLongBug() {
     return minApiLevel < AndroidApiLevel.L.getLevel();
   }
+
+  // Some Lollipop VMs incorrectly optimize code with mul2addr instructions. In particular,
+  // the following hash code method produces wrong results after optimizations:
+  //
+  //    0:   0x00: IgetObject          v0, v3, Field java.lang.Class MultiClassKey.first
+  //    1:   0x02: InvokeVirtual       { v0 } Ljava/lang/Object;->hashCode()I
+  //    2:   0x05: MoveResult          v0
+  //    3:   0x06: Const16             v1, 0x001f (31)
+  //    4:   0x08: MulInt2Addr         v1, v0
+  //    5:   0x09: IgetObject          v2, v3, Field java.lang.Class MultiClassKey.second
+  //    6:   0x0b: InvokeVirtual       { v2 } Ljava/lang/Object;->hashCode()I
+  //    7:   0x0e: MoveResult          v2
+  //    8:   0x0f: AddInt2Addr         v1, v2
+  //    9:   0x10: Return              v1
+  //
+  // It seems that the issue is the MulInt2Addr instructions. Avoiding that, the VM computes
+  // hash codes correctly also after optimizations.
+  //
+  // This issue has only been observed on a Verizon Ellipsis 8 tablet. See b/76115465.
+  public boolean canHaveMul2AddrBug() {
+    return minApiLevel < AndroidApiLevel.M.getLevel();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
index 45b99e3..d51d5c3 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/R8InliningTest.java
@@ -27,6 +27,7 @@
 import com.android.tools.r8.code.Throw;
 import com.android.tools.r8.graph.DexCode;
 import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.DexInspector;
 import com.android.tools.r8.utils.DexInspector.ClassSubject;
 import com.android.tools.r8.utils.DexInspector.MethodSubject;
@@ -96,6 +97,7 @@
     R8Command command =
         R8Command.builder()
             .addProgramFiles(getInputFile())
+            .setMinApiLevel(AndroidApiLevel.M.getLevel())
             .setOutput(out, OutputMode.DexIndexed)
             .addProguardConfigurationFiles(Paths.get(keepRulesFile))
             .build();
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 2ea44eb..55195a4 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.ValueType;
+import com.android.tools.r8.utils.InternalOptions;
 import org.junit.Test;
 
 public class IdenticalAfterRegisterAllocationTest {
@@ -41,6 +42,11 @@
     public int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber) {
       return value.getNumber();
     }
+
+    @Override
+    public InternalOptions getOptions() {
+      return new InternalOptions();
+    }
   }
 
   @Test