Merge "Add @NeverMerge annotation for testing"
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
index d258839..fa43dea 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstMethodType.java
@@ -80,7 +80,7 @@
   }
 
   @Override
-  public boolean isConstString() {
+  public boolean isConstMethodType() {
     return true;
   }
 
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
index 1f2e418..640d24c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Dup.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Dup.java
@@ -27,7 +27,7 @@
 
   @Override
   public void buildCf(CfBuilder builder) {
-    if (this.inValues.get(0).type == ValueType.LONG_OR_DOUBLE) {
+    if (this.inValues.get(0).type.isWide()) {
       builder.add(new CfStackInstruction(Opcode.Dup2));
     } else {
       builder.add(new CfStackInstruction(Opcode.Dup));
@@ -36,12 +36,12 @@
 
   @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
-    return false;
+    throw new Unreachable();
   }
 
   @Override
   public int compareNonValueParts(Instruction other) {
-    return 0;
+    throw new Unreachable();
   }
 
   @Override
@@ -61,10 +61,22 @@
   }
 
   @Override
-  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {}
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    // Intentionally empty. Dup is a stack operation.
+  }
 
   @Override
   public boolean hasInvariantOutType() {
     return false;
   }
+
+  @Override
+  public boolean isDup() {
+    return true;
+  }
+
+  @Override
+  public Dup asDup() {
+    return this;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index 44a7a8e..90e8a6a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -621,6 +621,14 @@
     return null;
   }
 
+  public boolean isDup() {
+    return false;
+  }
+
+  public Dup asDup() {
+    return null;
+  }
+
   public boolean isJumpInstruction() {
     return false;
   }
@@ -1052,6 +1060,14 @@
     return null;
   }
 
+  public boolean isSwap() {
+    return false;
+  }
+
+  public Swap asSwap() {
+    return null;
+  }
+
   public boolean isLoad() {
     return false;
   }
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 d9dabef..fdff15e 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,10 @@
     return objectType;
   }
 
+  public StackValue duplicate(int height) {
+    return new StackValue(this.objectType, this.type.toTypeLattice(), height);
+  }
+
   @Override
   public boolean needsRegister() {
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Swap.java b/src/main/java/com/android/tools/r8/ir/code/Swap.java
new file mode 100644
index 0000000..494fbab
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/Swap.java
@@ -0,0 +1,91 @@
+// 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;
+import java.util.Arrays;
+
+public class Swap extends Instruction {
+
+  public Swap(StackValues dest, StackValues src) {
+    super(dest, src.getStackValues());
+    assert src.getStackValues().size() == 2;
+    assert !(this.inValues.get(0).type.isWide() ^ this.inValues.get(1).type.isWide());
+  }
+
+  public Swap(StackValues dest, StackValue src1, StackValue src2) {
+    super(dest, Arrays.asList(src1, src2));
+    assert !(this.inValues.get(0).type.isWide() ^ !this.inValues.get(1).type.isWide());
+  }
+
+  @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.isWide()) {
+      builder.add(new CfStackInstruction(Opcode.Dup2X2));
+      builder.add(new CfStackInstruction(Opcode.Pop2));
+    } else {
+      builder.add(new CfStackInstruction(Opcode.Swap));
+    }
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int compareNonValueParts(Instruction other) {
+    throw new Unreachable();
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    return 0;
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    throw new Unreachable();
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, DexType invocationContext) {
+    return inliningConstraints.forSwap();
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    // Intentionally empty. Swap is a stack operation.
+  }
+
+  @Override
+  public boolean hasInvariantOutType() {
+    return false;
+  }
+
+  @Override
+  public boolean isSwap() {
+    return true;
+  }
+
+  @Override
+  public Swap asSwap() {
+    return this;
+  }
+}
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 7130516..cc85e39 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
@@ -237,6 +237,10 @@
     return ConstraintWithTarget.ALWAYS;
   }
 
+  public ConstraintWithTarget forSwap() {
+    return ConstraintWithTarget.ALWAYS;
+  }
+
   public ConstraintWithTarget forThrow() {
     return ConstraintWithTarget.ALWAYS;
   }
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index 21a5e0e..4897701 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -5,19 +5,19 @@
 
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
-public class D8TestBuilder extends TestCompilerBuilder<D8Command, Builder, D8TestBuilder> {
+public class D8TestBuilder
+    extends TestCompilerBuilder<D8Command, Builder, D8TestCompileResult, D8TestBuilder> {
 
-  private final D8Command.Builder builder;
-
-  private D8TestBuilder(TestState state, D8Command.Builder builder) {
+  private D8TestBuilder(TestState state, Builder builder) {
     super(state, builder, Backend.DEX);
-    this.builder = builder;
   }
 
   public static D8TestBuilder create(TestState state) {
@@ -30,9 +30,11 @@
   }
 
   @Override
-  void internalCompile(Builder builder, Consumer<InternalOptions> optionsConsumer)
+  D8TestCompileResult internalCompile(
+      Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
     ToolHelper.runD8(builder, optionsConsumer);
+    return new D8TestCompileResult(getState(), app.get());
   }
 
   public D8TestBuilder addClasspathClasses(Class<?>... classes) {
diff --git a/src/test/java/com/android/tools/r8/D8TestCompileResult.java b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
new file mode 100644
index 0000000..482a611
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/D8TestCompileResult.java
@@ -0,0 +1,18 @@
+// 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;
+
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.utils.AndroidApp;
+
+public class D8TestCompileResult extends TestCompileResult {
+  D8TestCompileResult(TestState state, AndroidApp app) {
+    super(state, app);
+  }
+
+  @Override
+  public Backend getBackend() {
+    return Backend.DEX;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 74b40b0..3b01a71 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -6,6 +6,7 @@
 import com.android.tools.r8.R8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringUtils;
 import java.util.ArrayList;
@@ -13,14 +14,13 @@
 import java.util.Collection;
 import java.util.List;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
-public class R8TestBuilder extends TestCompilerBuilder<R8Command, Builder, R8TestBuilder> {
-
-  private final R8Command.Builder builder;
+public class R8TestBuilder
+    extends TestCompilerBuilder<R8Command, Builder, R8TestCompileResult, R8TestBuilder> {
 
   private R8TestBuilder(TestState state, Builder builder, Backend backend) {
     super(state, builder, backend);
-    this.builder = builder;
   }
 
   public static R8TestBuilder create(TestState state, Backend backend) {
@@ -35,12 +35,16 @@
   }
 
   @Override
-  void internalCompile(Builder builder, Consumer<InternalOptions> optionsConsumer)
+  R8TestCompileResult internalCompile(
+      Builder builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException {
     if (enableInliningAnnotations) {
       ToolHelper.allowTestProguardOptions(builder);
     }
+    StringBuilder proguardMapBuilder = new StringBuilder();
+    builder.setProguardMapConsumer((string, ignore) -> proguardMapBuilder.append(string));
     ToolHelper.runR8WithoutResult(builder.build(), optionsConsumer);
+    return new R8TestCompileResult(getState(), backend, app.get(), proguardMapBuilder.toString());
   }
 
   public R8TestBuilder addDataResources(List<DataEntryResource> resources) {
diff --git a/src/test/java/com/android/tools/r8/R8TestCompileResult.java b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
new file mode 100644
index 0000000..45fb31c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/R8TestCompileResult.java
@@ -0,0 +1,32 @@
+// 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;
+
+import com.android.tools.r8.TestBase.Backend;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.io.IOException;
+import java.util.concurrent.ExecutionException;
+
+public class R8TestCompileResult extends TestCompileResult {
+
+  private final Backend backend;
+  private final String proguardMap;
+
+  R8TestCompileResult(TestState state, Backend backend, AndroidApp app, String proguardMap) {
+    super(state, app);
+    this.backend = backend;
+    this.proguardMap = proguardMap;
+  }
+
+  @Override
+  public Backend getBackend() {
+    return backend;
+  }
+
+  @Override
+  public CodeInspector inspector() throws IOException, ExecutionException {
+    return new CodeInspector(app, proguardMap);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/TestCompileResult.java b/src/test/java/com/android/tools/r8/TestCompileResult.java
index 4e7cf37..7505a83 100644
--- a/src/test/java/com/android/tools/r8/TestCompileResult.java
+++ b/src/test/java/com/android/tools/r8/TestCompileResult.java
@@ -12,19 +12,23 @@
 import java.nio.file.Path;
 import java.util.concurrent.ExecutionException;
 
-public class TestCompileResult {
-  private final TestState state;
-  private final Backend backend;
-  private final AndroidApp app;
+public abstract class TestCompileResult {
+  final TestState state;
+  final AndroidApp app;
 
-  public TestCompileResult(TestState state, Backend backend, AndroidApp app) {
+  TestCompileResult(TestState state, AndroidApp app) {
     this.state = state;
-    this.backend = backend;
     this.app = app;
   }
 
+  public abstract Backend getBackend();
+
+  public TestRunResult run(Class<?> mainClass) throws IOException {
+    return run(mainClass.getTypeName());
+  }
+
   public TestRunResult run(String mainClass) throws IOException {
-    switch (backend) {
+    switch (getBackend()) {
       case DEX:
         return runArt(mainClass);
       case CF:
@@ -34,6 +38,12 @@
     }
   }
 
+  public TestCompileResult writeToZip(Path file) throws IOException {
+    app.writeToZip(
+        file, getBackend() == Backend.DEX ? OutputMode.DexIndexed : OutputMode.ClassFile);
+    return this;
+  }
+
   public CodeInspector inspector() throws IOException, ExecutionException {
     return new CodeInspector(app);
   }
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index db25352..4706125 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -5,17 +5,21 @@
 
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.AndroidAppConsumers;
 import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.base.Suppliers;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.function.Consumer;
+import java.util.function.Supplier;
 
 public abstract class TestCompilerBuilder<
         C extends BaseCompilerCommand,
         B extends BaseCompilerCommand.Builder<C, B>,
-        T extends TestCompilerBuilder<C, B, T>>
+        R extends TestCompileResult,
+        T extends TestCompilerBuilder<C, B, R, T>>
     extends TestBuilder<T> {
 
   public static final Consumer<InternalOptions> DEFAULT_OPTIONS =
@@ -24,8 +28,8 @@
         public void accept(InternalOptions options) {}
       };
 
-  private final B builder;
-  private final Backend backend;
+  final B builder;
+  final Backend backend;
 
   // Default initialized setup. Can be overwritten if needed.
   private Path defaultLibrary;
@@ -43,7 +47,8 @@
 
   abstract T self();
 
-  abstract void internalCompile(B builder, Consumer<InternalOptions> optionsConsumer)
+  abstract R internalCompile(
+      B builder, Consumer<InternalOptions> optionsConsumer, Supplier<AndroidApp> app)
       throws CompilationFailedException;
 
   public T addOptionsModification(Consumer<InternalOptions> optionsConsumer) {
@@ -51,7 +56,7 @@
     return self();
   }
 
-  public TestCompileResult compile() throws CompilationFailedException {
+  public R compile() throws CompilationFailedException {
     AndroidAppConsumers sink = new AndroidAppConsumers();
     builder.setProgramConsumer(sink.wrapProgramConsumer(programConsumer));
     if (defaultLibrary != null) {
@@ -60,8 +65,7 @@
     if (backend == Backend.DEX && defaultMinApiLevel != null) {
       builder.setMinApiLevel(defaultMinApiLevel.getLevel());
     }
-    internalCompile(builder, optionsConsumer);
-    return new TestCompileResult(getState(), backend, sink.build());
+    return internalCompile(builder, optionsConsumer, Suppliers.memoize(sink::build));
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
index 7202914..d2d434a 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -36,8 +36,8 @@
 import org.junit.rules.TemporaryFolder;
 
 /**
- * This test relies on a freshly built from builds/libs/r8lib_with_deps.jar. If this test fails
- * rebuild r8lib_with_deps by calling test.py or gradle r8libWithdeps.
+ * This test relies on a freshly built builds/libs/r8lib_with_deps.jar. If this test fails
+ * remove build directory and rebuild r8lib_with_deps by calling test.py or gradle r8libWithdeps.
  */
 public class BootstrapCurrentEqualityTest extends TestBase {
 
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 28404f0..660156a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.signature.GenericSignatureAction;
 import com.android.tools.r8.naming.signature.GenericSignatureParser;
+import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
@@ -111,10 +112,18 @@
     return internalOptions;
   }
 
-  public CodeInspector(AndroidApp app, Path proguardMap) throws IOException, ExecutionException {
+  public CodeInspector(AndroidApp app, Path proguardMapFile)
+      throws IOException, ExecutionException {
     this(
         new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
-            .read(StringResource.fromFile(proguardMap)));
+            .read(StringResource.fromFile(proguardMapFile)));
+  }
+
+  public CodeInspector(AndroidApp app, String proguardMapContent)
+      throws IOException, ExecutionException {
+    this(
+        new ApplicationReader(app, runOptionsConsumer(null), new Timing("CodeInspector"))
+            .read(StringResource.fromString(proguardMapContent, Origin.unknown())));
   }
 
   public CodeInspector(DexApplication application) {
diff --git a/tools/test.py b/tools/test.py
index cd9b70f..f1543f1 100755
--- a/tools/test.py
+++ b/tools/test.py
@@ -103,6 +103,9 @@
   if 'BUILDBOT_BUILDERNAME' in os.environ:
     gradle.RunGradle(['clean'])
 
+  # Build R8lib with dependencies for bootstrapping tests before adding test sources
+  gradle.RunGradle(['r8libwithdeps'])
+
   gradle_args = ['--stacktrace']
   # Set all necessary Gradle properties and options first.
   if options.verbose:
@@ -156,8 +159,6 @@
     gradle_args.append('-PHEAD_sha1=' + utils.get_HEAD_sha1())
   # Add Gradle tasks
   gradle_args.append('cleanTest')
-  # Build R8lib with dependencies for bootstrapping tests.
-  gradle_args.append('r8libWithDeps')
   gradle_args.append('test')
   # Test filtering. Must always follow the 'test' task.
   for testFilter in args: