diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java index 91cbaa2ded4..b79ce2049fd 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/ExpressionCodegen.java @@ -1184,7 +1184,6 @@ public class ExpressionCodegen extends KtVisitor impleme @Nullable private StackValue genCoroutineInstanceForSuspendLambda(@NotNull FunctionDescriptor suspendFunction) { - if (!CoroutineCodegenUtilKt.isStateMachineNeeded(suspendFunction, bindingContext)) return null; if (!(suspendFunction instanceof AnonymousFunctionDescriptor)) return null; ClassDescriptor suspendLambdaClassDescriptor = bindingContext.get(CodegenBinding.CLASS_FOR_CALLABLE, suspendFunction); @@ -2138,9 +2137,9 @@ public class ExpressionCodegen extends KtVisitor impleme @NotNull CallGenerator callGenerator, @NotNull ArgumentGenerator argumentGenerator ) { - boolean isSuspensionPoint = CoroutineCodegenUtilKt.isSuspensionPointInStateMachine(resolvedCall, bindingContext); + boolean isSuspendCall = CoroutineCodegenUtilKt.isSuspendNoInlineCall(resolvedCall); boolean isConstructor = resolvedCall.getResultingDescriptor() instanceof ConstructorDescriptor; - putReceiverAndInlineMarkerIfNeeded(callableMethod, resolvedCall, receiver, isSuspensionPoint, isConstructor); + putReceiverAndInlineMarkerIfNeeded(callableMethod, resolvedCall, receiver, isSuspendCall, isConstructor); callGenerator.processAndPutHiddenParameters(false); @@ -2171,7 +2170,7 @@ public class ExpressionCodegen extends KtVisitor impleme } } - if (isSuspensionPoint) { + if (isSuspendCall) { v.invokestatic( CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER, CoroutineCodegenUtilKt.BEFORE_SUSPENSION_POINT_MARKER_NAME, @@ -2181,7 +2180,7 @@ public class ExpressionCodegen extends KtVisitor impleme callGenerator.genCall(callableMethod, resolvedCall, defaultMaskWasGenerated, this); - if (isSuspensionPoint) { + if (isSuspendCall) { v.invokestatic( CoroutineCodegenUtilKt.COROUTINE_MARKER_OWNER, CoroutineCodegenUtilKt.AFTER_SUSPENSION_POINT_MARKER_NAME, "()V", false); @@ -2199,12 +2198,12 @@ public class ExpressionCodegen extends KtVisitor impleme @NotNull Callable callableMethod, @NotNull ResolvedCall resolvedCall, @NotNull StackValue receiver, - boolean isSuspensionPoint, + boolean isSuspendCall, boolean isConstructor ) { boolean isSafeCallOrOnStack = receiver instanceof StackValue.SafeCall || receiver instanceof StackValue.OnStack; - if (isSuspensionPoint && !isSafeCallOrOnStack) { + if (isSuspendCall && !isSafeCallOrOnStack) { // Inline markers are used to spill the stack before coroutine suspension addInlineMarker(v, true); } @@ -2231,7 +2230,7 @@ public class ExpressionCodegen extends KtVisitor impleme // The problem is that the stack before the call is not restored in case of null receiver. // The solution is to spill stack just after receiver is loaded (after IFNULL) in case of safe call. // But the problem is that we should leave the receiver itself on the stack, so we store it in a temporary variable. - if (isSuspensionPoint && isSafeCallOrOnStack) { + if (isSuspendCall && isSafeCallOrOnStack) { boolean bothReceivers = receiver instanceof StackValue.CallReceiver && ((StackValue.CallReceiver) receiver).getDispatchReceiver().type.getSort() != Type.VOID diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java index 7b7e9aed832..cd2e3c92628 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/FunctionCodegen.java @@ -131,7 +131,7 @@ public class FunctionCodegen { if (owner.getContextKind() != OwnerKind.DEFAULT_IMPLS || function.hasBody()) { FunctionGenerationStrategy strategy; - if (functionDescriptor.isSuspend()) { + if (functionDescriptor.isSuspend() && !functionDescriptor.isInline()) { strategy = new SuspendFunctionGenerationStrategy( state, CoroutineCodegenUtilKt.unwrapInitialDescriptorForSuspendFunction(functionDescriptor), diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java index 26b4f12b138..74d32f39dc6 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/binding/CodegenAnnotatingVisitor.java @@ -103,6 +103,20 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid { return recordClassForCallable(element, callableDescriptor, supertypes, name, null); } + @NotNull + private ClassDescriptor recordClassForFunction( + @NotNull KtElement element, + @NotNull FunctionDescriptor functionDescriptor, + @NotNull String name, + @Nullable DeclarationDescriptor customContainer + ) { + return recordClassForCallable( + element, functionDescriptor, + runtimeTypes.getSupertypesForClosure(functionDescriptor), + name, customContainer + ); + } + @NotNull private ClassDescriptor recordClassForCallable( @NotNull KtElement element, @@ -454,18 +468,23 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid { jvmSuspendFunctionView ); - if (CoroutineCodegenUtilKt.containsNonTailSuspensionCalls(functionDescriptor, bindingContext)) { - if (nameForClassOrPackageMember != null) { - nameStack.push(nameForClassOrPackageMember); - } - - processNamedFunctionWithClosure(function, functionDescriptor, functionDescriptor).setSuspend(true); - - if (nameForClassOrPackageMember != null) { - nameStack.pop(); - } - return; + if (nameForClassOrPackageMember != null) { + nameStack.push(nameForClassOrPackageMember); } + + String name = inventAnonymousClassName(); + ClassDescriptor classDescriptor = + recordClassForFunction(function, functionDescriptor, name, functionDescriptor); + MutableClosure closure = recordClosure(classDescriptor, name); + closure.setSuspend(true); + + super.visitNamedFunction(function); + + if (nameForClassOrPackageMember != null) { + nameStack.pop(); + } + + return; } if (nameForClassOrPackageMember != null) { @@ -474,29 +493,18 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid { nameStack.pop(); } else { - processNamedFunctionWithClosure(function, functionDescriptor, null); + String name = inventAnonymousClassName(); + ClassDescriptor classDescriptor = recordClassForFunction(function, functionDescriptor, name, null); + recordClosure(classDescriptor, name); + + classStack.push(classDescriptor); + nameStack.push(name); + super.visitNamedFunction(function); + nameStack.pop(); + classStack.pop(); } } - private MutableClosure processNamedFunctionWithClosure( - @NotNull KtNamedFunction function, - @NotNull FunctionDescriptor functionDescriptor, - @Nullable DeclarationDescriptor customContainer - ) { - String name = inventAnonymousClassName(); - Collection supertypes = runtimeTypes.getSupertypesForClosure(functionDescriptor); - ClassDescriptor classDescriptor = recordClassForCallable(function, functionDescriptor, supertypes, name, customContainer); - MutableClosure closure = recordClosure(classDescriptor, name); - - classStack.push(classDescriptor); - nameStack.push(name); - super.visitNamedFunction(function); - nameStack.pop(); - classStack.pop(); - - return closure; - } - @Nullable private String getNameForClassOrPackageMember(@NotNull DeclarationDescriptor descriptor) { DeclarationDescriptor containingDeclaration = descriptor.getContainingDeclaration(); diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt index 57989341ac1..11fdb487351 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/CoroutineTransformerMethodVisitor.kt @@ -21,12 +21,12 @@ import org.jetbrains.kotlin.codegen.AsmUtil import org.jetbrains.kotlin.codegen.ClassBuilder import org.jetbrains.kotlin.codegen.StackValue import org.jetbrains.kotlin.codegen.TransformationMethodVisitor +import org.jetbrains.kotlin.codegen.inline.InlineCodegenUtil import org.jetbrains.kotlin.codegen.optimization.DeadCodeEliminationMethodTransformer import org.jetbrains.kotlin.codegen.optimization.FixStackWithLabelNormalizationMethodTransformer -import org.jetbrains.kotlin.codegen.optimization.common.StrictBasicValue -import org.jetbrains.kotlin.codegen.optimization.common.analyzeLiveness -import org.jetbrains.kotlin.codegen.optimization.common.insnListOf -import org.jetbrains.kotlin.codegen.optimization.common.removeEmptyCatchBlocks +import org.jetbrains.kotlin.codegen.optimization.common.* +import org.jetbrains.kotlin.codegen.optimization.fixStack.top +import org.jetbrains.kotlin.codegen.optimization.transformer.MethodTransformer import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin import org.jetbrains.kotlin.utils.sure @@ -36,6 +36,8 @@ import org.jetbrains.org.objectweb.asm.Opcodes import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.org.objectweb.asm.tree.* +import org.jetbrains.org.objectweb.asm.tree.analysis.SourceInterpreter +import org.jetbrains.org.objectweb.asm.tree.analysis.SourceValue class CoroutineTransformerMethodVisitor( delegate: MethodVisitor, @@ -45,15 +47,35 @@ class CoroutineTransformerMethodVisitor( signature: String?, exceptions: Array?, private val containingClassInternalName: String, - private val classBuilderForCoroutineState: ClassBuilder, - isForNamedFunction: Boolean + obtainClassBuilderForCoroutineState: () -> ClassBuilder, + private val isForNamedFunction: Boolean, + // It's only matters for named functions, may differ from '!isStatic(access)' in case of DefaultImpls + private val needDispatchReceiver: Boolean = false, + // May differ from containingClassInternalName in case of DefaultImpls + private val internalNameForDispatchReceiver: String? = null ) : TransformationMethodVisitor(delegate, access, name, desc, signature, exceptions) { + private val classBuilderForCoroutineState: ClassBuilder by lazy(obtainClassBuilderForCoroutineState) + private val continuationIndex = if (isForNamedFunction) getLastParameterIndex(desc, access) else 0 - private val dataIndex = continuationIndex + 1 - private val exceptionIndex = dataIndex + 1 + var dataIndex = if (isForNamedFunction) -1 else 1 + var exceptionIndex = if (isForNamedFunction) -1 else 2 override fun performTransformations(methodNode: MethodNode) { + val suspensionPoints = collectSuspensionPoints(methodNode) + + if (isForNamedFunction) { + if (allSuspensionPointsAreTailCalls(containingClassInternalName, methodNode, suspensionPoints)) { + dropSuspensionMarkers(methodNode, suspensionPoints) + return + } + + dataIndex = methodNode.maxLocals++ + exceptionIndex = methodNode.maxLocals++ + + prepareMethodNodePreludeForNamedFunction(methodNode) + } + val customCoroutineStart = run { val customCoroutineStartMarker = methodNode.instructions.toArray().filterIsInstance().firstOrNull { it.owner == COROUTINE_MARKER_OWNER && it.name == ACTUAL_COROUTINE_START_MARKER_NAME @@ -64,8 +86,6 @@ class CoroutineTransformerMethodVisitor( } } - val suspensionPoints = collectSuspensionPoints(methodNode) - for (suspensionPoint in suspensionPoints) { splitTryCatchBlocksContainingSuspensionPoint(methodNode, suspensionPoint) } @@ -125,6 +145,87 @@ class CoroutineTransformerMethodVisitor( } + private fun prepareMethodNodePreludeForNamedFunction(methodNode: MethodNode) { + val objectTypeForState = Type.getObjectType(classBuilderForCoroutineState.thisName) + methodNode.instructions.insert(withInstructionAdapter { + val createStateInstance = Label() + val storeStateObject = Label() + + // We have to distinguish the following situations: + // - Our function got called in a common way (e.g. from another function or via recursive call) and we should execute our + // code from the beginning + // - We got called from `doResume` of our continuation, i.e. we need to continue from the last suspension point + // + // Also in the first case we wrap the completion into a special anonymous class instance (let's call it X$1) + // that we'll use as a continuation argument for suspension points + // + // How we distinguish the cases: + // - If the continuation is not an instance of X$1 we know exactly it's not the second case, because when resuming + // the continuation we pass an instance of that class + // - Otherwise it's still can be a recursive call. To check it's not the case we set the last bit in the label in + // `doResume` just before calling the suspend function (see kotlin.coroutines.experimental.jvm.internal.CoroutineImplForNamedFunction). + // So, if it's set we're in continuation. + visitVarInsn(Opcodes.ALOAD, continuationIndex) + instanceOf(objectTypeForState) + ifeq(createStateInstance) + + visitVarInsn(Opcodes.ALOAD, continuationIndex) + checkcast(objectTypeForState) + invokevirtual( + COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, + "checkAndFlushLastBit", Type.getMethodDescriptor(Type.BOOLEAN_TYPE), false + ) + ifeq(createStateInstance) + + visitVarInsn(Opcodes.ALOAD, continuationIndex) + checkcast(objectTypeForState) + goTo(storeStateObject) + + visitLabel(createStateInstance) + + anew(objectTypeForState) + dup() + + val parameterTypesAndIndices = + getParameterTypesIndicesForCoroutineConstructor( + methodNode.desc, + methodNode.access, + needDispatchReceiver, internalNameForDispatchReceiver ?: containingClassInternalName + ) + for ((type, index) in parameterTypesAndIndices) { + load(index, type) + } + + invokespecial( + classBuilderForCoroutineState.thisName, + "", + Type.getMethodDescriptor( + Type.VOID_TYPE, + *getParameterTypesForCoroutineConstructor( + methodNode.desc, needDispatchReceiver, + internalNameForDispatchReceiver ?: containingClassInternalName + ) + ), + false + ) + + visitLabel(storeStateObject) + visitVarInsn(Opcodes.ASTORE, continuationIndex) + + visitVarInsn(Opcodes.ALOAD, continuationIndex) + checkcast(objectTypeForState) + getfield(COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, "data", AsmTypes.OBJECT_TYPE.descriptor) + visitVarInsn(Opcodes.ASTORE, dataIndex) + + visitVarInsn(Opcodes.ALOAD, continuationIndex) + checkcast(objectTypeForState) + getfield(COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, "exception", AsmTypes.JAVA_THROWABLE_TYPE.descriptor) + visitVarInsn(Opcodes.ASTORE, exceptionIndex) + + invokestatic(COROUTINE_MARKER_OWNER, ACTUAL_COROUTINE_START_MARKER_NAME, "()V", false) + }) + } + private fun removeUnreachableSuspensionPointsAndExitPoints(methodNode: MethodNode, suspensionPoints: MutableList) { val dceResult = DeadCodeEliminationMethodTransformer().transformWithResult(containingClassInternalName, methodNode) @@ -216,10 +317,12 @@ class CoroutineTransformerMethodVisitor( // k + 1 - data // k + 2 - exception val variablesToSpill = - ((exceptionIndex + 1) until localsCount) + (0 until localsCount) + .filter{ it !in setOf(continuationIndex, dataIndex, exceptionIndex) } .map { Pair(it, frame.getLocal(it)) } .filter { (index, value) -> - value != StrictBasicValue.UNINITIALIZED_VALUE && livenessFrame.isAlive(index) + (index == 0 && needDispatchReceiver && isForNamedFunction) || + (value != StrictBasicValue.UNINITIALIZED_VALUE && livenessFrame.isAlive(index)) } for ((index, basicValue) in variablesToSpill) { @@ -446,4 +549,120 @@ private class SuspensionPoint( } private fun getLastParameterIndex(desc: String, access: Int) = - Type.getArgumentTypes(desc).dropLast(1).map { it.size }.sum() + (if (access and Opcodes.ACC_STATIC != 0) 0 else 1) + Type.getArgumentTypes(desc).dropLast(1).map { it.size }.sum() + (if (!isStatic(access)) 1 else 0) + +private fun getParameterTypesForCoroutineConstructor(desc: String, hasDispatchReceiver: Boolean, thisName: String) = + listOfNotNull(if (!hasDispatchReceiver) null else Type.getObjectType(thisName)).toTypedArray() + + Type.getArgumentTypes(desc).last() + +private fun isStatic(access: Int) = access and Opcodes.ACC_STATIC != 0 + +private fun getParameterTypesIndicesForCoroutineConstructor( + desc: String, + containingFunctionAccess: Int, + needDispatchReceiver: Boolean, + thisName: String +): Collection> { + return mutableListOf>().apply { + if (needDispatchReceiver) { + add(Type.getObjectType(thisName) to 0) + } + val continuationIndex = + getAllParameterTypes(desc, !isStatic(containingFunctionAccess), thisName).dropLast(1).map(Type::getSize).sum() + add(CONTINUATION_ASM_TYPE to continuationIndex) + } +} + +private fun getAllParameterTypes(desc: String, hasDispatchReceiver: Boolean, thisName: String) = + listOfNotNull(if (!hasDispatchReceiver) null else Type.getObjectType(thisName)).toTypedArray() + + Type.getArgumentTypes(desc) + +private fun allSuspensionPointsAreTailCalls( + thisName: String, + methodNode: MethodNode, + suspensionPoints: List +): Boolean { + val safelyReachableReturns = findSafelyReachableReturns(methodNode) + val sourceFrames = MethodTransformer.analyze(thisName, methodNode, IgnoringCopyOperationSourceInterpreter()) + + val instructions = methodNode.instructions + return suspensionPoints.all { suspensionPoint -> + val beginIndex = instructions.indexOf(suspensionPoint.suspensionCallBegin) + val endIndex = instructions.indexOf(suspensionPoint.suspensionCallEnd) + + safelyReachableReturns[endIndex + 1]?.all { returnIndex -> + val sourceInsn = + sourceFrames[returnIndex].top().sure { + "There must be some value on stack to return" + }.insns.singleOrNull() + + sourceInsn?.let(instructions::indexOf) in beginIndex..endIndex + } ?: false + } +} + +private class IgnoringCopyOperationSourceInterpreter : SourceInterpreter() { + override fun copyOperation(insn: AbstractInsnNode?, value: SourceValue?) = value +} + +/** + * Let's call an instruction safe if its execution is always invisible: stack modifications, branching, variable insns (invisible in debug) + * + * For some instruction `insn` define the result as following: + * - if there is a path leading to the non-safe instruction then result is `null` + * - Otherwise result contains all the reachable ARETURN indices + * + * @return indices of safely reachable returns for each instruction in the method node + */ +private fun findSafelyReachableReturns(methodNode: MethodNode): Array?> { + val controlFlowGraph = ControlFlowGraph.build(methodNode) + + val insns = methodNode.instructions + val reachableReturnsIndices = Array?>(insns.size()) init@{ index -> + val insn = insns[index] + + if (insn.opcode == Opcodes.ARETURN) { + return@init setOf(index) + } + + if (!insn.isMeaningful || insn.opcode in SAFE_OPCODES || insn.isInvisibleInDebugVarInsn(methodNode) || + InlineCodegenUtil.isInlineMarker(insn)) { + setOf() + } + else null + } + + var changed: Boolean + do { + changed = false + for (index in 0 until insns.size()) { + if (insns[index].opcode == Opcodes.ARETURN) continue + + val newResult = + controlFlowGraph + .getSuccessorsIndices(index).plus(index) + .map(reachableReturnsIndices::get) + .fold?, Set?>(mutableSetOf()) { acc, successorsResult -> + if (acc != null && successorsResult != null) acc + successorsResult else null + } + + if (newResult != reachableReturnsIndices[index]) { + reachableReturnsIndices[index] = newResult + changed = true + } + } + } while (changed) + + return reachableReturnsIndices +} + +private fun AbstractInsnNode?.isInvisibleInDebugVarInsn(methodNode: MethodNode): Boolean { + val insns = methodNode.instructions + val index = insns.indexOf(this) + return (this is VarInsnNode && methodNode.localVariables.none { + it.index == `var` && index in it.start.let(insns::indexOf)..it.end.let(insns::indexOf) + }) +} + +private val SAFE_OPCODES = + ((Opcodes.DUP..Opcodes.DUP2_X2) + Opcodes.POP + Opcodes.POP2 + (Opcodes.IFEQ..Opcodes.GOTO)).toSet() diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/SuspendFunctionGenerationStrategy.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/SuspendFunctionGenerationStrategy.kt index 1f1d702551e..a8bd64150f9 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/SuspendFunctionGenerationStrategy.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/SuspendFunctionGenerationStrategy.kt @@ -20,15 +20,14 @@ import org.jetbrains.kotlin.codegen.ExpressionCodegen import org.jetbrains.kotlin.codegen.FunctionGenerationStrategy import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.state.GenerationState +import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.FunctionDescriptor import org.jetbrains.kotlin.psi.KtFunction -import org.jetbrains.kotlin.resolve.jvm.AsmTypes import org.jetbrains.kotlin.resolve.jvm.diagnostics.OtherOrigin import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature -import org.jetbrains.org.objectweb.asm.Label +import org.jetbrains.kotlin.utils.addToStdlib.safeAs import org.jetbrains.org.objectweb.asm.MethodVisitor import org.jetbrains.org.objectweb.asm.Opcodes -import org.jetbrains.org.objectweb.asm.Type class SuspendFunctionGenerationStrategy( state: GenerationState, @@ -36,98 +35,40 @@ class SuspendFunctionGenerationStrategy( private val declaration: KtFunction, private val containingClassInternalName: String ) : FunctionGenerationStrategy.CodegenBased(state) { - private val containsNonTailSuspensionCalls = originalSuspendDescriptor.containsNonTailSuspensionCalls(state.bindingContext) + + private lateinit var transformer: CoroutineTransformerMethodVisitor + private lateinit var codegen: ExpressionCodegen private val classBuilderForCoroutineState by lazy { state.factory.newVisitor( OtherOrigin(declaration, originalSuspendDescriptor), CodegenBinding.asmTypeForAnonymousClass(state.bindingContext, originalSuspendDescriptor), declaration.containingFile - ) + ).also { + val coroutineCodegen = + CoroutineCodegenForNamedFunction.create(it, codegen, originalSuspendDescriptor, declaration) + coroutineCodegen.generate() + } } override fun wrapMethodVisitor(mv: MethodVisitor, access: Int, name: String, desc: String): MethodVisitor { - if (containsNonTailSuspensionCalls) { - return CoroutineTransformerMethodVisitor( - mv, access, name, desc, null, null, containingClassInternalName, classBuilderForCoroutineState, - isForNamedFunction = true - ) - } + if (access and Opcodes.ACC_ABSTRACT != 0) return mv - return super.wrapMethodVisitor(mv, access, name, desc) + return CoroutineTransformerMethodVisitor( + mv, access, name, desc, null, null, containingClassInternalName, this::classBuilderForCoroutineState, + isForNamedFunction = true, + needDispatchReceiver = originalSuspendDescriptor.dispatchReceiverParameter != null, + internalNameForDispatchReceiver = containingClassInternalNameOrNull() + ).also { + transformer = it + } } + private fun containingClassInternalNameOrNull() = + originalSuspendDescriptor.containingDeclaration.safeAs()?.let(state.typeMapper::mapClass)?.internalName + override fun doGenerateBody(codegen: ExpressionCodegen, signature: JvmMethodSignature) { - if (!containsNonTailSuspensionCalls) { - codegen.returnExpression(declaration.bodyExpression) - return - } - - val coroutineCodegen = - CoroutineForNamedFunctionCodegen.create(classBuilderForCoroutineState, codegen, originalSuspendDescriptor, declaration) - coroutineCodegen.generate() - - val functionDescriptor = codegen.context.functionDescriptor - - val continuationIndex = codegen.frameMap.getIndex(functionDescriptor.valueParameters.last()) - val objectTypeForState = Type.getObjectType(classBuilderForCoroutineState.thisName) - - val dataIndex = codegen.frameMap.enterTemp(AsmTypes.OBJECT_TYPE) - val exceptionIndex = codegen.frameMap.enterTemp(AsmTypes.OBJECT_TYPE) - - with(codegen.v) { - val createStateInstance = Label() - val storeStateObject = Label() - - // We have to distinguish the following situations: - // - Our function got called in a common way (e.g. from another function or via recursive call) and we should execute our - // code from the beginning - // - We got called from `doResume` of our continuation, i.e. we need to continue from the last suspension point - // - // Also in the first case we wrap the completion into a special anonymous class instance (let's call it X$1) - // that we'll use as a continuation argument for suspension points - // - // How we distinguish the cases: - // - If the continuation is not an instance of X$1 we know exactly it's not the second case, because when resuming - // the continuation we pass an instance of that class - // - Otherwise it's still can be a recursive call. To check it's not the case we set the last bit in the label in - // `doResume` just before calling the suspend function (see kotlin.coroutines.experimental.jvm.internal.CoroutineImplForNamedFunction). - // So, if it's set we're in continuation. - visitVarInsn(Opcodes.ALOAD, continuationIndex) - instanceOf(objectTypeForState) - ifeq(createStateInstance) - - visitVarInsn(Opcodes.ALOAD, continuationIndex) - checkcast(objectTypeForState) - invokevirtual( - COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, - "checkAndFlushLastBit", Type.getMethodDescriptor(Type.BOOLEAN_TYPE), false - ) - ifeq(createStateInstance) - - visitVarInsn(Opcodes.ALOAD, continuationIndex) - checkcast(objectTypeForState) - goTo(storeStateObject) - - visitLabel(createStateInstance) - coroutineCodegen.putInstanceOnStack(codegen, null).put(objectTypeForState, this) - - visitLabel(storeStateObject) - visitVarInsn(Opcodes.ASTORE, continuationIndex) - - visitVarInsn(Opcodes.ALOAD, continuationIndex) - checkcast(objectTypeForState) - getfield(COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, "data", AsmTypes.OBJECT_TYPE.descriptor) - visitVarInsn(Opcodes.ASTORE, dataIndex) - - visitVarInsn(Opcodes.ALOAD, continuationIndex) - checkcast(objectTypeForState) - getfield(COROUTINE_IMPL_FOR_NAMED_ASM_TYPE.internalName, "exception", AsmTypes.JAVA_THROWABLE_TYPE.descriptor) - visitVarInsn(Opcodes.ASTORE, exceptionIndex) - - invokestatic(COROUTINE_MARKER_OWNER, ACTUAL_COROUTINE_START_MARKER_NAME, "()V", false) - } - + this.codegen = codegen codegen.returnExpression(declaration.bodyExpression) } } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt index 2c5ceae84d4..2339bb0d3dc 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/coroutines/coroutineCodegenUtil.kt @@ -26,7 +26,6 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.codegen.topLevelClassAsmType import org.jetbrains.kotlin.codegen.topLevelClassInternalName -import org.jetbrains.kotlin.coroutines.isSuspendLambda import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotations import org.jetbrains.kotlin.descriptors.impl.ValueParameterDescriptorImpl @@ -156,18 +155,11 @@ fun ResolvedCall<*>.replaceSuspensionFunctionWithRealDescriptor( return ResolvedCallWithRealDescriptor(newCall, thisExpression) } -fun ResolvedCall<*>.isSuspensionPointInStateMachine(bindingContext: BindingContext): Boolean { - if (resultingDescriptor.safeAs()?.isSuspend != true) return false - val enclosingSuspendFunction = bindingContext[BindingContext.ENCLOSING_SUSPEND_FUNCTION_FOR_SUSPEND_FUNCTION_CALL, call] ?: return false - - return enclosingSuspendFunction.isStateMachineNeeded(bindingContext) -} - -fun FunctionDescriptor.isStateMachineNeeded(bindingContext: BindingContext) = - isSuspendLambda || containsNonTailSuspensionCalls(bindingContext) - -fun FunctionDescriptor.containsNonTailSuspensionCalls(bindingContext: BindingContext) = - bindingContext[BindingContext.CONTAINS_NON_TAIL_SUSPEND_CALLS, original] == true +fun ResolvedCall<*>.isSuspendNoInlineCall() = + resultingDescriptor.safeAs() + ?.let { + it.isSuspend && (!it.isInline || it.isBuiltInSuspendCoroutineOrReturnInJvm()) + } == true fun CallableDescriptor.isSuspendFunctionNotSuspensionView(): Boolean { if (this !is FunctionDescriptor) return false diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java index 9762ba38b6c..ada65496c16 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.java @@ -27,7 +27,6 @@ import org.jetbrains.kotlin.builtins.BuiltInsPackageFragment; import org.jetbrains.kotlin.codegen.*; import org.jetbrains.kotlin.codegen.context.*; import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt; -import org.jetbrains.kotlin.codegen.coroutines.SuspendFunctionGenerationStrategy; import org.jetbrains.kotlin.codegen.intrinsics.IntrinsicArrayConstructorsKt; import org.jetbrains.kotlin.codegen.state.GenerationState; import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper; @@ -593,14 +592,6 @@ public class InlineCodegen extends CallGenerator { else if (expression instanceof KtFunctionLiteral) { strategy = new ClosureGenerationStrategy(state, (KtDeclarationWithBody) expression); } - else if (descriptor.isSuspend() && expression instanceof KtFunction) { - strategy = - new SuspendFunctionGenerationStrategy( - state, - CoroutineCodegenUtilKt.unwrapInitialDescriptorForSuspendFunction(descriptor), - (KtFunction) expression, null - ); - } else { strategy = new FunctionGenerationStrategy.FunctionDefault(state, (KtDeclarationWithBody) expression); } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/ControlFlowGraph.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/ControlFlowGraph.kt index e9695c6ebde..2bfb3bb8230 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/ControlFlowGraph.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/optimization/common/ControlFlowGraph.kt @@ -25,7 +25,8 @@ import org.jetbrains.org.objectweb.asm.tree.analysis.BasicValue class ControlFlowGraph private constructor(private val insns: InsnList) { private val edges: Array> = Array(insns.size()) { arrayListOf() } - fun getSuccessorsIndices(insn: AbstractInsnNode): List = edges[insns.indexOf(insn)] + fun getSuccessorsIndices(insn: AbstractInsnNode): List = getSuccessorsIndices(insns.indexOf(insn)) + fun getSuccessorsIndices(index: Int): List = edges[index] companion object { @JvmStatic diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java index 9600ba02965..97b76d94ad9 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/KotlinTypeMapper.java @@ -787,7 +787,8 @@ public class KotlinTypeMapper { isInterfaceMember = true; } else { - boolean isPrivateFunInvocation = Visibilities.isPrivate(functionDescriptor.getVisibility()); + boolean isPrivateFunInvocation = + Visibilities.isPrivate(functionDescriptor.getVisibility()) && !functionDescriptor.isSuspend(); invokeOpcode = superCall || isPrivateFunInvocation ? INVOKESPECIAL : INVOKEVIRTUAL; isInterfaceMember = superCall && currentIsInterface; } diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt new file mode 100644 index 00000000000..b8f4a5f4a78 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt @@ -0,0 +1,31 @@ +// IGNORE_BACKEND: JS +// WITH_RUNTIME +// WITH_COROUTINES +import helpers.* +// CHECK_BYTECODE_LISTING +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +inline suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x -> + x.resume(v) + COROUTINE_SUSPENDED +} + +// There's no state machine in the suspendHere, since it's inline +inline suspend fun suspendHere(): String = suspendThere("O") + suspendThere("K") +// There should be a state machine for mainSuspend as it has two suspend non-tail calls inlined +suspend fun mainSuspend() = suspendHere() + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +fun box(): String { + var result = "" + + builder { + result = mainSuspend() + } + + return result +} diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.txt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.txt new file mode 100644 index 00000000000..f30ea21d9d4 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.txt @@ -0,0 +1,31 @@ +@kotlin.Metadata +final class InlineWithStateMachineKt$box$1 { + synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef + field L$0: java.lang.Object + inner class InlineWithStateMachineKt$box$1 + method (p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): kotlin.coroutines.experimental.Continuation + public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + public final @org.jetbrains.annotations.Nullable method invoke(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object + public synthetic method invoke(p0: java.lang.Object): java.lang.Object +} + +@kotlin.Metadata +final class InlineWithStateMachineKt$mainSuspend$1 { + field L$0: java.lang.Object + field L$1: java.lang.Object + inner class InlineWithStateMachineKt$mainSuspend$1 + method (p0: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.Nullable method doResume(): java.lang.Object +} + +@kotlin.Metadata +public final class InlineWithStateMachineKt { + inner class InlineWithStateMachineKt$box$1 + inner class InlineWithStateMachineKt$mainSuspend$1 + 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 mainSuspend(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method suspendHere(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.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.experimental.Continuation): java.lang.Object +} diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt new file mode 100644 index 00000000000..845db0b8be0 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt @@ -0,0 +1,38 @@ +// IGNORE_BACKEND: NATIVE +// WITH_RUNTIME +// WITH_COROUTINES +import helpers.* +// CHECK_BYTECODE_LISTING +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +inline suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x -> + x.resume(v) + COROUTINE_SUSPENDED +} + +suspend fun suspendHere(): String = suspendThere("O") + +// There is a kind of redundant state machine generated for complexSuspend: +// it's basically has the only suspend call just before return, but there is +// a redundant CHECKCAST String in the run's lambda, so we have to insert the state machine. +// TODO: Think of avoiding such redundant casts +suspend fun complexSuspend(): String { + return run { + suspendThere("K") + } +} + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +fun box(): String { + var result = "" + + builder { + result = suspendHere() + complexSuspend() + } + + return result +} diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.txt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.txt new file mode 100644 index 00000000000..93f4b80f255 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.txt @@ -0,0 +1,32 @@ +@kotlin.Metadata +final class InlineWithoutStateMachineKt$box$1 { + synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef + field L$0: java.lang.Object + field L$1: java.lang.Object + inner class InlineWithoutStateMachineKt$box$1 + method (p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): kotlin.coroutines.experimental.Continuation + public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + public final @org.jetbrains.annotations.Nullable method invoke(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object + public synthetic method invoke(p0: java.lang.Object): java.lang.Object +} + +@kotlin.Metadata +final class InlineWithoutStateMachineKt$complexSuspend$1 { + field L$0: java.lang.Object + field L$1: java.lang.Object + inner class InlineWithoutStateMachineKt$complexSuspend$1 + method (p0: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.Nullable method doResume(): java.lang.Object +} + +@kotlin.Metadata +public final class InlineWithoutStateMachineKt { + inner class InlineWithoutStateMachineKt$box$1 + inner class InlineWithoutStateMachineKt$complexSuspend$1 + 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 complexSuspend(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object + public final static @org.jetbrains.annotations.Nullable method suspendHere(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.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.experimental.Continuation): java.lang.Object +} diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt new file mode 100644 index 00000000000..85f0f44f878 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt @@ -0,0 +1,28 @@ +// IGNORE_BACKEND: NATIVE +// WITH_RUNTIME +// WITH_COROUTINES +// CHECK_BYTECODE_LISTING +import helpers.* +import kotlin.coroutines.experimental.* +import kotlin.coroutines.experimental.intrinsics.* + +suspend fun suspendThere(v: String): String = suspendCoroutineOrReturn { x -> + x.resume(v) + COROUTINE_SUSPENDED +} + +suspend fun suspendHere(): String = suspendThere("OK") + +fun builder(c: suspend () -> Unit) { + c.startCoroutine(EmptyContinuation) +} + +fun box(): String { + var result = "" + + builder { + result = suspendHere() + } + + return result +} diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.txt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.txt new file mode 100644 index 00000000000..18525856960 --- /dev/null +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.txt @@ -0,0 +1,20 @@ +@kotlin.Metadata +final class SimpleKt$box$1 { + synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef + field L$0: java.lang.Object + inner class SimpleKt$box$1 + method (p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.experimental.Continuation): void + public final @org.jetbrains.annotations.NotNull method create(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): kotlin.coroutines.experimental.Continuation + public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + public final @org.jetbrains.annotations.Nullable method invoke(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.Continuation): java.lang.Object + public synthetic method invoke(p0: java.lang.Object): java.lang.Object +} + +@kotlin.Metadata +public final class SimpleKt { + inner class SimpleKt$box$1 + 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 suspendHere(@org.jetbrains.annotations.NotNull p0: kotlin.coroutines.experimental.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.experimental.Continuation): java.lang.Object +} diff --git a/compiler/testData/codegen/bytecodeListing/coroutineFields.txt b/compiler/testData/codegen/bytecodeListing/coroutineFields.txt index a6c6043e36c..00a6576f8d9 100644 --- a/compiler/testData/codegen/bytecodeListing/coroutineFields.txt +++ b/compiler/testData/codegen/bytecodeListing/coroutineFields.txt @@ -1,17 +1,19 @@ @kotlin.Metadata final class Controller$multipleSuspensions$1 { + field L$0: java.lang.Object synthetic final field this$0: Controller inner class Controller$multipleSuspensions$1 method (p0: Controller, p1: kotlin.coroutines.experimental.Continuation): void - public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + public final @org.jetbrains.annotations.Nullable method doResume(): java.lang.Object } @kotlin.Metadata final class Controller$nonTailCall$1 { + field L$0: java.lang.Object synthetic final field this$0: Controller inner class Controller$nonTailCall$1 method (p0: Controller, p1: kotlin.coroutines.experimental.Continuation): void - public final @org.jetbrains.annotations.Nullable method doResume(@org.jetbrains.annotations.Nullable p0: java.lang.Object, @org.jetbrains.annotations.Nullable p1: java.lang.Throwable): java.lang.Object + public final @org.jetbrains.annotations.Nullable method doResume(): java.lang.Object } @kotlin.Metadata @@ -28,9 +30,9 @@ public final class Controller { @kotlin.Metadata final class CoroutineFieldsKt$box$1 { synthetic final field $result: kotlin.jvm.internal.Ref$ObjectRef - private field J$0: long - private field L$0: java.lang.Object - private field L$1: java.lang.Object + field J$0: long + field L$0: java.lang.Object + field L$1: java.lang.Object private field p$: Controller inner class CoroutineFieldsKt$box$1 method (p0: kotlin.jvm.internal.Ref$ObjectRef, p1: kotlin.coroutines.experimental.Continuation): void diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index ff3a75aa40e..68af09b298b 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -5708,6 +5708,33 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes } } + @TestMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class TailCallOptimizations extends AbstractIrBlackBoxCodegenTest { + public void testAllFilesPresentInTailCallOptimizations() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("inlineWithStateMachine.kt") + public void testInlineWithStateMachine() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt"); + doTest(fileName); + } + + @TestMetadata("inlineWithoutStateMachine.kt") + public void testInlineWithoutStateMachine() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 2d6cb377bf9..154b9e85131 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -5708,6 +5708,33 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { } } + @TestMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class TailCallOptimizations extends AbstractBlackBoxCodegenTest { + public void testAllFilesPresentInTailCallOptimizations() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("inlineWithStateMachine.kt") + public void testInlineWithStateMachine() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt"); + doTest(fileName); + } + + @TestMetadata("inlineWithoutStateMachine.kt") + public void testInlineWithoutStateMachine() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index d575b7256b0..28a8c86a5e9 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -5708,6 +5708,33 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes } } + @TestMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class TailCallOptimizations extends AbstractLightAnalysisModeTest { + public void testAllFilesPresentInTailCallOptimizations() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true); + } + + @TestMetadata("inlineWithStateMachine.kt") + public void testInlineWithStateMachine() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt"); + doTest(fileName); + } + + @TestMetadata("inlineWithoutStateMachine.kt") + public void testInlineWithoutStateMachine() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java index 23b8fd2eb8b..a8990d2d0e3 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/semantics/JsCodegenBoxTestGenerated.java @@ -6405,6 +6405,39 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest { } } + @TestMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class TailCallOptimizations extends AbstractJsCodegenBoxTest { + public void testAllFilesPresentInTailCallOptimizations() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/coroutines/tailCallOptimizations"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JS, true); + } + + @TestMetadata("inlineWithStateMachine.kt") + public void testInlineWithStateMachine() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithStateMachine.kt"); + try { + doTest(fileName); + } + catch (Throwable ignore) { + return; + } + throw new AssertionError("Looks like this test can be unmuted. Remove IGNORE_BACKEND directive for that."); + } + + @TestMetadata("inlineWithoutStateMachine.kt") + public void testInlineWithoutStateMachine() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/inlineWithoutStateMachine.kt"); + doTest(fileName); + } + + @TestMetadata("simple.kt") + public void testSimple() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/coroutines/tailCallOptimizations/simple.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/box/coroutines/tailOperations") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class)