Fix inlining of instance initializers

Change-Id: Ica254cb4b06901a2ca45a3a8f3a997a2dfa3c95d
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index 661371c..fa9384a 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -42,6 +42,7 @@
 import java.util.ListIterator;
 import java.util.Map;
 import java.util.Map.Entry;
+import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.WeakHashMap;
 import java.util.function.Consumer;
@@ -566,6 +567,40 @@
     return () -> iterator(instruction);
   }
 
+  public Iterable<Instruction> instructionsBefore(Instruction instruction) {
+    return () ->
+        new Iterator<Instruction>() {
+
+          private InstructionIterator iterator = iterator();
+          private Instruction next = advance();
+
+          private Instruction advance() {
+            if (iterator.hasNext()) {
+              Instruction next = iterator.next();
+              if (next != instruction) {
+                return next;
+              }
+            }
+            return null;
+          }
+
+          @Override
+          public boolean hasNext() {
+            return next != null;
+          }
+
+          @Override
+          public Instruction next() {
+            Instruction result = next;
+            if (result == null) {
+              throw new NoSuchElementException();
+            }
+            next = advance();
+            return result;
+          }
+        };
+  }
+
   public boolean isEmpty() {
     return instructions.isEmpty();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index c4180a3..fee2c94 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -1167,7 +1167,7 @@
    * <p>Note: It is the responsibility of the caller to return the marking color.
    */
   public void markTransitivePredecessors(BasicBlock subject, int color) {
-    assert isMarkingColorInUse(color) && !anyBlocksMarkedWithColor(color);
+    assert isMarkingColorInUse(color);
     Queue<BasicBlock> worklist = new ArrayDeque<>();
     worklist.add(subject);
     while (!worklist.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index f755108..d23805d 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
@@ -33,6 +32,7 @@
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.IteratorUtils;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.BitSet;
 import java.util.List;
 import java.util.ListIterator;
@@ -460,7 +460,6 @@
 
   @Override
   public boolean canInlineInstanceInitializer(
-      InvokeDirect invoke,
       IRCode inlinee,
       WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     // In the Java VM Specification section "4.10.2.4. Instance Initialization Methods and
@@ -473,68 +472,88 @@
     // Allow inlining a constructor into a constructor of the same class, as the constructor code
     // is expected to adhere to the VM specification.
     DexType callerMethodHolder = method.method.holder;
-    boolean callerMethodIsConstructor = method.isInstanceInitializer();
-    DexType calleeMethodHolder = invoke.asInvokeMethod().getInvokedMethod().holder;
+    DexType calleeMethodHolder = inlinee.method.method.holder;
     // Calling a constructor on the same class from a constructor can always be inlined.
-    if (callerMethodIsConstructor && callerMethodHolder == calleeMethodHolder) {
+    if (method.isInstanceInitializer() && callerMethodHolder == calleeMethodHolder) {
       return true;
     }
 
-    // We cannot invoke <init> on other values than |this| on Dalvik 4.4.4. Compute whether
-    // the receiver to the call was the this value at the call-site.
-    boolean receiverOfInnerCallIsThisOfOuter = invoke.getReceiver().isThis();
+    // Only allow inlining a constructor into a non-constructor if:
+    // (1) the first use of the uninitialized object is the receiver of an invoke of <init>(),
+    // (2) the constructor does not initialize any final fields, as such is only allowed from within
+    //     a constructor of the corresponding class, and
+    // (3) the constructors own <init>() call is on the same class.
+    //
+    // Note that, due to (3), we do allow inlining of `A(int x)` into another class, but not the
+    // default constructor `A()`, since the default constructor invokes Object.<init>().
+    //
+    //   class A {
+    //     A() { ... }
+    //     A(int x) {
+    //       this()
+    //       ...
+    //     }
+    //   }
+    Value thisValue = inlinee.entryBlock().entry().asArgument().outValue();
 
-    // Don't allow inlining a constructor into a non-constructor if the first use of the
-    // un-initialized object is not an argument of an invoke of <init>.
-    // Also, we cannot inline a constructor if it initializes final fields, as such is only allowed
-    // from within a constructor of the corresponding class.
-    // Lastly, we can only inline a constructor, if its own <init> call is on the method's class. If
-    // we inline into a constructor, calls to super.<init> are also OK if the receiver of the
-    // super.<init> call is the this argument.
-    InstructionIterator iterator = inlinee.instructionIterator();
-    Instruction instruction = iterator.next();
-    // A constructor always has the un-initialized object as the first argument.
-    assert instruction.isArgument();
-    Value unInitializedObject = instruction.outValue();
-    boolean seenSuperInvoke = false;
-    while (iterator.hasNext()) {
-      instruction = iterator.next();
-      if (instruction.inValues().contains(unInitializedObject)) {
-        if (instruction.isInvokeDirect() && !seenSuperInvoke) {
-          DexMethod target = instruction.asInvokeDirect().getInvokedMethod();
-          seenSuperInvoke = appView.dexItemFactory().isConstructor(target);
-          boolean callOnConstructorThatCallsConstructorSameClass =
-              calleeMethodHolder == target.holder;
-          boolean callOnSupertypeOfThisInConstructor =
-              appView.appInfo().isDirectSubtype(callerMethodHolder, target.holder)
-                  && instruction.asInvokeDirect().getReceiver() == unInitializedObject
-                  && receiverOfInnerCallIsThisOfOuter
-                  && callerMethodIsConstructor;
-          if (seenSuperInvoke
-              // Calls to init on same class than the called constructor are OK.
-              && !callOnConstructorThatCallsConstructorSameClass
-              // If we are inlining into a constructor, calls to superclass init are only OK on the
-              // |this| value in the outer context.
-              && !callOnSupertypeOfThisInConstructor) {
-            return false;
+    List<InvokeDirect> initCallsOnThis = new ArrayList<>();
+    for (Instruction instruction : inlinee.instructions()) {
+      if (instruction.isInvokeDirect()) {
+        InvokeDirect initCall = instruction.asInvokeDirect();
+        DexMethod invokedMethod = initCall.getInvokedMethod();
+        if (appView.dexItemFactory().isConstructor(invokedMethod)) {
+          Value receiver = initCall.getReceiver().getAliasedValue();
+          if (receiver == thisValue) {
+            // The <init>() call of the constructor must be on the same class.
+            if (calleeMethodHolder != invokedMethod.holder) {
+              return false;
+            }
+            initCallsOnThis.add(initCall);
           }
         }
-        if (!seenSuperInvoke) {
-          return false;
-        }
-      }
-      if (instruction.isInstancePut()) {
-        // Fields may not be initialized outside of a constructor.
-        if (!callerMethodIsConstructor) {
-          return false;
-        }
+      } else if (instruction.isInstancePut()) {
+        // Final fields may not be initialized outside of a constructor in the enclosing class.
         DexField field = instruction.asInstancePut().getField();
         DexEncodedField target = appView.appInfo().lookupInstanceTarget(field.holder, field);
-        if (target != null && target.accessFlags.isFinal()) {
+        if (target == null || target.accessFlags.isFinal()) {
           return false;
         }
       }
     }
+
+    // Check that there are no uses of the uninitialized object before it gets initialized.
+    int markingColor = inlinee.reserveMarkingColor();
+    for (InvokeDirect initCallOnThis : initCallsOnThis) {
+      BasicBlock block = initCallOnThis.getBlock();
+      for (Instruction instruction : block.instructionsBefore(initCallOnThis)) {
+        for (Value inValue : instruction.inValues()) {
+          Value root = inValue.getAliasedValue();
+          if (root == thisValue) {
+            inlinee.returnMarkingColor(markingColor);
+            return false;
+          }
+        }
+      }
+      for (BasicBlock predecessor : block.getPredecessors()) {
+        inlinee.markTransitivePredecessors(predecessor, markingColor);
+      }
+    }
+
+    for (BasicBlock block : inlinee.blocks) {
+      if (block.isMarked(markingColor)) {
+        for (Instruction instruction : block.getInstructions()) {
+          for (Value inValue : instruction.inValues()) {
+            Value root = inValue.getAliasedValue();
+            if (root == thisValue) {
+              inlinee.returnMarkingColor(markingColor);
+              return false;
+            }
+          }
+        }
+      }
+    }
+
+    inlinee.returnMarkingColor(markingColor);
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
index 4f34bd3..b44e32c 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/ForcedInliningOracle.java
@@ -11,7 +11,6 @@
 import com.android.tools.r8.ir.analysis.ClassInitializationAnalysis;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMethodWithReceiver;
 import com.android.tools.r8.ir.code.InvokeStatic;
@@ -97,7 +96,7 @@
 
   @Override
   public boolean canInlineInstanceInitializer(
-      InvokeDirect invoke, IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
+      IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter) {
     return true;
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 9c5a539..b6ae500 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -776,7 +776,7 @@
           assert !singleTarget.isClassInitializer();
           if (singleTarget.isInstanceInitializer()
               && !strategy.canInlineInstanceInitializer(
-                  invoke.asInvokeDirect(), inlinee.code, whyAreYouNotInliningReporter)) {
+                  inlinee.code, whyAreYouNotInliningReporter)) {
             // TODO(b/142108662): Enable assertion once reporting is complete.
             // assert whyAreYouNotInliningReporter.verifyReasonHasBeenReported();
             continue;
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
index 5d7bdc2..21b060a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/InliningStrategy.java
@@ -8,7 +8,6 @@
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
-import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.optimize.Inliner.InlineAction;
 import com.android.tools.r8.ir.optimize.Inliner.InlineeWithReason;
@@ -19,7 +18,7 @@
 interface InliningStrategy {
 
   boolean canInlineInstanceInitializer(
-      InvokeDirect invoke, IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
+      IRCode code, WhyAreYouNotInliningReporter whyAreYouNotInliningReporter);
 
   /** Return true if there is still budget for inlining into this method. */
   boolean stillHasBudget(