Add Dup as an IR code instruction

Change-Id: I66f76267fff30242caae8229d79a25197586a41b
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 22bbf46..dbfcbf2 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.Phi;
 import com.android.tools.r8.ir.code.StackValue;
+import com.android.tools.r8.ir.code.StackValues;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.regalloc.LinearScanRegisterAllocator;
 import com.android.tools.r8.ir.regalloc.LiveIntervals;
@@ -116,7 +117,9 @@
       Instruction next = it.next();
       Value outValue = next.outValue();
       if (outValue != null) {
-        outValue.setNeedsRegister(!(outValue instanceof StackValue));
+        boolean isStackValue =
+            (outValue instanceof StackValue) || (outValue instanceof StackValues);
+        outValue.setNeedsRegister(!isStackValue);
       }
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Dup.java b/src/main/java/com/android/tools/r8/ir/code/Dup.java
new file mode 100644
index 0000000..1f2e418
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -0,0 +1,70 @@
+// 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.cf.LoadStoreHelper;
+import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+
+public class Dup extends Instruction {
+
+  public Dup(StackValues dest, StackValue src) {
+    super(dest, src);
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    throw new Unreachable("This classfile-specific IR should not be inserted in the Dex backend.");
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    if (this.inValues.get(0).type == ValueType.LONG_OR_DOUBLE) {
+      builder.add(new CfStackInstruction(Opcode.Dup2));
+    } else {
+      builder.add(new CfStackInstruction(Opcode.Dup));
+    }
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    return false;
+  }
+
+  @Override
+  public int compareNonValueParts(Instruction other) {
+    return 0;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    return 0;
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forDup();
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {}
+
+  @Override
+  public boolean hasInvariantOutType() {
+    return false;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValues.java b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
new file mode 100644
index 0000000..475572e
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValues.java
@@ -0,0 +1,50 @@
+// 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.ir.analysis.type.TypeLatticeElement;
+import java.util.List;
+
+/**
+ * {@link StackValues} allow us to represent stack operations that produces two or more elements on
+ * the stack while using the same logic for instructions.
+ */
+public class StackValues extends Value {
+
+  private final int height;
+  private final List<StackValue> stackValues;
+
+  public StackValues(TypeLatticeElement typeLattice, int height, List<StackValue> stackValues) {
+    super(Value.UNDEFINED_NUMBER, typeLattice, null);
+    this.height = height;
+    this.stackValues = stackValues;
+    assert height >= 0;
+    assert stackValues.size() >= 2;
+  }
+
+  public int getHeight() {
+    return height;
+  }
+
+  public List<StackValue> getStackValues() {
+    return stackValues;
+  }
+
+  @Override
+  public boolean needsRegister() {
+    return false;
+  }
+
+  @Override
+  public void setNeedsRegister(boolean value) {
+    assert !value;
+  }
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder();
+    return String.format("s%d+%d", height, stackValues.size() - 1);
+  }
+}
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 8816326..ea6cac0 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
@@ -36,6 +36,7 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.ir.code.StackValue;
+import com.android.tools.r8.ir.code.StackValues;
 import com.android.tools.r8.ir.code.Store;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -363,6 +364,11 @@
         if (outValue instanceof StackValue) {
           stack.push(outValue);
         }
+        if (outValue instanceof StackValues) {
+          for (StackValue outVal : ((StackValues) outValue).getStackValues()) {
+            stack.push(outVal);
+          }
+        }
       }
       if (instruction.isDebugLocalsChange()) {
         if (instruction.asDebugLocalsChange().apply(pendingLocals)) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
index de657de..7130516 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningConstraints.java
@@ -94,6 +94,10 @@
     return ConstraintWithTarget.ALWAYS;
   }
 
+  public ConstraintWithTarget forDup() {
+    return ConstraintWithTarget.ALWAYS;
+  }
+
   public ConstraintWithTarget forInstanceGet(DexField field, DexType invocationContext) {
     DexField lookup = graphLense.lookupField(field);
     return forFieldInstruction(
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 9345bb9..d98496e 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
@@ -26,6 +26,8 @@
 import com.android.tools.r8.ir.code.NumericType;
 import com.android.tools.r8.ir.code.Or;
 import com.android.tools.r8.ir.code.Phi;
+import com.android.tools.r8.ir.code.StackValue;
+import com.android.tools.r8.ir.code.StackValues;
 import com.android.tools.r8.ir.code.Sub;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.Xor;
@@ -2520,7 +2522,11 @@
           // 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()) {
+          if (definition instanceof StackValues) {
+            for (StackValue value : ((StackValues) definition).getStackValues()) {
+              live.remove(value);
+            }
+          } else if (!definition.isUsed()) {
             addLiveRange(
                 definition,
                 block,