blob: 79070373331d4c7aa63bff9f556288f755226988 [file] [log] [blame]
// 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.apiconversion;
import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.methodWithVivifiedTypeInSignature;
import static com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryAPIConverter.vivifiedTypeFor;
import com.android.tools.r8.cf.code.CfArrayLoad;
import com.android.tools.r8.cf.code.CfArrayStore;
import com.android.tools.r8.cf.code.CfCheckCast;
import com.android.tools.r8.cf.code.CfConstNumber;
import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.cf.code.CfInvoke;
import com.android.tools.r8.cf.code.CfLoad;
import com.android.tools.r8.cf.code.CfNewArray;
import com.android.tools.r8.cf.code.CfReturn;
import com.android.tools.r8.cf.code.CfStackInstruction;
import com.android.tools.r8.cf.code.CfStackInstruction.Opcode;
import com.android.tools.r8.cf.code.CfStore;
import com.android.tools.r8.contexts.CompilationContext.MainThreadContext;
import com.android.tools.r8.contexts.CompilationContext.MethodProcessingContext;
import com.android.tools.r8.contexts.CompilationContext.UniqueContext;
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.DexField;
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.DexProto;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.graph.MethodAccessFlags;
import com.android.tools.r8.graph.ProgramMethod;
import com.android.tools.r8.ir.code.MemberType;
import com.android.tools.r8.ir.code.ValueType;
import com.android.tools.r8.ir.desugar.CfInstructionDesugaringEventConsumer;
import com.android.tools.r8.ir.desugar.FreshLocalProvider;
import com.android.tools.r8.ir.desugar.LocalStackAllocator;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPICallbackSynthesizorEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryAPIConverterEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryClasspathWrapperSynthesizeEventConsumer;
import com.android.tools.r8.ir.desugar.desugaredlibrary.apiconversion.DesugaredLibraryWrapperSynthesizerEventConsumer.DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer;
import com.android.tools.r8.ir.synthetic.apiconverter.APIConversionCfCodeProvider;
import com.android.tools.r8.ir.synthetic.apiconverter.EqualsCfCodeProvider;
import com.android.tools.r8.ir.synthetic.apiconverter.HashCodeCfCodeProvider;
import com.android.tools.r8.utils.OptionalBool;
import java.util.ArrayList;
import java.util.Collection;
import java.util.function.BiFunction;
import java.util.function.Supplier;
import org.objectweb.asm.Opcodes;
public class DesugaredLibraryConversionCfProvider {
private final AppView<?> appView;
private final DexItemFactory factory;
private final DesugaredLibraryWrapperSynthesizer wrapperSynthesizer;
public DesugaredLibraryConversionCfProvider(
AppView<?> appView, DesugaredLibraryWrapperSynthesizer wrapperSynthesizer) {
this.appView = appView;
this.factory = appView.dexItemFactory();
this.wrapperSynthesizer = wrapperSynthesizer;
}
public DexEncodedMethod generateWrapperConversionWithoutCode(
DexMethod method, DexField wrapperField) {
DexMethod methodToInstall =
methodWithVivifiedTypeInSignature(method, wrapperField.getHolderType(), appView);
return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, null);
}
public DexEncodedMethod generateWrapperConversion(
DexMethod method,
DexField wrapperField,
DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
Supplier<UniqueContext> contextSupplier) {
DexClass holderClass = appView.definitionFor(method.getHolderType());
assert holderClass != null || appView.options().isDesugaredLibraryCompilation();
boolean isInterface = holderClass == null || holderClass.isInterface();
ProgramMethod context = resolveContext(method, isInterface);
DexMethod returnConversion =
computeReturnConversion(method, true, eventConsumer, context, contextSupplier);
DexMethod[] parameterConversions =
computeParameterConversions(method, false, eventConsumer, context, contextSupplier);
DexMethod methodToInstall =
convertedMethod(
method, false, returnConversion, parameterConversions, wrapperField.getHolderType());
CfCode cfCode =
new APIConversionCfCodeProvider(
appView,
wrapperField.getHolderType(),
method,
isInterface,
returnConversion,
parameterConversions,
wrapperField)
.generateCfCode();
return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode);
}
public DexEncodedMethod generateWrapperHashCode(DexField wrapperField) {
return wrapperSynthesizer.newSynthesizedMethod(
appView
.dexItemFactory()
.createMethod(
wrapperField.getHolderType(),
appView.dexItemFactory().createProto(appView.dexItemFactory().intType),
appView.dexItemFactory().hashCodeMethodName),
new HashCodeCfCodeProvider(appView, wrapperField.getHolderType(), wrapperField)
.generateCfCode());
}
public DexEncodedMethod generateWrapperEquals(DexField wrapperField) {
return wrapperSynthesizer.newSynthesizedMethod(
appView
.dexItemFactory()
.createMethod(
wrapperField.getHolderType(),
appView
.dexItemFactory()
.createProto(
appView.dexItemFactory().booleanType, appView.dexItemFactory().objectType),
appView.dexItemFactory().equalsMethodName),
new EqualsCfCodeProvider(appView, wrapperField.getHolderType(), wrapperField)
.generateCfCode());
}
public DexEncodedMethod generateVivifiedWrapperConversionWithoutCode(
DexMethod method, DexField wrapperField) {
DexMethod methodToInstall =
factory.createMethod(wrapperField.getHolderType(), method.proto, method.name);
return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, null);
}
public DexEncodedMethod generateVivifiedWrapperConversion(
DexMethod method,
DexField wrapperField,
DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
Supplier<UniqueContext> contextSupplier) {
DexMethod methodToInstall =
factory.createMethod(wrapperField.getHolderType(), method.proto, method.name);
DexClass holderClass = appView.definitionFor(method.getHolderType());
boolean isInterface;
if (holderClass == null) {
assert appView
.options()
.machineDesugaredLibrarySpecification
.isEmulatedInterfaceRewrittenType(method.getHolderType());
isInterface = true;
} else {
isInterface = holderClass.isInterface();
}
ProgramMethod context = resolveContext(method, isInterface);
DexMethod returnConversion =
computeReturnConversion(method, false, eventConsumer, context, contextSupplier);
DexMethod[] parameterConversions =
computeParameterConversions(method, true, eventConsumer, context, contextSupplier);
DexType newHolder =
appView.typeRewriter.hasRewrittenType(method.getHolderType(), appView)
? vivifiedTypeFor(method.getHolderType(), appView)
: method.getHolderType();
DexMethod forwardMethod =
convertedMethod(method, true, returnConversion, parameterConversions, newHolder);
CfCode cfCode =
new APIConversionCfCodeProvider(
appView,
wrapperField.getHolderType(),
forwardMethod,
isInterface,
returnConversion,
parameterConversions,
wrapperField)
.generateCfCode();
return wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode);
}
public ProgramMethod generateCallbackConversion(
ProgramMethod method,
DesugaredLibraryAPICallbackSynthesizorEventConsumer eventConsumer,
MainThreadContext context) {
DexProgramClass clazz = method.getHolder();
DexMethod returnConversion =
computeReturnConversion(
method.getReference(),
true,
eventConsumer,
method,
() -> context.createUniqueContext(clazz));
DexMethod[] parameterConversions =
computeParameterConversions(
method.getReference(),
false,
eventConsumer,
method,
() -> context.createUniqueContext(clazz));
DexMethod methodToInstall =
convertedMethod(method.getReference(), false, returnConversion, parameterConversions);
CfCode cfCode =
new APIConversionCfCodeProvider(
appView,
method.getHolderType(),
method.getReference(),
clazz.isInterface(),
returnConversion,
parameterConversions)
.generateCfCode();
DexEncodedMethod newMethod = wrapperSynthesizer.newSynthesizedMethod(methodToInstall, cfCode);
newMethod.setCode(cfCode, DexEncodedMethod.NO_PARAMETER_INFO);
if (method.getDefinition().isLibraryMethodOverride().isTrue()) {
newMethod.setLibraryMethodOverride(OptionalBool.TRUE);
}
ProgramMethod callback = new ProgramMethod(clazz, newMethod);
assert eventConsumer != null;
eventConsumer.acceptAPIConversionCallback(callback);
return callback;
}
public ProgramMethod generateOutlinedAPIConversion(
CfInvoke invoke,
DesugaredLibraryAPIConverterEventConsumer eventConsumer,
ProgramMethod context,
MethodProcessingContext methodProcessingContext) {
DexMethod method = invoke.getMethod();
DexProto newProto =
invoke.isInvokeStatic()
? method.proto
: factory.prependTypeToProto(method.getHolderType(), method.getProto());
DexMethod returnConversion =
computeReturnConversion(
method, false, eventConsumer, context, methodProcessingContext::createUniqueContext);
DexMethod[] parameterConversions =
computeParameterConversions(
method, true, eventConsumer, context, methodProcessingContext::createUniqueContext);
ProgramMethod outline =
appView
.getSyntheticItems()
.createMethod(
kinds -> kinds.API_CONVERSION,
methodProcessingContext.createUniqueContext(),
appView,
builder ->
builder
.setProto(newProto)
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
// Will be traced by the enqueuer.
.disableAndroidApiLevelCheck()
.setCode(
methodSignature ->
new APIConversionCfCodeProvider(
appView,
methodSignature.holder,
convertedMethod(
method, true, returnConversion, parameterConversions),
invoke.isInterface(),
returnConversion,
parameterConversions,
invoke.getOpcode())
.generateCfCode()));
eventConsumer.acceptAPIConversion(outline);
return outline;
}
public Collection<CfInstruction> generateInlinedAPIConversion(
CfInvoke invoke,
MethodProcessingContext methodProcessingContext,
FreshLocalProvider freshLocalProvider,
LocalStackAllocator localStackAllocator,
CfInstructionDesugaringEventConsumer eventConsumer,
ProgramMethod context) {
DexMethod invokedMethod = invoke.getMethod();
DexMethod returnConversion =
computeReturnConversion(
invokedMethod,
false,
eventConsumer,
context,
methodProcessingContext::createUniqueContext);
DexMethod[] parameterConversions =
computeParameterConversions(
invokedMethod,
true,
eventConsumer,
context,
methodProcessingContext::createUniqueContext);
int parameterSize = invokedMethod.getParameters().size();
ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
if (parameterSize != 0) {
// If only the last 2 parameters require conversion, we do everything inlined.
// If other parameters require conversion, we outline the parameter conversion but keep the
// API
// call inlined. The returned value is always converted inlined.
boolean requireOutlinedParameterConversion = false;
for (int i = 0; i < parameterConversions.length - 2; i++) {
requireOutlinedParameterConversion |= parameterConversions[i] != null;
}
// We cannot use the swap instruction if the last parameter is wide.
requireOutlinedParameterConversion |=
invokedMethod.getParameters().get(parameterSize - 1).isWideType();
if (requireOutlinedParameterConversion) {
addOutlineParameterConversionInstructions(
parameterConversions,
cfInstructions,
methodProcessingContext,
invokedMethod,
freshLocalProvider,
localStackAllocator,
eventConsumer);
} else {
addInlineParameterConversionInstructions(
parameterConversions, cfInstructions, invokedMethod);
}
}
DexMethod convertedMethod =
convertedMethod(invokedMethod, true, returnConversion, parameterConversions);
cfInstructions.add(new CfInvoke(invoke.getOpcode(), convertedMethod, invoke.isInterface()));
if (returnConversion != null) {
cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, returnConversion, false));
}
return cfInstructions;
}
// The parameters are converted and returned in an array of converted parameters. The parameter
// array then needs to be unwrapped at the call site.
private void addOutlineParameterConversionInstructions(
DexMethod[] parameterConversions,
ArrayList<CfInstruction> cfInstructions,
MethodProcessingContext methodProcessingContext,
DexMethod invokedMethod,
FreshLocalProvider freshLocalProvider,
LocalStackAllocator localStackAllocator,
CfInstructionDesugaringEventConsumer eventConsumer) {
localStackAllocator.allocateLocalStack(4);
DexProto newProto =
appView
.dexItemFactory()
.createProto(
appView.dexItemFactory().objectArrayType, invokedMethod.getParameters().values);
ProgramMethod parameterConversion =
appView
.getSyntheticItems()
.createMethod(
kinds -> kinds.API_CONVERSION_PARAMETERS,
methodProcessingContext.createUniqueContext(),
appView,
builder ->
builder
.setProto(newProto)
.setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
// Will be traced by the enqueuer.
.disableAndroidApiLevelCheck()
.setCode(
methodSignature ->
computeParameterConversionCfCode(
methodSignature.holder, invokedMethod, parameterConversions)));
eventConsumer.acceptAPIConversion(parameterConversion);
cfInstructions.add(
new CfInvoke(Opcodes.INVOKESTATIC, parameterConversion.getReference(), false));
int arrayLocal = freshLocalProvider.getFreshLocal(ValueType.OBJECT.requiredRegisters());
cfInstructions.add(new CfStore(ValueType.OBJECT, arrayLocal));
for (int i = 0; i < parameterConversions.length; i++) {
cfInstructions.add(new CfLoad(ValueType.OBJECT, arrayLocal));
cfInstructions.add(new CfConstNumber(i, ValueType.INT));
DexType parameterType =
parameterConversions[i] != null
? parameterConversions[i].getReturnType()
: invokedMethod.getParameter(i);
cfInstructions.add(new CfArrayLoad(MemberType.OBJECT));
if (parameterType.isPrimitiveType()) {
cfInstructions.add(new CfCheckCast(factory.getBoxedForPrimitiveType(parameterType)));
DexMethod method = appView.dexItemFactory().getUnboxPrimitiveMethod(parameterType);
cfInstructions.add(new CfInvoke(Opcodes.INVOKEVIRTUAL, method, false));
} else {
cfInstructions.add(new CfCheckCast(parameterType));
}
}
}
private CfCode computeParameterConversionCfCode(
DexType holder, DexMethod invokedMethod, DexMethod[] parameterConversions) {
ArrayList<CfInstruction> cfInstructions = new ArrayList<>();
cfInstructions.add(new CfConstNumber(parameterConversions.length, ValueType.INT));
cfInstructions.add(new CfNewArray(factory.objectArrayType));
int stackIndex = 0;
for (int i = 0; i < invokedMethod.getArity(); i++) {
cfInstructions.add(new CfStackInstruction(Opcode.Dup));
cfInstructions.add(new CfConstNumber(i, ValueType.INT));
DexType param = invokedMethod.getParameter(i);
cfInstructions.add(new CfLoad(ValueType.fromDexType(param), stackIndex));
if (parameterConversions[i] != null) {
cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, parameterConversions[i], false));
}
if (param.isPrimitiveType()) {
DexMethod method = appView.dexItemFactory().getBoxPrimitiveMethod(param);
cfInstructions.add(new CfInvoke(Opcodes.INVOKESTATIC, method, false));
}
cfInstructions.add(new CfArrayStore(MemberType.OBJECT));
if (param == appView.dexItemFactory().longType
|| param == appView.dexItemFactory().doubleType) {
stackIndex++;
}
stackIndex++;
}
cfInstructions.add(new CfReturn(ValueType.OBJECT));
return new CfCode(holder, stackIndex + 4, stackIndex, cfInstructions);
}
private void addInlineParameterConversionInstructions(
DexMethod[] parameterConversions,
ArrayList<CfInstruction> cfInstructions,
DexMethod invokedMethod) {
if (parameterConversions.length > 0
&& parameterConversions[parameterConversions.length - 1] != null) {
cfInstructions.add(
new CfInvoke(
Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 1], false));
}
if (parameterConversions.length > 1
&& parameterConversions[parameterConversions.length - 2] != null) {
assert !invokedMethod
.getParameters()
.get(invokedMethod.getParameters().size() - 1)
.isWideType();
cfInstructions.add(new CfStackInstruction(Opcode.Swap));
cfInstructions.add(
new CfInvoke(
Opcodes.INVOKESTATIC, parameterConversions[parameterConversions.length - 2], false));
cfInstructions.add(new CfStackInstruction(Opcode.Swap));
}
}
private ProgramMethod resolveContext(DexMethod method, boolean isInterface) {
// The context is provided to improve the error message with origin and position. If the method
// is missing from the input, the context is null but the code runs correctly.
return appView
.appInfoForDesugaring()
.resolveMethodLegacy(method, isInterface)
.getResolvedProgramMethod();
}
private DexMethod computeReturnConversion(
DexMethod invokedMethod,
boolean destIsVivified,
DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
ProgramMethod context,
Supplier<UniqueContext> contextSupplier) {
return internalComputeReturnConversion(
invokedMethod,
(returnType, apiConversionCollection) ->
wrapperSynthesizer.ensureConversionMethod(
returnType,
destIsVivified,
apiConversionCollection,
eventConsumer,
contextSupplier),
context);
}
private DexMethod computeReturnConversion(
DexMethod invokedMethod,
boolean destIsVivified,
DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
ProgramMethod context,
Supplier<UniqueContext> contextSupplier) {
return internalComputeReturnConversion(
invokedMethod,
(returnType, apiConversionCollection) ->
wrapperSynthesizer.getExistingProgramConversionMethod(
returnType,
destIsVivified,
apiConversionCollection,
eventConsumer,
contextSupplier),
context);
}
private DexMethod internalComputeReturnConversion(
DexMethod invokedMethod,
BiFunction<DexType, DexMethod, DexMethod> methodSupplier,
ProgramMethod context) {
DexType returnType = invokedMethod.proto.returnType;
DexMethod apiGenericTypesConversion = getReturnApiGenericConversion(invokedMethod);
if (wrapperSynthesizer.shouldConvert(
returnType, apiGenericTypesConversion, invokedMethod, context)) {
return methodSupplier.apply(returnType, apiGenericTypesConversion);
}
return null;
}
private DexMethod[] computeParameterConversions(
DexMethod invokedMethod,
boolean destIsVivified,
DesugaredLibraryClasspathWrapperSynthesizeEventConsumer eventConsumer,
ProgramMethod context,
Supplier<UniqueContext> contextSupplier) {
return internalComputeParameterConversions(
invokedMethod,
wrapperSynthesizer,
(argType, apiGenericTypesConversion) ->
wrapperSynthesizer.ensureConversionMethod(
argType, destIsVivified, apiGenericTypesConversion, eventConsumer, contextSupplier),
context);
}
private DexMethod[] computeParameterConversions(
DexMethod invokedMethod,
boolean destIsVivified,
DesugaredLibraryL8ProgramWrapperSynthesizerEventConsumer eventConsumer,
ProgramMethod context,
Supplier<UniqueContext> contextSupplier) {
return internalComputeParameterConversions(
invokedMethod,
wrapperSynthesizer,
(argType, apiGenericTypesConversion) ->
wrapperSynthesizer.getExistingProgramConversionMethod(
argType, destIsVivified, apiGenericTypesConversion, eventConsumer, contextSupplier),
context);
}
private DexMethod[] internalComputeParameterConversions(
DexMethod invokedMethod,
DesugaredLibraryWrapperSynthesizer wrapperSynthesizor,
BiFunction<DexType, DexMethod, DexMethod> methodSupplier,
ProgramMethod context) {
DexMethod[] parameterConversions = new DexMethod[invokedMethod.getArity()];
DexType[] parameters = invokedMethod.proto.parameters.values;
for (int i = 0; i < parameters.length; i++) {
DexMethod apiGenericTypesConversion = getApiGenericConversion(invokedMethod, i);
DexType argType = parameters[i];
if (wrapperSynthesizor.shouldConvert(
argType, apiGenericTypesConversion, invokedMethod, context)) {
parameterConversions[i] = methodSupplier.apply(argType, apiGenericTypesConversion);
}
}
return parameterConversions;
}
public DexMethod getReturnApiGenericConversion(DexMethod method) {
return getApiGenericConversion(method, method.getArity());
}
public DexMethod getApiGenericConversion(DexMethod method, int parameterIndex) {
DexMethod[] conversions =
appView
.options()
.machineDesugaredLibrarySpecification
.getApiGenericConversion()
.get(method);
return conversions == null ? null : conversions[parameterIndex];
}
private DexMethod convertedMethod(
DexMethod method,
boolean parameterDestIsVivified,
DexMethod returnConversion,
DexMethod[] parameterConversions) {
return convertedMethod(
method,
parameterDestIsVivified,
returnConversion,
parameterConversions,
method.getHolderType());
}
private DexMethod convertedMethod(
DexMethod method,
boolean parameterDestIsVivified,
DexMethod returnConversion,
DexMethod[] parameterConversions,
DexType newHolder) {
DexType newReturnType =
returnConversion != null
? parameterDestIsVivified
? returnConversion.getParameter(0)
: returnConversion.getReturnType()
: method.getReturnType();
DexType[] newParameterTypes = new DexType[parameterConversions.length];
for (int i = 0; i < parameterConversions.length; i++) {
newParameterTypes[i] =
parameterConversions[i] != null
? parameterDestIsVivified
? parameterConversions[i].getReturnType()
: parameterConversions[i].getParameter(0)
: method.getParameter(i);
}
DexMethod convertedAPI =
appView
.dexItemFactory()
.createMethod(
newHolder,
appView.dexItemFactory().createProto(newReturnType, newParameterTypes),
method.name);
assert convertedAPI == methodWithVivifiedTypeInSignature(method, newHolder, appView)
|| appView
.options()
.machineDesugaredLibrarySpecification
.getApiGenericConversion()
.containsKey(method)
|| invalidType(method, returnConversion, parameterConversions, appView) != null;
return convertedAPI;
}
private static DexType invalidType(
DexMethod invokedMethod,
DexMethod returnConversion,
DexMethod[] parameterConversions,
AppView<?> appView) {
DexMethod convertedMethod =
methodWithVivifiedTypeInSignature(invokedMethod, invokedMethod.holder, appView);
if (invokedMethod.getReturnType() != convertedMethod.getReturnType()
&& returnConversion == null) {
return invokedMethod.getReturnType();
}
for (int i = 0; i < invokedMethod.getArity(); i++) {
if (invokedMethod.getParameter(i) != convertedMethod.getParameter(i)
&& parameterConversions[i] == null) {
return invokedMethod.getParameter(i);
}
}
return null;
}
}