Compile the C Adaptor with clang and link it to the final binary.

This commit is contained in:
Alexander Gorshenev
2017-12-04 12:28:51 +03:00
committed by alexander-gorshenev
parent 5116d43f4f
commit 63aff3eded
11 changed files with 185 additions and 29 deletions
@@ -0,0 +1,30 @@
/*
* 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
import org.jetbrains.kotlin.konan.exec.*
import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.konan.file.*
fun produceCAdapterBitcode(clang: TargetClang, headerFileName: String, cppFileName: String, bitcodeFileName: String) {
val headerDirName = File(headerFileName).absoluteFile.parent
val clangCommand = clang.clangCXX("-std=c++11", cppFileName, "-I", headerDirName, "-emit-llvm", "-c", "-o", bitcodeFileName)
runTool(clangCommand)
}
@@ -32,7 +32,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.konan.file.File import org.jetbrains.kotlin.konan.file.*
private enum class ScopeKind { private enum class ScopeKind {
TOP, TOP,
@@ -426,7 +426,10 @@ internal class CAdapterGenerator(val context: Context,
} }
private fun makeGlobalStruct(top: ExportedElementScope) { private fun makeGlobalStruct(top: ExportedElementScope) {
outputStreamWriter = PrintWriter(File(".", "${prefix}_api.h").outputStream()) outputStreamWriter = context.config.tempFiles
.cAdapterHeader
.printWriter()
output("#ifndef KONAN_${prefix.toUpperCase()}_H") output("#ifndef KONAN_${prefix.toUpperCase()}_H")
output("#define KONAN_${prefix.toUpperCase()}_H") output("#define KONAN_${prefix.toUpperCase()}_H")
// TODO: use namespace for C++ case? // TODO: use namespace for C++ case?
@@ -472,7 +475,10 @@ internal class CAdapterGenerator(val context: Context,
outputStreamWriter.close() outputStreamWriter.close()
println("Produced dynamic library API in ${prefix}_api.h") println("Produced dynamic library API in ${prefix}_api.h")
outputStreamWriter = PrintWriter(File(".", "${prefix}_api.cpp").outputStream()) outputStreamWriter = context.config.tempFiles
.cAdapterCpp
.printWriter()
output("#include \"${prefix}_api.h\"") output("#include \"${prefix}_api.h\"")
output(""" output("""
|struct KObjHeader; |struct KObjHeader;
@@ -31,18 +31,31 @@ internal fun produceOutput(context: Context) {
val llvmModule = context.llvmModule!! val llvmModule = context.llvmModule!!
val config = context.config.configuration val config = context.config.configuration
val tempFiles = context.config.tempFiles
val produce = config.get(KonanConfigKeys.PRODUCE)
when (config.get(KonanConfigKeys.PRODUCE)) { when (produce) {
CompilerOutputKind.DYNAMIC, CompilerOutputKind.DYNAMIC,
CompilerOutputKind.FRAMEWORK, CompilerOutputKind.FRAMEWORK,
CompilerOutputKind.PROGRAM -> { CompilerOutputKind.PROGRAM -> {
val program = context.config.outputName val program = context.config.outputName
val output = "$program.kt.bc" val output = tempFiles.nativeBinaryFileName
context.bitcodeFileName = output context.bitcodeFileName = output
val generatedBitcodeFiles =
if (produce == CompilerOutputKind.DYNAMIC) {
produceCAdapterBitcode(
context.config.clang,
tempFiles.cAdapterHeaderName,
tempFiles.cAdapterCppName,
tempFiles.cAdapterBitcodeName)
listOf(tempFiles.cAdapterBitcodeName)
} else emptyList()
val nativeLibraries = val nativeLibraries =
context.config.nativeLibraries + context.config.nativeLibraries +
context.config.defaultNativeLibraries context.config.defaultNativeLibraries +
generatedBitcodeFiles
PhaseManager(context).phase(KonanPhase.BITCODE_LINKER) { PhaseManager(context).phase(KonanPhase.BITCODE_LINKER) {
for (library in nativeLibraries) { for (library in nativeLibraries) {
@@ -21,6 +21,11 @@ package org.jetbrains.kotlin.backend.konan
*/ */
open class KonanException(message: String = "", cause: Throwable? = null) : Exception(message, cause) open class KonanException(message: String = "", cause: Throwable? = null) : Exception(message, cause)
/**
* An error occured during external tool invocation. Such as non-zero exit code.
*/
class KonanExternalToolFailure(message: String = "", cause: Throwable? = null) : KonanException(message, cause)
/** /**
* Represents a compilation error caused by mistakes in an input file, e.g. undefined reference. * Represents a compilation error caused by mistakes in an input file, e.g. undefined reference.
*/ */
@@ -31,8 +36,3 @@ class KonanCompilationException(message: String = "", cause: Throwable? = null)
*/ */
class KonanIrDeserializationException(message: String = "", cause: Throwable? = null) : KonanException(message, cause) class KonanIrDeserializationException(message: String = "", cause: Throwable? = null) : KonanException(message, cause)
/**
* An error occured during external tool invocation. Such as non-zero exit code.
*/
class KonanExternalToolFailure(message: String = "", cause: Throwable? = null) : KonanException(message, cause)
@@ -20,9 +20,7 @@ import com.intellij.openapi.project.Project
import org.jetbrains.kotlin.backend.konan.descriptors.createForwardDeclarationsModule import org.jetbrains.kotlin.backend.konan.descriptors.createForwardDeclarationsModule
import org.jetbrains.kotlin.backend.konan.library.* import org.jetbrains.kotlin.backend.konan.library.*
import org.jetbrains.kotlin.backend.konan.library.impl.* import org.jetbrains.kotlin.backend.konan.library.impl.*
import org.jetbrains.kotlin.backend.konan.util.profile import org.jetbrains.kotlin.backend.konan.util.*
import org.jetbrains.kotlin.backend.konan.util.removeSuffixIfPresent
import org.jetbrains.kotlin.backend.konan.util.suffixIfNot
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
@@ -44,15 +42,16 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
internal val targetManager = TargetManager( internal val targetManager = TargetManager(
configuration.get(KonanConfigKeys.TARGET)) configuration.get(KonanConfigKeys.TARGET))
val indirectBranchesAreAllowed = targetManager.target != KonanTarget.WASM32 private val target = targetManager.target
init { init {
val target = targetManager.target
if (!target.enabled) { if (!target.enabled) {
error("Target $target is not available on the ${TargetManager.host} host") error("Target $target is not available on the ${TargetManager.host} host")
} }
} }
val indirectBranchesAreAllowed = target != KonanTarget.WASM32
private fun Distribution.prepareDependencies(checkDependencies: Boolean) { private fun Distribution.prepareDependencies(checkDependencies: Boolean) {
if (checkDependencies) { if (checkDependencies) {
DependencyProcessor(java.io.File(dependenciesDir), targetProperties).run() DependencyProcessor(java.io.File(dependenciesDir), targetProperties).run()
@@ -65,10 +64,17 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
prepareDependencies(configuration.getBoolean(KonanConfigKeys.CHECK_DEPENDENCIES)) prepareDependencies(configuration.getBoolean(KonanConfigKeys.CHECK_DEPENDENCIES))
} }
internal val clang = TargetClang(distribution.properties, distribution.dependenciesDir, target)
internal val produce get() = configuration.get(KonanConfigKeys.PRODUCE)!! internal val produce get() = configuration.get(KonanConfigKeys.PRODUCE)!!
private val suffix = produce.suffix(targetManager.target) private val prefix = produce.prefix(target)
private val suffix = produce.suffix(target)
val outputName = configuration.get(KonanConfigKeys.OUTPUT)?.removeSuffixIfPresent(suffix) ?: produce.name.toLowerCase() val outputName = configuration.get(KonanConfigKeys.OUTPUT)?.removeSuffixIfPresent(suffix) ?: produce.name.toLowerCase()
val outputFile = outputName.suffixIfNot(produce.suffix(targetManager.target)) val outputFile = outputName
.prefixIfNot(prefix)
.suffixIfNot(suffix)
val tempFiles = TempFiles(outputName)
val moduleId: String val moduleId: String
get() = configuration.get(KonanConfigKeys.MODULE_NAME) ?: File(outputName).name get() = configuration.get(KonanConfigKeys.MODULE_NAME) ?: File(outputName).name
@@ -85,13 +91,13 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
internal val immediateLibraries: List<LibraryReaderImpl> by lazy { internal val immediateLibraries: List<LibraryReaderImpl> by lazy {
val result = resolver.resolveImmediateLibraries( val result = resolver.resolveImmediateLibraries(
libraryNames, libraryNames,
targetManager.target, target,
currentAbiVersion, currentAbiVersion,
configuration.getBoolean(KonanConfigKeys.NOSTDLIB), configuration.getBoolean(KonanConfigKeys.NOSTDLIB),
configuration.getBoolean(KonanConfigKeys.NODEFAULTLIBS), configuration.getBoolean(KonanConfigKeys.NODEFAULTLIBS),
{ msg -> configuration.report(STRONG_WARNING, msg) } { msg -> configuration.report(STRONG_WARNING, msg) }
) )
resolver.resolveLibrariesRecursive(result, targetManager.target, currentAbiVersion) resolver.resolveLibrariesRecursive(result, target, currentAbiVersion)
result result
} }
@@ -0,0 +1,32 @@
/*
* 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
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.file.*
class TempFiles(val outputName: String) {
val nativeBinaryFile by lazy { File("${outputName}.kt.bc") }
val cAdapterHeader by lazy { File("${outputName}_api.h") }
val cAdapterCpp by lazy { createTempFile("api", ".cpp").deleteOnExit() }
val cAdapterBitcode by lazy { createTempFile("api", ".bc").deleteOnExit() }
val nativeBinaryFileName get() = nativeBinaryFile.absolutePath
val cAdapterHeaderName get() = cAdapterHeader.absolutePath
val cAdapterCppName get() = cAdapterCpp.absolutePath
val cAdapterBitcodeName get() = cAdapterBitcode.absolutePath
}
@@ -36,6 +36,9 @@ fun nTabs(amount: Int): String {
return String.format("%1$-${(amount+1)*4}s", "") return String.format("%1$-${(amount+1)*4}s", "")
} }
fun String.prefixIfNot(prefix: String) =
if (this.startsWith(prefix)) this else "$prefix$this"
fun String.suffixIfNot(suffix: String) = fun String.suffixIfNot(suffix: String) =
if (this.endsWith(suffix)) this else "$this$suffix" if (this.endsWith(suffix)) this else "$this$suffix"
@@ -0,0 +1,46 @@
/*
* 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.konan.exec
import java.lang.ProcessBuilder
import java.lang.ProcessBuilder.Redirect
fun executeCommand(command: List<String>) =
executeCommand(*command.toTypedArray())
fun executeCommand(vararg command: String): Int {
// TODO: need a verbose logger here.
val builder = ProcessBuilder(command.asList())
builder.redirectOutput(Redirect.INHERIT)
builder.redirectInput(Redirect.INHERIT)
builder.redirectError(Redirect.INHERIT)
val process = builder.start()
val exitCode = process.waitFor()
return exitCode
}
fun runTool(command: List<String>) =
runTool(*command.toTypedArray())
fun runTool(vararg command: String) {
val code = executeCommand(*command)
if (code != 0) error("The ${command[0]} command returned non-zero exit code: $code.")
}
@@ -103,10 +103,11 @@ data class File constructor(internal val javaPath: Path) {
}) })
} }
fun deleteOnExit() { fun deleteOnExit(): File {
// Works only on the default file system, // Works only on the default file system,
// but that's okay for now. // but that's okay for now.
javaPath.toFile().deleteOnExit() javaPath.toFile().deleteOnExit()
return this // Allow streaming.
} }
fun readBytes() = Files.readAllBytes(javaPath) fun readBytes() = Files.readAllBytes(javaPath)
fun writeBytes(bytes: ByteArray) = Files.write(javaPath, bytes) fun writeBytes(bytes: ByteArray) = Files.write(javaPath, bytes)
@@ -136,6 +137,7 @@ data class File constructor(internal val javaPath: Path) {
// TODO: Consider removeing these after konanazing java.util.Properties. // TODO: Consider removeing these after konanazing java.util.Properties.
fun bufferedReader() = Files.newBufferedReader(javaPath) fun bufferedReader() = Files.newBufferedReader(javaPath)
fun outputStream() = Files.newOutputStream(javaPath) fun outputStream() = Files.newOutputStream(javaPath)
fun printWriter() = javaPath.toFile().printWriter()
companion object { companion object {
val userDir val userDir
@@ -39,5 +39,21 @@ class ClangManager(val properties: Properties, val baseDir: String) {
= (hostClang.commonClangArgs + targetClangArgs[target]!!.specificClangArgs).toTypedArray() = (hostClang.commonClangArgs + targetClangArgs[target]!!.specificClangArgs).toTypedArray()
val hostCompilerArgsForJni = hostClang.hostCompilerArgsForJni.toTypedArray() val hostCompilerArgsForJni = hostClang.hostCompilerArgsForJni.toTypedArray()
fun targetClangCmd(target: KonanTarget)
= listOf("${hostClang.llvmDir}/bin/clang") + targetClangArgs(target)
fun targetClangXXCmd(target: KonanTarget)
= listOf("${hostClang.llvmDir}/bin/clang++") + targetClangArgs(target)
} }
class TargetClang(private val clangManager: ClangManager, private val target: KonanTarget) {
constructor (properties: Properties, baseDir: String, target: KonanTarget)
: this (ClangManager(properties, baseDir), target)
fun clangC(vararg userArgs: String) = clangManager.targetClangCmd(target) + userArgs.asList()
fun clangCXX(vararg userArgs: String) = clangManager.targetClangXXCmd(target) + userArgs.asList()
}
@@ -16,13 +16,13 @@
package org.jetbrains.kotlin.konan.target package org.jetbrains.kotlin.konan.target
enum class Family(name:String, val exeSuffix:String, val dynamicSuffix: String?) { enum class Family(name:String, val exeSuffix:String, val dynamicPrefix: String, val dynamicSuffix: String) {
OSX("osx", "kexe", "dylib"), OSX( "osx" , "kexe", "lib", "dylib"),
IOS("ios", "kexe", "dylib"), IOS( "ios" , "kexe", "lib", "dylib"),
LINUX("linux", "kexe", "so"), LINUX( "linux" , "kexe", "lib", "so" ),
WINDOWS("windows", "exe", "dll"), WINDOWS("windows", "exe" , "" , "dll" ),
ANDROID("android", "so", "so"), ANDROID("android", "so" , "lib", "so" ),
WASM("wasm", "wasm", "wasm") WASM( "wasm" , "wasm", "" , "wasm" )
} }
enum class Architecture(val bitness: Int) { enum class Architecture(val bitness: Int) {
@@ -62,6 +62,7 @@ enum class CompilerOutputKind {
}, },
DYNAMIC { DYNAMIC {
override fun suffix(target: KonanTarget?) = ".${target!!.family.dynamicSuffix}" override fun suffix(target: KonanTarget?) = ".${target!!.family.dynamicSuffix}"
override fun prefix(target: KonanTarget?) = ".${target!!.family.dynamicPrefix}"
}, },
FRAMEWORK { FRAMEWORK {
override fun suffix(target: KonanTarget?): String = ".framework" override fun suffix(target: KonanTarget?): String = ".framework"
@@ -74,6 +75,7 @@ enum class CompilerOutputKind {
}; };
abstract fun suffix(target: KonanTarget? = null): String abstract fun suffix(target: KonanTarget? = null): String
open fun prefix(target: KonanTarget? = null): String = ""
} }
class TargetManager(val userRequest: String? = null) { class TargetManager(val userRequest: String? = null) {