JVM_IR: use indy SAM conversions in jvmTarget 1.8+, fix bridges

KT-44278 KT-26060 KT-42621
This commit is contained in:
Dmitry Petrov
2021-02-05 15:23:04 +03:00
parent 6c6d43c29a
commit 3ebeca5852
50 changed files with 1293 additions and 287 deletions
@@ -545,19 +545,53 @@ class JvmSymbols(
returnType = dst.defaultType
}.symbol
val indySamConversionIntrinsic: IrSimpleFunctionSymbol =
val arrayOfAnyType = irBuiltIns.arrayClass.typeWith(irBuiltIns.anyType)
// Intrinsic to represent closure creation using INVOKEDYNAMIC with LambdaMetafactory.{metafactory, altMetafactory}
// as a bootstrap method.
// fun <SAM_TYPE> `<jvm-indy-lambda-metafactory>`(
// samMethodType,
// implMethodReference,
// instantiatedMethodType,
// vararg extraOverriddenMethodTypes
// ): SAM_TYPE
// where:
// `SAM_TYPE` is a single abstract method interface, which is implemented by a resulting closure;
// `samMethodType` is a method type (signature and return type) of a method to be implemented by a closure;
// `implMethodReference` is an actual implementation method (e.g., method for a lambda function);
// `instantiatedMethodType` is a specialized implementation method type;
// `extraOverriddenMethodTypes` is a possibly empty vararg of additional methods to be implemented by a closure.
//
// At this stage, "method types" are represented as IrRawFunctionReference nodes for the functions with corresponding signature.
// `<jvm-indy-lambda-metafactory>` call rewriting selects a particular bootstrap method (`metafactory` or `altMetafactory`)
// and takes care about low-level detains of bootstrap method arguments representation.
// Note that `instantiatedMethodType` is a raw function reference to a "fake" specialized function (belonging to a "fake" specialized
// class) that doesn't exist in the bytecode and serves only the purpose of representing a corresponding method signature.
//
// Resulting closure produced by INVOKEDYNAMIC instruction has (approximately) the following shape:
// object : ${SAM_TYPE} {
// override fun ${samMethodName}(${instantiatedMethodType}) = ${implMethod}(...)
// // bridge fun ${samMethodName}(${bridgeMethodType}) = ${instantiatedMethod}(...)
// // for each 'bridgeMethodType' in [ ${samMethodType}, *${extraOverriddenMethodTypes} ]
// }
val indyLambdaMetafactoryIntrinsic: IrSimpleFunctionSymbol =
irFactory.buildFun {
name = Name.special("<jvm-indy-sam-conversion>")
name = Name.special("<jvm-indy-lambda-metafactory>")
origin = IrDeclarationOrigin.IR_BUILTINS_STUB
}.apply {
parent = kotlinJvmInternalPackage
val samType = addTypeParameter("SAM_TYPE", irBuiltIns.anyType)
addValueParameter("method", irBuiltIns.anyNType)
addValueParameter("samMethodType", irBuiltIns.anyNType)
addValueParameter("implMethodReference", irBuiltIns.anyNType)
addValueParameter("instantiatedMethodType", irBuiltIns.anyNType)
addValueParameter {
name = Name.identifier("extraOverriddenMethodTypes")
type = arrayOfAnyType
varargElementType = irBuiltIns.anyType
}
returnType = samType.defaultType
}.symbol
val arrayOfAnyType = irBuiltIns.arrayClass.typeWith(irBuiltIns.anyType)
// Intrinsic to represent INVOKEDYNAMIC calls in IR.
// fun <T> `<jvm-indy>`(
// dynamicCall: T,
@@ -136,9 +136,9 @@ 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 superType = irCall.getTypeArgument(0) as? IrSimpleType
?: fail("Type argument expected")
val patchedSuperType = replaceTypeArgumentsWithNullable(superType)
val fakeClass = codegen.context.irFactory.buildClass { name = Name.special("<fake>") }
@@ -25,13 +25,14 @@ import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.overrides.buildFakeOverrideMember
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.types.*
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,
@@ -92,20 +93,24 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext)
expression.statements.dropLast(1).forEach { it.transform(this, null) }
reference.transformChildrenVoid(this)
if (shouldGenerateIndyLambdas && canUseIndySamConversion(reference, reference.type, true)) {
return wrapLambdaReferenceWithIndySamConversion(expression, reference)
if (shouldGenerateIndyLambdas) {
val lambdaMetafactoryArguments = getLambdaMetafactoryArgumentsOrNull(reference, reference.type, true)
if (lambdaMetafactoryArguments != null) {
return wrapLambdaReferenceWithIndySamConversion(expression, reference, lambdaMetafactoryArguments)
}
}
return FunctionReferenceBuilder(reference).build()
}
private fun wrapLambdaReferenceWithIndySamConversion(expression: IrBlock, reference: IrFunctionReference): IrBlock {
expression.statements[expression.statements.size - 1] = wrapWithIndySamConversion(reference.type, reference)
val irLambda = reference.symbol.owner
// JDK LambdaMetafactory can't adapt '(...)V' to '(...)Lkotlin/Unit;'.
if (irLambda.returnType.isUnit()) {
irLambda.returnType = irLambda.returnType.makeNullable()
}
private fun wrapLambdaReferenceWithIndySamConversion(
expression: IrBlock,
reference: IrFunctionReference,
lambdaMetafactoryArguments: LambdaMetafactoryArguments
): IrBlock {
val indySamConversion = wrapWithIndySamConversion(reference.type, lambdaMetafactoryArguments)
expression.statements[expression.statements.size - 1] = indySamConversion
expression.type = indySamConversion.type
return expression
}
@@ -140,49 +145,389 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext)
reference.transformChildrenVoid()
val samSuperType = expression.typeOperand
return if (shouldGenerateIndySamConversions && canUseIndySamConversion(reference, samSuperType, false)) {
wrapSamConversionArgumentWithIndySamConversion(expression)
} else {
FunctionReferenceBuilder(reference, samSuperType).build()
if (shouldGenerateIndySamConversions) {
val lambdaMetafactoryArguments = getLambdaMetafactoryArgumentsOrNull(reference, samSuperType, false)
if (lambdaMetafactoryArguments != null) {
return wrapSamConversionArgumentWithIndySamConversion(expression, lambdaMetafactoryArguments)
}
}
return FunctionReferenceBuilder(reference, samSuperType).build()
}
private fun canUseIndySamConversion(reference: IrFunctionReference, samSuperType: IrType, plainLambda: Boolean): Boolean {
private class LambdaMetafactoryArguments(
val samMethod: IrSimpleFunction,
val fakeInstanceMethod: IrSimpleFunction,
val implMethodReference: IrFunctionReference,
val extraOverriddenMethods: List<IrSimpleFunction>
)
/**
* @see java.lang.invoke.LambdaMetafactory
*/
private fun getLambdaMetafactoryArgumentsOrNull(
reference: IrFunctionReference,
samType: IrType,
plainLambda: Boolean
): LambdaMetafactoryArguments? {
// Can't use JDK LambdaMetafactory for function references by default (because of 'equals').
// TODO special mode that would generate indy everywhere?
if (reference.origin != IrStatementOrigin.LAMBDA)
return false
return null
// TODO wrap intrinsic function in lambda?
if (context.irIntrinsics.getIntrinsic(reference.symbol) != null)
return false
val samClass = samType.getClass()
?: throw AssertionError("SAM type is not a class: ${samType.render()}")
val samMethod = samClass.getSingleAbstractMethod()
?: throw AssertionError("SAM class has no single abstract method: ${samClass.render()}")
// Can't use JDK LambdaMetafactory for fun interface with suspend fun
if (samSuperType.getSingleAbstractMethod()?.isSuspend == true)
return false
// Can't use JDK LambdaMetafactory for fun interface with suspend fun.
if (samMethod.isSuspend)
return null
// 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
// Can't use JDK LambdaMetafactory for fun interfaces that require delegation to $DefaultImpls.
if (samClass.requiresDelegationToDefaultImpls())
return null
val target = reference.symbol.owner as? IrSimpleFunction
?: throw AssertionError("Simple function expected: ${reference.symbol.owner.render()}")
// Can't use JDK LambdaMetafactory for annotated lambdas.
// JDK LambdaMetafactory doesn't copy annotations from implementation method to an instance method in a
// corresponding synthetic class, which doesn't look like a binary compatible change.
// TODO relaxed mode?
if (target.annotations.isNotEmpty())
return null
// Don't use JDK LambdaMetafactory for big arity lambdas.
if (plainLambda) {
var parametersCount = target.valueParameters.size
if (target.extensionReceiverParameter != null) ++parametersCount
if (parametersCount >= BuiltInFunctionArity.BIG_ARITY)
return false
return null
}
// Can't use indy-based SAM conversion inside inline fun (Ok in inline lambda).
if (target.parents.any { it.isInlineFunction() || it.isCrossinlineLambda() })
return false
return null
return true
// Do the hard work of matching Kotlin functional interface hierarchy against LambdaMetafactory constraints.
// Briefly: sometimes we have to force boxing on the primitive and inline class values, sometimes we have to keep them unboxed.
// If this results in conflicting requirements, we can't use INVOKEDYNAMIC with LambdaMetafactory for creating a closure.
return getLambdaMetafactoryArgsOrNullInner(reference, samMethod, samType, target)
}
private fun IrClass.requiresDelegationToDefaultImpls(): Boolean {
for (irMemberFun in functions) {
if (irMemberFun.modality == Modality.ABSTRACT)
continue
val irImplFun =
if (irMemberFun.isFakeOverride)
irMemberFun.findInterfaceImplementation(context.state.jvmDefaultMode)
?: continue
else
irMemberFun
if (irImplFun.origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB)
continue
if (!irImplFun.isCompiledToJvmDefault(context.state.jvmDefaultMode))
return true
}
return false
}
private fun getLambdaMetafactoryArgsOrNullInner(
reference: IrFunctionReference,
samMethod: IrSimpleFunction,
samType: IrType,
implLambda: IrSimpleFunction
): LambdaMetafactoryArguments? {
val nonFakeOverriddenFuns = samMethod.allOverridden().filterNot { it.isFakeOverride }
val relevantOverriddenFuns = if (samMethod.isFakeOverride) nonFakeOverriddenFuns else nonFakeOverriddenFuns + samMethod
// Create a fake instance method as if it was defined in a class implementing SAM interface
// (such class would be eventually created by LambdaMetafactory at run-time).
val fakeClass = context.irFactory.buildClass { name = Name.special("<fake>") }
fakeClass.parent = context.ir.symbols.kotlinJvmInternalInvokeDynamicPackage
val fakeInstanceMethod = buildFakeOverrideMember(samType, samMethod, fakeClass) as IrSimpleFunction
(fakeInstanceMethod as IrFakeOverrideFunction).acquireSymbol(IrSimpleFunctionSymbolImpl())
fakeInstanceMethod.overriddenSymbols = listOf(samMethod.symbol)
// Compute signature adaptation constraints for a fake instance method signature against all relevant overrides.
// If at any step we encounter a conflict (e.g., one override requires boxing a parameter, and another requires
// to keep it unboxed), we can't adapt this signature and can't use LambdaMetafactory to create a closure.
//
// Note that those constraints are not checked precisely in JDK 1.8 (jdk1.8.0_231), but are checked more strictly
// in later JDK versions and in D8 (so if you see an exception from D8 in codegen test failures, corresponding code
// with INVOKEDYNAMIC would quite likely fail on JDK 9 and beyond).
//
// Example 1 (requires boxing):
// fun interface IFoo<T> {
// fun foo(x: T)
// }
// val t = IFoo<Int> { println(it + 1) }
// Here IFoo<T>::foo requires 'x' to be reference type (even though corresponding lambda accepts a primitive int).
// this
//
// Example 2 (no explicit override, boxing-unboxing conflict):
// fun interface IFooT<T> {
// fun foo(x: T)
// }
// fun interface IFooInt {
// fun foo(x: Int)
// }
// fun interface IFooMix : IFooT<Int>, IFooInt
// val t = IFooMix { println(it + 1) }
// Here IFooT<T>::foo requires 'x' to be of a reference type, and IFooInt::foo requires 'x' to be of a primitive type.
// LambdaMetafactory can't handle such case.
//
// Example 3 (explicit override, boxing-unboxing conflict):
// fun interface IFooT<T> {
// fun foo(x: T)
// }
// fun interface IFooInt {
// fun foo(x: Int)
// }
// fun interface IFooMix : IFooT<Int>, IFooInt {
// override fun foo(x: Int)
// }
// val t = IFooMix { println(it + 1) }
// Here, even though we have an explicit 'override fun foo(x: Int)' in IFooMix, we don't generate a bridge for 'foo' in IFooMix.
// Thus, class for a lambda created by LambdaMetafactory should provide a bridge for 'foo'.
// Thus, 'x' should be of a reference type.
// On the other hand, it should also override IFooInt#foo, where 'x' should be a primitive type.
// LambdaMetafactory can't handle such case.
//
// TODO accept Example 3 if IFooMix is compiled with default interface methods
// Note that this is a conservative check; if we reject LambdaMetafactory-based closure generation scheme, compiler would still
// generate proper (although somewhat sub-optimal) code with explicit class for a corresponding SAM-converted lambda.
val signatureAdaptationConstraints = run {
var result = SignatureAdaptationConstraints(emptyMap(), null)
for (overriddenFun in relevantOverriddenFuns) {
val constraintsFromOverridden = computeSignatureAdaptationConstraints(fakeInstanceMethod, overriddenFun)
?: return null
result = joinSignatureAdaptationConstraints(result, constraintsFromOverridden)
?: return null
}
result
}
// We should have bailed out before if we encountered any kind of type adaptation conflict.
// Still, check that we are fine - just in case.
if (signatureAdaptationConstraints.returnType == TypeAdaptationConstraint.CONFLICT ||
signatureAdaptationConstraints.valueParameters.values.any { it == TypeAdaptationConstraint.CONFLICT }
)
return null
adaptFakeInstanceMethodSignature(fakeInstanceMethod, signatureAdaptationConstraints)
adaptLambdaSignature(implLambda, fakeInstanceMethod, signatureAdaptationConstraints)
if (samMethod.isFakeOverride && nonFakeOverriddenFuns.size == 1) {
return LambdaMetafactoryArguments(nonFakeOverriddenFuns.single(), fakeInstanceMethod, reference, listOf())
}
return LambdaMetafactoryArguments(samMethod, fakeInstanceMethod, reference, nonFakeOverriddenFuns)
}
private fun adaptLambdaSignature(
lambda: IrSimpleFunction,
fakeInstanceMethod: IrSimpleFunction,
constraints: SignatureAdaptationConstraints
) {
val lambdaParameters = collectValueParameters(lambda)
val methodParameters = collectValueParameters(fakeInstanceMethod)
if (lambdaParameters.size != methodParameters.size)
throw AssertionError(
"Mismatching lambda and instance method parameters:\n" +
"lambda: ${lambda.render()}\n" +
" (${lambdaParameters.size} parameters)\n" +
"instance method: ${fakeInstanceMethod.render()}\n" +
" (${methodParameters.size} parameters)"
)
for ((lambdaParameter, methodParameter) in lambdaParameters.zip(methodParameters)) {
// TODO box inline class parameters only?
val parameterConstraint = constraints.valueParameters[methodParameter]
if (parameterConstraint == TypeAdaptationConstraint.FORCE_BOXING) {
lambdaParameter.type = lambdaParameter.type.makeNullable()
}
}
if (constraints.returnType == TypeAdaptationConstraint.FORCE_BOXING) {
lambda.returnType = lambda.returnType.makeNullable()
}
}
private fun adaptFakeInstanceMethodSignature(fakeInstanceMethod: IrSimpleFunction, constraints: SignatureAdaptationConstraints) {
for ((valueParameter, constraint) in constraints.valueParameters) {
if (valueParameter.parent != fakeInstanceMethod)
throw AssertionError(
"Unexpected value parameter: ${valueParameter.render()}; fakeInstanceMethod:\n" +
fakeInstanceMethod.dump()
)
if (constraint == TypeAdaptationConstraint.FORCE_BOXING) {
valueParameter.type = valueParameter.type.makeNullable()
}
}
if (constraints.returnType == TypeAdaptationConstraint.FORCE_BOXING) {
fakeInstanceMethod.returnType = fakeInstanceMethod.returnType.makeNullable()
}
}
private enum class TypeAdaptationConstraint {
FORCE_BOXING,
KEEP_UNBOXED,
CONFLICT
}
private class SignatureAdaptationConstraints(
val valueParameters: Map<IrValueParameter, TypeAdaptationConstraint>,
val returnType: TypeAdaptationConstraint?
)
private fun computeSignatureAdaptationConstraints(
adapteeFun: IrSimpleFunction,
expectedFun: IrSimpleFunction
): SignatureAdaptationConstraints? {
val returnTypeConstraint = computeReturnTypeAdaptationConstraint(adapteeFun, expectedFun)
if (returnTypeConstraint == TypeAdaptationConstraint.CONFLICT)
return null
val valueParameterConstraints = HashMap<IrValueParameter, TypeAdaptationConstraint>()
val adapteeParameters = collectValueParameters(adapteeFun)
val expectedParameters = collectValueParameters(expectedFun)
if (adapteeParameters.size != expectedParameters.size)
throw AssertionError(
"Mismatching value parameters:\n" +
"adaptee: ${adapteeFun.render()}\n" +
" ${adapteeParameters.size} value parameters;\n" +
"expected: ${expectedFun.render()}\n" +
" ${expectedParameters.size} value parameters."
)
for ((adapteeParameter, expectedParameter) in adapteeParameters.zip(expectedParameters)) {
val parameterConstraint = computeParameterTypeAdaptationConstraint(adapteeParameter.type, expectedParameter.type)
?: continue
if (parameterConstraint == TypeAdaptationConstraint.CONFLICT)
return null
valueParameterConstraints[adapteeParameter] = parameterConstraint
}
return SignatureAdaptationConstraints(
if (valueParameterConstraints.isEmpty()) emptyMap() else valueParameterConstraints,
returnTypeConstraint
)
}
private fun computeParameterTypeAdaptationConstraint(adapteeType: IrType, expectedType: IrType): TypeAdaptationConstraint? {
if (adapteeType !is IrSimpleType)
throw AssertionError("Simple type expected: ${adapteeType.render()}")
if (expectedType !is IrSimpleType)
throw AssertionError("Simple type expected: ${expectedType.render()}")
// TODO what if adapteeType and/or expectedType are type parameters with JVM primitive type upper bounds?
if (adapteeType.isNothing() || adapteeType.isNullableNothing())
return TypeAdaptationConstraint.CONFLICT
// ** JVM primitives **
// All Kotlin types mapped to JVM primitive are final,
// and their supertypes are trivially mapped reference types.
if (adapteeType.isJvmPrimitiveType()) {
return if (expectedType.isJvmPrimitiveType())
TypeAdaptationConstraint.KEEP_UNBOXED
else
TypeAdaptationConstraint.FORCE_BOXING
}
// ** Inline classes **
// All Kotlin inline classes are final,
// and their supertypes are trivially mapped to reference types.
val erasedAdapteeClass = getErasedClassForSignatureAdaptation(adapteeType)
if (erasedAdapteeClass.isInline) {
// Inline classes mapped to non-null reference types are a special case because they can't be boxed trivially.
// TODO consider adding a special type annotation to force boxing on an inline class type regardless of its underlying type.
val underlyingAdapteeType = getInlineClassUnderlyingType(erasedAdapteeClass) as? IrSimpleType
?: throw AssertionError("Underlying type for inline class should be a simple type: ${erasedAdapteeClass.render()}")
if (!underlyingAdapteeType.hasQuestionMark && !underlyingAdapteeType.isJvmPrimitiveType()) {
return TypeAdaptationConstraint.CONFLICT
}
val erasedExpectedClass = getErasedClassForSignatureAdaptation(expectedType)
return if (erasedExpectedClass.isInline) {
// LambdaMetafactory doesn't know about method mangling.
TypeAdaptationConstraint.CONFLICT
} else {
// Trying to pass inline class value as non-inline class value (Any or other supertype)
// => box it
TypeAdaptationConstraint.FORCE_BOXING
}
}
// Other cases don't enforce type adaptation
return null
}
private fun getErasedClassForSignatureAdaptation(irType: IrSimpleType): IrClass =
when (val classifier = irType.classifier.owner) {
is IrTypeParameter -> classifier.erasedUpperBound
is IrClass -> classifier
else ->
throw AssertionError("Unexpected classifier: ${classifier.render()}")
}
private fun computeReturnTypeAdaptationConstraint(
adapteeFun: IrSimpleFunction,
expectedFun: IrSimpleFunction
): TypeAdaptationConstraint? {
val adapteeReturnType = adapteeFun.returnType
if (adapteeReturnType.isUnit()) {
// Can't mix '()V' and '()Lkotlin.Unit;' or '()Ljava.lang.Object;' in supertype method signatures.
return if (expectedFun.returnType.isUnit())
TypeAdaptationConstraint.KEEP_UNBOXED
else {
TypeAdaptationConstraint.FORCE_BOXING
}
}
val expectedReturnType = expectedFun.returnType
return computeParameterTypeAdaptationConstraint(adapteeReturnType, expectedReturnType)
}
private fun joinSignatureAdaptationConstraints(
sig1: SignatureAdaptationConstraints,
sig2: SignatureAdaptationConstraints
): SignatureAdaptationConstraints? {
val newReturnTypeConstraint = composeTypeAdaptationConstraints(sig1.returnType, sig2.returnType)
if (newReturnTypeConstraint == TypeAdaptationConstraint.CONFLICT)
return null
val newValueParameterConstraints =
when {
sig1.valueParameters.isEmpty() -> sig2.valueParameters
sig2.valueParameters.isEmpty() -> sig1.valueParameters
else -> {
val joined = HashMap<IrValueParameter, TypeAdaptationConstraint>()
joined.putAll(sig1.valueParameters)
for ((vp2, t2) in sig2.valueParameters.entries) {
val tx = composeTypeAdaptationConstraints(joined[vp2], t2) ?: continue
if (tx == TypeAdaptationConstraint.CONFLICT)
return null
joined[vp2] = tx
}
joined
}
}
return SignatureAdaptationConstraints(newValueParameterConstraints, newReturnTypeConstraint)
}
private fun composeTypeAdaptationConstraints(t1: TypeAdaptationConstraint?, t2: TypeAdaptationConstraint?): TypeAdaptationConstraint? =
when {
t1 == null -> t2
t2 == null -> t1
t1 == t2 -> t1
else ->
TypeAdaptationConstraint.CONFLICT
}
private fun IrDeclarationParent.isInlineFunction() =
this is IrSimpleFunction && isInline && origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
@@ -192,42 +537,29 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext)
inlineLambdaToValueParameter[irFun]?.isCrossinline == true
}
private fun IrType.isProhibitedTypeForIndySamConversion(): Boolean {
if (this !is IrSimpleType) return false
val erasedClass = when (val classifier = classifier.owner) {
is IrTypeParameter -> classifier.erasedUpperBound
is IrClass -> classifier
else -> throw AssertionError("Unexpected classifier: ${classifier.render()}")
}
if (!erasedClass.isInline) 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 {
private fun wrapSamConversionArgumentWithIndySamConversion(
expression: IrTypeOperatorCall,
lambdaMetafactoryArguments: LambdaMetafactoryArguments
): IrExpression {
val samType = expression.typeOperand
return when (val argument = expression.argument) {
is IrFunctionReference -> {
wrapWithIndySamConversion(samType, argument)
wrapWithIndySamConversion(samType, lambdaMetafactoryArguments)
}
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(samType, functionReference)
val indySamConversion = wrapWithIndySamConversion(samType, lambdaMetafactoryArguments)
argument.statements[argument.statements.size - 1] = indySamConversion
argument.type = indySamConversion.type
return argument
}
else -> throw AssertionError("Block or function reference expected: ${expression.render()}")
}
}
private val jvmIndySamConversionIntrinsic = context.ir.symbols.indySamConversionIntrinsic
private val jvmIndyLambdaMetafactoryIntrinsic = context.ir.symbols.indyLambdaMetafactoryIntrinsic
private val specialNullabilityAnnotationsFqNames =
setOf(
@@ -235,59 +567,37 @@ internal class FunctionReferenceLowering(private val context: JvmBackendContext)
context.ir.symbols.enhancedNullabilityAnnotationFqName
)
private fun wrapWithIndySamConversion(samType: IrType, irFunRef: IrFunctionReference): IrCall {
patchSignatureForIndySamConversion(irFunRef.symbol.owner, samType)
private fun wrapWithIndySamConversion(
samType: IrType,
lambdaMetafactoryArguments: LambdaMetafactoryArguments
): IrCall {
val notNullSamType = samType.makeNotNull()
.removeAnnotations { it.type.classFqName in specialNullabilityAnnotationsFqNames }
return context.createJvmIrBuilder(currentScope!!.scope.scopeOwnerSymbol).run {
// We should produce the following expression:
// `<jvm-indy-sam-conversion>`<samType>(method)
// where:
// - 'samType' is a substituted SAM type;
// - 'method' is a function reference to the actual method we are going to call
// (note that we need an IrFunctionReference here, so that further transformations would extract closure properly).
irCall(jvmIndySamConversionIntrinsic, notNullSamType).apply {
// See [org.jetbrains.kotlin.backend.jvm.JvmSymbols::indyLambdaMetafactoryIntrinsic].
irCall(jvmIndyLambdaMetafactoryIntrinsic, notNullSamType).apply {
putTypeArgument(0, notNullSamType)
putValueArgument(0, irFunRef)
putValueArgument(0, irRawFunctionRef(lambdaMetafactoryArguments.samMethod))
putValueArgument(1, lambdaMetafactoryArguments.implMethodReference)
putValueArgument(2, irRawFunctionRef(lambdaMetafactoryArguments.fakeInstanceMethod))
putValueArgument(3, irVarargOfRawFunctionRefs(lambdaMetafactoryArguments.extraOverriddenMethods))
}
}
}
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()}")
private fun IrBuilderWithScope.irRawFunctionRef(irFun: IrFunction) =
irRawFunctionReferefence(context.irBuiltIns.anyType, irFun.symbol)
val samMethod = samType.getSingleAbstractMethod()
?: throw AssertionError("SAM method not found:\n${samType.render()}")
private fun IrBuilderWithScope.irVarargOfRawFunctionRefs(irFuns: List<IrFunction>) =
irVararg(context.irBuiltIns.anyType, irFuns.map { irRawFunctionRef(it) })
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()}"
)
private fun collectValueParameters(irFun: IrFunction): List<IrValueParameter> {
if (irFun.extensionReceiverParameter == null)
return irFun.valueParameters
return ArrayList<IrValueParameter>().apply {
add(irFun.extensionReceiverParameter!!)
addAll(irFun.valueParameters)
}
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() =
@@ -15,7 +15,6 @@ import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
import org.jetbrains.kotlin.backend.jvm.codegen.fileParent
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.ir.getSingleAbstractMethod
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.*
@@ -37,6 +36,8 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.Handle
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.commons.Method
import java.lang.invoke.LambdaMetafactory
// After this pass runs there are only four kinds of IrTypeOperatorCalls left:
//
@@ -101,7 +102,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
builder.irAs(argument, type)
}
private val indySamConversionIntrinsic = context.ir.symbols.indySamConversionIntrinsic
private val jvmIndyLambdaMetafactoryIntrinsic = context.ir.symbols.indyLambdaMetafactoryIntrinsic
private val indyIntrinsic = context.ir.symbols.jvmIndyIntrinsic
@@ -128,13 +129,17 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
putValueArgument(0, irRawFunctionReferefence(context.irBuiltIns.anyType, methodSymbol))
}
@Suppress("unused")
private fun IrBuilderWithScope.jvmSubstitutedMethodType(ownerType: IrType, methodSymbol: IrFunctionSymbol) =
irCall(substitutedMethodTypeIntrinsic, context.irBuiltIns.anyType).apply {
putTypeArgument(0, ownerType)
putValueArgument(0, irRawFunctionReferefence(context.irBuiltIns.anyType, methodSymbol))
}
private val lambdaMetafactoryHandle =
/**
* @see java.lang.invoke.LambdaMetafactory.metafactory
*/
private val jdkMetafactoryHandle =
Handle(
Opcodes.H_INVOKESTATIC,
"java/lang/invoke/LambdaMetafactory",
@@ -150,9 +155,26 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
false
)
/**
* @see java.lang.invoke.LambdaMetafactory.altMetafactory
*/
private val jdkAltMetafactoryHandle =
Handle(
Opcodes.H_INVOKESTATIC,
"java/lang/invoke/LambdaMetafactory",
"altMetafactory",
"(" +
"Ljava/lang/invoke/MethodHandles\$Lookup;" +
"Ljava/lang/String;" +
"Ljava/lang/invoke/MethodType;" +
"[Ljava/lang/Object;" +
")Ljava/lang/invoke/CallSite;",
false
)
override fun visitCall(expression: IrCall): IrExpression {
return when (expression.symbol) {
indySamConversionIntrinsic -> updateIndySamConversionIntrinsicCall(expression)
jvmIndyLambdaMetafactoryIntrinsic -> rewriteIndyLambdaMetafactoryCall(expression)
else -> super.visitCall(expression)
}
}
@@ -160,46 +182,86 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
/**
* @see FunctionReferenceLowering.wrapWithIndySamConversion
*/
private fun updateIndySamConversionIntrinsicCall(call: IrCall): IrCall {
private fun rewriteIndyLambdaMetafactoryCall(call: IrCall): IrCall {
fun fail(message: String): Nothing =
throw AssertionError("$message, call:\n${call.dump()}")
// We expect:
// `<jvm-indy-sam-conversion>`<samType>(method)
// where
// - 'samType' is a substituted SAM type;
// - 'method' is an IrFunctionReference to an actual method that should be called,
// with arguments captured by closure stored as function reference arguments.
// We replace it with JVM INVOKEDYNAMIC intrinsic.
val startOffset = call.startOffset
val endOffset = call.endOffset
val samType = call.getTypeArgument(0) as? IrSimpleType
?: fail("'samType' is expected to be a simple type")
val samMethod = samType.getSingleAbstractMethod()
?: fail("'${samType.render()}' is not a SAM-type")
val irFunRef = call.getValueArgument(0) as? IrFunctionReference
?: fail("'method' is expected to be 'IrFunctionReference'")
val funSymbol = irFunRef.symbol
val samMethodRef = call.getValueArgument(0) as? IrRawFunctionReference
?: fail("'samMethodType' should be 'IrRawFunctionReference'")
val implFunRef = call.getValueArgument(1) as? IrFunctionReference
?: fail("'implMethodReference' is expected to be 'IrFunctionReference'")
val implFunSymbol = implFunRef.symbol
val instanceMethodRef = call.getValueArgument(2) as? IrRawFunctionReference
?: fail("'instantiatedMethodType' is expected to be 'IrRawFunctionReference'")
val dynamicCall = wrapClosureInDynamicCall(samType, samMethod, irFunRef)
return context.createJvmIrBuilder(
funSymbol, // TODO actual symbol for outer scope
startOffset, endOffset
).run {
val samMethodType = jvmOriginalMethodType(samMethod.symbol)
val irRawFunRef = irRawFunctionReferefence(irFunRef.type, funSymbol)
val instanceMethodType = jvmSubstitutedMethodType(samType, samMethod.symbol)
jvmInvokeDynamic(
dynamicCall,
lambdaMetafactoryHandle,
listOf(samMethodType, irRawFunRef, instanceMethodType)
)
val extraOverriddenMethods = run {
val extraOverriddenMethodVararg = call.getValueArgument(3) as? IrVararg
?: fail("'extraOverriddenMethodTypes' is expected to be 'IrVararg'")
extraOverriddenMethodVararg.elements.map {
val ref = it as? IrRawFunctionReference
?: fail("'extraOverriddenMethodTypes' elements are expected to be 'IrRawFunctionReference'")
ref.symbol.owner as? IrSimpleFunction
?: fail("Extra overridden method is expected to be 'IrSimpleFunction': ${ref.symbol.owner.render()}")
}
}
val samMethod = samMethodRef.symbol.owner as? IrSimpleFunction
?: fail("SAM method is expected to be 'IrSimpleFunction': ${samMethodRef.symbol.owner.render()}")
val instanceMethod = instanceMethodRef.symbol.owner as? IrSimpleFunction
?: fail("Instance method is expected to be 'IrSimpleFunction': ${instanceMethodRef.symbol.owner.render()}")
val dynamicCall = wrapClosureInDynamicCall(samType, samMethod, implFunRef)
val requiredBridges = getOverriddenMethodsRequiringBridges(instanceMethod, samMethod, extraOverriddenMethods)
return context.createJvmIrBuilder(implFunSymbol, startOffset, endOffset).run {
val samMethodType = jvmOriginalMethodType(samMethod.symbol)
val irRawFunRef = irRawFunctionReferefence(implFunRef.type, implFunSymbol)
val instanceMethodType = jvmOriginalMethodType(instanceMethodRef.symbol)
if (requiredBridges.isNotEmpty()) {
val bridgeMethodTypes = requiredBridges.map { jvmOriginalMethodType(it.symbol) }
jvmInvokeDynamic(
dynamicCall,
jdkAltMetafactoryHandle,
listOf(
samMethodType, irRawFunRef, instanceMethodType,
irInt(LambdaMetafactory.FLAG_BRIDGES),
irInt(requiredBridges.size)
) + bridgeMethodTypes
)
} else {
jvmInvokeDynamic(
dynamicCall,
jdkMetafactoryHandle,
listOf(samMethodType, irRawFunRef, instanceMethodType)
)
}
}
}
private fun getOverriddenMethodsRequiringBridges(
instanceMethod: IrSimpleFunction,
samMethod: IrSimpleFunction,
extraOverriddenMethods: List<IrSimpleFunction>
): Collection<IrSimpleFunction> {
val jvmInstanceMethod = context.methodSignatureMapper.mapAsmMethod(instanceMethod)
val jvmSamMethod = context.methodSignatureMapper.mapAsmMethod(samMethod)
val signatureToNonFakeOverride = LinkedHashMap<Method, IrSimpleFunction>()
for (overridden in extraOverriddenMethods) {
val jvmOverriddenMethod = context.methodSignatureMapper.mapAsmMethod(overridden)
if (jvmOverriddenMethod != jvmInstanceMethod && jvmOverriddenMethod != jvmSamMethod) {
signatureToNonFakeOverride[jvmOverriddenMethod] = overridden
}
}
return signatureToNonFakeOverride.values
}
private fun wrapClosureInDynamicCall(
@@ -96,6 +96,7 @@ fun IrType.isArray(): Boolean = isNotNullClassType(IdSignatureValues.array)
fun IrType.isNullableArray(): Boolean = isNullableClassType(IdSignatureValues.array)
fun IrType.isCollection(): Boolean = isNotNullClassType(IdSignatureValues.collection)
fun IrType.isNothing(): Boolean = isNotNullClassType(IdSignatureValues.nothing)
fun IrType.isNullableNothing(): Boolean = isNullableClassType(IdSignatureValues.nothing)
fun IrType.isPrimitiveType(hasQuestionMark: Boolean = false): Boolean =
(this is IrSimpleType && hasQuestionMark == this.hasQuestionMark) &&
@@ -600,6 +600,15 @@ open class DeepCopyIrTreeWithSymbols(
}.copyAttributes(expression)
}
override fun visitRawFunctionReference(expression: IrRawFunctionReference): IrRawFunctionReference {
val symbol = symbolRemapper.getReferencedFunction(expression.symbol)
return IrRawFunctionReferenceImpl(
expression.startOffset, expression.endOffset,
expression.type.remapType(),
symbol
).copyAttributes(expression)
}
override fun visitPropertyReference(expression: IrPropertyReference): IrPropertyReference =
IrPropertyReferenceImpl(
expression.startOffset, expression.endOffset,