Do not transform state-machine when inlining
Sometimes, state-machine, generated in inline functions with crossinline parameter, is transformed, since all usages should be renamed. However, this is wrong: in this case, we will have state-machine inside state-machine. This fix addresses the issue. #KT-25893 Fixed
This commit is contained in:
+17
-127
@@ -9,8 +9,8 @@ import com.intellij.util.ArrayUtil
|
|||||||
import org.jetbrains.kotlin.codegen.AsmUtil
|
import org.jetbrains.kotlin.codegen.AsmUtil
|
||||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||||
import org.jetbrains.kotlin.codegen.StackValue
|
import org.jetbrains.kotlin.codegen.StackValue
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.*
|
import org.jetbrains.kotlin.codegen.coroutines.isCoroutineSuperClass
|
||||||
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
|
import org.jetbrains.kotlin.codegen.inline.coroutines.CoroutineTransformer
|
||||||
import org.jetbrains.kotlin.codegen.serialization.JvmCodegenStringTable
|
import org.jetbrains.kotlin.codegen.serialization.JvmCodegenStringTable
|
||||||
import org.jetbrains.kotlin.codegen.writeKotlinMetadata
|
import org.jetbrains.kotlin.codegen.writeKotlinMetadata
|
||||||
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
import org.jetbrains.kotlin.load.java.JvmAnnotationNames
|
||||||
@@ -21,10 +21,8 @@ import org.jetbrains.kotlin.metadata.jvm.JvmProtoBuf
|
|||||||
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil
|
||||||
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
|
import org.jetbrains.kotlin.metadata.jvm.serialization.JvmStringTable
|
||||||
import org.jetbrains.kotlin.protobuf.MessageLite
|
import org.jetbrains.kotlin.protobuf.MessageLite
|
||||||
import org.jetbrains.kotlin.psi.KtExpression
|
|
||||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||||
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.Companion.NO_ORIGIN
|
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin.Companion.NO_ORIGIN
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
|
||||||
import org.jetbrains.org.objectweb.asm.*
|
import org.jetbrains.org.objectweb.asm.*
|
||||||
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
|
||||||
import org.jetbrains.org.objectweb.asm.tree.*
|
import org.jetbrains.org.objectweb.asm.tree.*
|
||||||
@@ -52,6 +50,7 @@ class AnonymousObjectTransformer(
|
|||||||
val classBuilder = createRemappingClassBuilderViaFactory(inliningContext)
|
val classBuilder = createRemappingClassBuilderViaFactory(inliningContext)
|
||||||
val methodsToTransform = ArrayList<MethodNode>()
|
val methodsToTransform = ArrayList<MethodNode>()
|
||||||
val metadataReader = ReadKotlinClassHeaderAnnotationVisitor()
|
val metadataReader = ReadKotlinClassHeaderAnnotationVisitor()
|
||||||
|
lateinit var superClassName: String
|
||||||
|
|
||||||
createClassReader().accept(object : ClassVisitor(API, classBuilder.visitor) {
|
createClassReader().accept(object : ClassVisitor(API, classBuilder.visitor) {
|
||||||
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String, interfaces: Array<String>) {
|
override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String, interfaces: Array<String>) {
|
||||||
@@ -59,6 +58,7 @@ class AnonymousObjectTransformer(
|
|||||||
if (languageVersionSettings.isCoroutineSuperClass(superName)) {
|
if (languageVersionSettings.isCoroutineSuperClass(superName)) {
|
||||||
inliningContext.isContinuation = true
|
inliningContext.isContinuation = true
|
||||||
}
|
}
|
||||||
|
superClassName = superName
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
|
override fun visitInnerClass(name: String, outerName: String?, innerName: String?, access: Int) {
|
||||||
@@ -133,44 +133,23 @@ class AnonymousObjectTransformer(
|
|||||||
constructor!!, allCapturedParamBuilder, constructorParamBuilder,transformationInfo, parentRemapper
|
constructor!!, allCapturedParamBuilder, constructorParamBuilder,transformationInfo, parentRemapper
|
||||||
)
|
)
|
||||||
|
|
||||||
val crossinlineSuspendElement = capturedCrossinlineSuspendElement()
|
|
||||||
val capturesCrossinlineSuspend = (!inliningContext.isInliningLambda || inliningContext.isContinuation) &&
|
|
||||||
crossinlineSuspendElement != null
|
|
||||||
|
|
||||||
val deferringMethods = ArrayList<DeferredMethodVisitor>()
|
val deferringMethods = ArrayList<DeferredMethodVisitor>()
|
||||||
|
|
||||||
generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, parentRemapper, additionalFakeParams)
|
generateConstructorAndFields(classBuilder, allCapturedParamBuilder, constructorParamBuilder, parentRemapper, additionalFakeParams)
|
||||||
|
|
||||||
val isLambdaAlreadyGeneratedAndNotGoingToBeInlined = transformationInfo.oldClassName.contains("\$\$special\$\$inlined")
|
val coroutineTransformer = CoroutineTransformer(
|
||||||
|
inliningContext,
|
||||||
val hasLambdasToInline =
|
classBuilder,
|
||||||
((parentRemapper is RegeneratedLambdaFieldRemapper) && parentRemapper.recapturedLambdas.isNotEmpty()) || transformationInfo.capturedLambdasToInline.isNotEmpty()
|
sourceInfo,
|
||||||
|
methodsToTransform,
|
||||||
|
superClassName
|
||||||
|
)
|
||||||
for (next in methodsToTransform) {
|
for (next in methodsToTransform) {
|
||||||
// Generate state machine for
|
|
||||||
// 1) doResume method of suspend lambda
|
|
||||||
// 2) Suspend named function
|
|
||||||
// Iff it captures crossinline suspend lambda
|
|
||||||
val generateStateMachineForLambda =
|
|
||||||
languageVersionSettings.isResumeImplMethodName(next.name) && capturesCrossinlineSuspend && inliningContext.isContinuation &&
|
|
||||||
!isLambdaAlreadyGeneratedAndNotGoingToBeInlined && hasLambdasToInline
|
|
||||||
val continuationClassName = findFakeContinuationConstructorClassName(next)
|
|
||||||
val generateStateMachineForNamedFunction =
|
|
||||||
capturesCrossinlineSuspend && !inliningContext.isContinuation && continuationClassName != null
|
|
||||||
|
|
||||||
val deferringVisitor =
|
val deferringVisitor =
|
||||||
if (crossinlineSuspendElement != null) {
|
when {
|
||||||
when {
|
coroutineTransformer.shouldTransform(next) -> coroutineTransformer.newMethod(next)
|
||||||
generateStateMachineForLambda -> newStateMachineForLambda(classBuilder, next, crossinlineSuspendElement)
|
else -> newMethod(classBuilder, next)
|
||||||
generateStateMachineForNamedFunction -> newStateMachineForNamedFunction(
|
}
|
||||||
classBuilder,
|
|
||||||
next,
|
|
||||||
continuationClassName!!,
|
|
||||||
crossinlineSuspendElement
|
|
||||||
)
|
|
||||||
else -> newMethod(classBuilder, next)
|
|
||||||
}
|
|
||||||
} else newMethod(classBuilder, next)
|
|
||||||
val funResult = inlineMethodAndUpdateGlobalResult(parentRemapper, deferringVisitor, next, allCapturedParamBuilder, false)
|
val funResult = inlineMethodAndUpdateGlobalResult(parentRemapper, deferringVisitor, next, allCapturedParamBuilder, false)
|
||||||
|
|
||||||
val returnType = Type.getReturnType(next.desc)
|
val returnType = Type.getReturnType(next.desc)
|
||||||
@@ -185,29 +164,9 @@ class AnonymousObjectTransformer(
|
|||||||
}
|
}
|
||||||
|
|
||||||
deferringMethods.forEach { method ->
|
deferringMethods.forEach { method ->
|
||||||
replaceFakeContinuationsWithRealOnes(
|
coroutineTransformer.replaceFakesWithReals(method.intermediate)
|
||||||
method.intermediate,
|
|
||||||
if (!inliningContext.isContinuation)
|
|
||||||
getLastParameterIndex(method.intermediate.desc, method.intermediate.access)
|
|
||||||
else 0
|
|
||||||
)
|
|
||||||
removeFinallyMarkers(method.intermediate)
|
removeFinallyMarkers(method.intermediate)
|
||||||
method.visitEnd()
|
method.visitEnd()
|
||||||
|
|
||||||
// During regeneration of named suspend functions, which capture crossinline suspend lambda, we need to spill the variables
|
|
||||||
// into continuation object.
|
|
||||||
// In order to do this, we reuse class builder, which regenerates continuation object.
|
|
||||||
if (capturesCrossinlineSuspend &&
|
|
||||||
!inliningContext.isContinuation &&
|
|
||||||
inliningContext is RegeneratedClassContext
|
|
||||||
) {
|
|
||||||
val continuationClassName = findFakeContinuationConstructorClassName(method.intermediate)
|
|
||||||
if (continuationClassName != null) {
|
|
||||||
inliningContext.continuationBuilders
|
|
||||||
.remove(continuationClassName)
|
|
||||||
?.let(ClassBuilder::done)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SourceMapper.flushToClassBuilder(sourceMapper, classBuilder)
|
SourceMapper.flushToClassBuilder(sourceMapper, classBuilder)
|
||||||
@@ -226,8 +185,7 @@ class AnonymousObjectTransformer(
|
|||||||
writeOuterInfo(visitor)
|
writeOuterInfo(visitor)
|
||||||
|
|
||||||
if (continuationClassName == transformationInfo.oldClassName) {
|
if (continuationClassName == transformationInfo.oldClassName) {
|
||||||
assert(inliningContext.parent?.parent is RegeneratedClassContext)
|
coroutineTransformer.registerClassBuilder(continuationClassName)
|
||||||
(inliningContext.parent?.parent as RegeneratedClassContext).continuationBuilders[continuationClassName] = classBuilder
|
|
||||||
} else {
|
} else {
|
||||||
classBuilder.done()
|
classBuilder.done()
|
||||||
}
|
}
|
||||||
@@ -235,10 +193,6 @@ class AnonymousObjectTransformer(
|
|||||||
return transformationResult
|
return transformationResult
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun capturedCrossinlineSuspendElement(): KtExpression? = inliningContext.expressionMap.values.find { lambda ->
|
|
||||||
lambda is PsiExpressionLambda && lambda.isCrossInline && lambda.invokeMethodDescriptor.isSuspend
|
|
||||||
}?.cast<PsiExpressionLambda>()?.functionWithBodyOrCallableReference
|
|
||||||
|
|
||||||
private fun writeTransformedMetadata(header: KotlinClassHeader, classBuilder: ClassBuilder) {
|
private fun writeTransformedMetadata(header: KotlinClassHeader, classBuilder: ClassBuilder) {
|
||||||
writeKotlinMetadata(classBuilder, state, header.kind, header.extraInt) action@ { av ->
|
writeKotlinMetadata(classBuilder, state, header.kind, header.extraInt) action@ { av ->
|
||||||
val (newProto, newStringTable) = transformMetadata(header) ?: run {
|
val (newProto, newStringTable) = transformMetadata(header) ?: run {
|
||||||
@@ -447,63 +401,6 @@ class AnonymousObjectTransformer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun newStateMachineForLambda(builder: ClassBuilder, original: MethodNode, element: KtExpression): DeferredMethodVisitor {
|
|
||||||
return DeferredMethodVisitor(
|
|
||||||
MethodNode(
|
|
||||||
original.access, original.name, original.desc, original.signature,
|
|
||||||
ArrayUtil.toStringArray(original.exceptions)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
CoroutineTransformerMethodVisitor(
|
|
||||||
builder.newMethod(
|
|
||||||
NO_ORIGIN, original.access, original.name, original.desc, original.signature,
|
|
||||||
ArrayUtil.toStringArray(original.exceptions)
|
|
||||||
), original.access, original.name, original.desc, null, null,
|
|
||||||
obtainClassBuilderForCoroutineState = { builder },
|
|
||||||
element = element,
|
|
||||||
diagnostics = state.diagnostics,
|
|
||||||
languageVersionSettings = languageVersionSettings,
|
|
||||||
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
|
||||||
containingClassInternalName = builder.thisName,
|
|
||||||
isForNamedFunction = false,
|
|
||||||
sourceFile = sourceInfo ?: "",
|
|
||||||
isCrossinlineLambda = inliningContext.isContinuation
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun newStateMachineForNamedFunction(
|
|
||||||
builder: ClassBuilder,
|
|
||||||
original: MethodNode,
|
|
||||||
continuationClassName: String,
|
|
||||||
element: KtExpression
|
|
||||||
): DeferredMethodVisitor {
|
|
||||||
assert(inliningContext is RegeneratedClassContext)
|
|
||||||
return DeferredMethodVisitor(
|
|
||||||
MethodNode(
|
|
||||||
original.access, original.name, original.desc, original.signature,
|
|
||||||
ArrayUtil.toStringArray(original.exceptions)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
CoroutineTransformerMethodVisitor(
|
|
||||||
builder.newMethod(
|
|
||||||
NO_ORIGIN, original.access, original.name, original.desc, original.signature,
|
|
||||||
ArrayUtil.toStringArray(original.exceptions)
|
|
||||||
), original.access, original.name, original.desc, null, null,
|
|
||||||
obtainClassBuilderForCoroutineState = { (inliningContext as RegeneratedClassContext).continuationBuilders[continuationClassName]!! },
|
|
||||||
element = element,
|
|
||||||
diagnostics = state.diagnostics,
|
|
||||||
languageVersionSettings = languageVersionSettings,
|
|
||||||
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
|
||||||
containingClassInternalName = builder.thisName,
|
|
||||||
isForNamedFunction = true,
|
|
||||||
needDispatchReceiver = true,
|
|
||||||
internalNameForDispatchReceiver = builder.thisName,
|
|
||||||
sourceFile = sourceInfo ?: ""
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun extractParametersMappingAndPatchConstructor(
|
private fun extractParametersMappingAndPatchConstructor(
|
||||||
constructor: MethodNode,
|
constructor: MethodNode,
|
||||||
capturedParamBuilder: ParametersBuilder,
|
capturedParamBuilder: ParametersBuilder,
|
||||||
@@ -649,10 +546,3 @@ class AnonymousObjectTransformer(
|
|||||||
private fun isFirstDeclSiteLambdaFieldRemapper(parentRemapper: FieldRemapper): Boolean =
|
private fun isFirstDeclSiteLambdaFieldRemapper(parentRemapper: FieldRemapper): Boolean =
|
||||||
parentRemapper !is RegeneratedLambdaFieldRemapper && parentRemapper !is InlinedLambdaRemapper
|
parentRemapper !is RegeneratedLambdaFieldRemapper && parentRemapper !is InlinedLambdaRemapper
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun findFakeContinuationConstructorClassName(node: MethodNode): String? {
|
|
||||||
val marker = node.instructions.asSequence().firstOrNull(::isBeforeFakeContinuationConstructorCallMarker) ?: return null
|
|
||||||
val new = marker.next
|
|
||||||
assert(new?.opcode == Opcodes.NEW)
|
|
||||||
return (new as TypeInsnNode).desc
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,24 +1,12 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2015 JetBrains s.r.o.
|
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
|
||||||
*
|
* that can be found in the license/LICENSE.txt file.
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.codegen.inline
|
package org.jetbrains.kotlin.codegen.inline
|
||||||
|
|
||||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||||
import org.jetbrains.kotlin.codegen.state.GenerationState
|
import org.jetbrains.kotlin.codegen.state.GenerationState
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
|
||||||
|
|
||||||
class RootInliningContext(
|
class RootInliningContext(
|
||||||
expressionMap: Map<Int, LambdaInfo>,
|
expressionMap: Map<Int, LambdaInfo>,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.codegen.StackValue
|
|||||||
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmType
|
import org.jetbrains.kotlin.codegen.coroutines.continuationAsmType
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.getOrCreateJvmSuspendFunctionView
|
import org.jetbrains.kotlin.codegen.coroutines.getOrCreateJvmSuspendFunctionView
|
||||||
import org.jetbrains.kotlin.codegen.inline.FieldRemapper.Companion.foldName
|
import org.jetbrains.kotlin.codegen.inline.FieldRemapper.Companion.foldName
|
||||||
|
import org.jetbrains.kotlin.codegen.inline.coroutines.CoroutineTransformer
|
||||||
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
|
import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicMethods
|
||||||
import org.jetbrains.kotlin.codegen.optimization.ApiVersionCallsPreprocessingMethodTransformer
|
import org.jetbrains.kotlin.codegen.optimization.ApiVersionCallsPreprocessingMethodTransformer
|
||||||
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
|
import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer
|
||||||
@@ -182,7 +183,7 @@ class MethodInliner(
|
|||||||
val transformer = transformationInfo!!.createTransformer(
|
val transformer = transformationInfo!!.createTransformer(
|
||||||
childInliningContext,
|
childInliningContext,
|
||||||
isSameModule,
|
isSameModule,
|
||||||
findFakeContinuationConstructorClassName(node)
|
CoroutineTransformer.findFakeContinuationConstructorClassName(node)
|
||||||
)
|
)
|
||||||
|
|
||||||
val transformResult = transformer.doTransform(nodeRemapper)
|
val transformResult = transformer.doTransform(nodeRemapper)
|
||||||
|
|||||||
+164
@@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2018 JetBrains s.r.o. 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.coroutines
|
||||||
|
|
||||||
|
import com.intellij.util.ArrayUtil
|
||||||
|
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||||
|
import org.jetbrains.kotlin.codegen.coroutines.*
|
||||||
|
import org.jetbrains.kotlin.codegen.inline.*
|
||||||
|
import org.jetbrains.kotlin.codegen.optimization.common.asSequence
|
||||||
|
import org.jetbrains.kotlin.config.isReleaseCoroutines
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.cast
|
||||||
|
import org.jetbrains.kotlin.utils.sure
|
||||||
|
import org.jetbrains.org.objectweb.asm.Opcodes
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.MethodNode
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.MethodInsnNode
|
||||||
|
import org.jetbrains.org.objectweb.asm.tree.TypeInsnNode
|
||||||
|
|
||||||
|
class CoroutineTransformer(
|
||||||
|
private val inliningContext: InliningContext,
|
||||||
|
private val classBuilder: ClassBuilder,
|
||||||
|
private val sourceFile: String?,
|
||||||
|
private val methods: List<MethodNode>,
|
||||||
|
private val superClassName: String
|
||||||
|
) {
|
||||||
|
private val state = inliningContext.state
|
||||||
|
|
||||||
|
fun shouldTransform(node: MethodNode): Boolean {
|
||||||
|
if (isContinuationNotLambda()) return false
|
||||||
|
val crossinlineSuspend = crossinlineSuspend() ?: return false
|
||||||
|
if (inliningContext.isInliningLambda && !inliningContext.isContinuation) return false
|
||||||
|
return when {
|
||||||
|
isSuspendFunction(node) -> true
|
||||||
|
isSuspendLambda(node) -> {
|
||||||
|
if (isStateMachine(node)) return false
|
||||||
|
val functionDescriptor =
|
||||||
|
crossinlineSuspend.invokeMethodDescriptor.containingDeclaration as? FunctionDescriptor ?: return true
|
||||||
|
!functionDescriptor.isInline
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isContinuationNotLambda(): Boolean = inliningContext.isContinuation &&
|
||||||
|
if (state.languageVersionSettings.isReleaseCoroutines()) superClassName.endsWith("ContinuationImpl")
|
||||||
|
else methods.any { it.name == "getLabel" }
|
||||||
|
|
||||||
|
private fun crossinlineSuspend(): PsiExpressionLambda? = inliningContext.expressionMap.values.find {
|
||||||
|
it is PsiExpressionLambda && it.isCrossInline && it.invokeMethodDescriptor.isSuspend
|
||||||
|
}?.cast()
|
||||||
|
|
||||||
|
private fun isStateMachine(node: MethodNode): Boolean =
|
||||||
|
node.instructions.asSequence().any { it.opcode == Opcodes.INVOKESTATIC && (it as MethodInsnNode).name == "getCOROUTINE_SUSPENDED" }
|
||||||
|
|
||||||
|
private fun isSuspendLambda(node: MethodNode) = isResumeImpl(node)
|
||||||
|
|
||||||
|
fun newMethod(node: MethodNode): DeferredMethodVisitor {
|
||||||
|
val element = crossinlineSuspend()?.functionWithBodyOrCallableReference.sure {
|
||||||
|
"crossinline lambda should have element"
|
||||||
|
}
|
||||||
|
return when {
|
||||||
|
isResumeImpl(node) -> {
|
||||||
|
assert(!isStateMachine(node)) {
|
||||||
|
"Inlining/transforming state-machine"
|
||||||
|
}
|
||||||
|
newStateMachineForLambda(node, element)
|
||||||
|
}
|
||||||
|
isSuspendFunction(node) -> newStateMachineForNamedFunction(node, element)
|
||||||
|
else -> error("no need to generate state maching for ${node.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isResumeImpl(node: MethodNode): Boolean =
|
||||||
|
state.languageVersionSettings.isResumeImplMethodName(node.name) &&
|
||||||
|
inliningContext.isContinuation
|
||||||
|
|
||||||
|
private fun isSuspendFunction(node: MethodNode): Boolean = findFakeContinuationConstructorClassName(node) != null
|
||||||
|
|
||||||
|
private fun newStateMachineForLambda(node: MethodNode, element: KtElement): DeferredMethodVisitor {
|
||||||
|
return DeferredMethodVisitor(
|
||||||
|
MethodNode(
|
||||||
|
node.access, node.name, node.desc, node.signature,
|
||||||
|
ArrayUtil.toStringArray(node.exceptions)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
CoroutineTransformerMethodVisitor(
|
||||||
|
classBuilder.newMethod(
|
||||||
|
JvmDeclarationOrigin.NO_ORIGIN,
|
||||||
|
node.access,
|
||||||
|
node.name,
|
||||||
|
node.desc,
|
||||||
|
node.signature,
|
||||||
|
ArrayUtil.toStringArray(node.exceptions)
|
||||||
|
), node.access, node.name, node.desc, null, null,
|
||||||
|
obtainClassBuilderForCoroutineState = { classBuilder },
|
||||||
|
element = element,
|
||||||
|
diagnostics = state.diagnostics,
|
||||||
|
languageVersionSettings = state.languageVersionSettings,
|
||||||
|
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||||
|
containingClassInternalName = classBuilder.thisName,
|
||||||
|
isForNamedFunction = false,
|
||||||
|
sourceFile = sourceFile ?: "",
|
||||||
|
isCrossinlineLambda = inliningContext.isContinuation
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun newStateMachineForNamedFunction(node: MethodNode, element: KtElement): DeferredMethodVisitor {
|
||||||
|
val continuationClassName = findFakeContinuationConstructorClassName(node)
|
||||||
|
assert(inliningContext is RegeneratedClassContext)
|
||||||
|
return DeferredMethodVisitor(
|
||||||
|
MethodNode(
|
||||||
|
node.access, node.name, node.desc, node.signature,
|
||||||
|
ArrayUtil.toStringArray(node.exceptions)
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
CoroutineTransformerMethodVisitor(
|
||||||
|
classBuilder.newMethod(
|
||||||
|
JvmDeclarationOrigin.NO_ORIGIN, node.access, node.name, node.desc, node.signature,
|
||||||
|
ArrayUtil.toStringArray(node.exceptions)
|
||||||
|
), node.access, node.name, node.desc, null, null,
|
||||||
|
obtainClassBuilderForCoroutineState = { (inliningContext as RegeneratedClassContext).continuationBuilders[continuationClassName]!! },
|
||||||
|
element = element,
|
||||||
|
diagnostics = state.diagnostics,
|
||||||
|
languageVersionSettings = state.languageVersionSettings,
|
||||||
|
shouldPreserveClassInitialization = state.constructorCallNormalizationMode.shouldPreserveClassInitialization,
|
||||||
|
containingClassInternalName = classBuilder.thisName,
|
||||||
|
isForNamedFunction = true,
|
||||||
|
needDispatchReceiver = true,
|
||||||
|
internalNameForDispatchReceiver = classBuilder.thisName,
|
||||||
|
sourceFile = sourceFile ?: ""
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun replaceFakesWithReals(node: MethodNode) {
|
||||||
|
findFakeContinuationConstructorClassName(node)?.let(::unregisterClassBuilder)?.let(ClassBuilder::done)
|
||||||
|
replaceFakeContinuationsWithRealOnes(
|
||||||
|
node, if (!inliningContext.isContinuation) getLastParameterIndex(node.desc, node.access) else 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun registerClassBuilder(continuationClassName: String) {
|
||||||
|
val context = inliningContext.parent?.parent as? RegeneratedClassContext ?: error("incorrect context")
|
||||||
|
context.continuationBuilders[continuationClassName] = classBuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unregisterClassBuilder(continuationClassName: String) =
|
||||||
|
(inliningContext as RegeneratedClassContext).continuationBuilders.remove(continuationClassName)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun findFakeContinuationConstructorClassName(node: MethodNode): String? {
|
||||||
|
val marker = node.instructions.asSequence().firstOrNull(::isBeforeFakeContinuationConstructorCallMarker) ?: return null
|
||||||
|
val new = marker.next
|
||||||
|
assert(new?.opcode == Opcodes.NEW)
|
||||||
|
return (new as TypeInsnNode).desc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// LANGUAGE_VERSION: 1.2
|
||||||
|
// IGNORE_BACKEND: JVM
|
||||||
|
// WITH_COROUTINES
|
||||||
|
import helpers.*
|
||||||
|
import kotlin.coroutines.experimental.*
|
||||||
|
import kotlin.coroutines.experimental.intrinsics.*
|
||||||
|
|
||||||
|
fun builder(c: suspend () -> Unit) {
|
||||||
|
c.startCoroutine(EmptyContinuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SuspendRunnable {
|
||||||
|
suspend fun run(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun inlineMe(crossinline c: suspend () -> String) = object : SuspendRunnable {
|
||||||
|
override suspend fun run() = c()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
var res = "FAIL"
|
||||||
|
builder {
|
||||||
|
res = inlineMe { "OK" }.run()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for continuation of 'run' transformation.
|
||||||
|
// Since continuation is not suspend lambda, it should not have state-machine.
|
||||||
|
// @CrossinlineSuspendContinuation_1_2Kt$box$1$doResume$$inlined$inlineMe$1$1.class:
|
||||||
|
// 0 TABLESWITCH
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
// WITH_RUNTIME
|
||||||
|
// LANGUAGE_VERSION: 1.3
|
||||||
|
// WITH_COROUTINES
|
||||||
|
import helpers.*
|
||||||
|
import kotlin.coroutines.*
|
||||||
|
import kotlin.coroutines.intrinsics.*
|
||||||
|
|
||||||
|
fun builder(c: suspend () -> Unit) {
|
||||||
|
c.startCoroutine(EmptyContinuation)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SuspendRunnable {
|
||||||
|
suspend fun run(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun inlineMe(crossinline c: suspend () -> String) = object : SuspendRunnable {
|
||||||
|
override suspend fun run() = c()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun box(): String {
|
||||||
|
var res = "FAIL"
|
||||||
|
builder {
|
||||||
|
res = inlineMe { "OK" }.run()
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test for continuation of 'run' transformation.
|
||||||
|
// Since continuation is not suspend lambda, it should not have state-machine
|
||||||
|
// @CrossinlineSuspendContinuation_1_3Kt$box$1$invokeSuspend$$inlined$inlineMe$1$1.class:
|
||||||
|
// 0 TABLESWITCH
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
class Handler(val func: suspend (Any) -> Unit)
|
||||||
|
|
||||||
|
inline fun createHandler(crossinline handler: suspend (Any) -> Unit): Handler {
|
||||||
|
return Handler({ handler.invoke(it) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
createHandler({
|
||||||
|
if (it !is String) {}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2 TABLESWITCH
|
||||||
+23
@@ -0,0 +1,23 @@
|
|||||||
|
fun <T> builder(c: suspend () -> T): T = TODO()
|
||||||
|
|
||||||
|
class Test {
|
||||||
|
fun doWork() {
|
||||||
|
builder {
|
||||||
|
execute {
|
||||||
|
getData { getSomeString() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun execute(crossinline action: suspend () -> Unit) {
|
||||||
|
builder { action() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun <T> getData(dataProvider: suspend () -> T): T = builder { dataProvider() }
|
||||||
|
|
||||||
|
private suspend fun getSomeString(): String {
|
||||||
|
return "OK"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4 TABLESWITCH
|
||||||
@@ -1090,6 +1090,16 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/coroutines"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/coroutines"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("crossinlineSuspendContinuation_1_2.kt")
|
||||||
|
public void testCrossinlineSuspendContinuation_1_2() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/coroutines/crossinlineSuspendContinuation_1_2.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("crossinlineSuspendContinuation_1_3.kt")
|
||||||
|
public void testCrossinlineSuspendContinuation_1_3() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/coroutines/crossinlineSuspendContinuation_1_3.kt");
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("doNotReassignContinuation.kt")
|
@TestMetadata("doNotReassignContinuation.kt")
|
||||||
public void testDoNotReassignContinuation() throws Exception {
|
public void testDoNotReassignContinuation() throws Exception {
|
||||||
runTest("compiler/testData/codegen/bytecodeText/coroutines/doNotReassignContinuation.kt");
|
runTest("compiler/testData/codegen/bytecodeText/coroutines/doNotReassignContinuation.kt");
|
||||||
@@ -1297,6 +1307,29 @@ public class BytecodeTextTestGenerated extends AbstractBytecodeTextTest {
|
|||||||
runTestWithPackageReplacement("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/usedInVarStore.kt", "kotlin.coroutines");
|
runTestWithPackageReplacement("compiler/testData/codegen/bytecodeText/coroutines/intLikeVarSpilling/usedInVarStore.kt", "kotlin.coroutines");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("compiler/testData/codegen/bytecodeText/coroutines/stateMachine")
|
||||||
|
@TestDataPath("$PROJECT_ROOT")
|
||||||
|
@RunWith(JUnit3RunnerWithInners.class)
|
||||||
|
public static class StateMachine extends AbstractBytecodeTextTest {
|
||||||
|
private void runTest(String testDataFilePath) throws Exception {
|
||||||
|
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testAllFilesPresentInStateMachine() throws Exception {
|
||||||
|
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/bytecodeText/coroutines/stateMachine"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("kt25893.kt")
|
||||||
|
public void testKt25893() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/coroutines/stateMachine/kt25893.kt");
|
||||||
|
}
|
||||||
|
|
||||||
|
@TestMetadata("withTypeParameter.kt")
|
||||||
|
public void testWithTypeParameter() throws Exception {
|
||||||
|
runTest("compiler/testData/codegen/bytecodeText/coroutines/stateMachine/withTypeParameter.kt");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestMetadata("compiler/testData/codegen/bytecodeText/deadCodeElimination")
|
@TestMetadata("compiler/testData/codegen/bytecodeText/deadCodeElimination")
|
||||||
|
|||||||
Reference in New Issue
Block a user