Support main entry-point without arguments in JVM

#KT-26574 In Progress
This commit is contained in:
Denis Zharkov
2018-09-06 18:24:35 +03:00
parent 2c920b732c
commit bc722f9c5f
11 changed files with 148 additions and 35 deletions
@@ -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,
"<init>",
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,
"<init>",
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
@@ -0,0 +1 @@
Return code: 0
@@ -0,0 +1,9 @@
package Hello
fun main(args: Array<String>) {
System.out.println("Hello!")
}
fun main() {
System.out.println("Fail")
}
@@ -0,0 +1,4 @@
OUT:
Hello!
Return code: 0
@@ -0,0 +1 @@
Return code: 0
@@ -0,0 +1,5 @@
package Hello
fun main() {
System.out.println("Hello!")
}
@@ -0,0 +1,4 @@
OUT:
Hello!
Return code: 0
@@ -0,0 +1 @@
Return code: 0
@@ -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<Unit>? = null
suspend fun appendAndSuspend(s: String) {
result += s
suspendCoroutine<Unit> { 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
}
@@ -0,0 +1,4 @@
OUT:
OK
Return code: 0
@@ -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";