Revert "Revert "Add backporting of sun.misc.unsafe.compareAndSwapObject""
This reverts commit 8b67422de9e376c7027bf4827144b179fe93c0e7.
Change-Id: I36b71daba892064851a77bcadc8ee30c2b3e7354
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 197ab34..1e8735f 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
@@ -34,6 +34,7 @@
import com.android.tools.r8.ir.desugar.backports.SparseArrayMethodRewrites;
import com.android.tools.r8.ir.desugar.desugaredlibrary.DesugaredLibraryRetargeter;
import com.android.tools.r8.synthesis.SyntheticNaming;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
@@ -85,10 +86,13 @@
invoke, appView, eventConsumer, methodProcessingContext, localStackAllocator)
: null;
}
-
@Override
public boolean needsDesugaring(CfInstruction instruction, ProgramMethod context) {
- return instruction.isInvoke() && methodIsBackport(instruction.asInvoke().getMethod());
+ return instruction.isInvoke()
+ && methodIsBackport(instruction.asInvoke().getMethod())
+ && !appView
+ .getSyntheticItems()
+ .isSyntheticOfKind(context.getContextType(), SyntheticKind.BACKPORT_WITH_FORWARDING);
}
public boolean methodIsBackport(DexMethod method) {
@@ -123,7 +127,6 @@
BackportedMethods.registerSynthesizedCodeReferences(options.itemFactory);
}
-
private MethodProvider getMethodProviderOrNull(DexMethod method) {
DexMethod original = appView.graphLens().getOriginalMethodSignature(method);
assert original != null;
@@ -184,6 +187,9 @@
if (options.minApiLevel.isLessThan(AndroidApiLevel.S)) {
initializeAndroidSMethodProviders(factory);
}
+ if (options.getMinApiLevel().isLessThan(AndroidApiLevel.Sv2)) {
+ initializeAndroidSv2MethodProviders(factory);
+ }
// The following providers are currently not implemented at any API level in Android.
// They however require the Optional/Stream class to be present, either through desugared
@@ -1086,6 +1092,30 @@
factory.androidUtilSparseArrayMembers.set, SparseArrayMethodRewrites.rewriteSet()));
}
+ private void initializeAndroidSv2MethodProviders(DexItemFactory factory) {
+ DexString name;
+ DexProto proto;
+ DexMethod method;
+ // sun.misc.Unsafe
+
+ // 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));
+ }
+
private void initializeJava9MethodProviders(DexItemFactory factory) {
// Integer
DexType type = factory.boxedIntType;
@@ -1448,6 +1478,10 @@
this.methodName = methodName;
}
+ protected SyntheticKind getSyntheticKind() {
+ return SyntheticNaming.SyntheticKind.BACKPORT;
+ }
+
@Override
public Collection<CfInstruction> rewriteInvoke(
CfInvoke invoke,
@@ -1465,7 +1499,7 @@
return appView
.getSyntheticItems()
.createMethod(
- SyntheticNaming.SyntheticKind.BACKPORT,
+ getSyntheticKind(),
methodProcessingContext.createUniqueContext(),
appView,
builder ->
@@ -1503,6 +1537,20 @@
}
}
+ // Version of StatifyingMethodGenerator for backports which will call the method they backport.
+ // Such backports will not go through backporting again as that would cause infinite recursion.
+ private static class StatifyingMethodWithForwardingGenerator extends StatifyingMethodGenerator {
+ StatifyingMethodWithForwardingGenerator(
+ DexMethod method, TemplateMethodFactory factory, String methodName, DexType receiverType) {
+ super(method, factory, methodName, receiverType);
+ }
+
+ @Override
+ protected SyntheticKind getSyntheticKind() {
+ return SyntheticKind.BACKPORT_WITH_FORWARDING;
+ }
+ }
+
private interface TemplateMethodFactory {
CfCode create(InternalOptions options, DexMethod method);
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 9413849..c49293a 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
@@ -104,6 +104,7 @@
factory.createSynthesizedType("Ljava/util/stream/IntStream;");
factory.createSynthesizedType("Ljava/util/stream/LongStream;");
factory.createSynthesizedType("Ljava/util/stream/Stream;");
+ factory.createSynthesizedType("Lsun/misc/Unsafe;");
factory.createSynthesizedType("[Ljava/lang/CharSequence;");
factory.createSynthesizedType("[Ljava/lang/Class;");
factory.createSynthesizedType("[Ljava/lang/Object;");
@@ -8968,4 +8969,84 @@
ImmutableList.of(),
ImmutableList.of());
}
+
+ public static CfCode UnsafeMethods_compareAndSwapObject(
+ 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,
+ 6,
+ 6,
+ ImmutableList.of(
+ label0,
+ new CfFrame(
+ new Int2ReferenceAVLTreeMap<>(
+ new int[] {0, 1, 2, 4, 5},
+ new FrameType[] {
+ FrameType.initialized(options.itemFactory.createType("Lsun/misc/Unsafe;")),
+ FrameType.initialized(options.itemFactory.objectType),
+ FrameType.initialized(options.itemFactory.longType),
+ 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.LONG, 2),
+ new CfLoad(ValueType.OBJECT, 4),
+ new CfLoad(ValueType.OBJECT, 5),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Lsun/misc/Unsafe;"),
+ options.itemFactory.createProto(
+ options.itemFactory.booleanType,
+ options.itemFactory.objectType,
+ options.itemFactory.longType,
+ options.itemFactory.objectType,
+ options.itemFactory.objectType),
+ options.itemFactory.createString("compareAndSwapObject")),
+ 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, 4, 5},
+ new FrameType[] {
+ FrameType.initialized(options.itemFactory.createType("Lsun/misc/Unsafe;")),
+ FrameType.initialized(options.itemFactory.objectType),
+ FrameType.initialized(options.itemFactory.longType),
+ 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.LONG, 2),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Lsun/misc/Unsafe;"),
+ options.itemFactory.createProto(
+ options.itemFactory.objectType,
+ options.itemFactory.objectType,
+ options.itemFactory.longType),
+ options.itemFactory.createString("getObject")),
+ false),
+ new CfLoad(ValueType.OBJECT, 4),
+ new CfIfCmp(If.Type.EQ, ValueType.OBJECT, label0),
+ label3,
+ new CfConstNumber(0, ValueType.INT),
+ new CfReturn(ValueType.INT),
+ label4),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
}
diff --git a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
index 0a05c0f..7fd4cd6 100644
--- a/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
+++ b/src/main/java/com/android/tools/r8/synthesis/SyntheticNaming.java
@@ -44,6 +44,7 @@
ENUM_UNBOXING_CHECK_NOT_ZERO_METHOD("CheckNotZero", 27, true),
RECORD_HELPER("Record", 9, true),
BACKPORT("Backport", 10, true),
+ BACKPORT_WITH_FORWARDING("BackportWithForwarding", 34, true),
STATIC_INTERFACE_CALL("StaticInterfaceCall", 11, true),
TO_STRING_IF_NOT_NULL("ToStringIfNotNull", 12, true),
THROW_CCE_IF_NOT_NULL("ThrowCCEIfNotNull", 13, true),
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 7f77265..0c6d576 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -42,26 +42,34 @@
private final Set<String> ignoredInvokes = new HashSet<>();
private static class ClassInfo {
+ private final String name;
private final Class<?> clazz;
private final List<byte[]> classFileData;
+ private ClassInfo(String name) {
+ this.name = name;
+ this.clazz = null;
+ this.classFileData = null;
+ }
+
private ClassInfo(Class<?> clazz) {
+ this.name = clazz.getName();
this.clazz = clazz;
this.classFileData = null;
}
private ClassInfo(byte[] classFileData) {
- this.clazz = null;
- this.classFileData = ImmutableList.of(classFileData);
+ this(ImmutableList.of(classFileData));
}
private ClassInfo(List<byte[]> classFileData) {
+ this.name = extractClassName(classFileData.get(0));
this.clazz = null;
this.classFileData = classFileData;
}
String getName() {
- return clazz != null ? clazz.getName() : extractClassName(classFileData.get(0));
+ return name;
}
TestBuilder<?, ?> addAsProgramClass(TestBuilder<?, ?> builder) throws IOException {
@@ -79,6 +87,11 @@
}
AbstractBackportTest(
+ TestParameters parameters, String className, List<byte[]> testClassFileData) {
+ this(parameters, new ClassInfo(className), new ClassInfo(testClassFileData), null, null);
+ }
+
+ AbstractBackportTest(
TestParameters parameters, byte[] targetClassFileData, List<byte[]> testClassFileData) {
this(
parameters,
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
new file mode 100644
index 0000000..dbe597d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/UnsafeBackportTest.java
@@ -0,0 +1,90 @@
+// 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.transformers.ClassFileTransformer.MethodPredicate;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class UnsafeBackportTest extends AbstractBackportTest {
+
+ private static final String UNSAFE_TYPE_NAME = "sun.misc.Unsafe";
+ private static final String UNSAFE_DESCRIPTOR =
+ DescriptorUtils.javaTypeToDescriptor(UNSAFE_TYPE_NAME);
+
+ @Parameters(name = "{0}")
+ public static Iterable<?> data() {
+ return getTestParameters()
+ .withDexRuntimesStartingFromExcluding(Version.V4_0_4)
+ .withAllApiLevels()
+ .build();
+ }
+
+ public UnsafeBackportTest(TestParameters parameters) throws IOException {
+ super(
+ parameters,
+ "sun.misc.Unsafe",
+ ImmutableList.of(UnsafeBackportTest.getTestRunner(), UnsafeBackportTest.getA()));
+
+ ignoreInvokes("objectFieldOffset");
+
+ // sun.misc.Unsafe issue is on API 31.
+ registerTarget(AndroidApiLevel.Sv2, 3);
+ }
+
+ public static class UnsafeStub {
+
+ boolean compareAndSwapObject(Object receiver, long offset, Object expect, Object update) {
+ throw new RuntimeException("Stub called.");
+ }
+
+ public long objectFieldOffset(Field field) {
+ throw new RuntimeException("Stub called.");
+ }
+ }
+
+ private static byte[] getTestRunner() throws IOException {
+ return transformer(TestRunner.class)
+ .setReturnType(MethodPredicate.onName("getUnsafe"), UNSAFE_TYPE_NAME)
+ .replaceClassDescriptorInMethodInstructions(descriptor(UnsafeStub.class), UNSAFE_DESCRIPTOR)
+ .transform();
+ }
+
+ private static byte[] getA() throws IOException {
+ return transformer(A.class).transform();
+ }
+
+ public static class A {
+ public String field;
+ }
+
+ public static class TestRunner extends MiniAssert {
+
+ private static UnsafeStub getUnsafe() throws Exception {
+ Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
+ Field f = unsafeClass.getDeclaredField("theUnsafe");
+ f.setAccessible(true);
+ return (UnsafeStub) f.get(null);
+ }
+
+ public static void main(String[] args) throws Exception {
+ UnsafeStub theUnsafe = getUnsafe();
+ A x = new A();
+ long offset = theUnsafe.objectFieldOffset(A.class.getField("field"));
+ assertTrue(theUnsafe.compareAndSwapObject(x, offset, null, "A"));
+ assertTrue(theUnsafe.compareAndSwapObject(x, offset, "A", "B"));
+ assertFalse(theUnsafe.compareAndSwapObject(x, offset, "A", "B"));
+ }
+ }
+}
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 5e79870..ce75bd9 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
@@ -50,7 +50,8 @@
OptionalMethods.class,
ShortMethods.class,
StreamMethods.class,
- StringMethods.class);
+ StringMethods.class,
+ UnsafeMethods.class);
protected final TestParameters parameters;
@@ -101,6 +102,34 @@
}
}
+ private static CfInstruction rewriteToUnsafe(
+ DexItemFactory itemFactory, CfInstruction instruction) {
+ // Rewrite references to UnsafeStub to sun.misc.Unsafe.
+ if (instruction.isInvoke()) {
+ String name = instruction.asInvoke().getMethod().getName().toString();
+ if (name.equals("compareAndSwapObject") || name.equals("getObject")) {
+ CfInvoke invoke = instruction.asInvoke();
+ return new CfInvoke(
+ invoke.getOpcode(),
+ itemFactory.createMethod(
+ itemFactory.createType("Lsun/misc/Unsafe;"),
+ invoke.getMethod().getProto(),
+ itemFactory.createString(name)),
+ invoke.isInterface());
+ }
+ }
+ if (instruction.isFrame()) {
+ return instruction
+ .asFrame()
+ .map(
+ type ->
+ (type.getTypeName().endsWith("$UnsafeStub"))
+ ? itemFactory.createType("Lsun/misc/Unsafe;")
+ : type);
+ }
+ return instruction;
+ }
+
@Override
protected CfCode getCode(String holderName, String methodName, CfCode code) {
if (methodName.endsWith("Stub")) {
@@ -113,6 +142,12 @@
.map(instruction -> rewriteToJava9API(factory, instruction))
.collect(Collectors.toList()));
}
+ if (holderName.equals("UnsafeMethods") && methodName.equals("compareAndSwapObject")) {
+ code.setInstructions(
+ code.getInstructions().stream()
+ .map(instruction -> rewriteToUnsafe(factory, instruction))
+ .collect(Collectors.toList()));
+ }
return code;
}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/UnsafeMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/UnsafeMethods.java
new file mode 100644
index 0000000..95dab48
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/UnsafeMethods.java
@@ -0,0 +1,31 @@
+// 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;
+
+public final class UnsafeMethods {
+ // Stub out sun.misc.Unsafe to avoid compiler issues with referring to sun.misc.Unsafe.
+ private static class UnsafeStub {
+
+ public boolean compareAndSwapObject(
+ Object receiver, long offset, Object expect, Object update) {
+ throw new RuntimeException("Stub called.");
+ }
+
+ public Object getObject(Object receiver, long offset) {
+ throw new RuntimeException("Stub called.");
+ }
+ }
+
+ // Workaround Android S issue with compareAndSwapObject (b/211646483).
+ public static boolean compareAndSwapObject(
+ UnsafeStub unsafe, Object receiver, long offset, Object expect, Object update) {
+ do {
+ if (unsafe.compareAndSwapObject(receiver, offset, expect, update)) {
+ return true;
+ }
+ } while (unsafe.getObject(receiver, offset) == expect);
+ return false;
+ }
+}