Fix initClass with position and pop in cf back-end

- initClass in cf back-end must be followed by a pop.

Bug: b/290565182
Change-Id: I0672675a4a48ade99e35395ed5eb4c4dcd7269b7
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 dd732b4..c46e899 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
@@ -412,11 +412,6 @@
    */
   public abstract boolean identicalNonValueNonPositionParts(Instruction other);
 
-  public boolean identicalNonValueParts(Instruction other) {
-    assert getClass() == other.getClass();
-    return position.equals(other.position) && identicalNonValueNonPositionParts(other);
-  }
-
   private boolean identicalInputAfterRegisterAllocation(
       Value a,
       int aInstrNumber,
@@ -504,18 +499,29 @@
     return a.outType() == b.outType();
   }
 
+  protected boolean identicalNonValueParts(Instruction other, RegisterAllocator allocator) {
+    assert getClass() == other.getClass();
+    if (!identicalNonValueNonPositionParts(other)) {
+      return false;
+    }
+    return identicalPosition(other, allocator);
+  }
+
+  protected final boolean identicalPosition(Instruction other, RegisterAllocator allocator) {
+    // In debug mode or if the instruction can throw we must account for positions, in release mode
+    // we do want to share non-throwing instructions even if their positions differ.
+    if (instructionTypeCanThrow() || allocator.options().debug) {
+      return position.equals(other.position);
+    }
+    return true;
+  }
+
   public boolean identicalAfterRegisterAllocation(
       Instruction other, RegisterAllocator allocator, MethodConversionOptions conversionOptions) {
     if (other.getClass() != getClass()) {
       return false;
     }
-    // In debug mode or if the instruction can throw we must account for positions, in release mode
-    // we do want to share non-throwing instructions even if their positions differ.
-    if (instructionTypeCanThrow() || allocator.options().debug) {
-      if (!identicalNonValueParts(other)) {
-        return false;
-      }
-    } else if (!identicalNonValueNonPositionParts(other)) {
+    if (!identicalNonValueParts(other, allocator)) {
       return false;
     }
     if (isInvokeDirect() && !asInvokeDirect().sameConstructorReceiverValue(other.asInvoke())) {
diff --git a/src/main/java/com/android/tools/r8/ir/code/Pop.java b/src/main/java/com/android/tools/r8/ir/code/Pop.java
index 5839b47..00554e8 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Pop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Pop.java
@@ -13,7 +13,9 @@
 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;
+import com.android.tools.r8.ir.regalloc.RegisterAllocator;
 import com.android.tools.r8.lightir.LirBuilder;
+import java.util.function.BiPredicate;
 
 public class Pop extends Instruction {
 
@@ -53,20 +55,38 @@
   }
 
   @Override
+  protected boolean identicalNonValueParts(Instruction other, RegisterAllocator allocator) {
+    assert other.isPop();
+    if (!identicalPop(
+        other.asPop(),
+        (initClass, otherInitClass) ->
+            initClass.identicalNonValueParts(otherInitClass, allocator))) {
+      return false;
+    }
+    assert identicalNonValueNonPositionParts(other);
+    return identicalPosition(other, allocator);
+  }
+
+  @Override
   public boolean identicalNonValueNonPositionParts(Instruction other) {
     if (!other.isPop()) {
       return false;
     }
-    Pop pop = other.asPop();
+    return identicalPop(other.asPop(), InitClass::identicalNonValueNonPositionParts);
+  }
+
+  // Pops are identical except if immediately following an InitClass, in which case they are
+  // identical only if the corresponding InitClass are.
+  private boolean identicalPop(Pop other, BiPredicate<InitClass, InitClass> predicate) {
     if (getFirstOperand().isDefinedByInstructionSatisfying(Instruction::isInitClass)) {
       InitClass initClass = getFirstOperand().getDefinition().asInitClass();
-      if (!pop.getFirstOperand().isDefinedByInstructionSatisfying(Instruction::isInitClass)) {
+      if (!other.getFirstOperand().isDefinedByInstructionSatisfying(Instruction::isInitClass)) {
         return false;
       }
-      InitClass otherInitClass = pop.getFirstOperand().getDefinition().asInitClass();
-      return initClass.getClassValue() == otherInitClass.getClassValue();
+      InitClass otherInitClass = other.getFirstOperand().getDefinition().asInitClass();
+      return predicate.test(initClass, otherInitClass);
     }
-    return !pop.getFirstOperand().isDefinedByInstructionSatisfying(Instruction::isInitClass);
+    return !other.getFirstOperand().isDefinedByInstructionSatisfying(Instruction::isInitClass);
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/debug/classinit/InitClassPopCfTest.java b/src/test/java/com/android/tools/r8/debug/classinit/InitClassPopCfTest.java
new file mode 100644
index 0000000..86ff2c4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/debug/classinit/InitClassPopCfTest.java
@@ -0,0 +1,132 @@
+// Copyright (c) 2023, 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.debug.classinit;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.AlwaysInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.AppView;
+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.Position;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.Iterator;
+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 InitClassPopCfTest extends TestBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InitClassPopCfTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testInitClass() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .enableInliningAnnotations()
+        .enableAlwaysInliningAnnotations()
+        .addOptionsModification(
+            internalOptions -> internalOptions.testing.irModifier = this::modifyIR)
+        .addKeepRules("-keepattributes LineNumberTable")
+        .addKeepRules("-keep class * { static java.lang.Object f; }")
+        .addKeepMainRule(Main.class)
+        .compile()
+        .inspect(InitClassPopCfTest::verifyInitClassFollowedByPopInCf);
+  }
+
+  private void modifyIR(IRCode irCode, AppView<?> appView) {
+    InstructionIterator instructionIterator = irCode.instructionIterator();
+    Position pos = null;
+    while (instructionIterator.hasNext()) {
+      Instruction next = instructionIterator.next();
+      // Modify the position so code can be shared except for initClass and invoke-static that
+      // will be transformed into initClass.
+      if (!next.isInitClass()
+          && !(next.isInvokeStatic()
+              && next.asInvokeStatic().getInvokedMethod().getName().toString().equals("nop"))) {
+        if (pos == null) {
+          pos = next.getPosition();
+        }
+        next.forceOverwritePosition(pos);
+      }
+    }
+  }
+
+  private static void verifyInitClassFollowedByPopInCf(CodeInspector i) {
+    MethodSubject main = i.clazz(Main.class).mainMethod();
+    Iterator<InstructionSubject> iterator = main.iterateInstructions();
+    int index = 0;
+    while (iterator.hasNext()) {
+      InstructionSubject next = iterator.next();
+      // All static gets on non System are init class.
+      if (next.isStaticGet()
+          && !next.getField().getHolderType().toString().equals("java.lang.System")) {
+        assertTrue(iterator.next().isPop());
+        index++;
+      }
+    }
+    assertEquals(4, index);
+  }
+
+  public static class A {
+    static {
+      System.out.println("clinit A");
+    }
+
+    static Object f;
+
+    @AlwaysInline
+    static void nop() {}
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      int i = System.currentTimeMillis() > 0 ? 5 : 3;
+      if (System.currentTimeMillis() > 0) {
+        System.out.println("0");
+        A.nop();
+        print((((i << 2) + 1 << 3) + 4) << 5);
+      } else if (System.currentTimeMillis() > 1) {
+        System.out.println("1");
+        A.nop();
+        print((((i << 2) + 1 << 3) + 4) << 5);
+      } else if (System.currentTimeMillis() > 2) {
+        System.out.println("2");
+        A.nop();
+        print((((i << 2) + 1 << 3) + 4) << 5);
+      } else {
+        System.out.println("3");
+        A.nop();
+        print((((i << 2) + 1 << 3) + 4) << 5);
+      }
+      System.out.println(i);
+    }
+
+    @NeverInline
+    private static void print(int k) {
+      System.out.println(k);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
index 79425a3..989b9a6 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CfInstructionSubject.java
@@ -33,6 +33,7 @@
 import com.android.tools.r8.cf.code.CfReturn;
 import com.android.tools.r8.cf.code.CfReturnVoid;
 import com.android.tools.r8.cf.code.CfStackInstruction;
+import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
 import com.android.tools.r8.cf.code.CfStore;
 import com.android.tools.r8.cf.code.CfSwitch;
 import com.android.tools.r8.cf.code.CfThrow;
@@ -139,6 +140,12 @@
   }
 
   @Override
+  public boolean isPop() {
+    return instruction instanceof CfStackInstruction
+        && ((CfStackInstruction) instruction).getOpcode() == Opcode.Pop;
+  }
+
+  @Override
   public DexMethod getMethod() {
     assert isInvokeMethod();
     return ((CfInvoke) instruction).getMethod();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
index bd68422..e7e5bc3 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/DexInstructionSubject.java
@@ -392,6 +392,11 @@
   }
 
   @Override
+  public boolean isPop() {
+    return false;
+  }
+
+  @Override
   public boolean isIfNez() {
     return instruction instanceof DexIfNez;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
index b77029f..b07a466 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/InstructionSubject.java
@@ -94,6 +94,8 @@
 
   boolean isGoto();
 
+  boolean isPop();
+
   boolean isIfNez();
 
   boolean isIfEq();