JVM_IR: Generate $$forInline companion for suspend inline functions

Set isInline flag for $$forInline functions. Otherwise, SMAP will not be generated
for inner objects/lambdas, leading to error in inliner.
This commit is contained in:
Ilmir Usmanov
2019-12-23 16:17:11 +01:00
parent 4ef2ecf9a9
commit 64c1446fbe
11 changed files with 109 additions and 51 deletions
@@ -1,17 +1,6 @@
/*
* Copyright 2010-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* Copyright 2010-2020 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.codegen.inline
@@ -105,6 +105,7 @@ class JvmBackendContext(
val continuationClassBuilders = mutableMapOf<IrSimpleFunction, ClassBuilder>()
val suspendFunctionOriginalToView = mutableMapOf<IrFunction, IrFunction>()
val suspendFunctionViewToOriginal = mutableMapOf<IrFunction, IrFunction>()
val originalToForInline = mutableMapOf<IrFunction, IrFunction>()
val fakeContinuation: IrExpression = createFakeContinuation(this)
val staticDefaultStubs = mutableMapOf<IrFunctionSymbol, IrFunction>()
@@ -407,13 +407,13 @@ class ExpressionCodegen(
expression.dispatchReceiver?.let { receiver ->
val type = if ((expression as? IrCall)?.superQualifierSymbol != null) receiver.asmType else callable.owner
callGenerator.genValueAndPut(callee.dispatchReceiverParameter!!, receiver, type, this, data)
continuationAwareGenValueAndPut(receiver, type, callee.dispatchReceiverParameter!!, data, callGenerator)
}
expression.extensionReceiver?.let { receiver ->
val type = callable.signature.valueParameters.singleOrNull { it.kind == JvmMethodParameterKind.RECEIVER }?.asmType
?: error("No single extension receiver parameter: ${callable.signature.valueParameters}")
callGenerator.genValueAndPut(callee.extensionReceiverParameter!!, receiver, type, this, data)
continuationAwareGenValueAndPut(receiver, type, callee.extensionReceiverParameter!!, data, callGenerator)
}
callGenerator.beforeValueParametersStart()
@@ -421,7 +421,7 @@ class ExpressionCodegen(
val arg = expression.getValueArgument(i)
val parameterType = callable.valueParameterTypes[i]
require(arg != null) { "Null argument in ExpressionCodegen for parameter ${irParameter.render()}" }
callGenerator.genValueAndPut(irParameter, arg, parameterType, this, data)
continuationAwareGenValueAndPut(arg, parameterType, irParameter, data, callGenerator)
}
expression.markLineNumber(true)
@@ -431,7 +431,14 @@ class ExpressionCodegen(
addSuspendMarker(mv, isStartNotEnd = true)
}
callGenerator.genCall(callable, this, expression)
if (irFunction.isInvokeSuspendOfContinuation()) {
// Do not inline callee to continuation, instead, call it
with(callable) {
mv.visitMethodInsn(invokeOpcode, owner.internalName, asmMethod.name, asmMethod.descriptor, isInterfaceMethod)
}
} else {
callGenerator.genCall(callable, this, expression)
}
if (callee.shouldGenerateSuspendMarkers()) {
// Check return type of non-lowered suspend call, in order to replace the result of the call with Unit,
@@ -465,6 +472,23 @@ class ExpressionCodegen(
}
}
// In order to support java interop of inline suspend functions, we generate continuations of inline suspend functions.
// They should behave as ordinary suspend functions, i.e. we should not inline the content of the inline function into continuation.
// Thus, we should put its arguments to stack.
private fun continuationAwareGenValueAndPut(
arg: IrExpression,
parameterType: Type,
irParameter: IrValueParameter,
data: BlockInfo,
callGenerator: IrCallGenerator
) {
if (irFunction.isInvokeSuspendOfContinuation()) {
gen(arg, parameterType, irParameter.type, data)
} else {
callGenerator.genValueAndPut(irParameter, arg, parameterType, this, data)
}
}
private fun IrFunction.shouldGenerateSuspendMarkers(): Boolean {
if (!isSuspend) return false
if (irFunction.shouldNotContainSuspendMarkers()) return false
@@ -14,6 +14,9 @@ import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_IMPL_NAME_SUFFIX
import org.jetbrains.kotlin.codegen.mangleNameIfNeeded
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_IMPL_NAME_SUFFIX
import org.jetbrains.kotlin.codegen.inline.coroutines.FOR_INLINE_SUFFIX
import org.jetbrains.kotlin.codegen.visitAnnotableParameterCount
import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.descriptors.Modality
@@ -31,6 +34,7 @@ import org.jetbrains.kotlin.resolve.jvm.annotations.SYNCHRONIZED_ANNOTATION_FQ_N
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterKind
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.kotlin.utils.sure
import org.jetbrains.org.objectweb.asm.MethodVisitor
@@ -125,17 +129,17 @@ open class FunctionCodegen(
// We do not generate continuation and state-machine for synthetic accessors, bridges, and delegated members,
// in a sense, they are tail-call
!isKnownToBeTailCall() &&
// TODO: We should generate two versions of inline suspend function: one with state-machine and one without
!isInline &&
// This is suspend lambda parameter of inline function
origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA &&
// This is just a template for inliner
origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE &&
// Continuations are generated for suspendImpls
parentAsClass.functions.none { it.name.asString() == name.asString() + SUSPEND_IMPL_NAME_SUFFIX }
parentAsClass.functions.none { it.name.asString() == name.asString() + SUSPEND_IMPL_NAME_SUFFIX } &&
// $$forInline functions never have a continuation
origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE
private fun continuationClass(): IrClass =
irFunction.body!!.statements[0] as IrClass
irFunction.body!!.statements.firstIsInstance() ?: error("No continuation class generated for ${irFunction.name}")
private fun IrFunction.getVisibilityForDefaultArgumentStub(): Int =
when (visibility) {
@@ -14,6 +14,7 @@ import org.jetbrains.kotlin.codegen.ClassBuilder
import org.jetbrains.kotlin.codegen.OwnerKind
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
import org.jetbrains.kotlin.codegen.inline.*
import org.jetbrains.kotlin.codegen.inline.coroutines.FOR_INLINE_SUFFIX
import org.jetbrains.kotlin.codegen.serialization.JvmSerializationBindings
import org.jetbrains.kotlin.codegen.state.GenerationState
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
@@ -23,11 +24,9 @@ import org.jetbrains.kotlin.incremental.components.LookupLocation
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
import org.jetbrains.kotlin.ir.expressions.IrMemberAccessExpression
import org.jetbrains.kotlin.ir.util.isSuspend
import org.jetbrains.kotlin.ir.util.nameForIrSerialization
import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.psi.KtElement
import org.jetbrains.kotlin.resolve.DescriptorUtils
@@ -35,6 +34,7 @@ import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
import org.jetbrains.kotlin.resolve.source.getPsi
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import org.jetbrains.org.objectweb.asm.*
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import org.jetbrains.org.objectweb.asm.commons.Method
@@ -80,10 +80,21 @@ class IrSourceCompilerForInline(
private fun makeInlineNode(function: IrFunction, classCodegen: ClassCodegen, isLambda: Boolean): SMAPAndMethodNode {
var node: MethodNode? = null
val functionCodegen = object : FunctionCodegen(function, classCodegen, codegen.takeIf { isLambda }) {
val forInlineFunctionView =
if (function.isSuspend)
codegen.context.originalToForInline[codegen.context.suspendFunctionViewToOriginal[function]] ?: function
else function
val functionCodegen = object : FunctionCodegen(forInlineFunctionView, classCodegen, codegen.takeIf { isLambda }) {
override fun createMethod(flags: Int, signature: JvmMethodGenericSignature): MethodVisitor {
val asmMethod = signature.asmMethod
node = MethodNode(Opcodes.API_VERSION, flags, asmMethod.name, asmMethod.descriptor, signature.genericsSignature, null)
node = MethodNode(
Opcodes.API_VERSION,
flags,
asmMethod.name.removeSuffix(FOR_INLINE_SUFFIX),
asmMethod.descriptor,
signature.genericsSignature,
null
)
return wrapWithMaxLocalCalc(node!!)
}
}
@@ -30,7 +30,7 @@ import org.jetbrains.kotlin.load.java.JavaVisibilities
import org.jetbrains.kotlin.load.java.JvmAbi
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.resolve.checkers.ExpectedActualDeclarationChecker
import org.jetbrains.kotlin.resolve.inline.INLINE_ONLY_ANNOTATION_FQ_NAME
import org.jetbrains.kotlin.resolve.inline.*
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmClassSignature
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
import org.jetbrains.kotlin.utils.addIfNotNull
@@ -279,9 +279,13 @@ fun IrDeclarationWithVisibility.isInlineOnlyOrReifiable(): Boolean =
this is IrFunction && (isReifiable() || isInlineOnly())
fun IrDeclarationWithVisibility.isEffectivelyInlineOnly(): Boolean =
isInlineOnlyOrReifiable() ||
(this is IrSimpleFunction && isSuspend && isInline &&
(valueParameters.any { it.isCrossinline } || visibility === Visibilities.PRIVATE))
isInlineOnlyOrReifiable() || isInlineOnlyPrivateInBytecode()
fun IrDeclarationWithVisibility.isInlineOnlyPrivateInBytecode(): Boolean =
(this is IrFunction && isInlineOnly()) || isPrivateInlineSuspend()
private fun IrDeclarationWithVisibility.isPrivateInlineSuspend(): Boolean =
this is IrFunction && isSuspend && isInline && visibility == Visibilities.PRIVATE
fun IrFunction.isInlineOnly() =
isInline && hasAnnotation(INLINE_ONLY_ANNOTATION_FQ_NAME)
@@ -47,16 +47,10 @@ internal val addContinuationPhase = makeIrFilePhase(
)
private class AddContinuationLowering(private val context: JvmBackendContext) : FileLoweringPass {
private val functionsToAdd = mutableMapOf<IrClass, MutableSet<IrFunction>>()
override fun lower(irFile: IrFile) {
val (suspendLambdas, inlineLambdas) = markSuspendLambdas(irFile)
transformSuspendFunctions(irFile, (suspendLambdas.map { it.function } + inlineLambdas).toSet())
for ((clazz, functions) in functionsToAdd) {
for (function in functions) {
clazz.declarations.add(function)
}
}
transformReferencesToSuspendLambdas(irFile, suspendLambdas)
}
@@ -533,11 +527,19 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
return static
}
// TODO: Generate two copies of inline suspend functions
private fun transformSuspendFunctions(irFile: IrFile, suspendAndInlineLambdas: Set<IrFunction>) {
irFile.transformChildrenVoid(object : IrElementTransformerVoid() {
private val functionsStack = arrayListOf<IrFunction>()
private val suspendFunctionsCapturingCrossinline = mutableSetOf<IrFunction>()
private val functionsToAdd = arrayListOf<MutableSet<IrFunction>>()
override fun visitClass(declaration: IrClass): IrStatement {
functionsToAdd.push(mutableSetOf())
val res = super.visitClass(declaration)
(res as IrClass).declarations.addAll(functionsToAdd.peek()!!)
functionsToAdd.pop()
return res
}
override fun visitFunction(declaration: IrFunction): IrStatement {
functionsStack.push(declaration)
@@ -546,27 +548,34 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
if (skip(function)) return function
function as IrSimpleFunction
if (function in suspendFunctionsCapturingCrossinline) {
val newFunction = buildFun {
if (function in suspendFunctionsCapturingCrossinline || function.isInline) {
val newFunction = buildFunWithDescriptorForInlining(function.descriptor) {
name = Name.identifier(function.name.asString() + FOR_INLINE_SUFFIX)
returnType = function.returnType
modality = function.modality
isSuspend = function.isSuspend
origin = JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE
isInline = function.isInline
origin =
if (function.isInline) JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE
else JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE
}.apply {
copyTypeParameters(function.typeParameters)
dispatchReceiverParameter = function.dispatchReceiverParameter?.copyTo(this)
extensionReceiverParameter = function.extensionReceiverParameter?.copyTo(this)
function.valueParameters.mapTo(valueParameters) { it.copyTo(this) }
body = function.copyBodyTo(this)
copyAttributes(function)
}
registerNewFunction(function.parentAsClass, newFunction)
registerNewFunction(newFunction)
if (function.isInline) {
context.originalToForInline[function] = newFunction
}
}
val newFunction = if (function.isOverridable) {
// Create static method for the suspend state machine method so that reentering the method
// does not lead to virtual dispatch to the wrong method.
registerNewFunction(function.parentAsClass, function)
registerNewFunction(function)
createStaticSuspendImpl(function)
} else function
@@ -584,11 +593,15 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
}
private fun skip(function: IrFunction) =
!function.isSuspend || function in suspendAndInlineLambdas || function.isInline || function.body == null ||
!function.isSuspend || function in suspendAndInlineLambdas || function.body == null ||
function.origin == JvmLoweredDeclarationOrigin.DEFAULT_IMPLS_BRIDGE ||
function.origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER ||
function.parentAsClass.origin == JvmLoweredDeclarationOrigin.FUNCTION_REFERENCE_IMPL
private fun registerNewFunction(function: IrFunction) {
functionsToAdd.peek()!!.add(function)
}
override fun visitFieldAccess(expression: IrFieldAccessExpression): IrExpression {
val result = super.visitFieldAccess(expression)
val function = functionsStack.peek() ?: return result
@@ -602,10 +615,6 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
})
}
private fun registerNewFunction(container: IrClass, function: IrFunction) {
functionsToAdd.getOrPut(container) { mutableSetOf() }.add(function)
}
private fun markSuspendLambdas(irElement: IrElement): Pair<List<SuspendLambdaInfo>, List<IrFunction>> {
val suspendLambdas = arrayListOf<SuspendLambdaInfo>()
val capturesCrossinline = mutableSetOf<IrCallableReference>()
@@ -1,5 +1,4 @@
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// COMMON_COROUTINES_TEST
// WITH_RUNTIME
@@ -1,5 +1,4 @@
// IGNORE_BACKEND_FIR: JVM_IR
// IGNORE_BACKEND: JVM_IR
// WITH_COROUTINES
// WITH_RUNTIME
// COMMON_COROUTINES_TEST
@@ -31,6 +31,14 @@ final class InlineWithoutStateMachineKt$suspendHere$1 {
public final @org.jetbrains.annotations.Nullable method invokeSuspend(@org.jetbrains.annotations.NotNull p0: java.lang.Object): java.lang.Object
}
@kotlin.Metadata
final class InlineWithoutStateMachineKt$suspendThere$1 {
field label: int
@org.jetbrains.annotations.NotNull field result: java.lang.Object
public method <init>(p0: kotlin.coroutines.Continuation): void
public final @org.jetbrains.annotations.Nullable method invokeSuspend(@org.jetbrains.annotations.NotNull p0: java.lang.Object): java.lang.Object
}
@kotlin.Metadata
public final class InlineWithoutStateMachineKt {
inner class InlineWithoutStateMachineKt$box$1
@@ -38,5 +46,6 @@ public final class InlineWithoutStateMachineKt {
public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void
public final static @org.jetbrains.annotations.Nullable method complexSuspend(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method suspendHere(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method suspendThere$$forInline(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method suspendThere(@org.jetbrains.annotations.NotNull p0: java.lang.String, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object
}
@@ -76,6 +76,14 @@ final class flow/InnerObjectRetransformationKt$check$1 {
public final @org.jetbrains.annotations.Nullable method invokeSuspend(@org.jetbrains.annotations.NotNull p0: java.lang.Object): java.lang.Object
}
@kotlin.Metadata
final class flow/InnerObjectRetransformationKt$collect$1 {
field label: int
@org.jetbrains.annotations.NotNull field result: java.lang.Object
public method <init>(p0: kotlin.coroutines.Continuation): void
public final @org.jetbrains.annotations.Nullable method invokeSuspend(@org.jetbrains.annotations.NotNull p0: java.lang.Object): java.lang.Object
}
@kotlin.Metadata
final class flow/InnerObjectRetransformationKt$collect$2$emit$1 {
field label: int
@@ -164,7 +172,8 @@ public final class flow/InnerObjectRetransformationKt {
public final static @org.jetbrains.annotations.NotNull method box(): java.lang.String
public final static method builder(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function1): void
public final static @org.jetbrains.annotations.Nullable method check(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.Continuation): java.lang.Object
private final static @org.jetbrains.annotations.Nullable method collect(@org.jetbrains.annotations.NotNull p0: flow.Flow, @org.jetbrains.annotations.NotNull p1: kotlin.jvm.functions.Function2, @org.jetbrains.annotations.NotNull p2: kotlin.coroutines.Continuation): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method collect$$forInline(@org.jetbrains.annotations.NotNull p0: flow.Flow, @org.jetbrains.annotations.NotNull p1: kotlin.jvm.functions.Function2, @org.jetbrains.annotations.NotNull p2: kotlin.coroutines.Continuation): java.lang.Object
public final static @org.jetbrains.annotations.Nullable method collect(@org.jetbrains.annotations.NotNull p0: flow.Flow, @org.jetbrains.annotations.NotNull p1: kotlin.jvm.functions.Function2, @org.jetbrains.annotations.NotNull p2: kotlin.coroutines.Continuation): java.lang.Object
public final static @org.jetbrains.annotations.NotNull method flow(@org.jetbrains.annotations.NotNull p0: kotlin.jvm.functions.Function2): flow.Flow
public final static @org.jetbrains.annotations.NotNull method flowWith(@org.jetbrains.annotations.NotNull p0: flow.Flow, @org.jetbrains.annotations.NotNull p1: kotlin.jvm.functions.Function2): flow.Flow
}