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:
@@ -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
|
||||
|
||||
+6
@@ -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
-63
@@ -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()
|
||||
|
||||
-26
@@ -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]
|
||||
}
|
||||
}
|
||||
+14
@@ -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
|
||||
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+6
@@ -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 {
|
||||
|
||||
+5
@@ -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");
|
||||
|
||||
Generated
+5
@@ -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");
|
||||
|
||||
Generated
+5
@@ -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");
|
||||
|
||||
Reference in New Issue
Block a user