Fall through to shared suffixes in suffix sharer

IdenticalBlockSuffixSharer inserts the block with the shared suffix
after the first predecessor, which then falls through to it. When the
first predecessor only consists of the shared suffix, it becomes an
empty block and all the other predecessors need a goto to the shared
suffix. Use the largest predecessor instead, so that a predecessor
keeping some instructions falls through to the shared suffix.

Change-Id: I574ee1ebbce8491ab3164f736fe1c9dd00000000
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/finalizer/passes/IdenticalBlockSuffixSharer.java b/src/main/java/com/android/tools/r8/ir/conversion/finalizer/passes/IdenticalBlockSuffixSharer.java
index 4865429..d45efd3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/finalizer/passes/IdenticalBlockSuffixSharer.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/finalizer/passes/IdenticalBlockSuffixSharer.java
@@ -25,6 +25,8 @@
 import it.unimi.dsi.fastutil.ints.Int2ReferenceOpenHashMap;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
 import java.util.List;
@@ -159,7 +161,13 @@
                   syntheticNormalExits.contains(block) ? null : block,
                   allocator,
                   blocksToRemove);
-          newBlocks.put(predsWithSameLastInstruction.get(0), newBlock);
+          // Insert the new block after a predecessor that keeps some instructions, so that it falls
+          // through to the new block instead of being removed.
+          BasicBlock predBeforeNewBlock =
+              Collections.max(
+                  predsWithSameLastInstruction,
+                  Comparator.comparingInt(pred -> pred.getInstructions().size()));
+          newBlocks.put(predBeforeNewBlock, newBlock);
           hasChanged = true;
         }
       }
diff --git a/src/test/java/com/android/tools/r8/ir/conversion/finalizer/passes/IdenticalBlockSuffixSharerFallthroughTest.java b/src/test/java/com/android/tools/r8/ir/conversion/finalizer/passes/IdenticalBlockSuffixSharerFallthroughTest.java
new file mode 100644
index 0000000..a01f0a6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/conversion/finalizer/passes/IdenticalBlockSuffixSharerFallthroughTest.java
@@ -0,0 +1,76 @@
+// Copyright (c) 2026, 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.conversion.finalizer.passes;
+
+import static org.junit.Assert.assertTrue;
+
+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.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class IdenticalBlockSuffixSharerFallthroughTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(getClass())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("10", "19", "1");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    MethodSubject method = inspector.clazz(TestClass.class).uniqueMethodWithOriginalName("test");
+    assertTrue(method.isPresent());
+    // The suffix `b * 3 + 7` is shared by the first two branches. The shared suffix is placed after
+    // the second branch, which falls through to it, so that no goto is needed.
+    assertTrue(method.streamInstructions().noneMatch(InstructionSubject::isGoto));
+  }
+
+  static class TestClass {
+
+    @NeverInline
+    static int test(int a, int b) {
+      int r;
+      if (a == 0) {
+        r = b * 3 + 7;
+      } else if (a == 1) {
+        b = b ^ 5;
+        r = b * 3 + 7;
+      } else {
+        r = b;
+      }
+      return r;
+    }
+
+    public static void main(String[] args) {
+      System.out.println(test(0, args.length + 1));
+      System.out.println(test(1, args.length + 1));
+      System.out.println(test(2, args.length + 1));
+    }
+  }
+}