blob: 150e6c4e22edcc707cfaa03ff3b4f15be90c32f5 [file] [log] [blame]
// Copyright (c) 2021, 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.apiconversion;
import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.generateTrackDesugaredAPIWarnings;
import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.isAPIConversionSyntheticType;
import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.CfCode;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexMethod;
import com.android.tools.r8.graph.DexProgramClass;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaring;
import com.android.tools.r8.ir.desugar.CfPostProcessingDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
import com.android.tools.r8.ir.synthetic.DesugaredLibraryAPIConversionCfCodeProvider.APICallbackWrapperCfCodeProvider;
import com.android.tools.r8.utils.OptionalBool;
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
public class DesugaredLibraryAPICallbackSynthesizer implements CfPostProcessingDesugaring {
private final AppView<?> appView;
private final DexItemFactory factory;
private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizor;
private final Set<DexMethod> trackedCallBackAPIs;
public DesugaredLibraryAPICallbackSynthesizer(AppView<?> appView) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.wrapperSynthesizor = new DesugaredLibraryWrapperSynthesizer(appView);
if (appView.options().testing.trackDesugaredAPIConversions) {
trackedCallBackAPIs = Sets.newConcurrentHashSet();
} else {
trackedCallBackAPIs = null;
}
}
// TODO(b/191656218): Consider parallelizing post processing.
@Override
public void postProcessingDesugaring(
Collection<DexProgramClass> programClasses,
CfPostProcessingDesugaringEventConsumer eventConsumer,
ExecutorService executorService) {
assert noPendingWrappersOrConversions();
for (DexProgramClass clazz : programClasses) {
if (!appView.isAlreadyLibraryDesugared(clazz)) {
ArrayList<DexEncodedMethod> callbacks = new ArrayList<>();
// We note that methods requiring callbacks are library overrides, therefore, they should
// always be live in R8.
for (ProgramMethod virtualProgramMethod : clazz.virtualProgramMethods()) {
if (shouldRegisterCallback(virtualProgramMethod)) {
if (trackedCallBackAPIs != null) {
trackedCallBackAPIs.add(virtualProgramMethod.getReference());
}
ProgramMethod callback =
generateCallbackMethod(
virtualProgramMethod.getDefinition(),
virtualProgramMethod.getHolder(),
eventConsumer);
callbacks.add(callback.getDefinition());
}
}
if (!callbacks.isEmpty()) {
clazz.addVirtualMethods(callbacks);
}
}
}
assert noPendingWrappersOrConversions();
generateTrackingWarnings();
}
private boolean noPendingWrappersOrConversions() {
for (DexProgramClass pendingSyntheticClass :
appView.getSyntheticItems().getPendingSyntheticClasses()) {
assert !isAPIConversionSyntheticType(pendingSyntheticClass.type, wrapperSynthesizor, appView);
}
return true;
}
public boolean shouldRegisterCallback(ProgramMethod method) {
// Any override of a library method can be called by the library.
// We duplicate the method to have a vivified type version callable by the library and
// a type version callable by the program. We need to add the vivified version to the rootset
// as it is actually overriding a library method (after changing the vivified type to the core
// library type), but the enqueuer cannot see that.
// To avoid too much computation we first look if the method would need to be rewritten if
// it would override a library method, then check if it overrides a library method.
DexEncodedMethod definition = method.getDefinition();
if (definition.isPrivateMethod()
|| definition.isStatic()
|| definition.isAbstract()
|| definition.isLibraryMethodOverride().isFalse()) {
return false;
}
if (!appView.rewritePrefix.hasRewrittenTypeInSignature(definition.getProto(), appView)
|| appView
.options()
.desugaredLibrarySpecification
.getEmulateLibraryInterface()
.containsKey(method.getHolderType())) {
return false;
}
// In R8 we should be in the enqueuer, therefore we can duplicate a default method and both
// methods will be desugared.
// In D8, this happens after interface method desugaring, we cannot introduce new default
// methods, but we do not need to since this is a library override (invokes will resolve) and
// all implementors have been enhanced with a forwarding method which will be duplicated.
if (!appView.enableWholeProgramOptimizations()) {
if (method.getHolder().isInterface()
&& method.getDefinition().isDefaultMethod()
&& (!appView.options().canUseDefaultAndStaticInterfaceMethods()
|| appView.options().isDesugaredLibraryCompilation())) {
return false;
}
}
if (!appView.options().desugaredLibrarySpecification.supportAllCallbacksFromLibrary()
&& appView.options().isDesugaredLibraryCompilation()) {
return false;
}
return overridesNonFinalLibraryMethod(method);
}
private boolean overridesNonFinalLibraryMethod(ProgramMethod method) {
// We look up everywhere to see if there is a supertype/interface implementing the method...
DexProgramClass holder = method.getHolder();
WorkList<DexType> workList = WorkList.newIdentityWorkList();
workList.addIfNotSeen(holder.interfaces.values);
boolean foundOverrideToRewrite = false;
// There is no methods with desugared types on Object.
if (holder.superType != factory.objectType) {
workList.addIfNotSeen(holder.superType);
}
while (workList.hasNext()) {
DexType current = workList.next();
DexClass dexClass = appView.definitionFor(current);
if (dexClass == null) {
continue;
}
workList.addIfNotSeen(dexClass.interfaces.values);
if (dexClass.superType != factory.objectType) {
workList.addIfNotSeen(dexClass.superType);
}
if (!dexClass.isLibraryClass() && !appView.options().isDesugaredLibraryCompilation()) {
continue;
}
if (!shouldGenerateCallbacksForEmulateInterfaceAPIs(dexClass)) {
continue;
}
DexEncodedMethod dexEncodedMethod = dexClass.lookupVirtualMethod(method.getReference());
if (dexEncodedMethod != null) {
// In this case, the object will be wrapped.
if (appView.rewritePrefix.hasRewrittenType(dexClass.type, appView)) {
return false;
}
if (dexEncodedMethod.isFinal()) {
// We do not introduce overrides of final methods, in this case, the runtime always
// execute the default behavior in the final method.
return false;
}
foundOverrideToRewrite = true;
}
}
return foundOverrideToRewrite;
}
private boolean shouldGenerateCallbacksForEmulateInterfaceAPIs(DexClass dexClass) {
if (appView.options().desugaredLibrarySpecification.supportAllCallbacksFromLibrary()) {
return true;
}
Map<DexType, DexType> emulateLibraryInterfaces =
appView.options().desugaredLibrarySpecification.getEmulateLibraryInterface();
return !(emulateLibraryInterfaces.containsKey(dexClass.type)
|| emulateLibraryInterfaces.containsValue(dexClass.type));
}
private ProgramMethod generateCallbackMethod(
DexEncodedMethod originalMethod,
DexProgramClass clazz,
DesugaredLibraryAPICallbackSynthesizorEventConsumer eventConsumer) {
DexMethod methodToInstall =
methodWithVivifiedTypeInSignature(originalMethod.getReference(), clazz.type, appView);
CfCode cfCode =
new APICallbackWrapperCfCodeProvider(
appView,
originalMethod.getReference(),
wrapperSynthesizor,
clazz.isInterface(),
eventConsumer)
.generateCfCode();
DexEncodedMethod newMethod = wrapperSynthesizor.newSynthesizedMethod(methodToInstall, cfCode);
newMethod.setCode(cfCode, appView);
if (originalMethod.isLibraryMethodOverride().isTrue()) {
newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
}
ProgramMethod callback = new ProgramMethod(clazz, newMethod);
assert eventConsumer != null;
eventConsumer.acceptAPIConversionCallback(callback);
return callback;
}
private void generateTrackingWarnings() {
generateTrackDesugaredAPIWarnings(trackedCallBackAPIs, "callback ", appView);
}
}