Introduce Machine specification wrapper flags
Bug:184026720
Change-Id: I452753be77f2501e35e6d39f7dcf6473d55de2c8
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
index 0795a4e..150e6c4 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryAPICallbackSynthesizer.java
@@ -201,8 +201,7 @@
clazz.isInterface(),
eventConsumer)
.generateCfCode();
- DexEncodedMethod newMethod =
- wrapperSynthesizor.newSynthesizedMethod(methodToInstall, originalMethod, cfCode);
+ DexEncodedMethod newMethod = wrapperSynthesizor.newSynthesizedMethod(methodToInstall, cfCode);
newMethod.setCode(cfCode, appView);
if (originalMethod.isLibraryMethodOverride().isTrue()) {
newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
index 55d61b9..4a8cabe 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/apiconversion/DesugaredLibraryWrapperSynthesizer.java
@@ -29,7 +29,6 @@
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.legacyspecification.LegacyDesugaredLibrarySpecification;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterConstructorCfCodeProvider;
-import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterThrowRuntimeExceptionCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterVivifiedWrapperCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperCfCodeProvider;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APIConverterWrapperConversionCfCodeProvider;
@@ -41,14 +40,11 @@
import com.android.tools.r8.synthesis.SyntheticNaming.SyntheticKind;
import com.android.tools.r8.utils.StringDiagnostic;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Sets;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
-import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
@@ -387,22 +383,8 @@
wrapperField, reverseWrapperField, context)));
}
- private boolean isInvalidWrapper(DexClass clazz) {
- return Iterables.any(allImplementedMethods(clazz), DexEncodedMethod::isFinal);
- }
-
private CfCode computeProgramConversionMethodCode(
DexField wrapperField, DexField reverseWrapperField, DexClass context) {
- if (isInvalidWrapper(context)) {
- return new APIConverterThrowRuntimeExceptionCfCodeProvider(
- appView,
- factory.createString(
- "Unsupported conversion for "
- + context.type
- + ". See compilation time warnings for more details."),
- wrapperField.holder)
- .generateCfCode();
- }
assert context.isProgramClass();
return new APIConverterWrapperConversionCfCodeProvider(
appView, reverseWrapperField, wrapperField)
@@ -459,7 +441,7 @@
private Collection<DexEncodedMethod> synthesizeVirtualMethodsForVivifiedTypeWrapper(
DexClass dexClass, DexEncodedField wrapperField) {
- List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+ Iterable<DexMethod> allImplementedMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only types in their signature, but each method the wrapper forwards
// to should used only vivified types.
@@ -470,30 +452,23 @@
// v2 <- convertTypeToVivifiedType(v0);
// v3 <- wrappedValue.foo(v2,v1);
// return v3;
- Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
- for (DexEncodedMethod dexEncodedMethod : dexMethods) {
- DexClass holderClass = appView.definitionFor(dexEncodedMethod.getHolderType());
+ for (DexMethod method : allImplementedMethods) {
+ DexClass holderClass = appView.definitionFor(method.getHolderType());
boolean isInterface;
if (holderClass == null) {
assert appView
.options()
.desugaredLibrarySpecification
.getEmulateLibraryInterface()
- .containsValue(dexEncodedMethod.getHolderType());
+ .containsValue(method.getHolderType());
isInterface = true;
} else {
isInterface = holderClass.isInterface();
}
DexMethod methodToInstall =
- factory.createMethod(
- wrapperField.getHolderType(),
- dexEncodedMethod.getReference().proto,
- dexEncodedMethod.getReference().name);
+ factory.createMethod(wrapperField.getHolderType(), method.proto, method.name);
CfCode cfCode;
- if (dexEncodedMethod.isFinal()) {
- finalMethods.add(dexEncodedMethod.getReference());
- continue;
- } else if (dexClass.isProgramClass()) {
+ if (dexClass.isProgramClass()) {
cfCode =
new APIConverterVivifiedWrapperCfCodeProvider(
appView, methodToInstall, wrapperField.getReference(), this, isInterface)
@@ -501,16 +476,15 @@
} else {
cfCode = null;
}
- DexEncodedMethod newDexEncodedMethod =
- newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
+ DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode);
generatedMethods.add(newDexEncodedMethod);
}
- return finalizeWrapperMethods(generatedMethods, finalMethods);
+ return generatedMethods;
}
private Collection<DexEncodedMethod> synthesizeVirtualMethodsForTypeWrapper(
DexClass dexClass, DexEncodedField wrapperField) {
- List<DexEncodedMethod> dexMethods = allImplementedMethods(dexClass);
+ Iterable<DexMethod> dexMethods = allImplementedMethods(dexClass);
List<DexEncodedMethod> generatedMethods = new ArrayList<>();
// Each method should use only vivified types in their signature, but each method the wrapper
// forwards
@@ -522,87 +496,64 @@
// v2 <- convertVivifiedTypeToType(v0);
// v3 <- wrappedValue.foo(v2,v1);
// return v3;
- Set<DexMethod> finalMethods = Sets.newIdentityHashSet();
- for (DexEncodedMethod dexEncodedMethod : dexMethods) {
- DexClass holderClass = appView.definitionFor(dexEncodedMethod.getHolderType());
+ for (DexMethod method : dexMethods) {
+ DexClass holderClass = appView.definitionFor(method.getHolderType());
assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
boolean isInterface = holderClass == null || holderClass.isInterface();
DexMethod methodToInstall =
DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature(
- dexEncodedMethod.getReference(), wrapperField.getHolderType(), appView);
+ method, wrapperField.getHolderType(), appView);
CfCode cfCode;
- if (dexEncodedMethod.isFinal()) {
- finalMethods.add(dexEncodedMethod.getReference());
- continue;
- } else if (dexClass.isProgramClass()) {
+ if (dexClass.isProgramClass()) {
cfCode =
new APIConverterWrapperCfCodeProvider(
- appView,
- dexEncodedMethod.getReference(),
- wrapperField.getReference(),
- this,
- isInterface)
+ appView, method, wrapperField.getReference(), this, isInterface)
.generateCfCode();
} else {
cfCode = null;
}
- DexEncodedMethod newDexEncodedMethod =
- newSynthesizedMethod(methodToInstall, dexEncodedMethod, cfCode);
+ DexEncodedMethod newDexEncodedMethod = newSynthesizedMethod(methodToInstall, cfCode);
generatedMethods.add(newDexEncodedMethod);
}
- return finalizeWrapperMethods(generatedMethods, finalMethods);
+ return generatedMethods;
}
- private Collection<DexEncodedMethod> finalizeWrapperMethods(
- List<DexEncodedMethod> generatedMethods, Set<DexMethod> finalMethods) {
- if (finalMethods.isEmpty()) {
- return generatedMethods;
- }
- // Wrapper is invalid, no need to add the virtual methods.
- reportFinalMethodsInWrapper(finalMethods);
- return Collections.emptyList();
- }
-
- private void reportFinalMethodsInWrapper(Set<DexMethod> methods) {
- String[] methodArray =
- methods.stream().map(method -> method.holder + "#" + method.name).toArray(String[]::new);
- appView
- .options()
- .reporter
- .warning(
- new StringDiagnostic(
- "Desugared library API conversion: cannot wrap final methods "
- + Arrays.toString(methodArray)
- + ". "
- + methods.iterator().next().holder
- + " is marked as invalid and will throw a runtime exception upon conversion."));
- }
-
- DexEncodedMethod newSynthesizedMethod(
- DexMethod methodToInstall, DexEncodedMethod template, Code code) {
- MethodAccessFlags newFlags = template.accessFlags.copy();
- assert newFlags.isPublic();
- // It can happen that we wrap an abstract method, in which case the wrapping method is no
- // longer abstract.
- if (code != null) {
- newFlags.unsetAbstract();
- }
- // TODO(b/146114533): Fix inlining in synthetic methods and remove unsetBridge.
- newFlags.unsetBridge();
- newFlags.setSynthetic();
+ DexEncodedMethod newSynthesizedMethod(DexMethod methodToInstall, Code code) {
+ MethodAccessFlags newFlags =
+ MethodAccessFlags.fromSharedAccessFlags(
+ Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC, false);
+ ComputedApiLevel apiLevelForDefinition =
+ appView.enableWholeProgramOptimizations()
+ ? ComputedApiLevel.notSet()
+ : appView
+ .apiLevelCompute()
+ .computeApiLevelForDefinition(methodToInstall, factory, ComputedApiLevel.unknown());
+ // Since the method is a forwarding method, the api level for code is the same as the
+ // definition.
+ ComputedApiLevel apiLevelForCode = apiLevelForDefinition;
return DexEncodedMethod.syntheticBuilder()
.setMethod(methodToInstall)
.setAccessFlags(newFlags)
.setCode(code)
- .setApiLevelForDefinition(template.getApiLevelForDefinition())
- .setApiLevelForCode(
- code == null ? ComputedApiLevel.notSet() : template.getApiLevelForCode())
+ .setApiLevelForDefinition(apiLevelForDefinition)
+ .setApiLevelForCode(code == null ? ComputedApiLevel.notSet() : apiLevelForCode)
.build();
}
- private List<DexEncodedMethod> allImplementedMethods(DexClass clazz) {
- return allImplementedMethodsCache.computeIfAbsent(
- clazz.type, type -> internalAllImplementedMethods(clazz));
+ private Iterable<DexMethod> allImplementedMethods(DexClass clazz) {
+ if (appView.options().testing.machineDesugaredLibrarySpecification != null) {
+ return appView
+ .options()
+ .testing
+ .machineDesugaredLibrarySpecification
+ .getRewritingFlags()
+ .getWrappers()
+ .get(clazz.type);
+ }
+ List<DexEncodedMethod> dexEncodedMethods =
+ allImplementedMethodsCache.computeIfAbsent(
+ clazz.type, type -> internalAllImplementedMethods(clazz));
+ return Iterables.transform(dexEncodedMethods, m -> m.getReference());
}
private List<DexEncodedMethod> internalAllImplementedMethods(DexClass libraryClass) {
@@ -641,6 +592,7 @@
workList.add(superClass);
}
}
+ assert !Iterables.any(implementedMethods, DexEncodedMethod::isFinal);
return implementedMethods;
}
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 4077e4b..eea4697 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
@@ -6,8 +6,10 @@
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexType;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.util.IdentityHashMap;
+import java.util.List;
import java.util.Map;
public class MachineRewritingFlags {
@@ -22,13 +24,15 @@
Map<DexMethod, DexMethod> staticRetarget,
Map<DexMethod, DexMethod> nonEmulatedVirtualRetarget,
Map<DexMethod, EmulatedDispatchMethodDescriptor> emulatedVirtualRetarget,
- Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces) {
+ Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces,
+ Map<DexType, List<DexMethod>> wrappers) {
this.rewriteType = rewriteType;
this.rewriteDerivedTypeOnly = rewriteDerivedTypeOnly;
this.staticRetarget = staticRetarget;
this.nonEmulatedVirtualRetarget = nonEmulatedVirtualRetarget;
this.emulatedVirtualRetarget = emulatedVirtualRetarget;
this.emulatedInterfaces = emulatedInterfaces;
+ this.wrappers = wrappers;
}
// Rewrites all the references to the keys as well as synthetic types derived from any key.
@@ -53,6 +57,9 @@
// Emulated interface descriptors.
private final Map<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces;
+ // Wrappers and the list of methods they implement.
+ private final Map<DexType, List<DexMethod>> wrappers;
+
public Map<DexType, DexType> getRewriteType() {
return rewriteType;
}
@@ -77,6 +84,10 @@
return emulatedInterfaces;
}
+ public Map<DexType, List<DexMethod>> getWrappers() {
+ return wrappers;
+ }
+
public static class Builder {
Builder() {}
@@ -91,6 +102,7 @@
emulatedVirtualRetarget = ImmutableMap.builder();
private final ImmutableMap.Builder<DexType, EmulatedInterfaceDescriptor> emulatedInterfaces =
ImmutableMap.builder();
+ private final ImmutableMap.Builder<DexType, List<DexMethod>> wrappers = ImmutableMap.builder();
public void rewriteType(DexType src, DexType target) {
rewriteType.put(src, target);
@@ -116,6 +128,10 @@
emulatedVirtualRetarget.put(src, dest);
}
+ public void addWrapper(DexType wrapperConversion, List<DexMethod> methods) {
+ wrappers.put(wrapperConversion, ImmutableList.copyOf(methods));
+ }
+
public MachineRewritingFlags build() {
return new MachineRewritingFlags(
rewriteType,
@@ -123,7 +139,8 @@
staticRetarget.build(),
nonEmulatedVirtualRetarget.build(),
emulatedVirtualRetarget.build(),
- emulatedInterfaces.build());
+ emulatedInterfaces.build(),
+ wrappers.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 af380d4..63b903c 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
@@ -47,6 +47,7 @@
.convertEmulatedInterfaces(rewritingFlags, appInfo, builder);
new HumanToMachinePrefixConverter(appInfo)
.convertPrefixFlags(rewritingFlags, builder, synthesizedPrefix);
+ new HumanToMachineWrapperConverter(appInfo).convertWrappers(rewritingFlags, builder);
return builder.build();
}
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
new file mode 100644
index 0000000..db87890
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/desugaredlibrary/specificationconversion/HumanToMachineWrapperConverter.java
@@ -0,0 +1,75 @@
+// 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.specificationconversion;
+
+import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
+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.DexType;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.humanspecification.HumanRewritingFlags;
+import com.android.tools.r8.ir.desugar.desugaredlibrary.machinespecification.MachineRewritingFlags;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class HumanToMachineWrapperConverter {
+
+ private final AppInfoWithClassHierarchy appInfo;
+
+ public HumanToMachineWrapperConverter(AppInfoWithClassHierarchy appInfo) {
+ this.appInfo = appInfo;
+ }
+
+ public void convertWrappers(
+ HumanRewritingFlags rewritingFlags, MachineRewritingFlags.Builder builder) {
+ for (DexType wrapperConversion : rewritingFlags.getWrapperConversions()) {
+ DexClass wrapperClass = appInfo.definitionFor(wrapperConversion);
+ assert wrapperClass != null;
+ List<DexMethod> methods = allImplementedMethods(wrapperClass);
+ methods.sort(DexMethod::compareTo);
+ builder.addWrapper(wrapperConversion, methods);
+ }
+ }
+
+ private List<DexMethod> allImplementedMethods(DexClass wrapperClass) {
+ LinkedList<DexClass> workList = new LinkedList<>();
+ List<DexMethod> implementedMethods = new ArrayList<>();
+ workList.add(wrapperClass);
+ while (!workList.isEmpty()) {
+ DexClass dexClass = workList.removeFirst();
+ for (DexEncodedMethod virtualMethod : dexClass.virtualMethods()) {
+ if (!virtualMethod.isPrivateMethod()) {
+ assert virtualMethod.isProtectedMethod() || virtualMethod.isPublicMethod();
+ boolean alreadyAdded = false;
+ // This looks quadratic but given the size of the collections met in practice for
+ // desugared libraries (Max ~15) it does not matter.
+ for (DexMethod alreadyImplementedMethod : implementedMethods) {
+ if (alreadyImplementedMethod.match(virtualMethod.getReference())) {
+ alreadyAdded = true;
+ break;
+ }
+ }
+ if (!alreadyAdded) {
+ assert !virtualMethod.isFinal() : "Cannot wrap final method " + virtualMethod;
+ implementedMethods.add(virtualMethod.getReference());
+ }
+ }
+ }
+ for (DexType itf : dexClass.interfaces.values) {
+ DexClass itfClass = appInfo.definitionFor(itf);
+ if (itfClass != null) {
+ workList.add(itfClass);
+ }
+ }
+ if (dexClass.superType != appInfo.dexItemFactory().objectType) {
+ DexClass superClass = appInfo.definitionFor(dexClass.superType);
+ assert superClass != null; // Cannot be null since we started from a LibraryClass.
+ workList.add(superClass);
+ }
+ }
+ return implementedMethods;
+ }
+}