blob: 0922cd249cbf509a03857fc72f12acfd5ad15e40 [file] [log] [blame]
// Copyright (c) 2019, 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;
import com.android.tools.r8.graph.AppView;
import com.android.tools.r8.graph.DexClass;
import com.android.tools.r8.graph.DexItemFactory;
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.ir.analysis.type.Nullability;
import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
import com.android.tools.r8.ir.code.Invoke;
import com.android.tools.r8.ir.code.InvokeMethod;
import com.android.tools.r8.ir.code.InvokeStatic;
import com.android.tools.r8.ir.code.Value;
import com.android.tools.r8.utils.DescriptorUtils;
import com.android.tools.r8.utils.StringDiagnostic;
import java.util.Collections;
// TODO(b/134732760): In progress.
// I convert library calls with desugared parameters/return values so they can work normally.
// In the JSON of the desugared library, one can specify conversions between desugared and
// non-desugared types. If no conversion is specified, D8/R8 simply generate wrapper classes around
// the types. Wrappers induce both memory and runtime performance overhead. Wrappers overload
// all potential called APIs.
// Since many types are going to be rewritten, I also need to change the signature of the method
// called so that they are still called with the original types. Hence the vivified types.
// Given a type from the library, the prefix rewriter rewrites (->) as follow:
// vivifiedType -> type;
// type -> desugarType;
// No vivified types can be present in the compiled program (will necessarily be rewritten).
// DesugarType is only a rewritten type (generated through rewriting of type).
// The type, from the library, may either be rewritten to the desugarType,
// or be a rewritten type (generated through rewriting of vivifiedType).
public class DesugaredLibraryAPIConverter {
private static final String VIVIFIED_PREFIX = "$-vivified-$.";
private final AppView<?> appView;
private final DexItemFactory factory;
public DesugaredLibraryAPIConverter(AppView<?> appView) {
this.appView = appView;
this.factory = appView.dexItemFactory();
}
public void desugar(IRCode code) {
// TODO(b/134732760): The current code does not catch library calls into a program override
// which gets rewritten. If method signature has rewritten types and method overrides library,
// I should convert back.
InstructionListIterator iterator = code.instructionListIterator();
while (iterator.hasNext()) {
Instruction instruction = iterator.next();
if (!instruction.isInvokeMethod()) {
continue;
}
InvokeMethod invokeMethod = instruction.asInvokeMethod();
DexMethod invokedMethod = invokeMethod.getInvokedMethod();
// Rewritting is required only on calls to library methods which are not desugared.
if (appView.rewritePrefix.hasRewrittenType(invokedMethod.holder)
|| invokedMethod.holder.isArrayType()) {
continue;
}
DexClass dexClass = appView.definitionFor(invokedMethod.holder);
if (dexClass == null || !dexClass.isLibraryClass()) {
continue;
}
if (appView.rewritePrefix.hasRewrittenType(invokedMethod.proto.returnType)) {
addReturnConversion(code, invokeMethod, iterator);
}
for (int i = 0; i < invokedMethod.proto.parameters.values.length; i++) {
DexType argType = invokedMethod.proto.parameters.values[i];
if (appView.rewritePrefix.hasRewrittenType(argType)) {
addParameterConversion(code, invokeMethod, iterator, argType, i);
}
}
}
}
private void warnInvalidInvoke(DexType type, DexMethod invokedMethod, String debugString) {
DexType desugaredType = appView.rewritePrefix.rewrittenType(type);
appView
.options()
.reporter
.warning(
new StringDiagnostic(
"Invoke to "
+ invokedMethod.holder
+ "#"
+ invokedMethod.name
+ " may not work correctly at runtime ("
+ debugString
+ " type "
+ desugaredType
+ " is a desugared type)."));
}
private DexType vivifiedTypeFor(DexType type) {
DexType vivifiedType =
factory.createType(DescriptorUtils.javaTypeToDescriptor(VIVIFIED_PREFIX + type.toString()));
appView.rewritePrefix.rewriteType(vivifiedType, type);
return vivifiedType;
}
private void addParameterConversion(
IRCode code,
InvokeMethod invokeMethod,
InstructionListIterator iterator,
DexType argType,
int parameter) {
if (!appView
.options()
.desugaredLibraryConfiguration
.getCustomConversions()
.containsKey(argType)) {
// TODO(b/134732760): Add Wrapper Conversions.
warnInvalidInvoke(argType, invokeMethod.getInvokedMethod(), "parameter");
return;
}
Value inValue = invokeMethod.inValues().get(parameter);
DexType argVivifiedType = vivifiedTypeFor(argType);
DexType conversionHolder =
appView.options().desugaredLibraryConfiguration.getCustomConversions().get(argType);
// ConversionType has static method "type convert(rewrittenType)".
// But everything is going to be rewritten, so we need to call "vivifiedType convert(type)".
DexMethod conversionMethod =
factory.createMethod(
conversionHolder,
factory.createProto(argVivifiedType, argType),
factory.convertMethodName);
Value convertedValue =
code.createValue(
TypeLatticeElement.fromDexType(
argVivifiedType, inValue.getTypeLattice().nullability(), appView));
InvokeStatic conversionInstruction =
new InvokeStatic(conversionMethod, convertedValue, Collections.singletonList(inValue));
conversionInstruction.setPosition(invokeMethod.getPosition());
iterator.previous();
iterator.add(conversionInstruction);
iterator.next();
// Rewrite invoke (signature and inValue to rewrite).
DexMethod newDexMethod =
dexMethodWithDifferentParameter(
invokeMethod.getInvokedMethod(), argVivifiedType, parameter);
Invoke newInvokeMethod =
Invoke.create(
invokeMethod.getType(),
newDexMethod,
newDexMethod.proto,
invokeMethod.outValue(),
invokeMethod.inValues());
newInvokeMethod.replaceValue(parameter, conversionInstruction.outValue());
iterator.replaceCurrentInstruction(newInvokeMethod);
}
private void addReturnConversion(
IRCode code, InvokeMethod invokeMethod, InstructionListIterator iterator) {
DexType returnType = invokeMethod.getReturnType();
if (!appView
.options()
.desugaredLibraryConfiguration
.getCustomConversions()
.containsKey(returnType)) {
// TODO(b/134732760): Add Wrapper Conversions.
warnInvalidInvoke(returnType, invokeMethod.getInvokedMethod(), "return");
return;
}
DexType returnVivifiedType = vivifiedTypeFor(returnType);
DexType conversionHolder =
appView.options().desugaredLibraryConfiguration.getCustomConversions().get(returnType);
// ConversionType has static method "rewrittenType convert(type)".
// But everything is going to be rewritten, so we need to call "type convert(vivifiedType)".
DexMethod conversionMethod =
factory.createMethod(
conversionHolder,
factory.createProto(returnType, returnVivifiedType),
factory.convertMethodName);
Value convertedValue =
code.createValue(
TypeLatticeElement.fromDexType(returnType, Nullability.maybeNull(), appView));
invokeMethod.outValue().replaceUsers(convertedValue);
InvokeStatic conversionInstruction =
new InvokeStatic(
conversionMethod, convertedValue, Collections.singletonList(invokeMethod.outValue()));
conversionInstruction.setPosition(invokeMethod.getPosition());
// Rewrite invoke (signature to rewrite).
DexMethod newDexMethod =
dexMethodWithDifferentReturn(invokeMethod.getInvokedMethod(), returnVivifiedType);
Invoke newInvokeMethod =
Invoke.create(
invokeMethod.getType(),
newDexMethod,
newDexMethod.proto,
invokeMethod.outValue(),
invokeMethod.inValues());
iterator.replaceCurrentInstruction(newInvokeMethod);
iterator.add(conversionInstruction);
}
private DexMethod dexMethodWithDifferentParameter(
DexMethod method, DexType newParameterType, int parameter) {
DexType[] newParameters = method.proto.parameters.values.clone();
newParameters[parameter] = newParameterType;
DexProto newProto = factory.createProto(method.proto.returnType, newParameters);
return factory.createMethod(method.holder, newProto, method.name);
}
private DexMethod dexMethodWithDifferentReturn(DexMethod method, DexType newReturnType) {
DexProto newProto = factory.createProto(newReturnType, method.proto.parameters.values);
return factory.createMethod(method.holder, newProto, method.name);
}
}