[JVM IR] Support Suspend Main
This adds supports for (parameterless) suspend main entry points for
the JVM IR backend.
In case main is a suspend function, it gains a continuation during
lowering, so we simply generate a plain old `public static void
main(String[] args)`. This entry point invokes `suspend main` via
`kotlin.coroutines.jvm.internal.RunSuspendKt#runSuspend`.
This PR introduces `runSuspend` as a built-in, and generates the
following `main`, passing `args` as appropriate:
```
fun main(args: Array<String>) {
runSuspend { main(args) }
}
```
The phase ordering has been reshuffled countrary to previous
discussion on #2780, as the MainMethodGeneration pass now introduces lambdas in
the IR. Hence, it has to run before InventNamesForLocalClasses, yet
still after JvmOverloadsAnnotations.
Some dead code was discovered in AddContinuationLowering
This commit is contained in:
committed by
Ilmir Usmanov
parent
068d547375
commit
55aafb3430
@@ -32,7 +32,8 @@ interface JvmLoweredDeclarationOrigin : IrDeclarationOrigin {
|
||||
object INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD")
|
||||
object STATIC_INLINE_CLASS_REPLACEMENT : IrDeclarationOriginImpl("STATIC_INLINE_CLASS_REPLACEMENT")
|
||||
object GENERATED_ASSERTION_ENABLED_FIELD : IrDeclarationOriginImpl("GENERATED_ASSERTION_ENABLED_FIELD", isSynthetic = true)
|
||||
object GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD : IrDeclarationOriginImpl("GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD", isSynthetic = true)
|
||||
object GENERATED_EXTENDED_MAIN : IrDeclarationOriginImpl("GENERATED_EXTENDED_MAIN", isSynthetic = true)
|
||||
object SUSPEND_FUNCTION_VIEW : IrDeclarationOriginImpl("SUSPEND_FUNCTION_VIEW")
|
||||
object SUSPEND_IMPL_STATIC_FUNCTION : IrDeclarationOriginImpl("SUSPEND_IMPL_STATIC_FUNCTION", isSynthetic = true)
|
||||
object INTERFACE_COMPANION_PRIVATE_INSTANCE : IrDeclarationOriginImpl("INTERFACE_COMPANION_PRIVATE_INSTANCE", isSynthetic = true)
|
||||
object POLYMORPHIC_SIGNATURE_INSTANTIATION : IrDeclarationOriginImpl("POLYMORPHIC_SIGNATURE_INSTANTIATION", isSynthetic = true)
|
||||
|
||||
@@ -260,6 +260,10 @@ private val jvmFilePhases =
|
||||
typeAliasAnnotationMethodsPhase then
|
||||
stripTypeAliasDeclarationsPhase then
|
||||
provisionalFunctionExpressionPhase then
|
||||
|
||||
jvmOverloadsAnnotationPhase then
|
||||
mainMethodGenerationPhase then
|
||||
|
||||
inventNamesForLocalClassesPhase then
|
||||
kCallableNamePropertyPhase then
|
||||
annotationPhase then
|
||||
@@ -298,7 +302,6 @@ private val jvmFilePhases =
|
||||
localDeclarationsPhase then
|
||||
jvmLocalClassExtractionPhase then
|
||||
|
||||
jvmOverloadsAnnotationPhase then
|
||||
jvmDefaultConstructorPhase then
|
||||
|
||||
forLoopsPhase then
|
||||
@@ -347,7 +350,6 @@ private val jvmFilePhases =
|
||||
|
||||
checkLocalNamesWithOldBackendPhase then
|
||||
|
||||
mainMethodGenerationPhase then
|
||||
renameFieldsPhase then
|
||||
fakeInliningLocalVariablesLowering then
|
||||
|
||||
|
||||
@@ -19,10 +19,9 @@ import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.*
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
@@ -607,6 +606,16 @@ class JvmSymbols(
|
||||
|
||||
val enumValueOfFunction: IrSimpleFunctionSymbol =
|
||||
javaLangEnum.functionByName("valueOf")
|
||||
|
||||
val runSuspendFunction: IrSimpleFunctionSymbol =
|
||||
buildFun {
|
||||
name = Name.identifier("runSuspend")
|
||||
origin = IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB
|
||||
}.apply {
|
||||
parent = kotlinCoroutinesJvmInternalPackage
|
||||
returnType = irBuiltIns.unitType
|
||||
addValueParameter("block", getJvmSuspendFunctionClass(0).typeWith(continuationClass.typeWith(irBuiltIns.unitType), irBuiltIns.anyNType))
|
||||
}.symbol
|
||||
}
|
||||
|
||||
private fun IrClassSymbol.functionByName(name: String): IrSimpleFunctionSymbol =
|
||||
|
||||
+2
-1
@@ -104,7 +104,8 @@ class IrIntrinsicMethods(val irBuiltIns: IrBuiltIns, val symbols: JvmSymbols) {
|
||||
irBuiltIns.dataClassArrayMemberToStringSymbol.toKey()!! to IrDataClassArrayMemberToString,
|
||||
symbols.unsafeCoerceIntrinsic.toKey()!! to UnsafeCoerce,
|
||||
symbols.signatureStringIntrinsic.toKey()!! to SignatureString,
|
||||
symbols.reassignParameterIntrinsic.toKey()!! to ReassignParameter
|
||||
symbols.reassignParameterIntrinsic.toKey()!! to ReassignParameter,
|
||||
symbols.runSuspendFunction.toKey()!! to RunSuspend
|
||||
) +
|
||||
numberConversionMethods() +
|
||||
unaryFunForPrimitives("plus", UnaryPlus) +
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.jvm.intrinsics
|
||||
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.ir.expressions.IrFunctionAccessExpression
|
||||
import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodSignature
|
||||
|
||||
object RunSuspend : IntrinsicMethod() {
|
||||
override fun toCallable(
|
||||
expression: IrFunctionAccessExpression,
|
||||
signature: JvmMethodSignature,
|
||||
context: JvmBackendContext
|
||||
): IrIntrinsicFunction {
|
||||
return IrIntrinsicFunction.create(expression, signature, context) {
|
||||
it.invokestatic("kotlin/coroutines/jvm/internal/RunSuspendKt", "runSuspend", "(Lkotlin/jvm/functions/Function1;)V", false)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
-1
@@ -24,7 +24,9 @@ import org.jetbrains.org.objectweb.asm.Type
|
||||
val inventNamesForLocalClassesPhase = makeIrFilePhase<JvmBackendContext>(
|
||||
{ context -> InventNamesForLocalClasses(context) },
|
||||
name = "InventNamesForLocalClasses",
|
||||
description = "Invent names for local classes and anonymous objects"
|
||||
description = "Invent names for local classes and anonymous objects",
|
||||
// MainMethodGeneration introduces lambdas, needing names for their local classes.
|
||||
prerequisite = setOf(mainMethodGenerationPhase)
|
||||
)
|
||||
|
||||
class InventNamesForLocalClasses(private val context: JvmBackendContext) : FileLoweringPass {
|
||||
|
||||
+115
-26
@@ -11,19 +11,24 @@ import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
|
||||
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
|
||||
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.createJvmIrBuilder
|
||||
import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
import org.jetbrains.kotlin.config.languageVersionSettings
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addFunction
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
|
||||
import org.jetbrains.kotlin.ir.builders.irBlockBody
|
||||
import org.jetbrains.kotlin.ir.builders.irCall
|
||||
import org.jetbrains.kotlin.ir.builders.irReturn
|
||||
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
import org.jetbrains.kotlin.ir.declarations.IrValueParameter
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.IrStatementOrigin
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
import org.jetbrains.kotlin.ir.util.isFileClass
|
||||
@@ -33,37 +38,69 @@ import org.jetbrains.kotlin.types.Variance
|
||||
internal val mainMethodGenerationPhase = makeIrFilePhase(
|
||||
::MainMethodGenerationLowering,
|
||||
name = "MainMethodGeneration",
|
||||
description = "Identify parameterless main methods and generate bridge main-methods",
|
||||
description = "Generate main bridges to parameterless mains, and wrappers for suspend mains.",
|
||||
prerequisite = setOf(jvmOverloadsAnnotationPhase)
|
||||
)
|
||||
|
||||
internal class MainMethodGenerationLowering(val context: JvmBackendContext) : ClassLoweringPass {
|
||||
private class MainMethodGenerationLowering(private val context: JvmBackendContext) : ClassLoweringPass {
|
||||
|
||||
/**
|
||||
* This pass finds extended main methods and introduces a regular
|
||||
* `public static void main(String[] args)` entry point, as appropriate:
|
||||
* - invocation via [kotlin.coroutines.jvm.internal.runSuspend] suspend main methods.
|
||||
* - a simple delegating wrapper for parameterless main methods
|
||||
*
|
||||
* There are three cases that must be handled, in order of precedence:
|
||||
*
|
||||
* 1. `suspend fun main(args: Array<String>) { .. }` for which we generate
|
||||
* ```
|
||||
* fun main(args: Array<String>) {
|
||||
* runSuspend { main(args) }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 2. `suspend fun main() { .. }` for which we generate
|
||||
* ```
|
||||
* fun main(args: Array<String>) {
|
||||
* runSuspend { main() }
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* 3. `fun main() { .. }` for which we generate
|
||||
* ```
|
||||
* fun main(args: Array<String>) {
|
||||
* main()
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
override fun lower(irClass: IrClass) {
|
||||
if (!context.configuration.languageVersionSettings.supportsFeature(LanguageFeature.ExtendedMainConvention)) return
|
||||
if (!irClass.isFileClass) return
|
||||
|
||||
val parameterlessMain = irClass.functions.find { it.isParameterlessMainMethod() } ?: return
|
||||
|
||||
if (irClass.functions.any { it.isMainMethod() }) return
|
||||
|
||||
generateMainMethod(irClass, parameterlessMain)
|
||||
}
|
||||
|
||||
private fun generateMainMethod(irClass: IrClass, parameterlessMain: IrSimpleFunction) {
|
||||
irClass.addFunction {
|
||||
name = Name.identifier("main")
|
||||
visibility = Visibilities.PUBLIC
|
||||
returnType = context.irBuiltIns.unitType
|
||||
modality = Modality.FINAL
|
||||
origin = JvmLoweredDeclarationOrigin.GENERATED_MAIN_FOR_PARAMETERLESS_MAIN_METHOD
|
||||
}.apply {
|
||||
addValueParameter {
|
||||
name = Name.identifier("args")
|
||||
type = context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.stringType)
|
||||
irClass.functions.find { it.isMainMethod() }?.let { mainMethod ->
|
||||
if (mainMethod.isSuspend) {
|
||||
generateMainMethod(irClass) { args ->
|
||||
body = buildRunSuspendWrapper {
|
||||
+irCall(mainMethod).apply {
|
||||
putValueArgument(0, irGet(args))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
body = context.createIrBuilder(this.symbol).irBlockBody {
|
||||
+irReturn(this.irCall(parameterlessMain))
|
||||
return
|
||||
}
|
||||
|
||||
irClass.functions.find { it.isParameterlessMainMethod() }?.let { parameterlessMainMethod ->
|
||||
generateMainMethod(irClass) {
|
||||
body = if (parameterlessMainMethod.isSuspend) {
|
||||
buildRunSuspendWrapper {
|
||||
+irCall(parameterlessMainMethod)
|
||||
}
|
||||
} else {
|
||||
context.createIrBuilder(symbol).irBlockBody {
|
||||
+irCall(parameterlessMainMethod)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -75,7 +112,6 @@ internal class MainMethodGenerationLowering(val context: JvmBackendContext) : Cl
|
||||
returnType.isUnit() &&
|
||||
name.asString() == "main"
|
||||
|
||||
|
||||
private fun IrSimpleFunction.isMainMethod(): Boolean {
|
||||
if (getJvmNameFromAnnotation() ?: name.asString() != "main") return false
|
||||
if (!returnType.isUnit()) return false
|
||||
@@ -91,4 +127,57 @@ internal class MainMethodGenerationLowering(val context: JvmBackendContext) : Cl
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateMainMethod(
|
||||
irClass: IrClass,
|
||||
makeBody: IrFunctionImpl.(IrValueParameter) -> Unit
|
||||
) {
|
||||
irClass.addFunction {
|
||||
name = Name.identifier("main")
|
||||
visibility = Visibilities.PUBLIC
|
||||
returnType = context.irBuiltIns.unitType
|
||||
modality = Modality.FINAL
|
||||
this.origin = JvmLoweredDeclarationOrigin.GENERATED_EXTENDED_MAIN
|
||||
}.apply {
|
||||
val args = addValueParameter {
|
||||
name = Name.identifier("args")
|
||||
type = context.irBuiltIns.arrayClass.typeWith(context.irBuiltIns.stringType)
|
||||
}
|
||||
makeBody(args)
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrFunctionImpl.buildRunSuspendWrapper(buildMainInvocation: IrBlockBodyBuilder.() -> Unit) =
|
||||
context.createJvmIrBuilder(symbol).irBlockBody {
|
||||
val lambda = irBlock(UNDEFINED_OFFSET, UNDEFINED_OFFSET, IrStatementOrigin.LAMBDA) {
|
||||
val function = buildFun {
|
||||
origin = IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA
|
||||
name = Name.identifier("wrapper_for_suspend_main")
|
||||
visibility = Visibilities.LOCAL
|
||||
returnType = context.irBuiltIns.unitType
|
||||
}.apply {
|
||||
addValueParameter("k", context.irBuiltIns.anyNType)
|
||||
parent = this
|
||||
body = irBlockBody {
|
||||
buildMainInvocation()
|
||||
}
|
||||
}
|
||||
+function
|
||||
+IrFunctionReferenceImpl(
|
||||
UNDEFINED_OFFSET,
|
||||
UNDEFINED_OFFSET,
|
||||
context.irBuiltIns.function1.typeWith(
|
||||
context.irBuiltIns.anyNType,
|
||||
context.irBuiltIns.unitType
|
||||
),
|
||||
function.symbol,
|
||||
typeArgumentsCount = 0,
|
||||
origin = IrStatementOrigin.LAMBDA,
|
||||
reflectionTarget = null
|
||||
)
|
||||
}
|
||||
+irCall(this@MainMethodGenerationLowering.context.ir.symbols.runSuspendFunction).apply {
|
||||
putValueArgument(0, lambda)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -318,4 +318,6 @@ class IrBuiltIns(
|
||||
const val ANDAND = "ANDAND"
|
||||
const val OROR = "OROR"
|
||||
}
|
||||
|
||||
val function1: IrClassSymbol = builtIns.getFunction(1).toIrSymbol()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
// WITH_RUNTIME
|
||||
suspend fun main() {
|
||||
|
||||
}
|
||||
|
||||
// method: ParameterlessSuspendMainKt::main
|
||||
// jvm signature: (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
// generic signature: (Lkotlin/coroutines/Continuation<-Lkotlin/Unit;>;)Ljava/lang/Object;
|
||||
|
||||
// method: ParameterlessSuspendMainKt::main
|
||||
// jvm signature: ([Ljava/lang/String;)V
|
||||
// generic signature: null
|
||||
@@ -0,0 +1,17 @@
|
||||
// WITH_RUNTIME
|
||||
suspend fun f(): Int {
|
||||
return 4
|
||||
}
|
||||
|
||||
suspend fun main(args: Array<String>) {
|
||||
f()
|
||||
}
|
||||
|
||||
// method: SuspendMainKt::main
|
||||
// jvm signature: ([Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
|
||||
// generic signature: ([Ljava/lang/String;Lkotlin/coroutines/Continuation<-Lkotlin/Unit;>;)Ljava/lang/Object;
|
||||
|
||||
// method: SuspendMainKt::main
|
||||
// jvm signature: ([Ljava/lang/String;)V
|
||||
// generic signature: null
|
||||
|
||||
+23
@@ -656,4 +656,27 @@ public class IrWriteSignatureTestGenerated extends AbstractIrWriteSignatureTest
|
||||
runTest("compiler/testData/writeSignature/parameterlessMain/simple.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/writeSignature/suspendMain")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class SuspendMain extends AbstractIrWriteSignatureTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInSuspendMain() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/writeSignature/suspendMain"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM_IR, true);
|
||||
}
|
||||
|
||||
@TestMetadata("parameterlessSuspendMain.kt")
|
||||
public void testParameterlessSuspendMain() throws Exception {
|
||||
runTest("compiler/testData/writeSignature/suspendMain/parameterlessSuspendMain.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendMain.kt")
|
||||
public void testSuspendMain() throws Exception {
|
||||
runTest("compiler/testData/writeSignature/suspendMain/suspendMain.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -655,4 +655,27 @@ public class WriteSignatureTestGenerated extends AbstractWriteSignatureTest {
|
||||
runTest("compiler/testData/writeSignature/parameterlessMain/simple.kt");
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/testData/writeSignature/suspendMain")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class SuspendMain extends AbstractWriteSignatureTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInSuspendMain() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/writeSignature/suspendMain"), Pattern.compile("^(.+)\\.kt$"), true);
|
||||
}
|
||||
|
||||
@TestMetadata("parameterlessSuspendMain.kt")
|
||||
public void testParameterlessSuspendMain() throws Exception {
|
||||
runTest("compiler/testData/writeSignature/suspendMain/parameterlessSuspendMain.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("suspendMain.kt")
|
||||
public void testSuspendMain() throws Exception {
|
||||
runTest("compiler/testData/writeSignature/suspendMain/suspendMain.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user