| // 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); |
| } |
| } |