[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 INLINE_CLASS_GENERATED_IMPL_METHOD : IrDeclarationOriginImpl("INLINE_CLASS_GENERATED_IMPL_METHOD")
object STATIC_INLINE_CLASS_REPLACEMENT : IrDeclarationOriginImpl("STATIC_INLINE_CLASS_REPLACEMENT") object STATIC_INLINE_CLASS_REPLACEMENT : IrDeclarationOriginImpl("STATIC_INLINE_CLASS_REPLACEMENT")
object GENERATED_ASSERTION_ENABLED_FIELD : IrDeclarationOriginImpl("GENERATED_ASSERTION_ENABLED_FIELD", isSynthetic = true) 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 SUSPEND_IMPL_STATIC_FUNCTION : IrDeclarationOriginImpl("SUSPEND_IMPL_STATIC_FUNCTION", isSynthetic = true)
object INTERFACE_COMPANION_PRIVATE_INSTANCE : IrDeclarationOriginImpl("INTERFACE_COMPANION_PRIVATE_INSTANCE", isSynthetic = true) object INTERFACE_COMPANION_PRIVATE_INSTANCE : IrDeclarationOriginImpl("INTERFACE_COMPANION_PRIVATE_INSTANCE", isSynthetic = true)
object POLYMORPHIC_SIGNATURE_INSTANTIATION : IrDeclarationOriginImpl("POLYMORPHIC_SIGNATURE_INSTANTIATION", isSynthetic = true) object POLYMORPHIC_SIGNATURE_INSTANTIATION : IrDeclarationOriginImpl("POLYMORPHIC_SIGNATURE_INSTANTIATION", isSynthetic = true)
@@ -260,6 +260,10 @@ private val jvmFilePhases =
typeAliasAnnotationMethodsPhase then typeAliasAnnotationMethodsPhase then
stripTypeAliasDeclarationsPhase then stripTypeAliasDeclarationsPhase then
provisionalFunctionExpressionPhase then provisionalFunctionExpressionPhase then
jvmOverloadsAnnotationPhase then
mainMethodGenerationPhase then
inventNamesForLocalClassesPhase then inventNamesForLocalClassesPhase then
kCallableNamePropertyPhase then kCallableNamePropertyPhase then
annotationPhase then annotationPhase then
@@ -298,7 +302,6 @@ private val jvmFilePhases =
localDeclarationsPhase then localDeclarationsPhase then
jvmLocalClassExtractionPhase then jvmLocalClassExtractionPhase then
jvmOverloadsAnnotationPhase then
jvmDefaultConstructorPhase then jvmDefaultConstructorPhase then
forLoopsPhase then forLoopsPhase then
@@ -347,7 +350,6 @@ private val jvmFilePhases =
checkLocalNamesWithOldBackendPhase then checkLocalNamesWithOldBackendPhase then
mainMethodGenerationPhase then
renameFieldsPhase then renameFieldsPhase then
fakeInliningLocalVariablesLowering then fakeInliningLocalVariablesLowering then
@@ -19,10 +19,9 @@ import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
import org.jetbrains.kotlin.ir.builders.declarations.* import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl 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.*
import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl
import org.jetbrains.kotlin.ir.types.* import org.jetbrains.kotlin.ir.types.*
@@ -607,6 +606,16 @@ class JvmSymbols(
val enumValueOfFunction: IrSimpleFunctionSymbol = val enumValueOfFunction: IrSimpleFunctionSymbol =
javaLangEnum.functionByName("valueOf") 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 = private fun IrClassSymbol.functionByName(name: String): IrSimpleFunctionSymbol =
@@ -104,7 +104,8 @@ class IrIntrinsicMethods(val irBuiltIns: IrBuiltIns, val symbols: JvmSymbols) {
irBuiltIns.dataClassArrayMemberToStringSymbol.toKey()!! to IrDataClassArrayMemberToString, irBuiltIns.dataClassArrayMemberToStringSymbol.toKey()!! to IrDataClassArrayMemberToString,
symbols.unsafeCoerceIntrinsic.toKey()!! to UnsafeCoerce, symbols.unsafeCoerceIntrinsic.toKey()!! to UnsafeCoerce,
symbols.signatureStringIntrinsic.toKey()!! to SignatureString, symbols.signatureStringIntrinsic.toKey()!! to SignatureString,
symbols.reassignParameterIntrinsic.toKey()!! to ReassignParameter symbols.reassignParameterIntrinsic.toKey()!! to ReassignParameter,
symbols.runSuspendFunction.toKey()!! to RunSuspend
) + ) +
numberConversionMethods() + numberConversionMethods() +
unaryFunForPrimitives("plus", UnaryPlus) + 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>( val inventNamesForLocalClassesPhase = makeIrFilePhase<JvmBackendContext>(
{ context -> InventNamesForLocalClasses(context) }, { context -> InventNamesForLocalClasses(context) },
name = "InventNamesForLocalClasses", 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 { 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.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.backend.jvm.JvmLoweredDeclarationOrigin 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.backend.jvm.ir.getJvmNameFromAnnotation
import org.jetbrains.kotlin.config.LanguageFeature import org.jetbrains.kotlin.config.LanguageFeature
import org.jetbrains.kotlin.config.languageVersionSettings import org.jetbrains.kotlin.config.languageVersionSettings
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities 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.addFunction
import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter import org.jetbrains.kotlin.ir.builders.declarations.addValueParameter
import org.jetbrains.kotlin.ir.builders.irBlockBody import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irReturn
import org.jetbrains.kotlin.ir.declarations.IrClass import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin import org.jetbrains.kotlin.ir.declarations.IrDeclarationOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction 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.types.*
import org.jetbrains.kotlin.ir.util.functions import org.jetbrains.kotlin.ir.util.functions
import org.jetbrains.kotlin.ir.util.isFileClass import org.jetbrains.kotlin.ir.util.isFileClass
@@ -33,37 +38,69 @@ import org.jetbrains.kotlin.types.Variance
internal val mainMethodGenerationPhase = makeIrFilePhase( internal val mainMethodGenerationPhase = makeIrFilePhase(
::MainMethodGenerationLowering, ::MainMethodGenerationLowering,
name = "MainMethodGeneration", 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) 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) { override fun lower(irClass: IrClass) {
if (!context.configuration.languageVersionSettings.supportsFeature(LanguageFeature.ExtendedMainConvention)) return if (!context.configuration.languageVersionSettings.supportsFeature(LanguageFeature.ExtendedMainConvention)) return
if (!irClass.isFileClass) return if (!irClass.isFileClass) return
val parameterlessMain = irClass.functions.find { it.isParameterlessMainMethod() } ?: return irClass.functions.find { it.isMainMethod() }?.let { mainMethod ->
if (mainMethod.isSuspend) {
if (irClass.functions.any { it.isMainMethod() }) return generateMainMethod(irClass) { args ->
body = buildRunSuspendWrapper {
generateMainMethod(irClass, parameterlessMain) +irCall(mainMethod).apply {
} putValueArgument(0, irGet(args))
}
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)
} }
body = context.createIrBuilder(this.symbol).irBlockBody { return
+irReturn(this.irCall(parameterlessMain)) }
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() && returnType.isUnit() &&
name.asString() == "main" name.asString() == "main"
private fun IrSimpleFunction.isMainMethod(): Boolean { private fun IrSimpleFunction.isMainMethod(): Boolean {
if (getJvmNameFromAnnotation() ?: name.asString() != "main") return false if (getJvmNameFromAnnotation() ?: name.asString() != "main") return false
if (!returnType.isUnit()) return false if (!returnType.isUnit()) return false
@@ -91,4 +127,57 @@ internal class MainMethodGenerationLowering(val context: JvmBackendContext) : Cl
else -> false 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 ANDAND = "ANDAND"
const val OROR = "OROR" 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"); 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"); 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");
}
}
} }