From 5192bd13137417989a04f62d3a53af73e46b3ed3 Mon Sep 17 00:00:00 2001 From: Alexander Gorshenev Date: Thu, 1 Jun 2017 18:45:54 +0300 Subject: [PATCH] Entry point selection. $ kotlinc -entry foo.bar.qux ... selects `qux(args: Array):Unit` in the package `foo.bar` as an entry point. The short flag is `-e`. --- .../org/jetbrains/kotlin/cli/bc/K2Native.kt | 1 + .../cli/bc/K2NativeCompilerArguments.kt | 3 + .../backend/konan/KonanConfigurationKeys.kt | 2 + .../kotlin/backend/konan/llvm/EntryPoint.kt | 77 +++++++++++++++++++ .../kotlin/backend/konan/llvm/IrToBitcode.kt | 39 ++++++++++ backend.native/tests/build.gradle | 19 +++++ backend.native/tests/runtime/basic/entry0.kt | 32 ++++++++ backend.native/tests/runtime/basic/entry1.kt | 13 ++++ backend.native/tests/runtime/basic/entry2.kt | 4 + .../tests/runtime/basic/libentry2.kt | 8 ++ runtime/src/launcher/kotlin/konan/start.kt | 10 ++- 11 files changed, 205 insertions(+), 3 deletions(-) create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/EntryPoint.kt create mode 100644 backend.native/tests/runtime/basic/entry0.kt create mode 100644 backend.native/tests/runtime/basic/entry1.kt create mode 100644 backend.native/tests/runtime/basic/entry2.kt create mode 100644 backend.native/tests/runtime/basic/libentry2.kt diff --git a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt index 73f062ec526..77d2b83fa91 100644 --- a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt +++ b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt @@ -122,6 +122,7 @@ class K2Native : CLICompiler() { put(CommonConfigurationKeys.MODULE_NAME, output) put(ABI_VERSION, 1) + arguments.mainPackage ?.let{ put(ENTRY, it) } arguments.runtimeFile ?.let{ put(RUNTIME_FILE, it) } arguments.propertyFile ?.let{ put(PROPERTY_FILE, it) } diff --git a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt index ea9fd080281..8a059ba4367 100644 --- a/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt +++ b/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt @@ -57,6 +57,9 @@ class K2NativeCompilerArguments : CommonCompilerArguments() { @field:Argument(value = "-output", shortName = "-o", valueDescription = "", description = "Output file path") @JvmField var outputFile: String? = null + @field:Argument(value = "-entry", shortName = "-e", valueDescription = "", description = "Qualified entry point name") + @JvmField var mainPackage: String? = null + @field:Argument(value = "-produce", shortName = "-p", valueDescription = "{program|library|bitcode}", description = "Produce either .kexe, .klib or a .bc file.") @JvmField var produce: String? = null diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt index 8dcf7b29052..0d35facb701 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt @@ -40,6 +40,8 @@ class KonanConfigKeys { = CompilerConfigurationKey.create("list backend phases") val LIST_TARGETS: CompilerConfigurationKey = CompilerConfigurationKey.create("list available targets") + val ENTRY: CompilerConfigurationKey + = CompilerConfigurationKey.create("fully qualified main() name") val META_INFO: CompilerConfigurationKey> = CompilerConfigurationKey.create("generate metadata") val MODULE_KIND: CompilerConfigurationKey diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/EntryPoint.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/EntryPoint.kt new file mode 100644 index 00000000000..181e566a2b4 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/EntryPoint.kt @@ -0,0 +1,77 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.backend.konan.llvm + +import org.jetbrains.kotlin.backend.konan.CompilerOutputKind +import org.jetbrains.kotlin.backend.konan.Context +import org.jetbrains.kotlin.backend.konan.KonanConfigKeys +import org.jetbrains.kotlin.backend.konan.descriptors.isArray +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.FunctionDescriptor +import org.jetbrains.kotlin.incremental.components.NoLookupLocation +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.types.KotlinType +import org.jetbrains.kotlin.types.typeUtil.isUnit +import org.jetbrains.kotlin.utils.alwaysTrue + +internal fun findMainEntryPoint(context: Context): FunctionDescriptor? { + + val config = context.config.configuration + if (config.get(KonanConfigKeys.PRODUCE) != CompilerOutputKind.PROGRAM) return null + + val entryPoint = FqName(config.get(KonanConfigKeys.ENTRY) ?: defaultEntryName) + + val entryName = entryPoint.shortName() + val packageName = entryPoint.parent() + + val packageScope = context.builtIns.builtInsModule.getPackage(packageName).memberScope + + val main = packageScope.getContributedFunctions(entryName, + NoLookupLocation.FROM_BACKEND).singleOrNull { + it.returnType?.isUnit() == true && + it.hasSingleArrayOfStringParameter && + it.typeParameters.isEmpty() && + it.isExported() + } + return main +} + +private val defaultEntryName = "main" + +private val defaultEntryPackage = FqName.ROOT + +private val KotlinType.filterClass: ClassDescriptor? + get() { + val constr = constructor.declarationDescriptor + return constr as? ClassDescriptor + } + +private val ClassDescriptor.isString + get() = fqNameSafe.asString() == "kotlin.String" + +private val KotlinType.isString + get() = filterClass?.isString ?: false + +private val KotlinType.isArrayOfString: Boolean + get() = (filterClass?.isArray ?: false) && + (arguments.singleOrNull()?.type?.isString ?: false) + +private val FunctionDescriptor.hasSingleArrayOfStringParameter: Boolean + get() = valueParameters.singleOrNull()?.type?.isArrayOfString ?: false + diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index f1ae904ac35..eb0e4794873 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -334,6 +334,7 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid module.acceptChildrenVoid(this) appendLlvmUsed(context.llvm.usedFunctions) appendStaticInitializers(context.llvm.staticInitializers) + appendEntryPointSelector(findMainEntryPoint(context)) } //-------------------------------------------------------------------------// @@ -2096,6 +2097,44 @@ internal class CodeGeneratorVisitor(val context: Context) : IrElementVisitorVoid } } + //-------------------------------------------------------------------------// + + fun entryPointSelector(entryPoint: LLVMValueRef, + entryPointType: LLVMTypeRef, selectorName: String): LLVMValueRef { + + assert(LLVMCountParams(entryPoint) == 1) + + val selector = LLVMAddFunction(context.llvmModule, selectorName, entryPointType)!! + codegen.prologue(selector, voidType) + + // Note, that 'parameter' is an object reference, and as such, shall + // be accounted for in the rootset. However, current object management + // scheme for arguments guarantees, that reference is being held in C++ + // launcher, so we could optimize out creating slot for 'parameter' in + // this function. + val parameter = LLVMGetParam(selector, 0)!! + codegen.callAtFunctionScope(entryPoint, listOf(parameter), Lifetime.IRRELEVANT) + + codegen.ret(null) + codegen.epilogue() + return selector + } + + //-------------------------------------------------------------------------// + + fun appendEntryPointSelector(descriptor: FunctionDescriptor?) { + if (descriptor == null) return + + val entryPoint = codegen.llvmFunction(descriptor) + val selectorName = "EntryPointSelector" + val entryPointType = getFunctionType(entryPoint)!! + + val selector = entryPointSelector(entryPoint, entryPointType, selectorName) + + LLVMSetLinkage(selector, LLVMLinkage.LLVMExternalLinkage) + } + + //-------------------------------------------------------------------------// // Create type { i32, void ()*, i8* } diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index eb95e0d72bb..0c675da553a 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -371,6 +371,25 @@ task hello4(type: RunKonanTest) { source = "runtime/basic/hello4.kt" } +task entry0(type: RunKonanTest) { + goldValue = "Hello.\n" + source = "runtime/basic/entry0.kt" + flags = ["-entry", "koko.lala.main"] +} + +task entry1(type: RunKonanTest) { + goldValue = "Hello.\n" + source = "runtime/basic/entry1.kt" + flags = ["-entry", "foo"] +} + +task entry2(type: LinkKonanTest) { + goldValue = "Hello.\n" + source = "runtime/basic/entry2.kt" + lib = "runtime/basic/libentry2.kt" + flags = ["-entry", "foo"] +} + task tostring0(type: RunKonanTest) { goldValue = "127\n-1\n239\nA\nЁ\nト\n1122334455\n112233445566778899\n3.14159265358\n1.0E27\n1.0E-300\ntrue\nfalse\n" source = "runtime/basic/tostring0.kt" diff --git a/backend.native/tests/runtime/basic/entry0.kt b/backend.native/tests/runtime/basic/entry0.kt new file mode 100644 index 00000000000..3e0535a9925 --- /dev/null +++ b/backend.native/tests/runtime/basic/entry0.kt @@ -0,0 +1,32 @@ +package koko.lala + +fun fail() { + println("Test failed, this is a wrong main() function.") +} + +fun main() { + fail() +} + +fun main(args: Array) { + fail() +} + +fun main(args: Array) { + fail() +} + +fun main(args: Array, second_arg: Int) { + fail() +} + +class Foo { + fun main(args: Array) { + fail() + } +} + +fun main(args: Array) { + println("Hello.") +} + diff --git a/backend.native/tests/runtime/basic/entry1.kt b/backend.native/tests/runtime/basic/entry1.kt new file mode 100644 index 00000000000..30878af591a --- /dev/null +++ b/backend.native/tests/runtime/basic/entry1.kt @@ -0,0 +1,13 @@ +fun fail() { + println("Test failed, this is a wrong main() function.") +} + +fun foo(args: Array) { + println("Hello.") +} + + +fun main(args: Array) { + fail() +} + diff --git a/backend.native/tests/runtime/basic/entry2.kt b/backend.native/tests/runtime/basic/entry2.kt new file mode 100644 index 00000000000..ce15f37213d --- /dev/null +++ b/backend.native/tests/runtime/basic/entry2.kt @@ -0,0 +1,4 @@ +fun main(args: Array) { + fail() +} + diff --git a/backend.native/tests/runtime/basic/libentry2.kt b/backend.native/tests/runtime/basic/libentry2.kt new file mode 100644 index 00000000000..b9d0e5ae40e --- /dev/null +++ b/backend.native/tests/runtime/basic/libentry2.kt @@ -0,0 +1,8 @@ +fun fail() { + println("Test failed, this is a wrong main() function.") +} + +fun foo(args: Array) { + println("Hello.") +} + diff --git a/runtime/src/launcher/kotlin/konan/start.kt b/runtime/src/launcher/kotlin/konan/start.kt index d61fb04d097..9bf2eab0873 100644 --- a/runtime/src/launcher/kotlin/konan/start.kt +++ b/runtime/src/launcher/kotlin/konan/start.kt @@ -16,14 +16,18 @@ import konan.internal.ExportForCppRuntime -external fun main(args: Array) +// This function is produced by the code generator given +// the '-entry foo.bar.main' flag. +// It calls the requested entry point. +// The default is main(Array):Unit in the root package. +@ExportForCppRuntime +external fun EntryPointSelector(args: Array) @ExportForCppRuntime private fun Konan_start(args: Array): Int { try { - // This is kotlin program main entry point - main(args) + EntryPointSelector(args) // Successfully finished: return 0