Add backporting of AtomicReferenceFieldUpdater.compareAndSet

Bug: 211646483
Change-Id: Ia25ebc48b432377bb8df47a487a2951e064f7f4b
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index a3fc0e8..ee7f8e1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -1091,27 +1091,44 @@
     }
 
     private void initializeAndroidSv2MethodProviders(DexItemFactory factory) {
-      DexString name;
-      DexProto proto;
-      DexMethod method;
       // sun.misc.Unsafe
+      {
+        // compareAndSwapObject(Object receiver, long offset, Object expect, Object update)
+        DexType type = factory.unsafeType;
+        DexString name = factory.createString("compareAndSwapObject");
+        DexProto proto =
+            factory.createProto(
+                factory.booleanType,
+                factory.objectType,
+                factory.longType,
+                factory.objectType,
+                factory.objectType);
+        DexMethod method = factory.createMethod(type, proto, name);
+        addProvider(
+            new StatifyingMethodWithForwardingGenerator(
+                method,
+                BackportedMethods::UnsafeMethods_compareAndSwapObject,
+                "compareAndSwapObject",
+                type));
+      }
 
-      // compareAndSwapObject(Object receiver, long offset, Object expect, Object update)
-      name = factory.createString("compareAndSwapObject");
-      proto =
-          factory.createProto(
-              factory.booleanType,
-              factory.objectType,
-              factory.longType,
-              factory.objectType,
-              factory.objectType);
-      method = factory.createMethod(factory.unsafeType, proto, name);
-      addProvider(
-          new StatifyingMethodWithForwardingGenerator(
-              method,
-              BackportedMethods::UnsafeMethods_compareAndSwapObject,
-              "compareAndSwapObject",
-              factory.unsafeType));
+      // java.util.concurrent.atomic.AtomicReferenceFieldUpdater
+      {
+        // compareAndSet(Object object, Object expect, Object update)
+        DexType type =
+            factory.createType("Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;");
+        DexString name = factory.createString("compareAndSet");
+        DexProto proto =
+            factory.createProto(
+                factory.booleanType, factory.objectType, factory.objectType, factory.objectType);
+        DexMethod method = factory.createMethod(type, proto, name);
+        addProvider(
+            new StatifyingMethodWithForwardingGenerator(
+                method,
+                BackportedMethods::AtomicReferenceFieldUpdaterMethods_compareAndSet,
+                "compareAndSet",
+                type));
+      }
     }
 
     private void initializeJava9MethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index b934c40..e6b793f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -97,6 +97,7 @@
     factory.createSynthesizedType("Ljava/util/OptionalInt;");
     factory.createSynthesizedType("Ljava/util/OptionalLong;");
     factory.createSynthesizedType("Ljava/util/Set;");
+    factory.createSynthesizedType("Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;");
     factory.createSynthesizedType("Ljava/util/function/Consumer;");
     factory.createSynthesizedType("Ljava/util/function/DoubleConsumer;");
     factory.createSynthesizedType("Ljava/util/function/IntConsumer;");
@@ -114,6 +115,85 @@
     factory.createSynthesizedType("[Ljava/util/Map$Entry;");
   }
 
+  public static CfCode AtomicReferenceFieldUpdaterMethods_compareAndSet(
+      InternalOptions options, DexMethod method) {
+    CfLabel label0 = new CfLabel();
+    CfLabel label1 = new CfLabel();
+    CfLabel label2 = new CfLabel();
+    CfLabel label3 = new CfLabel();
+    CfLabel label4 = new CfLabel();
+    return new CfCode(
+        method.holder,
+        4,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;")),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.objectType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfLoad(ValueType.OBJECT, 3),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType(
+                        "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.booleanType,
+                        options.itemFactory.objectType,
+                        options.itemFactory.objectType,
+                        options.itemFactory.objectType),
+                    options.itemFactory.createString("compareAndSet")),
+                false),
+            new CfIf(If.Type.EQ, ValueType.INT, label2),
+            label1,
+            new CfConstNumber(1, ValueType.INT),
+            new CfReturn(ValueType.INT),
+            label2,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initialized(
+                          options.itemFactory.createType(
+                              "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;")),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.objectType),
+                      FrameType.initialized(options.itemFactory.objectType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.OBJECT, 1),
+            new CfInvoke(
+                182,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType(
+                        "Ljava/util/concurrent/atomic/AtomicReferenceFieldUpdater;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.objectType, options.itemFactory.objectType),
+                    options.itemFactory.createString("get")),
+                false),
+            new CfLoad(ValueType.OBJECT, 2),
+            new CfIfCmp(If.Type.EQ, ValueType.OBJECT, label0),
+            label3,
+            new CfConstNumber(0, ValueType.INT),
+            new CfReturn(ValueType.INT),
+            label4),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode BooleanMethods_compare(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AtomicReferenceFieldUpdaterTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AtomicReferenceFieldUpdaterTest.java
new file mode 100644
index 0000000..1e66d40
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AtomicReferenceFieldUpdaterTest.java
@@ -0,0 +1,47 @@
+// Copyright (c) 2022, 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.desugar.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class AtomicReferenceFieldUpdaterTest extends AbstractBackportTest {
+
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters()
+        .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+        .withAllApiLevels()
+        .build();
+  }
+
+  public AtomicReferenceFieldUpdaterTest(TestParameters parameters) {
+    super(parameters, AtomicReferenceFieldUpdater.class, Main.class);
+
+    ignoreInvokes("newUpdater");
+
+    // java.util.concurrent.atomic.AtomicReferenceFieldUpdater issue is on API 31, see b/211646483.
+    registerTarget(AndroidApiLevel.Sv2, 3);
+  }
+
+  public static class Main extends MiniAssert {
+    public volatile String field;
+
+    public static void main(String[] args) throws Exception {
+      AtomicReferenceFieldUpdater<Main, String> updater =
+          AtomicReferenceFieldUpdater.newUpdater(Main.class, String.class, "field");
+      Main x = new Main();
+      assertTrue(updater.compareAndSet(x, null, "A"));
+      assertTrue(updater.compareAndSet(x, "A", "B"));
+      assertFalse(updater.compareAndSet(x, "A", "B"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java
index dbe597d..00134b5 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java
@@ -39,7 +39,7 @@
 
     ignoreInvokes("objectFieldOffset");
 
-    // sun.misc.Unsafe issue is on API 31.
+    // sun.misc.Unsafe issue is on API 31, see b/211646483..
     registerTarget(AndroidApiLevel.Sv2, 3);
   }
 
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/AtomicReferenceFieldUpdaterMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/AtomicReferenceFieldUpdaterMethods.java
new file mode 100644
index 0000000..b3f658c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/AtomicReferenceFieldUpdaterMethods.java
@@ -0,0 +1,23 @@
+// Copyright (c) 2022, 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.desugar.backports;
+
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
+
+public final class AtomicReferenceFieldUpdaterMethods {
+  // Workaround Android S issue with AtomicReferenceFieldUpdater.compareAndSet (b/211646483).
+  public static boolean compareAndSet(
+      AtomicReferenceFieldUpdater<Object, Object> updater,
+      Object object,
+      Object expect,
+      Object update) {
+    do {
+      if (updater.compareAndSet(object, expect, update)) {
+        return true;
+      }
+    } while (updater.get(object) == expect);
+    return false;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
index f955ca3..c6e3eea 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/GenerateBackportMethods.java
@@ -34,6 +34,7 @@
       factory.createType("Lcom/android/tools/r8/ir/desugar/backports/BackportedMethods;");
   private final List<Class<?>> METHOD_TEMPLATE_CLASSES =
       ImmutableList.of(
+          AtomicReferenceFieldUpdaterMethods.class,
           BooleanMethods.class,
           ByteMethods.class,
           CharSequenceMethods.class,