Remove trivial goto blocks.

Bug: b/233857593
Change-Id: I7bfa8b94cfb62955bff1426462bf20fcb4bb1c15
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
index 201c789..9c04bd4 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/DexBuilder.java
@@ -55,6 +55,7 @@
 import com.android.tools.r8.ir.code.InstructionIterator;
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.IntSwitch;
+import com.android.tools.r8.ir.code.JumpInstruction;
 import com.android.tools.r8.ir.code.Move;
 import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.Position;
@@ -351,11 +352,10 @@
         && currentBlock.getPredecessors().get(0) == previousBlock;
   }
 
-  private static void removeTrivialFallthroughBlocks(IRCode code) {
-    for (int blockIndex = 1; blockIndex < code.blocks.size() - 1; blockIndex++) {
-      // We skip the last block as it has no fallthrough. We also skip checking the entry block as
-      // it has no predecessors and must define the initial position. Any subsequent block must be
-      // statically reachable and thus have predecessors.
+  private static void removeTrivialGotoBlocks(IRCode code) {
+    for (int blockIndex = 1; blockIndex < code.blocks.size(); blockIndex++) {
+      // We skip checking the entry block as it has no predecessors and must define the initial
+      // position. Any subsequent block must be statically reachable and thus have predecessors.
       BasicBlock currentBlock = code.blocks.get(blockIndex);
       assert !currentBlock.getPredecessors().isEmpty();
       if (currentBlock.size() != 2) {
@@ -366,35 +366,62 @@
       if (debugPosition == null || exit == null || debugPosition.getPosition().isNone()) {
         continue;
       }
-      BasicBlock nextBlock = code.blocks.get(blockIndex + 1);
-      if (exit.getTarget() != nextBlock) {
-        continue;
-      }
-      // The block is a trivial position block that falls through to the following.
-      // If the position is equal to each predecessor block then the line is already active on
-      // each entry to the fallthrough and it can be safely removed.
       boolean allMatch = true;
       Position position = debugPosition.getPosition();
       for (BasicBlock pred : currentBlock.getPredecessors()) {
-        // Do to the target == next check this cannot be a trivial loop.
-        assert pred != currentBlock;
+        // If the block is a trivial loop it must remain.
+        if (pred == currentBlock) {
+          allMatch = false;
+          break;
+        }
+        // If the position is already active on each predecessor exit it can be safely removed
+        // (except for if/switch fallthrough cases guarded below).
         Position predExit = pred.exit().getPosition();
         if (!position.equals(predExit)) {
           allMatch = false;
           break;
         }
+        // If this is a required fallthrough, we can only remove it if it targets the next block.
+        // Note that this could fail for a given block but then become valid after an intermediate
+        // block is removed. See the reset of blockIndex below for dealing with that case.
+        if (isFallthroughTargetToNonFallthroughTarget(pred, currentBlock, blockIndex, code)) {
+          allMatch = false;
+          break;
+        }
       }
       if (allMatch) {
         currentBlock.removeInstruction(debugPosition);
-        CodeRewriter.unlinkTrivialGotoBlock(currentBlock, nextBlock);
+        CodeRewriter.unlinkTrivialGotoBlock(currentBlock, exit.getTarget());
         code.removeBlocks(Collections.singleton(currentBlock));
         // Having removed the block at blockIndex, the previous block may now be a trivial
-        // fallthrough. Rewind to that point and retry. This avoids iterating to a fixed point.
+        // fallthrough from an if/switch. Rewind to that point and retry. This avoids iterating to
+        // a fixed point.
         blockIndex = Math.max(0, blockIndex - 2);
       }
     }
   }
 
+  private static boolean isFallthroughTargetToNonFallthroughTarget(
+      BasicBlock pred, BasicBlock current, int blockIndex, IRCode code) {
+    JumpInstruction exit = pred.exit();
+    BasicBlock fallthrough;
+    if (exit.isIf()) {
+      fallthrough = exit.asIf().fallthroughBlock();
+    } else if (exit.isSwitch()) {
+      fallthrough = exit.asSwitch().fallthroughBlock();
+    } else {
+      return false;
+    }
+    if (fallthrough != current) {
+      return false;
+    }
+    if (blockIndex + 1 >= code.blocks.size()) {
+      return false;
+    }
+    BasicBlock nextBlock = code.blocks.get(blockIndex + 1);
+    return current.exit().asGoto().getTarget() != nextBlock;
+  }
+
   // Eliminates unneeded debug positions.
   //
   // After this pass all remaining debug positions mark places where we must ensure a materializing
@@ -407,7 +434,7 @@
     // We must start by removing any blocks that are already trivial fallthrough blocks with no
     // position change. With these removed it is then sound to make the fallthrough judgement when
     // determining if a goto will materialize or not.
-    removeTrivialFallthroughBlocks(code);
+    removeTrivialGotoBlocks(code);
 
     // Current position known to have a materializing instruction associated with it.
     Position currentMaterializedPosition = Position.none();
diff --git a/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java b/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java
index d786a12..596707f 100644
--- a/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java
+++ b/src/test/java/com/android/tools/r8/debuginfo/Regress233857593Test.java
@@ -7,8 +7,9 @@
 
 import com.android.tools.r8.TestBase;
 import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -21,8 +22,8 @@
   @Parameter() public TestParameters parameters;
 
   @Parameters(name = "{0}")
-  public static List<Object[]> data() {
-    return buildParameters(getTestParameters().withDexRuntimes().withAllApiLevels().build());
+  public static TestParametersCollection data() {
+    return getTestParameters().withDefaultDexRuntime().withApiLevel(AndroidApiLevel.B).build();
   }
 
   @Test
@@ -32,17 +33,15 @@
         .setMinApi(parameters.getApiLevel())
         .compile()
         .inspect(
-            // TODO(b/233857593): There used to be only one goto.
-            inspector -> {
-              assertEquals(
-                  2,
-                  inspector
-                      .clazz(TestClass.class)
-                      .uniqueMethodWithName("testLoopPhiWithNullFirstInput")
-                      .streamInstructions()
-                      .filter(InstructionSubject::isGoto)
-                      .count());
-            });
+            inspector ->
+                assertEquals(
+                    1,
+                    inspector
+                        .clazz(TestClass.class)
+                        .uniqueMethodWithName("testLoopPhiWithNullFirstInput")
+                        .streamInstructions()
+                        .filter(InstructionSubject::isGoto)
+                        .count()));
   }
 
   static class TestClass {