diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt index 366bd61b692..29e189138ac 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/codegenUtil.kt @@ -484,16 +484,25 @@ fun generateBridgeForMainFunctionIfNecessary( ) { val originElement = origin.element ?: return if (functionDescriptor.name.asString() != "main" || !DescriptorUtils.isTopLevelDeclaration(functionDescriptor)) return - if (!state.mainFunctionDetector.isMain(functionDescriptor.unwrapInitialDescriptorForSuspendFunction(), false, true)) return - if (!functionDescriptor.isSuspend) return + val unwrappedFunctionDescriptor = functionDescriptor.unwrapInitialDescriptorForSuspendFunction() + val isParameterless = + unwrappedFunctionDescriptor.extensionReceiverParameter == null && unwrappedFunctionDescriptor.valueParameters.isEmpty() - val lambdaInternalName = generateLambdaForRunSuspend( - state, - originElement, - packagePartClassBuilder.thisName, - signatureOfRealDeclaration - ) + if (!functionDescriptor.isSuspend && !isParameterless) return + if (!state.mainFunctionDetector.isMain(unwrappedFunctionDescriptor, false, true)) return + + val lambdaInternalName = + if (functionDescriptor.isSuspend) + generateLambdaForRunSuspend( + state, + originElement, + packagePartClassBuilder.thisName, + signatureOfRealDeclaration, + isParameterless + ) + else + null packagePartClassBuilder.newMethod( Synthetic(originElement, functionDescriptor), @@ -502,26 +511,39 @@ fun generateBridgeForMainFunctionIfNecessary( METHOD_DESCRIPTOR_FOR_MAIN, null, null ).apply { visitCode() - visitTypeInsn(NEW, lambdaInternalName) - visitInsn(DUP) - visitVarInsn(ALOAD, 0) - visitMethodInsn( - INVOKESPECIAL, - lambdaInternalName, - "", - METHOD_DESCRIPTOR_FOR_MAIN, - false - ) - visitMethodInsn( - INVOKESTATIC, - "kotlin/coroutines/jvm/internal/RunSuspendKt", "runSuspend", - Type.getMethodDescriptor( - Type.VOID_TYPE, - Type.getObjectType(NUMBERED_FUNCTION_PREFIX + "1") - ), - false - ) + if (lambdaInternalName != null) { + visitTypeInsn(NEW, lambdaInternalName) + visitInsn(DUP) + visitVarInsn(ALOAD, 0) + visitMethodInsn( + INVOKESPECIAL, + lambdaInternalName, + "", + METHOD_DESCRIPTOR_FOR_MAIN, + false + ) + + visitMethodInsn( + INVOKESTATIC, + "kotlin/coroutines/jvm/internal/RunSuspendKt", "runSuspend", + Type.getMethodDescriptor( + Type.VOID_TYPE, + Type.getObjectType(NUMBERED_FUNCTION_PREFIX + "1") + ), + false + ) + } else { + visitMethodInsn( + INVOKESTATIC, + packagePartClassBuilder.thisName, "main", + Type.getMethodDescriptor( + Type.VOID_TYPE + ), + false + ) + } + visitInsn(RETURN) visitEnd() } @@ -531,7 +553,8 @@ private fun generateLambdaForRunSuspend( state: GenerationState, originElement: PsiElement, packagePartClassInternalName: String, - signatureOfRealDeclaration: JvmMethodGenericSignature + signatureOfRealDeclaration: JvmMethodGenericSignature, + parameterless: Boolean ): String { val internalName = "$packagePartClassInternalName$$\$main" val lambdaBuilder = state.factory.newVisitor( @@ -592,13 +615,17 @@ private fun generateLambdaForRunSuspend( ).apply { visitCode() - visitVarInsn(ALOAD, 0) - visitFieldInsn( - GETFIELD, - lambdaBuilder.thisName, - "args", - ARRAY_OF_STRINGS_TYPE.descriptor - ) + if (!parameterless) { + // Actually, the field for arguments may also be removed in case of parameterless main, + // but probably it'd much easier when IR is ready + visitVarInsn(ALOAD, 0) + visitFieldInsn( + GETFIELD, + lambdaBuilder.thisName, + "args", + ARRAY_OF_STRINGS_TYPE.descriptor + ) + } visitVarInsn(ALOAD, 1) val continuationInternalName = state.languageVersionSettings.continuationAsmType().internalName diff --git a/compiler/testData/integration/smoke/helloAppOldAndParameterlessMain/hello.compile.expected b/compiler/testData/integration/smoke/helloAppOldAndParameterlessMain/hello.compile.expected new file mode 100644 index 00000000000..a14ac74940f --- /dev/null +++ b/compiler/testData/integration/smoke/helloAppOldAndParameterlessMain/hello.compile.expected @@ -0,0 +1 @@ +Return code: 0 diff --git a/compiler/testData/integration/smoke/helloAppOldAndParameterlessMain/hello.kt b/compiler/testData/integration/smoke/helloAppOldAndParameterlessMain/hello.kt new file mode 100644 index 00000000000..3625851a795 --- /dev/null +++ b/compiler/testData/integration/smoke/helloAppOldAndParameterlessMain/hello.kt @@ -0,0 +1,9 @@ +package Hello + +fun main(args: Array) { + System.out.println("Hello!") +} + +fun main() { + System.out.println("Fail") +} diff --git a/compiler/testData/integration/smoke/helloAppOldAndParameterlessMain/hello.run.expected b/compiler/testData/integration/smoke/helloAppOldAndParameterlessMain/hello.run.expected new file mode 100644 index 00000000000..fd58d467c30 --- /dev/null +++ b/compiler/testData/integration/smoke/helloAppOldAndParameterlessMain/hello.run.expected @@ -0,0 +1,4 @@ +OUT: +Hello! + +Return code: 0 diff --git a/compiler/testData/integration/smoke/helloAppParameterlessMain/hello.compile.expected b/compiler/testData/integration/smoke/helloAppParameterlessMain/hello.compile.expected new file mode 100644 index 00000000000..a14ac74940f --- /dev/null +++ b/compiler/testData/integration/smoke/helloAppParameterlessMain/hello.compile.expected @@ -0,0 +1 @@ +Return code: 0 diff --git a/compiler/testData/integration/smoke/helloAppParameterlessMain/hello.kt b/compiler/testData/integration/smoke/helloAppParameterlessMain/hello.kt new file mode 100644 index 00000000000..54674a02472 --- /dev/null +++ b/compiler/testData/integration/smoke/helloAppParameterlessMain/hello.kt @@ -0,0 +1,5 @@ +package Hello + +fun main() { + System.out.println("Hello!") +} diff --git a/compiler/testData/integration/smoke/helloAppParameterlessMain/hello.run.expected b/compiler/testData/integration/smoke/helloAppParameterlessMain/hello.run.expected new file mode 100644 index 00000000000..fd58d467c30 --- /dev/null +++ b/compiler/testData/integration/smoke/helloAppParameterlessMain/hello.run.expected @@ -0,0 +1,4 @@ +OUT: +Hello! + +Return code: 0 diff --git a/compiler/testData/integration/smoke/helloAppSuspendParameterlessMain/hello.compile.expected b/compiler/testData/integration/smoke/helloAppSuspendParameterlessMain/hello.compile.expected new file mode 100644 index 00000000000..a14ac74940f --- /dev/null +++ b/compiler/testData/integration/smoke/helloAppSuspendParameterlessMain/hello.compile.expected @@ -0,0 +1 @@ +Return code: 0 diff --git a/compiler/testData/integration/smoke/helloAppSuspendParameterlessMain/hello.kt b/compiler/testData/integration/smoke/helloAppSuspendParameterlessMain/hello.kt new file mode 100644 index 00000000000..473bde1e5d8 --- /dev/null +++ b/compiler/testData/integration/smoke/helloAppSuspendParameterlessMain/hello.kt @@ -0,0 +1,36 @@ +package Hello + +import kotlin.concurrent.thread +import kotlin.coroutines.suspendCoroutine +import kotlin.coroutines.resume +import kotlin.reflect.jvm.javaMethod + +@kotlin.jvm.Volatile +private var result = "" +@kotlin.jvm.Volatile +private var callback: Function0? = null + +suspend fun appendAndSuspend(s: String) { + result += s + + suspendCoroutine { continuation -> + callback = { + continuation.resume(Unit) + } + } +} + +suspend fun main() { + thread(isDaemon = true) { + while (true) { + val c = callback + c?.invoke() + Thread.sleep(500) + } + } + + appendAndSuspend("O") + appendAndSuspend("K") + println(result) + callback = null +} diff --git a/compiler/testData/integration/smoke/helloAppSuspendParameterlessMain/hello.run.expected b/compiler/testData/integration/smoke/helloAppSuspendParameterlessMain/hello.run.expected new file mode 100644 index 00000000000..4000785a4d1 --- /dev/null +++ b/compiler/testData/integration/smoke/helloAppSuspendParameterlessMain/hello.run.expected @@ -0,0 +1,4 @@ +OUT: +OK + +Return code: 0 diff --git a/compiler/tests/org/jetbrains/kotlin/integration/CompilerSmokeTest.java b/compiler/tests/org/jetbrains/kotlin/integration/CompilerSmokeTest.java index 7848466c539..7f8b45e5260 100644 --- a/compiler/tests/org/jetbrains/kotlin/integration/CompilerSmokeTest.java +++ b/compiler/tests/org/jetbrains/kotlin/integration/CompilerSmokeTest.java @@ -48,6 +48,27 @@ public class CompilerSmokeTest extends CompilerSmokeTestBase { run("hello.run", "-cp", jar, "Hello.HelloKt", "O", "K"); } + public void testHelloAppParameterlessMain() throws Exception { + String jar = tmpdir.getAbsolutePath() + File.separator + "hello.jar"; + + assertEquals("compilation failed", 0, runCompiler("hello.compile", "-include-runtime", "hello.kt", "-d", jar)); + run("hello.run", "-cp", jar, "Hello.HelloKt"); + } + + public void testHelloAppOldAndParameterlessMain() throws Exception { + String jar = tmpdir.getAbsolutePath() + File.separator + "hello.jar"; + + assertEquals("compilation failed", 0, runCompiler("hello.compile", "-include-runtime", "hello.kt", "-d", jar)); + run("hello.run", "-cp", jar, "Hello.HelloKt"); + } + + public void testHelloAppSuspendParameterlessMain() throws Exception { + String jar = tmpdir.getAbsolutePath() + File.separator + "hello.jar"; + + assertEquals("compilation failed", 0, runCompiler("hello.compile", "-include-runtime", "hello.kt", "-d", jar)); + run("hello.run", "-cp", jar, "Hello.HelloKt", "O", "K"); + } + public void testCompilationFailed() throws Exception { String jar = tmpdir.getAbsolutePath() + File.separator + "smoke.jar";