Reenable loop unrolling with phis
Change-Id: I8e22c86e63596730ad249cd787d6ab03245b74cc
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
index 23cf067..1e327f8 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/passes/NaturalIntLoopRemover.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.ir.conversion.passes.result.CodeRewriterResult;
import com.android.tools.r8.ir.optimize.AffectedValues;
import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
import java.util.Set;
/**
@@ -109,7 +110,7 @@
if (!analyzeLoopExit(loopBody, comparison, builder)) {
return false;
}
- if (!analyzePhiUses(loopBody, comparison)) {
+ if (!analyzePhiUses(loopBody, comparison, builder)) {
return false;
}
@@ -123,10 +124,25 @@
}
/**
- * The loop unroller removes phis corresponding to the loop backjump if they are not used outside
- * the loop.
+ * The loop unroller removes phis corresponding to the loop backjump. There are three scenarios:
+ * (1) The loop has a single exit point analyzed, phis used outside the loop are replaced by the
+ * value at the end of the loop body.
+ * (2) The phis are unused outside the loop, and they are simply removed.
+ * (3) The loop has multiple exits and the phis are used outside the loop, this would require
+ * dealing with complex merge point and postponing phis after the loop, we bail out.
*/
- private boolean analyzePhiUses(Set<BasicBlock> loopBody, If comparison) {
+ private boolean analyzePhiUses(
+ Set<BasicBlock> loopBody, If comparison, NaturalIntLoopWithKnowIterations.Builder builder) {
+ // Check for single exit scenario.
+ Set<BasicBlock> successors = Sets.newIdentityHashSet();
+ for (BasicBlock basicBlock : loopBody) {
+ successors.addAll(basicBlock.getSuccessors());
+ }
+ successors.removeAll(loopBody);
+ if (successors.size() == 1) {
+ assert successors.iterator().next() == builder.getLoopExit();
+ return true;
+ }
// Check phis are unused outside the loop.
for (Phi phi : comparison.getBlock().getPhis()) {
for (Instruction use : phi.uniqueUsers()) {
@@ -411,6 +427,10 @@
for (Phi phi : comparisonBlock.getPhis()) {
Value loopEntryValue = phi.getOperand(1 - backIndex);
Value loopExitValue = phi.getOperand(backIndex);
+ if (loopExitValue.isPhi() && comparisonBlock.getPhis().contains(loopExitValue.asPhi())) {
+ loopExitValue = loopExitValue.asPhi().getOperand(1 - backIndex);
+ }
+ assert !loopExitValue.isPhi() || !comparisonBlock.getPhis().contains(loopExitValue.asPhi());
for (Instruction uniqueUser : phi.uniqueUsers()) {
if (loopBody.contains(uniqueUser.getBlock())) {
uniqueUser.replaceValue(phi, loopEntryValue, affectedValues);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/loops/B341618078Test.java b/src/test/java/com/android/tools/r8/regress/B341618078Test.java
similarity index 76%
rename from src/test/java/com/android/tools/r8/ir/optimize/loops/B341618078Test.java
rename to src/test/java/com/android/tools/r8/regress/B341618078Test.java
index 85d5d98..a0a14ed 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/loops/B341618078Test.java
+++ b/src/test/java/com/android/tools/r8/regress/B341618078Test.java
@@ -1,14 +1,16 @@
// Copyright (c) 2024, 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.optimize.loops;
+package com.android.tools.r8.regress;
+import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
import com.android.tools.r8.TestParametersCollection;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -26,7 +28,7 @@
return getTestParameters().withAllRuntimesAndApiLevels().build();
}
- private static final String EXPECTED_OUTPUT = StringUtils.lines("0", "0");
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("0", "0", "1");
@Test
public void testJvm() throws Exception {
@@ -53,10 +55,18 @@
.addInnerClasses(getClass())
.addKeepMainRule(TestClass.class)
.setMinApi(parameters)
+ .compile()
+ .inspect(this::assertLoopUnrolled)
.run(parameters.getRuntime(), TestClass.class)
.assertSuccessWithOutput(EXPECTED_OUTPUT);
}
+ private void assertLoopUnrolled(CodeInspector inspector) {
+ // All control flow has been removed.
+ inspector.clazz(TestClass.class).forAllMethods(m ->
+ assertTrue(m.streamInstructions().noneMatch(i -> i.isGoto() || i.isIf())));
+ }
+
static class TestClass {
long a;
@@ -87,10 +97,24 @@
System.out.println(g);
}
+ static void cChainedSimplified() {
+ int a = 1;
+ int b = 2;
+ int c = 3;
+ int d = 4;
+ for (int h = 0; h < 1; h++) {
+ b = a;
+ c = b;
+ d = c;
+ }
+ System.out.println(d);
+ }
+
public static void main(String[] k) {
TestClass m = new TestClass();
m.i(k);
- TestClass.cSimplified();
+ cSimplified();
+ cChainedSimplified();
}
}
}