JVM: inline default lambda coercions from bridge invoke

The type of the default lambda may be a subtype of the parameter type,
so we can't really generate our own coercions at all as we don't know
the precise Kotlin type of the `invoke` method.
This commit is contained in:
pyos
2021-06-27 22:02:39 +02:00
committed by max-kammerer
parent d0b9c4ae6b
commit 100d2d629c
18 changed files with 213 additions and 221 deletions
@@ -33,9 +33,9 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
protected val invocationParamBuilder = ParametersBuilder.newBuilder()
protected val expressionMap = linkedMapOf<Int, FunctionalArgument>()
protected val maskValues = ArrayList<Int>()
protected var maskStartIndex = -1
protected var methodHandleInDefaultMethodIndex = -1
private val maskValues = ArrayList<Int>()
private var maskStartIndex = -1
private var methodHandleInDefaultMethodIndex = -1
protected fun generateStub(text: String, codegen: BaseExpressionCodegen) {
leaveTemps()
@@ -71,11 +71,16 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
private fun inlineCall(nodeAndSmap: SMAPAndMethodNode, isInlineOnly: Boolean): InlineResult {
val node = nodeAndSmap.node
if (maskStartIndex != -1) {
for (lambda in extractDefaultLambdas(node)) {
invocationParamBuilder.buildParameters().getParameterByDeclarationSlot(lambda.offset).functionalArgument = lambda
val prev = expressionMap.put(lambda.offset, lambda)
assert(prev == null) { "Lambda with offset ${lambda.offset} already exists: $prev" }
if (lambda.needReification) {
val infos = expandMaskConditionsAndUpdateVariableNodes(
node, maskStartIndex, maskValues, methodHandleInDefaultMethodIndex, computeDefaultLambdaOffsets()
)
val parameters = invocationParamBuilder.buildParameters()
for (info in infos) {
val lambda = DefaultLambda(info, sourceCompiler)
parameters.getParameterByDeclarationSlot(info.offset).functionalArgument = lambda
val prev = expressionMap.put(info.offset, lambda)
assert(prev == null) { "Lambda with offset ${info.offset} already exists: $prev" }
if (info.needReification) {
lambda.reifiedTypeParametersUsages.mergeAll(reifiedTypeInliner.reifyInstructions(lambda.node.node))
}
rememberCapturedForDefaultLambda(lambda)
@@ -136,15 +141,7 @@ abstract class InlineCodegen<out T : BaseExpressionCodegen>(
return result
}
abstract fun extractDefaultLambdas(node: MethodNode): List<DefaultLambda>
protected inline fun <T> extractDefaultLambdas(
node: MethodNode, parameters: Map<Int, T>, block: ExtractedDefaultLambda.(T) -> DefaultLambda
): List<DefaultLambda> = expandMaskConditionsAndUpdateVariableNodes(
node, maskStartIndex, maskValues, methodHandleInDefaultMethodIndex, parameters.keys
).map {
it.block(parameters[it.offset]!!)
}
abstract fun computeDefaultLambdaOffsets(): Set<Int>
private fun generateAndInsertFinallyBlocks(
intoNode: MethodNode,
@@ -6,12 +6,12 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.coroutines.isCoroutineSuperClass
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.*
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.ClassReader
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.commons.Method
import org.jetbrains.org.objectweb.asm.tree.FieldInsnNode
@@ -73,73 +73,70 @@ abstract class ExpressionLambda : LambdaInfo() {
}
}
abstract class DefaultLambda(
final override val lambdaClassType: Type,
capturedArgs: Array<Type>,
val offset: Int,
val needReification: Boolean,
sourceCompiler: SourceCompilerForInline
) : LambdaInfo() {
final override val isSuspend
get() = false // TODO: it should probably be true sometimes, but it never was
final override val isBoundCallableReference: Boolean
final override val capturedVars: List<CapturedParamDesc>
final override val invokeMethod: Method
class DefaultLambda(info: ExtractedDefaultLambda, sourceCompiler: SourceCompilerForInline) : LambdaInfo() {
val needReification = info.needReification // TODO: remove this
override val lambdaClassType: Type = info.type
override val isSuspend: Boolean get() = false // TODO: it should probably be true sometimes, but it never was
override val isBoundCallableReference: Boolean
override val capturedVars: List<CapturedParamDesc>
override val invokeMethod: Method
get() = Method(node.node.name, node.node.desc)
private val nullableAnyType = sourceCompiler.state.module.builtIns.nullableAnyType
override val invokeMethodParameters: List<KotlinType>
get() = List(invokeMethod.argumentTypes.size) { nullableAnyType }
override val invokeMethodReturnType: KotlinType
get() = nullableAnyType
val originalBoundReceiverType: Type?
protected val isPropertyReference: Boolean
protected val isFunctionReference: Boolean
init {
val classBytes = loadClass(sourceCompiler)
val classBytes =
sourceCompiler.state.inlineCache.classBytes.getOrPut(lambdaClassType.internalName) {
loadClassBytesByInternalName(sourceCompiler.state, lambdaClassType.internalName)
}
val superName = ClassReader(classBytes).superName
isPropertyReference = superName in PROPERTY_REFERENCE_SUPER_CLASSES
isFunctionReference = superName == FUNCTION_REFERENCE.internalName || superName == FUNCTION_REFERENCE_IMPL.internalName
// TODO: suspend lambdas are their own continuations, so the body is pre-inlined into `invokeSuspend`
// and thus can't be detangled from the state machine. To make them inlinable, this needs to be redesigned.
// See `SuspendLambdaLowering`.
require(!sourceCompiler.state.languageVersionSettings.isCoroutineSuperClass(superName)) {
"suspend default lambda ${lambdaClassType.internalName} cannot be inlined; use a function reference instead"
}
val constructorMethod = Method("<init>", Type.VOID_TYPE, capturedArgs)
val constructorMethod = Method("<init>", Type.VOID_TYPE, info.capturedArgs)
val constructor = getMethodNode(classBytes, lambdaClassType, constructorMethod)?.node
assert(constructor != null || capturedArgs.isEmpty()) {
assert(constructor != null || info.capturedArgs.isEmpty()) {
"can't find constructor '$constructorMethod' for default lambda '${lambdaClassType.internalName}'"
}
val isPropertyReference = superName in PROPERTY_REFERENCE_SUPER_CLASSES
val isReference = isPropertyReference ||
superName == FUNCTION_REFERENCE.internalName || superName == FUNCTION_REFERENCE_IMPL.internalName
// This only works for primitives but not inline classes, since information about the Kotlin type of the bound
// receiver is not present anywhere. This is why with JVM_IR the constructor argument of bound references
// is already `Object`, and this field is never used.
originalBoundReceiverType =
capturedArgs.singleOrNull()?.takeIf { (isFunctionReference || isPropertyReference) && AsmUtil.isPrimitive(it) }
info.capturedArgs.singleOrNull()?.takeIf { isReference && AsmUtil.isPrimitive(it) }
capturedVars =
if (isFunctionReference || isPropertyReference)
capturedArgs.singleOrNull()?.let {
listOf(capturedParamDesc(AsmUtil.RECEIVER_PARAMETER_NAME, AsmUtil.boxType(it), isSuspend = false))
if (isReference)
info.capturedArgs.singleOrNull()?.let {
// See `InlinedLambdaRemapper`
listOf(capturedParamDesc(AsmUtil.RECEIVER_PARAMETER_NAME, OBJECT_TYPE, isSuspend = false))
} ?: emptyList()
else
constructor?.findCapturedFieldAssignmentInstructions()?.map { fieldNode ->
capturedParamDesc(fieldNode.name, Type.getType(fieldNode.desc), isSuspend = false)
}?.toList() ?: emptyList()
isBoundCallableReference = (isFunctionReference || isPropertyReference) && capturedVars.isNotEmpty()
}
private fun loadClass(sourceCompiler: SourceCompilerForInline): ByteArray =
sourceCompiler.state.inlineCache.classBytes.getOrPut(lambdaClassType.internalName) {
loadClassBytesByInternalName(sourceCompiler.state, lambdaClassType.internalName)
}
// Returns whether the loaded invoke is erased, i.e. the name equals the fallback and all types are `Object`.
protected fun loadInvoke(sourceCompiler: SourceCompilerForInline, erasedName: String, actualMethod: Method): Boolean {
node = getMethodNodeImprecise(loadClass(sourceCompiler), lambdaClassType, actualMethod, erasedName)
?: error("Can't find method '$actualMethod' in '${lambdaClassType.internalName}'")
return invokeMethod.run { name == erasedName && returnType == OBJECT_TYPE && argumentTypes.all { it == OBJECT_TYPE } }
isBoundCallableReference = isReference && capturedVars.isNotEmpty()
node = loadDefaultLambdaBody(classBytes, lambdaClassType, isPropertyReference)
}
private companion object {
fun getMethodNodeImprecise(classBytes: ByteArray, classType: Type, method: Method, erasedName: String) =
getMethodNode(classBytes, classType) { it, access ->
(it.name == method.name || it.name == erasedName) &&
it.argumentTypes.size == method.argumentTypes.size && access.and(Opcodes.ACC_SYNTHETIC) == 0
}
val PROPERTY_REFERENCE_SUPER_CLASSES =
listOf(
PROPERTY_REFERENCE0, PROPERTY_REFERENCE1, PROPERTY_REFERENCE2,
@@ -5,16 +5,13 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
import org.jetbrains.kotlin.builtins.isSuspendFunctionTypeOrSubtype
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.DescriptorAsmUtil.getMethodAsmFlags
import org.jetbrains.kotlin.codegen.binding.CalculatedClosure
import org.jetbrains.kotlin.codegen.binding.CodegenBinding
import org.jetbrains.kotlin.codegen.coroutines.getOrCreateJvmSuspendFunctionView
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
@@ -28,12 +25,9 @@ import org.jetbrains.kotlin.resolve.inline.isInlineOnly
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
import org.jetbrains.org.objectweb.asm.tree.MethodNode
class PsiInlineCodegen(
codegen: ExpressionCodegen,
@@ -95,8 +89,7 @@ class PsiInlineCodegen(
private val hiddenParameters = mutableListOf<Pair<ParameterInfo, Int>>()
override fun processHiddenParameters() {
val contextKind = (sourceCompiler as PsiSourceCompilerForInline).context.contextKind
if (getMethodAsmFlags(functionDescriptor, contextKind, state) and Opcodes.ACC_STATIC == 0) {
if (!DescriptorAsmUtil.isStaticMethod((sourceCompiler as PsiSourceCompilerForInline).context.contextKind, functionDescriptor)) {
hiddenParameters += invocationParamBuilder.addNextParameter(methodOwner, false, actualDispatchReceiver) to
codegen.frameMap.enterTemp(methodOwner)
}
@@ -199,10 +192,14 @@ class PsiInlineCodegen(
override fun reorderArgumentsIfNeeded(actualArgsWithDeclIndex: List<ArgumentAndDeclIndex>, valueParameterTypes: List<Type>) = Unit
override fun extractDefaultLambdas(node: MethodNode): List<DefaultLambda> =
extractDefaultLambdas(node, extractDefaultLambdaOffsetAndDescriptor(jvmSignature, functionDescriptor)) { parameter ->
PsiDefaultLambda(type, capturedArgs, parameter, offset, needReification, sourceCompiler)
}
override fun computeDefaultLambdaOffsets(): Set<Int> {
val contextKind = (sourceCompiler as PsiSourceCompilerForInline).context.contextKind
return parameterOffsets(DescriptorAsmUtil.isStaticMethod(contextKind, functionDescriptor), jvmSignature.valueParameters)
.drop(jvmSignature.valueParameters.indexOfFirst { it.kind == JvmMethodParameterKind.VALUE })
.filterIndexedTo(mutableSetOf()) { index, _ ->
functionDescriptor.valueParameters[index].let { InlineUtil.isInlineParameter(it) && it.declaresDefaultValue() }
}
}
}
private val FunctionDescriptor.explicitParameters
@@ -301,32 +298,3 @@ class PsiExpressionLambda(
val isPropertyReference: Boolean
get() = propertyReferenceInfo != null
}
class PsiDefaultLambda(
lambdaClassType: Type,
capturedArgs: Array<Type>,
parameterDescriptor: ValueParameterDescriptor,
offset: Int,
needReification: Boolean,
sourceCompiler: SourceCompilerForInline
) : DefaultLambda(lambdaClassType, capturedArgs, offset, needReification, sourceCompiler) {
private val invokeMethodDescriptor: FunctionDescriptor
override val invokeMethodParameters: List<KotlinType?>
get() = invokeMethodDescriptor.explicitParameters.map { it.returnType }
override val invokeMethodReturnType: KotlinType?
get() = invokeMethodDescriptor.returnType
init {
val name = if (isPropertyReference) OperatorNameConventions.GET else OperatorNameConventions.INVOKE
val descriptor = parameterDescriptor.type.memberScope
.getContributedFunctions(OperatorNameConventions.INVOKE, NoLookupLocation.FROM_BACKEND)
.single()
.let { if (parameterDescriptor.type.isSuspendFunctionType) getOrCreateJvmSuspendFunctionView(it, sourceCompiler.state) else it }
// This is technically wrong as it always uses `invoke`, but `loadInvoke` will fall back to `get` which is never mangled...
val asmMethod = sourceCompiler.state.typeMapper.mapAsmMethod(descriptor)
val invokeIsErased = loadInvoke(sourceCompiler, name.asString(), asmMethod)
invokeMethodDescriptor = if (invokeIsErased) descriptor.original else descriptor
}
}
@@ -16,20 +16,16 @@
package org.jetbrains.kotlin.codegen.inline
import org.jetbrains.kotlin.codegen.DescriptorAsmUtil
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.inline.ReifiedTypeInliner.Companion.isNeedClassReificationMarker
import org.jetbrains.kotlin.codegen.optimization.common.InsnSequence
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.inline.InlineUtil
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.org.objectweb.asm.Opcodes
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
import org.jetbrains.org.objectweb.asm.tree.*
import kotlin.math.max
private data class Condition(
val mask: Int, val constant: Int,
@@ -41,25 +37,6 @@ private data class Condition(
val varIndex = varInsNode?.`var` ?: 0
}
fun extractDefaultLambdaOffsetAndDescriptor(
jvmSignature: JvmMethodSignature,
functionDescriptor: FunctionDescriptor
): Map<Int, ValueParameterDescriptor> {
val valueParameters = jvmSignature.valueParameters
val containingDeclaration = functionDescriptor.containingDeclaration
val kind =
if (DescriptorUtils.isInterface(containingDeclaration)) OwnerKind.DEFAULT_IMPLS
else OwnerKind.getMemberOwnerKind(containingDeclaration)
val parameterOffsets = parameterOffsets(DescriptorAsmUtil.isStaticMethod(kind, functionDescriptor), valueParameters)
val valueParameterOffset = valueParameters.takeWhile { it.kind != JvmMethodParameterKind.VALUE }.size
return functionDescriptor.valueParameters.filter {
InlineUtil.isInlineParameter(it) && it.declaresDefaultValue()
}.associateBy {
parameterOffsets[valueParameterOffset + it.index]
}
}
class ExtractedDefaultLambda(val type: Type, val capturedArgs: Array<Type>, val offset: Int, val needReification: Boolean)
fun expandMaskConditionsAndUpdateVariableNodes(
@@ -202,3 +179,68 @@ private fun defaultLambdaFakeCallStub(args: Array<Type>, lambdaOffset: Int): Met
false
)
}
fun loadDefaultLambdaBody(classBytes: ByteArray, classType: Type, isPropertyReference: Boolean): SMAPAndMethodNode {
// In general we can't know what the correct unboxed `invoke` is, and what Kotlin types its arguments have,
// as the type of this object may be any subtype of the parameter's type. All we know is that Function<N>
// has to have a `invoke` that takes `Object`s and returns an `Object`; everything else needs to be figured
// out from its contents. TODO: for > 22 arguments, the only argument is an array. `MethodInliner` can't do that.
val invokeName = if (isPropertyReference) OperatorNameConventions.GET.asString() else OperatorNameConventions.INVOKE.asString()
val invokeNode = getMethodNode(classBytes, classType) {
it.name == invokeName && it.returnType == AsmTypes.OBJECT_TYPE && it.argumentTypes.all { arg -> arg == AsmTypes.OBJECT_TYPE }
} ?: error("can't find erased invoke '$invokeName(Object...): Object' in default lambda '${classType.internalName}'")
return if (invokeNode.node.access.and(Opcodes.ACC_BRIDGE) == 0)
invokeNode
else
invokeNode.node.inlineBridge(classBytes, classType)
}
private fun MethodNode.inlineBridge(classBytes: ByteArray, classType: Type): SMAPAndMethodNode {
// If the erased invoke is a bridge, we need to locate the unboxed invoke and inline it. As mentioned above,
// we don't know what the Kotlin types of its arguments/returned value are, so we can't generate our own
// boxing/unboxing code; luckily, the bridge already has that.
val invokeInsn = instructions.singleOrNull { it is MethodInsnNode && it.owner == classType.internalName } as MethodInsnNode?
?: error("no single invoke of method on this in '${name}${desc}' of default lambda '${classType.internalName}'")
val targetMethod = Method(invokeInsn.name, invokeInsn.desc)
val target = getMethodNode(classBytes, classType, targetMethod)
?: error("can't find non-bridge invoke '$targetMethod' in default lambda '${classType.internalName}")
// Store unboxed/casted arguments in the correct variable slots
val targetArgs = targetMethod.argumentTypes
val targetArgsSize = targetArgs.sumOf { it.size } + if (target.node.access.and(Opcodes.ACC_STATIC) == 0) 1 else 0
var offset = targetArgsSize
for (type in targetArgs.reversed()) {
offset -= type.size
instructions.insertBefore(invokeInsn, VarInsnNode(type.getOpcode(Opcodes.ISTORE), offset))
}
if (target.node.access.and(Opcodes.ACC_STATIC) == 0) {
instructions.insertBefore(invokeInsn, InsnNode(Opcodes.POP)) // this
}
// Remap returns and ranges for arguments' LVT entries
val invokeLabel = LabelNode()
val returnLabel = LabelNode()
instructions.insertBefore(invokeInsn, invokeLabel)
instructions.insert(invokeInsn, returnLabel)
for (insn in target.node.instructions) {
if (insn.opcode in Opcodes.IRETURN..Opcodes.RETURN) {
target.node.instructions.set(insn, JumpInsnNode(Opcodes.GOTO, returnLabel))
}
}
for (local in target.node.localVariables) {
if (local.index < targetArgsSize) {
local.start = invokeLabel
local.end = returnLabel
}
}
// Insert contents of the method into the bridge
instructions.filterIsInstance<LineNumberNode>().forEach { instructions.remove(it) } // those are not meaningful
instructions.insertBefore(invokeInsn, target.node.instructions)
instructions.remove(invokeInsn)
localVariables = target.node.localVariables
tryCatchBlocks = target.node.tryCatchBlocks
maxLocals = max(maxLocals, target.node.maxLocals)
maxStack = max(maxStack, target.node.maxStack)
return SMAPAndMethodNode(this, target.classSMAP)
}
@@ -85,7 +85,7 @@ private const val INLINE_MARKER_AFTER_INLINE_SUSPEND_ID = 7
private const val INLINE_MARKER_BEFORE_UNBOX_INLINE_CLASS = 8
private const val INLINE_MARKER_AFTER_UNBOX_INLINE_CLASS = 9
internal inline fun getMethodNode(classData: ByteArray, classType: Type, crossinline match: (Method, Int) -> Boolean): SMAPAndMethodNode? {
internal inline fun getMethodNode(classData: ByteArray, classType: Type, crossinline match: (Method) -> Boolean): SMAPAndMethodNode? {
var node: MethodNode? = null
var sourceFile: String? = null
var sourceMap: String? = null
@@ -96,7 +96,7 @@ internal inline fun getMethodNode(classData: ByteArray, classType: Type, crossin
}
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
if (!match(Method(name, desc), access)) return null
if (!match(Method(name, desc))) return null
node?.let { existing ->
throw AssertionError("Can't find proper '$name' method for inline: ambiguity between '${existing.name + existing.desc}' and '${name + desc}'")
}
@@ -112,7 +112,7 @@ internal inline fun getMethodNode(classData: ByteArray, classType: Type, crossin
}
internal fun getMethodNode(classData: ByteArray, classType: Type, method: Method): SMAPAndMethodNode? =
getMethodNode(classData, classType) { it, _ -> it == method }
getMethodNode(classData, classType) { it == method }
internal fun Collection<MethodNode>.lineNumberRange(): Pair<Int, Int> {
var minLine = Int.MAX_VALUE
@@ -1948,6 +1948,12 @@ public class FirBlackBoxInlineCodegenTestGenerated extends AbstractFirBlackBoxIn
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@Test
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@Test
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
@@ -6,9 +6,7 @@
package org.jetbrains.kotlin.backend.jvm.codegen
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.ir.isInlineClassType
import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.InlineClassAbi
import org.jetbrains.kotlin.codegen.IrExpressionLambda
import org.jetbrains.kotlin.codegen.JvmKotlinType
import org.jetbrains.kotlin.codegen.StackValue
@@ -21,18 +19,13 @@ import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.ir.descriptors.toIrBasedKotlinType
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.IrTypeProjection
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.resolve.jvm.AsmTypes
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.util.OperatorNameConventions
import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.Method
import org.jetbrains.org.objectweb.asm.tree.MethodNode
class IrInlineCodegen(
codegen: ExpressionCodegen,
@@ -123,10 +116,12 @@ class IrInlineCodegen(
generateStub(text, codegen)
}
override fun extractDefaultLambdas(node: MethodNode): List<DefaultLambda> =
extractDefaultLambdas(node, extractDefaultLambdaOffsetAndDescriptor(jvmSignature, function)) { parameter ->
IrDefaultLambda(type, capturedArgs, parameter, offset, needReification, sourceCompiler as IrSourceCompilerForInline)
}
override fun computeDefaultLambdaOffsets(): Set<Int> =
parameterOffsets(function.isStatic, jvmSignature.valueParameters)
.drop(if (function.extensionReceiverParameter != null) 1 else 0)
.filterIndexedTo(mutableSetOf()) { index, _ ->
function.valueParameters[index].let { it.defaultValue != null && it.isInlineParameter() }
}
}
class IrExpressionLambdaImpl(
@@ -187,58 +182,6 @@ class IrExpressionLambdaImpl(
}
}
class IrDefaultLambda(
lambdaClassType: Type,
capturedArgs: Array<Type>,
irValueParameter: IrValueParameter,
offset: Int,
needReification: Boolean,
sourceCompiler: IrSourceCompilerForInline
) : DefaultLambda(lambdaClassType, capturedArgs, offset, needReification, sourceCompiler) {
private val typeArguments: MutableList<IrType>
override val invokeMethodParameters: List<KotlinType>
get() = typeArguments.dropLast(1).map { it.toIrBasedKotlinType() }
override val invokeMethodReturnType: KotlinType
get() = typeArguments.last().toIrBasedKotlinType()
init {
val context = sourceCompiler.codegen.context
typeArguments =
(irValueParameter.type as IrSimpleType).arguments
.mapTo(mutableListOf()) {
when (it) {
is IrTypeProjection -> it.type
else -> context.irBuiltIns.anyNType
}
}
.apply {
// Suspend function references: `suspend (A) -> B` => `invoke(A, Continuation<B>): Any?`
// TODO: default suspend lambdas are currently uninlinable due to having a state machine
if (irValueParameter.type.isSuspendFunction()) {
set(size - 1, context.ir.symbols.continuationClass.typeWith(get(size - 1)))
add(context.irBuiltIns.anyNType)
}
}
val base = if (isPropertyReference) OperatorNameConventions.GET.asString() else OperatorNameConventions.INVOKE.asString()
val name = InlineClassAbi.hashSuffix(
context.state.useOldManglingSchemeForFunctionsWithInlineClassesInSignatures,
typeArguments.dropLast(1),
typeArguments.last().takeIf { it.isInlineClassType() }
)?.let { "$base-$it" } ?: base
// TODO: while technically only the number of arguments here matters right now (see `loadInvoke`),
// it would be better to map to a non-erased signature if not a property reference.
if (loadInvoke(sourceCompiler, base, Method(name, AsmTypes.OBJECT_TYPE, Array(typeArguments.size - 1) { AsmTypes.OBJECT_TYPE }))) {
// If the loaded method is `invoke(Object, ...) -> Object`, then it expects boxed parameters and returns a boxed value.
typeArguments.replaceAll { context.irBuiltIns.anyNType }
}
}
}
fun IrExpression.isInlineIrExpression() =
when (this) {
is IrBlock -> origin.isInlineIrExpression()
@@ -1,26 +0,0 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.codegen
import org.jetbrains.kotlin.backend.jvm.ir.isInlineParameter
import org.jetbrains.kotlin.codegen.inline.parameterOffsets
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
fun extractDefaultLambdaOffsetAndDescriptor(
jvmSignature: JvmMethodSignature,
irFunction: IrFunction
): Map<Int, IrValueParameter> {
val valueParameters = jvmSignature.valueParameters
val parameterOffsets = parameterOffsets(irFunction.isStatic, valueParameters)
val valueParameterOffset = if (irFunction.extensionReceiverParameter != null) 1 else 0
return irFunction.valueParameters.filter { it.defaultValue != null && it.isInlineParameter() }.associateBy {
parameterOffsets[valueParameterOffset + it.index]
}
}
@@ -0,0 +1,14 @@
// SKIP_INLINE_CHECK_IN: inlineFun$default
// FILE: 1.kt
package test
inline class C(val value: Any?)
fun foo(x: Any?): C = x as C
inline fun inlineFun(s: (C) -> Any? = ::foo): Any? = s(C("OK"))
// FILE: 2.kt
import test.*
fun box(): String = (inlineFun() as C).value as String
@@ -1948,6 +1948,12 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@Test
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@Test
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
@@ -1948,6 +1948,12 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@Test
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@Test
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
@@ -1948,6 +1948,12 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@Test
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@Test
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
@@ -1948,6 +1948,12 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@Test
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@Test
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
@@ -1948,6 +1948,12 @@ public class JvmIrAgainstOldBoxInlineTestGenerated extends AbstractJvmIrAgainstO
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@Test
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@Test
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
@@ -1948,6 +1948,12 @@ public class JvmOldAgainstIrBoxInlineTestGenerated extends AbstractJvmOldAgainst
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@Test
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@Test
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
@@ -1540,6 +1540,11 @@ public class IrJsCodegenInlineES6TestGenerated extends AbstractIrJsCodegenInline
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/functionImportedFromObject.kt");
@@ -1540,6 +1540,11 @@ public class IrJsCodegenInlineTestGenerated extends AbstractIrJsCodegenInlineTes
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/functionImportedFromObject.kt");
@@ -1540,6 +1540,11 @@ public class JsCodegenInlineTestGenerated extends AbstractJsCodegenInlineTest {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature2.kt");
}
@TestMetadata("differentInvokeSignature3.kt")
public void testDifferentInvokeSignature3() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/differentInvokeSignature3.kt");
}
@TestMetadata("functionImportedFromObject.kt")
public void testFunctionImportedFromObject() throws Exception {
runTest("compiler/testData/codegen/boxInline/defaultValues/lambdaInlining/callableReferences/functionImportedFromObject.kt");