JVM_IR indy-SAM conversions: inline classes

KT-44278 KT-26060 KT-42621
This commit is contained in:
Dmitry Petrov
2021-01-21 17:39:46 +03:00
parent f30e25aa52
commit 1f16b96796
28 changed files with 721 additions and 50 deletions
@@ -454,7 +454,7 @@ class JvmSymbols(
}
val receiverFieldName = Name.identifier("receiver")
klass.addProperty() {
klass.addProperty {
name = receiverFieldName
}.apply {
backingField = irFactory.buildField {
@@ -658,7 +658,7 @@ class JvmSymbols(
collectionToArrayClass.functions.single { it.owner.name.asString() == "toArray" && it.owner.valueParameters.size == 2 }
val kClassJava: IrPropertySymbol =
irFactory.buildProperty() {
irFactory.buildProperty {
name = Name.identifier("java")
}.apply {
parent = kotlinJvmPackage
@@ -15,9 +15,7 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.overrides.buildFakeOverrideMember
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
import org.jetbrains.kotlin.ir.util.dump
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.Handle
@@ -136,38 +134,46 @@ object JvmInvokeDynamic : IntrinsicMethod() {
?: fail("Argument in ${irCall.symbol.owner.name} call is expected to be a raw function reference")
val irOriginalFun = irRawFunRef.symbol.owner as? IrSimpleFunction
?: fail("IrSimpleFunction expected: ${irRawFunRef.symbol.owner.render()}")
val substitutedType = irCall.getTypeArgument(0) as? IrSimpleType
val superType = irCall.getTypeArgument(0) as? IrSimpleType
?: fail("Type argument expected")
// Force boxing on primitive types, otherwise D8 fails to accept resulting MethodType in presence of primitive types in arguments
// (LambdaMetafactory is ok with such types, though).
val substitutedTypeWithNullableArgs =
substitutedType.classifier.typeWithArguments(
substitutedType.arguments.map {
when (it) {
is IrStarProjection -> it
is IrTypeProjection -> {
val type = it.type
if (type !is IrSimpleType || type.hasQuestionMark)
it
else
makeTypeProjection(type.withHasQuestionMark(true), it.variance)
}
else ->
fail("Unexpected type argument '${it}' :: ${it::class.simpleName}")
}
}
)
val patchedSuperType = replaceTypeArgumentsWithNullable(superType)
val fakeClass = codegen.context.irFactory.buildClass { name = Name.special("<fake>") }
fakeClass.parent = codegen.context.ir.symbols.kotlinJvmInternalInvokeDynamicPackage
val irFakeOverride = buildFakeOverrideMember(substitutedTypeWithNullableArgs, irOriginalFun, fakeClass) as IrSimpleFunction
val irFakeOverride = buildFakeOverrideMember(patchedSuperType, irOriginalFun, fakeClass) as IrSimpleFunction
irFakeOverride.overriddenSymbols = listOf(irOriginalFun.symbol)
val asmMethod = codegen.methodSignatureMapper.mapAsmMethod(irFakeOverride)
return Type.getMethodType(asmMethod.descriptor)
}
// Given the following functional interface
// fun interface IFoo<T> {
// fun foo(x: T): T
// }
// To comply with java.lang.invoke.LambdaMetafactory requirements, we need an instance method that accepts references
// (not primitives, and not unboxed inline classes).
// In order to do so, we replace type arguments with nullable types.
private fun replaceTypeArgumentsWithNullable(substitutedType: IrSimpleType) =
substitutedType.classifier.typeWithArguments(
substitutedType.arguments.map { typeArgument ->
when (typeArgument) {
is IrStarProjection -> typeArgument
is IrTypeProjection -> {
val type = typeArgument.type
if (type !is IrSimpleType || type.hasQuestionMark)
typeArgument
else {
makeTypeProjection(type.withHasQuestionMark(true), typeArgument.variance)
}
}
else ->
throw AssertionError("Unexpected type argument '$typeArgument' :: ${typeArgument::class.simpleName}")
}
}
)
private fun IrExpression.getIntConst() =
if (this is IrConst<*> && kind == IrConstKind.Int)
this.value as Int
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.backend.common.lower.SamEqualsHashCodeMethodsGenerat
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.representativeUpperBound
import org.jetbrains.kotlin.backend.jvm.ir.*
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi
import org.jetbrains.kotlin.config.JvmSamConversions
@@ -29,6 +30,7 @@ import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.utils.addIfNotNull
internal val functionReferencePhase = makeIrFilePhase(
::FunctionReferenceLowering,
@@ -115,21 +117,54 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext)
// TODO special mode that would generate indy everywhere?
if (reference.origin != IrStatementOrigin.LAMBDA)
return false
// TODO wrap intrinsic function in lambda?
if (context.irIntrinsics.getIntrinsic(reference.symbol) != null)
return false
// Can't use JDK LambdaMetafactory if lambda signature contains an inline class mapped to a non-null reference type.
val target = reference.symbol.owner
if (target.extensionReceiverParameter?.run { type.isProhibitedTypeForIndySamConversion() } == true ||
target.valueParameters.any { it.type.isProhibitedTypeForIndySamConversion() } ||
target.returnType.isProhibitedTypeForIndySamConversion()
)
return false
return true
}
private fun IrType.isProhibitedTypeForIndySamConversion(): Boolean {
if (this !is IrSimpleType) return false
val erasedType = when (val classifier = classifier.owner) {
is IrTypeParameter ->
classifier.representativeUpperBound.withHasQuestionMark(hasQuestionMark)
else ->
this
}
val erasedClass = erasedType.getClass() ?: return false
if (!erasedType.isInlined()) return false
val underlyingType = getInlineClassUnderlyingType(erasedClass) as? IrSimpleType
?: throw AssertionError("Underlying type for inline class should be a simple type: ${erasedClass.render()}")
return !underlyingType.hasQuestionMark && !underlyingType.isJvmPrimitiveType()
}
private fun IrType.isJvmPrimitiveType() =
isBoolean() || isChar() || isByte() || isShort() || isInt() || isLong() || isFloat() || isDouble()
private fun wrapSamConversionArgumentWithIndySamConversion(expression: IrTypeOperatorCall): IrExpression {
val samType = expression.typeOperand
return when (val argument = expression.argument) {
is IrFunctionReference ->
wrapWithIndySamConversion(expression.typeOperand, argument)
is IrFunctionReference -> {
wrapWithIndySamConversion(samType, argument)
}
is IrBlock -> {
val last = argument.statements.last()
val functionReference = last as? IrFunctionReference
?: throw AssertionError("Function reference expected: ${last.render()}")
argument.statements[argument.statements.size - 1] = wrapWithIndySamConversion(expression.typeOperand, functionReference)
argument.statements[argument.statements.size - 1] = wrapWithIndySamConversion(samType, functionReference)
return argument
}
else -> throw AssertionError("Block or function reference expected: ${expression.render()}")
@@ -145,6 +180,7 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext)
)
private fun wrapWithIndySamConversion(samType: IrType, irFunRef: IrFunctionReference): IrCall {
patchSignatureForIndySamConversion(irFunRef.symbol.owner, samType)
val notNullSamType = samType.makeNotNull()
.removeAnnotations { it.type.classFqName in specialNullabilityAnnotationsFqNames }
return context.createJvmIrBuilder(currentScope!!.scope.scopeOwnerSymbol).run {
@@ -161,6 +197,48 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext)
}
}
private fun patchSignatureForIndySamConversion(irLambda: IrFunction, samType: IrType) {
if (irLambda.origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA)
throw AssertionError("Can't patch a signature of a non-lambda: ${irLambda.render()}")
val samClass = samType.classOrNull?.owner
?: throw AssertionError("SAM type should be a class type: '${samType.render()}'")
val samMethod = samClass.functions.singleOrNull { it.modality == Modality.ABSTRACT }
?: throw AssertionError("SAM method not found:\n${samClass.dump()}")
val samMethodParameters = collectValueParameters(samMethod)
val irLambdaParameters = collectValueParameters(irLambda)
if (samMethodParameters.size != irLambdaParameters.size) {
throw AssertionError(
"SAM method and implementing lambda have mismatching value parameters " +
"(${samMethodParameters.size} != ${irLambdaParameters.size}:\n" +
"samMethod: ${samMethod.render()}\n" +
"lambda: ${irLambda.render()}"
)
}
for ((irLambdaParameter, samMethodParameter) in irLambdaParameters.zip(samMethodParameters)) {
irLambdaParameter.type = patchTypeForIndySamConversion(irLambdaParameter.type, samMethodParameter.type)
}
irLambda.returnType = patchTypeForIndySamConversion(irLambda.returnType, samMethod.returnType)
}
private fun collectValueParameters(irFunction: IrFunction): List<IrValueParameter> =
ArrayList<IrValueParameter>().apply {
addIfNotNull(irFunction.extensionReceiverParameter)
addAll(irFunction.valueParameters)
}
private fun patchTypeForIndySamConversion(originalType: IrType, targetType: IrType): IrType {
if (originalType.isUnboxedInlineClassType() && !targetType.isUnboxedInlineClassType())
return targetType
return originalType
}
private fun IrType.isUnboxedInlineClassType() =
this is IrSimpleType && isInlined() && !hasQuestionMark
private inner class FunctionReferenceBuilder(val irFunctionReference: IrFunctionReference, val samSuperType: IrType? = null) {
private val isLambda = irFunctionReference.origin.isLambda