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 val originElement = origin.element ?: return
if (functionDescriptor.name.asString() != "main" || !DescriptorUtils.isTopLevelDeclaration(functionDescriptor)) 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( if (!functionDescriptor.isSuspend && !isParameterless) return
if (!state.mainFunctionDetector.isMain(unwrappedFunctionDescriptor, false, true)) return
val lambdaInternalName =
if (functionDescriptor.isSuspend)
generateLambdaForRunSuspend(
state, state,
originElement, originElement,
packagePartClassBuilder.thisName, packagePartClassBuilder.thisName,
signatureOfRealDeclaration signatureOfRealDeclaration,
isParameterless
) )
else
null
packagePartClassBuilder.newMethod( packagePartClassBuilder.newMethod(
Synthetic(originElement, functionDescriptor), Synthetic(originElement, functionDescriptor),
@@ -502,6 +511,8 @@ fun generateBridgeForMainFunctionIfNecessary(
METHOD_DESCRIPTOR_FOR_MAIN, null, null METHOD_DESCRIPTOR_FOR_MAIN, null, null
).apply { ).apply {
visitCode() visitCode()
if (lambdaInternalName != null) {
visitTypeInsn(NEW, lambdaInternalName) visitTypeInsn(NEW, lambdaInternalName)
visitInsn(DUP) visitInsn(DUP)
visitVarInsn(ALOAD, 0) visitVarInsn(ALOAD, 0)
@@ -522,6 +533,17 @@ fun generateBridgeForMainFunctionIfNecessary(
), ),
false false
) )
} else {
visitMethodInsn(
INVOKESTATIC,
packagePartClassBuilder.thisName, "main",
Type.getMethodDescriptor(
Type.VOID_TYPE
),
false
)
}
visitInsn(RETURN) visitInsn(RETURN)
visitEnd() visitEnd()
} }
@@ -531,7 +553,8 @@ private fun generateLambdaForRunSuspend(
state: GenerationState, state: GenerationState,
originElement: PsiElement, originElement: PsiElement,
packagePartClassInternalName: String, packagePartClassInternalName: String,
signatureOfRealDeclaration: JvmMethodGenericSignature signatureOfRealDeclaration: JvmMethodGenericSignature,
parameterless: Boolean
): String { ): String {
val internalName = "$packagePartClassInternalName$$\$main" val internalName = "$packagePartClassInternalName$$\$main"
val lambdaBuilder = state.factory.newVisitor( val lambdaBuilder = state.factory.newVisitor(
@@ -592,6 +615,9 @@ private fun generateLambdaForRunSuspend(
).apply { ).apply {
visitCode() visitCode()
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) visitVarInsn(ALOAD, 0)
visitFieldInsn( visitFieldInsn(
GETFIELD, GETFIELD,
@@ -599,6 +625,7 @@ private fun generateLambdaForRunSuspend(
"args", "args",
ARRAY_OF_STRINGS_TYPE.descriptor ARRAY_OF_STRINGS_TYPE.descriptor
) )
}
visitVarInsn(ALOAD, 1) visitVarInsn(ALOAD, 1)
val continuationInternalName = state.languageVersionSettings.continuationAsmType().internalName 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"); 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 { public void testCompilationFailed() throws Exception {
String jar = tmpdir.getAbsolutePath() + File.separator + "smoke.jar"; String jar = tmpdir.getAbsolutePath() + File.separator + "smoke.jar";