diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt index 984907ff5ea..670510e1a80 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/AnonymousObjectTransformer.kt @@ -8,7 +8,6 @@ package org.jetbrains.kotlin.codegen.inline import com.intellij.util.ArrayUtil import org.jetbrains.kotlin.codegen.* import org.jetbrains.kotlin.codegen.coroutines.DEBUG_METADATA_ANNOTATION_ASM_TYPE -import org.jetbrains.kotlin.codegen.coroutines.isCapturedSuspendLambda import org.jetbrains.kotlin.codegen.coroutines.isCoroutineSuperClass import org.jetbrains.kotlin.codegen.coroutines.isResumeImplMethodName import org.jetbrains.kotlin.codegen.inline.coroutines.CoroutineTransformer @@ -526,10 +525,8 @@ class AnonymousObjectTransformer( alreadyAddedParam?.newFieldName ?: getNewFieldName(desc.fieldName, false), alreadyAddedParam != null ) - if (info is PsiExpressionLambda && info.closure.captureVariables.any { it.value.fieldName == desc.fieldName }) { - recapturedParamInfo.functionalArgument = NonInlineableArgumentForInlineableParameterCalledInSuspend( - isCapturedSuspendLambda(info.closure, desc.fieldName, inliningContext.state.bindingContext) - ) + if (info is ExpressionLambda && info.isCapturedSuspend(desc, inliningContext)) { + recapturedParamInfo.functionalArgument = NonInlineableArgumentForInlineableParameterCalledInSuspend } val composed = StackValue.field( desc.type, diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt index 7d0e4e7d3cd..2769befdd89 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/InlineCodegen.kt @@ -391,7 +391,7 @@ abstract class InlineCodegen( info = invocationParamBuilder.addNextValueParameter(jvmType, false, remappedValue, parameterIndex) info.functionalArgument = when (kind) { ValueKind.NON_INLINEABLE_ARGUMENT_FOR_INLINE_PARAMETER_CALLED_IN_SUSPEND -> - NonInlineableArgumentForInlineableParameterCalledInSuspend(kotlinType?.isSuspendFunctionTypeOrSubtype == true) + NonInlineableArgumentForInlineableParameterCalledInSuspend ValueKind.NON_INLINEABLE_ARGUMENT_FOR_INLINE_SUSPEND_PARAMETER -> NonInlineableArgumentForInlineableSuspendParameter else -> null } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt index c02fb3755b6..b679543f105 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/LambdaInfo.kt @@ -14,6 +14,7 @@ import org.jetbrains.kotlin.codegen.binding.CodegenBinding.* import org.jetbrains.kotlin.codegen.binding.MutableClosure import org.jetbrains.kotlin.codegen.context.EnclosedValueDescriptor import org.jetbrains.kotlin.codegen.coroutines.getOrCreateJvmSuspendFunctionView +import org.jetbrains.kotlin.codegen.coroutines.isCapturedSuspendLambda import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper import org.jetbrains.kotlin.config.LanguageVersionSettings import org.jetbrains.kotlin.config.isReleaseCoroutines @@ -82,7 +83,7 @@ abstract class LambdaInfo(@JvmField val isCrossInline: Boolean) : FunctionalArgu } } -class NonInlineableArgumentForInlineableParameterCalledInSuspend(val isSuspend: Boolean) : FunctionalArgument +object NonInlineableArgumentForInlineableParameterCalledInSuspend : FunctionalArgument object NonInlineableArgumentForInlineableSuspendParameter : FunctionalArgument @@ -215,6 +216,7 @@ abstract class ExpressionLambda(isCrossInline: Boolean) : LambdaInfo(isCrossInli } abstract fun getInlineSuspendLambdaViewDescriptor(): FunctionDescriptor + abstract fun isCapturedSuspend(desc: CapturedParamDesc, inliningContext: InliningContext): Boolean } class PsiExpressionLambda( @@ -328,4 +330,7 @@ class PsiExpressionLambda( typeMapper.bindingContext ) } + + override fun isCapturedSuspend(desc: CapturedParamDesc, inliningContext: InliningContext): Boolean = + isCapturedSuspendLambda(closure, desc.fieldName, inliningContext.state.bindingContext) } \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineCodegen.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineCodegen.kt index df0f924ccb5..d7a8169510c 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineCodegen.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/PsiInlineCodegen.kt @@ -156,7 +156,8 @@ class PsiInlineCodegen( } else { val value = codegen.gen(argumentExpression) val kind = when { - isCallSiteIsSuspend(valueParameterDescriptor) -> ValueKind.NON_INLINEABLE_ARGUMENT_FOR_INLINE_PARAMETER_CALLED_IN_SUSPEND + isCallSiteIsSuspend(valueParameterDescriptor) && parameterType.kotlinType?.isSuspendFunctionTypeOrSubtype == true -> + ValueKind.NON_INLINEABLE_ARGUMENT_FOR_INLINE_PARAMETER_CALLED_IN_SUSPEND isInlineSuspendParameter(valueParameterDescriptor) -> ValueKind.NON_INLINEABLE_ARGUMENT_FOR_INLINE_SUSPEND_PARAMETER else -> ValueKind.GENERAL } diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/coroutines/CoroutineTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/coroutines/CoroutineTransformer.kt index 0d3058fe87d..ac851edb6c5 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/coroutines/CoroutineTransformer.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/coroutines/CoroutineTransformer.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.codegen.inline.coroutines import com.intellij.util.ArrayUtil -import org.jetbrains.kotlin.backend.common.CodegenUtil import org.jetbrains.kotlin.codegen.AsmUtil.CAPTURED_THIS_FIELD import org.jetbrains.kotlin.codegen.ClassBuilder import org.jetbrains.kotlin.codegen.TransformationMethodVisitor @@ -19,18 +18,7 @@ import org.jetbrains.kotlin.codegen.optimization.fixStack.FixStackMethodTransfor import org.jetbrains.kotlin.config.isReleaseCoroutines import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor -import org.jetbrains.kotlin.load.java.JvmAnnotationNames -import org.jetbrains.kotlin.load.kotlin.FileBasedKotlinClass -import org.jetbrains.kotlin.load.kotlin.header.KotlinClassHeader -import org.jetbrains.kotlin.load.kotlin.header.ReadKotlinClassHeaderAnnotationVisitor -import org.jetbrains.kotlin.metadata.ProtoBuf -import org.jetbrains.kotlin.metadata.deserialization.* -import org.jetbrains.kotlin.metadata.jvm.deserialization.JvmProtoBufUtil -import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.resolve.jvm.diagnostics.JvmDeclarationOrigin -import org.jetbrains.kotlin.serialization.deserialization.getClassId -import org.jetbrains.kotlin.serialization.deserialization.getName import org.jetbrains.kotlin.utils.addToStdlib.cast import org.jetbrains.org.objectweb.asm.* import org.jetbrains.org.objectweb.asm.tree.* @@ -291,8 +279,7 @@ class SurroundSuspendLambdaCallsWithSuspendMarkersMethodVisitor( } private fun FunctionalArgument.isSuspendLambda(): Boolean = - (this is NonInlineableArgumentForInlineableParameterCalledInSuspend && isSuspend) || - (this is PsiExpressionLambda && isSuspend) + this is NonInlineableArgumentForInlineableParameterCalledInSuspend || (this is ExpressionLambda && isSuspend) fun surroundInvokesWithSuspendMarkers( methodNode: MethodNode, diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/CoroutineCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/CoroutineCodegen.kt index 6755c7e7f1c..7a1d1251b61 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/CoroutineCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/CoroutineCodegen.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.backend.jvm.codegen import org.jetbrains.kotlin.backend.common.CodegenUtil +import org.jetbrains.kotlin.backend.common.lower.LocalDeclarationsLowering import org.jetbrains.kotlin.backend.common.lower.allOverridden import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin @@ -17,11 +18,11 @@ import org.jetbrains.kotlin.codegen.coroutines.reportSuspensionPointInsideMonito import org.jetbrains.kotlin.codegen.inline.addFakeContinuationConstructorCallMarker import org.jetbrains.kotlin.config.isReleaseCoroutines import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET -import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin -import org.jetbrains.kotlin.ir.declarations.IrFunction -import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction -import org.jetbrains.kotlin.ir.declarations.name +import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.expressions.IrExpression +import org.jetbrains.kotlin.ir.expressions.IrGetField +import org.jetbrains.kotlin.ir.expressions.IrGetValue +import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin import org.jetbrains.kotlin.ir.expressions.impl.IrErrorExpressionImpl import org.jetbrains.kotlin.ir.types.createType import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection @@ -135,6 +136,15 @@ internal fun IrFunction.isKnownToBeTailCall(): Boolean = internal fun IrFunction.shouldNotContainSuspendMarkers(): Boolean = isInvokeSuspendOfContinuation() || isKnownToBeTailCall() +internal fun IrExpression?.isReadOfCrossinline(): Boolean = when (this) { + is IrGetValue -> (symbol.owner as? IrValueParameter)?.isCrossinline == true + is IrGetField -> symbol.owner.origin == LocalDeclarationsLowering.DECLARATION_ORIGIN_FIELD_FOR_CROSSINLINE_CAPTURED_VALUE + else -> false +} + +internal fun IrExpression?.isReadOfInlineLambda(): Boolean = isReadOfCrossinline() || + (this is IrGetValue && origin == IrStatementOrigin.VARIABLE_AS_FUNCTION && (symbol.owner as? IrValueParameter)?.isNoinline == false) + internal fun createFakeContinuation(context: JvmBackendContext): IrExpression = IrErrorExpressionImpl( UNDEFINED_OFFSET, UNDEFINED_OFFSET, diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 0d08c9fe4c7..e6d7122e2eb 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -6,7 +6,6 @@ package org.jetbrains.kotlin.backend.jvm.codegen import org.jetbrains.kotlin.backend.common.lower.BOUND_RECEIVER_PARAMETER -import org.jetbrains.kotlin.backend.common.lower.LocalDeclarationsLowering import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin.* import org.jetbrains.kotlin.backend.jvm.intrinsics.IrIntrinsicMethods @@ -476,21 +475,15 @@ class ExpressionCodegen( } } - private fun IrFunctionAccessExpression.isInvokeOfInlineLambda(): Boolean = when (val receiver = dispatchReceiver) { - is IrGetField -> receiver.symbol.owner.origin == LocalDeclarationsLowering.DECLARATION_ORIGIN_FIELD_FOR_CROSSINLINE_CAPTURED_VALUE - is IrGetValue -> receiver.origin == IrStatementOrigin.VARIABLE_AS_FUNCTION && - (receiver.symbol.owner as? IrValueParameter)?.isNoinline == false - else -> false - } - private fun IrFunctionAccessExpression.isSuspensionPoint(): Boolean = when { !symbol.owner.isSuspend || irFunction.shouldNotContainSuspendMarkers() -> false // Copy-pasted bytecode blocks are not suspension points. symbol.owner.isInline -> symbol.owner.fqNameForIrSerialization == FqName("kotlin.coroutines.intrinsics.IntrinsicsKt.suspendCoroutineUninterceptedOrReturn") // This includes inline lambdas, but only in functions intended for the inliner; in others, they stay as `f.invoke()`. - isInvokeOfInlineLambda() -> - irFunction.origin != FOR_INLINE_STATE_MACHINE_TEMPLATE && irFunction.origin != FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE + dispatchReceiver.isReadOfInlineLambda() -> + irFunction.origin != FOR_INLINE_STATE_MACHINE_TEMPLATE && irFunction.origin != FOR_INLINE_STATE_MACHINE_TEMPLATE_CAPTURES_CROSSINLINE && + irFunction.origin != IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA else -> true } diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt index 73321bd23ee..81f4a8be259 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/FunctionCodegen.kt @@ -141,14 +141,13 @@ open class FunctionCodegen( // 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 && 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 && it.attributeOwnerId == (this as? IrAttributeContainer)?.attributeOwnerId - } && - // $$forInline functions never have a continuation - origin != JvmLoweredDeclarationOrigin.FOR_INLINE_STATE_MACHINE_TEMPLATE + } private fun continuationClass() = irFunction.body!!.statements.firstIsInstance() diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt index 397d62ae759..f9a6374ca02 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/codegen/IrInlineCodegen.kt @@ -241,11 +241,13 @@ class IrExpressionLambdaImpl( override val lambdaClassType: Type = context.getLocalClassType(reference) ?: throw AssertionError("callable reference ${reference.dump()} has no name in context") - override val capturedVars: List = - reference.getArgumentsWithIr().map { (param, _) -> - capturedParamDesc(param.name.asString(), typeMapper.mapType(param.type)) + private val capturedParameters: Map = + reference.getArgumentsWithIr().associate { (param, _) -> + capturedParamDesc(param.name.asString(), typeMapper.mapType(param.type)) to param } + override val capturedVars: List = capturedParameters.keys.toList() + private val loweredMethod = methodSignatureMapper.mapAsmMethod(function.suspendFunctionViewOrStub(context)) val capturedParamsInDesc: List = if (isBoundCallableReference) { @@ -271,6 +273,9 @@ class IrExpressionLambdaImpl( override fun getInlineSuspendLambdaViewDescriptor(): FunctionDescriptor { return function.suspendFunctionViewOrStub(context).descriptor } + + override fun isCapturedSuspend(desc: CapturedParamDesc, inliningContext: InliningContext): Boolean = + capturedParameters[desc]?.let { it.isInlineParameter() && it.type.isSuspendFunctionTypeOrSubtype() } == true } class IrDefaultLambda( diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt index 86b3836c0c9..8f5e8a3feca 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/AddContinuationLowering.kt @@ -15,6 +15,7 @@ import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeSuspendForInlineOfLambda import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeSuspendOfContinuation import org.jetbrains.kotlin.backend.jvm.codegen.isInvokeSuspendOfLambda +import org.jetbrains.kotlin.backend.jvm.codegen.isReadOfCrossinline import org.jetbrains.kotlin.backend.jvm.ir.* import org.jetbrains.kotlin.backend.jvm.localDeclarationsPhase import org.jetbrains.kotlin.builtins.functions.FunctionInvokeDescriptor.Factory.BIG_ARITY @@ -679,13 +680,7 @@ private class AddContinuationLowering(private val context: JvmBackendContext) : private class SuspendLambdaInfo(val reference: IrFunctionReference) { val function = reference.symbol.owner val arity = (reference.type as IrSimpleType).arguments.size - 1 - val capturesCrossinline = function.valueParameters.any { - when (val argument = reference.getValueArgument(it.index)) { - is IrGetValue -> (argument.symbol.owner as? IrValueParameter)?.isCrossinline == true - is IrGetField -> argument.symbol.owner.origin == LocalDeclarationsLowering.DECLARATION_ORIGIN_FIELD_FOR_CROSSINLINE_CAPTURED_VALUE - else -> false - } - } + val capturesCrossinline = function.valueParameters.any { reference.getValueArgument(it.index).isReadOfCrossinline() } lateinit var constructor: IrConstructor } } diff --git a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt index eb7055d2a28..c1609cb539a 100644 --- a/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt +++ b/compiler/testData/codegen/box/coroutines/tailCallOptimizations/crossinline_ir.txt @@ -44,20 +44,6 @@ public final class CrossinlineKt$box$1$invokeSuspend$$inlined$filter$1 { public @org.jetbrains.annotations.Nullable method consume(@org.jetbrains.annotations.NotNull p0: Sink, @org.jetbrains.annotations.NotNull p1: kotlin.coroutines.Continuation): java.lang.Object } -@kotlin.Metadata -@kotlin.coroutines.jvm.internal.DebugMetadata -final class CrossinlineKt$box$1$invokeSuspend$$inlined$fold$1$1 { - field L$0: java.lang.Object - field L$1: java.lang.Object - field L$2: java.lang.Object - field L$3: java.lang.Object - field label: int - @org.jetbrains.annotations.NotNull field result: java.lang.Object - synthetic final field this$0: CrossinlineKt$box$1$invokeSuspend$$inlined$fold$1 - public method (p0: CrossinlineKt$box$1$invokeSuspend$$inlined$fold$1, p1: 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 CrossinlineKt$box$1$invokeSuspend$$inlined$fold$1 { synthetic final field $acc$inlined: kotlin.jvm.internal.Ref$ObjectRef diff --git a/compiler/testData/codegen/boxInline/suspend/crossinlineSuspendLambdaInsideCrossinlineSuspendLambda.kt b/compiler/testData/codegen/boxInline/suspend/crossinlineSuspendLambdaInsideCrossinlineSuspendLambda.kt index a5075cb2728..83d15d96c40 100644 --- a/compiler/testData/codegen/boxInline/suspend/crossinlineSuspendLambdaInsideCrossinlineSuspendLambda.kt +++ b/compiler/testData/codegen/boxInline/suspend/crossinlineSuspendLambdaInsideCrossinlineSuspendLambda.kt @@ -2,6 +2,8 @@ // COMMON_COROUTINES_TEST // WITH_RUNTIME // WITH_COROUTINES +// IGNORE_BACKEND: JVM_IR +// IGNORE_BACKEND_MULTI_MODULE: JVM_IR import COROUTINES_PACKAGE.* import helpers.* diff --git a/compiler/testData/codegen/boxInline/suspend/doubleRegenerationWithNonSuspendingLambda.kt b/compiler/testData/codegen/boxInline/suspend/doubleRegenerationWithNonSuspendingLambda.kt index a904f0cec31..beca6d56678 100644 --- a/compiler/testData/codegen/boxInline/suspend/doubleRegenerationWithNonSuspendingLambda.kt +++ b/compiler/testData/codegen/boxInline/suspend/doubleRegenerationWithNonSuspendingLambda.kt @@ -2,8 +2,6 @@ // COMMON_COROUTINES_TEST // WITH_RUNTIME // WITH_COROUTINES -// IGNORE_BACKEND: JVM_IR -// IGNORE_BACKEND_MULTI_MODULE: JVM_IR interface Flow { abstract suspend fun collect(collector: FlowCollector)