[K/JS] Compile Kotlin coroutines as JS generator ^KT-63038 Fixed

This commit is contained in:
Artem Kobzar
2023-12-18 17:13:07 +00:00
committed by Space Team
parent 4d07fdf97e
commit 2530cba82a
73 changed files with 2240 additions and 144 deletions
@@ -181,6 +181,8 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
val jsCoroutineContext
get() = context.ir.symbols.coroutineContextGetter
val jsYieldFunctionSymbol = getInternalFunction("jsYield")
val jsGetContinuation = getInternalFunction("getContinuation")
val jsInvokeSuspendSuperType =
getInternalWithoutPackage("kotlin.coroutines.intrinsics.invokeSuspendSuperType")
@@ -189,6 +191,25 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
val jsInvokeSuspendSuperTypeWithReceiverAndParam =
getInternalWithoutPackage("kotlin.coroutines.intrinsics.invokeSuspendSuperTypeWithReceiverAndParam")
val createCoroutineUnintercepted =
getManyInternalWithoutPackage("kotlin.coroutines.intrinsics.createCoroutineUnintercepted")
val startCoroutineUninterceptedOrReturn =
getManyInternalWithoutPackage("kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturn")
val createCoroutineUninterceptedGeneratorVersion =
getManyInternalWithoutPackage("kotlin.coroutines.intrinsics.createCoroutineUninterceptedGeneratorVersion")
val startCoroutineUninterceptedOrReturnGeneratorVersion =
getManyInternalWithoutPackage("kotlin.coroutines.intrinsics.startCoroutineUninterceptedOrReturnGeneratorVersion")
val startCoroutineUninterceptedOrReturnGeneratorVersion1 by context.lazy2 {
startCoroutineUninterceptedOrReturnGeneratorVersion.single { it.owner.valueParameters.size == 1 }
}
val startCoroutineUninterceptedOrReturnGeneratorVersion2 by context.lazy2 {
startCoroutineUninterceptedOrReturnGeneratorVersion.single { it.owner.valueParameters.size == 2 }
}
val suspendOrReturnFunctionSymbol = getInternalWithoutPackage("kotlin.coroutines.intrinsics.suspendOrReturn")
val jsNumberRangeToNumber = getInternalFunction("numberRangeToNumber")
val jsNumberRangeToLong = getInternalFunction("numberRangeToLong")
@@ -328,6 +349,7 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("DoNotIntrinsify"))
val jsFunAnnotationSymbol = context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("JsFun"))
val jsNameAnnotationSymbol = context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("JsName"))
val jsGeneratorAnnotationSymbol = context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("JsGenerator"))
val jsExportAnnotationSymbol by lazy(LazyThreadSafetyMode.NONE) {
context.symbolTable.descriptorExtension.referenceClass(context.getJsInternalClass("JsExport"))
@@ -392,6 +414,9 @@ class JsIntrinsics(private val irBuiltIns: IrBuiltIns, val context: JsIrBackendC
private fun getInternalWithoutPackage(name: String) =
context.symbolTable.descriptorExtension.referenceSimpleFunction(context.getFunctions(FqName(name)).single())
private fun getManyInternalWithoutPackage(name: String) =
context.getFunctions(FqName(name)).mapTo(mutableSetOf()) { context.symbolTable.descriptorExtension.referenceSimpleFunction(it) }
private fun getInternalWithoutPackageOrNull(name: String): IrSimpleFunctionSymbol? {
val descriptor = context.getFunctions(FqName(name)).singleOrNull() ?: return null
return context.symbolTable.descriptorExtension.referenceSimpleFunction(descriptor)
@@ -14,15 +14,12 @@ import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesInInlineFunc
import org.jetbrains.kotlin.backend.common.lower.inline.LocalClassesInInlineLambdasLowering
import org.jetbrains.kotlin.backend.common.lower.loops.ForLoopsLowering
import org.jetbrains.kotlin.backend.common.phaser.*
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.backend.js.lower.calls.CallsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.cleanup.CleanupLowering
import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.AddContinuationToFunctionCallsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendArityStoreLowering
import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.JsSuspendFunctionsLowering
import org.jetbrains.kotlin.ir.backend.js.lower.coroutines.*
import org.jetbrains.kotlin.ir.backend.js.lower.inline.*
import org.jetbrains.kotlin.ir.backend.js.transformers.irToJs.JsGenerationGranularity
import org.jetbrains.kotlin.ir.backend.js.utils.compileSuspendAsJsGenerator
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.interpreter.IrInterpreterConfiguration
import org.jetbrains.kotlin.platform.js.JsPlatforms
@@ -185,11 +182,18 @@ private val wrapInlineDeclarationsWithReifiedTypeParametersLowering = makeIrModu
description = "Wrap inline declarations with reified type parameters"
)
private val replaceSuspendIntrinsicLowering = makeIrModulePhase(
::ReplaceSuspendIntrinsicLowering,
name = "ReplaceSuspendIntrinsicLowering",
description = "Replace suspend intrinsic for generator based coroutines"
)
private val saveInlineFunctionsBeforeInlining = makeIrModulePhase(
::SaveInlineFunctionsBeforeInlining,
name = "SaveInlineFunctionsBeforeInlining",
description = "Save inline function before inlining",
prerequisite = setOf(
replaceSuspendIntrinsicLowering,
expectDeclarationsRemovingPhase, sharedVariablesLoweringPhase,
localClassesInInlineLambdasPhase, localClassesExtractionFromInlineFunctionsPhase,
syntheticAccessorLoweringPhase, wrapInlineDeclarationsWithReifiedTypeParametersLowering
@@ -426,10 +430,16 @@ private val innerClassConstructorCallsLoweringPhase = makeIrModulePhase<JsIrBack
description = "Replace inner class constructor invocation"
)
private val suspendFunctionsLoweringPhase = makeIrModulePhase(
::JsSuspendFunctionsLowering,
private val suspendFunctionsLoweringPhase = makeIrModulePhase<JsIrBackendContext>(
{ context ->
if (context.compileSuspendAsJsGenerator) {
JsSuspendFunctionWithGeneratorsLowering(context)
} else {
JsSuspendFunctionsLowering(context)
}
},
name = "SuspendFunctionsLowering",
description = "Transform suspend functions into CoroutineImpl instance and build state machine"
description = "Transform suspend functions into CoroutineImpl instance and build state machine or into GeneratorCoroutineImpl and ES2015 generators"
)
private val addContinuationToNonLocalSuspendFunctionsLoweringPhase = makeIrModulePhase(
@@ -789,6 +799,7 @@ val loweringList = listOf<SimpleNamedCompilerPhase<JsIrBackendContext, IrModuleF
localClassesExtractionFromInlineFunctionsPhase,
syntheticAccessorLoweringPhase,
wrapInlineDeclarationsWithReifiedTypeParametersLowering,
replaceSuspendIntrinsicLowering,
saveInlineFunctionsBeforeInlining,
functionInliningPhase,
constEvaluationPhase,
@@ -27,7 +27,6 @@ internal class JsUsefulDeclarationProcessor(
) : UsefulDeclarationProcessor(printReachabilityInfo, removeUnusedAssociatedObjects) {
private val equalsMethod = getMethodOfAny("equals")
private val hashCodeMethod = getMethodOfAny("hashCode")
private val isEsModules = context.configuration[JSConfigurationKeys.MODULE_KIND] == ModuleKind.ES
override val bodyVisitor: BodyVisitorBase = object : BodyVisitorBase() {
override fun visitCall(expression: IrCall, data: IrDeclaration) {
@@ -163,6 +163,7 @@ internal class ICHasher {
JSConfigurationKeys.PROPERTY_LAZY_INITIALIZATION,
JSConfigurationKeys.GENERATE_INLINE_ANONYMOUS_FUNCTIONS,
JSConfigurationKeys.GENERATE_STRICT_IMPLICIT_EXPORT,
JSConfigurationKeys.COMPILE_SUSPEND_AS_JS_GENERATOR,
JSConfigurationKeys.OPTIMIZE_GENERATED_JS,
)
hashCalculator.updateConfigKeys(config, booleanKeys) { value: Boolean ->
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.ir.backend.js.lower
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.compilationException
import org.jetbrains.kotlin.backend.common.ir.moveBodyTo
import org.jetbrains.kotlin.backend.common.lower.LoweredStatementOrigins
@@ -16,6 +15,8 @@ import org.jetbrains.kotlin.backend.common.runOnFilePostfix
import org.jetbrains.kotlin.builtins.StandardNames
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.compileSuspendAsJsGenerator
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.*
@@ -30,7 +31,7 @@ import org.jetbrains.kotlin.name.SpecialNames
import org.jetbrains.kotlin.utils.memoryOptimizedMapIndexed
import org.jetbrains.kotlin.utils.memoryOptimizedPlus
class CallableReferenceLowering(private val context: CommonBackendContext) : BodyLoweringPass {
class CallableReferenceLowering(private val context: JsCommonBackendContext) : BodyLoweringPass {
override fun lower(irFile: IrFile) {
runOnFilePostfix(irFile, withLocalDeclarations = true)
@@ -115,9 +116,9 @@ class CallableReferenceLowering(private val context: CommonBackendContext) : Bod
private val isLambda: Boolean get() = reflectionTarget == null
private val isSuspendLambda = isLambda && function.isSuspend
private val shouldBeCoroutineImpl = isLambda && function.isSuspend && !context.compileSuspendAsJsGenerator
private val superClass = if (isSuspendLambda) context.ir.symbols.coroutineImpl.owner.defaultType else context.irBuiltIns.anyType
private val superClass = if (shouldBeCoroutineImpl) context.ir.symbols.coroutineImpl.owner.defaultType else context.irBuiltIns.anyType
private var boundReceiverField: IrField? = null
private val referenceType = reference.type as IrSimpleType
@@ -225,7 +226,7 @@ class CallableReferenceLowering(private val context: CommonBackendContext) : Bod
var continuation: IrValueParameter? = null
if (isSuspendLambda) {
if (shouldBeCoroutineImpl) {
val superContinuation = superConstructor.valueParameters.single()
continuation = addValueParameter {
name = superContinuation.name
@@ -117,7 +117,7 @@ class JsDefaultArgumentStubGenerator(context: JsIrBackendContext) :
context.additionalExportedDeclarations.add(defaultFunStub)
if (!originalFun.hasAnnotation(JsAnnotations.jsNameFqn)) {
annotations = annotations memoryOptimizedPlus originalFun.generateJsNameAnnotationCall()
originalFun.annotations = originalFun.annotations memoryOptimizedPlus originalFun.generateJsNameAnnotationCall()
}
}
}
@@ -130,7 +130,7 @@ class JsDefaultArgumentStubGenerator(context: JsIrBackendContext) :
}
originalFun.annotations = irrelevantAnnotations
defaultFunStub.annotations = defaultFunStub.annotations memoryOptimizedPlus exportAnnotations
defaultFunStub.annotations = exportAnnotations
originalFun.origin = JsLoweredDeclarationOrigin.JS_SHADOWED_EXPORT
return listOf(originalFun, defaultFunStub)
@@ -10,11 +10,13 @@ import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.JsMainFunctionDetector
import org.jetbrains.kotlin.ir.backend.js.utils.compileSuspendAsJsGenerator
import org.jetbrains.kotlin.ir.backend.js.utils.isLoweredSuspendFunction
import org.jetbrains.kotlin.ir.backend.js.utils.isStringArrayParameter
import org.jetbrains.kotlin.ir.declarations.IrFile
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrRawFunctionReferenceImpl
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.util.toIrConst
import org.jetbrains.kotlin.name.Name
@@ -59,19 +61,35 @@ class MainFunctionCallWrapperLowering(private val context: JsIrBackendContext) :
).also {
it.parent = parent
it.body = context.irFactory.createBlockBody(UNDEFINED_OFFSET, UNDEFINED_OFFSET).apply {
statements.add(JsIrBuilder.buildCall(originalFunctionSymbol).apply {
generateMainArguments().forEachIndexed(this::putValueArgument)
})
val shouldCallMainFunctionAsCoroutine = isLoweredSuspendFunction(context) && context.compileSuspendAsJsGenerator
val functionSymbolToCall = when {
!shouldCallMainFunctionAsCoroutine -> originalFunctionSymbol
hasStringArrayParameter() -> context.intrinsics.startCoroutineUninterceptedOrReturnGeneratorVersion2
else -> context.intrinsics.startCoroutineUninterceptedOrReturnGeneratorVersion1
}
val mainFunctionCall = JsIrBuilder.buildCall(functionSymbolToCall).apply {
if (shouldCallMainFunctionAsCoroutine) {
extensionReceiver = IrRawFunctionReferenceImpl(
UNDEFINED_OFFSET,
UNDEFINED_OFFSET,
context.irBuiltIns.anyType,
originalFunctionSymbol
)
}
generateMainArguments().forEachIndexed { index, arg ->
putValueArgument(index, arg)
}
}
statements.add(mainFunctionCall)
}
}
}
private fun IrSimpleFunction.generateMainArguments(): List<IrExpression> {
val generateArgv = valueParameters.firstOrNull()?.isStringArrayParameter() ?: false
val generateContinuation = isLoweredSuspendFunction(context)
return listOfNotNull(
runIf(generateArgv) {
runIf(hasStringArrayParameter()) {
context.platformArgumentsProviderJsExpression?.let {
JsIrBuilder.buildCall(context.intrinsics.jsCode).apply {
putValueArgument(0, it.toIrConst(context.irBuiltIns.stringType))
@@ -82,9 +100,13 @@ class MainFunctionCallWrapperLowering(private val context: JsIrBackendContext) :
context.irBuiltIns.stringType
)
},
runIf(generateContinuation) {
runIf(isLoweredSuspendFunction(context)) {
JsIrBuilder.buildCall(context.coroutineEmptyContinuation.owner.getter!!.symbol)
}
)
}
private fun IrSimpleFunction.hasStringArrayParameter(): Boolean {
return valueParameters.firstOrNull()?.isStringArrayParameter() == true
}
}
@@ -60,6 +60,7 @@ class PrivateMembersLowering(val context: JsIrBackendContext) : DeclarationTrans
visibility = newVisibility
}.also {
it.parent = function.parent
it.annotations = function.annotations
}
staticFunction.typeParameters =
@@ -13,6 +13,7 @@ import org.jetbrains.kotlin.ir.backend.js.JsCommonBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceField
import org.jetbrains.kotlin.ir.backend.js.utils.isObjectInstanceGetter
import org.jetbrains.kotlin.ir.backend.js.utils.primaryConstructorReplacement
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.IrExpressionBodyImpl
@@ -102,7 +103,4 @@ class PurifyObjectInstanceGettersLowering(val context: JsCommonBackendContext) :
)
}
private val IrClass.primaryConstructorReplacement: IrSimpleFunction?
get() = findDeclaration<IrSimpleFunction> { it.isEs6PrimaryConstructorReplacement }
}
@@ -6,7 +6,6 @@
package org.jetbrains.kotlin.ir.backend.js.lower.coroutines
import org.jetbrains.kotlin.backend.common.*
import org.jetbrains.kotlin.backend.common.ir.*
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.descriptors.Modality
@@ -78,71 +77,10 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
}
}
private fun getSuspendFunctionKind(function: IrSimpleFunction, body: IrBody): SuspendFunctionKind {
fun IrSimpleFunction.isSuspendLambda() =
name.asString() == "invoke" && parentClassOrNull?.let { it.origin === CallableReferenceLowering.Companion.LAMBDA_IMPL } == true
if (function.isSuspendLambda())
return SuspendFunctionKind.NEEDS_STATE_MACHINE // Suspend lambdas always need coroutine implementation.
var numberOfSuspendCalls = 0
body.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitCall(expression: IrCall) {
expression.acceptChildrenVoid(this)
if (expression.isSuspend)
++numberOfSuspendCalls
}
})
// It is important to optimize the case where there is only one suspend call and it is the last statement
// because we don't need to build a fat coroutine class in that case.
// This happens a lot in practice because of suspend functions with default arguments.
// TODO: use TailRecursionCallsCollector.
val lastCall = when (val lastStatement = (body as IrBlockBody).statements.lastOrNull()) {
is IrCall ->
// Delegation to call without return can only be performed to Unit-returning function call from Unit-returning function
if (lastStatement.type == context.irBuiltIns.unitType && function.returnType == context.irBuiltIns.unitType)
lastStatement
else
null
is IrReturn -> {
var value: IrElement = lastStatement
/*
* Check if matches this pattern:
* block/return {
* block/return {
* .. suspendCall()
* }
* }
*/
loop@ while (true) {
value = when {
value is IrBlock && value.statements.size == 1 -> value.statements.first()
value is IrReturn -> value.value
else -> break@loop
}
}
value as? IrCall
}
else -> null
}
val suspendCallAtEnd = lastCall != null && lastCall.isSuspend // Suspend call.
return when {
numberOfSuspendCalls == 0 -> SuspendFunctionKind.NO_SUSPEND_CALLS
numberOfSuspendCalls == 1
&& suspendCallAtEnd -> SuspendFunctionKind.DELEGATING(lastCall!!)
else -> SuspendFunctionKind.NEEDS_STATE_MACHINE
}
}
private fun transformSuspendFunction(function: IrSimpleFunction, body: IrBody): IrClass? {
assert(function.isSuspend)
return when (val functionKind = getSuspendFunctionKind(function, body)) {
return when (val functionKind = getSuspendFunctionKind(context, function, body)) {
is SuspendFunctionKind.NO_SUSPEND_CALLS -> {
null // No suspend function calls - just an ordinary function.
}
@@ -480,24 +418,13 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
}
}
// Suppress since it is used in native
@Suppress("MemberVisibilityCanBePrivate")
protected fun IrCall.isReturnIfSuspendedCall() =
symbol.owner.run { fqNameWhenAvailable == context.internalPackageFqn.child(Name.identifier("returnIfSuspended")) }
private sealed class SuspendFunctionKind {
object NO_SUSPEND_CALLS : SuspendFunctionKind()
class DELEGATING(val delegatingCall: IrCall) : SuspendFunctionKind()
object NEEDS_STATE_MACHINE : SuspendFunctionKind()
}
private val symbols = context.ir.symbols
private val getContinuationSymbol = symbols.getContinuation
private val continuationClassSymbol = getContinuationSymbol.owner.returnType.classifierOrFail as IrClassSymbol
private fun removeReturnIfSuspendedCallAndSimplifyDelegatingCall(irFunction: IrFunction, delegatingCall: IrCall) {
val returnValue =
if (delegatingCall.isReturnIfSuspendedCall())
if (delegatingCall.isReturnIfSuspendedCall(context))
delegatingCall.getValueArgument(0)!!
else delegatingCall
val body = irFunction.body as IrBlockBody
@@ -575,3 +502,81 @@ abstract class AbstractSuspendFunctionsLowering<C : CommonBackendContext>(val co
}
}
}
sealed class SuspendFunctionKind {
object NO_SUSPEND_CALLS : SuspendFunctionKind()
class DELEGATING(val delegatingCall: IrCall) : SuspendFunctionKind()
object NEEDS_STATE_MACHINE : SuspendFunctionKind()
}
fun getSuspendFunctionKind(
context: CommonBackendContext,
function: IrSimpleFunction,
body: IrBody,
includeSuspendLambda: Boolean = true
): SuspendFunctionKind {
fun IrSimpleFunction.isSuspendLambda() =
name.asString() == "invoke" && parentClassOrNull?.let { it.origin === CallableReferenceLowering.Companion.LAMBDA_IMPL } == true
if (function.isSuspendLambda() && includeSuspendLambda)
return SuspendFunctionKind.NEEDS_STATE_MACHINE // Suspend lambdas always need coroutine implementation.
var numberOfSuspendCalls = 0
body.acceptVoid(object : IrElementVisitorVoid {
override fun visitElement(element: IrElement) {
element.acceptChildrenVoid(this)
}
override fun visitCall(expression: IrCall) {
expression.acceptChildrenVoid(this)
if (expression.isSuspend)
++numberOfSuspendCalls
}
})
// It is important to optimize the case where there is only one suspend call and it is the last statement
// because we don't need to build a fat coroutine class in that case.
// This happens a lot in practice because of suspend functions with default arguments.
// TODO: use TailRecursionCallsCollector.
val lastCall = when (val lastStatement = (body as IrBlockBody).statements.lastOrNull()) {
is IrCall ->
// Delegation to call without return can only be performed to Unit-returning function call from Unit-returning function
if (lastStatement.type == context.irBuiltIns.unitType && function.returnType == context.irBuiltIns.unitType)
lastStatement
else
null
is IrReturn -> {
var value: IrElement = lastStatement
/*
* Check if matches this pattern:
* block/return {
* block/return {
* .. suspendCall()
* }
* }
*/
loop@ while (true) {
value = when {
value is IrBlock && value.statements.size == 1 -> value.statements.first()
value is IrReturn -> value.value
else -> break@loop
}
}
value as? IrCall
}
else -> null
}
val suspendCallAtEnd = lastCall != null && lastCall.isSuspend // Suspend call.
return when {
numberOfSuspendCalls == 0 -> SuspendFunctionKind.NO_SUSPEND_CALLS
numberOfSuspendCalls == 1
&& suspendCallAtEnd -> SuspendFunctionKind.DELEGATING(lastCall!!)
else -> SuspendFunctionKind.NEEDS_STATE_MACHINE
}
}
// Suppress since it is used in native
@Suppress("MemberVisibilityCanBePrivate")
fun IrCall.isReturnIfSuspendedCall(context: CommonBackendContext) =
symbol.owner.run { fqNameWhenAvailable == context.internalPackageFqn.child(Name.identifier("returnIfSuspended")) }
@@ -0,0 +1,156 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.coroutines
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.ir.ValueRemapper
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.descriptors.DescriptorVisibilities
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.symbols.IrValueSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.butIf
import org.jetbrains.kotlin.utils.addToStdlib.runIf
import org.jetbrains.kotlin.utils.memoryOptimizedPlus
private val SUSPEND_FUNCTION_AS_GENERATOR by IrDeclarationOriginImpl
class JsSuspendFunctionWithGeneratorsLowering(private val context: JsIrBackendContext) : DeclarationTransformer {
private val getContinuationSymbol = context.ir.symbols.getContinuation
private val jsYieldFunctionSymbol = context.intrinsics.jsYieldFunctionSymbol
private val suspendOrReturnFunctionSymbol = context.intrinsics.suspendOrReturnFunctionSymbol
private val coroutineSuspendedGetterSymbol = context.coroutineSymbols.coroutineSuspendedGetter
override fun transformFlat(declaration: IrDeclaration): List<IrDeclaration>? {
if (declaration is IrSimpleFunction && declaration.isSuspend) {
return transformSuspendFunction(declaration)
}
return null
}
private fun transformSuspendFunction(function: IrSimpleFunction): List<IrFunction>? {
val originalReturnType = function.returnType.also { function.returnType = context.irBuiltIns.anyNType }
val body = function.body ?: return null
return when (val functionKind = getSuspendFunctionKind(context, function, body, includeSuspendLambda = false)) {
is SuspendFunctionKind.NO_SUSPEND_CALLS -> null
is SuspendFunctionKind.DELEGATING -> {
removeReturnIfSuspendedCallAndSimplifyDelegatingCall(function, functionKind.delegatingCall)
null
}
is SuspendFunctionKind.NEEDS_STATE_MACHINE -> {
generateGeneratorAndItsWrapper(function, body, originalReturnType)
}
}
}
private fun IrSimpleFunction.addJsGeneratorAnnotation() {
annotations = annotations memoryOptimizedPlus JsIrBuilder.buildConstructorCall(
context.intrinsics.jsGeneratorAnnotationSymbol.owner.primaryConstructor!!.symbol
)
}
private fun generateGeneratorAndItsWrapper(
function: IrSimpleFunction,
functionBody: IrBody,
originalReturnType: IrType
): List<IrFunction> {
val generatorFunction = context.irFactory.createSimpleFunction(
function.startOffset,
function.endOffset,
SUSPEND_FUNCTION_AS_GENERATOR,
Name.special("<generator-${function.name.asString()}>"),
DescriptorVisibilities.PRIVATE,
function.isInline,
function.isExpect,
originalReturnType,
function.modality,
IrSimpleFunctionSymbolImpl(),
function.isTailrec,
function.isSuspend,
function.isOperator,
function.isInfix,
function.isExternal,
).apply {
copyParameterDeclarationsFrom(function)
parent = function.parent
annotations = function.annotations
body = functionBody.apply {
val valueSymbols = function.valueParameters.zip(valueParameters)
.plus(function.dispatchReceiverParameter to dispatchReceiverParameter)
.plus(function.extensionReceiverParameter to extensionReceiverParameter)
.mapNotNull { (old, new) -> new?.let { old?.symbol?.to(it.symbol) } }
.toMap<IrValueSymbol, IrValueSymbol>()
transformChildrenVoid(object : ValueRemapper(valueSymbols) {
override fun visitCall(expression: IrCall): IrExpression {
val call = super.visitCall(expression)
return if (call !is IrCall || !call.symbol.owner.isSuspend) {
call
} else {
context.createIrBuilder(call.symbol).run {
irBlock(resultType = call.type) {
val tmp = createTmpVariable(call, irType = context.irBuiltIns.anyNType)
val coroutineSuspended = irCall(coroutineSuspendedGetterSymbol)
val condition = irEqeqeq(irGet(tmp), coroutineSuspended)
val yield = irCall(jsYieldFunctionSymbol).apply { putValueArgument(0, irGet(tmp)) }
+irIfThen(context.irBuiltIns.unitType, condition, irSet(tmp, yield))
+irImplicitCast(irGet(tmp), call.type)
}
}
}
}
})
}
addJsGeneratorAnnotation()
}
function.body = context.createIrBuilder(function.symbol).irBlockBody {
+irReturn(
irCall(suspendOrReturnFunctionSymbol).also {
it.putValueArgument(0, irCall(generatorFunction.symbol).apply {
dispatchReceiver = function.dispatchReceiverParameter?.let(::irGet)
extensionReceiver = function.extensionReceiverParameter?.let(::irGet)
contextReceiversCount = function.contextReceiverParametersCount
function.valueParameters.forEachIndexed { i, v -> putValueArgument(i, irGet(v)) }
})
it.putValueArgument(1, irCall(getContinuationSymbol))
}
)
}
return listOf(generatorFunction, function)
}
private fun removeReturnIfSuspendedCallAndSimplifyDelegatingCall(irFunction: IrFunction, delegatingCall: IrCall) {
val returnValue = runIf(delegatingCall.isReturnIfSuspendedCall(context)) {
delegatingCall.getValueArgument(0)
} ?: delegatingCall
val body = irFunction.body as IrBlockBody
context.createIrBuilder(
irFunction.symbol,
startOffset = body.endOffset.previousOffset,
endOffset = body.endOffset.previousOffset
).run {
val statements = body.statements
val lastStatement = statements.last()
assert(lastStatement == delegatingCall || lastStatement is IrReturn) { "Unexpected statement $lastStatement" }
val tempVar = scope.createTemporaryVariable(returnValue, irType = context.irBuiltIns.anyType)
statements[statements.lastIndex] = tempVar
statements.add(irReturn(irGet(tempVar)))
}
}
}
@@ -32,8 +32,7 @@ import org.jetbrains.kotlin.utils.DFS
import org.jetbrains.kotlin.utils.addToStdlib.assertedCast
class JsSuspendFunctionsLowering(ctx: JsCommonBackendContext) : AbstractSuspendFunctionsLowering<JsCommonBackendContext>(ctx) {
val coroutineSymbols = ctx.coroutineSymbols
private val coroutineSymbols = ctx.coroutineSymbols
private val coroutineImplExceptionPropertyGetter = coroutineSymbols.coroutineImplExceptionPropertyGetter
private val coroutineImplExceptionPropertySetter = coroutineSymbols.coroutineImplExceptionPropertySetter
@@ -0,0 +1,60 @@
/*
* Copyright 2010-2023 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.ir.backend.js.lower.coroutines
import org.jetbrains.kotlin.backend.common.BodyLoweringPass
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.utils.compileSuspendAsJsGenerator
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.expressions.IrBody
import org.jetbrains.kotlin.ir.expressions.IrCall
import org.jetbrains.kotlin.ir.expressions.IrCallableReference
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
class ReplaceSuspendIntrinsicLowering(private val context: JsIrBackendContext) : BodyLoweringPass {
private val valueParamSizeToItsCreateCoroutineUnintercepted =
context.intrinsics.createCoroutineUninterceptedGeneratorVersion.groupPerValueParamSize()
private val valueParamSizeToItsStartCoroutineUninterceptedOrReturn =
context.intrinsics.startCoroutineUninterceptedOrReturnGeneratorVersion.groupPerValueParamSize()
private fun Set<IrSimpleFunctionSymbol>.groupPerValueParamSize(): Map<Int, IrSimpleFunctionSymbol> {
return associateBy { it.owner.valueParameters.size }
}
override fun lower(irBody: IrBody, container: IrDeclaration) {
if (!context.compileSuspendAsJsGenerator) return
irBody.transformChildrenVoid(object : IrElementTransformerVoid() {
override fun visitCallableReference(expression: IrCallableReference<*>): IrExpression {
if (expression.symbol !is IrSimpleFunctionSymbol) return super.visitCallableReference(expression)
@Suppress("UNCHECKED_CAST")
val reference = expression as IrCallableReference<IrSimpleFunctionSymbol>
when (val symbol = reference.symbol) {
in context.intrinsics.createCoroutineUnintercepted ->
reference.symbol = valueParamSizeToItsCreateCoroutineUnintercepted.getValue(symbol.owner.valueParameters.size)
in context.intrinsics.startCoroutineUninterceptedOrReturn ->
reference.symbol = valueParamSizeToItsStartCoroutineUninterceptedOrReturn.getValue(symbol.owner.valueParameters.size)
}
return super.visitCallableReference(reference)
}
override fun visitCall(expression: IrCall): IrExpression {
when (val symbol = expression.symbol) {
in context.intrinsics.createCoroutineUnintercepted ->
expression.symbol = valueParamSizeToItsCreateCoroutineUnintercepted.getValue(symbol.owner.valueParameters.size)
in context.intrinsics.startCoroutineUninterceptedOrReturn ->
expression.symbol = valueParamSizeToItsStartCoroutineUninterceptedOrReturn.getValue(symbol.owner.valueParameters.size)
}
return super.visitCall(expression)
}
})
}
}
@@ -8,6 +8,8 @@ package org.jetbrains.kotlin.ir.backend.js.lower.inline
import org.jetbrains.kotlin.backend.common.DeclarationTransformer
import org.jetbrains.kotlin.backend.common.lower.inline.DefaultInlineFunctionResolver
import org.jetbrains.kotlin.ir.backend.js.JsIrBackendContext
import org.jetbrains.kotlin.ir.backend.js.lazy2
import org.jetbrains.kotlin.ir.backend.js.utils.compileSuspendAsJsGenerator
import org.jetbrains.kotlin.ir.declarations.IrDeclaration
import org.jetbrains.kotlin.ir.declarations.IrFunction
import org.jetbrains.kotlin.ir.deepCopyWithVariables
@@ -143,7 +143,9 @@ class IrElementToJsStatementTransformer : BaseIrElementToJsNodeTransformer<JsSta
}
}
return expression.value.maybeOptimizeIntoSwitch(context, lastStatementTransformer).withSource(expression, context)
return expression.value
.maybeOptimizeIntoSwitch(context, lastStatementTransformer)
.withSource(expression, context)
}
override fun visitThrow(expression: IrThrow, context: JsGenerationContext): JsStatement {
@@ -88,6 +88,10 @@ class JsIntrinsicTransformers(backendContext: JsIrBackendContext) {
add(intrinsics.jsIsEs6) { _, _ -> JsBooleanLiteral(backendContext.es6mode) }
add(intrinsics.jsYieldFunctionSymbol) { call, context ->
JsYield(translateCallArguments(call, context).single())
}
add(intrinsics.jsObjectCreateSymbol) { call, context ->
val classToCreate = call.getTypeArgument(0)!!.classifierOrFail.owner as IrClass
val className = classToCreate.getClassRef(context.staticContext)
@@ -12,8 +12,7 @@ import org.jetbrains.kotlin.ir.IrFileEntry
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.lower.isBoxParameter
import org.jetbrains.kotlin.ir.backend.js.lower.isEs6ConstructorReplacement
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.backend.js.sourceMapsInfo
import org.jetbrains.kotlin.ir.backend.js.utils.*
import org.jetbrains.kotlin.ir.declarations.*
@@ -24,6 +23,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.ir.visitors.acceptVoid
import org.jetbrains.kotlin.js.backend.ast.*
import org.jetbrains.kotlin.js.backend.ast.metadata.SideEffectKind
import org.jetbrains.kotlin.js.backend.ast.metadata.isGeneratorFunction
import org.jetbrains.kotlin.js.backend.ast.metadata.sideEffects
import org.jetbrains.kotlin.js.common.isValidES5Identifier
import org.jetbrains.kotlin.js.config.SourceMapNamesPolicy
@@ -119,7 +119,13 @@ fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerat
val body = declaration.body?.accept(IrElementToJsStatementTransformer(), functionContext) as? JsBlock ?: JsBlock()
val function = JsFunction(emptyScope, body, "member function ${name ?: "annon"}")
.apply { if (declaration.isEs6ConstructorReplacement) modifiers.add(JsFunction.Modifier.STATIC) }
.apply {
if (declaration.isEs6ConstructorReplacement) modifiers.add(JsFunction.Modifier.STATIC)
if (declaration.shouldBeCompiledAsGenerator()) {
name?.isGeneratorFunction = true
modifiers.add(JsFunction.Modifier.GENERATOR)
}
}
.withSource(declaration, context, useNameOf = declaration)
function.name = name
@@ -135,6 +141,9 @@ fun translateFunction(declaration: IrFunction, name: JsName?, context: JsGenerat
return function
}
private fun IrFunction.shouldBeCompiledAsGenerator(): Boolean =
hasAnnotation(JsAnnotations.jsGeneratorFqn)
private fun isFunctionTypeInvoke(receiver: JsExpression?, call: IrCall): Boolean {
if (receiver == null || receiver is JsThisRef) return false
val simpleFunction = call.symbol.owner
@@ -237,6 +246,11 @@ fun translateCall(
else -> jsElementAccess(symbolName.ident, jsDispatchReceiver)
}
if (symbolName.isGeneratorFunction) {
(ref.commentsBeforeNode ?: mutableListOf<JsComment>().also { ref.commentsBeforeNode = it })
.add(JsMultiLineComment("#__NOINLINE__"))
}
return if (isExternalVararg) {
// TODO: Don't use `Function.prototype.apply` when number of arguments is known at compile time (e.g. there are no spread operators)
@@ -667,4 +681,4 @@ private fun IrClass?.canUseSuperRef(context: JsGenerationContext, superClass: Ir
context.staticContext.backendContext.es6mode &&
!superClass.isInterface &&
!isInner && !isLocal && !currentFunction.isEs6ConstructorReplacement && currentFunction.parentClassOrNull?.superClass?.symbol != context.staticContext.backendContext.coroutineSymbols.coroutineImpl
}
}
@@ -29,6 +29,7 @@ object JsAnnotations {
val jsNativeInvoke = FqName("kotlin.js.nativeInvoke")
val jsFunFqn = FqName("kotlin.js.JsFun")
val JsPolyfillFqn = FqName("kotlin.js.JsPolyfill")
val jsGeneratorFqn = FqName("kotlin.js.JsGenerator")
}
@Suppress("UNCHECKED_CAST")
@@ -15,10 +15,7 @@ import org.jetbrains.kotlin.ir.backend.js.JsLoweredDeclarationOrigin
import org.jetbrains.kotlin.ir.backend.js.JsStatementOrigins
import org.jetbrains.kotlin.ir.backend.js.export.isExported
import org.jetbrains.kotlin.ir.backend.js.ir.JsIrBuilder
import org.jetbrains.kotlin.ir.backend.js.lower.isBoxParameter
import org.jetbrains.kotlin.ir.backend.js.lower.isEs6ConstructorReplacement
import org.jetbrains.kotlin.ir.backend.js.lower.isSyntheticConstructorForExport
import org.jetbrains.kotlin.ir.backend.js.lower.isSyntheticPrimaryConstructor
import org.jetbrains.kotlin.ir.backend.js.lower.*
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
@@ -135,4 +132,7 @@ fun JsIrBackendContext.findDefaultConstructorFor(irClass: IrClass): IrFunction?
return mapping.classToItsDefaultConstructor[irClass]?.let {
mapping.secondaryConstructorToFactory[it] ?: it
}
}
}
val IrClass.primaryConstructorReplacement: IrSimpleFunction?
get() = findDeclaration<IrSimpleFunction> { it.isEs6PrimaryConstructorReplacement }
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.ir.util.isEffectivelyExternal
import org.jetbrains.kotlin.ir.util.isMethodOfAny
import org.jetbrains.kotlin.ir.util.isTopLevel
import org.jetbrains.kotlin.ir.util.isTopLevelDeclaration
import org.jetbrains.kotlin.js.config.JSConfigurationKeys
import org.jetbrains.kotlin.util.OperatorNameConventions
fun TODO(element: IrElement): Nothing = TODO(element::class.java.simpleName + " is not supported yet here")
@@ -110,6 +111,9 @@ fun JsCommonBackendContext.findUnitGetInstanceFunction(): IrSimpleFunction =
fun JsCommonBackendContext.findUnitInstanceField(): IrField =
mapping.objectToInstanceField[irBuiltIns.unitClass.owner]!!
val JsCommonBackendContext.compileSuspendAsJsGenerator: Boolean
get() = configuration[JSConfigurationKeys.COMPILE_SUSPEND_AS_JS_GENERATOR] == true
fun IrDeclaration.isImportedFromModuleOnly(): Boolean {
return isTopLevel && isEffectivelyExternal() && (getJsModule() != null && !isJsNonModule() || (parent as? IrAnnotationContainer)?.getJsModule() != null)
}
@@ -118,4 +122,4 @@ fun invokeFunForLambda(call: IrCall) =
call.extensionReceiver!!
.type
.getClass()!!
.invokeFun!!
.invokeFun!!
@@ -70,4 +70,5 @@ object ExpressionIds {
const val NEW = 21
const val CLASS = 22
const val SUPER_REF = 23
const val YIELD = 24
}
@@ -390,6 +390,9 @@ private class JsIrAstDeserializer(private val source: ByteArray) {
NEW -> {
JsNew(readExpression(), readList { readExpression() })
}
YIELD -> {
JsYield(ifTrue { readExpression() })
}
else -> error("Unknown expression id: $id")
}
}
@@ -537,6 +537,11 @@ private class JsIrAstSerializer {
writeExpression(x.constructorExpression)
writeCollection(x.arguments) { writeExpression(it) }
}
override fun visitYield(x: JsYield) {
writeByte(ExpressionIds.YIELD)
ifNotNull(x.expression) { writeExpression(it) }
}
}
withComments(expression) {