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);