Version 1.4.78

Cherry pick: Compare DEX instructions as last step in identicalAfterRegisterAllocation

CL: https://r8-review.googlesource.com/c/r8/+/36121
Bug: 127524985
Change-Id: Ib2043cb11f69eab28f3cd1c5f28f404ac68d2a4b
diff --git a/.gitignore b/.gitignore
index 22b29da..0a8d8b8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,6 +41,8 @@
 third_party/dart-sdk.tar.gz
 third_party/desugar/desugar_*/
 third_party/desugar/desugar_*.tar.gz
+third_party/internal/*
+!third_party/internal/*.sha1
 third_party/gmail/*
 !third_party/gmail/*.sha1
 third_party/gmscore/*
diff --git a/build.gradle b/build.gradle
index 308914a..8ff0521 100644
--- a/build.gradle
+++ b/build.gradle
@@ -335,6 +335,7 @@
         "benchmarks/kotlin-benches",
         "chrome",
         "desugar/desugar_20180308",
+        "internal/issue-127524985",
         "framework",
         "gmail/gmail_android_170604.16",
         "gmail/gmail_android_180826.15",
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 2fe03c0..11d013c 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.4.77";
+  public static final String LABEL = "1.4.78";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index 0d05021..8cab73b 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -358,7 +358,7 @@
 
   public void setCode(IRCode ir, RegisterAllocator registerAllocator, InternalOptions options) {
     checkIfObsolete();
-    final DexBuilder builder = new DexBuilder(ir, registerAllocator, options);
+    final DexBuilder builder = new DexBuilder(ir, registerAllocator);
     setCode(builder.build());
   }
 
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 6d21c5a..e30e9bb 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
@@ -473,6 +473,11 @@
         return false;
       }
     }
+    // Finally check that the dex instructions for the generated code actually are the same.
+    if (allocator.getOptions().isGeneratingDex()
+        && !DexBuilder.identicalInstructionsAfterBuildingDexCode(this, other, allocator)) {
+      return false;
+    }
     return true;
   }
 
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 4b2fabb..d989d07 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
@@ -71,6 +71,7 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.HashSet;
 import java.util.List;
 import java.util.ListIterator;
@@ -121,15 +122,40 @@
 
   BasicBlock nextBlock;
 
-  public DexBuilder(
-      IRCode ir,
-      RegisterAllocator registerAllocator,
-      InternalOptions options) {
+  public DexBuilder(IRCode ir, RegisterAllocator registerAllocator) {
+    this(ir, registerAllocator, ir.options);
     assert ir != null;
     assert registerAllocator != null;
+  }
+
+  private DexBuilder(IRCode ir, RegisterAllocator registerAllocator, InternalOptions options) {
     this.ir = ir;
     this.registerAllocator = registerAllocator;
     this.options = options;
+    if (isBuildingForComparison()) {
+      instructionToInfo = new Info[1];
+    }
+  }
+
+  public static boolean identicalInstructionsAfterBuildingDexCode(
+      com.android.tools.r8.ir.code.Instruction a,
+      com.android.tools.r8.ir.code.Instruction b,
+      RegisterAllocator allocator) {
+    DexBuilder builder = new DexBuilder(null, allocator, allocator.getOptions());
+    Info infoA = buildInfoForComparison(a, builder);
+    Info infoB = buildInfoForComparison(b, builder);
+    return infoA.identicalInstructions(infoB, builder);
+  }
+
+  private static Info buildInfoForComparison(
+      com.android.tools.r8.ir.code.Instruction instruction, DexBuilder builder) {
+    instruction.buildDex(builder);
+    assert builder.instructionToInfo.length == 1;
+    return builder.instructionToInfo[0];
+  }
+
+  private boolean isBuildingForComparison() {
+    return ir == null;
   }
 
   private void reset() {
@@ -565,7 +591,8 @@
 
   public void add(com.android.tools.r8.ir.code.Instruction instr, Instruction dex) {
     assert !instr.isGoto();
-    assert !instr.isDexItemBasedConstString()
+    assert isBuildingForComparison()
+        || !instr.isDexItemBasedConstString()
         || ir.method.getOptimizationInfo().useIdentifierNameString();
     add(instr, new FixedSizeInfo(instr, dex));
   }
@@ -601,6 +628,11 @@
   }
 
   private void add(com.android.tools.r8.ir.code.Instruction ir, Info info) {
+    if (isBuildingForComparison()) {
+      // We are building for instruction comparison, so just set the info.
+      setSingleInfo(info);
+      return;
+    }
     assert ir != null;
     assert info != null;
     assert getInfo(ir) == null;
@@ -627,6 +659,11 @@
     instructionToInfo[instructionNumberToIndex(instruction.getNumber())] = info;
   }
 
+  private void setSingleInfo(Info info) {
+    assert instructionToInfo.length == 1;
+    instructionToInfo[0] = info;
+  }
+
   private Info getTargetInfo(BasicBlock block) {
     InstructionIterator iterator = block.iterator();
     com.android.tools.r8.ir.code.Instruction instruction = null;
@@ -891,6 +928,8 @@
     public com.android.tools.r8.ir.code.Instruction getIR() {
       return ir;
     }
+
+    public abstract boolean identicalInstructions(Info other, DexBuilder builder);
   }
 
   private static class FixedSizeInfo extends Info {
@@ -927,6 +966,12 @@
     public void addInstructions(DexBuilder builder, List<Instruction> instructions) {
       instructions.add(instruction);
     }
+
+    @Override
+    public boolean identicalInstructions(Info other, DexBuilder builder) {
+      return other instanceof FixedSizeInfo
+          && instruction.equals(((FixedSizeInfo) other).instruction);
+    }
   }
 
   private static class MultiFixedSizeInfo extends Info {
@@ -974,6 +1019,12 @@
     public int getSize() {
       return size;
     }
+
+    @Override
+    public boolean identicalInstructions(Info other, DexBuilder builder) {
+      return other instanceof MultiFixedSizeInfo
+          && Arrays.equals(instructions, ((MultiFixedSizeInfo) other).instructions);
+    }
   }
 
   private static class FallThroughInfo extends Info {
@@ -1005,6 +1056,11 @@
     public int maxSize() {
       return 0;
     }
+
+    @Override
+    public boolean identicalInstructions(Info other, DexBuilder builder) {
+      return other instanceof FallThroughInfo;
+    }
   }
 
   private static class GotoInfo extends Info {
@@ -1131,6 +1187,11 @@
         instructions.add(dex);
       }
     }
+
+    @Override
+    public boolean identicalInstructions(Info other, DexBuilder builder) {
+      return other instanceof GotoInfo;
+    }
   }
 
   public static class IfInfo extends Info {
@@ -1141,6 +1202,18 @@
       super(branch);
     }
 
+    private int getRegister(int operandIndex, DexBuilder builder) {
+      If branch = getBranch();
+      return builder.allocatedRegister(branch.inValues().get(operandIndex), branch.getNumber());
+    }
+
+    private int[] getRegisters(DexBuilder builder) {
+      if (getBranch().isZeroTest()) {
+        return new int[] {getRegister(0, builder)};
+      }
+      return new int[] {getRegister(0, builder), getRegister(1, builder)};
+    }
+
     private If getBranch() {
       return (If) getIR();
     }
@@ -1171,7 +1244,7 @@
       int source = builder.getInfo(branch).getOffset();
       int target = builder.getInfo(branch.getTrueTarget().entry()).getOffset();
       int relativeOffset = target - source;
-      int register1 = builder.allocatedRegister(branch.inValues().get(0), branch.getNumber());
+      int register1 = getRegister(0, builder);
 
       if (relativeOffset < 0) {
         builder.hasBackwardsBranch = true;
@@ -1207,7 +1280,7 @@
             break;
         }
       } else {
-        int register2 = builder.allocatedRegister(branch.inValues().get(1), branch.getNumber());
+        int register2 = getRegister(1, builder);
         switch (getBranch().getType()) {
           case EQ:
             instruction = new IfEq(register1, register2, relativeOffset);
@@ -1256,6 +1329,16 @@
     public int getSize() {
       return size;
     }
+
+    @Override
+    public boolean identicalInstructions(Info other, DexBuilder builder) {
+      if (!(other instanceof IfInfo)) {
+        return false;
+      }
+      IfInfo otherInfo = (IfInfo) other;
+      return getBranch().getType() == otherInfo.getBranch().getType()
+          && Arrays.equals(getRegisters(builder), otherInfo.getRegisters(builder));
+    }
   }
 
   public static class MoveInfo extends Info {
@@ -1279,6 +1362,16 @@
     }
 
     @Override
+    public boolean identicalInstructions(Info other, DexBuilder builder) {
+      if (!(other instanceof MoveInfo)) {
+        return false;
+      }
+      MoveInfo moveInfo = (MoveInfo) other;
+      return srcRegister(builder) == moveInfo.srcRegister(builder)
+          && destRegister(builder) == moveInfo.destRegister(builder);
+    }
+
+    @Override
     public int computeSize(DexBuilder builder) {
       int srcRegister = srcRegister(builder);
       int destRegister = destRegister(builder);
diff --git a/src/test/java/com/android/tools/r8/DXTestBuilder.java b/src/test/java/com/android/tools/r8/DXTestBuilder.java
index 190db23..ffe0bfb 100644
--- a/src/test/java/com/android/tools/r8/DXTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/DXTestBuilder.java
@@ -3,12 +3,13 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8;
 
+import com.android.tools.r8.ClassFileConsumer.ArchiveConsumer;
 import com.android.tools.r8.D8Command.Builder;
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.ToolHelper.ProcessResult;
-import com.android.tools.r8.errors.Unimplemented;
 import com.android.tools.r8.utils.AndroidApp;
 import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.ListUtils;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
@@ -63,12 +64,33 @@
 
   @Override
   public DXTestBuilder addProgramClasses(Collection<Class<?>> classes) {
-    throw new Unimplemented("No support for adding classes directly");
+    return addProgramClassFileData(
+        ListUtils.map(
+            classes,
+            c -> {
+              try {
+                return ToolHelper.getClassAsBytes(c);
+              } catch (IOException e) {
+                throw new RuntimeException(e);
+              }
+            }));
   }
 
   @Override
   public DXTestBuilder addProgramClassFileData(Collection<byte[]> classes) {
-    throw new Unimplemented("No support for adding classfile data directly");
+    try {
+      Path out = getState().getNewTempFolder().resolve("out.jar");
+      ArchiveConsumer consumer = new ArchiveConsumer(out);
+      for (byte[] clazz : classes) {
+        String descriptor = TestBase.extractClassDescriptor(clazz);
+        consumer.accept(ByteDataView.of(clazz), descriptor, null);
+      }
+      consumer.finished(null);
+      addProgramFiles(out);
+      return self();
+    } catch (IOException e) {
+      throw new RuntimeException(e);
+    }
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 2b66df4..af03f51 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -708,7 +708,7 @@
    * Run application on Art with the specified main class.
    */
   protected ProcessResult runOnArtRaw(AndroidApp app, Class mainClass) throws IOException {
-    return runOnArtRaw(app, mainClass.getCanonicalName());
+    return runOnArtRaw(app, mainClass.getTypeName());
   }
 
   /**
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 217463e..62b2b65 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -114,6 +114,10 @@
     return self();
   }
 
+  public T addKeepAllAttributes() {
+    return addKeepRules("-keepattributes *");
+  }
+
   private static String getMethodLine(MethodReference method) {
     // Should we encode modifiers in method references?
     StringBuilder builder = new StringBuilder();
diff --git a/src/test/java/com/android/tools/r8/internal/Regression127524985.java b/src/test/java/com/android/tools/r8/internal/Regression127524985.java
new file mode 100644
index 0000000..f91a08b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/Regression127524985.java
@@ -0,0 +1,57 @@
+// Copyright (c) 2019, 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.internal;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class Regression127524985 extends TestBase {
+
+  private static final String MAIN = "com.android.tools.r8.internal.Regression127524985$Main";
+
+  private static final Path JAR =
+      Paths.get("third_party/internal/issue-127524985/issue-127524985.jar");
+
+  private static final String EXPECTED = StringUtils.lines("true");
+
+  @Parameters(name = "{0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  private final Backend backend;
+
+  public Regression127524985(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Throwable {
+    if (backend == Backend.CF) {
+      testForJvm()
+          .addClasspath(JAR)
+          .run(MAIN)
+          .assertSuccessWithOutput(EXPECTED);
+    }
+    (backend == Backend.DEX
+            ? testForD8()
+            : testForR8(backend)
+                .debug()
+                .noTreeShaking()
+                .noMinification()
+                .addKeepAllAttributes()
+                .addKeepRules("-dontwarn *"))
+        .addProgramFiles(JAR)
+        .compile()
+        .run(MAIN)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+}
diff --git a/third_party/internal/issue-127524985.tar.gz.sha1 b/third_party/internal/issue-127524985.tar.gz.sha1
new file mode 100644
index 0000000..a1b57e3
--- /dev/null
+++ b/third_party/internal/issue-127524985.tar.gz.sha1
@@ -0,0 +1 @@
+c1d03d36435cc08d1ee218c94e8dbfc74df975f1
\ No newline at end of file