Reland "Add UninitializedThisLocalRead to instance initializer exits"

This reverts commit 376b4a5dcb300fe3855c91c00f1e6be689c05f41.

Bug: 183285081
Bug: 166738818
Change-Id: Id74ace648d61709ad6e18ea97172f96551e0b601
diff --git a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
index a3dc92b..51b19d8e 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Opcodes.java
@@ -73,4 +73,5 @@
   int THROW = 64;
   int USHR = 65;
   int XOR = 66;
+  int UNINITIALIZED_THIS_LOCAL_READ = 67;
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java b/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
new file mode 100644
index 0000000..3d8419d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/code/UninitializedThisLocalRead.java
@@ -0,0 +1,92 @@
+// Copyright (c) 2021, 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.dex.Constants;
+import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.conversion.CfBuilder;
+import com.android.tools.r8.ir.conversion.DexBuilder;
+import com.android.tools.r8.ir.optimize.DeadCodeRemover.DeadInstructionResult;
+import com.android.tools.r8.ir.optimize.Inliner.ConstraintWithTarget;
+import com.android.tools.r8.ir.optimize.InliningConstraints;
+
+/**
+ * To preserve stack-map table information regarding uninitializedThis flags the
+ * UninitializedThisLocalRead is added to every instance-initializer's exits to fake a "read" of
+ * this. For more information, see b/183285081
+ */
+public class UninitializedThisLocalRead extends Instruction {
+
+  private static final String ERROR_MESSAGE =
+      "Unexpected attempt to emit/use UninitializedThisLocalRead.";
+
+  public UninitializedThisLocalRead(Value inValue) {
+    super(null, inValue);
+  }
+
+  @Override
+  public int opcode() {
+    return Opcodes.UNINITIALIZED_THIS_LOCAL_READ;
+  }
+
+  @Override
+  public <T> T accept(InstructionVisitor<T> visitor) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
+  public void buildDex(DexBuilder builder) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
+  public void buildCf(CfBuilder builder) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
+  public boolean identicalNonValueNonPositionParts(Instruction other) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
+  public boolean instructionMayTriggerMethodInvocation(AppView<?> appView, ProgramMethod context) {
+    return false;
+  }
+
+  @Override
+  public int maxInValueRegister() {
+    return Constants.U16BIT_MAX;
+  }
+
+  @Override
+  public int maxOutValueRegister() {
+    return Constants.U16BIT_MAX;
+  }
+
+  @Override
+  public ConstraintWithTarget inliningConstraint(
+      InliningConstraints inliningConstraints, ProgramMethod context) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
+  public void insertLoadAndStores(InstructionListIterator it, LoadStoreHelper helper) {
+    // Non-materializing so no stack values are needed.
+  }
+
+  @Override
+  public DeadInstructionResult canBeDeadCode(AppView<?> appView, IRCode code) {
+    throw new Unreachable(ERROR_MESSAGE);
+  }
+
+  @Override
+  public boolean hasInvariantOutType() {
+    return true;
+  }
+}
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 6595192..0d7ad7f 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
@@ -45,6 +45,7 @@
 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.UninitializedThisLocalRead;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.code.Xor;
 import com.android.tools.r8.ir.optimize.CodeRewriter;
@@ -52,6 +53,8 @@
 import com.android.tools.r8.ir.optimize.PeepholeOptimizer;
 import com.android.tools.r8.ir.optimize.PhiOptimizations;
 import com.android.tools.r8.ir.optimize.peepholes.BasicBlockMuncher;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap;
 import it.unimi.dsi.fastutil.ints.Int2ReferenceMap.Entry;
@@ -59,9 +62,11 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceSortedMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.ListIterator;
 import java.util.Map;
@@ -148,8 +153,19 @@
       }
     }
     assert code.isConsistentSSA();
+    // Insert reads for uninitialized read blocks to ensure correct stack maps.
+    Set<UninitializedThisLocalRead> uninitializedThisLocalReads =
+        insertUninitializedThisLocalReads();
     registerAllocator = new CfRegisterAllocator(appView, code, typeVerificationHelper);
     registerAllocator.allocateRegisters();
+    // Remove any uninitializedThisLocalRead now that the register allocation will preserve this
+    // for instance initializers.
+    if (!uninitializedThisLocalReads.isEmpty()) {
+      for (UninitializedThisLocalRead uninitializedThisLocalRead : uninitializedThisLocalReads) {
+        uninitializedThisLocalRead.getBlock().removeInstruction(uninitializedThisLocalRead);
+      }
+    }
+
     loadStoreHelper.insertPhiMoves(registerAllocator);
 
     for (int i = 0; i < PEEPHOLE_OPTIMIZATION_PASSES; i++) {
@@ -168,6 +184,27 @@
     return code;
   }
 
+  private Set<UninitializedThisLocalRead> insertUninitializedThisLocalReads() {
+    if (!method.isInstanceInitializer()) {
+      return Collections.emptySet();
+    }
+    // Find all non-normal exit blocks.
+    Set<UninitializedThisLocalRead> uninitializedThisLocalReads = Sets.newIdentityHashSet();
+    for (BasicBlock exitBlock : code.blocks) {
+      if (exitBlock.exit().isThrow() && !exitBlock.hasCatchHandlers()) {
+        LinkedList<Instruction> instructions = exitBlock.getInstructions();
+        Instruction throwing = instructions.removeLast();
+        assert throwing.isThrow();
+        UninitializedThisLocalRead read = new UninitializedThisLocalRead(code.getThis());
+        uninitializedThisLocalReads.add(read);
+        read.setBlock(exitBlock);
+        instructions.addLast(read);
+        instructions.addLast(throwing);
+      }
+    }
+    return uninitializedThisLocalReads;
+  }
+
   private static boolean verifyInvokeInterface(CfCode code, AppView<?> appView) {
     if (appView.options().testing.allowInvokeErrors) {
       return true;
@@ -590,31 +627,32 @@
       throw new Unreachable("Unexpected type info: " + typeInfo);
     }
     BasicBlock definitionBlock = definition.getBlock();
-    Set<BasicBlock> visited = new HashSet<>();
-    Deque<BasicBlock> toVisit = new ArrayDeque<>();
+    assert definitionBlock != liveBlock;
+    Set<BasicBlock> initializerBlocks = new HashSet<>();
     List<InvokeDirect> valueInitializers =
         definition.isArgument() ? thisInitializers : initializers.get(definition.asNewInstance());
     for (InvokeDirect initializer : valueInitializers) {
       BasicBlock initializerBlock = initializer.getBlock();
       if (initializerBlock == liveBlock) {
+        // We assume that we are not initializing an object twice.
         return res;
       }
-      if (initializerBlock != definitionBlock && visited.add(initializerBlock)) {
-        toVisit.addLast(initializerBlock);
+      initializerBlocks.add(initializerBlock);
+    }
+    // If the live-block has a predecessor that is an initializer-block the value is initialized,
+    // otherwise it is not.
+    WorkList<BasicBlock> findInitWorkList =
+        WorkList.newEqualityWorkList(liveBlock.getPredecessors());
+    while (findInitWorkList.hasNext()) {
+      BasicBlock predecessor = findInitWorkList.next();
+      if (initializerBlocks.contains(predecessor)) {
+        return null;
+      }
+      if (predecessor != definitionBlock) {
+        findInitWorkList.addIfNotSeen(predecessor.getPredecessors());
       }
     }
-    while (!toVisit.isEmpty()) {
-      BasicBlock block = toVisit.removeLast();
-      for (BasicBlock predecessor : block.getPredecessors()) {
-        if (predecessor == liveBlock) {
-          return res;
-        }
-        if (predecessor != definitionBlock && visited.add(predecessor)) {
-          toVisit.addLast(predecessor);
-        }
-      }
-    }
-    return null;
+    return res;
   }
 
   private void emitLabel(CfLabel label) {
diff --git a/src/test/java/com/android/tools/r8/cf/StackMapFlagThisUninitTest.java b/src/test/java/com/android/tools/r8/cf/StackMapFlagThisUninitTest.java
index b3f7fe4..977d7c6 100644
--- a/src/test/java/com/android/tools/r8/cf/StackMapFlagThisUninitTest.java
+++ b/src/test/java/com/android/tools/r8/cf/StackMapFlagThisUninitTest.java
@@ -51,11 +51,7 @@
         .addProgramClassFileData(ADump.dump())
         .addKeepAllClassesRule()
         .run(parameters.getRuntime(), Main.class)
-        // TODO(b/166738818): We should generate the correct stack map entry.
-        .assertFailureWithErrorThatMatches(
-            containsString(
-                "Exception in thread \"main\" java.lang.VerifyError: Inconsistent stackmap frames"
-                    + " at branch target 22"));
+        .assertSuccessWithOutputLines("foo");
   }
 
   public static class Main {
diff --git a/src/test/java/com/android/tools/r8/cf/StackMapForThrowingInitializerTest.java b/src/test/java/com/android/tools/r8/cf/StackMapForThrowingInitializerTest.java
index bea206e..e7a8f4f 100644
--- a/src/test/java/com/android/tools/r8/cf/StackMapForThrowingInitializerTest.java
+++ b/src/test/java/com/android/tools/r8/cf/StackMapForThrowingInitializerTest.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.cf;
 
-import static org.hamcrest.CoreMatchers.containsString;
-
 import com.android.tools.r8.NeverClassInline;
 import com.android.tools.r8.NeverInline;
 import com.android.tools.r8.TestBase;
@@ -65,9 +63,7 @@
         .enableNeverClassInliningAnnotations()
         .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), Main.class, EXPECTED)
-        // TODO(b/183285081): Should not have verification errors.
-        .assertFailureWithErrorThatMatches(
-            containsString("java.lang.VerifyError: Inconsistent stackmap frames at branch target"));
+        .assertSuccessWithOutputLines(EXPECTED);
   }
 
   @NeverClassInline