Version 1.2.46.

Merge: Workaround mediatek constructor inlining bug.
CL: https://r8-review.googlesource.com/26260

R=sgjesse@google.com

Bug: 68378480
Change-Id: I4c95310656da2262f48a9b356ac138551d84fa78
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 0b00160..cc8472a 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.2.45";
+  public static final String LABEL = "1.2.46";
 
   private Version() {
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 1252d82..cc0a5f9 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -20,6 +20,7 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DexTypeList;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.analysis.constant.SparseConditionalConstantPropagation;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
@@ -836,7 +837,8 @@
     // Always perform dead code elimination before register allocation. The register allocator
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
     DeadCodeRemover.removeDeadCode(code, codeRewriter, graphLense, options);
-    materializeInstructionBeforeLongOperationsWorkaround(code, options);
+    materializeInstructionBeforeLongOperationsWorkaround(code);
+    workaroundForwardingInitializerBug(code);
     LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(code, options);
     registerAllocator.allocateRegisters(options.debug);
     printMethod(code, "After register allocation (non-SSA)");
@@ -852,8 +854,42 @@
     return registerAllocator;
   }
 
-  private static void materializeInstructionBeforeLongOperationsWorkaround(
-      IRCode code, InternalOptions options) {
+  private void workaroundForwardingInitializerBug(IRCode code) {
+    if (!options.canHaveForwardingInitInliningBug()) {
+      return;
+    }
+    // Only constructors.
+    if (!code.method.isInstanceInitializer()) {
+      return;
+    }
+    // Only constructors with certain signatures.
+    DexTypeList paramTypes = code.method.method.proto.parameters;
+    if (paramTypes.size() != 3 ||
+        paramTypes.values[0] != options.itemFactory.doubleType ||
+        paramTypes.values[1] != options.itemFactory.doubleType ||
+        !paramTypes.values[2].isClassType()) {
+      return;
+    }
+    // Only if the constructor contains a super constructor call taking only parameters as
+    // inputs.
+    for (BasicBlock block : code.blocks) {
+      InstructionListIterator it = block.listIterator();
+      Instruction superConstructorCall = it.nextUntil((i) ->
+          i.isInvokeDirect() &&
+          i.asInvokeDirect().getInvokedMethod().name == options.itemFactory.constructorMethodName &&
+          i.asInvokeDirect().arguments().size() == 4 &&
+          i.asInvokeDirect().arguments().stream().allMatch(Value::isArgument));
+      if (superConstructorCall != null) {
+        // We force a materializing const instruction in front of the super call to make
+        // sure that there is at least one temporary register in the method. That disables
+        // the inlining that is crashing on these devices.
+        ensureInstructionBefore(code, superConstructorCall, it);
+        break;
+      }
+    }
+  }
+
+  private void materializeInstructionBeforeLongOperationsWorkaround(IRCode code) {
     if (!options.canHaveDex2OatLinkedListBug()) {
       return;
     }
@@ -862,25 +898,25 @@
       Instruction firstMaterializing =
           it.nextUntil(IRConverter::isMaterializingInstructionOnArtArmVersionM);
       if (needsInstructionBeforeLongOperation(firstMaterializing)) {
-        ensureInstructionBeforeLongOperation(code, block, firstMaterializing, it);
+        ensureInstructionBefore(code, firstMaterializing, it);
       }
     }
   }
 
-  private static void ensureInstructionBeforeLongOperation(
-      IRCode code, BasicBlock block, Instruction firstMaterializing, InstructionListIterator it) {
+  private static void ensureInstructionBefore(
+      IRCode code, Instruction addBefore, InstructionListIterator it) {
     // Force materialize a constant-zero before the long operation.
     Instruction check = it.previous();
-    assert firstMaterializing == check;
-    Value fixitValue = code.createValue(ValueType.INT);
+    assert addBefore == check;
     // Forced definition of const-zero
+    Value fixitValue = code.createValue(ValueType.INT);
     Instruction fixitDefinition = new AlwaysMaterializingDefinition(fixitValue);
-    fixitDefinition.setBlock(block);
-    fixitDefinition.setPosition(firstMaterializing.getPosition());
+    fixitDefinition.setBlock(addBefore.getBlock());
+    fixitDefinition.setPosition(addBefore.getPosition());
     it.add(fixitDefinition);
     // Forced user of the forced definition to ensure it has a user and thus live range.
     Instruction fixitUser = new AlwaysMaterializingUser(fixitValue);
-    fixitUser.setBlock(block);
+    fixitUser.setBlock(addBefore.getBlock());
     it.add(fixitUser);
   }
 
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 1818f88..470f64a 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -645,4 +645,17 @@
   public boolean canHaveNumberConversionRegisterAllocationBug() {
     return minApiLevel < AndroidApiLevel.L.getLevel();
   }
+
+  // Some Lollipop mediatek VMs have a peculiar bug where the inliner crashes if there is a
+  // simple constructor that just forwards its arguments to the super constructor. Strangely,
+  // this happens only for specific signatures: so far the only reproduction we have is for
+  // a constructor accepting two doubles and one object.
+  //
+  // To workaround this we insert a materializing const instruction before the super init
+  // call. Having a temporary register seems to disable the buggy optimizations.
+  //
+  // See b/68378480.
+  public boolean canHaveForwardingInitInliningBug() {
+    return minApiLevel < AndroidApiLevel.M.getLevel();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/regress/b68378480/B68378480.java b/src/test/java/com/android/tools/r8/regress/b68378480/B68378480.java
new file mode 100644
index 0000000..044ddff
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b68378480/B68378480.java
@@ -0,0 +1,86 @@
+// 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.regress.b68378480;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.D8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.ir.code.SingleConstant;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import org.junit.Test;
+
+
+class SuperClass {
+  SuperClass(double a, double b, Object c) {
+  }
+}
+
+class SubClass extends SuperClass {
+  SubClass(double a, double b, Object c) {
+    super(a, b, c);
+  }
+}
+
+class Main {
+  public static void main(String[] args) {
+    new SubClass(1.1, 2.2, null);
+  }
+}
+
+public class B68378480 {
+
+  private DexCode compileClassesGetSubClassInit(int minApi)
+      throws IOException, CompilationFailedException, ExecutionException {
+    D8Command.Builder builder = D8Command.builder()
+        .setMode(CompilationMode.RELEASE)
+        .setMinApiLevel(minApi);
+    List<Class> classes = ImmutableList.of(SuperClass.class, SubClass.class, Main.class);
+    for (Class c : classes) {
+      builder.addClassProgramData(ToolHelper.getClassAsBytes(c), Origin.unknown());
+    }
+    AndroidApp app = ToolHelper.runD8(builder);
+    DexInspector inspector = new DexInspector(app);
+    ClassSubject clazz = inspector.clazz(SubClass.class);
+    assertThat(clazz, isPresent());
+    MethodSubject method =
+        clazz.method("void", "<init>", ImmutableList.of("double", "double", "java.lang.Object"));
+    assertThat(method, isPresent());
+    DexCode code = method.getMethod().getCode().asDexCode();
+    return code;
+  }
+
+  @Test
+  public void addExtraLocalToConstructor()
+      throws IOException, CompilationFailedException, ExecutionException {
+    DexCode code = compileClassesGetSubClassInit(AndroidApiLevel.L_MR1.getLevel());
+    assertTrue(code.registerSize > code.incomingRegisterSize);
+    assertTrue(Arrays.stream(code.instructions).anyMatch((i) -> i instanceof SingleConstant));
+  }
+
+  @Test
+  public void doNotAddExtraLocalToConstructor()
+      throws IOException, CompilationFailedException, ExecutionException {
+    DexCode code = compileClassesGetSubClassInit(AndroidApiLevel.M.getLevel());
+    assertEquals(code.registerSize, code.incomingRegisterSize);
+    assertTrue(Arrays.stream(code.instructions).noneMatch((i) -> i instanceof SingleConstant));
+  }
+}