Relax dead condition of static-put.
by implementing instructionMayHaveSideEffects:
in addition to general side-effects of field instructions,
we need to check if the target field is read.
Bug: 129530569, 130561746
Change-Id: I606055d0b57a78f34d6deb84d947ea284f0a031a
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
index 6bb754f..e633ab2 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstanceGet.java
@@ -114,10 +114,10 @@
// * IncompatibleClassChangeError (instance-* instruction for static fields)
// * IllegalAccessError (not visible from the access context)
// * NullPointerException (null receiver)
- boolean canBeDeadCode = !instructionMayHaveSideEffects(appView, code.method.method.holder);
- assert appView.enableWholeProgramOptimizations() || !canBeDeadCode
+ boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.method.holder);
+ assert appView.enableWholeProgramOptimizations() || haveSideEffects
: "Expected instance-get instruction to have side effects in D8";
- return canBeDeadCode;
+ return !haveSideEffects;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
index 6c48db2..ba106cb 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticGet.java
@@ -107,10 +107,10 @@
// * IncompatibleClassChangeError (static-* instruction for instance fields)
// * IllegalAccessError (not visible from the access context)
// * side-effects in <clinit>
- boolean canBeDeadCode = !instructionMayHaveSideEffects(appView, code.method.method.holder);
- assert appView.enableWholeProgramOptimizations() || !canBeDeadCode
+ boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.method.holder);
+ assert appView.enableWholeProgramOptimizations() || haveSideEffects
: "Expected static-get instruction to have side effects in D8";
- return canBeDeadCode;
+ return !haveSideEffects;
}
@Override
diff --git a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
index a87692d..ea103dd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StaticPut.java
@@ -26,6 +26,8 @@
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.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.ProguardMemberRule;
import org.objectweb.asm.Opcodes;
public class StaticPut extends FieldInstruction {
@@ -89,6 +91,51 @@
}
@Override
+ public boolean instructionMayHaveSideEffects(
+ AppView<? extends AppInfo> appView, DexType context) {
+ if (appView.appInfo().hasLiveness()) {
+ AppInfoWithLiveness appInfoWithLiveness = appView.appInfo().withLiveness();
+ // MemberValuePropagation will replace the field read only if the target field has bound
+ // -assumevalues rule whose return value is *single*.
+ //
+ // Note that, in principle, class initializer of the field's holder may have side effects.
+ // However, with -assumevalues, we assume that the developer wants to remove field accesses.
+ ProguardMemberRule rule = appInfoWithLiveness.assumedValues.get(getField());
+ if (rule != null && rule.getReturnValue().isSingleValue()) {
+ return false;
+ }
+
+ if (instructionInstanceCanThrow(appView, context).isThrowing()) {
+ return true;
+ }
+
+ if (appInfoWithLiveness.isFieldRead(getField())) {
+ return true;
+ }
+
+ return false;
+ }
+
+ // In D8, we always have to assume that the field can be read, and thus have side effects.
+ assert instructionInstanceCanThrow(appView, context).isThrowing();
+ return true;
+ }
+
+ @Override
+ public boolean canBeDeadCode(AppView<? extends AppInfo> appView, IRCode code) {
+ // static-put can be dead as long as it cannot have any of the following:
+ // * NoSuchFieldError (resolution failure)
+ // * IncompatibleClassChangeError (static-* instruction for instance fields)
+ // * IllegalAccessError (not visible from the access context)
+ // * side-effects in <clinit>
+ // * not read _globally_
+ boolean haveSideEffects = instructionMayHaveSideEffects(appView, code.method.method.holder);
+ assert appView.enableWholeProgramOptimizations() || haveSideEffects
+ : "Expected static-put instruction to have side effects in D8";
+ return !haveSideEffects;
+ }
+
+ @Override
public int maxInValueRegister() {
return Constants.U8BIT_MAX;
}
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 7d5e6bf..8c27bd7 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -375,7 +375,7 @@
if (current.isInvokeMethod()) {
rewriteInvokeMethodWithConstantValues(
code, callingContext, affectedValues, blocks, iterator, current.asInvokeMethod());
- } else if (current.isInstancePut() || current.isStaticPut()) {
+ } else if (current.isInstancePut()) {
rewritePutWithConstantValues(iterator, current.asFieldInstruction(), callingContext);
} else if (current.isStaticGet()) {
rewriteStaticGetWithConstantValues(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
index 640a20d..c6402bd 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/MemberValuePropagationTest.java
@@ -6,32 +6,21 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
-import com.android.tools.r8.TestBase.Backend;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.utils.FileUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.InstructionSubject;
-import java.io.File;
-import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.Collections;
import java.util.Iterator;
-import java.util.List;
-import java.util.stream.Collectors;
-import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@RunWith(Parameterized.class)
-public class MemberValuePropagationTest {
+public class MemberValuePropagationTest extends TestBase {
private static final String PACKAGE_NAME = "write_only_field";
private static final String QUALIFIED_CLASS_NAME = PACKAGE_NAME + ".WriteOnlyCls";
private static final Path EXAMPLE_JAR =
@@ -54,13 +43,9 @@
this.backend = backend;
}
- @Rule
- public TemporaryFolder temp = ToolHelper.getTemporaryFolderForTest();
-
@Test
public void testWriteOnlyField_putObject_gone() throws Exception {
- List<Path> processedApp = runR8(EXAMPLE_KEEP);
- CodeInspector inspector = new CodeInspector(processedApp);
+ CodeInspector inspector = runR8(EXAMPLE_KEEP);
ClassSubject clazz = inspector.clazz(QUALIFIED_CLASS_NAME);
clazz.forAllMethods(
methodSubject -> {
@@ -74,10 +59,8 @@
@Test
public void testWriteOnlyField_dontoptimize() throws Exception {
- List<Path> processedApp = runR8(DONT_OPTIMIZE);
- CodeInspector inspector = new CodeInspector(processedApp);
+ CodeInspector inspector = runR8(DONT_OPTIMIZE);
ClassSubject clazz = inspector.clazz(QUALIFIED_CLASS_NAME);
- assert backend == Backend.DEX || backend == Backend.CF;
clazz.forAllMethods(
methodSubject -> {
Iterator<InstructionSubject> iterator = methodSubject.iterateInstructions();
@@ -88,7 +71,8 @@
++numPuts;
}
}
- assertEquals(1, numPuts);
+ // dead code removal is not part of -dontoptimize.
+ assertEquals(0, numPuts);
}
if (methodSubject.isInstanceInitializer()) {
int numPuts = 0;
@@ -102,29 +86,13 @@
});
}
- private List<Path> runR8(Path proguardConfig) throws IOException, CompilationFailedException {
- Path outputDir = temp.newFolder().toPath();
- assert backend == Backend.DEX || backend == Backend.CF;
- ToolHelper.runR8(
- R8Command.builder()
- .setOutput(outputDir, TestBase.outputMode(backend))
- .addProgramFiles(EXAMPLE_JAR)
- .addLibraryFiles(TestBase.runtimeJar(backend))
- .addProguardConfigurationFiles(proguardConfig)
- .setDisableMinification(true)
- .build(),
- o -> {
- o.enableClassInlining = false;
- });
-
- return backend == Backend.DEX
- ? Collections.singletonList(outputDir.resolve(Paths.get("classes.dex")))
- : Arrays.stream(
- outputDir
- .resolve(PACKAGE_NAME)
- .toFile()
- .listFiles(f -> f.toString().endsWith(".class")))
- .map(File::toPath)
- .collect(Collectors.toList());
+ private CodeInspector runR8(Path proguardConfig) throws Exception {
+ return testForR8(backend)
+ .addProgramFiles(EXAMPLE_JAR)
+ .addKeepRuleFiles(proguardConfig)
+ .noMinification()
+ .addOptionsModification(o -> o.enableClassInlining = false)
+ .compile()
+ .inspector();
}
}
diff --git a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
index dcea0e6..f02109d 100644
--- a/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
+++ b/src/test/java/com/android/tools/r8/naming/IdentifierNameStringMarkerTest.java
@@ -162,6 +162,7 @@
List<String> pgConfigs = ImmutableList.of(
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep class " + CLASS_NAME,
+ "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { static <fields>; }",
"-dontoptimize");
CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
@@ -193,6 +194,7 @@
List<String> pgConfigs = ImmutableList.of(
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep class " + CLASS_NAME,
+ "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { static <fields>; }",
"-dontoptimize");
CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
@@ -230,6 +232,7 @@
List<String> pgConfigs = ImmutableList.of(
"-identifiernamestring class " + CLASS_NAME + " { static java.lang.String sClassName; }",
"-keep class " + CLASS_NAME,
+ "-keepclassmembers,allowobfuscation class " + CLASS_NAME + " { static <fields>; }",
"-keep,allowobfuscation class " + BOO,
"-dontoptimize");
CodeInspector inspector = getInspectorAfterRunR8(builder, pgConfigs);
diff --git a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
index 55f5370..105bf6b 100644
--- a/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/FieldTypeTest.java
@@ -19,6 +19,7 @@
import com.android.tools.r8.naming.MemberNaming.FieldSignature;
import com.android.tools.r8.naming.MemberNaming.MethodSignature;
import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.StringUtils;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.google.common.collect.ImmutableList;
@@ -76,7 +77,8 @@
"new " + impl1.name,
"dup",
"invokespecial " + impl1.name + "/<init>()V",
- // Unused, i.e., not read, field, yet still remained in the output.
+ // Unused, i.e., not read, field, hence removable.
+ // Needs an extra keep rule to keep this (to reproduce broken type hierarchy)
"putstatic " + client.name + "/" + obj1.name + " " + itf1.getDescriptor(),
"new " + impl2.name,
"dup",
@@ -105,11 +107,12 @@
);
final String mainClassName = mainClass.name;
- String proguardConfig =
- keepMainProguardConfiguration(mainClass.name, false, false)
- // AGP default is to not turn optimizations on, which disables MemberValuePropagation,
- // resulting in the problematic putstatic being remained.
- + "-dontoptimize\n";
+ String proguardConfig = StringUtils.lines(
+ keepMainProguardConfiguration(mainClass.name, false, false),
+ // AGP default is to not turn optimizations on
+ "-dontoptimize",
+ "-keepclassmembers class " + client.name + " { static ** " + obj1.name + "; }"
+ );
// Run input program on java.
Path outputDirectory = temp.newFolder().toPath();
diff --git a/src/test/java/com/android/tools/r8/shaking/assumevalues/DeadFieldAfterAssumevaluesTest.java b/src/test/java/com/android/tools/r8/shaking/assumevalues/DeadFieldAfterAssumevaluesTest.java
index 6dbd97e..b0211bc 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumevalues/DeadFieldAfterAssumevaluesTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumevalues/DeadFieldAfterAssumevaluesTest.java
@@ -4,6 +4,7 @@
package com.android.tools.r8.shaking.assumevalues;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
@@ -53,7 +54,7 @@
}
@Test
- public void b130561746() throws Exception {
+ public void testR8() throws Exception {
testForR8(parameters.getBackend())
.addProgramClasses(MAIN)
.addKeepMainRule(MAIN)
@@ -75,11 +76,10 @@
.noneMatch(i -> i.isIf() || i.isIfEqz() || i.isIfNez()));
FieldSubject hasR8 = main.uniqueFieldWithName("HAS_R8");
- // TODO(b/130561746): can be removed.
- assertThat(hasR8, isPresent());
+ assertThat(hasR8, not(isPresent()));
MethodSubject clinit = main.clinit();
- // TODO(b/130561746): can be removed if the above static field is gone.
+ // TODO(b/130561746): need to model that Boolean.parseBoolean doesn't have side-effects.
assertThat(clinit, isPresent());
}
}