[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:
Kristoffer Andersen
2019-11-19 11:18:04 +01:00
committed by Ilmir Usmanov
parent 068d547375
commit 55aafb3430
12 changed files with 237 additions and 34 deletions
@@ -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 =
@@ -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)
}
}
}
@@ -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 {
@@ -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
@@ -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");
}
}
}
@@ -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");
}
}
}