Add emulated dispatch in machine specifications
Bug: 184026720
Change-Id: Ie0afa5afe0d0aa3bbb9a02118a74992475523b42
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
new file mode 100644
index 0000000..abe876f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/DerivedMethod.java
@@ -0,0 +1,29 @@
+// 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.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
+
+/**
+ * A derived method is: - if the holderKind is null, a normal dexMethod; - if the holderKind is
+ * non-null, a method derived from the dexMethod In that case the method holder is used as the
+ * context to generate the holder type. The method may however differ (for example the method name
+ * may be different).
+ */
+public class DerivedMethod {
+
+ private final DexMethod method;
+ private final SyntheticKind holderKind;
+
+ public DerivedMethod(DexMethod method) {
+ this(method, null);
+ }
+
+ public DerivedMethod(DexMethod method, SyntheticKind holderKind) {
+ this.holderKind = holderKind;
+ this.method = method;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
new file mode 100644
index 0000000..7b368fc
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/EmulatedDispatchMethodDescriptor.java
@@ -0,0 +1,55 @@
+// 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.desugaredlibrary.machinespecification;
+
+import com.android.tools.r8.graph.DexType;
+import java.util.LinkedHashMap;
+
+public class EmulatedDispatchMethodDescriptor {
+
+ /**
+ * When resolving into the descriptor, if the resolution is used for a super-invoke or to generate
+ * a forwarding method, then the forwarding method should be used. If the resolution is used to
+ * rewrite an invoke, then it should be rewritten to an invoke-static to the emulated dispatch
+ * method.
+ *
+ * <p>Emulated dispatch method are generated as follows: <code>emulatedDispatchMethod {
+ * if (rcvr instanceof itf) {
+ * invoke-interface itfMethod
+ * }
+ * if (rcvr instanceof dispatchCases0) {
+ * invoke-static dispatchCases0
+ * }
+ * ...
+ * if (rcvr instanceof dispatchCasesN) {
+ * invoke-static dispatchCasesN
+ * }
+ * invoke-static forwardingMethod }</code>
+ *
+ * <p>For emulatedVirtualRetarget instances, the itfMethod holder and emulatedDispatchMethod
+ * holder are the itf and emulated dispatch holder types to synthesize. The forwardingMethod is
+ * the method to retarget to.
+ *
+ * <p>For emulated interface, itfMethod holder is the rewritten emulated interface type and the
+ * emulated dispatch method is on the $-EI class holding the dispatch methods. The forwarding
+ * method is the method on the companion class.
+ */
+ private final DerivedMethod interfaceMethod;
+
+ private final DerivedMethod emulatedDispatchMethod;
+ private final DerivedMethod forwardingMethod;
+ private final LinkedHashMap<DexType, DerivedMethod> dispatchCases;
+
+ public EmulatedDispatchMethodDescriptor(
+ DerivedMethod interfaceMethod,
+ DerivedMethod emulatedDispatchMethod,
+ DerivedMethod forwardingMethod,
+ LinkedHashMap<DexType, DerivedMethod> dispatchCases) {
+ this.interfaceMethod = interfaceMethod;
+ this.emulatedDispatchMethod = emulatedDispatchMethod;
+ this.forwardingMethod = forwardingMethod;
+ this.dispatchCases = dispatchCases;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
index 4dd87f6..4fd73d7 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/machinespecification/MachineRewritingFlags.java
@@ -16,9 +16,11 @@
MachineRewritingFlags(
Map<DexMethod, DexMethod> staticRetarget,
- Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget) {
+ Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
+ Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget) {
this.staticRetarget = staticRetarget;
this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
+ this.emulatedVirtualRetarget = emulatedVirtualRetarget;
}
// Static methods to retarget, duplicated to library boundaries.
@@ -32,6 +34,9 @@
// code.
private final Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget;
+ // Virtual methods to retarget through emulated dispatch.
+ private final Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget;
+
public static class Builder {
Builder() {}
@@ -40,6 +45,8 @@
ImmutableMap.builder();
private final ImmutableMap.Builder<DexMethod, DexMethod> nonEmulatedVirtualRetarget =
ImmutableMap.builder();
+ private final ImmutableMap.Builder<DexMethod, EmulatedDispatchMethodDescriptor>
+ emulatedVirtualRetarget = ImmutableMap.builder();
public void putStaticRetarget(DexMethod src, DexMethod dest) {
staticRetarget.put(src, dest);
@@ -49,8 +56,15 @@
nonEmulatedVirtualRetarget.put(src, dest);
}
+ public void putEmulatedVirtualRetarget(DexMethod src, EmulatedDispatchMethodDescriptor dest) {
+ emulatedVirtualRetarget.put(src, dest);
+ }
+
public MachineRewritingFlags build() {
- return new MachineRewritingFlags(staticRetarget.build(), nonEmulatedVirtualRetarget.build());
+ return new MachineRewritingFlags(
+ staticRetarget.build(),
+ nonEmulatedVirtualRetarget.build(),
+ emulatedVirtualRetarget.build());
}
}
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
index 7796c19..d2227cb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineSpecificationConverter.java
@@ -12,19 +12,26 @@
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.graph.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodResolutionResult;
import com.android.tools.r8.graph.SubtypingInfo;
import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanDesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.DerivedMethod;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.EmulatedDispatchMethodDescriptor;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineDesugaredLibrarySpecification;
import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.InternalOptions;
import com.android.tools.r8.utils.ThreadUtils;
import com.android.tools.r8.utils.Timing;
+import com.android.tools.r8.utils.TraversalContinuation;
import java.io.IOException;
import java.nio.file.Path;
+import java.util.LinkedHashMap;
+import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer;
@@ -49,12 +56,14 @@
.getRetargetCoreLibMember()
.forEach(
(method, type) ->
- convertRetargetCoreLibMemberFlag(builder, method, type, appInfo, subtypingInfo));
+ convertRetargetCoreLibMemberFlag(
+ builder, rewritingFlags, method, type, appInfo, subtypingInfo));
return builder.build();
}
private void convertRetargetCoreLibMemberFlag(
MachineRewritingFlags.Builder builder,
+ HumanRewritingFlags rewritingFlags,
DexMethod method,
DexType type,
AppInfoWithClassHierarchy appInfo,
@@ -70,16 +79,79 @@
convertNonEmulatedVirtualRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
return;
}
- convertEmulatedVirtualRetarget(builder, foundMethod, type, appInfo, subtypingInfo);
+ convertEmulatedVirtualRetarget(
+ builder, rewritingFlags, foundMethod, type, appInfo, subtypingInfo);
}
private void convertEmulatedVirtualRetarget(
MachineRewritingFlags.Builder builder,
- DexEncodedMethod foundMethod,
+ HumanRewritingFlags rewritingFlags,
+ DexEncodedMethod src,
DexType type,
AppInfoWithClassHierarchy appInfo,
SubtypingInfo subtypingInfo) {
- // TODO(b/184026720): To implement.
+ if (isEmulatedInterfaceDispatch(src, appInfo, rewritingFlags)) {
+ // Handled by emulated interface dispatch.
+ return;
+ }
+ // TODO(b/184026720): Implement library boundaries.
+ DexProto newProto = appInfo.dexItemFactory().prependHolderToProto(src.getReference());
+ DexMethod forwardingDexMethod =
+ appInfo.dexItemFactory().createMethod(type, newProto, src.getName());
+ DerivedMethod forwardingMethod = new DerivedMethod(forwardingDexMethod);
+ DerivedMethod interfaceMethod =
+ new DerivedMethod(src.getReference(), SyntheticKind.RETARGET_INTERFACE);
+ DexMethod dispatchDexMethod =
+ appInfo.dexItemFactory().createMethod(src.getHolderType(), newProto, src.getName());
+ DerivedMethod dispatchMethod =
+ new DerivedMethod(dispatchDexMethod, SyntheticKind.RETARGET_CLASS);
+ LinkedHashMap<DexType, DerivedMethod> dispatchCases = new LinkedHashMap<>();
+ assert validateNoOverride(src, appInfo, subtypingInfo);
+ builder.putEmulatedVirtualRetarget(
+ src.getReference(),
+ new EmulatedDispatchMethodDescriptor(
+ interfaceMethod, dispatchMethod, forwardingMethod, dispatchCases));
+ }
+
+ private boolean validateNoOverride(
+ DexEncodedMethod src, AppInfoWithClassHierarchy appInfo, SubtypingInfo subtypingInfo) {
+ for (DexType subtype : subtypingInfo.subtypes(src.getHolderType())) {
+ DexClass subclass = appInfo.definitionFor(subtype);
+ MethodResolutionResult resolutionResult =
+ appInfo.resolveMethodOn(subclass, src.getReference());
+ if (resolutionResult.isSuccessfulMemberResolutionResult()
+ && resolutionResult.getResolvedMethod().getReference() != src.getReference()) {
+ assert false; // Unsupported.
+ }
+ }
+ return true;
+ }
+
+ private boolean isEmulatedInterfaceDispatch(
+ DexEncodedMethod method,
+ AppInfoWithClassHierarchy appInfo,
+ HumanRewritingFlags humanRewritingFlags) {
+ // Answers true if this method is already managed through emulated interface dispatch.
+ Map<DexType, DexType> emulateLibraryInterface =
+ humanRewritingFlags.getEmulateLibraryInterface();
+ if (emulateLibraryInterface.isEmpty()) {
+ return false;
+ }
+ DexMethod methodToFind = method.getReference();
+ // Look-up all superclass and interfaces, if an emulated interface is found,
+ // and it implements the method, answers true.
+ DexClass dexClass = appInfo.definitionFor(method.getHolderType());
+ // Cannot retarget a method on a virtual method on an emulated interface.
+ assert !emulateLibraryInterface.containsKey(dexClass.getType());
+ return appInfo
+ .traverseSuperTypes(
+ dexClass,
+ (supertype, subclass, isSupertypeAnInterface) ->
+ TraversalContinuation.breakIf(
+ subclass.isInterface()
+ && emulateLibraryInterface.containsKey(subclass.getType())
+ && subclass.lookupMethod(methodToFind) != null))
+ .shouldBreak();
}
private void convertNonEmulatedRetarget(