Do not share constant use one time by a phi
- Do no share constant used only one time by a phi instruction,
on contrary move it directly to the branch corresponding to
the phi operand. It will avoid to define constant on useless path.
- It save about 11k of dex code for deploy.jar of GMSCore V9 and V10.
Bug: 65432240
Change-Id: I0b4b9d57e083cbcd5e0e05c696f3d1938d092561
diff --git a/src/main/java/com/android/tools/r8/ir/code/Phi.java b/src/main/java/com/android/tools/r8/ir/code/Phi.java
index 28a4d74..a644470 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Phi.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Phi.java
@@ -363,4 +363,8 @@
public Set<Value> getDebugValues() {
return debugValues != null ? debugValues : ImmutableSet.of();
}
+
+ public boolean usesValueOneTime(Value usedValue) {
+ return operands.indexOf(usedValue) == operands.lastIndexOf(usedValue);
+ }
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index fbde02b..5570abe 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1177,7 +1177,16 @@
// needs to go in the immediate dominator block so that it is available for phi moves.
for (Phi phi : instruction.outValue().uniquePhiUsers()) {
if (phi.getBlock() == dominator) {
- dominator = dominatorTree.immediateDominator(dominator);
+ if (instruction.outValue().numberOfAllUsers() == 1 &&
+ phi.usesValueOneTime(instruction.outValue())) {
+ // Out value is used only one time, move the constant directly to the corresponding
+ // branch rather than into the dominator to avoid to generate a const on paths which
+ // does not required it.
+ int predIndex = phi.getOperands().indexOf(instruction.outValue());
+ dominator = dominator.getPredecessors().get(predIndex);
+ } else {
+ dominator = dominatorTree.immediateDominator(dominator);
+ }
break;
}
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index 1573b34..92a755e 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -4,21 +4,29 @@
package com.android.tools.r8.jasmin;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
import com.android.tools.r8.CompilationException;
import com.android.tools.r8.R8;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.dex.ApplicationReader;
+import com.android.tools.r8.errors.DexOverflowException;
import com.android.tools.r8.graph.AppInfo;
import com.android.tools.r8.graph.AppInfoWithSubtyping;
import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.naming.NamingLens;
-import com.android.tools.r8.shaking.ProguardRuleParserException;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.android.tools.r8.utils.DexInspector.MethodSubject;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.OutputMode;
import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.Timing;
import com.google.common.collect.ImmutableList;
import jasmin.ClassFile;
import java.io.File;
@@ -28,6 +36,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
@@ -135,4 +144,65 @@
throws IOException, CompilationException, ExecutionException {
return ToolHelper.optimizeWithR8(app, new AppInfoWithSubtyping(app), options);
}
+
+ protected DexApplication buildApplication(JasminBuilder builder, InternalOptions options) {
+ try {
+ return buildApplication(AndroidApp.fromClassProgramData(builder.buildClasses()), options);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected DexApplication buildApplication(AndroidApp input, InternalOptions options) {
+ try {
+ options.itemFactory.resetSortedIndices();
+ return new ApplicationReader(input, options, new Timing("JasminTest")).read();
+ } catch (IOException | ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String runArt(DexApplication application, InternalOptions options, String mainClass)
+ throws DexOverflowException {
+ try {
+ AndroidApp app = writeDex(application, options);
+ return runOnArt(app, mainClass);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public AndroidApp writeDex(DexApplication application, InternalOptions options)
+ throws DexOverflowException {
+ AppInfo appInfo = new AppInfo(application);
+ try {
+ return R8.writeApplication(
+ Executors.newSingleThreadExecutor(),
+ application,
+ appInfo,
+ null,
+ NamingLens.getIdentityLens(),
+ null,
+ null,
+ options);
+ } catch (ExecutionException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected DexEncodedMethod getMethod(DexApplication application, String clazz,
+ MethodSignature signature) {
+ return getMethod(application,
+ clazz, signature.type, signature.name, signature.parameters);
+ }
+
+ protected DexEncodedMethod getMethod(DexApplication application, String className,
+ String returnType, String methodName, String[] parameters) {
+ DexInspector inspector = new DexInspector(application);
+ ClassSubject clazz = inspector.clazz(className);
+ assertTrue(clazz.isPresent());
+ MethodSubject method = clazz.method(returnType, methodName, Arrays.asList(parameters));
+ assertTrue(method.isPresent());
+ return method.getMethod();
+ }
}
diff --git a/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
new file mode 100644
index 0000000..e8efd38
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/jasmin/Regress65432240.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2017, 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.jasmin;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.code.Const4;
+import com.android.tools.r8.code.IfNez;
+import com.android.tools.r8.graph.DexApplication;
+import com.android.tools.r8.graph.DexCode;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.ir.code.ConstNumber;
+import com.android.tools.r8.naming.MemberNaming.MethodSignature;
+import com.android.tools.r8.utils.InternalOptions;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+
+public class Regress65432240 extends JasminTestBase {
+
+ @Test
+ public void testConstantNotIntoEntryBlock() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ JasminBuilder.ClassBuilder clazz = builder.addClass("Test");
+
+ MethodSignature signature = clazz.addStaticMethod("test1", ImmutableList.of("I"), "I",
+ ".limit stack 3",
+ ".limit locals 2",
+ " iload 0",
+ " ifne L2",
+ "L1:",
+ " iconst_0",
+ " ireturn",
+ "L2:",
+ " iload 0",
+ " iload 0",
+ " iconst_1",
+ " isub",
+ " invokestatic Test/test1(I)I",
+ " iadd",
+ " ireturn");
+
+ clazz.addMainMethod(
+ ".limit stack 2",
+ ".limit locals 1",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_0",
+ " invokestatic Test/test1(I)I",
+ " invokestatic java/lang/Integer/toString(I)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " getstatic java/lang/System/out Ljava/io/PrintStream;",
+ " iconst_0",
+ " invokestatic Test/test1(I)I",
+ " invokestatic java/lang/Integer/toString(I)Ljava/lang/String;",
+ " invokevirtual java/io/PrintStream/print(Ljava/lang/String;)V",
+ " return");
+
+ String expected = runOnJava(builder, clazz.name);
+
+ InternalOptions options = new InternalOptions();
+ DexApplication originalApplication = buildApplication(builder, options);
+ DexApplication processedApplication = process(originalApplication, options);
+
+ DexEncodedMethod method = getMethod(processedApplication, clazz.name, signature);
+ DexCode code = method.getCode().asDexCode();
+ assertTrue(code.instructions[0] instanceof IfNez);
+
+ String artResult = runArt(processedApplication, options, clazz.name);
+ assertEquals(expected, artResult);
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
index 3750a86..21d3b9e 100644
--- a/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/smali/SwitchRewritingTest.java
@@ -302,11 +302,10 @@
DexCode code = method.getCode().asDexCode();
if (key == 0) {
assertEquals(5, code.instructions.length);
- assertTrue(code.instructions[2] instanceof IfEqz);
+ assertTrue(code.instructions[0] instanceof IfEqz);
} else {
assertEquals(6, code.instructions.length);
- assertTrue(some16BitConst(code.instructions[2]));
- assertTrue(code.instructions[3] instanceof IfEq);
+ assertTrue(code.instructions[1] instanceof IfEq);
}
}
@@ -360,13 +359,13 @@
DexEncodedMethod method = getMethod(app, signature);
DexCode code = method.getCode().asDexCode();
if (twoCaseWillUsePackedSwitch(key1, key2)) {
- assertTrue(code.instructions[3] instanceof PackedSwitch);
+ assertTrue(code.instructions[0] instanceof PackedSwitch);
} else {
if (key1 == 0) {
- assertTrue(code.instructions[3] instanceof IfEqz);
+ assertTrue(code.instructions[0] instanceof IfEqz);
} else {
// Const instruction before if.
- assertTrue(code.instructions[4] instanceof IfEq);
+ assertTrue(code.instructions[1] instanceof IfEq);
}
}
}