Don't optimize AtomicFieldUpdater kept fields.
Change-Id: Ife0c71db3e0aaf75a1892b121e924241b0fe5fe6
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java b/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java
index 39920a6..c6d4d8b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/AtomicFieldUpdaterInstrumentor.java
@@ -210,7 +210,13 @@
// and valid call to
// AtomicReferenceFieldUpdater.newUpdater(ThisClass.class, FieldType.class, "fieldName").
var f = new ProgramField(clazz, field);
- if (!appView
+ // Keep info must be checked before static write to report the correct reason.
+ if (!appView.getKeepInfo(f).isOptimizationAllowed(appView.options())) {
+ reportInfo(
+ appView,
+ new Event.CannotInstrument(field.getReference()),
+ Reason.EXISTS_IN_KEEP_RULE);
+ } else if (!appView
.appInfoWithLiveness()
.isStaticFieldWrittenOnlyInEnclosingStaticInitializer(f)) {
reportInfo(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/atomicupdaters/eligibility/Reason.java b/src/main/java/com/android/tools/r8/ir/optimize/info/atomicupdaters/eligibility/Reason.java
index 0a2c002..d77cff6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/atomicupdaters/eligibility/Reason.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/atomicupdaters/eligibility/Reason.java
@@ -20,6 +20,8 @@
public static final Reason WRITTEN_OUTSIDE_CLASS_INITIALIZER =
new GenericReason("written outside class initializer");
+ public static final Reason EXISTS_IN_KEEP_RULE = new GenericReason("disallowed by keep rules");
+
public static final Reason MULTIPLE_WRITES = new GenericReason("multiple writes");
public static final Reason UPDATER_INITIALIZED_BY_PHI =
diff --git a/src/test/java/com/android/tools/r8/optimize/atomicfieldupdater/AtomicFieldUpdaterKeepTest.java b/src/test/java/com/android/tools/r8/optimize/atomicfieldupdater/AtomicFieldUpdaterKeepTest.java
new file mode 100644
index 0000000..86acdb8
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/optimize/atomicfieldupdater/AtomicFieldUpdaterKeepTest.java
@@ -0,0 +1,118 @@
+// 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.optimize.atomicfieldupdater;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.AnyOf.anyOf;
+import static org.hamcrest.core.IsNot.not;
+import static org.hamcrest.core.StringContains.containsString;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeMatchers;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.hamcrest.core.AnyOf;
+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 AtomicFieldUpdaterKeepTest extends TestBase {
+
+ @Parameter(0)
+ public TestParameters parameters;
+
+ @Parameter(1)
+ public boolean keepRule;
+
+ @Parameters(name = "{0}, keeprule:{1}")
+ public static List<Object[]> data() {
+ // TODO(b/453628974): test all dex and api levels.
+ return buildParameters(
+ TestParameters.builder()
+ .withDexRuntimesStartingFromIncluding(
+ Version.V4_4_4) // Unsafe synthetic doesn't work for 4.0.4.
+ .withAllApiLevels()
+ .build(),
+ BooleanUtils.values());
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Class<TestClass> testClass = TestClass.class;
+ testForR8(parameters)
+ .addOptionsModification(
+ options -> {
+ assertFalse(options.enableAtomicFieldUpdaterOptimization);
+ options.enableAtomicFieldUpdaterOptimization = true;
+ assertFalse(options.testing.enableAtomicFieldUpdaterLogs);
+ options.testing.enableAtomicFieldUpdaterLogs = true;
+ })
+ .addProgramClasses(testClass)
+ .allowDiagnosticInfoMessages()
+ .addKeepMainRule(testClass)
+ .applyIf(
+ keepRule,
+ testing ->
+ testing.addKeepFieldRules(
+ Reference.fieldFromField(testClass.getDeclaredField("myString$FU"))))
+ .compileWithExpectedDiagnostics(
+ diagnostics -> {
+ if (keepRule) {
+ diagnostics.assertInfosMatch(
+ diagnosticMessage(containsString("Cannot instrument")));
+ } else {
+ diagnostics.assertInfoThatMatches(
+ diagnosticMessage(containsString("Can instrument")));
+ }
+ })
+ .inspect(
+ inspector -> {
+ MethodSubject method = inspector.clazz(testClass).mainMethod();
+ AnyOf<MethodSubject> usesUnsafe =
+ anyOf(
+ CodeMatchers.invokesMethodWithHolder("sun.misc.Unsafe"),
+ CodeMatchers.invokesMethodWithHolder("jdk.internal.misc.Unsafe"));
+ if (keepRule) {
+ assertThat(method, not(usesUnsafe));
+ } else {
+ assertThat(method, usesUnsafe);
+ }
+ })
+ .run(parameters.getRuntime(), testClass)
+ .assertSuccessWithOutputLines("Hello");
+ }
+
+ // Corresponding to simple kotlin usage of `atomic("Hello")` via atomicfu.
+ public static class TestClass {
+
+ private volatile Object myString;
+
+ private static final AtomicReferenceFieldUpdater<TestClass, Object> myString$FU;
+
+ static {
+ myString$FU =
+ AtomicReferenceFieldUpdater.newUpdater(TestClass.class, Object.class, "myString");
+ }
+
+ public TestClass() {
+ super();
+ myString = "Hello";
+ }
+
+ public static void main(String[] args) {
+ TestClass instance = new TestClass();
+ System.out.println(myString$FU.get(instance));
+ }
+ }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
index 5a29a7c..404d288 100644
--- a/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
+++ b/src/test/testbase/java/com/android/tools/r8/utils/codeinspector/CodeMatchers.java
@@ -292,6 +292,10 @@
};
}
+ public static Matcher<MethodSubject> invokesMethodWithHolder(String holderType) {
+ return invokesMethod(null, holderType, null, null);
+ }
+
public static Matcher<MethodSubject> invokesMethodWithHolderAndName(
String holderType, String name) {
return invokesMethod(null, holderType, name, null);