diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt index 11c694ad570..e484c27bbd6 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/DeclarationOrigins.kt @@ -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) diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index f949436b640..ce4087a1c67 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -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 diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt index a2e2168ede6..4483f678400 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmSymbols.kt @@ -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 = diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt index a81dd7e9d5c..425273df1db 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/IrIntrinsicMethods.kt @@ -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) + diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/RunSuspend.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/RunSuspend.kt new file mode 100644 index 00000000000..c1374e4cb2f --- /dev/null +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/intrinsics/RunSuspend.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/InventNamesForLocalClasses.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/InventNamesForLocalClasses.kt index eccf77f616c..8cdf951d7a5 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/InventNamesForLocalClasses.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/InventNamesForLocalClasses.kt @@ -24,7 +24,9 @@ import org.jetbrains.org.objectweb.asm.Type val inventNamesForLocalClassesPhase = makeIrFilePhase( { 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 { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/MainMethodGenerationLowering.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/MainMethodGenerationLowering.kt index 27378e33595..eaf46c24c2d 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/MainMethodGenerationLowering.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/lower/MainMethodGenerationLowering.kt @@ -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) { .. }` for which we generate + * ``` + * fun main(args: Array) { + * runSuspend { main(args) } + * } + * ``` + * + * 2. `suspend fun main() { .. }` for which we generate + * ``` + * fun main(args: Array) { + * runSuspend { main() } + * } + * ``` + * + * 3. `fun main() { .. }` for which we generate + * ``` + * fun main(args: Array) { + * 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) + } + } } \ No newline at end of file diff --git a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/descriptors/IrBuiltIns.kt b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/descriptors/IrBuiltIns.kt index 26080f8aa02..5dcb4cc061e 100644 --- a/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/descriptors/IrBuiltIns.kt +++ b/compiler/ir/ir.tree/src/org/jetbrains/kotlin/ir/descriptors/IrBuiltIns.kt @@ -318,4 +318,6 @@ class IrBuiltIns( const val ANDAND = "ANDAND" const val OROR = "OROR" } + + val function1: IrClassSymbol = builtIns.getFunction(1).toIrSymbol() } diff --git a/compiler/testData/writeSignature/suspendMain/parameterlessSuspendMain.kt b/compiler/testData/writeSignature/suspendMain/parameterlessSuspendMain.kt new file mode 100644 index 00000000000..9fa5bb25554 --- /dev/null +++ b/compiler/testData/writeSignature/suspendMain/parameterlessSuspendMain.kt @@ -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 \ No newline at end of file diff --git a/compiler/testData/writeSignature/suspendMain/suspendMain.kt b/compiler/testData/writeSignature/suspendMain/suspendMain.kt new file mode 100644 index 00000000000..ea1ca1d34a1 --- /dev/null +++ b/compiler/testData/writeSignature/suspendMain/suspendMain.kt @@ -0,0 +1,17 @@ +// WITH_RUNTIME +suspend fun f(): Int { + return 4 +} + +suspend fun main(args: Array) { + 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 + diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrWriteSignatureTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrWriteSignatureTestGenerated.java index a46a4cff27d..492cacf7f3b 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrWriteSignatureTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/ir/IrWriteSignatureTestGenerated.java @@ -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"); + } + } } diff --git a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java index 75c1765d443..dd1f40ffba7 100644 --- a/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/jvm/compiler/WriteSignatureTestGenerated.java @@ -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"); + } + } }