| // Copyright (c) 2017, 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.errors.Unreachable; |
| 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.DexProto; |
| import com.android.tools.r8.graph.DexType; |
| import com.android.tools.r8.ir.code.Invoke; |
| import com.android.tools.r8.ir.code.NumericType; |
| import com.android.tools.r8.ir.code.Position; |
| import com.android.tools.r8.ir.code.ValueType; |
| import com.android.tools.r8.ir.conversion.IRBuilder; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Lists; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.List; |
| |
| // Source code representing synthesized lambda main method |
| final class LambdaMainMethodSourceCode extends SynthesizedLambdaSourceCode { |
| |
| LambdaMainMethodSourceCode(LambdaClass lambda, DexMethod mainMethod, Position callerPosition) { |
| super(lambda, mainMethod, callerPosition); |
| } |
| |
| private boolean checkSignatures( |
| DexType[] captures, DexType[] enforcedParams, DexType enforcedReturnType, |
| List<DexType> implReceiverAndArgs, DexType implReturnType) { |
| List<DexType> capturesAndParams = new ArrayList<>(); |
| capturesAndParams.addAll(Lists.newArrayList(captures)); |
| capturesAndParams.addAll(Lists.newArrayList(enforcedParams)); |
| |
| int size = capturesAndParams.size(); |
| if (size != implReceiverAndArgs.size()) { |
| assert false; |
| } |
| |
| for (int i = 0; i < size; i++) { |
| if (!isSameOrAdaptableTo(capturesAndParams.get(i), implReceiverAndArgs.get(i))) { |
| assert false; |
| } |
| } |
| |
| if (!enforcedReturnType.isVoidType() |
| && !isSameOrAdaptableTo(implReturnType, enforcedReturnType)) { |
| assert false; |
| } |
| return true; |
| } |
| |
| private DexType getPrimitiveFromBoxed(DexType boxedPrimitive) { |
| return factory().getPrimitiveFromBoxed(boxedPrimitive); |
| } |
| |
| private DexType getBoxedForPrimitiveType(DexType primitive) { |
| switch (primitive.descriptor.content[0]) { |
| case 'Z': // byte |
| case 'B': // byte |
| case 'S': // short |
| case 'C': // char |
| case 'I': // int |
| case 'J': // long |
| case 'F': // float |
| case 'D': // double |
| return factory().getBoxedForPrimitiveType(primitive); |
| default: |
| throw new Unreachable("Invalid primitive type descriptor: " + primitive); |
| } |
| } |
| |
| // Checks if the types are the same OR type `a` is adaptable to type `b`. |
| private boolean isSameOrAdaptableTo(DexType a, DexType b) { |
| if (a == b) { |
| return true; |
| } |
| |
| DexItemFactory factory = factory(); |
| if (a.isArrayType()) { |
| // Arrays are only adaptable to java.lang.Object or other arrays, note that we |
| // don't check element type inheritance in the second case since we assume the |
| // input code is verifiable. |
| return b == factory.objectType || b.isArrayType(); |
| } |
| |
| if (b.isArrayType()) { |
| // If A is typed object it can be convertible to an array type. |
| return a == factory.objectType; |
| } |
| |
| if (a.isPrimitiveType()) { |
| if (b.isPrimitiveType()) { |
| return isSameOrAdaptableTo(a.descriptor.content[0], b.descriptor.content[0]); |
| } |
| |
| // `a` is primitive and `b` is a supertype of the boxed type `a`. |
| DexType boxedPrimitiveType = getBoxedForPrimitiveType(a); |
| if (b == boxedPrimitiveType || |
| b == factory.objectType || |
| b == factory.serializableType || |
| b == factory.comparableType) { |
| return true; |
| } |
| return boxedPrimitiveType != factory.boxedCharType |
| && boxedPrimitiveType != factory.boxedBooleanType |
| && b.descriptor == factory.boxedNumberDescriptor; |
| } |
| |
| if (b.isPrimitiveType()) { |
| if (a == factory.objectType) { |
| // `a` is java.lang.Object in which case we assume it represented by |
| // proper boxed type. |
| return true; |
| } |
| // `a` is a boxed type for `a*` which can be |
| // widened to primitive type `b`. |
| DexType unboxedA = getPrimitiveFromBoxed(a); |
| return unboxedA != null && |
| isSameOrAdaptableTo(unboxedA.descriptor.content[0], b.descriptor.content[0]); |
| } |
| |
| // Otherwise `a` should be a reference type derived from `b`. |
| // NOTE: we don't check `b` for being actually a supertype, since we |
| // might not have full classpath with inheritance information to do that. |
| // We assume this requirement stands and will be caught by cast |
| // instruction anyways in most cases. |
| return a.isClassType() && b.isClassType(); |
| } |
| |
| // For two primitive types `a` is adjustable to `b` iff `a` is the same as `b` |
| // or can be converted to `b` via a primitive widening conversion. |
| private boolean isSameOrAdaptableTo(byte from, byte to) { |
| if (from == to) { |
| return true; |
| } |
| switch (from) { |
| case 'B': // byte |
| return to == 'S' || to == 'I' || to == 'J' || to == 'F' || to == 'D'; |
| case 'S': // short |
| case 'C': // char |
| return to == 'I' || to == 'J' || to == 'F' || to == 'D'; |
| case 'I': // int |
| return to == 'J' || to == 'F' || to == 'D'; |
| case 'J': // long |
| return to == 'F' || to == 'D'; |
| case 'F': // float |
| return to == 'D'; |
| case 'Z': // boolean |
| case 'D': // double |
| return false; |
| default: |
| throw new Unreachable("Invalid primitive type descriptor: " + from); |
| } |
| } |
| |
| @Override |
| protected void prepareInstructions() { |
| DexType[] capturedTypes = captures(); |
| DexType[] erasedParams = descriptor().erasedProto.parameters.values; |
| DexType erasedReturnType = descriptor().erasedProto.returnType; |
| DexType[] enforcedParams = descriptor().enforcedProto.parameters.values; |
| DexType enforcedReturnType = descriptor().enforcedProto.returnType; |
| |
| LambdaClass.Target target = lambda.target; |
| DexMethod methodToCall = target.callTarget; |
| |
| // Only constructor call should use direct invoke type since super |
| // and private methods require accessor methods. |
| boolean constructorTarget = target.invokeType == Invoke.Type.DIRECT; |
| assert !constructorTarget || methodToCall.name == factory().constructorMethodName; |
| |
| List<DexType> implReceiverAndArgs = new ArrayList<>(); |
| if (target.invokeType == Invoke.Type.VIRTUAL || target.invokeType == Invoke.Type.INTERFACE) { |
| implReceiverAndArgs.add(methodToCall.holder); |
| } |
| implReceiverAndArgs.addAll(Lists.newArrayList(methodToCall.proto.parameters.values)); |
| DexType implReturnType = methodToCall.proto.returnType; |
| |
| assert target.invokeType == Invoke.Type.STATIC |
| || target.invokeType == Invoke.Type.VIRTUAL |
| || target.invokeType == Invoke.Type.DIRECT |
| || target.invokeType == Invoke.Type.INTERFACE; |
| assert checkSignatures(capturedTypes, enforcedParams, |
| enforcedReturnType, implReceiverAndArgs, |
| constructorTarget ? target.callTarget.holder : implReturnType); |
| |
| // Prepare call arguments. |
| List<ValueType> argValueTypes = new ArrayList<>(); |
| List<Integer> argRegisters = new ArrayList<>(); |
| |
| // If the target is a constructor, we need to create the instance first. |
| // This instance will be the first argument to the call. |
| if (constructorTarget) { |
| int instance = nextRegister(ValueType.OBJECT); |
| add(builder -> builder.addNewInstance(instance, methodToCall.holder)); |
| argValueTypes.add(ValueType.OBJECT); |
| argRegisters.add(instance); |
| } |
| |
| // Load captures if needed. |
| int capturedValues = capturedTypes.length; |
| for (int i = 0; i < capturedValues; i++) { |
| ValueType valueType = ValueType.fromDexType(capturedTypes[i]); |
| int register = nextRegister(valueType); |
| |
| argValueTypes.add(valueType); |
| argRegisters.add(register); |
| |
| // Read field into tmp local. |
| DexField field = lambda.getCaptureField(i); |
| add(builder -> builder.addInstanceGet(register, getReceiverRegister(), field)); |
| } |
| |
| // Prepare arguments. |
| for (int i = 0; i < erasedParams.length; i++) { |
| DexType expectedParamType = implReceiverAndArgs.get(i + capturedValues); |
| argValueTypes.add(ValueType.fromDexType(expectedParamType)); |
| argRegisters.add(prepareParameterValue( |
| getParamRegister(i), erasedParams[i], enforcedParams[i], expectedParamType)); |
| } |
| |
| // Method call to the method implementing lambda or method-ref. |
| add( |
| builder -> |
| builder.addInvoke( |
| target.invokeType, |
| methodToCall, |
| methodToCall.proto, |
| argValueTypes, |
| argRegisters, |
| false /* isInterface */)); |
| |
| // Does the method have return value? |
| if (enforcedReturnType.isVoidType()) { |
| add(IRBuilder::addReturn); |
| } else if (constructorTarget) { |
| // Return newly created instance |
| int instanceRegister = argRegisters.get(0); |
| int adjustedValue = prepareReturnValue(instanceRegister, |
| erasedReturnType, enforcedReturnType, methodToCall.holder); |
| add(builder -> builder.addReturn(adjustedValue)); |
| } else { |
| ValueType implValueType = ValueType.fromDexType(implReturnType); |
| int tempValue = nextRegister(implValueType); |
| add(builder -> builder.addMoveResult(tempValue)); |
| int adjustedValue = prepareReturnValue(tempValue, |
| erasedReturnType, enforcedReturnType, methodToCall.proto.returnType); |
| add(builder -> builder.addReturn(adjustedValue)); |
| } |
| } |
| |
| // Adds necessary casts and transformations to adjust the value |
| // returned by impl-method to expected return type of the method. |
| private int prepareReturnValue(int register, |
| DexType erasedType, DexType enforcedType, DexType actualType) { |
| // `actualType` must be adjusted to `enforcedType` first. |
| register = adjustType(register, actualType, enforcedType, true); |
| |
| // `erasedType` and `enforcedType` may only differ when they both |
| // are class types and `erasedType` is a base type of `enforcedType`, |
| // so no transformation is actually needed. |
| assert LambdaDescriptor.isSameOrDerived(factory(), enforcedType, erasedType); |
| return register; |
| } |
| |
| // Adds necessary casts and transformations to adjust parameter |
| // value to the expected type of method-impl argument. |
| // |
| // Note that the original parameter type (`erasedType`) may need to |
| // be converted to enforced parameter type (`enforcedType`), which, |
| // in its turn, may need to be adjusted to the parameter type of |
| // the impl-method (`expectedType`). |
| private int prepareParameterValue(int register, |
| DexType erasedType, DexType enforcedType, DexType expectedType) { |
| register = enforceParameterType(register, erasedType, enforcedType); |
| register = adjustType(register, enforcedType, expectedType, false); |
| return register; |
| } |
| |
| private int adjustType(int register, DexType fromType, DexType toType, boolean returnType) { |
| if (fromType == toType) { |
| return register; |
| } |
| |
| boolean fromTypePrimitive = fromType.isPrimitiveType(); |
| boolean toTypePrimitive = toType.isPrimitiveType(); |
| |
| // If both are primitive they must be convertible via primitive widening conversion. |
| if (fromTypePrimitive && toTypePrimitive) { |
| return addPrimitiveWideningConversion(register, fromType, toType); |
| } |
| |
| // If the first one is a boxed primitive type and the second one is a primitive |
| // type, the value must be unboxed and converted to the resulting type via primitive |
| // widening conversion. |
| if (toTypePrimitive) { |
| DexType boxedType = fromType; |
| if (boxedType == factory().objectType) { |
| // We are in situation when from(=java.lang.Object) is being adjusted to a |
| // primitive type, in which case we assume it is of proper box type. |
| boxedType = getBoxedForPrimitiveType(toType); |
| register = castToBoxedType(register, boxedType); |
| } |
| DexType fromTypeAsPrimitive = getPrimitiveFromBoxed(boxedType); |
| if (fromTypeAsPrimitive != null) { |
| int unboxedRegister = addPrimitiveUnboxing(register, fromTypeAsPrimitive, boxedType); |
| return addPrimitiveWideningConversion(unboxedRegister, fromTypeAsPrimitive, toType); |
| } |
| } |
| |
| // If the first one is a primitive type and the second one is a boxed |
| // type for this primitive type, just box the value. |
| if (fromTypePrimitive) { |
| DexType boxedFromType = getBoxedForPrimitiveType(fromType); |
| if (toType == boxedFromType || |
| toType == factory().objectType || |
| toType == factory().serializableType || |
| toType == factory().comparableType || |
| (boxedFromType != factory().booleanType && |
| boxedFromType != factory().charType && |
| toType == factory().boxedNumberType)) { |
| return addPrimitiveBoxing(register, fromType, boxedFromType); |
| } |
| } |
| |
| if (fromType.isArrayType() && (toType == factory().objectType || toType.isArrayType())) { |
| // If `fromType` is an array and `toType` is java.lang.Object, no cast is needed. |
| // If both `fromType` and `toType` are arrays, no cast is needed since we assume |
| // the input code is verifiable. |
| return register; |
| } |
| |
| if ((fromType.isClassType() && toType.isClassType()) |
| || (fromType == factory().objectType && toType.isArrayType())) { |
| if (returnType) { |
| // For return type adjustment in case `fromType` and `toType` are both reference types, |
| // `fromType` does NOT have to be deriving from `toType` and we need to add a cast. |
| // NOTE: we don't check `toType` for being actually a supertype, since we |
| // might not have full classpath with inheritance information to do that. |
| int finalRegister = register; |
| add(builder -> builder.addCheckCast(finalRegister, toType)); |
| } |
| return register; |
| } |
| |
| throw new Unreachable("Unexpected type adjustment from " |
| + fromType.toSourceString() + " to " + toType); |
| } |
| |
| private int addPrimitiveWideningConversion(int register, DexType fromType, DexType toType) { |
| assert fromType.isPrimitiveType() && toType.isPrimitiveType(); |
| if (fromType == toType) { |
| return register; |
| } |
| |
| NumericType from = NumericType.fromDexType(fromType); |
| NumericType to = NumericType.fromDexType(toType); |
| |
| if (from != null && to != null) { |
| assert from != to; |
| |
| switch (to) { |
| case SHORT: { |
| if (from != NumericType.BYTE) { |
| break; // Only BYTE can be converted to SHORT via widening conversion. |
| } |
| int result = nextRegister(ValueType.INT); |
| add(builder -> builder.addConversion(to, NumericType.INT, result, register)); |
| return result; |
| } |
| |
| case INT: |
| if (from == NumericType.BYTE || from == NumericType.CHAR || from == NumericType.SHORT) { |
| return register; // No actual conversion is needed. |
| } |
| break; |
| |
| case LONG: { |
| if (from == NumericType.FLOAT || from == NumericType.DOUBLE) { |
| break; // Not a widening conversion. |
| } |
| int result = nextRegister(ValueType.LONG); |
| add(builder -> builder.addConversion(to, NumericType.INT, result, register)); |
| return result; |
| } |
| |
| case FLOAT: { |
| if (from == NumericType.DOUBLE) { |
| break; // Not a widening conversion. |
| } |
| int result = nextRegister(ValueType.FLOAT); |
| NumericType type = (from == NumericType.LONG) ? NumericType.LONG : NumericType.INT; |
| add(builder -> builder.addConversion(to, type, result, register)); |
| return result; |
| } |
| |
| case DOUBLE: { |
| int result = nextRegister(ValueType.DOUBLE); |
| NumericType type = (from == NumericType.FLOAT || from == NumericType.LONG) |
| ? from : NumericType.INT; |
| add(builder -> builder.addConversion(to, type, result, register)); |
| return result; |
| } |
| default: |
| // exception is thrown below |
| break; |
| } |
| } |
| |
| throw new Unreachable("Type " + fromType.toSourceString() + " cannot be " + |
| "converted to " + toType.toSourceString() + " via primitive widening conversion."); |
| } |
| |
| private DexMethod getUnboxMethod(byte primitive, DexType boxType) { |
| DexItemFactory factory = factory(); |
| DexProto proto; |
| switch (primitive) { |
| case 'Z': // byte |
| proto = factory.createProto(factory.booleanType); |
| return factory.createMethod(boxType, proto, factory.unboxBooleanMethodName); |
| case 'B': // byte |
| proto = factory.createProto(factory.byteType); |
| return factory.createMethod(boxType, proto, factory.unboxByteMethodName); |
| case 'S': // short |
| proto = factory.createProto(factory.shortType); |
| return factory.createMethod(boxType, proto, factory.unboxShortMethodName); |
| case 'C': // char |
| proto = factory.createProto(factory.charType); |
| return factory.createMethod(boxType, proto, factory.unboxCharMethodName); |
| case 'I': // int |
| proto = factory.createProto(factory.intType); |
| return factory.createMethod(boxType, proto, factory.unboxIntMethodName); |
| case 'J': // long |
| proto = factory.createProto(factory.longType); |
| return factory.createMethod(boxType, proto, factory.unboxLongMethodName); |
| case 'F': // float |
| proto = factory.createProto(factory.floatType); |
| return factory.createMethod(boxType, proto, factory.unboxFloatMethodName); |
| case 'D': // double |
| proto = factory.createProto(factory.doubleType); |
| return factory.createMethod(boxType, proto, factory.unboxDoubleMethodName); |
| default: |
| throw new Unreachable("Invalid primitive type descriptor: " + primitive); |
| } |
| } |
| |
| private int addPrimitiveUnboxing(int register, DexType primitiveType, DexType boxType) { |
| DexMethod method = getUnboxMethod(primitiveType.descriptor.content[0], boxType); |
| |
| List<ValueType> argValueTypes = ImmutableList.of(ValueType.OBJECT); |
| List<Integer> argRegisters = Collections.singletonList(register); |
| add( |
| builder -> |
| builder.addInvoke( |
| Invoke.Type.VIRTUAL, |
| method, |
| method.proto, |
| argValueTypes, |
| argRegisters, |
| false /* isInterface */)); |
| |
| ValueType valueType = ValueType.fromDexType(primitiveType); |
| int result = nextRegister(valueType); |
| add(builder -> builder.addMoveResult(result)); |
| return result; |
| } |
| |
| private int castToBoxedType(int register, DexType boxType) { |
| add(builder -> builder.addCheckCast(register, boxType)); |
| return register; |
| } |
| |
| private int addPrimitiveBoxing(int register, DexType primitiveType, DexType boxType) { |
| // Generate factory method fo boxing. |
| DexItemFactory factory = factory(); |
| DexProto proto = factory.createProto(boxType, primitiveType); |
| DexMethod method = factory.createMethod(boxType, proto, factory.valueOfMethodName); |
| |
| ValueType valueType = ValueType.fromDexType(primitiveType); |
| List<ValueType> argValueTypes = ImmutableList.of(valueType); |
| List<Integer> argRegisters = Collections.singletonList(register); |
| add( |
| builder -> |
| builder.addInvoke( |
| Invoke.Type.STATIC, |
| method, |
| method.proto, |
| argValueTypes, |
| argRegisters, |
| false /* isInterface */)); |
| |
| int result = nextRegister(ValueType.OBJECT); |
| add(builder -> builder.addMoveResult(result)); |
| return result; |
| } |
| } |