JVM_IR: Move suspend functions into view transformation to codegen phase
Support suspend functions with default arguments.
This commit is contained in:
@@ -1,17 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Copyright 2010-2017 JetBrains s.r.o.
|
* Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
*
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
* 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.backend.common.ir
|
package org.jetbrains.kotlin.backend.common.ir
|
||||||
@@ -528,7 +517,7 @@ fun createStaticFunctionWithReceivers(
|
|||||||
modality,
|
modality,
|
||||||
oldFunction.returnType,
|
oldFunction.returnType,
|
||||||
isInline = oldFunction.isInline,
|
isInline = oldFunction.isInline,
|
||||||
isExternal = false, isTailrec = false, isSuspend = false
|
isExternal = false, isTailrec = false, isSuspend = oldFunction.isSuspend
|
||||||
).apply {
|
).apply {
|
||||||
descriptor.bind(this)
|
descriptor.bind(this)
|
||||||
parent = irParent
|
parent = irParent
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ class JvmBackendContext(
|
|||||||
val suspendFunctionContinuations = mutableMapOf<IrFunction, IrClass>()
|
val suspendFunctionContinuations = mutableMapOf<IrFunction, IrClass>()
|
||||||
val suspendLambdaToOriginalFunctionMap = mutableMapOf<IrClass, IrFunction>()
|
val suspendLambdaToOriginalFunctionMap = mutableMapOf<IrClass, IrFunction>()
|
||||||
val continuationClassBuilders = mutableMapOf<IrClass, ClassBuilder>()
|
val continuationClassBuilders = mutableMapOf<IrClass, ClassBuilder>()
|
||||||
|
val suspendFunctionViews = mutableMapOf<IrFunction, IrFunction>()
|
||||||
|
|
||||||
val staticDefaultStubs = mutableMapOf<IrFunctionSymbol, IrFunction>()
|
val staticDefaultStubs = mutableMapOf<IrFunctionSymbol, IrFunction>()
|
||||||
|
|
||||||
|
|||||||
+105
-3
@@ -5,18 +5,46 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.backend.jvm.codegen
|
package org.jetbrains.kotlin.backend.jvm.codegen
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.descriptors.WrappedFunctionDescriptorWithContainerSource
|
||||||
|
import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.backend.common.ir.copyTo
|
||||||
|
import org.jetbrains.kotlin.backend.common.ir.copyTypeParametersFrom
|
||||||
|
import org.jetbrains.kotlin.backend.common.ir.isSuspend
|
||||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||||
import org.jetbrains.kotlin.codegen.ClassBuilder
|
import org.jetbrains.kotlin.codegen.ClassBuilder
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.CoroutineTransformerMethodVisitor
|
import org.jetbrains.kotlin.codegen.coroutines.CoroutineTransformerMethodVisitor
|
||||||
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
import org.jetbrains.kotlin.codegen.coroutines.INVOKE_SUSPEND_METHOD_NAME
|
||||||
|
import org.jetbrains.kotlin.codegen.coroutines.SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME
|
||||||
|
import org.jetbrains.kotlin.config.coroutinesPackageFqName
|
||||||
import org.jetbrains.kotlin.config.isReleaseCoroutines
|
import org.jetbrains.kotlin.config.isReleaseCoroutines
|
||||||
|
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||||
|
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||||
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
import org.jetbrains.kotlin.ir.declarations.IrFunction
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||||
|
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.IrGetValue
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.copyTypeArgumentsFrom
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||||
|
import org.jetbrains.kotlin.ir.expressions.impl.IrGetValueImpl
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
|
||||||
|
import org.jetbrains.kotlin.ir.types.createType
|
||||||
|
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
|
||||||
|
import org.jetbrains.kotlin.ir.util.deepCopyWithSymbols
|
||||||
|
import org.jetbrains.kotlin.ir.util.explicitParameters
|
||||||
import org.jetbrains.kotlin.ir.util.isSuspend
|
import org.jetbrains.kotlin.ir.util.isSuspend
|
||||||
|
import org.jetbrains.kotlin.ir.util.parentAsClass
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||||
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature
|
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodGenericSignature
|
||||||
|
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
|
||||||
|
import org.jetbrains.kotlin.types.Variance
|
||||||
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
import org.jetbrains.org.objectweb.asm.MethodVisitor
|
||||||
|
|
||||||
fun generateStateMachineForNamedFunction(
|
internal fun generateStateMachineForNamedFunction(
|
||||||
irFunction: IrFunction,
|
irFunction: IrFunction,
|
||||||
classCodegen: ClassCodegen,
|
classCodegen: ClassCodegen,
|
||||||
methodVisitor: MethodVisitor,
|
methodVisitor: MethodVisitor,
|
||||||
@@ -44,7 +72,7 @@ fun generateStateMachineForNamedFunction(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun generateStateMachineForLambda(
|
internal fun generateStateMachineForLambda(
|
||||||
classCodegen: ClassCodegen,
|
classCodegen: ClassCodegen,
|
||||||
methodVisitor: MethodVisitor,
|
methodVisitor: MethodVisitor,
|
||||||
access: Int,
|
access: Int,
|
||||||
@@ -66,5 +94,79 @@ fun generateStateMachineForLambda(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun IrFunction.isInvokeSuspendOfLambda(context: JvmBackendContext): Boolean =
|
internal fun IrFunction.isInvokeSuspendOfLambda(context: JvmBackendContext): Boolean =
|
||||||
name.asString() == INVOKE_SUSPEND_METHOD_NAME && parent in context.suspendLambdaToOriginalFunctionMap
|
name.asString() == INVOKE_SUSPEND_METHOD_NAME && parent in context.suspendLambdaToOriginalFunctionMap
|
||||||
|
|
||||||
|
internal fun IrFunction.isInvokeSuspendOfContinuation(context: JvmBackendContext): Boolean =
|
||||||
|
name.asString() == INVOKE_SUSPEND_METHOD_NAME && parentAsClass in context.suspendFunctionContinuations.values
|
||||||
|
|
||||||
|
// Transform `suspend fun foo(params): RetType` into `fun foo(params, $completion: Continuation<RetType>): Any?`
|
||||||
|
// the result is called 'view', just to be consistent with old backend.
|
||||||
|
internal fun IrFunction.getOrCreateSuspendFunctionViewIfNeeded(context: JvmBackendContext): IrFunction {
|
||||||
|
if (!isSuspend) return this
|
||||||
|
// TODO: Optimize
|
||||||
|
if (context.suspendFunctionViews.values.contains(this)) return this
|
||||||
|
return if (isSuspend) context.suspendFunctionViews.getOrPut(this) { suspendFunctionView(context) } else this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun IrFunction.suspendFunctionView(context: JvmBackendContext): IrFunction {
|
||||||
|
require(this.isSuspend && this is IrSimpleFunction)
|
||||||
|
val originalDescriptor = this.descriptor
|
||||||
|
val descriptor =
|
||||||
|
if (originalDescriptor is DescriptorWithContainerSource && originalDescriptor.containerSource != null)
|
||||||
|
WrappedFunctionDescriptorWithContainerSource(originalDescriptor.containerSource!!)
|
||||||
|
else
|
||||||
|
WrappedSimpleFunctionDescriptor(sourceElement = originalDescriptor.source)
|
||||||
|
return IrFunctionImpl(
|
||||||
|
startOffset, endOffset, origin, IrSimpleFunctionSymbolImpl(descriptor),
|
||||||
|
name, visibility, modality, context.irBuiltIns.anyNType,
|
||||||
|
isInline, isExternal, isTailrec, isSuspend
|
||||||
|
).also {
|
||||||
|
descriptor.bind(it)
|
||||||
|
it.parent = parent
|
||||||
|
it.copyTypeParametersFrom(this)
|
||||||
|
|
||||||
|
it.dispatchReceiverParameter = dispatchReceiverParameter?.copyTo(it)
|
||||||
|
it.extensionReceiverParameter = extensionReceiverParameter?.copyTo(it)
|
||||||
|
|
||||||
|
valueParameters.mapTo(it.valueParameters) { p -> p.copyTo(it) }
|
||||||
|
it.addValueParameter(
|
||||||
|
SUSPEND_FUNCTION_COMPLETION_PARAMETER_NAME, context.getTopLevelClass(
|
||||||
|
context.state.languageVersionSettings.coroutinesPackageFqName().child(
|
||||||
|
Name.identifier("Continuation")
|
||||||
|
)
|
||||||
|
).createType(false, listOf(makeTypeProjection(returnType, Variance.INVARIANT)))
|
||||||
|
)
|
||||||
|
val valueParametersMapping = explicitParameters.zip(it.explicitParameters).toMap()
|
||||||
|
it.body = body?.deepCopyWithSymbols(this)
|
||||||
|
it.body?.transformChildrenVoid(object : IrElementTransformerVoid() {
|
||||||
|
override fun visitGetValue(expression: IrGetValue): IrGetValue =
|
||||||
|
valueParametersMapping[expression.symbol.owner]?.let { newParam ->
|
||||||
|
expression.run { IrGetValueImpl(startOffset, endOffset, type, newParam.symbol, origin) }
|
||||||
|
} ?: expression
|
||||||
|
|
||||||
|
override fun visitCall(expression: IrCall): IrExpression {
|
||||||
|
if (!expression.isSuspend) return super.visitCall(expression)
|
||||||
|
return super.visitCall(expression.createView(context, it))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun IrCall.createView(context: JvmBackendContext, caller: IrFunction): IrCall {
|
||||||
|
if (!isSuspend) return this
|
||||||
|
val view = (symbol.owner as IrSimpleFunction).getOrCreateSuspendFunctionViewIfNeeded(context)
|
||||||
|
if (view == symbol.owner) return this
|
||||||
|
return IrCallImpl(startOffset, endOffset, view.returnType, view.symbol).also {
|
||||||
|
it.copyTypeArgumentsFrom(this)
|
||||||
|
it.dispatchReceiver = dispatchReceiver
|
||||||
|
for (i in 0 until valueArgumentsCount) {
|
||||||
|
it.putValueArgument(i, getValueArgument(i))
|
||||||
|
}
|
||||||
|
val continuationParameter =
|
||||||
|
if (caller.isInvokeSuspendOfLambda(context) || caller.isInvokeSuspendOfContinuation(context)) caller.dispatchReceiverParameter!!
|
||||||
|
else caller.valueParameters.last()
|
||||||
|
val continuation = IrGetValueImpl(UNDEFINED_OFFSET, UNDEFINED_OFFSET, continuationParameter.symbol)
|
||||||
|
it.putValueArgument(valueArgumentsCount, continuation)
|
||||||
|
}
|
||||||
|
}
|
||||||
+6
-8
@@ -298,6 +298,7 @@ class ExpressionCodegen(
|
|||||||
visitStatementContainer(expression, data).coerce(expression.type)
|
visitStatementContainer(expression, data).coerce(expression.type)
|
||||||
|
|
||||||
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: BlockInfo): PromisedValue {
|
override fun visitFunctionAccess(expression: IrFunctionAccessExpression, data: BlockInfo): PromisedValue {
|
||||||
|
val expression = if (expression is IrCall) expression.createView(context, irFunction) else expression
|
||||||
classCodegen.context.irIntrinsics.getIntrinsic(expression.symbol)
|
classCodegen.context.irIntrinsics.getIntrinsic(expression.symbol)
|
||||||
?.invoke(expression, this, data)?.let { return it.coerce(expression.type) }
|
?.invoke(expression, this, data)?.let { return it.coerce(expression.type) }
|
||||||
|
|
||||||
@@ -333,7 +334,7 @@ class ExpressionCodegen(
|
|||||||
}
|
}
|
||||||
expression.descriptor is ConstructorDescriptor ->
|
expression.descriptor is ConstructorDescriptor ->
|
||||||
throw AssertionError("IrCall with ConstructorDescriptor: ${expression.javaClass.simpleName}")
|
throw AssertionError("IrCall with ConstructorDescriptor: ${expression.javaClass.simpleName}")
|
||||||
callee.isSuspend && !irFunction.isInvokeSuspendInContinuation() ->
|
callee.isSuspend && !irFunction.isInvokeSuspendOfContinuation(classCodegen.context) ->
|
||||||
addInlineMarker(mv, isStartNotEnd = true)
|
addInlineMarker(mv, isStartNotEnd = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -359,13 +360,13 @@ class ExpressionCodegen(
|
|||||||
expression.markLineNumber(true)
|
expression.markLineNumber(true)
|
||||||
|
|
||||||
// Do not generate redundant markers in continuation class.
|
// Do not generate redundant markers in continuation class.
|
||||||
if (callee.isSuspend && !irFunction.isInvokeSuspendInContinuation()) {
|
if (callee.isSuspend && !irFunction.isInvokeSuspendOfContinuation(classCodegen.context)) {
|
||||||
addSuspendMarker(mv, isStartNotEnd = true)
|
addSuspendMarker(mv, isStartNotEnd = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
callGenerator.genCall(callable, this, expression)
|
callGenerator.genCall(callable, this, expression)
|
||||||
|
|
||||||
if (callee.isSuspend && !irFunction.isInvokeSuspendInContinuation()) {
|
if (callee.isSuspend && !irFunction.isInvokeSuspendOfContinuation(classCodegen.context)) {
|
||||||
addSuspendMarker(mv, isStartNotEnd = false)
|
addSuspendMarker(mv, isStartNotEnd = false)
|
||||||
addInlineMarker(mv, isStartNotEnd = false)
|
addInlineMarker(mv, isStartNotEnd = false)
|
||||||
}
|
}
|
||||||
@@ -389,9 +390,6 @@ class ExpressionCodegen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrFunction.isInvokeSuspendInContinuation(): Boolean =
|
|
||||||
name.asString() == INVOKE_SUSPEND_METHOD_NAME && parentAsClass in classCodegen.context.suspendFunctionContinuations.values
|
|
||||||
|
|
||||||
override fun visitVariable(declaration: IrVariable, data: BlockInfo): PromisedValue {
|
override fun visitVariable(declaration: IrVariable, data: BlockInfo): PromisedValue {
|
||||||
val varType = typeMapper.mapType(declaration)
|
val varType = typeMapper.mapType(declaration)
|
||||||
val index = frameMap.enter(declaration.symbol, varType)
|
val index = frameMap.enter(declaration.symbol, varType)
|
||||||
@@ -532,9 +530,9 @@ class ExpressionCodegen(
|
|||||||
override fun visitReturn(expression: IrReturn, data: BlockInfo): PromisedValue {
|
override fun visitReturn(expression: IrReturn, data: BlockInfo): PromisedValue {
|
||||||
val returnTarget = expression.returnTargetSymbol.owner
|
val returnTarget = expression.returnTargetSymbol.owner
|
||||||
val owner =
|
val owner =
|
||||||
returnTarget as? IrFunction
|
(returnTarget as? IrFunction
|
||||||
?: (returnTarget as? IrReturnableBlock)?.inlineFunctionSymbol?.owner
|
?: (returnTarget as? IrReturnableBlock)?.inlineFunctionSymbol?.owner
|
||||||
?: error("Unsupported IrReturnTarget: $returnTarget")
|
?: error("Unsupported IrReturnTarget: $returnTarget")).getOrCreateSuspendFunctionViewIfNeeded(context)
|
||||||
//TODO: should be owner != irFunction
|
//TODO: should be owner != irFunction
|
||||||
val isNonLocalReturn =
|
val isNonLocalReturn =
|
||||||
methodSignatureMapper.mapFunctionName(owner) != methodSignatureMapper.mapFunctionName(irFunction)
|
methodSignatureMapper.mapFunctionName(owner) != methodSignatureMapper.mapFunctionName(irFunction)
|
||||||
|
|||||||
+15
-10
@@ -43,9 +43,10 @@ open class FunctionCodegen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun doGenerate(): JvmMethodGenericSignature {
|
private fun doGenerate(): JvmMethodGenericSignature {
|
||||||
val signature = classCodegen.methodSignatureMapper.mapSignatureWithGeneric(irFunction)
|
val functionView = irFunction.getOrCreateSuspendFunctionViewIfNeeded(context)
|
||||||
|
val signature = classCodegen.methodSignatureMapper.mapSignatureWithGeneric(functionView)
|
||||||
|
|
||||||
val flags = calculateMethodFlags(irFunction.isStatic)
|
val flags = calculateMethodFlags(functionView.isStatic)
|
||||||
var methodVisitor = createMethod(flags, signature)
|
var methodVisitor = createMethod(flags, signature)
|
||||||
|
|
||||||
val hasSyntheticFlag = flags.and(Opcodes.ACC_SYNTHETIC) != 0
|
val hasSyntheticFlag = flags.and(Opcodes.ACC_SYNTHETIC) != 0
|
||||||
@@ -81,15 +82,17 @@ open class FunctionCodegen(
|
|||||||
?: context.suspendLambdaToOriginalFunctionMap[irFunction.parent]?.symbol?.descriptor?.psiElement) as? KtElement
|
?: context.suspendLambdaToOriginalFunctionMap[irFunction.parent]?.symbol?.descriptor?.psiElement) as? KtElement
|
||||||
val continuationClassBuilder = context.continuationClassBuilders[irClass]
|
val continuationClassBuilder = context.continuationClassBuilders[irClass]
|
||||||
methodVisitor = when {
|
methodVisitor = when {
|
||||||
irFunction.isSuspend -> generateStateMachineForNamedFunction(
|
// We do not generate continuation and state-machine for synthetic accessors, in a sence, they are tail-call
|
||||||
irFunction, classCodegen, methodVisitor, flags, signature, continuationClassBuilder, element!!
|
irFunction.isSuspend && irFunction.origin != JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR ->
|
||||||
)
|
generateStateMachineForNamedFunction(
|
||||||
|
irFunction, classCodegen, methodVisitor, flags, signature, continuationClassBuilder, element!!
|
||||||
|
)
|
||||||
irFunction.isInvokeSuspendOfLambda(context) -> generateStateMachineForLambda(
|
irFunction.isInvokeSuspendOfLambda(context) -> generateStateMachineForLambda(
|
||||||
classCodegen, methodVisitor, flags, signature, element!!
|
classCodegen, methodVisitor, flags, signature, element!!
|
||||||
)
|
)
|
||||||
else -> methodVisitor
|
else -> methodVisitor
|
||||||
}
|
}
|
||||||
ExpressionCodegen(irFunction, signature, frameMap, InstructionAdapter(methodVisitor), classCodegen, isInlineLambda).generate()
|
ExpressionCodegen(functionView, signature, frameMap, InstructionAdapter(methodVisitor), classCodegen, isInlineLambda).generate()
|
||||||
methodVisitor.visitMaxs(-1, -1)
|
methodVisitor.visitMaxs(-1, -1)
|
||||||
continuationClassBuilder?.done()
|
continuationClassBuilder?.done()
|
||||||
}
|
}
|
||||||
@@ -182,15 +185,17 @@ open class FunctionCodegen(
|
|||||||
|
|
||||||
private fun createFrameMapWithReceivers(signature: JvmMethodSignature): IrFrameMap {
|
private fun createFrameMapWithReceivers(signature: JvmMethodSignature): IrFrameMap {
|
||||||
val frameMap = IrFrameMap()
|
val frameMap = IrFrameMap()
|
||||||
|
val functionView = irFunction.getOrCreateSuspendFunctionViewIfNeeded(context)
|
||||||
|
|
||||||
if (irFunction is IrConstructor) {
|
if (irFunction is IrConstructor) {
|
||||||
frameMap.enterDispatchReceiver(irFunction.constructedClass.thisReceiver!!)
|
frameMap.enterDispatchReceiver(irFunction.constructedClass.thisReceiver!!)
|
||||||
} else if (irFunction.dispatchReceiverParameter != null) {
|
} else if (functionView.dispatchReceiverParameter != null) {
|
||||||
frameMap.enterDispatchReceiver(irFunction.dispatchReceiverParameter!!)
|
frameMap.enterDispatchReceiver(functionView.dispatchReceiverParameter!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (parameter in signature.valueParameters) {
|
for (parameter in signature.valueParameters) {
|
||||||
if (parameter.kind == JvmMethodParameterKind.RECEIVER) {
|
if (parameter.kind == JvmMethodParameterKind.RECEIVER) {
|
||||||
val receiverParameter = irFunction.extensionReceiverParameter
|
val receiverParameter = functionView.extensionReceiverParameter
|
||||||
if (receiverParameter != null) {
|
if (receiverParameter != null) {
|
||||||
frameMap.enter(receiverParameter, classCodegen.typeMapper.mapType(receiverParameter))
|
frameMap.enter(receiverParameter, classCodegen.typeMapper.mapType(receiverParameter))
|
||||||
} else {
|
} else {
|
||||||
@@ -201,7 +206,7 @@ open class FunctionCodegen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (parameter in irFunction.valueParameters) {
|
for (parameter in functionView.valueParameters) {
|
||||||
frameMap.enter(parameter, classCodegen.typeMapper.mapType(parameter.type))
|
frameMap.enter(parameter, classCodegen.typeMapper.mapType(parameter.type))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-5
@@ -30,11 +30,8 @@ import org.jetbrains.kotlin.ir.expressions.IrConst
|
|||||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.util.*
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.load.java.BuiltinMethodsWithSpecialGenericSignature
|
import org.jetbrains.kotlin.load.java.*
|
||||||
import org.jetbrains.kotlin.load.java.JvmAbi
|
|
||||||
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
|
||||||
import org.jetbrains.kotlin.load.java.getJvmMethodNameIfSpecial
|
|
||||||
import org.jetbrains.kotlin.load.java.getOverriddenBuiltinReflectingJvmDescriptor
|
|
||||||
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
|
import org.jetbrains.kotlin.load.kotlin.TypeMappingMode
|
||||||
import org.jetbrains.kotlin.load.kotlin.forceSingleValueParameterBoxing
|
import org.jetbrains.kotlin.load.kotlin.forceSingleValueParameterBoxing
|
||||||
import org.jetbrains.kotlin.load.kotlin.getJvmModuleNameForDeserializedDescriptor
|
import org.jetbrains.kotlin.load.kotlin.getJvmModuleNameForDeserializedDescriptor
|
||||||
@@ -295,7 +292,7 @@ class MethodSignatureMapper(private val context: JvmBackendContext) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun mapToCallableMethod(expression: IrFunctionAccessExpression): IrCallableMethod {
|
fun mapToCallableMethod(expression: IrFunctionAccessExpression): IrCallableMethod {
|
||||||
val callee = expression.symbol.owner
|
val callee = expression.symbol.owner.getOrCreateSuspendFunctionViewIfNeeded(context)
|
||||||
val calleeParent = callee.parent
|
val calleeParent = callee.parent
|
||||||
if (calleeParent !is IrClass) {
|
if (calleeParent !is IrClass) {
|
||||||
// Non-class parent is only possible for intrinsics created in IrBuiltIns, such as dataClassArrayMemberHashCode. In that case,
|
// Non-class parent is only possible for intrinsics created in IrBuiltIns, such as dataClassArrayMemberHashCode. In that case,
|
||||||
|
|||||||
+12
-128
@@ -6,13 +6,10 @@
|
|||||||
package org.jetbrains.kotlin.backend.jvm.lower
|
package org.jetbrains.kotlin.backend.jvm.lower
|
||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedFunctionDescriptorWithContainerSource
|
|
||||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor
|
|
||||||
import org.jetbrains.kotlin.backend.common.descriptors.synthesizedName
|
import org.jetbrains.kotlin.backend.common.descriptors.synthesizedName
|
||||||
import org.jetbrains.kotlin.backend.common.ir.*
|
import org.jetbrains.kotlin.backend.common.ir.*
|
||||||
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||||
import org.jetbrains.kotlin.backend.common.lower.parents
|
import org.jetbrains.kotlin.backend.common.lower.parents
|
||||||
import org.jetbrains.kotlin.backend.common.peek
|
|
||||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||||
import org.jetbrains.kotlin.backend.common.pop
|
import org.jetbrains.kotlin.backend.common.pop
|
||||||
import org.jetbrains.kotlin.backend.common.push
|
import org.jetbrains.kotlin.backend.common.push
|
||||||
@@ -26,11 +23,9 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
|||||||
import org.jetbrains.kotlin.ir.builders.*
|
import org.jetbrains.kotlin.ir.builders.*
|
||||||
import org.jetbrains.kotlin.ir.builders.declarations.*
|
import org.jetbrains.kotlin.ir.builders.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.*
|
import org.jetbrains.kotlin.ir.declarations.*
|
||||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
|
|
||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||||
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
|
|
||||||
import org.jetbrains.kotlin.ir.types.*
|
import org.jetbrains.kotlin.ir.types.*
|
||||||
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
|
import org.jetbrains.kotlin.ir.types.impl.IrSimpleTypeImpl
|
||||||
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
|
import org.jetbrains.kotlin.ir.types.impl.makeTypeProjection
|
||||||
@@ -38,23 +33,19 @@ import org.jetbrains.kotlin.ir.util.*
|
|||||||
import org.jetbrains.kotlin.ir.visitors.*
|
import org.jetbrains.kotlin.ir.visitors.*
|
||||||
import org.jetbrains.kotlin.load.java.JavaVisibilities
|
import org.jetbrains.kotlin.load.java.JavaVisibilities
|
||||||
import org.jetbrains.kotlin.name.Name
|
import org.jetbrains.kotlin.name.Name
|
||||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DescriptorWithContainerSource
|
|
||||||
import org.jetbrains.kotlin.types.Variance
|
import org.jetbrains.kotlin.types.Variance
|
||||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||||
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstance
|
||||||
|
|
||||||
// TODO: Split into several lowerings, merge with ones in JS and Native
|
|
||||||
internal val addContinuationPhase = makeIrFilePhase(
|
internal val addContinuationPhase = makeIrFilePhase(
|
||||||
::AddContinuationLowering,
|
::AddContinuationLowering,
|
||||||
"AddContinuation",
|
"AddContinuation",
|
||||||
"Add continuation classes and continuation parameter to suspend functions and suspend calls"
|
"Add continuation classes to suspend functions and transform suspend lambdas into continuations"
|
||||||
)
|
)
|
||||||
|
|
||||||
private object CONTINUATION_CLASS : IrDeclarationOriginImpl("CONTINUATION_CLASS")
|
private object CONTINUATION_CLASS : IrDeclarationOriginImpl("CONTINUATION_CLASS")
|
||||||
|
|
||||||
private class AddContinuationLowering(private val context: JvmBackendContext) : FileLoweringPass {
|
private class AddContinuationLowering(private val context: JvmBackendContext) : FileLoweringPass {
|
||||||
private val transformedSuspendFunctionsCache = mutableMapOf<IrSimpleFunction, IrSimpleFunction>()
|
|
||||||
|
|
||||||
private val continuation by lazy {
|
private val continuation by lazy {
|
||||||
context.getTopLevelClass(context.state.languageVersionSettings.coroutinesPackageFqName().child(Name.identifier("Continuation")))
|
context.getTopLevelClass(context.state.languageVersionSettings.coroutinesPackageFqName().child(Name.identifier("Continuation")))
|
||||||
}
|
}
|
||||||
@@ -67,7 +58,7 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
|
|||||||
|
|
||||||
override fun lower(irFile: IrFile) {
|
override fun lower(irFile: IrFile) {
|
||||||
val suspendLambdas = markSuspendLambdas(irFile)
|
val suspendLambdas = markSuspendLambdas(irFile)
|
||||||
val suspendFunctions = addContinuationParameter(irFile, suspendLambdas.map { it.function }.toSet())
|
val suspendFunctions = markSuspendFunctions(irFile, suspendLambdas.map { it.function }.toSet())
|
||||||
for (lambda in suspendLambdas) {
|
for (lambda in suspendLambdas) {
|
||||||
generateContinuationClassForLambda(lambda)
|
generateContinuationClassForLambda(lambda)
|
||||||
}
|
}
|
||||||
@@ -385,49 +376,32 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
|
|||||||
it.dispatchReceiver = capturedThisField?.let { irField ->
|
it.dispatchReceiver = capturedThisField?.let { irField ->
|
||||||
irGetField(irGet(function.dispatchReceiverParameter!!), irField)
|
irGetField(irGet(function.dispatchReceiverParameter!!), irField)
|
||||||
}
|
}
|
||||||
for (i in irFunction.valueParameters.dropLast(1).indices) {
|
for (i in irFunction.valueParameters.indices) {
|
||||||
// TODO: also support primitives
|
// TODO: also support primitives
|
||||||
it.putValueArgument(i, irNull())
|
it.putValueArgument(i, irNull())
|
||||||
}
|
}
|
||||||
it.putValueArgument(irFunction.valueParameters.size - 1, irGet(function.dispatchReceiverParameter!!))
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addContinuationParameter(irFile: IrFile, suspendLambdas: Set<IrFunction>): Set<IrFunction> {
|
private fun markSuspendFunctions(irFile: IrFile, suspendLambdas: Set<IrFunction>): Set<IrFunction> {
|
||||||
val views = hashSetOf<IrFunction>()
|
val result = hashSetOf<IrFunction>()
|
||||||
|
|
||||||
// Collect all suspend functions
|
irFile.acceptChildrenVoid(object : IrElementVisitorVoid {
|
||||||
irFile.acceptVoid(object : IrElementVisitorVoid {
|
|
||||||
override fun visitElement(element: IrElement) {
|
override fun visitElement(element: IrElement) {
|
||||||
element.acceptChildrenVoid(this)
|
element.acceptChildrenVoid(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun visitClass(declaration: IrClass) {
|
override fun visitFunction(declaration: IrFunction) {
|
||||||
declaration.acceptChildrenVoid(this)
|
super.visitFunction(declaration)
|
||||||
declaration.transformDeclarationsFlat { element ->
|
if (declaration.isSuspend && declaration !in suspendLambdas) {
|
||||||
if (element is IrSimpleFunction && element.isSuspend && element !in suspendLambdas) {
|
result.add(declaration)
|
||||||
listOf(element.getOrCreateView().also { views.add(it) })
|
|
||||||
} else null
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
// fix return targets
|
|
||||||
for (view in views) {
|
return result
|
||||||
view.transformChildrenVoid(object : IrElementTransformerVoid() {
|
|
||||||
override fun visitReturn(expression: IrReturn): IrExpression {
|
|
||||||
val owner = expression.returnTargetSymbol.owner as? IrSimpleFunction
|
|
||||||
?: return super.visitReturn(expression)
|
|
||||||
val ownerView = owner.getOrCreateView()
|
|
||||||
if (ownerView == owner) return super.visitReturn(expression)
|
|
||||||
val result =
|
|
||||||
IrReturnImpl(expression.startOffset, expression.endOffset, ownerView.returnType, ownerView.symbol, expression.value)
|
|
||||||
return super.visitReturn(result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return views
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun markSuspendLambdas(irElement: IrElement): List<SuspendLambdaInfo> {
|
private fun markSuspendLambdas(irElement: IrElement): List<SuspendLambdaInfo> {
|
||||||
@@ -466,99 +440,9 @@ private class AddContinuationLowering(private val context: JvmBackendContext) :
|
|||||||
functionStack.pop()
|
functionStack.pop()
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getContinuationFromCaller(): IrGetValue? {
|
|
||||||
val caller = functionStack.peek()!!
|
|
||||||
val continuationParameter =
|
|
||||||
if (caller.isSuspend) caller.valueParameters.last().symbol
|
|
||||||
else if (caller.name.asString() == INVOKE_SUSPEND_METHOD_NAME && caller.parent in context.suspendLambdaToOriginalFunctionMap)
|
|
||||||
caller.dispatchReceiverParameter!!.symbol
|
|
||||||
else return null
|
|
||||||
return IrGetValueImpl(
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
UNDEFINED_OFFSET,
|
|
||||||
continuationParameter
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitCall(expression: IrCall): IrExpression {
|
|
||||||
if (!expression.isSuspend) return super.visitCall(expression)
|
|
||||||
val view = (expression.symbol.owner as IrSimpleFunction).getOrCreateView()
|
|
||||||
val res = IrCallImpl(expression.startOffset, expression.endOffset, view.returnType, view.symbol).apply {
|
|
||||||
copyTypeArgumentsFrom(expression)
|
|
||||||
dispatchReceiver = expression.dispatchReceiver
|
|
||||||
for (i in 0 until expression.valueArgumentsCount) {
|
|
||||||
putValueArgument(i, expression.getValueArgument(i))
|
|
||||||
}
|
|
||||||
val continuation = getContinuationFromCaller() ?: error(
|
|
||||||
"Cannot get continuation from context for a suspend call.\n" +
|
|
||||||
"Caller: ${ir2string(functionStack.peek())}\n" +
|
|
||||||
"Callee: ${ir2string(expression)}"
|
|
||||||
)
|
|
||||||
putValueArgument(expression.valueArgumentsCount, continuation)
|
|
||||||
}
|
|
||||||
return super.visitCall(res)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
|
|
||||||
if (!expression.isSuspend) return super.visitFunctionReference(expression)
|
|
||||||
val view = (expression.symbol.owner as IrSimpleFunction).getOrCreateView()
|
|
||||||
val res = IrFunctionReferenceImpl(
|
|
||||||
expression.startOffset,
|
|
||||||
expression.endOffset,
|
|
||||||
expression.type,
|
|
||||||
view.symbol,
|
|
||||||
view.descriptor,
|
|
||||||
expression.typeArgumentsCount,
|
|
||||||
expression.origin
|
|
||||||
).apply {
|
|
||||||
copyTypeArgumentsFrom(expression)
|
|
||||||
dispatchReceiver = expression.dispatchReceiver
|
|
||||||
for (i in 0 until expression.valueArgumentsCount) {
|
|
||||||
putValueArgument(i, expression.getValueArgument(i))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.visitFunctionReference(res)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun IrSimpleFunction.getOrCreateView(): IrSimpleFunction =
|
|
||||||
transformedSuspendFunctionsCache.getOrPut(this) {
|
|
||||||
// Copy source element, so we can check for suspend calls in monitor during state-machine generation
|
|
||||||
val originalDescriptor = this.descriptor
|
|
||||||
val descriptor =
|
|
||||||
if (originalDescriptor is DescriptorWithContainerSource && originalDescriptor.containerSource != null)
|
|
||||||
WrappedFunctionDescriptorWithContainerSource(originalDescriptor.containerSource!!)
|
|
||||||
else
|
|
||||||
WrappedSimpleFunctionDescriptor(sourceElement = originalDescriptor.source)
|
|
||||||
IrFunctionImpl(
|
|
||||||
startOffset, endOffset, origin, IrSimpleFunctionSymbolImpl(descriptor),
|
|
||||||
name, visibility, modality, context.irBuiltIns.anyType,
|
|
||||||
isInline, isExternal, isTailrec, isSuspend
|
|
||||||
).also {
|
|
||||||
descriptor.bind(it)
|
|
||||||
it.parent = parent
|
|
||||||
it.copyTypeParametersFrom(this)
|
|
||||||
|
|
||||||
it.dispatchReceiverParameter = dispatchReceiverParameter?.copyTo(it)
|
|
||||||
it.extensionReceiverParameter = extensionReceiverParameter?.copyTo(it)
|
|
||||||
|
|
||||||
valueParameters.mapTo(it.valueParameters) { p -> p.copyTo(it) }
|
|
||||||
it.addCompletionValueParameter()
|
|
||||||
|
|
||||||
// Fix links to parameters
|
|
||||||
val valueParametersMapping = explicitParameters.zip(it.explicitParameters).toMap()
|
|
||||||
it.body = body?.deepCopyWithSymbols(this)
|
|
||||||
it.body?.transformChildrenVoid(object : IrElementTransformerVoid() {
|
|
||||||
override fun visitGetValue(expression: IrGetValue) =
|
|
||||||
valueParametersMapping[expression.symbol.owner]?.let { newParam ->
|
|
||||||
expression.run { IrGetValueImpl(startOffset, endOffset, type, newParam.symbol, origin) }
|
|
||||||
} ?: expression
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class SuspendLambdaInfo(val function: IrFunction, val arity: Int, val reference: IrFunctionReference) {
|
private class SuspendLambdaInfo(val function: IrFunction, val arity: Int, val reference: IrFunctionReference) {
|
||||||
lateinit var constructor: IrConstructor
|
lateinit var constructor: IrConstructor
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -155,7 +155,7 @@ private class SyntheticAccessorLowering(val context: JvmBackendContext) : IrElem
|
|||||||
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR
|
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR
|
||||||
name = source.accessorName()
|
name = source.accessorName()
|
||||||
visibility = Visibilities.PUBLIC
|
visibility = Visibilities.PUBLIC
|
||||||
isSuspend = false // do not generate state-machine for synthetic accessors
|
isSuspend = this@makeSimpleFunctionAccessor.isSuspend // synthetic accessors of suspend functions are handled in codegen
|
||||||
}.also { accessor ->
|
}.also { accessor ->
|
||||||
// Find the right container to insert the accessor. Simply put, when we call a function on a class A,
|
// Find the right container to insert the accessor. Simply put, when we call a function on a class A,
|
||||||
// we also need to put its accessor into A. However, due to the way that calls are implemented in the
|
// we also need to put its accessor into A. However, due to the way that calls are implemented in the
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// COMMON_COROUTINES_TEST
|
// COMMON_COROUTINES_TEST
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// COMMON_COROUTINES_TEST
|
// COMMON_COROUTINES_TEST
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// KJS_WITH_FULL_RUNTIME
|
// KJS_WITH_FULL_RUNTIME
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// COMMON_COROUTINES_TEST
|
// COMMON_COROUTINES_TEST
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// COMMON_COROUTINES_TEST
|
// COMMON_COROUTINES_TEST
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// COMMON_COROUTINES_TEST
|
// COMMON_COROUTINES_TEST
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// COMMON_COROUTINES_TEST
|
// COMMON_COROUTINES_TEST
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// COMMON_COROUTINES_TEST
|
// COMMON_COROUTINES_TEST
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
// TARGET_BACKEND: JVM
|
// TARGET_BACKEND: JVM
|
||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
|
|
||||||
|
|||||||
-1
@@ -1,4 +1,3 @@
|
|||||||
// IGNORE_BACKEND: JVM_IR
|
|
||||||
// WITH_RUNTIME
|
// WITH_RUNTIME
|
||||||
// WITH_COROUTINES
|
// WITH_COROUTINES
|
||||||
// COMMON_COROUTINES_TEST
|
// COMMON_COROUTINES_TEST
|
||||||
|
|||||||
Reference in New Issue
Block a user