[MERGE] Kotlin/Native history merged into kotlin/master

This commit is contained in:
Stanislav Erokhin
2021-02-26 15:30:58 +01:00
3031 changed files with 282024 additions and 32 deletions
+2
View File
@@ -62,3 +62,5 @@ local.properties
buildSrcTmp/
distTmp/
outTmp/
/test.output
/kotlin-native/dist
+8
View File
@@ -0,0 +1,8 @@
<component name="ArtifactManager">
<artifact type="jar" name="kotlinx.cli-jvm-1.5.255-SNAPSHOT">
<output-path>$PROJECT_DIR$/kotlin-native/endorsedLibraries/kotlinx.cli/build/libs</output-path>
<root id="archive" name="kotlinx.cli-jvm-1.5.255-SNAPSHOT.jar">
<element id="module-output" name="kotlin.kotlin-native.endorsedLibraries.kotlinx.cli.jvmMain" />
</root>
</artifact>
</component>
+4
View File
@@ -12,10 +12,14 @@
<item index="2" class="java.lang.String" itemvalue="org.gradle.api.tasks.options.Option" />
</list>
</component>
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="FrameworkDetectionExcludesConfiguration">
<file type="web" url="file://$PROJECT_DIR$" />
</component>
<component name="IdProvider" IDEtalkID="71A301FF1940049D6D82F12C40F1E1D5" />
<component name="JavaScriptSettings">
<option name="languageLevel" value="ES6" />
</component>
<component name="JavadocGenerationManager">
<option name="OTHER_OPTIONS" value="" />
</component>
+5 -1
View File
@@ -27,7 +27,7 @@ buildscript {
dependencies {
bootstrapCompilerClasspath(kotlin("compiler-embeddable", bootstrapKotlinVersion))
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.21")
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.25")
classpath(kotlin("gradle-plugin", bootstrapKotlinVersion))
classpath(kotlin("serialization", bootstrapKotlinVersion))
classpath("org.jetbrains.dokka:dokka-gradle-plugin:0.9.17")
@@ -115,6 +115,10 @@ extra["ideaUltimateSandboxDir"] = project.file(ideaUltimateSandboxDir)
extra["ideaPluginDir"] = project.file(ideaPluginDir)
extra["ideaUltimatePluginDir"] = project.file(ideaUltimatePluginDir)
extra["isSonatypeRelease"] = false
val kotlinNativeVersionObject = project.kotlinNativeVersionValue()
subprojects {
extra["kotlinNativeVersion"] = kotlinNativeVersionObject
}
// Work-around necessary to avoid setting null javaHome. Will be removed after support of lazy task configuration
val jdkNotFoundConst = "JDK NOT FOUND"
+126 -5
View File
@@ -24,14 +24,12 @@ buildscript {
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.21")
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.25")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.bootstrapKotlinVersion}")
classpath("org.jetbrains.kotlin:kotlin-sam-with-receiver:${project.bootstrapKotlinVersion}")
}
}
val cacheRedirectorEnabled = findProperty("cacheRedirectorEnabled")?.toString()?.toBoolean() == true
logger.info("buildSrcKotlinVersion: " + extra["bootstrapKotlinVersion"])
logger.info("buildSrc kotlin compiler version: " + org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION)
logger.info("buildSrc stdlib version: " + KotlinVersion.CURRENT)
@@ -39,6 +37,7 @@ logger.info("buildSrc stdlib version: " + KotlinVersion.CURRENT)
apply {
plugin("kotlin")
plugin("kotlin-sam-with-receiver")
plugin("groovy")
from("../gradle/checkCacheability.gradle.kts")
}
@@ -81,12 +80,14 @@ extra["intellijReleaseType"] = when {
else -> "releases"
}
extra["versions.androidDxSources"] = "5.0.0_r2"
extra["customDepsOrg"] = "kotlin.build"
repositories {
jcenter()
maven("https://jetbrains.bintray.com/intellij-third-party-dependencies/")
maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/kotlin-dependencies")
maven("https://kotlin.bintray.com/kotlinx")
gradlePluginPortal()
extra["bootstrapKotlinRepo"]?.let {
@@ -94,10 +95,59 @@ repositories {
}
}
val generateCompilerVersion by tasks.registering(VersionGenerator::class) {
kotlinNativeVersionInResources=true
defaultVersionFileLocation()
}
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile> {
dependsOn(generateCompilerVersion)
}
tasks.clean {
doFirst {
val versionSourceDirectory = project.konanVersionGeneratedSrc()
if (versionSourceDirectory.exists()) {
versionSourceDirectory.delete()
}
}
}
sourceSets["main"].withConvention(org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet::class) {
kotlin.srcDir("src/main/kotlin")
if (!kotlinBuildProperties.isInJpsBuildIdeaSync) {
kotlin.srcDir("../kotlin-native/shared/src/library/kotlin")
kotlin.srcDir("../kotlin-native/shared/src/main/kotlin")
kotlin.srcDir("../kotlin-native/build-tools/src/main/kotlin")
kotlin.srcDir("../kotlin-native/tools/kotlin-native-gradle-plugin/src/main/kotlin")
kotlin.srcDir("../compiler/util-klib/src")
kotlin.srcDir("../native/utils/src")
}
kotlin.srcDir(project.kotlinNativeVersionSrc())
/**
* TODO: mentioned bellow and Co it'd be better to move to :kotlin-native:performance:buildSrc,
* because all this relates to benchmarking.
*/
kotlin.exclude("**/benchmark/*.kt")
kotlin.exclude("**/kotlin/MPPTools.kt")
kotlin.exclude("**/kotlin/RegressionsReporter.kt")
kotlin.exclude("**/kotlin/RunJvmTask.kt")
kotlin.exclude("**/kotlin/RunKotlinNativeTask.kt")
kotlin.exclude("**/kotlin/BuildRegister.kt")
kotlin.exclude("**/kotlin/benchmarkUtils.kt")
}
tasks.validatePlugins.configure {
enabled = false
}
java {
disableAutoTargetJvm()
}
dependencies {
implementation(kotlin("stdlib", embeddedKotlinVersion))
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${project.bootstrapKotlinVersion}")
implementation("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.21")
implementation("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.25")
implementation("com.gradle.publish:plugin-publish-plugin:0.12.0")
implementation("net.rubygrapefruit:native-platform:${property("versions.native-platform")}")
@@ -113,6 +163,27 @@ dependencies {
implementation("org.gradle:test-retry-gradle-plugin:1.1.9")
implementation("com.gradle.enterprise:test-distribution-gradle-plugin:1.2.1")
compileOnly(gradleApi())
val kotlinVersion = project.bootstrapKotlinVersion
val ktorVersion = "1.2.1"
val slackApiVersion = "1.2.0"
val metadataVersion = "0.0.1-dev-10"
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
implementation("org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion")
implementation("com.ullink.slack:simpleslackapi:$slackApiVersion")
implementation("io.ktor:ktor-client-auth:$ktorVersion")
implementation("io.ktor:ktor-client-core:$ktorVersion")
implementation("io.ktor:ktor-client-cio:$ktorVersion")
implementation("org.jetbrains.kotlinx:kotlinx-metadata-klib:$metadataVersion")
if (kotlinBuildProperties.isInJpsBuildIdeaSync) {
implementation("org.jetbrains.kotlin:kotlin-native-utils:${project.bootstrapKotlinVersion}")
}
}
samWithReceiver {
@@ -129,11 +200,24 @@ java {
tasks.withType<KotlinCompile>().configureEach {
kotlinOptions.freeCompilerArgs += listOf(
"-Xopt-in=kotlin.RequiresOptIn", "-Xskip-runtime-version-check", "-Xsuppress-version-warnings"
"-Xopt-in=kotlin.RequiresOptIn",
"-Xskip-runtime-version-check",
"-Xsuppress-version-warnings",
"-Xopt-in=kotlin.ExperimentalStdlibApi"
)
}
tasks["build"].dependsOn(":prepare-deps:build")
sourceSets["main"].withConvention(org.gradle.api.tasks.GroovySourceSet::class) {
if (!kotlinBuildProperties.isInJpsBuildIdeaSync) {
groovy.srcDir("../kotlin-native/build-tools/src/main/groovy")
}
}
tasks.named("compileGroovy", GroovyCompile::class.java) {
classpath += project.files(tasks.named("compileKotlin", org.jetbrains.kotlin.gradle.tasks.KotlinCompile::class.java))
dependsOn(tasks.named("compileKotlin"))
}
allprojects {
tasks.register("checkBuild")
@@ -142,3 +226,40 @@ allprojects {
apply(from = "$rootDir/../gradle/cacheRedirector.gradle.kts")
}
}
gradlePlugin {
plugins {
create("compileToBitcode") {
id = "compile-to-bitcode"
implementationClass = "org.jetbrains.kotlin.bitcode.CompileToBitcodePlugin"
}
create("runtimeTesting") {
id = "runtime-testing"
implementationClass = "org.jetbrains.kotlin.testing.native.RuntimeTestingPlugin"
}
create("konan") {
id = "konan"
implementationClass = "org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin"
}
// We bundle a shaded version of kotlinx-serialization plugin
create("kotlinx-serialization-native") {
id = "kotlinx-serialization-native"
implementationClass = "shadow.org.jetbrains.kotlinx.serialization.gradle.SerializationGradleSubplugin"
}
create("org.jetbrains.kotlin.konan") {
id = "org.jetbrains.kotlin.konan"
implementationClass = "org.jetbrains.kotlin.gradle.plugin.konan.KonanPlugin"
}
create("native") {
id = "native"
implementationClass = "org.jetbrains.gradle.plugins.tools.NativePlugin"
}
create("native-interop-plugin") {
id = "native-interop-plugin"
implementationClass = "org.jetbrains.kotlin.NativeInteropPlugin"
}
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ buildscript {
}
}
dependencies {
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.21")
classpath("org.jetbrains.kotlin:kotlin-build-gradle-plugin:0.0.25")
}
}
@@ -0,0 +1,17 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.konan
import java.io.*
val VERSION_PATH = "/META-INF/kotlin-native.compiler.version"
val CompilerVersion.Companion.CURRENT: CompilerVersion
get() {
return InputStreamReader(this::class.java.getResourceAsStream(VERSION_PATH)).use {
it.readLines().single().parseCompilerVersion()
}
}
val currentCompilerVersion = CompilerVersion.CURRENT
+2 -2
View File
@@ -105,8 +105,8 @@ private val macOsJavaHomeOutRegexes =
listOf(
Regex("""\s+(\S+),\s+(\S+):\s+".*?"\s+(.+)"""),
Regex("""\s+(\S+)\s+\((.*?)\):\s+(.+)"""),
Regex("""\s+(\S+)\s+\((.*?)\)\s+"[^"]*"\s+-\s+"[^"]*"\s(.+)""")
)
Regex("""\s+(\S+)\s+\((.*?)\)\s+"[^"]*"\s+-\s+"[^"]*"\s(.+)"""),
Regex("""\s+(\S+)\s+\((.+)\)\s+".+"\s+-\s+".+"\s+(.+)"""))
fun MutableCollection<JdkId>.discoverJdksOnMacOS(project: Project) {
val procBuilder = ProcessBuilder("/usr/libexec/java_home", "-V").redirectErrorStream(true)
@@ -0,0 +1,228 @@
/*
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.gradle.plugins.tools
import org.gradle.api.DefaultTask
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.file.FileCollection
import org.gradle.api.plugins.BasePlugin
import org.gradle.api.tasks.*
import org.gradle.kotlin.dsl.apply
import org.gradle.kotlin.dsl.withType
import org.gradle.language.base.plugins.LifecycleBasePlugin
import org.jetbrains.kotlin.konan.target.HostManager.Companion.hostIsMac
import org.jetbrains.kotlin.konan.target.HostManager.Companion.hostIsMingw
import java.io.File
import kotlin.collections.List
import kotlin.collections.MutableMap
import kotlin.collections.addAll
import kotlin.collections.drop
import kotlin.collections.first
import kotlin.collections.flatMap
import kotlin.collections.forEach
import kotlin.collections.listOf
import kotlin.collections.map
import kotlin.collections.mutableListOf
import kotlin.collections.mutableMapOf
import kotlin.collections.plusAssign
import kotlin.collections.set
import kotlin.collections.toTypedArray
open class NativePlugin : Plugin<Project> {
override fun apply(project: Project) {
project.apply<BasePlugin>()
project.extensions.create("native", NativeToolsExtension::class.java, project)
}
}
abstract class ToolExecutionTask : DefaultTask() {
@get:OutputFile
abstract var output: File
@get:InputFiles
abstract var input: List<File>
@get:Input
abstract var cmd: String
@get:Input
abstract var args: List<String>
@TaskAction
fun action() {
project.exec {
executable(cmd)
args(*this@ToolExecutionTask.args.toTypedArray())
}
}
}
class ToolPatternImpl(val extension: NativeToolsExtension, val output:String, vararg val input: String):ToolPattern {
val tool = mutableListOf<String>()
val args = mutableListOf<String>()
override fun ruleOut(): String = output
override fun ruleInFirst(): String = input.first()
override fun ruleInAll(): Array<String> = arrayOf(*input)
override fun flags(vararg args: String) {
this.args.addAll(args)
}
override fun tool(vararg arg: String) {
tool.addAll(arg)
}
override fun env(name: String) = emptyArray<String>()
fun configure(task: ToolExecutionTask, configureDepencies:Boolean) {
extension.cleanupfiles += output
task.input = input.map {
extension.project.file(it)
}
task.dependsOn(":kotlin-native:dependencies:update")
if (configureDepencies)
task.input.forEach { task.dependsOn(it.name) }
val file = extension.project.file(output)
file.parentFile.mkdirs()
task.output = file
task.cmd = tool.first()
task.args = listOf(*tool.drop(1).toTypedArray(), *args.toTypedArray())
}
}
open class SourceSet(
val sourceSets: SourceSets,
val name: String,
val initialDirectory: File = sourceSets.project.projectDir,
val initialSourceSet: SourceSet? = null,
val rule: Pair<String, String>? = null
) {
var collection = sourceSets.project.objects.fileCollection() as FileCollection
fun file(path: String) {
collection = collection.plus(sourceSets.project.files("${initialDirectory.absolutePath}/$path"))
}
fun dir(path: String) {
sourceSets.project.fileTree("${initialDirectory.absolutePath}/$path").files.forEach {
collection = collection.plus(sourceSets.project.files(it))
}
}
fun transform(suffixes: Pair<String, String>): SourceSet {
return SourceSet(
sourceSets,
name,
sourceSets.project.file("${sourceSets.project.buildDir}/$name/${suffixes.first}_${suffixes.second}/"),
this,
suffixes
)
}
fun implicitTasks(): Array<TaskProvider<*>> {
rule ?: return emptyArray()
initialSourceSet?.implicitTasks()
return initialSourceSet!!.collection
.filter { !it.isDirectory() }
.filter { it.name.endsWith(rule.first) }
.map { it.relativeTo(initialSourceSet.initialDirectory) }
.map { it.path }
.map { it to (it.substring(0, it.lastIndexOf(rule.first)) + rule.second) }
.map {
file(it.second)
sourceSets.project.file("${initialSourceSet.initialDirectory.path}/${it.first}") to sourceSets.project.file("${initialDirectory.path}/${it.second}")
}.map {
sourceSets.project.tasks.register<ToolExecutionTask>(it.second.name, ToolExecutionTask::class.java) {
val toolConfiguration = ToolPatternImpl(sourceSets.extension, it.second.path, it.first.path)
sourceSets.extension.toolPatterns[rule]!!.invoke(toolConfiguration)
toolConfiguration.configure(this, initialSourceSet.rule != null)
}
}.toTypedArray()
}
}
class SourceSets(val project: Project, val extension: NativeToolsExtension, val sources: MutableMap<String, SourceSet>) :
MutableMap<String, SourceSet> by sources {
operator fun String.invoke(initialDirectory: File = project.projectDir, configuration: SourceSet.() -> Unit) {
sources[this] = SourceSet(this@SourceSets, this, initialDirectory).also {
configuration(it)
}
}
}
interface Environment {
operator fun String.invoke(vararg values: String)
}
interface ToolPattern {
fun ruleOut(): String
fun ruleInFirst(): String
fun ruleInAll(): Array<String>
fun flags(vararg args: String): Unit
fun tool(vararg arg: String): Unit
fun env(name: String): Array<String>
}
typealias ToolPatternConfiguration = ToolPattern.() -> Unit
typealias EnvironmentConfiguration = Environment.() -> Unit
class ToolConfigurationPatterns(
val extension: NativeToolsExtension,
val patterns: MutableMap<Pair<String, String>, ToolPatternConfiguration>
) : MutableMap<Pair<String, String>, ToolPatternConfiguration> by patterns {
operator fun Pair<String, String>.invoke(configuration: ToolPatternConfiguration) {
patterns[this] = configuration
}
}
open class NativeToolsExtension(val project: Project) {
val sourceSets = SourceSets(project, this, mutableMapOf<String, SourceSet>())
val toolPatterns = ToolConfigurationPatterns(this, mutableMapOf<Pair<String, String>, ToolPatternConfiguration>())
val cleanupfiles = mutableListOf<String>()
fun sourceSet(configuration: SourceSets.() -> Unit) {
sourceSets.configuration()
}
var environmentConfiguration: EnvironmentConfiguration? = null
fun environment(configuration: EnvironmentConfiguration) {
environmentConfiguration = configuration
}
fun suffixes(configuration: ToolConfigurationPatterns.() -> Unit) = toolPatterns.configuration()
fun target(name: String, vararg objSet: SourceSet, configuration: ToolPatternConfiguration) {
project.tasks.withType<Delete>().named(LifecycleBasePlugin.CLEAN_TASK_NAME).configure {
doLast {
delete(*this@NativeToolsExtension.cleanupfiles.toTypedArray())
}
}
sourceSets.project.tasks.create<ToolExecutionTask>(name, ToolExecutionTask::class.java) {
objSet.forEach {
dependsOn(it.implicitTasks())
}
val deps = objSet.flatMap { it.collection.files }.map { it.path }
val toolConfiguration = ToolPatternImpl(sourceSets.extension, "${project.buildDir.path}/$name", *deps.toTypedArray())
toolConfiguration.configuration()
toolConfiguration.configure(this, false )
}
}
}
fun solib(name: String) = when {
hostIsMingw -> "$name.dll"
hostIsMac -> "lib$name.dylib"
else -> "lib$name.so"
}
fun lib(name:String) = when {
hostIsMingw -> "$name.lib"
else -> "lib$name.a"
}
@@ -1,6 +1,7 @@
// DONT_TARGET_EXACT_BACKEND: WASM
// IGNORE_BACKEND: JS
// IGNORE_BACKEND: JS_IR
// IGNORE_BACKEND: NATIVE
// FILE: DiagnosticFactory0.java
import org.jetbrains.annotations.NotNull;
@@ -60,7 +60,10 @@ class CodeConformanceTest : TestCase() {
"libraries/tools/kotlin-test-nodejs-runner/node_modules",
"libraries/tools/kotlinp/src",
"libraries/tools/new-project-wizard/new-project-wizard-cli/build",
"out"
"out",
"kotlin-native/build",
"kotlin-native/performance",
"kotlin-native/samples"
)
)
@@ -99,7 +102,9 @@ class CodeConformanceTest : TestCase() {
"libraries/tools/kotlin-test-js-runner/node_modules",
"libraries/tools/kotlin-test-nodejs-runner/.gradle",
"libraries/tools/kotlin-test-nodejs-runner/node_modules",
"out"
"kotlin-native", // Have a separate licences manager
"out",
"kotlin-native/runtime"
)
)
}
@@ -53,10 +53,8 @@ data class CompilerVersionImpl(
append(major)
append('.')
append(minor)
if (maintenance != 0) {
append('.')
append(maintenance)
}
append('.')
append(maintenance)
if (milestone != -1) {
append("-M")
append(milestone)
@@ -10,12 +10,15 @@ import org.jetbrains.kotlin.konan.file.*
import java.lang.StringBuilder
fun <T> printMillisec(message: String, body: () -> T): T {
val result: T
val msec = measureTimeMillis {
result = body()
var msec = 0L
try {
msec = measureTimeMillis {
return body()
}
} finally {
println("$message: $msec msec")
}
println("$message: $msec msec")
return result
error("shouldn't happens")
}
fun profile(message: String, body: () -> Unit) = profileIf(
+7 -2
View File
@@ -4,7 +4,7 @@ plugins {
}
group = "org.jetbrains.kotlin"
version = "0.0.21"
version = "0.0.25"
repositories {
mavenCentral()
@@ -29,10 +29,15 @@ java {
sourceSets {
main {
java.setSrcDirs(listOf("src"))
/*TODO: move version to build-plugin*/
java.setSrcDirs(listOf("src", "../../compiler/util-io/src"))
}
}
tasks.withType<GenerateModuleMetadata> {
enabled = false
}
publishing {
publications {
create<MavenPublication>("KotlinBuildGradlePlugin") {
@@ -120,6 +120,8 @@ class KotlinBuildProperties(
val teamCityBootstrapUrl: String? = getOrNull("bootstrap.teamcity.url") as String?
val rootProjectDir: File = propertiesProvider.rootProjectDir
val isKotlinNativeEnabled: Boolean = getBoolean("kotlin.native.enabled")
}
private const val extensionName = "kotlinBuildProperties"
@@ -0,0 +1,132 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.*
import java.io.*
import java.util.regex.Pattern
import java.util.Properties
import org.gradle.api.Project
import org.jetbrains.kotlin.konan.*
fun Project.konanVersionGeneratedSrc() = rootProject.findProject(":kotlin-native")?.file("../buildSrc/build/version-generated/src/generated") ?: file("build/version-generated/src/generated")
fun Project.kotlinNativeVersionSrc():File {
val kotlinNativeProject = rootProject.findProject(":kotlin-native")
return if(kotlinNativeProject != null){
if (kotlinNativeVersionInResources)
kotlinNativeProject.file("${findProperty("kotlin_root")!!}/buildSrc/src/kotlin-native-binary-version/kotlin")
else
kotlinNativeProject.file("../buildSrc/build/version-generated/src/generated")
} else {
if (kotlinNativeVersionInResources)
file("src/kotlin-native-binary-version/kotlin")
else
file("build/version-generated/src/generated")
}
}
fun Project.konanRootDir() = rootProject.findProject(":kotlin-native")?.projectDir ?: file("../kotlin-native")
fun Project.kotlinNativeProperties() = Properties().apply{
val kotlinNativeProperyFile = File(this@kotlinNativeProperties.konanRootDir(), "gradle.properties")
if (!kotlinNativeProperyFile.exists())
return@apply
FileReader(kotlinNativeProperyFile).use {
load(it)
}
}
val Project.kotlinNativeVersionInResources:Boolean
get() = kotlinNativeProperties()["kotlinNativeVersionInResources"]?.toString()?.toBoolean() ?: false
fun Project.kotlinNativeVersionResourceFile() = File("${project.findProperty("kotlin_root")!!}/buildSrc/build/version-generated/META-INF/kotlin-native.compiler.version")
fun Project.kotlinNativeVersionValue(): CompilerVersion? {
return if (this.kotlinNativeVersionInResources)
kotlinNativeVersionResourceFile().let { file ->
file.readLines().single().parseCompilerVersion()
}
else null
}
open class VersionGenerator: DefaultTask() {
private val kotlinNativeProperties = project.kotlinNativeProperties()
@Input
var kotlinNativeVersionInResources = project.kotlinNativeVersionInResources
@OutputDirectory
val versionSourceDirectory = project.file("build/version-generated")
@OutputFile
open var versionFile: File? = project.file("${versionSourceDirectory.path}/org/jetbrains/kotlin/konan/CompilerVersionGenerated.kt")
@Input
open val konanVersion = kotlinNativeProperties["konanVersion"].toString()
// TeamCity passes all configuration parameters into a build script as project properties.
// Thus we can use them here instead of environment variables.
@Optional
@Input
open val buildNumber = project.findProperty("build.number")?.toString()
@Input
open val meta = kotlinNativeProperties["konanMetaVersion"]?.let{ MetaVersion.valueOf(it.toString().toUpperCase()) } ?: MetaVersion.DEV
private val versionPattern = Pattern.compile(
"^(\\d+)\\.(\\d+)(?:\\.(\\d+))?(?:-M(\\p{Digit}))?(?:-(\\p{Alpha}\\p{Alnum}*))?(?:-(\\d+))?$"
)
fun defaultVersionFileLocation() {
versionFile = if (kotlinNativeVersionInResources)
project.file("${versionSourceDirectory.path}/META-INF/kotlin-native.compiler.version")
else
project.file("${versionSourceDirectory.path}/org/jetbrains/kotlin/konan/CompilerVersionGenerated.kt")
}
@TaskAction
open fun generateVersion() {
val matcher = versionPattern.matcher(konanVersion)
require(matcher.matches()) { "Cannot parse Kotlin/Native version: \$konanVersion" }
val major = matcher.group(1).toInt()
val minor = matcher.group(2).toInt()
val maintenanceStr = matcher.group(3)
val maintenance = maintenanceStr?.toInt() ?: 0
val milestoneStr = matcher.group(4)
val milestone = milestoneStr?.toInt() ?: -1
project.logger.info("BUILD_NUMBER: $buildNumber")
var build = -1
if (buildNumber != null) {
val buildNumberSplit = buildNumber!!.split("-".toRegex()).toTypedArray()
build = buildNumberSplit[buildNumberSplit.size - 1].toInt() // //7-dev-buildcount
}
val versionObject = CompilerVersionImpl(meta, major, minor, maintenance, milestone, build)
versionObject.serialize()
}
private fun CompilerVersion.serialize() {
versionFile!!.parentFile.mkdirs()
project.logger.info("version file: ${versionFile}")
if (!kotlinNativeVersionInResources) {
PrintWriter(versionFile).use { printWriter ->
printWriter.println(
"""|package org.jetbrains.kotlin.konan
|internal val currentCompilerVersion: CompilerVersion =
|CompilerVersionImpl($meta, $major, $minor,
| $maintenance, $milestone, $build)
|val CompilerVersion.Companion.CURRENT: CompilerVersion
|get() = currentCompilerVersion""".trimMargin()
)
}
} else {
PrintStream(versionFile).use {
it.println(this)
}
}
}
}
+2
View File
@@ -42,3 +42,5 @@ kotlin.stdlib.default.dependency=false
kotlin.internal.mpp12x.deprecation.suppress=true
kotlin.mpp.stability.nowarn=true
kotlin.2js.nowarn=true
kotlin.native.enabled=false
kotlin.native.home=kotlin-native/dist
+2 -1
View File
@@ -77,7 +77,8 @@ val mirroredUrls = listOf(
"https://www.python.org/ftp",
"https://www.jetbrains.com/intellij-repository/nightly",
"https://www.jetbrains.com/intellij-repository/releases",
"https://www.jetbrains.com/intellij-repository/snapshots"
"https://www.jetbrains.com/intellij-repository/snapshots",
"https://kotlin.bintray.com/kotlinx"
)
val aliases = mapOf(
+194
View File
@@ -0,0 +1,194 @@
# Also see codestyle/cpp/README.md
# For documentation on options see: https://releases.llvm.org/8.0.0/tools/clang/docs/ClangFormatStyleOptions.html
# "Kt N/A" means that this C/C++/ObjC construct have no corresponding Kotlin construct to refer to
# "Kt U" means that formatting is unspecified in Kotlin formatting guide
# "Kt <url>" means that this is specified in Kotlin formatting guide with url pointing to it
---
DisableFormat: false
Standard: Cpp11
# Kt N/A. Different from 0 to make modifiers stand out. An alternative is -2, but it introduces another level of indentation.
AccessModifierOffset: -4
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#method-call-formatting
AlignAfterOpenBracket: AlwaysBreak
# Kt U. Not touching
AlignConsecutiveAssignments: false
# Kt U. Not touching
AlignConsecutiveDeclarations: false
# Kt N/A. Not touching
AlignEscapedNewlines: DontAlign
# Kt U. Not touching
AlignOperands: false
# Kt U. Not touching
AlignTrailingComments: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#function-formatting
AllowAllParametersOfDeclarationOnNextLine: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
AllowShortBlocksOnASingleLine: false
# Kt U. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
# Using false, because case statements usually contain at least 2 statements: doing something + break, which makes them multiline
AllowShortCaseLabelsOnASingleLine: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#function-formatting Inline is somewhat close to "single expression" functions
AllowShortFunctionsOnASingleLine: Inline
# Kt U. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#using-conditional-statements however it mostly refers to the ternary operator.
AllowShortIfStatementsOnASingleLine: true
# Kt U. Same as previous
AllowShortLoopsOnASingleLine: true
# Kt N/A. In Kotlin return type is in a trailing position.
AlwaysBreakAfterReturnType: None
# Kt U. Not touching.
AlwaysBreakBeforeMultilineStrings: false
# Kt N/A. In Kotlin type parameters are declared inline in a much more compact way.
# Using Yes to make it easy to detect function name
AlwaysBreakTemplateDeclarations: Yes
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#method-call-formatting
BinPackArguments: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#function-formatting
BinPackParameters: false
BraceWrapping:
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterClass: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterControlStatement: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterEnum: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterFunction: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterNamespace: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterObjCDeclaration: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterStruct: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterUnion: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
AfterExternBlock: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
BeforeCatch: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
BeforeElse: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
IndentBraces: false
# Only used when AfterFunction is true
SplitEmptyFunction: true
# Only used when AfterRecord is true
SplitEmptyRecord: true
# Only used if AfterNamespace is true
SplitEmptyNamespace: true
# Kt U. Break after operators
BreakBeforeBinaryOperators: None
# Configured by BraceWrapping
BreakBeforeBraces: Custom
# Kt U. Using true because it looks more like an if-expression with "then" and "else" branches marked "?" and ":"
BreakBeforeTernaryOperators: true
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#class-header-formatting AfterColon looks the most consistent
BreakConstructorInitializers: AfterColon
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#class-header-formatting
BreakInheritanceList: AfterColon
# Kt U. Choosing to break long strings to fit into line length
BreakStringLiterals: true
# Kt U. IDEA displays a vertical guide at 120. Choosing it to avoid very long lines.
ColumnLimit: 140
# Kt N/A. Probably should use nested namespaces instead.
CompactNamespaces: false
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#class-header-formatting
ConstructorInitializerAllOnOneLineOrOnePerLine: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
ConstructorInitializerIndentWidth: 4
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting IDEA seems to indent continuations with 8
ContinuationIndentWidth: 8
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
# Braced initalizer braces are close to parens in function call or to brackets in array initialier. And styleguide asks not to put spaces in them.
Cpp11BracedListStyle: true
# Configured by PointerAlignment
DerivePointerAlignment: false
# Confgured by BinPack*
ExperimentalAutoDetectBinPacking: false
# Kt N/A. Setting to true because it helps to see when anonymous namespace ends
FixNamespaceComments: true
# Kt U. Not touching manually created including blocks
IncludeBlocks: Preserve
# Kt U. Sorting: main header (priority 0) > system headers > project headers
IncludeCategories:
- Regex: '^<.*'
Priority: 1
- Regex: '.*'
Priority: 2
# Kt N/A. Main header must match current filename (modulo extension) exactly.
IncludeIsMainRegex: '$'
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting-control-flow-statements
IndentCaseLabels: true
# Kt N/A. Do not indent macro code
IndentPPDirectives: None
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
IndentWidth: 4
# Kt U. IDEA seems to indent
IndentWrappedFunctionNames: true
# Kt U. Do not touch.
KeepEmptyLinesAtTheStartOfBlocks: false
# Kt U. IDEA keeps 1 empty line by default.
MaxEmptyLinesToKeep: 1
# Kt N/A.
NamespaceIndentation: None
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#class-header-formatting
ObjCBinPackProtocolList: Never
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
ObjCBlockIndentWidth: 4
# Kt N/A. Following Google and LLVM styleguides.
ObjCSpaceAfterProperty: false
# Kt N/A. The closest is class inheritance list. Following Google and LLVM styleguides.
ObjCSpaceBeforeProtocolList: true
# Kt N/A.
PointerAlignment: Left
# Kt U. Reflow comments to fit into line width
ReflowComments: true
# Kt U. IDEA does not sort by default, but allows this option. Do not touch.
SortIncludes: false
# Kt U. Like SortIncludes.
SortUsingDeclarations: false
# Kt N/A.
SpaceAfterCStyleCast: false
# Kt N/A. IDEA puts space in `fun <T>`
SpaceAfterTemplateKeyword: true
# Kt U. https://kotlinlang.org/docs/reference/coding-conventions.html puts spaces
SpaceBeforeAssignmentOperators: true
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace which does not put space before parens and brackets.
SpaceBeforeCpp11BracedList: false
# Kt N/A. The closest is https://kotlinlang.org/docs/reference/coding-conventions.html#colon
SpaceBeforeCtorInitializerColon: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#colon
SpaceBeforeInheritanceColon: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpaceBeforeParens: ControlStatements
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpaceBeforeRangeBasedForLoopColon: true
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpaceInEmptyParentheses: false
# Kt U. https://kotlinlang.org/docs/reference/coding-conventions.html uses 1
SpacesBeforeTrailingComments: 1
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpacesInAngles: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpacesInContainerLiterals: false
# Kt N/A.
SpacesInCStyleCastParentheses: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpacesInParentheses: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#horizontal-whitespace
SpacesInSquareBrackets: false
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
TabWidth: 4
# Kt https://kotlinlang.org/docs/reference/coding-conventions.html#formatting
UseTab: Never
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
...
+336
View File
@@ -0,0 +1,336 @@
root = true
[*]
ij_continuation_indent_size = 8
ij_formatter_off_tag = @formatter:off
ij_formatter_on_tag = @formatter:on
ij_formatter_tags_enabled = false
ij_smart_tabs = false
ij_wrap_on_typing = false
[*.java]
ij_java_align_consecutive_assignments = false
ij_java_align_consecutive_variable_declarations = false
ij_java_align_group_field_declarations = false
ij_java_align_multiline_annotation_parameters = false
ij_java_align_multiline_array_initializer_expression = false
ij_java_align_multiline_assignment = false
ij_java_align_multiline_binary_operation = false
ij_java_align_multiline_chained_methods = false
ij_java_align_multiline_extends_list = false
ij_java_align_multiline_for = true
ij_java_align_multiline_method_parentheses = false
ij_java_align_multiline_parameters = true
ij_java_align_multiline_parameters_in_calls = false
ij_java_align_multiline_parenthesized_expression = false
ij_java_align_multiline_records = true
ij_java_align_multiline_resources = true
ij_java_align_multiline_ternary_operation = false
ij_java_align_multiline_text_blocks = false
ij_java_align_multiline_throws_list = false
ij_java_align_subsequent_simple_methods = false
ij_java_align_throws_keyword = false
ij_java_annotation_parameter_wrap = off
ij_java_array_initializer_new_line_after_left_brace = false
ij_java_array_initializer_right_brace_on_new_line = false
ij_java_array_initializer_wrap = off
ij_java_assert_statement_colon_on_next_line = false
ij_java_assert_statement_wrap = off
ij_java_assignment_wrap = off
ij_java_binary_operation_sign_on_next_line = false
ij_java_binary_operation_wrap = off
ij_java_blank_lines_after_anonymous_class_header = 0
ij_java_blank_lines_after_class_header = 0
ij_java_blank_lines_after_imports = 1
ij_java_blank_lines_after_package = 1
ij_java_blank_lines_around_class = 1
ij_java_blank_lines_around_field = 0
ij_java_blank_lines_around_field_in_interface = 0
ij_java_blank_lines_around_initializer = 1
ij_java_blank_lines_around_method = 1
ij_java_blank_lines_around_method_in_interface = 1
ij_java_blank_lines_before_class_end = 0
ij_java_blank_lines_before_imports = 1
ij_java_blank_lines_before_method_body = 0
ij_java_blank_lines_before_package = 0
ij_java_block_brace_style = end_of_line
ij_java_block_comment_at_first_column = true
ij_java_call_parameters_new_line_after_left_paren = false
ij_java_call_parameters_right_paren_on_new_line = false
ij_java_call_parameters_wrap = off
ij_java_case_statement_on_separate_line = true
ij_java_catch_on_new_line = false
ij_java_class_annotation_wrap = split_into_lines
ij_java_class_brace_style = end_of_line
ij_java_class_count_to_use_import_on_demand = 5
ij_java_class_names_in_javadoc = 1
ij_java_do_not_indent_top_level_class_members = false
ij_java_do_not_wrap_after_single_annotation = false
ij_java_do_while_brace_force = never
ij_java_doc_add_blank_line_after_description = true
ij_java_doc_add_blank_line_after_param_comments = false
ij_java_doc_add_blank_line_after_return = false
ij_java_doc_add_p_tag_on_empty_lines = true
ij_java_doc_align_exception_comments = true
ij_java_doc_align_param_comments = true
ij_java_doc_do_not_wrap_if_one_line = false
ij_java_doc_enable_formatting = true
ij_java_doc_enable_leading_asterisks = true
ij_java_doc_indent_on_continuation = false
ij_java_doc_keep_empty_lines = true
ij_java_doc_keep_empty_parameter_tag = true
ij_java_doc_keep_empty_return_tag = true
ij_java_doc_keep_empty_throws_tag = true
ij_java_doc_keep_invalid_tags = true
ij_java_doc_param_description_on_new_line = false
ij_java_doc_preserve_line_breaks = false
ij_java_doc_use_throws_not_exception_tag = true
ij_java_else_on_new_line = false
ij_java_entity_dd_suffix = EJB
ij_java_entity_eb_suffix = Bean
ij_java_entity_hi_suffix = Home
ij_java_entity_lhi_prefix = Local
ij_java_entity_lhi_suffix = Home
ij_java_entity_li_prefix = Local
ij_java_entity_pk_class = java.lang.String
ij_java_entity_vo_suffix = VO
ij_java_enum_constants_wrap = off
ij_java_extends_keyword_wrap = off
ij_java_extends_list_wrap = off
ij_java_field_annotation_wrap = split_into_lines
ij_java_finally_on_new_line = false
ij_java_for_brace_force = never
ij_java_for_statement_new_line_after_left_paren = false
ij_java_for_statement_right_paren_on_new_line = false
ij_java_for_statement_wrap = off
ij_java_generate_final_locals = false
ij_java_generate_final_parameters = false
ij_java_if_brace_force = never
ij_java_imports_layout = *,|,javax.**,java.**,|,$*
ij_java_indent_case_from_switch = true
ij_java_insert_inner_class_imports = false
ij_java_insert_override_annotation = true
ij_java_keep_blank_lines_before_right_brace = 2
ij_java_keep_blank_lines_between_package_declaration_and_header = 2
ij_java_keep_blank_lines_in_code = 2
ij_java_keep_blank_lines_in_declarations = 2
ij_java_keep_control_statement_in_one_line = true
ij_java_keep_first_column_comment = true
ij_java_keep_indents_on_empty_lines = false
ij_java_keep_line_breaks = true
ij_java_keep_multiple_expressions_in_one_line = false
ij_java_keep_simple_blocks_in_one_line = false
ij_java_keep_simple_classes_in_one_line = false
ij_java_keep_simple_lambdas_in_one_line = false
ij_java_keep_simple_methods_in_one_line = false
ij_java_label_indent_absolute = false
ij_java_label_indent_size = 0
ij_java_lambda_brace_style = end_of_line
ij_java_layout_static_imports_separately = true
ij_java_line_comment_add_space = false
ij_java_line_comment_at_first_column = true
ij_java_message_dd_suffix = EJB
ij_java_message_eb_suffix = Bean
ij_java_method_annotation_wrap = split_into_lines
ij_java_method_brace_style = end_of_line
ij_java_method_call_chain_wrap = off
ij_java_method_parameters_new_line_after_left_paren = false
ij_java_method_parameters_right_paren_on_new_line = false
ij_java_method_parameters_wrap = off
ij_java_modifier_list_wrap = false
ij_java_names_count_to_use_import_on_demand = 3
ij_java_new_line_after_lparen_in_record_header = false
ij_java_packages_to_use_import_on_demand = java.awt.*,javax.swing.*
ij_java_parameter_annotation_wrap = off
ij_java_parentheses_expression_new_line_after_left_paren = false
ij_java_parentheses_expression_right_paren_on_new_line = false
ij_java_place_assignment_sign_on_next_line = false
ij_java_prefer_longer_names = true
ij_java_prefer_parameters_wrap = false
ij_java_record_components_wrap = normal
ij_java_repeat_synchronized = true
ij_java_replace_instanceof_and_cast = false
ij_java_replace_null_check = true
ij_java_replace_sum_lambda_with_method_ref = true
ij_java_resource_list_new_line_after_left_paren = false
ij_java_resource_list_right_paren_on_new_line = false
ij_java_resource_list_wrap = off
ij_java_rparen_on_new_line_in_record_header = false
ij_java_session_dd_suffix = EJB
ij_java_session_eb_suffix = Bean
ij_java_session_hi_suffix = Home
ij_java_session_lhi_prefix = Local
ij_java_session_lhi_suffix = Home
ij_java_session_li_prefix = Local
ij_java_session_si_suffix = Service
ij_java_space_after_closing_angle_bracket_in_type_argument = false
ij_java_space_after_colon = true
ij_java_space_after_comma = true
ij_java_space_after_comma_in_type_arguments = true
ij_java_space_after_for_semicolon = true
ij_java_space_after_quest = true
ij_java_space_after_type_cast = true
ij_java_space_before_annotation_array_initializer_left_brace = false
ij_java_space_before_annotation_parameter_list = false
ij_java_space_before_array_initializer_left_brace = false
ij_java_space_before_catch_keyword = true
ij_java_space_before_catch_left_brace = true
ij_java_space_before_catch_parentheses = true
ij_java_space_before_class_left_brace = true
ij_java_space_before_colon = true
ij_java_space_before_colon_in_foreach = true
ij_java_space_before_comma = false
ij_java_space_before_do_left_brace = true
ij_java_space_before_else_keyword = true
ij_java_space_before_else_left_brace = true
ij_java_space_before_finally_keyword = true
ij_java_space_before_finally_left_brace = true
ij_java_space_before_for_left_brace = true
ij_java_space_before_for_parentheses = true
ij_java_space_before_for_semicolon = false
ij_java_space_before_if_left_brace = true
ij_java_space_before_if_parentheses = true
ij_java_space_before_method_call_parentheses = false
ij_java_space_before_method_left_brace = true
ij_java_space_before_method_parentheses = false
ij_java_space_before_opening_angle_bracket_in_type_parameter = false
ij_java_space_before_quest = true
ij_java_space_before_switch_left_brace = true
ij_java_space_before_switch_parentheses = true
ij_java_space_before_synchronized_left_brace = true
ij_java_space_before_synchronized_parentheses = true
ij_java_space_before_try_left_brace = true
ij_java_space_before_try_parentheses = true
ij_java_space_before_type_parameter_list = false
ij_java_space_before_while_keyword = true
ij_java_space_before_while_left_brace = true
ij_java_space_before_while_parentheses = true
ij_java_space_inside_one_line_enum_braces = false
ij_java_space_within_empty_array_initializer_braces = false
ij_java_space_within_empty_method_call_parentheses = false
ij_java_space_within_empty_method_parentheses = false
ij_java_spaces_around_additive_operators = true
ij_java_spaces_around_assignment_operators = true
ij_java_spaces_around_bitwise_operators = true
ij_java_spaces_around_equality_operators = true
ij_java_spaces_around_lambda_arrow = true
ij_java_spaces_around_logical_operators = true
ij_java_spaces_around_method_ref_dbl_colon = false
ij_java_spaces_around_multiplicative_operators = true
ij_java_spaces_around_relational_operators = true
ij_java_spaces_around_shift_operators = true
ij_java_spaces_around_type_bounds_in_type_parameters = true
ij_java_spaces_around_unary_operator = false
ij_java_spaces_within_angle_brackets = false
ij_java_spaces_within_annotation_parentheses = false
ij_java_spaces_within_array_initializer_braces = false
ij_java_spaces_within_braces = false
ij_java_spaces_within_brackets = false
ij_java_spaces_within_cast_parentheses = false
ij_java_spaces_within_catch_parentheses = false
ij_java_spaces_within_for_parentheses = false
ij_java_spaces_within_if_parentheses = false
ij_java_spaces_within_method_call_parentheses = false
ij_java_spaces_within_method_parentheses = false
ij_java_spaces_within_parentheses = false
ij_java_spaces_within_switch_parentheses = false
ij_java_spaces_within_synchronized_parentheses = false
ij_java_spaces_within_try_parentheses = false
ij_java_spaces_within_while_parentheses = false
ij_java_special_else_if_treatment = true
ij_java_subclass_name_suffix = Impl
ij_java_ternary_operation_signs_on_next_line = false
ij_java_ternary_operation_wrap = off
ij_java_test_name_suffix = Test
ij_java_throws_keyword_wrap = off
ij_java_throws_list_wrap = off
ij_java_use_external_annotations = false
ij_java_use_fq_class_names = false
ij_java_use_relative_indents = false
ij_java_use_single_class_imports = true
ij_java_variable_annotation_wrap = off
ij_java_visibility = public
ij_java_while_brace_force = never
ij_java_while_on_new_line = false
ij_java_wrap_comments = false
ij_java_wrap_first_method_in_call_chain = false
ij_java_wrap_long_lines = false
[{*.gradle.kts,*.kt,*.kts,*.main.kts,KotlinBuildPusher}]
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
ij_kotlin_align_multiline_method_parentheses = false
ij_kotlin_align_multiline_parameters = true
ij_kotlin_align_multiline_parameters_in_calls = false
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_assignment_wrap = off
ij_kotlin_blank_lines_after_class_header = 0
ij_kotlin_blank_lines_around_block_when_branches = 0
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
ij_kotlin_block_comment_at_first_column = true
ij_kotlin_call_parameters_new_line_after_left_paren = false
ij_kotlin_call_parameters_right_paren_on_new_line = false
ij_kotlin_call_parameters_wrap = off
ij_kotlin_catch_on_new_line = false
ij_kotlin_class_annotation_wrap = split_into_lines
ij_kotlin_continuation_indent_for_chained_calls = true
ij_kotlin_continuation_indent_for_expression_bodies = true
ij_kotlin_continuation_indent_in_argument_lists = true
ij_kotlin_continuation_indent_in_elvis = true
ij_kotlin_continuation_indent_in_if_conditions = true
ij_kotlin_continuation_indent_in_parameter_lists = true
ij_kotlin_continuation_indent_in_supertype_lists = true
ij_kotlin_else_on_new_line = false
ij_kotlin_enum_constants_wrap = off
ij_kotlin_extends_list_wrap = off
ij_kotlin_field_annotation_wrap = split_into_lines
ij_kotlin_finally_on_new_line = false
ij_kotlin_if_rparen_on_new_line = false
ij_kotlin_import_nested_classes = false
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
ij_kotlin_keep_blank_lines_before_right_brace = 2
ij_kotlin_keep_blank_lines_in_code = 2
ij_kotlin_keep_blank_lines_in_declarations = 2
ij_kotlin_keep_first_column_comment = true
ij_kotlin_keep_indents_on_empty_lines = false
ij_kotlin_keep_line_breaks = true
ij_kotlin_lbrace_on_next_line = false
ij_kotlin_line_comment_add_space = false
ij_kotlin_line_comment_at_first_column = true
ij_kotlin_method_annotation_wrap = split_into_lines
ij_kotlin_method_call_chain_wrap = off
ij_kotlin_method_parameters_new_line_after_left_paren = false
ij_kotlin_method_parameters_right_paren_on_new_line = false
ij_kotlin_method_parameters_wrap = off
ij_kotlin_name_count_to_use_star_import = 5
ij_kotlin_name_count_to_use_star_import_for_members = 3
ij_kotlin_parameter_annotation_wrap = off
ij_kotlin_space_after_comma = true
ij_kotlin_space_after_extend_colon = true
ij_kotlin_space_after_type_colon = true
ij_kotlin_space_before_catch_parentheses = true
ij_kotlin_space_before_comma = false
ij_kotlin_space_before_extend_colon = true
ij_kotlin_space_before_for_parentheses = true
ij_kotlin_space_before_if_parentheses = true
ij_kotlin_space_before_lambda_arrow = true
ij_kotlin_space_before_type_colon = false
ij_kotlin_space_before_when_parentheses = true
ij_kotlin_space_before_while_parentheses = true
ij_kotlin_spaces_around_additive_operators = true
ij_kotlin_spaces_around_assignment_operators = true
ij_kotlin_spaces_around_equality_operators = true
ij_kotlin_spaces_around_function_type_arrow = true
ij_kotlin_spaces_around_logical_operators = true
ij_kotlin_spaces_around_multiplicative_operators = true
ij_kotlin_spaces_around_range = false
ij_kotlin_spaces_around_relational_operators = true
ij_kotlin_spaces_around_unary_operator = false
ij_kotlin_spaces_around_when_arrow = true
ij_kotlin_variable_annotation_wrap = off
ij_kotlin_while_on_new_line = false
ij_kotlin_wrap_elvis_expressions = 1
ij_kotlin_wrap_expression_body_functions = 0
ij_kotlin_wrap_first_method_in_call_chain = false
+80
View File
@@ -0,0 +1,80 @@
.DS_Store
.idea/shelf
*.iml
/dependencies/all
dist
kotlin-native-*.tar.gz
kotlin-native-*.zip
translator/src/test/kotlin/tests/*/linked
out
tmp
workspace.xml
*.versionsBackup
local.properties
.gradle
build
translator/.gradle/2.9/taskArtifacts
kotstd/kotstd.iml
# test suit products.
*.bc
*.bc.o
*.kt.S
*.kt.exe
*.log
test.output
*.kexe
# Ignore Gradle GUI config
gradle-app.setting
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
!gradle-wrapper.jar
# Cache of project
.gradletasknamecache
# local project files
lib/
.idea/
proto/compiler/protoc-artifacts
proto/compiler/tests
proto/compiler/google/src/google/protobuf/compiler/kotlin/bin
proto/compiler/google/src/google/protobuf/compiler/kotlin/protoc
peformance/build
# translator auto generated artifacts
kotstd/ll
# test teamcity property: commitable only with -f
backend.native/tests/teamcity-test.property
# Sample output
samples/**/*.kt.bc-build
samples/androidNativeActivity/Polyhedron
# CMake
llvmCoverageMappingC/CMakeLists.txt
tools/performance-server/node_modules
tools/performance-server/server
tools/performance-server/ui/js
runtime/cmake-build-debug/
# compilation database
compile_commands.json
# clangd caches
.clangd/
# googletest framework used by runtime tests
runtime/googletest/
# Toolchain builder artifacts
tools/toolchain_builder/artifacts
# ignore the project model created by CLion (for now).
runtime/.idea
+20
View File
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="IssueNavigationConfiguration">
<option name="links">
<list>
<IssueNavigationLink>
<option name="issueRegexp" value="((KT|KTI|IDEA)\-(\d+))" />
<option name="linkRegexp" value="https://youtrack.jetbrains.com/issue/$1" />
</IssueNavigationLink>
<IssueNavigationLink>
<option name="issueRegexp" value="#(\d+)" />
<option name="linkRegexp" value="https://github.com/JetBrains/kotlin-native/issues/$1" />
</IssueNavigationLink>
</list>
</option>
</component>
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>
+65
View File
@@ -0,0 +1,65 @@
# Building Apple LLVM for Kotlin/Native
This document describes how to compile LLVM distribution and use it to build Kotlin/Native on macOS.
Usually, you don't need to compile LLVM by yourself:
* If you use Kotlin/Native compiler it will download LLVM on the first run.
* If you contribute to kotlin-native repository then use `:dependencies:update` Gradle task.
But if you don't want to download prebuilt LLVM or want to experiment with your own distribution,
you came to the right place.
## Part 1. Building the right LLVM version for macOS.
For macOS host we use LLVM from [Apple downstream](https://github.com/apple/llvm-project).
Branch is [**apple/stable/20190104**](https://github.com/apple/llvm-project/tree/apple/stable/20190104)
because it is similar (or even the same) to what Apple ships with Xcode 11.*.
After cloning the repo and changing the branch we perform the following steps to build LLVM toolchain:
```bash
mkdir build
cd build
cmake -DLLVM_ENABLE_PROJECTS="clang;lld;libcxx;libcxxabi" \
-DCMAKE_BUILD_TYPE=Release \
-DLLVM_ENABLE_ASSERTIONS=Off \
-G Ninja \
-DCMAKE_INSTALL_PREFIX=clang-llvm-apple-8.0.0-darwin-macos \
../llvm
ninja install
```
After these steps `clang-llvm-apple-8.0.0-darwin-macos` directory will contain LLVM distribution that is suitable for building Kotlin/Native.
## Part 2. Building Kotlin/Native against given LLVM distribution.
By default, Kotlin/Native will try to download LLVM distribution from CDN if it is not present in `$HOME/.konan/dependencies` folder.
There are two ways to bypass this behaviour.
#### Option A. Substitute prebuilt distribution.
This option doesn't require you to edit compiler sources, but a bit harder.
The compiler checks dependency presence by reading contents of `$HOME/.konan/dependencies/.extracted` file.
So to avoid LLVM downloading, we should manually add a record to the `.extracted` file:
1. Create `$HOME/.konan/dependencies/.extracted` file if it is not created.
2. Add `clang-llvm-apple-8.0.0-darwin-macos` line.
and put `clang-llvm-apple-8.0.0-darwin-macos` directory from the Part 1 to `$HOME/.konan/dependencies/`.
#### Option B. Provide an absolute path to the distribution.
This option requires user to edit [konan.properties file](konan/konan.properties).
Set `llvmHome.<HOST_NAME>` to an absolute path to your LLVM distribution and
set `llvmVersion.<HOST_NAME>` to its version.
For example, provide a path to `clang-llvm-apple-8.0.0-darwin-macos` from the Part 1 and set version to 8.0.0.
Now we are ready to build Kotlin/Native itself. The process is described in [README.md](README.md).
Please note that we still need to run `./gradlew dependencies:update` to download other dependencies (e.g. libffi).
## Q&A
— Can I override `.konan` location?
— Yes, by setting `$KONAN_DATA_DIR` environment variable. See [HACKING.md](HACKING.md#compiler-environment-variables).
- Can I use another LLVM distribution without rebuilding Kotlin/Native?
- Yes, see [HACKING.md](HACKING.md#using-different-llvm-distributions-as-part-of-kotlinnative-compilation-pipeline).
+359
View File
@@ -0,0 +1,359 @@
# 1.4.30 (Feb 2021)
* [KT-44083](https://youtrack.jetbrains.com/issue/KT-44083) Fix NSUInteger size for Watchos x64
# 1.4.30-RC (Jan 2021)
* [KT-44271](https://youtrack.jetbrains.com/issue/KT-44271) Incorrect linking when targeting linux_x64 from mingw_x64 host
* [KT-44219](https://youtrack.jetbrains.com/issue/KT-44219) Non-reified type parameters with recursive bounds are not supported yet
* [KT-43599](https://youtrack.jetbrains.com/issue/KT-43599) K/N: Unbound symbols not allowed
* [KT-42172](https://youtrack.jetbrains.com/issue/KT-42172) Kotlin/Native: StableRef.dispose race condition on Kotlin deinitRuntime
* [KT-42482](https://youtrack.jetbrains.com/issue/KT-42482) Kotlin subclasses of Obj-C classes are incompatible with ISA swizzling (it causes crashes)
# 1.4.30-M1 (Dec 2020)
* [KT-43597](https://youtrack.jetbrains.com/issue/KT-43597) Xcode 12.2 support
* [KT-43276](https://youtrack.jetbrains.com/issue/KT-43276) Add watchos_x64 target
* [KT-43198](https://youtrack.jetbrains.com/issue/KT-43198) Init blocks inside of inline classes
* [KT-42649](https://youtrack.jetbrains.com/issue/KT-42649) Fix secondary constructors of generic inline classes
* [KT-38772](https://youtrack.jetbrains.com/issue/KT-38772) Support non-reified type parameters in typeOf
* [KT-42428](https://youtrack.jetbrains.com/issue/KT-42428) Inconsistent behavior of map.entries on Kotlin.Native
* Compiler customization
* [KT-40584](https://youtrack.jetbrains.com/issue/KT-40584) Untie Kotlin/Native from the fixed LLVM distribution
* [KT-42234](https://youtrack.jetbrains.com/issue/KT-42234) Move LLVM optimization parameters into konan.properties
* [KT-40670](https://youtrack.jetbrains.com/issue/KT-40670) Allow to override konan.properties via CLI
* Runtime
* [KT-42822](https://youtrack.jetbrains.com/issue/KT-42822) Kotlin/Native Worker leaks ObjC/Swift autorelease references (and indirectly bridged K/N references) on Darwin targets
* [KT-42397](https://youtrack.jetbrains.com/issue/KT-42397) Reverse-C interop usage of companion object reports spurious leaks
* [GH-4482](https://github.com/JetBrains/kotlin-native/pull/4482) Add a switch to destroy runtime only on shutdown
* [GH-4575](https://github.com/JetBrains/kotlin-native/pull/4575) Fix unchecked runtime shutdown
* [GH-4194](https://github.com/JetBrains/kotlin-native/pull/4194) Fix possible race in terminate handler
* C-interop
* [KT-42412](https://youtrack.jetbrains.com/issue/KT-42412) Modality of generated property accessors is always FINAL
* [KT-38530](https://youtrack.jetbrains.com/issue/KT-38530) values() method of enum classes is not exposed to Objective-C/Swift
* [GH-4572](https://github.com/JetBrains/kotlin-native/pull/4572) Fix for interop enum and struct generation
* Optimizations
* [KT-42294](https://youtrack.jetbrains.com/issue/KT-42294) Significantly improved compilation time
* [KT-42942](https://youtrack.jetbrains.com/issue/KT-42942) Optimize peak backend memory by clearing BindingContext after psi2ir
* [KT-31072](https://youtrack.jetbrains.com/issue/KT-31072) Don't use non-reified arguments to specialize type operations in IR inliner
# 1.4.21 (Dec 2020)
* Fixed [KT-43517](https://youtrack.jetbrains.com/issue/KT-43517)
* Fixed [KT-43530](https://youtrack.jetbrains.com/issue/KT-43530)
* Fixed [KT-43265](https://youtrack.jetbrains.com/issue/KT-43265)
# 1.4.20 (Nov 2020)
* XCode 12 support
* Completely reworked escape analysis for object allocation
* Use ForeignException wrapper to handle native exceptions ([GH-3553](https://github.com/JetBrains/kotlin-native/issues/3553))
* CocoaPods plugin improvements
* equals/hashCode support for adapted callable references ([KT-39800](https://youtrack.jetbrains.com/issue/KT-39800))
* equals/hashCode support for fun interfaces ([KT-39798](https://youtrack.jetbrains.com/issue/KT-39798))
* IR-level optimizations
* Constant folding
* String concatenation flattening
* Various fixes/improvements to compiler caches
* Some fixes to samples (calculator, tensorflow)
* Bug fixes
* Eliminate recursive GC calls ([KT-42275](https://youtrack.jetbrains.com/issue/KT-42275))
* Fix support for @OverrideInit constructors with default arguments ([KT-41910](https://youtrack.jetbrains.com/issue/KT-41910))
* Fix support for forward declarations ([KT-41655](https://youtrack.jetbrains.com/issue/KT-41655))
* [KT-41394](https://youtrack.jetbrains.com/issue/KT-41394)
* [KT-41811](https://youtrack.jetbrains.com/issue/KT-41811)
* [KT-41716](https://youtrack.jetbrains.com/issue/KT-41716)
* [KT-41250](https://youtrack.jetbrains.com/issue/KT-41250)
* [KT-42000](https://youtrack.jetbrains.com/issue/KT-42000)
* [KT-41907](https://youtrack.jetbrains.com/issue/KT-41907)
# 1.4.10 (Sep 2020)
* Fixed a newline handling in @Deprecated annotation in ObjCExport ([KT-39206](https://youtrack.jetbrains.com/issue/KT-39206))
* Fixed suspend function types in ObjCExport ([KT-40976](https://youtrack.jetbrains.com/issue/KT-40976))
* Fixed support for unsupported C declarations in cinterop ([KT-39762](https://youtrack.jetbrains.com/issue/KT-39762))
# v1.4.0 (Aug 2020)
* Objective-C/Swift interop:
* Reworked exception handling ([GH-3822](https://github.com/JetBrains/kotlin-native/pull/3822), [GH-3842](https://github.com/JetBrains/kotlin-native/pull/3842))
* Enabled support for Objective-C generics by default ([GH-3778](https://github.com/JetBrains/kotlin-native/pull/3778))
* Support for Kotlins suspending functions ([GH-3915](https://github.com/JetBrains/kotlin-native/pull/3915))
* Handle variadic block types in ObjC interop ([`KT-36766`](https://youtrack.jetbrains.com/issue/KT-36766))
* Added native-specific frontend checkers (implemented in the main Kotlin repository: [GH-3293](https://github.com/JetBrains/kotlin/pull/3293), [GH-3091](https://github.com/JetBrains/kotlin/pull/3091), [GH-3172](https://github.com/JetBrains/kotlin/pull/3172))
* .dSYMs for release binaries on Apple platforms ([GH-4085](https://github.com/JetBrains/kotlin-native/pull/4085))
* Improved compilation time of builds with interop libraries by reworking cinterop under the hood.
* Experimental mimalloc allocator support (-Xallocator=mimalloc) to improve execution time performance. ([GH-3704](https://github.com/JetBrains/kotlin-native/pull/3704))
* Tune GC to improve execution time performance
* Various fixes to compiler caches and Gradle daemon usage
# v1.3.72 (April 2020)
* Fix ios_x64 platform libs cache for iOS 11 and 12 (GH-4071)
# v1.3.71 (March 2020)
* Fix `lazy {}` memory leak regression ([`KT-37232`](https://youtrack.jetbrains.com/issue/KT-37232), GH-3944)
* Fix using cached Kotlin subclasses of Objective-C classes (GH-3986)
# v1.3.70 (Dec 2019)
* Support compiler caches for debug mode (GH-3650)
* Support running Kotlin/Native compiler from Gradle daemon (GH-3442)
* Support multiple independent Kotlin frameworks in the same application (GH-3457)
* Compile-time allocation for some singleton objects (GH-3645)
* Native support for SIMD vector types in compiler and interop (GH-3498)
* API for runtime detector of cyclic garbage (GH-3616)
* Commonized StringBuilder (GH-3593) and Float.rangeTo (KT-35299)
* Fix interop with localized strings (GH-3562)
* Provide utility for user-side generation of platform libraries (GH-3538)
* On-stack allocation using local escape analysis (GH-3625)
* Code coverage support on Linux and Windows (GH-3403)
* Debugging experience improvements (GH-3561, GH-3638, GH-3606)
# v1.3.60 (Oct 2019)
* Support XCode 11
* Switch to LLVM 8.0
* New compiler targets:
* watchOS targets, watchos_x86, watchos_arm64 and watchos_arm32 (GH-3323, GH-3404, GH-3344)
* tvOS targets tvos_x64 and tvos_arm64 (GH-3303, GH-3363)
* native Android targets android_x86 and android_x64 (GH-3306, GH-3314)
* Standard CLI library kotlinx.cli is shipped with the compiler distribution (GH-3215)
* Improved debug information for inline functions (KT-28929, GH-3292)
* Improved runtime performance of interface calls, up to 5x faster (GH-3377)
* Improved runtime performance of type checks, up to 50x faster (GH-3291)
* Produce native binaries directly from klibs, speeds up large project compilation (GH-3246)
* Supported arbitrary (up to 255 inclusive) function arity (GH-3253)
* Supported callable references to suspend functions (GH-3197)
* Implemented experimental -Xg0 switch, symbolication of release binaries for iOS (GH-3233, GH-3367)
* Interop:
* Allow passing untyped null as variadic function's parameter (GH-3312, KT-33525)
* Standard library:
* Allow scheduling jobs in arbitrary K/N context, not only Worker (GH-3316)
* Important bug fixes:
* Boxed negative values can lead to crashes on ios_arm64 (GH-3296)
* Implemented thread-safe tracking of Objective-C references to Kotlin objects (GH-3267)
# v1.3.50 (Aug 2019)
* Kotlin/Native versioning now aligned with Kotlin versioning
* Exhaustive platform libraries on macOS (GH-3141)
* Update to Gradle 5.5 (GH-3166)
* Improved debug information correctness (GH-3130)
* Major memory manager refactoring (GH-3129)
* Embed actual bitcode in produced frameworks (GH-2974)
* Compilation speed improvements
* Interop:
* Support kotlin.Deprecated when producing framework (GH-3114)
* Ensure produced Objective-C header does not have warnings (GH-3101)
* Speed up interop stub generator (GH-3082, GH-3050)
* getOriginalKotlinClass() to get KClass for Kotlin classes in Objective-C (GH-3036)
* Supported nullable primitive types in reverse C interop (GH-3198)
* Standard library
* API for delayed job execution on worker (GH-2971)
* API for running via worker's job queue (GH-3078)
* MonoClock and Duration support (GH-3028)
* Support typeOf (KT-29917, KT-28625)
* New zero-terminated utf8 to String conversion API (GH-3116)
* Optimize StringBuilder for certain cases (GH-3202)
* Implemented Array.fill API (GH-3244)
# v1.3.0 (Jun 2019)
* CoreLocation platform library on macOS (GH-3041)
* Converting Unit type to Void during producing framework for Objective-C/Swift (GH-2549, GH-1271)
* Support linux/arm64 targets (GH-2917)
* Performance improvements of memory manager (GH-2813)
* FreezableAtomicReference prototype (GH-2776)
* Logging and error messages enhancements
* Interop:
* Support nullable String return type in reverse C interop (GH-2956)
* Support setting custom exception hook in reverse C interop (GH-2941)
* Experimental generics support for produced frameworks for Objective-C/Swift implemented by Kevin Galligan (GH-2850)
* Improve support for Objective-C methods clashing with methods of Any (GH-2914)
* Support variadic Objective-C functions (GH-2896)
# v1.2.1 (Apr 2019)
* Fix Objective-C interop with React (GH-2872)
* Fix “not in vtable” compiler crash when generating frameworks (GH-2865)
* Implement some optimizations (GH-2854)
* Fix release build for 32-bit Windows (GH-2848)
* Fix casts to type parameters with multiple bounds (GH-2888)
* Fix “could not get descriptor uniq id for deserialized class FlagEnum” compiler crash when generating framework (GH-2874)
# v1.2.0 (Apr 2019)
* New intermediate representation based library format allowing global optimizations
* Exception backtraces in debug mode on macOS and iOS targets contains symbolic information
* Support for 32-bit Windows targets (target mingw_x86)
* Support for cross-compilation to Linux (x86-64 and arm32) from macOS and Windows hosts
* Static Apple frameworks can be produced
* Support Gradle 5.1
* Fix alignment-related issues on ARM32 and MIPS platforms
* Write unhandled exceptions stacktrace on device to iOS crash log
* Fix undefined behavior in some arithmetic operations
* Interop:
* Get rid of libffi dependency
* Support returning struct from C callbacks
* Support passing Kotlin strings to C interop functions accepting UTF-32 arguments
* Fix bool conversion
* Support variable length arrays
* Provide Kotlin access to C compiler intrinsics via platform.builtins package
* Support clang modules (for Objective-C only)
* Experimental integration with CocoaPods
* IDE
* Kotlin/Native plugin is supported in CLion 2018.3 and AppCode/CLion 2019.1
* Basic highlighting support for .def files
* Navigation to source files from exception backtrace
## v1.1.0 (Dec 2018)
* Performance optimizations:
* runtime: optimization of queue of finalization
* compiler: loop generation optimization
* compiler: reduce RTTI size
* runtime: reduce size of the object header
* Contracts support
* Regex engine: fix quantifier processing
## v0.9.3 (Sep 2018)
* Bugfixes
## v0.9.2 (Sep 2018)
* Support Xcode 10.0
* iOS 9.0 is the minimal supported version for all targets
* Swift interop improvements
* Support shared top level values of some immutable types (i.e. String and atomic references)
* Support release Kotlin 1.3.0
## v0.9.1 (Sep 2018)
* Improve naming in produced Objective-C frameworks. Use Kotlin prefix instead of Stdlib prefix.
* Improvements in KLIB: Library versioning, IDEA-friendly internal format.
# v0.9 (Sep 2018)
* Support Kotlin 1.3M2
* Note: Common modules of multiplatform projects also should use Kotlin 1.3
* Major standard library (native parts) rework and rename
* New Gradle plugin with multiplatform integration and reworked DSL
* Support unsigned types in Kotlin and interop
* Support non-experimental coroutines API (kotlin.coroutines)
* Top level object var/val can only be accessed from the main thread
* Support lazy properties in singleton objects
* Update LLVM to 6.0.1
## v0.8 (Jul 2018)
* Singleton objects are frozen after creation, and shared between threads
* String and primitives types are frozen by default
* Common stdlib with Kotlin/JVM and Kotlin/JS
* Implemented `kotlin.random.*` and `Collection.shuffle`
* Implemented atomic integers and atomic references
* Multiple bugfixes in compiler (coroutines, inliner)
* Support 32-bit iOS (target `ios_arm32`)
* New experimental Gradle plugin
* Support Xcode 9.4.1
* Optimizations (switch by enum, memory management)
## v0.7.1 (Jun 2018)
* Bugfixes in the runtime (indexOf, GC for kotlin.Array, enum equality) and the compiler
* Fix NSBlock problem, preventing upload of binaries to the AppStore
* Create primitive type boxes and kotlin.String as frozen by default
* Support Gradle 4.7, provide separate run task for each executable
* Support Xcode 9.4 and CoreML and ClassKit frameworks on Apple platforms
* Improved runtime Kotlin variable examination
* Minor performance optimizations in compiled code and runtime
* Add `disableDesignatedInitializerChecks` definition file support
## v0.7 (May 2018)
* Interop with Objective-C/Swift changes:
* Uniform direct and reverse interops (values could be passed in both directions now)
* Interop by exceptions
* Type conversion and checks (`as`, `is`) for interop types
* Seamless interop on numbers, strings, lists, maps and sets
* Better interop on constructors and initializers
* Switched to Xcode 9.3 on Apple platforms
* Introduced object freezing API, frozen object could be used from multiple threads
* Kotlin enums are frozen by default
* Switch to Gradle 4.6
* Use Gradle native dependency model, allowing to use `.klib` as Maven artifacts
* Introduced typed arrays API
* Introduced weak references API
* Activated global devirtualization analysis
* Performance improvements (box caching, bridge inlining, others)
## v0.6.2 (Mar 2018)
* Support several `expectedBy`-dependencies in Gradle plugin.
* Improved interaction between Gradle plugin and IDE.
* Various bugfixes
## v0.6.1 (Mar 2018)
* Various bugfixes
* Support total ordering in FP comparisons
* Interop generates string constants from string macrodefinitions
* STM32 blinky demo in pure Kotlin/Native
* Top level variables initialization redesign (proper dependency order)
* Support kotlin.math on WebAssembly targets
* Support embedded targets on Windows hosts
## v0.6 (Feb 2018)
* Support multiplatform projects (expect/actual) in compiler and Gradle plugin
* Support first embedded target (STM32 board)
* Support Kotlin 1.2.20
* Support Java 9
* Support Gradle 4.5
* Transparent Objective-C/Kotlin container classes interoperability
* Produce optimized WebAssembly binaries (10x smaller than it used to be)
* Improved APIs for object transfer between threads and workers
* Allow exporting top level C function in reverse interop with @CName annotation
* Supported debugging of code with inline functions
* Multiple bugfixes and performance optimizations
## v0.5 (Dec 2017)
* Reverse interop allowing to call Kotlin/Native code compiled as framework from Objective-C/Swift programs
* Reverse interop allowing to call Kotlin/Native code compiled as shared object from C/C++ programs
* Support generation of shared objects and DLLs by the compiler
* Migration to LLVM 5.0
* Support WebAssembly target on Linux and Windows hosts
* Make string conversions more robust
* Support kotlin.math package
* Refine workers and string conversion APIs
## v0.4 (Nov 2017) ##
* Objective-C frameworks interop for iOS and macOS targets
* Platform API libraries for Linux, iOS, macOS and Windows
* Kotlin 1.2 supported
* `val` and function parameters can be inspected in debugger
* Experimental support for WebAssembly (wasm32 target)
* Linux MIPS support (little and big endian, mips and mipsel targets)
* Gradle plugin DSL fully reworked
* Support for unit testing annotations and automatic test runner generation
* Final executable size reduced
* Various interop improvements (forward declaration, better handling of unsupported types)
* Workers object subgraph transfer checks implemented
* Optimized low level memory management using more efficient cycle tracing algorithm
## v0.3.4 (Oct 2017) ##
* Intermediate release
## v0.3.2 (Sep 2017) ##
* Bug fixes
## v0.3.1 (Aug 2017) ##
* Improvements in C interop tools (function pointers, bitfields, bugfixes)
* Improvements to Gradle plugin and dependency downloader
* Support for immutable data linked into an executable via ImmutableDataBlob class
* Kotlin 1.1.4 supported
* Basic variable inspection support in the debugger
* Some performance improvements ("for" loops, memory management)
* .klib improvements (keep options from .def file, faster inline handling)
* experimental workers API added (see [`sample`](https://github.com/JetBrains/kotlin-native/blob/master/samples/workers))
## v0.3 (Jun 2017) ##
* Preliminary support for x86-64 Windows hosts and targets
* Support for producing native activities on 32- and 64-bit Android targets
* Extended standard library (bitsets, character classification, regular expression)
* Preliminary support for Kotlin/Native library format (.klib)
* Preliminary source-level debugging support (stepping only, no variable inspection)
* Compiler switch `-entry` to select entry point
* Symbolic backtrace in runtime for unstripped binaries, for all supported targets
## v0.2 (May 2017) ##
* Added support for coroutines
* Fixed most stdlib incompatibilities
* Improved memory management performance
* Cross-module inline function support
* Unicode support independent from installed system locales
* Interoperability improvements
* file-based filtering in definition file
* stateless lambdas could be used as C callbacks
* any Unicode string could be passed to C function
* Very basic debugging support
* Improve compilation and linking performance
## v0.1 (Mar 2017) ##
Initial technical preview of Kotlin/Native
+3
View File
@@ -0,0 +1,3 @@
# CocoaPods integration
The content of this page is moved to https://kotlinlang.org/docs/native-cocoapods.html
+67
View File
@@ -0,0 +1,67 @@
# Code Coverage
Kotlin/Native has a code coverage support that is based on Clang's
[Source-based Code Coverage](https://clang.llvm.org/docs/SourceBasedCodeCoverage.html).
**Please note**:
1. Coverage support is in it's very early days and is in active development. Known issues are:
* Coverage information may be inaccurate.
* Line execution counts may be wrong.
2. Most of described functionality will be incorporated into Gradle plugin.
### Usage
#### TL;DR
```bash
kotlinc-native main.kt -Xcoverage
./program.kexe
llvm-profdata merge program.kexe.profraw -o program.profdata
llvm-cov report program.kexe -instr-profile program.profdata
```
#### Compiling with coverage enabled
There are 2 compiler flags that allows to generate coverage information:
* `-Xcoverage`. Generate coverage for immediate sources.
* `-Xlibrary-to-cover=<path>`. Generate coverage for specified `klib`.
Note that library also should be either linked via `-library/-l` compiler option or be a transitive dependency.
#### Running covered executable
After the execution of the compiled binary (ex. `program.kexe`) `program.kexe.profraw` will be generated.
By default it will be generated in the same location where binary was created. The are two ways to override this behavior:
* `-Xcoverage-file=<path>` compiler flag.
* `LLVM_PROFILE_FILE` environment variable. So if you run your program like this:
```
LLVM_PROFILE_FILE=build/program.profraw ./program.kexe
```
Then the coverage information will be stored to the `build` dir as `program.profraw`.
#### Parsing `*.profraw`
Generated file can be parsed with `llvm-profdata` utility. Basic usage:
```
llvm-profdata merge default.profraw -o program.profdata
```
See [command guide](http://llvm.org/docs/CommandGuide/llvm-profdata.html) for more options.
#### Creating reports
The last step is to create a report from the `program.profdata` file.
It can be done with `llvm-cov` utility (refer to [command guide](http://llvm.org/docs/CommandGuide/llvm-cov.html) for detailed usage).
For example, we can see a basic report using:
```
llvm-cov report program.kexe -instr-profile program.profdata
```
Or show a line-by-line coverage information in html:
```
llvm-cov show program.kexe -instr-profile program.profdata -format=html > report.html
```
### Sample
Usually coverage information is collected during running of the tests.
Please refer to `samples/coverage` to see how it can be done.
### Useful links
* [LLVM Code Coverage Mapping Format](https://llvm.org/docs/CoverageMappingFormat.html)
+3
View File
@@ -0,0 +1,3 @@
## Concurrency in Kotlin/Native
The content of this page is moved to https://kotlinlang.org/docs/native-concurrency.html
+3
View File
@@ -0,0 +1,3 @@
## Debugging
The content of this page is moved to https://kotlinlang.org/docs/native-debugging.html
+33
View File
@@ -0,0 +1,33 @@
# Kotlin/Native #
_Kotlin/Native_ is a LLVM backend for the Kotlin compiler, runtime
implementation and native code generation facility using LLVM toolchain.
_Kotlin/Native_ is primarily designed to allow compilation for platforms where
virtual machines are not desirable or possible (such as iOS, embedded targets),
or where developer is willing to produce reasonably-sized self-contained program
without need to ship an additional execution runtime.
_Kotlin/Native_ could be used either as standalone compiler toolchain or as Gradle
plugin. See [documentation](https://kotlinlang.org/docs/reference/native/gradle_plugin.html)
for more details on how to use this plugin.
Compile your programs like that:
export PATH=kotlin-native-<platform>-<version>/bin:$PATH
kotlinc hello.kt -o hello
For an optimized compilation use -opt:
kotlinc hello.kt -o hello -opt
To generate interoperability stubs create library definition file
(take a look on [Tetris sample](samples/tetris))
and run `cinterop` tool like this:
cinterop -def lib.def
See [C interop documentation](https://kotlinlang.org/docs/reference/native/c_interop.html)
for more information on how to use C libraries from _Kotlin/Native_.
See [`RELEASE_NOTES.md`](https://github.com/JetBrains/kotlin-native/blob/master/RELEASE_NOTES.md) for information on supported platforms and current limitations.
+1
View File
@@ -0,0 +1 @@
The content of this page is moved to https://kotlinlang.org/docs/native-faq.html
+729
View File
@@ -0,0 +1,729 @@
# Kotlin/Native Gradle plugin
Since 1.3.40, a separate Gradle plugin for Kotlin/Native is deprecated in favor of the `kotlin-multiplatform` plugin.
This plugin provides an IDE support along with support of the new multiplatform project model introduced in Kotlin 1.3.0.
Below you can find a short list of differences between `kotlin-platform-native` and `kotlin-muliplatform` plugins.
For more information see the `kotlin-muliplatform` [documentation page](https://kotlinlang.org/docs/mpp-discover-project.html).
For `kotlin-platform-native` reference see the [corresponding section](#kotlin-platform-native-reference).
### Applying the multiplatform plugin
To apply the `kotlin-multiplatform` plugin, just add the following snippet into your build script:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
plugins {
id("org.jetbrains.kotlin.multiplatform") version '1.3.40'
}
```
</div>
### Managing targets
With the `kotlin-platform-native` plugin a set of target platforms is specified as a list in properties of the main component:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
targets = ['macos_x64', 'linux_x64', 'mingw_x64']
}
```
</div>
With the `kotlin-multiplatform` plugin target platforms can be added into a project using special methods available in the `kotlin` extension.
Each method adds into a project one __target__ which can be accessed using the `targets` property. Each target can be configured independently
including output kinds, additional compiler options etc. See details about targets at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#setting-up-targets).
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
kotlin {
// These targets are declared without any target-specific settings.
macosX64()
linuxX64()
// You can specify a custom name used to access the target.
mingwX64("windows")
iosArm64 {
// Additional settings for ios_arm64.
}
// You can access declared targets using the `targets` property.
println(targets.macosX64)
println(targets.windows)
// You also can configure all native targets in a single block.
targets.withType(KotlinNativeTarget) {
// Native target configuration.
}
}
```
</div>
Each target includes two __compilations__: `main` and `test` compiling product and test sources respectively. A compilation is an abstraction
over a compiler invocation and described at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#configuring-compilations).
### Managing sources
With the `kotlin-platform-native` plugin source sets are used to separate test and product sources. Also you can specify different sources
for different platforms in the same source set:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
sourceSets {
// Adding target-independent sources.
main.kotlin.srcDirs += 'src/main/mySources'
// Adding Linux-specific code.
main.target('linux_x64').srcDirs += 'src/main/linux'
}
```
</div>
With the `kotlin-multiplatform` plugin __source__ __sets__ are also used to group sources but source files for different platforms are located in different source sets.
For each declared target two source sets are created: `<target-name>Main` and `<target-name>Test` containing product and test sources for this platform. Common for all
platforms sources are located in `commonMain` and `commonTest` source sets created by default. More information about source sets can be found
[here](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#configuring-source-sets).
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin {
sourceSets {
// Adding target-independent sources.
commonMain.kotlin.srcDirs += file("src/main/mySources")
// Adding Linux-specific code.
linuxX64Main.kotlin.srcDirs += file("src/main/linux")
}
}
```
</div>
### Managing dependencies
With the `kotlin-platform-native` plugin dependencies are configured in a traditional for Gradle way by grouping them into configurations
using the project `dependencies` block:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
dependencies {
implementation 'org.sample.test:mylibrary:1.0'
testImplementation 'org.sample.test:testlibrary:1.0'
}
```
</div>
The `kotlin-multiplatform` plugin also uses configurations under the hood but it also provides a `dependencies` block for each source set
allowing configuring dependencies of this sources set:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin.sourceSets {
commonMain {
dependencies {
implementation("org.sample.test:mylibrary:1.0")
}
}
commonTest {
dependencies {
implementation("org.sample.test:testlibrary:1.0")
}
}
}
```
</div>
Note that a module referenced by a dependency declared for `commonMain` or `commonTest` source set must be published using the `kotlin-multiplatform` plugin.
If you want to use libraries published by the `kotlin-platform-native` plugin, you need to declare a separate source set for common native sources.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin.sourceSets {
// Create a common source set used by native targets only.
nativeMain {
dependsOn(commonMain)
dependencies {
// Depend on a library published by the kotlin-platform-naive plugin.
implementation("org.sample.test:mylibrary:1.0")
}
}
// Configure all native platform sources sets to use it as a common one.
linuxX64Main.dependsOn(nativeMain)
macosX64Main.dependsOn(nativeMain)
// ...
}
```
</div>
See more info about dependencies at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#adding-dependencies).
### Output kinds
With the `kotlin-platform-native` plugin output kinds are specified as a list in properties of a component:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
// Compile the component into an executable and a Kotlin/Native library.
outputKinds = [EXECUTABLE, KLIBRARY]
}
```
</div>
With the `kotlin-multiplatform` plugin a compilation always produces a `*.klib` file. A separate `binaries` block is used to configure what
final native binaries should be produced by each target. Each binary can be configured independently including linker options, executable entry point etc.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin {
macosX64 {
binaries {
executable {
// Binary configuration: linker options, name, etc.
}
framework {
// ...
}
}
}
}
```
</div>
See more about native binaries declaration at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#building-final-native-binaries).
### Publishing
Both `kotlin-platform-native` and `kotlin-multiplatform` plugins automatically set up artifact publication when the
`maven-publish` plugin is applied. See details about publication at the [corresponding page](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#publishing-a-multiplatform-library).
Note that currently only Kotlin/Native libraries (`*.klib`) can be published for native targets.
### Cinterop support
With the `kotlin-platform-native` plugin interop with a native library can be declared in component dependencies:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
dependencies {
cinterop('mystdio') {
// Cinterop configuration.
}
}
}
```
</div>
With the `kotlin-multiplatform` plugin interops are configured as a part of a compilation (see details [here](https://kotlinlang.org/docs/reference/building-mpp-with-gradle.html#cinterop-support)).
The rest of an interop configuration is the same as for the `kotlin-platform-native` plugin.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
kotlin {
macosX64 {
compilations.main.cinterops {
mystdio {
// Cinterop configuration.
}
}
}
}
```
</div>
## `kotlin-platform-native` reference
### Overview
You may use the Gradle plugin to build _Kotlin/Native_ projects. Builds of the plugin are
[available](https://plugins.gradle.org/plugin/org.jetbrains.kotlin.platform.native) at the Gradle plugin portal, so you can apply it
using Gradle plugin DSL:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
plugins {
id "org.jetbrains.kotlin.platform.native" version "1.3.0-rc-146"
}
```
</div>
You also can get the plugin from a Bintray repository. In addition to releases, this repo contains old and development
versions of the plugin which are not available at the plugin portal. To get the plugin from the Bintray repo, include
the following snippet in your build script:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
buildscript {
repositories {
mavenCentral()
maven {
url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:1.3.0-rc-146"
}
}
apply plugin: 'org.jetbrains.kotlin.platform.native'
```
</div>
By default the plugin downloads the Kotlin/Native compiler during the first run. If you have already downloaded the compiler
manually you can specify the path to its root directory using `org.jetbrains.kotlin.native.home` project property (e.g. in `gradle.properties`).
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
org.jetbrains.kotlin.native.home=/home/user/kotlin-native-0.8
```
</div>
In this case the compiler will not be downloaded by the plugin.
### Source management
Source management in the `kotlin.platform.native` plugin is uniform with other Kotlin plugins and is based on source sets.
A source set is a group of Kotlin/Native source which may contain both common and platform-specific code. The plugin
provides a top-level script block `sourceSets` allowing you to configure source sets. Also it creates the default
source sets `main` and `test` (for production and test code respectively).
By default the production sources are located in `src/main/kotlin` and the test sources - in `src/test/kotlin`.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
sourceSets {
// Adding target-independent sources.
main.kotlin.srcDirs += 'src/main/mySources'
// Adding Linux-specific code. It will be compiled in Linux binaries only.
main.target('linux_x64').srcDirs += 'src/main/linux'
}
```
</div>
### Targets and output kinds
By default the plugin creates software components for the main and test source sets. You can access them via the
`components` container provided by Gradle or via the `component` property of a corresponding source set:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
// Main component.
components.main
sourceSets.main.component
// Test component.
components.test
sourceSets.test.component
```
</div>
Components allow you to specify:
* Targets (e.g. Linux/x64 or iOS/arm64 etc)
* Output kinds (e.g. executable, library, framework etc)
* Dependencies (including interop ones)
Targets can be specified by setting a corresponding component property:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
// Compile this component for 64-bit MacOS, Linux and Windows.
targets = ['macos_x64', 'linux_x64', 'mingw_x64']
}
```
</div>
The plugin uses the same notation as the compiler. By default, test component uses the same targets as specified for the main one.
Output kinds can also be specified using a special property:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
// Compile the component into an executable and a Kotlin/Native library.
outputKinds = [EXECUTABLE, KLIBRARY]
}
```
</div>
All constants used here are available inside a component configuration script block.
The plugin supports producing binaries of the following kinds:
* `EXECUTABLE` - an executable file;
* `KLIBRARY` - a Kotlin/Native library (*.klib);
* `FRAMEWORK` - an Objective-C framework;
* `DYNAMIC` - shared native library;
* `STATIC` - static native library.
Also each native binary is built in two variants (build types): `debug` (debuggable, not optimized) and `release` (not debuggable, optimized).
Note that Kotlin/Native libraries have only `debug` variant because optimizations are preformed only during compilation
of a final binary (executable, static lib etc) and affect all libraries used to build it.
### Compile tasks
The plugin creates a compilation task for each combination of the target, output kind, and build type. The tasks have the following naming convention:
compile<ComponentName><BuildType><OutputKind><Target>KotlinNative
For example `compileDebugKlibraryMacos_x64KotlinNative`, `compileTestDebugKotlinNative`.
The name contains the following parts (some of them may be empty):
* `<ComponentName>` - name of a component. Empty for the main component.
* `<BuildType>` - `Debug` or `Release`.
* `<OutputKind>` - output kind name, e.g. `Executabe` or `Dynamic`. Empty if the component has only one output kind.
* `<Target>` - target the component is built for, e.g. `Macos_x64` or `Wasm32`. Empty if the component is built only for one target.
Also the plugin creates a number of aggregate tasks allowing you to build all the binaries for a build type (e.g.
`assembleAllDebug`) or all the binaries for a particular target (e.g. `assembleAllWasm32`).
Basic lifecycle tasks like `assemble`, `build`, and `clean` are also available.
### Running tests
The plugin builds a test executable for all the targets specified for the `test` component. If the current host platform is
included in this list the test running tasks are also created. To run tests, execute the standard lifecycle `check` task:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
./gradlew check
```
</div>
### Dependencies
The plugin allows you to declare dependencies on files and other projects using traditional Gradle's mechanism of
configurations. The plugin supports Kotlin multiplatform projects allowing you to declare the `expectedBy` dependencies
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
dependencies {
implementation files('path/to/file/dependencies')
implementation project('library')
testImplementation project('testLibrary')
expectedBy project('common')
}
```
</div>
It's possible to depend on a Kotlin/Native library published earlier in a maven repo. The plugin relies on Gradle's
[metadata](https://github.com/gradle/gradle/blob/master/subprojects/docs/src/docs/design/gradle-module-metadata-latest-specification.md)
support so the corresponding feature must be enabled. Add the following line in your `settings.gradle`:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
enableFeaturePreview('GRADLE_METADATA')
```
</div>
Now you can declare a dependency on a Kotlin/Native library in the traditional `group:artifact:version` notation:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
dependencies {
implementation 'org.sample.test:mylibrary:1.0'
testImplementation 'org.sample.test:testlibrary:1.0'
}
```
</div>
Dependency declaration is also possible in the component block:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
dependencies {
implementation 'org.sample.test:mylibrary:1.0'
}
}
components.test {
dependencies {
implementation 'org.sample.test:testlibrary:1.0'
}
}
```
</div>
### Using cinterop
It's possible to declare a cinterop dependency for a component:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
dependencies {
cinterop('mystdio') {
// src/main/c_interop/mystdio.def is used as a def file.
// Set up compiler options
compilerOpts '-I/my/include/path'
// It's possible to set up different options for different targets
target('linux') {
compilerOpts '-I/linux/include/path'
}
}
}
}
```
</div>
Here an interop library will be built and added in the component dependencies.
Often it's necessary to specify target-specific linker options for a Kotlin/Native binary using an interop. It can be
done using the `target` script block:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
target('linux') {
linkerOpts '-L/path/to/linux/libs'
}
}
```
</div>
Also the `allTargets` block is available.
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
// Configure all targets.
allTargets {
linkerOpts '-L/path/to/libs'
}
}
```
</div>
### Publishing
In the presence of `maven-publish` plugin the publications for all the binaries built are created. The plugin uses Gradle
metadata to publish the artifacts so this feature must be enabled (see the [dependencies](#dependencies) section).
Now you can publish the artifacts with the standard Gradle `publish` task:
<div class="sample" markdown="1" theme="idea" mode="shell">
```bash
./gradlew publish
```
</div>
Only `EXECUTABLE` and `KLIBRARY` binaries are published currently.
The plugin allows you to customize the pom generated for the publication with the `pom` code block available for every component:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
components.main {
pom {
withXml {
def root = asNode()
root.appendNode('name', 'My library')
root.appendNode('description', 'A Kotlin/Native library')
}
}
}
```
</div>
### Serialization plugin
The plugin is shipped with a customized version of the `kotlinx.serialization` plugin. To use it you don't have to
add new buildscript dependencies, just apply the plugins and add a dependency on the serialization library:
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
apply plugin: 'org.jetbrains.kotlin.platform.native'
apply plugin: 'kotlinx-serialization-native'
dependencies {
implementation 'org.jetbrains.kotlinx:kotlinx-serialization-runtime-native'
}
```
</div>
The [example project](https://github.com/ilmat192/kotlin-native-serialization-sample) for details.
### DSL example
In this section a commented DSL is shown.
See also the example projects that use this plugin, e.g.
[Kotlinx.coroutines](https://github.com/Kotlin/kotlinx.coroutines),
[MPP http client](https://github.com/e5l/http-client-common/tree/master/samples/ios-test-application)
<div class="sample" markdown="1" theme="idea" mode="groovy">
```groovy
plugins {
id "org.jetbrains.kotlin.platform.native" version "1.3.0-rc-146"
}
sourceSets.main {
// Plugin uses Gradle's source directory sets here,
// so all the DSL methods available in SourceDirectorySet can be called here.
// Platform independent sources.
kotlin.srcDirs += 'src/main/customDir'
// Linux-specific sources
target('linux').srcDirs += 'src/main/linux'
}
components.main {
// Set up targets
targets = ['linux_x64', 'macos_x64', 'mingw_x64']
// Set up output kinds
outputKinds = [EXECUTABLE, KLIBRARY, FRAMEWORK, DYNAMIC, STATIC]
// Specify custom entry point for executables
entryPoint = "org.test.myMain"
// Target-specific options
target('linux_x64') {
linkerOpts '-L/linux/lib/path'
}
// Targets independent options
allTargets {
linkerOpts '-L/common/lib/path'
}
dependencies {
// Dependency on a published Kotlin/Native library.
implementation 'org.test:mylib:1.0'
// Dependency on a project
implementation project('library')
// Cinterop dependency
cinterop('interop-name') {
// Def-file describing the native API.
// The default path is src/main/c_interop/<interop-name>.def
defFile project.file("deffile.def")
// Package to place the Kotlin API generated.
packageName 'org.sample'
// Options to be passed to compiler and linker by cinterop tool.
compilerOpts 'Options for native stubs compilation'
linkerOpts 'Options for native stubs'
// Additional headers to parse.
headers project.files('header1.h', 'header2.h')
// Directories to look for headers.
includeDirs {
// All objects accepted by the Project.file method may be used with both options.
// Directories for header search (an analogue of the -I<path> compiler option).
allHeaders 'path1', 'path2'
// Additional directories to search headers listed in the 'headerFilter' def-file option.
// -headerFilterAdditionalSearchPrefix command line option analogue.
headerFilterOnly 'path1', 'path2'
}
// A shortcut for includeDirs.allHeaders.
includeDirs "include/directory" "another/directory"
// Pass additional command line options to the cinterop tool.
extraOpts '-verbose'
// Additional configuration for Linux.
target('linux') {
compilerOpts 'Linux-specific options'
}
}
}
// Additional pom settings for publication.
pom {
withXml {
def root = asNode()
root.appendNode('name', 'My library')
root.appendNode('description', 'A Kotlin/Native library')
}
}
// Additional options passed to the compiler.
extraOpts '--time'
}
```
</div>
+342
View File
@@ -0,0 +1,342 @@
## Profiling the compiler
### Profiling with Async profiler
IDEA Ultimate contains an Async sampling profiler.
As of IDEA 2018.3 Async sampling profiler is still an experimental feature, so use Ctrl-Alt-Shift-/ on Linux,
Cmd-Alt-Shift-/ on macOS to activate it. Then start compilation in CLI with `--no-daemon` and
`-Porg.gradle.workers.max=1` flags (running Gradle task with the profiler doesn't seem to work properly) and attach
to the running process using "Run/Attach Profiler to Local Process" menu item.
Select "K2NativeKt" or "org.jetbrains.kotlin.cli.utilities.MainKt" process.
On completion profiler will produce flame diagram which could be navigated with the mouse
(click-drag moves, wheel scales). More RAM in IDE (>4G) could be helpful when analyzing longer runs.
As Async is a sampling profiler, to get sensible coverage longer runs are important.
### Profiling with YourKit
Unlike Async profiler in IDEA, YourKit can work as an exact profiler and provide complete coverage
of all methods along with exact invocation counters.
Install the YourKit profiler for your platform from https://www.yourkit.com/java/profiler.
Set AGENT variable to the JVMTI agent provided by YourKit, like
export AGENT=/Applications/YourKit-Java-Profiler-2018.04.app/Contents/Resources/bin/mac/libyjpagent.jnilib
To profile standard library compilation:
./gradlew -PstdLibJvmArgs="-agentpath:$AGENT=probe_disable=*,listen=all,tracing" dist
To profile platform libraries start build of proper target like this:
./gradlew -PplatformLibsJvmArgs="-agentpath:$AGENT=probe_disable=*,listen=all,tracing" ios_arm64PlatformLibs
To profile standalone code compilation use:
JAVA_OPTS="-agentpath:$AGENT=probe_disable=*,listen=all,tracing" ./dist/bin/konanc file.kt
Then attach to the desired application in YourKit GUI and use CPU tab to inspect CPU consuming methods.
Saving the trace may be needed for more analysis. Adjusting `-Xmx` in `$HOME/.yjp/ui.ini` could help
with the big traces.
To perform memory profiling follow the steps above, and after attachment to the running process
use "Start Object Allocation Recording" button. See https://www.yourkit.com/docs/java/help/allocations.jsp for more details.
## Compiler Gradle options
There are several gradle flags one can use for Konan build.
* **-Pbuild_flags** passes flags to the compiler used to build stdlib
./gradlew -Pbuild_flags="--disable lower_inline --print_ir" stdlib
* **-Pshims** compiles LLVM interface with tracing "shims". Allowing one
to trace the LLVM calls from the compiler.
Make sure to rebuild the project.
./gradlew -Pshims=true dist
## Compiler environment variables
* **KONAN_DATA_DIR** changes `.konan` local data directory location (`$HOME/.konan` by default). Works both with cli compiler and gradle plugin
## Testing
### Compiler integration tests
To run blackbox compiler tests from JVM Kotlin use (takes time):
./gradlew run_external
To update the blackbox compiler tests set TeamCity build number in `gradle.properties`:
testKotlinVersion=<build number>
* **-Pfilter** allows one to choose test files to run.
./gradlew -Pfilter=overflowLong.kt run_external
* **-Pprefix** allows one to choose external test directories to run. Only tests from directories with given prefix will be executed.
./gradlew -Pprefix=build_external_compiler_codegen_box_cast run_external
* **-Ptest_flags** passes flags to the compiler used to compile tests
./gradlew -Ptest_flags="--time" backend.native:tests:array0
* **-Ptest_target** specifies cross target for a test run.
./gradlew -Ptest_target=raspberrypi backend.native:tests:array0
* **-Premote=user@host** sets remote test execution login/hostname. Good for cross compiled tests.
./gradlew -Premote=kotlin@111.22.33.444 backend.native:tests:run
* **-Ptest_verbose** enables printing compiler args and other helpful information during a test execution.
./gradlew -Ptest_verbose :backend.native:tests:mpp_optional_expectation
* **-Ptest_two_stage** enables two-stage compilation of tests. If two-stage compilation is enabled, test sources are compiled into a klibrary
and then a final native binary is produced from this klibrary using the -Xinclude compiler flag.
./gradlew -Ptest_two_stage backend.native:tests:array0
* **-Ptest_with_cache_kind=static|dynamic** enables using caches during testing.
### Runtime unit tests
To run runtime unit tests on the host machine for both mimalloc and the standard allocator:
./gradlew hostRuntimeTests
To run tests for only one of these two allocators, run `hostStdAllocRuntimeTests` or `hostMimallocRuntimeTests`.
We use [Google Test](https://github.com/google/googletest) to execute the runtime unit tests. The build automatically fetches
the specified Google Test revision to `runtime/googletest`. It is possible to manually modify the downloaded GTest sources for debug
purposes; the build will not overwrite them by default.
To forcibly redownload Google Test when running tests, use the corresponding project property:
./gradlew hostRuntimeTests -Prefresh-gtest
or run the `downloadGTest` task directly with the `--refresh` CLI key:
./gradlew downloadGTest --refresh
To use a local GTest copy instead of the downloaded one, add the following line to `runtime/build.gradle.kts`:
googletest.useLocalSources("<path to local GTest sources>")
## Performance measurement
Firstly, it's necessary to build analyzer tool to have opportunity to compare different performance results:
cd tools/benchmarksAnalyzer
../../gradlew build
To measure performance of Kotlin/Native compiler on existing benchmarks:
cd performance
../gradlew :konanRun
**NOTE**: **konanRun** task needs built compiler and libs. To test against working tree make sure to run
./gradlew dist distPlatformLibs
before **konanRun**
**konanRun** task can be run separately for one/several benchmark applications:
cd performance
../gradlew :cinterop:konanRun
**konanRun** task has parameter `filter` which allows to run only some subset of benchmarks:
cd performance
../gradlew :cinterop:konanRun --filter=struct,macros
Or you can use `filterRegex` if you want to specify the filter as regexes:
cd performance
../gradlew :ring:konanRun --filterRegex=String.*,Loop.*
There us also verbose mode to follow progress of running benchmarks
cd performance
../gradlew :cinterop:konanRun --verbose
> Task :performance:cinterop:konanRun
[DEBUG] Warm up iterations for benchmark macros
[DEBUG] Running benchmark macros
...
There are also tasks for running benchmarks on JVM (pay attention, some benchmarks e.g. cinterop benchmarks can't be run on JVM)
cd performance
../gradlew :jvmRun
Files with results of benchmarks run are saved in `performance/build/nativeReport.json` for konanRun and `jvmReport.json` for jvmRun.
You can change the output filename by setting the `nativeJson` property for konanRun and `jvmJson` for jvmRun:
cd performance
../gradlew :ring:konanRun --filter=String.*,Loop.* -PnativeJson=stringsAndLoops.json
You can use the `compilerArgs` property to pass flags to the compiler used to compile the benchmarks:
cd performance
../gradlew :konanRun -PcompilerArgs="--time -g"
To compare different results run benchmarksAnalyzer tool:
cd tools/benchmarksAnalyzer/build/bin/<target>/benchmarksAnalyzerReleaseExecutable/
./benchmarksAnalyzer.kexe <file1> <file2>
Tool has several renders which allow produce output report in different forms (text, html, etc.). To set up render use flag `--render/-r`.
Output can be redirected to file with flag `--output/-o`.
To get detailed information about supported options, please use `--help/-h`.
Analyzer tool can compare both local files and files placed on Artifactory/TeamCity.
File description stored on Artifactory
artifactory:<build number>:<target (Linux|Windows10|MacOSX)>:<filename>
Example
artifactory:1.2-dev-7942:Windows10:nativeReport.json
File description stored on TeamCity
teamcity:<build locator>:<filename>
Example
teamcity:id:42491947:nativeReport.json
Pay attention, user and password information(with flag `-u <username>:<password>`) should be provided to get data from TeamCity.
## Composite build and testing
If you have a fix spanning both Kotlin and Kotlin/native workspaces you need to be able to test Kotlin/Native composite build. Here's how to do it manually:
### Have a composite build with the proper Kotlin tag.
Find the version of Kotlin the current native is guaranteed to build with.
The version is specified in `kotlin-native/gradle.properties`. For example:
```
kotlinVersion=1.3.70-dev-1526
```
Checkout `kotlin` workspace to tag `build-1.3.70-dev-1526`. Make sure its path ends with `.../kotlin`.
Otherwise issues will arise.
Direct `kotlin-native` build to the kotlin with `kotlinProjectPath` in native's `gradle.properties`.
Now you have the kotlin + kotlin-native combination that is known to build.
Apply your fix on top of both workspaces and run
```
$ ./gradlew dist
```
in `kotlin-native` to check the buildability.
### Testing native
For a quick check use:
```
$ ./gradlew sanity 2>&1 | tee log
```
For a longer, more thorough testing build the complete build. Make sure you are running it on a macOS.
Have a complete build:
```
$ ./gradlew bundle # includes dist as its part
```
then run two test sets:
```
$ ./gradlew backend.native:tests:run 2>&1 | tee log
$ ./gradlew backend.native:tests:runExternal -Ptest_two_stage=true 2>&1 | tee log
```
## LLVM
See [BUILDING_LLVM.md](BUILDING_LLVM.md) if you want to build and use your own LLVM distribution
instead of provided one.
### Using different LLVM distributions as part of Kotlin/Native compilation pipeline.
`llvmHome.<HOST_NAME>` variable in `<distribution_location>/konan/konan.properties` controls
which LLVM distribution Kotlin/Native will use in its compilation pipeline.
You can replace its value with either `$llvm.<HOST_NAME>.{dev, user}` to use one of predefined distributions
or pass an absolute to your own distribution.
Don't forget to set `llvmVersion.<HOST_NAME>` to the version of your LLVM distribution.
#### Example. Using LLVM from an absolute path.
Assuming LLVM distribution is installed at `/usr` path, one can specify a path to it
with the `-Xoverride-konan-properties` option:
```
konanc main.kt -Xoverride-konan-properties=llvmHome.linux_x64=/usr
```
### Playing with compilation pipeline.
Following compiler phases control different parts of LLVM pipeline:
1. `LinkBitcodeDependencies`. Linkage of produced bitcode with runtime and some other dependencies.
2. `BitcodeOptimization`. Running LLVM optimization pipeline.
3. `ObjectFiles`. Compilation of bitcode with Clang.
For example, pass `-Xdisable-phases=BitcodeOptimization` to skip optimization pipeline.
Note that disabling `LinkBitcodeDependencies` or `ObjectFiles` will break compilation pipeline.
Compiler takes options for Clang from [konan.properties](konan/konan.properties) file
by combining `clangFlags.<TARGET>` and `clang<Noopt/Opt/Debug>Flags.<TARGET>` properties.
Use `-Xoverride-konan-properties=<key_1=value_1; ...;key_n=value_n>` flag to override default values.
Please note:
1. Kotlin Native passes bitcode files to Clang instead of C or C++, so many flags won't work.
2. `-cc1 -emit-obj` should be passed because Kotlin/Native calls linker by itself.
3. Use `clang -cc1 -help` to see a list of available options.
Another useful compiler option is `-Xtemporary-files-dir=<PATH>` which allows
to specify a directory for intermediate compiler artifacts like bitcode and object files.
#### Example 1. Bitcode right after IR to Bitcode translation.
```shell script
konanc main.kt -produce bitcode -o bitcode.bc
```
#### Example 2. Bitcode after LLVM optimizations.
```shell script
konanc main.kt -Xtemporary-files-dir=<PATH> -o <OUTPUT_NAME>
```
`<PATH>/<OUTPUT_NAME>.kt.bc` will contain bitcode after LLVM optimization pipeline.
#### Example 3. Replace predefined LLVM pipeline with Clang options.
```shell script
CLANG_FLAGS="clangFlags.macos_x64=-cc1 -emit-obj;clangNooptFlags.macos_x64=-O2"
konanc main.kt -Xdisable-phases=BitcodeOptimization -Xoverride-konan-properties="$CLANG_FLAGS"
```
## Running Clang the same way Kotlin/Native compiler does
Kotlin/Native compiler (including `cinterop` tool) has machinery that manages LLVM, Clang and native SDKs for supported targets
and runs bundled Clang with proper arguments.
To utilize this machinery, use `$dist/bin/run_konan clang $tool $target $arguments`, e.g.
```
$dist/bin/run_konan clang clang ios_arm64 1.c
```
will print and run the following command:
```
~/.konan/dependencies/clang-llvm-apple-8.0.0-darwin-macos/bin/clang \
-B/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin \
-fno-stack-protector -stdlib=libc++ -arch arm64 \
-isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS13.5.sdk \
-miphoneos-version-min=9.0 1.c
```
The similar helper is available for LLVM tools, `$dist/bin/run_konan llvm $tool $arguments`.
+3
View File
@@ -0,0 +1,3 @@
# Immutability in Kotlin/Native
The content of this page is moved to https://kotlinlang.org/docs/native-immutability.html
+3
View File
@@ -0,0 +1,3 @@
# _Kotlin/Native_ interoperability #
The content of this page is moved to https://kotlinlang.org/docs/native-c-interop.html
+3
View File
@@ -0,0 +1,3 @@
# Symbolicating iOS crash reports
The content of this page is moved to https://kotlinlang.org/docs/native-ios-symbolication.html
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>
+23
View File
@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="1.8" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$/.." />
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/Indexer" />
<option value="$PROJECT_DIR$/Runtime" />
<option value="$PROJECT_DIR$/StubGenerator" />
<option value="$PROJECT_DIR$/../InteropExample" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>
@@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="Gradle: org.jetbrains.kotlin:kotlin-runtime:1.0.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-runtime/1.0.3/10f40d016700cf4287e49fa1d51c2a8507e9b946/kotlin-runtime-1.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-runtime/1.0.3/44d99f6d9a1d69f25797590cdd3efe2cbce8bcb6/kotlin-runtime-1.0.3-sources.jar!/" />
</SOURCES>
</library>
</component>
@@ -0,0 +1,11 @@
<component name="libraryTable">
<library name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.0.3">
<CLASSES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.0.3/20738122b53399036c321eeb84687367757d622a/kotlin-stdlib-1.0.3.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/1.0.3/efeac5a1b15300f742d2119667f90df44230b94a/kotlin-stdlib-1.0.3-sources.jar!/" />
</SOURCES>
</library>
</component>
+10
View File
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Indexer/Indexer.iml" filepath="$PROJECT_DIR$/.idea/modules/Indexer/Indexer.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/Runtime/Runtime.iml" filepath="$PROJECT_DIR$/.idea/modules/Runtime/Runtime.iml" />
<module fileurl="file://$PROJECT_DIR$/.idea/modules/StubGenerator/StubGenerator.iml" filepath="$PROJECT_DIR$/.idea/modules/StubGenerator/StubGenerator.iml" />
</modules>
</component>
</project>
+24
View File
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":Interop:Indexer" external.linked.project.path="$MODULE_DIR$/../../../Indexer" external.root.project.path="$MODULE_DIR$/../../.." external.system.id="GRADLE" external.system.module.group="experiments.Interop" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/../../../Indexer/build/classes/main" />
<output-test url="file://$MODULE_DIR$/../../../Indexer/build/classes/test" />
<exclude-output />
<content url="file://$MODULE_DIR$/../../../Indexer">
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/prebuilt/nativeInteropStubs" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/../../../Indexer/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/../../../Indexer/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/../../../Indexer/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="Runtime" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.0.3" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-runtime:1.0.3" level="project" />
</component>
</module>
+22
View File
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":Interop:Runtime" external.linked.project.path="$MODULE_DIR$/../../../Runtime" external.root.project.path="$MODULE_DIR$/../../.." external.system.id="GRADLE" external.system.module.group="experiments.Interop" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/../../../Runtime/build/classes/main" />
<output-test url="file://$MODULE_DIR$/../../../Runtime/build/classes/test" />
<exclude-output />
<content url="file://$MODULE_DIR$/../../../Runtime">
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/../../../Runtime/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/../../../Runtime/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/../../../Runtime/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.0.3" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-runtime:1.0.3" level="project" />
</component>
</module>
@@ -0,0 +1,24 @@
<?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.id=":Interop:StubGenerator" external.linked.project.path="$MODULE_DIR$/../../../StubGenerator" external.root.project.path="$MODULE_DIR$/../../.." external.system.id="GRADLE" external.system.module.group="experiments.Interop" external.system.module.version="unspecified" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="false">
<output url="file://$MODULE_DIR$/../../../StubGenerator/build/classes/main" />
<output-test url="file://$MODULE_DIR$/../../../StubGenerator/build/classes/test" />
<exclude-output />
<content url="file://$MODULE_DIR$/../../../StubGenerator">
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/test/java" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/test/kotlin" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/../../../StubGenerator/src/test/resources" type="java-test-resource" />
<excludeFolder url="file://$MODULE_DIR$/../../../StubGenerator/.gradle" />
<excludeFolder url="file://$MODULE_DIR$/../../../StubGenerator/build" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="Indexer" />
<orderEntry type="module" module-name="Runtime" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-stdlib:1.0.3" level="project" />
<orderEntry type="library" name="Gradle: org.jetbrains.kotlin:kotlin-runtime:1.0.3" level="project" />
</component>
</module>
@@ -0,0 +1,194 @@
/*
* 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.
*/
import org.jetbrains.gradle.plugins.tools.lib
import org.jetbrains.gradle.plugins.tools.solib
import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.konan.target.*
import org.jetbrains.kotlin.konan.target.ClangArgs
import org.jetbrains.kotlin.konan.target.Family.*
import org.jetbrains.kotlin.konan.target.HostManager.Companion.hostIsMac
plugins {
`kotlin`
`native-interop-plugin`
`native`
}
val libclangextProject = project(":kotlin-native:libclangext")
val libclangextTask = libclangextProject.path + ":build"
val libclangextDir = libclangextProject.buildDir
val libclangextIsEnabled = libclangextProject.findProperty("isEnabled")!! as Boolean
val llvmDir = project.findProperty("llvmDir")
val libclang =
if (HostManager.hostIsMingw) {
"bin/libclang.dll"
} else {
"lib/${System.mapLibraryName("clang")}"
}
val cflags = mutableListOf( "-I$llvmDir/include",
"-I${project(":kotlin-native:libclangext").projectDir.absolutePath}/src/main/include",
*platformManager.hostPlatform.clang.hostCompilerArgsForJni)
if (!HostManager.hostIsMingw) {
cflags += "-fPIC"
}
val ldflags = mutableListOf("$llvmDir/$libclang", "-L${libclangextDir.absolutePath}", "-lclangext")
if (libclangextIsEnabled) {
assert(HostManager.hostIsMac)
ldflags.addAll(listOf("-Wl,--no-demangle", "-Wl,-search_paths_first", "-Wl,-headerpad_max_install_names", "-Wl,-U,_futimens",
"-Wl,-U,__ZN4llvm7remarks11parseFormatENS_9StringRefE",
"-Wl,-U,__ZN4llvm7remarks22createRemarkSerializerENS0_6FormatENS0_14SerializerModeERNS_11raw_ostreamE",
"-Wl,-U,__ZN4llvm7remarks14YAMLSerializerC1ERNS_11raw_ostreamENS0_14UseStringTableE"))
val llvmLibs = listOf(
"clangAST", "clangASTMatchers", "clangAnalysis", "clangBasic", "clangDriver", "clangEdit",
"clangFrontend", "clangFrontendTool", "clangLex", "clangParse", "clangSema", "clangEdit",
"clangRewrite", "clangRewriteFrontend", "clangStaticAnalyzerFrontend",
"clangStaticAnalyzerCheckers", "clangStaticAnalyzerCore", "clangSerialization",
"clangToolingCore",
"clangTooling", "clangFormat", "LLVMTarget", "LLVMMC", "LLVMLinker", "LLVMTransformUtils",
"LLVMBitWriter", "LLVMBitReader", "LLVMAnalysis", "LLVMProfileData", "LLVMCore",
"LLVMSupport", "LLVMBinaryFormat", "LLVMDemangle"
).map { "$llvmDir/lib/lib${it}.a" }
ldflags.addAll(llvmLibs)
ldflags.addAll(listOf("-lpthread", "-lz", "-lm", "-lcurses"))
}
val solib = when{
HostManager.hostIsMingw -> "dll"
HostManager.hostIsMac -> "dylib"
else -> "so"
}
val lib = if (HostManager.hostIsMingw) "lib" else "a"
native {
val obj = if (HostManager.hostIsMingw) "obj" else "o"
val host = rootProject.project(":kotlin-native").extra["hostName"]
val hostLibffiDir = rootProject.project(":kotlin-native").extra["${host}LibffiDir"]
val cxxflags = listOf("-std=c++11", *cflags.toTypedArray())
suffixes {
(".c" to ".$obj") {
tool(*platformManager.hostPlatform.clang.clangC("").toTypedArray())
flags(*cflags.toTypedArray(),
"-c", "-o", ruleOut(), ruleInFirst())
}
(".cpp" to ".$obj") {
tool(*platformManager.hostPlatform.clang.clangCXX("").toTypedArray())
flags(*cxxflags.toTypedArray(), "-c", "-o", ruleOut(), ruleInFirst())
}
}
sourceSet {
"main-c" {
dir("prebuilt/nativeInteropStubs/c")
}
"main-cpp" {
dir("src/nativeInteropStubs/cpp")
}
}
val objSet = arrayOf(sourceSets["main-c"]!!.transform(".c" to ".$obj"),
sourceSets["main-cpp"]!!.transform(".cpp" to ".$obj"))
target(solib("clangstubs"), *objSet) {
tool(*platformManager.hostPlatform.clang.clangCXX("").toTypedArray())
flags(
"-shared",
"-o", ruleOut(), *ruleInAll(),
*ldflags.toTypedArray())
}
}
tasks.named(solib("clangstubs")).configure {
dependsOn(":kotlin-native:libclangext:${lib("clangext")}")
}
sourceSets {
"main" {
java {
srcDirs("prebuilt/nativeInteropStubs/kotlin")
}
kotlin{
target {
}
}
}
}
dependencies {
compile(project(":kotlin-stdlib"))
compile(project(":kotlin-native:Interop:Runtime"))
}
val nativelibs = project.tasks.create<Copy>("nativelibs") {
dependsOn(solib("clangstubs"))
from("$buildDir/")
into("$buildDir/nativelibs/")
}
kotlinNativeInterop {
this.create("clang") {
defFile("clang.def")
compilerOpts(cflags)
linkerOpts = ldflags
genTask.dependsOn(libclangextTask)
genTask.inputs.dir(libclangextDir)
}
}
val compileKotlin: org.jetbrains.kotlin.gradle.tasks.KotlinCompile by tasks
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
allWarningsAsErrors = true
freeCompilerArgs = listOf("-Xskip-prerelease-check")
}
}
tasks.matching { it.name == "linkClangstubsSharedLibrary" }.all {
dependsOn(libclangextTask)
inputs.dir(libclangextDir)
}
tasks.create("updatePrebuilt") {
dependsOn("genClangInteropStubs")
doLast {
copy {
from("$buildDir/nativeInteropStubs/clang/kotlin") {
include("clang/clang.kt")
}
into("prebuilt/nativeInteropStubs/kotlin")
}
copy {
from("$buildDir/interopTemp") {
include("clangstubs.c")
}
into("prebuilt/nativeInteropStubs/c")
}
}
}
+16
View File
@@ -0,0 +1,16 @@
headers = clang-c/Index.h clang-c/ext.h clang-c/ExtVector.h
headerFilter = clang-c/**
compiler = clang
compilerOpts = -std=c99 -fPIC
linkerOpts.linux = -Wl,-z,noexecstack
linker = clang++
linkerOpts = -fPIC
strictEnums = CXErrorCode CXCursorKind CXTypeKind CXDiagnosticSeverity CXLoadDiag_Error CXSaveError \
CXTUResourceUsageKind CXLinkageKind CXVisibilityKind CXLanguageKind CXCallingConv CXChildVisitResult \
CXTokenKind CXEvalResultKind CXVisitorResult CXResult CXIdxEntityKind
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,35 @@
/*
* 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.native.interop.indexer
import java.io.File
class HeaderToIdMapper(sysRoot: String) {
private val headerPathToId = mutableMapOf<String, HeaderId>()
private val sysRoot = File(sysRoot).canonicalFile.toPath()
internal fun getHeaderId(filePath: String) = headerPathToId.getOrPut(filePath) {
val path = File(filePath).canonicalFile.toPath()
val headerIdValue = if (path.startsWith(sysRoot)) {
val relative = sysRoot.relativize(path)
relative.toString()
} else {
headerContentsHash(filePath)
}
HeaderId(headerIdValue)
}
}
@@ -0,0 +1,322 @@
/*
* 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.native.interop.indexer
import clang.*
import kotlinx.cinterop.*
import java.io.File
/**
* Finds all "macro constants" and registers them as [NativeIndex.constants] in given index.
*/
internal fun findMacros(
nativeIndex: NativeIndexImpl,
compilation: CompilationWithPCH,
translationUnit: CXTranslationUnit,
headers: Set<CXFile?>
) {
val names = collectMacroNames(nativeIndex, translationUnit, headers)
// TODO: apply user-defined filters.
val macros = expandMacros(compilation, names, typeConverter = { nativeIndex.convertType(it) })
macros.filterIsInstanceTo(nativeIndex.macroConstants)
macros.filterIsInstanceTo(nativeIndex.wrappedMacros)
}
private typealias TypeConverter = (CValue<CXType>) -> Type
/**
* For each name expands the macro with this name declared in the library,
* checking if it gets expanded to a constant expression.
*
* Note: in the worst case this method parses the code against the library a lot of times,
* so it requires library headers precompiled to significantly speed up the parsing and avoid visiting headers' AST.
*
* @return the list of constants.
*/
private fun expandMacros(
library: CompilationWithPCH,
names: List<String>,
typeConverter: TypeConverter
): List<MacroDef> {
withIndex(excludeDeclarationsFromPCH = true) { index ->
val sourceFile = library.createTempSource()
val compilerArgs = library.compilerArgs.toMutableList()
// We disable implicit function declaration to filter out cases when a macro is expanded as a function
// or function-like construction (e.g. #define FOO throw()) but such a function is undeclared.
compilerArgs += "-Werror=implicit-function-declaration"
// Ensure libclang reports all errors:
compilerArgs += "-ferror-limit=0"
val translationUnit = parseTranslationUnit(index, sourceFile, compilerArgs, options = 0)
try {
val nameToMacroDef = mutableMapOf<String, MacroDef>()
val unprocessedMacros = names.toMutableList()
// Note: will be slow for a library with a lot of macros having unbalanced '{'. TODO: Optimize this case too.
while (unprocessedMacros.isNotEmpty()) {
val processedMacros =
tryExpandMacros(library, translationUnit, sourceFile, unprocessedMacros, typeConverter)
unprocessedMacros -= (processedMacros.keys + unprocessedMacros.first())
// Note: removing first macro should not have any effect, doing this to ensure the loop is finite.
processedMacros.forEach { (name, macroDef) ->
if (macroDef != null) nameToMacroDef[name] = macroDef
}
}
return names.mapNotNull { nameToMacroDef[it] }
} finally {
clang_disposeTranslationUnit(translationUnit)
}
}
}
/**
* Tries to expand macros [names] defined in [library].
* Returns the map of successfully processed macros with resulting constant as a value
* or `null` if the result is not a constant (expression).
*
* As a side effect, modifies the [sourceFile] and reparses the [translationUnit].
*/
private fun tryExpandMacros(
library: CompilationWithPCH,
translationUnit: CXTranslationUnit,
sourceFile: File,
names: List<String>,
typeConverter: TypeConverter
): Map<String, MacroDef?> {
reparseWithCodeSnippets(library, translationUnit, sourceFile, names)
val macrosWithErrorsInSnippetFunctionHeader = mutableSetOf<String>()
val macrosWithErrorsInSnippetFunctionBody = mutableSetOf<String>()
val preambleSize = library.preambleLines.size
translationUnit.getErrorLineNumbers().map { it - preambleSize - 1 }.forEach { lineNumber ->
val index = lineNumber / CODE_SNIPPET_LINES_NUMBER
if (index >= 0 && index < names.size) {
when (lineNumber % CODE_SNIPPET_LINES_NUMBER) {
0 -> macrosWithErrorsInSnippetFunctionHeader += names[index]
1 -> macrosWithErrorsInSnippetFunctionBody += names[index]
else -> {}
}
}
}
val result = mutableMapOf<String, MacroDef?>()
visitChildren(translationUnit) { cursor, _ ->
if (cursor.kind == CXCursorKind.CXCursor_FunctionDecl) {
val functionName = getCursorSpelling(cursor)
if (functionName.startsWith(CODE_SNIPPET_FUNCTION_NAME_PREFIX)) {
val macroName = functionName.removePrefix(CODE_SNIPPET_FUNCTION_NAME_PREFIX)
if (macroName in macrosWithErrorsInSnippetFunctionHeader) {
// Code snippet is likely affected by previous macros' snippets, skip it for now.
} else {
result[macroName] = if (macroName in macrosWithErrorsInSnippetFunctionBody) {
// Code snippet is likely unaffected by previous ones but parsed with its own errors,
// so suppose macro is processed successfully as non-expression:
null
} else {
processCodeSnippet(cursor, macroName, typeConverter)
}
}
}
}
CXChildVisitResult.CXChildVisit_Continue
}
return result
}
private const val CODE_SNIPPET_LINES_NUMBER = 3
private const val CODE_SNIPPET_FUNCTION_NAME_PREFIX = "kni_indexer_function_"
/**
* Adds code snippets to be then processed with [processCodeSnippet] to the [sourceFile]
* and reparses the [translationUnit].
*
* - If a code snippet allows extracting the constant value using libclang API, we'll add a [ConstantDef] in the
* native index and generate a Kotlin constant for it.
* - If the expression type can be inferred by libclang, we'll add a [WrappedMacroDef] in the native index and
* generate a bridge for this macro.
* - Otherwise the macro is skipped.
*/
private fun reparseWithCodeSnippets(library: CompilationWithPCH,
translationUnit: CXTranslationUnit, sourceFile: File,
names: List<String>) {
// TODO: consider using CXUnsavedFile instead of writing the modified file to OS file system.
sourceFile.bufferedWriter().use { writer ->
writer.appendPreamble(library)
names.forEach { name ->
val codeSnippetLines = when (library.language) {
Language.C, Language.OBJECTIVE_C ->
listOf("void $CODE_SNIPPET_FUNCTION_NAME_PREFIX$name() {",
" __auto_type KNI_INDEXER_VARIABLE_$name = $name;",
"}")
}
assert(codeSnippetLines.size == CODE_SNIPPET_LINES_NUMBER)
codeSnippetLines.forEach { writer.appendLine(it) }
}
}
clang_reparseTranslationUnit(translationUnit, 0, null, 0)
}
/**
* Checks that [functionCursor] is parsed exactly as expected for the code appended by [reparseWithCodeSnippets],
* and returns the constant on success.
*/
private fun processCodeSnippet(
functionCursor: CValue<CXCursor>,
name: String,
typeConverter: TypeConverter
): MacroDef? {
val kindsToSkip = setOf(CXCursorKind.CXCursor_CompoundStmt)
var state = VisitorState.EXPECT_NODES_TO_SKIP
var evalResultOrNull: CXEvalResult? = null
var typeOrNull: Type? = null
val visitor: CursorVisitor = { cursor, _ ->
val kind = cursor.kind
when {
state == VisitorState.EXPECT_VARIABLE && kind == CXCursorKind.CXCursor_VarDecl -> {
evalResultOrNull = clang_Cursor_Evaluate(cursor)
state = VisitorState.EXPECT_VARIABLE_VALUE
CXChildVisitResult.CXChildVisit_Recurse
}
state == VisitorState.EXPECT_VARIABLE_VALUE && clang_isExpression(kind) != 0 -> {
typeOrNull = typeConverter(clang_getCursorType(cursor))
state = VisitorState.EXPECT_END
CXChildVisitResult.CXChildVisit_Continue
}
// Skip auxiliary elements.
state == VisitorState.EXPECT_NODES_TO_SKIP && kind in kindsToSkip ->
CXChildVisitResult.CXChildVisit_Recurse
state == VisitorState.EXPECT_NODES_TO_SKIP && kind == CXCursorKind.CXCursor_DeclStmt -> {
state = VisitorState.EXPECT_VARIABLE
CXChildVisitResult.CXChildVisit_Recurse
}
else -> {
state = VisitorState.INVALID
CXChildVisitResult.CXChildVisit_Break
}
}
}
try {
visitChildren(functionCursor, visitor)
if (state != VisitorState.EXPECT_END) {
return null
}
val type = typeOrNull!!
return if (evalResultOrNull == null) {
// The macro cannot be evaluated as a constant so we will wrap it in a bridge.
when(type.unwrapTypedefs()) {
is PrimitiveType,
is PointerType,
is ObjCPointer -> WrappedMacroDef(name, type)
else -> null
}
} else {
// Otherwise we can evaluate the expression and create a Kotlin constant for it.
val evalResult = evalResultOrNull!!
val evalResultKind = clang_EvalResult_getKind(evalResult)
when (evalResultKind) {
CXEvalResultKind.CXEval_Int ->
IntegerConstantDef(name, type, clang_EvalResult_getAsLongLong(evalResult))
CXEvalResultKind.CXEval_Float ->
FloatingConstantDef(name, type, clang_EvalResult_getAsDouble(evalResult))
CXEvalResultKind.CXEval_CFStr,
CXEvalResultKind.CXEval_ObjCStrLiteral,
CXEvalResultKind.CXEval_StrLiteral ->
if (evalResultKind == CXEvalResultKind.CXEval_StrLiteral && !type.canonicalIsPointerToChar()) {
// libclang doesn't seem to support wide string literals properly in this API;
// thus disable wide literals here:
null
} else {
StringConstantDef(name, type, clang_EvalResult_getAsStr(evalResult)!!.toKString())
}
CXEvalResultKind.CXEval_Other,
CXEvalResultKind.CXEval_UnExposed -> null
}
}
} finally {
evalResultOrNull?.let { clang_EvalResult_dispose(it) }
}
}
enum class VisitorState {
EXPECT_NODES_TO_SKIP,
EXPECT_VARIABLE, EXPECT_VARIABLE_VALUE,
EXPECT_END, INVALID
}
private fun collectMacroNames(nativeIndex: NativeIndexImpl, translationUnit: CXTranslationUnit, headers: Set<CXFile?>): List<String> {
val result = mutableSetOf<String>()
visitChildren(translationUnit) { cursor, _ ->
val file = memScoped {
val fileVar = alloc<CXFileVar>()
clang_getFileLocation(clang_getCursorLocation(cursor), fileVar.ptr, null, null, null)
fileVar.value
}
if (cursor.kind == CXCursorKind.CXCursor_MacroDefinition &&
nativeIndex.library.includesDeclaration(cursor) &&
file != null && // Builtin macros mostly seem to be useless.
file in headers &&
canMacroBeConstant(cursor))
{
val spelling = getCursorSpelling(cursor)
result.add(spelling)
}
CXChildVisitResult.CXChildVisit_Continue
}
return result.toList()
}
private fun canMacroBeConstant(cursor: CValue<CXCursor>): Boolean {
if (clang_Cursor_isMacroFunctionLike(cursor) != 0) {
return false
}
// TODO: check number of tokens and filter out empty definitions;
// Requires updating to 3.9.1 due to https://bugs.llvm.org//show_bug.cgi?id=9069
return true
}
@@ -0,0 +1,139 @@
package org.jetbrains.kotlin.native.interop.indexer
import clang.*
import kotlinx.cinterop.*
import java.nio.file.Files
data class ModulesInfo(val topLevelHeaders: List<String>, val ownHeaders: Set<String>)
fun getModulesInfo(compilation: Compilation, modules: List<String>): ModulesInfo {
if (modules.isEmpty()) return ModulesInfo(emptyList(), emptySet())
withIndex { index ->
ModularCompilation(compilation).use {
val modulesASTFiles = getModulesASTFiles(index, it, modules)
return buildModulesInfo(index, modules, modulesASTFiles)
}
}
}
private fun buildModulesInfo(index: CXIndex, modules: List<String>, modulesASTFiles: List<String>): ModulesInfo {
val ownHeaders = mutableSetOf<String>()
val topLevelHeaders = linkedSetOf<String>()
modulesASTFiles.forEach {
val moduleTranslationUnit = clang_createTranslationUnit(index, it)!!
try {
val modulesHeaders = getModulesHeaders(index, moduleTranslationUnit, modules.toSet(), topLevelHeaders)
modulesHeaders.mapTo(ownHeaders) { it.canonicalPath }
} finally {
clang_disposeTranslationUnit(moduleTranslationUnit)
}
}
return ModulesInfo(topLevelHeaders.toList(), ownHeaders)
}
internal open class ModularCompilation(compilation: Compilation): Compilation by compilation, Disposable {
companion object {
private const val moduleCacheFlag = "-fmodules-cache-path="
}
private val moduleCacheDirectory = if (compilation.compilerArgs.none { it.startsWith(moduleCacheFlag) }) {
Files.createTempDirectory("ModuleCache").toAbsolutePath().toFile()
} else {
null
}
override val compilerArgs: List<String> = compilation.compilerArgs +
listOfNotNull("-fmodules", moduleCacheDirectory?.let { "$moduleCacheFlag${it}" })
override fun dispose() {
moduleCacheDirectory?.deleteRecursively()
}
}
private fun getModulesASTFiles(index: CXIndex, compilation: ModularCompilation, modules: List<String>): List<String> {
val compilationWithImports = compilation.copy(
additionalPreambleLines = modules.map { "@import $it;" } + compilation.additionalPreambleLines
)
val result = linkedSetOf<String>()
val translationUnit = compilationWithImports.parse(
index,
options = CXTranslationUnit_DetailedPreprocessingRecord
)
try {
translationUnit.ensureNoCompileErrors()
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
override fun importedASTFile(info: CXIdxImportedASTFileInfo) {
result += info.file!!.canonicalPath
}
})
} finally {
clang_disposeTranslationUnit(translationUnit)
}
return result.toList()
}
private fun getModulesHeaders(
index: CXIndex,
translationUnit: CXTranslationUnit,
modules: Set<String>,
topLevelHeaders: LinkedHashSet<String>
): Set<CXFile> {
val nonModularIncludes = mutableMapOf<CXFile, MutableSet<CXFile>>()
val result = mutableSetOf<CXFile>()
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
override fun ppIncludedFile(info: CXIdxIncludedFileInfo) {
val file = info.file!!
val includer = clang_indexLoc_getCXSourceLocation(info.hashLoc.readValue()).getContainingFile()
if (includer == null) {
// i.e. the header is included by the module itself.
topLevelHeaders += file.path
}
val module = clang_getModuleForFile(translationUnit, file)
if (module != null) {
val moduleWithParents = generateSequence(module, { clang_Module_getParent(it) }).map {
clang_Module_getFullName(it).convertAndDispose()
}
if (moduleWithParents.any { it in modules }) {
result += file
}
} else if (includer != null) {
nonModularIncludes.getOrPut(includer, { mutableSetOf() }) += file
}
}
})
// There are cases when non-modular includes should also be considered as a part of module. For example:
// 1. Some module maps are broken,
// e.g. system header `IOKit/hid/IOHIDProperties.h` isn't included to framework module map at all.
// 2. Textual headers are reported as non-modular by libclang.
//
// Find and include non-modular headers too:
result += findReachable(roots = result, arcs = nonModularIncludes)
return result
}
private fun <T> findReachable(roots: Set<T>, arcs: Map<T, Set<T>>): Set<T> {
val visited = mutableSetOf<T>()
fun dfs(vertex: T) {
if (!visited.add(vertex)) return
arcs[vertex].orEmpty().forEach { dfs(it) }
}
roots.forEach { dfs(it) }
return visited
}
@@ -0,0 +1,331 @@
/*
* 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.native.interop.indexer
enum class Language(val sourceFileExtension: String) {
C("c"),
OBJECTIVE_C("m")
}
interface HeaderInclusionPolicy {
/**
* Whether unused declarations from given header should be excluded.
*
* @param headerName header path relative to the appropriate include path element (e.g. `time.h` or `curl/curl.h`),
* or `null` for builtin declarations.
*/
fun excludeUnused(headerName: String?): Boolean
}
interface HeaderExclusionPolicy {
/**
* Whether all declarations from this header should be excluded.
*
* Note: the declarations from such headers can be actually present in the internal representation,
* but not included into the root collections.
*/
fun excludeAll(headerId: HeaderId): Boolean
}
sealed class NativeLibraryHeaderFilter {
class NameBased(
val policy: HeaderInclusionPolicy,
val excludeDepdendentModules: Boolean
) : NativeLibraryHeaderFilter()
class Predefined(val headers: Set<String>) : NativeLibraryHeaderFilter()
}
interface Compilation {
val includes: List<String>
val additionalPreambleLines: List<String>
val compilerArgs: List<String>
val language: Language
}
data class CompilationWithPCH(
override val compilerArgs: List<String>,
override val language: Language
) : Compilation {
constructor(compilerArgs: List<String>, precompiledHeader: String, language: Language)
: this(compilerArgs + listOf("-include-pch", precompiledHeader), language)
override val includes: List<String>
get() = emptyList()
override val additionalPreambleLines: List<String>
get() = emptyList()
}
// TODO: Compilation hierarchy seems to require some refactoring.
data class NativeLibrary(override val includes: List<String>,
override val additionalPreambleLines: List<String>,
override val compilerArgs: List<String>,
val headerToIdMapper: HeaderToIdMapper,
override val language: Language,
val excludeSystemLibs: Boolean, // TODO: drop?
val headerExclusionPolicy: HeaderExclusionPolicy,
val headerFilter: NativeLibraryHeaderFilter) : Compilation
data class IndexerResult(val index: NativeIndex, val compilation: CompilationWithPCH)
/**
* Retrieves the definitions from given C header file using given compiler arguments (e.g. defines).
*/
fun buildNativeIndex(library: NativeLibrary, verbose: Boolean): IndexerResult = buildNativeIndexImpl(library, verbose)
/**
* This class describes the IR of definitions from C header file(s).
*/
abstract class NativeIndex {
abstract val structs: Collection<StructDecl>
abstract val enums: Collection<EnumDef>
abstract val objCClasses: Collection<ObjCClass>
abstract val objCProtocols: Collection<ObjCProtocol>
abstract val objCCategories: Collection<ObjCCategory>
abstract val typedefs: Collection<TypedefDef>
abstract val functions: Collection<FunctionDecl>
abstract val macroConstants: Collection<ConstantDef>
abstract val wrappedMacros: Collection<WrappedMacroDef>
abstract val globals: Collection<GlobalDecl>
abstract val includedHeaders: Collection<HeaderId>
}
/**
* The (contents-based) header id.
* Its [value] remains valid across different runs of the indexer and the process,
* and thus can be used to 'serialize' the id.
*/
data class HeaderId(val value: String)
data class Location(val headerId: HeaderId)
interface TypeDeclaration {
val location: Location
}
sealed class StructMember(val name: String, val type: Type) {
abstract val offset: Long?
}
/**
* C struct field.
*/
class Field(name: String, type: Type, override val offset: Long, val typeSize: Long, val typeAlign: Long)
: StructMember(name, type)
val Field.isAligned: Boolean
get() = offset % (typeAlign * 8) == 0L
class BitField(name: String, type: Type, override val offset: Long, val size: Int) : StructMember(name, type)
class IncompleteField(name: String, type: Type) : StructMember(name, type) {
override val offset: Long? get() = null
}
/**
* C struct declaration.
*/
abstract class StructDecl(val spelling: String) : TypeDeclaration {
abstract val def: StructDef?
}
/**
* C struct definition.
*
* @param hasNaturalLayout must be `false` if the struct has unnatural layout, e.g. it is `packed`.
* May be `false` even if the struct has natural layout.
*/
abstract class StructDef(val size: Long, val align: Int, val decl: StructDecl) {
enum class Kind {
STRUCT, UNION
}
abstract val members: List<StructMember>
abstract val kind: Kind
val fields: List<Field> get() = members.filterIsInstance<Field>()
val bitFields: List<BitField> get() = members.filterIsInstance<BitField>()
}
/**
* C enum value.
*/
class EnumConstant(val name: String, val value: Long, val isExplicitlyDefined: Boolean)
/**
* C enum definition.
*/
abstract class EnumDef(val spelling: String, val baseType: Type) : TypeDeclaration {
abstract val constants: List<EnumConstant>
}
sealed class ObjCContainer {
abstract val protocols: List<ObjCProtocol>
abstract val methods: List<ObjCMethod>
abstract val properties: List<ObjCProperty>
}
sealed class ObjCClassOrProtocol(val name: String) : ObjCContainer(), TypeDeclaration {
abstract val isForwardDeclaration: Boolean
}
data class ObjCMethod(
val selector: String, val encoding: String, val parameters: List<Parameter>, private val returnType: Type,
val isVariadic: Boolean, val isClass: Boolean, val nsConsumesSelf: Boolean, val nsReturnsRetained: Boolean,
val isOptional: Boolean, val isInit: Boolean, val isExplicitlyDesignatedInitializer: Boolean
) {
fun returnsInstancetype(): Boolean = returnType is ObjCInstanceType
fun getReturnType(container: ObjCClassOrProtocol): Type = if (returnType is ObjCInstanceType) {
when (container) {
is ObjCClass -> ObjCObjectPointer(container, returnType.nullability, protocols = emptyList())
is ObjCProtocol -> ObjCIdType(returnType.nullability, protocols = listOf(container))
}
} else {
returnType
}
}
data class ObjCProperty(val name: String, val getter: ObjCMethod, val setter: ObjCMethod?) {
fun getType(container: ObjCClassOrProtocol): Type = getter.getReturnType(container)
}
abstract class ObjCClass(name: String) : ObjCClassOrProtocol(name) {
abstract val binaryName: String?
abstract val baseClass: ObjCClass?
}
abstract class ObjCProtocol(name: String) : ObjCClassOrProtocol(name)
abstract class ObjCCategory(val name: String, val clazz: ObjCClass) : ObjCContainer()
/**
* C function parameter.
*/
data class Parameter(val name: String?, val type: Type, val nsConsumed: Boolean)
/**
* C function declaration.
*/
class FunctionDecl(val name: String, val parameters: List<Parameter>, val returnType: Type, val binaryName: String,
val isDefined: Boolean, val isVararg: Boolean)
/**
* C typedef definition.
*
* ```
* typedef $aliased $name;
* ```
*/
class TypedefDef(val aliased: Type, val name: String, override val location: Location) : TypeDeclaration
abstract class MacroDef(val name: String)
abstract class ConstantDef(name: String, val type: Type): MacroDef(name)
class IntegerConstantDef(name: String, type: Type, val value: Long) : ConstantDef(name, type)
class FloatingConstantDef(name: String, type: Type, val value: Double) : ConstantDef(name, type)
class StringConstantDef(name: String, type: Type, val value: String) : ConstantDef(name, type)
class WrappedMacroDef(name: String, val type: Type) : MacroDef(name)
class GlobalDecl(val name: String, val type: Type, val isConst: Boolean)
/**
* C type.
*/
interface Type
interface PrimitiveType : Type
object CharType : PrimitiveType
open class BoolType: PrimitiveType
object CBoolType : BoolType()
object ObjCBoolType : BoolType()
// We omit `const` qualifier for IntegerType and FloatingType to make `CBridgeGen` simpler.
// See KT-28102.
data class IntegerType(val size: Int, val isSigned: Boolean, val spelling: String) : PrimitiveType
// TODO: floating type is not actually defined entirely by its size.
data class FloatingType(val size: Int, val spelling: String) : PrimitiveType
data class VectorType(val elementType: Type, val elementCount: Int, val spelling: String) : PrimitiveType
object VoidType : Type
data class RecordType(val decl: StructDecl) : Type
data class EnumType(val def: EnumDef) : Type
data class PointerType(val pointeeType: Type, val pointeeIsConst: Boolean = false) : Type
// TODO: refactor type representation and support type modifiers more generally.
data class FunctionType(val parameterTypes: List<Type>, val returnType: Type) : Type
interface ArrayType : Type {
val elemType: Type
}
data class ConstArrayType(override val elemType: Type, val length: Long) : ArrayType
data class IncompleteArrayType(override val elemType: Type) : ArrayType
data class VariableArrayType(override val elemType: Type) : ArrayType
data class Typedef(val def: TypedefDef) : Type
sealed class ObjCPointer : Type {
enum class Nullability {
Nullable, NonNull, Unspecified
}
abstract val nullability: Nullability
}
sealed class ObjCQualifiedPointer : ObjCPointer() {
abstract val protocols: List<ObjCProtocol>
}
data class ObjCObjectPointer(
val def: ObjCClass,
override val nullability: Nullability,
override val protocols: List<ObjCProtocol>
) : ObjCQualifiedPointer()
data class ObjCClassPointer(
override val nullability: Nullability,
override val protocols: List<ObjCProtocol>
) : ObjCQualifiedPointer()
data class ObjCIdType(
override val nullability: Nullability,
override val protocols: List<ObjCProtocol>
) : ObjCQualifiedPointer()
data class ObjCInstanceType(override val nullability: Nullability) : ObjCPointer()
data class ObjCBlockPointer(
override val nullability: Nullability,
val parameterTypes: List<Type>, val returnType: Type
) : ObjCPointer()
object UnsupportedType : Type
@@ -0,0 +1,768 @@
/*
* 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.native.interop.indexer
import clang.*
import kotlinx.cinterop.*
import java.io.Closeable
import java.io.File
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.security.DigestInputStream
import java.security.MessageDigest
internal val CValue<CXType>.kind: CXTypeKind get() = this.useContents { kind }
internal val CValue<CXCursor>.kind: CXCursorKind get() = this.useContents { kind }
internal val CValue<CXCursor>.type: CValue<CXType> get() = clang_getCursorType(this)
internal val CValue<CXCursor>.spelling: String get() = clang_getCursorSpelling(this).convertAndDispose()
internal val CValue<CXType>.name: String get() = clang_getTypeSpelling(this).convertAndDispose()
internal val CXTypeKind.spelling: String get() = clang_getTypeKindSpelling(this).convertAndDispose()
internal val CXCursorKind.spelling: String get() = clang_getCursorKindSpelling(this).convertAndDispose()
internal fun CValue<CXString>.convertAndDispose(): String {
try {
return clang_getCString(this)!!.toKString()
} finally {
clang_disposeString(this)
}
}
internal fun CPointer<CXStringSet>.convertAndDispose(): Set<String> = try {
(0 until this.pointed.Count).mapTo(mutableSetOf()) {
clang_getCString(this.pointed.Strings!![it].readValue())!!.toKString()
}
} finally {
clang_disposeStringSet(this)
}
internal fun getCursorSpelling(cursor: CValue<CXCursor>) =
clang_getCursorSpelling(cursor).convertAndDispose()
internal fun CValue<CXType>.getSize(): Long {
val size = clang_Type_getSizeOf(this)
if (size < 0) {
throw Error(size.toString())
}
return size
}
internal inline fun <R> withIndex(
excludeDeclarationsFromPCH: Boolean = false,
displayDiagnostics: Boolean = false,
block: (index: CXIndex) -> R
): R {
val index = clang_createIndex(
excludeDeclarationsFromPCH = if (excludeDeclarationsFromPCH) 1 else 0,
displayDiagnostics = if (displayDiagnostics) 1 else 0
)!!
return try {
block(index)
} finally {
clang_disposeIndex(index)
}
}
internal fun parseTranslationUnit(
index: CXIndex,
sourceFile: File,
compilerArgs: List<String>,
options: Int
): CXTranslationUnit {
memScoped {
val resultVar = alloc<CXTranslationUnitVar>()
val errorCode = clang_parseTranslationUnit2(
index,
sourceFile.absolutePath,
compilerArgs.toNativeStringArray(memScope), compilerArgs.size,
null, 0,
options,
resultVar.ptr
)
if (errorCode != CXErrorCode.CXError_Success) {
val copiedSourceFile = sourceFile.copyTo(Files.createTempFile(null, sourceFile.name).toFile(), overwrite = true)
error("""
clang_parseTranslationUnit2 failed with $errorCode;
sourceFile = ${copiedSourceFile.absolutePath}
arguments = ${compilerArgs.joinToString(" ")}
""".trimIndent())
}
return resultVar.value!!
}
}
internal fun Compilation.parse(index: CXIndex, options: Int = 0): CXTranslationUnit =
parseTranslationUnit(index, this.createTempSource(), this.compilerArgs, options)
internal data class Diagnostic(val severity: CXDiagnosticSeverity, val format: String,
val location: CValue<CXSourceLocation>)
internal fun CXTranslationUnit.getDiagnostics(): Sequence<Diagnostic> {
val numDiagnostics = clang_getNumDiagnostics(this)
return (0 until numDiagnostics).asSequence()
.map { index ->
val diagnostic = clang_getDiagnostic(this, index)
try {
val severity = clang_getDiagnosticSeverity(diagnostic)
val format = clang_formatDiagnostic(diagnostic, clang_defaultDiagnosticDisplayOptions())
.convertAndDispose()
val location = clang_getDiagnosticLocation(diagnostic)
Diagnostic(severity, format, location)
} finally {
clang_disposeDiagnostic(diagnostic)
}
}
}
internal fun CXTranslationUnit.getCompileErrors(): Sequence<String> =
getDiagnostics().filter { it.isError() }.map { it.format }
private fun Diagnostic.isError() = (severity == CXDiagnosticSeverity.CXDiagnostic_Error) ||
(severity == CXDiagnosticSeverity.CXDiagnostic_Fatal)
internal fun CXTranslationUnit.hasCompileErrors() = (this.getCompileErrors().firstOrNull() != null)
internal fun CXTranslationUnit.ensureNoCompileErrors(): CXTranslationUnit {
val firstError = this.getCompileErrors().firstOrNull() ?: return this
throw Error(firstError)
}
internal typealias CursorVisitor = (cursor: CValue<CXCursor>, parent: CValue<CXCursor>) -> CXChildVisitResult
internal fun visitChildren(parent: CValue<CXCursor>, visitor: CursorVisitor) {
val visitorStableRef = StableRef.create(visitor)
try {
val clientData = visitorStableRef.asCPointer()
clang_visitChildren(parent, staticCFunction { cursorIt, parentIt, clientDataIt ->
val visitorIt = clientDataIt!!.asStableRef<CursorVisitor>().get()
visitorIt(cursorIt, parentIt)
}, clientData)
} finally {
visitorStableRef.dispose()
}
}
internal fun visitChildren(translationUnit: CXTranslationUnit, visitor: CursorVisitor) =
visitChildren(clang_getTranslationUnitCursor(translationUnit), visitor)
internal fun getFields(type: CValue<CXType>): List<CValue<CXCursor>> {
val result = mutableListOf<CValue<CXCursor>>()
val resultStableRef = StableRef.create(result)
try {
val clientData = resultStableRef.asCPointer()
@Suppress("NAME_SHADOWING")
clang_Type_visitFields(type, staticCFunction { cursor, clientData ->
val result = clientData!!.asStableRef<MutableList<CValue<CXCursor>>>().get()
result.add(cursor)
CXVisitorResult.CXVisit_Continue
}, clientData)
} finally {
resultStableRef.dispose()
}
return result
}
fun StructDef.fieldsHaveDefaultAlignment(): Boolean {
fun alignUp(x: Long, alignment: Long): Long = (x + alignment - 1) and (alignment - 1).inv()
var offset = 0L
this.members.forEach {
when (it) {
is Field -> {
if (alignUp(offset, it.typeAlign) * 8 != it.offset) return false
offset = it.offset / 8 + it.typeSize
}
is BitField -> return false
}
}
return true
}
internal fun CValue<CXCursor>.hasExpressionChild(): Boolean {
var result = false
visitChildren(this) { cursor, _ ->
if (clang_isExpression(cursor.kind) != 0) {
result = true
CXChildVisitResult.CXChildVisit_Break
} else {
CXChildVisitResult.CXChildVisit_Continue
}
}
return result
}
internal fun List<String>.toNativeStringArray(scope: AutofreeScope): CArrayPointer<CPointerVar<ByteVar>> {
return scope.allocArray(this.size) { index ->
this.value = this@toNativeStringArray[index].cstr.getPointer(scope)
}
}
val Compilation.preambleLines: List<String>
get() = this.includes.map { "#include <$it>" } + this.additionalPreambleLines
internal fun Appendable.appendPreamble(compilation: Compilation) = this.apply {
compilation.preambleLines.forEach {
this.appendLine(it)
}
}
/**
* Creates temporary source file which includes the library.
*/
internal fun Compilation.createTempSource(): File {
val result = Files.createTempFile(null, ".${language.sourceFileExtension}").toFile()
result.deleteOnExit()
result.bufferedWriter().use { writer ->
writer.appendPreamble(this)
}
return result
}
fun Compilation.copy(
includes: List<String> = this.includes,
additionalPreambleLines: List<String> = this.additionalPreambleLines,
compilerArgs: List<String> = this.compilerArgs,
language: Language = this.language
): Compilation = CompilationImpl(
includes = includes,
additionalPreambleLines = additionalPreambleLines,
compilerArgs = compilerArgs,
language = language
)
// Clang-8 crashes when consuming a precompiled header built with -fmodule-map-file argument (see KT-34467).
// We ignore this argument when building a pch to workaround this crash.
fun Compilation.copyWithArgsForPCH(): Compilation =
copy(compilerArgs = compilerArgs.filterNot { it.startsWith("-fmodule-map-file") })
data class CompilationImpl(
override val includes: List<String>,
override val additionalPreambleLines: List<String>,
override val compilerArgs: List<String>,
override val language: Language
) : Compilation
/**
* Precompiles the headers of this library.
*
* @return the library which includes the precompiled header instead of original ones.
*/
fun Compilation.precompileHeaders(): CompilationWithPCH = withIndex { index ->
val options = CXTranslationUnit_ForSerialization
val translationUnit = copyWithArgsForPCH().parse(index, options)
try {
translationUnit.ensureNoCompileErrors()
withPrecompiledHeader(translationUnit)
} finally {
clang_disposeTranslationUnit(translationUnit)
}
}
internal fun Compilation.withPrecompiledHeader(translationUnit: CXTranslationUnit): CompilationWithPCH {
val precompiledHeader = Files.createTempFile(null, ".pch").toFile().apply { this.deleteOnExit() }
clang_saveTranslationUnit(translationUnit, precompiledHeader.absolutePath, 0)
return CompilationWithPCH(this.compilerArgs, precompiledHeader.absolutePath, this.language)
}
internal fun NativeLibrary.includesDeclaration(cursor: CValue<CXCursor>): Boolean {
return if (this.excludeSystemLibs) {
clang_Location_isInSystemHeader(clang_getCursorLocation(cursor)) == 0
} else {
true
}
}
internal fun CXTranslationUnit.getErrorLineNumbers(): Sequence<Int> =
getDiagnostics().filter {
it.isError()
}.map {
memScoped {
val lineNumberVar = alloc<IntVar>()
clang_getFileLocation(it.location, null, lineNumberVar.ptr, null, null)
lineNumberVar.value
}
}
/**
* For each list of lines, checks if the code fragment composed from these lines is compilable against given library.
*/
fun List<List<String>>.mapFragmentIsCompilable(originalLibrary: CompilationWithPCH): List<Boolean> {
val library: CompilationWithPCH = originalLibrary
.copy(compilerArgs = originalLibrary.compilerArgs + "-ferror-limit=0")
val indicesOfNonCompilable = mutableSetOf<Int>()
val fragmentsToCheck = this.withIndex().toMutableList()
withIndex(excludeDeclarationsFromPCH = true) { index ->
val sourceFile = library.createTempSource()
val translationUnit = parseTranslationUnit(index, sourceFile, library.compilerArgs, options = 0)
try {
translationUnit.ensureNoCompileErrors()
while (fragmentsToCheck.isNotEmpty()) {
// Combine all fragments to be checked in a single file:
sourceFile.bufferedWriter().use { writer ->
writer.appendPreamble(library)
fragmentsToCheck.forEach {
it.value.forEach {
assert(!it.contains('\n'))
writer.appendLine(it)
}
}
}
clang_reparseTranslationUnit(translationUnit, 0, null, 0)
val errorLineNumbers = translationUnit.getErrorLineNumbers().toSet()
// Retain only those fragments that contain compilation error locations:
var lastLineNumber = library.preambleLines.size
fragmentsToCheck.retainAll {
val firstLineNumber = lastLineNumber + 1
lastLineNumber += it.value.size
(firstLineNumber .. lastLineNumber).any { it in errorLineNumbers }
}
if (fragmentsToCheck.isNotEmpty()) {
// The first fragment is now known to be non-compilable.
val firstFragment = fragmentsToCheck.removeAt(0)
indicesOfNonCompilable.add(firstFragment.index)
}
// The remaining fragments was potentially influenced by the first one,
// and thus require to be checked again.
}
} finally {
clang_disposeTranslationUnit(translationUnit)
}
}
return this.indices.map { it !in indicesOfNonCompilable }
}
internal interface Indexer {
/**
* Called when entered main file.
*/
fun enteredMainFile(file: CXFile) {}
/**
* Called when a file gets #included/#imported.
*/
fun ppIncludedFile(info: CXIdxIncludedFileInfo) {}
/**
* Called when a AST file (PCH or module) gets imported.
*/
fun importedASTFile(info: CXIdxImportedASTFileInfo) {}
/**
* Called to index a declaration.
*/
fun indexDeclaration(info: CXIdxDeclInfo) {}
}
internal fun indexTranslationUnit(index: CXIndex, translationUnit: CXTranslationUnit, options: Int, indexer: Indexer) {
val indexerStableRef = StableRef.create(indexer)
try {
val clientData = indexerStableRef.asCPointer()
memScoped {
val indexerCallbacks = alloc<IndexerCallbacks>().apply {
abortQuery = null
diagnostic = null
enteredMainFile = staticCFunction { clientData, mainFile, _ ->
@Suppress("NAME_SHADOWING")
val indexer = clientData!!.asStableRef<Indexer>().get()
indexer.enteredMainFile(mainFile!!)
// We must ensure only interop types exist in function signature.
@Suppress("USELESS_CAST")
null as CXIdxClientFile?
}
ppIncludedFile = staticCFunction { clientData, info ->
@Suppress("NAME_SHADOWING")
val indexer = clientData!!.asStableRef<Indexer>().get()
indexer.ppIncludedFile(info!!.pointed)
// We must ensure only interop types exist in function signature.
@Suppress("USELESS_CAST")
null as CXIdxClientFile?
}
importedASTFile = staticCFunction { clientData, info ->
@Suppress("NAME_SHADOWING")
val indexer = clientData!!.asStableRef<Indexer>().get()
indexer.importedASTFile(info!!.pointed)
// We must ensure only interop types exist in function signature.
@Suppress("USELESS_CAST")
null as CXIdxClientFile?
}
startedTranslationUnit = null
indexDeclaration = staticCFunction { clientData, info ->
@Suppress("NAME_SHADOWING")
val nativeIndex = clientData!!.asStableRef<Indexer>().get()
nativeIndex.indexDeclaration(info!!.pointed)
}
indexEntityReference = null
}
val indexAction = clang_IndexAction_create(index)
try {
val result = clang_indexTranslationUnit(indexAction, clientData,
indexerCallbacks.ptr, sizeOf<IndexerCallbacks>().toInt(), options, translationUnit)
if (result != 0) {
throw Error("clang_indexTranslationUnit returned $result")
}
} finally {
clang_IndexAction_dispose(indexAction)
}
}
} finally {
indexerStableRef.dispose()
}
}
internal class ModulesMap(
val compilation: Compilation,
val translationUnit: CXTranslationUnit
) : Closeable {
private val modularCompilation: ModularCompilation
private val index: CXIndex
private val translationUnitWithModules: CXTranslationUnit
private val arena = Arena()
private inline fun <T> T.toBeDisposedWith(crossinline block: (T) -> Unit): T = apply {
arena.defer { block(this) }
}
override fun close() {
arena.clear()
}
init {
try {
modularCompilation = ModularCompilation(compilation)
.toBeDisposedWith { it.dispose() }
index = clang_createIndex(0, 0)!!
.toBeDisposedWith { clang_disposeIndex(it) }
translationUnitWithModules = modularCompilation.parse(index)
.toBeDisposedWith { clang_disposeTranslationUnit(it) }
translationUnitWithModules.ensureNoCompileErrors()
} catch (e: Throwable) {
this.close()
throw e
}
}
data class Module(private val cxModule: CXModule)
fun getModule(file: CXFile): Module? {
// `file` is bound to `translationUnit`, however `translationUnitWithModules` is used to access modules.
// Find the corresponding file in `translationUnitWithModules`:
val fileInTuWithModules =
clang_getFile(translationUnitWithModules, clang_getFileName(file).convertAndDispose())!!
return clang_getModuleForFile(translationUnitWithModules, fileInTuWithModules)?.let { Module(it) }
}
}
internal fun getHeaderId(library: NativeLibrary, header: CXFile?): HeaderId {
if (header == null) {
return HeaderId("builtins")
}
val filePath = header.path
return library.headerToIdMapper.getHeaderId(filePath)
}
internal fun getFilteredHeaders(
nativeIndex: NativeIndexImpl,
index: CXIndex,
translationUnit: CXTranslationUnit
): Set<CXFile?> = getHeaders(nativeIndex.library, index, translationUnit).ownHeaders
class NativeLibraryHeaders<Header>(val ownHeaders: Set<Header>, val importedHeaders: Set<Header>)
internal fun getHeaders(
library: NativeLibrary,
index: CXIndex,
translationUnit: CXTranslationUnit
): NativeLibraryHeaders<CXFile?> {
val ownHeaders = mutableSetOf<CXFile?>()
val allHeaders = mutableSetOf<CXFile?>(null)
val filter = library.headerFilter
when (filter) {
is NativeLibraryHeaderFilter.NameBased ->
filterHeadersByName(library, filter, index, translationUnit, ownHeaders, allHeaders)
is NativeLibraryHeaderFilter.Predefined ->
filterHeadersByPredefined(filter, index, translationUnit, ownHeaders, allHeaders)
}
ownHeaders.removeAll { library.headerExclusionPolicy.excludeAll(getHeaderId(library, it)) }
return NativeLibraryHeaders(ownHeaders, allHeaders - ownHeaders)
}
private fun filterHeadersByName(
compilation: Compilation,
filter: NativeLibraryHeaderFilter.NameBased,
index: CXIndex,
translationUnit: CXTranslationUnit,
ownHeaders: MutableSet<CXFile?>,
allHeaders: MutableSet<CXFile?>
) {
val topLevelFiles = mutableListOf<CXFile>()
var mainFile: CXFile? = null
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
val headerToName = mutableMapOf<CXFile, String>()
// The *name* of the header here is the path relative to the include path element., e.g. `curl/curl.h`.
override fun enteredMainFile(file: CXFile) {
mainFile = file
allHeaders += file
}
override fun ppIncludedFile(info: CXIdxIncludedFileInfo) {
val includeLocation = clang_indexLoc_getCXSourceLocation(info.hashLoc.readValue())
val file = info.file!!
allHeaders += file
if (clang_Location_isFromMainFile(includeLocation) != 0) {
topLevelFiles.add(file)
}
val name = info.filename!!.toKString()
val headerName = if (info.isAngled != 0) {
// If the header is included with `#include <$name>`, then `name` is probably
// the path relative to the include path element.
name
} else {
// If it is included with `#include "$name"`, then `name` can also be the path relative to the includer.
val includerFile = includeLocation.getContainingFile()!!
val includerName = headerToName[includerFile] ?: ""
val includerPath = includerFile.path
if (clang_getFile(translationUnit, Paths.get(includerPath).resolveSibling(name).toString()) == file) {
// included file is accessible from the includer by `name` used as relative path, so
// `name` seems to be relative to the includer:
Paths.get(includerName).resolveSibling(name).normalize().toString()
} else {
name
}
}
headerToName[file] = headerName
if (!filter.policy.excludeUnused(headerName)) {
ownHeaders.add(file)
}
}
})
if (filter.excludeDepdendentModules) {
ModulesMap(compilation, translationUnit).use { modulesMap ->
val topLevelModules = topLevelFiles.map { modulesMap.getModule(it) }.toSet()
ownHeaders.removeAll {
val module = modulesMap.getModule(it!!)
module !in topLevelModules
}
// Note: if some of the top-level headers don't belong to modules,
// then all non-modular headers are included.
}
} else {
if (!filter.policy.excludeUnused(headerName = null)) {
// Builtins.
ownHeaders.add(null)
}
}
ownHeaders.add(mainFile!!)
}
private fun filterHeadersByPredefined(
filter: NativeLibraryHeaderFilter.Predefined,
index: CXIndex,
translationUnit: CXTranslationUnit,
ownHeaders: MutableSet<CXFile?>,
allHeaders: MutableSet<CXFile?>
) {
// Note: suboptimal but simple.
indexTranslationUnit(index, translationUnit, 0, object : Indexer {
override fun ppIncludedFile(info: CXIdxIncludedFileInfo) {
val file = info.file
allHeaders += file
if (file?.canonicalPath in filter.headers) {
ownHeaders += file
}
}
})
}
fun NativeLibrary.getHeaderPaths(): NativeLibraryHeaders<String> {
withIndex { index ->
val translationUnit =
this.parse(index, options = CXTranslationUnit_DetailedPreprocessingRecord).ensureNoCompileErrors()
try {
fun getPath(file: CXFile?) = if (file == null) "<builtins>" else file.canonicalPath
val headers = getHeaders(this, index, translationUnit)
return NativeLibraryHeaders(
headers.ownHeaders.map(::getPath).toSet(),
headers.importedHeaders.map(::getPath).toSet()
)
} finally {
clang_disposeTranslationUnit(translationUnit)
}
}
}
fun ObjCMethod.replaces(other: ObjCMethod): Boolean =
this.isClass == other.isClass && this.selector == other.selector
fun ObjCProperty.replaces(other: ObjCProperty): Boolean =
this.getter.replaces(other.getter)
fun File.sha256(): String {
val digest = MessageDigest.getInstance("SHA-256")
DigestInputStream(this.inputStream(), digest).use { dis ->
val buffer = ByteArray(8192)
// Read all bytes:
while (dis.read(buffer, 0, buffer.size) != -1) {}
}
// Convert to hex:
return digest.digest().joinToString("") {
Integer.toHexString((it.toInt() and 0xff) + 0x100).substring(1)
}
}
fun headerContentsHash(filePath: String) = File(filePath).sha256()
internal fun CValue<CXSourceLocation>.getContainingFile(): CXFile? = memScoped {
val fileVar = alloc<CXFileVar>()
clang_getFileLocation(this@getContainingFile, fileVar.ptr, null, null, null)
fileVar.value
}
@JvmName("getFileContainingCursor")
internal fun getContainingFile(cursor: CValue<CXCursor>): CXFile? {
return clang_getCursorLocation(cursor).getContainingFile()
}
internal val CXFile.path: String get() = clang_getFileName(this).convertAndDispose()
internal val CXFile.canonicalPath: String get() = File(this.path).canonicalPath
private fun createVfsOverlayFileContents(virtualPathToReal: Map<Path, Path>): ByteArray {
val overlay = clang_VirtualFileOverlay_create(0)
try {
fun addFileMapping(realPath: Path, virtualPath: Path) {
clang_VirtualFileOverlay_addFileMapping(
overlay,
virtualPath = virtualPath.toAbsolutePath().toString(),
realPath = realPath.toAbsolutePath().toString()
)
}
virtualPathToReal.forEach { virtualPath, realPath ->
if (Files.isDirectory(realPath)) {
realPath.toFile().walkTopDown().forEach {
if (!it.isDirectory) {
addFileMapping(
realPath = it.toPath(),
virtualPath = virtualPath.resolve(realPath.relativize(it.toPath()))
)
}
}
} else {
addFileMapping(realPath = realPath, virtualPath = virtualPath)
}
}
memScoped {
val bufferVar = alloc<CPointerVar<ByteVar>>().apply { value = null }
val bufferSizeVar = alloc<IntVar>()
val res = clang_VirtualFileOverlay_writeToBuffer(overlay, 0, bufferVar.ptr, bufferSizeVar.ptr)
if (res != CXErrorCode.CXError_Success) {
// TODO: shall we free the buffer in this case?
error(res)
}
return bufferVar.value!!.readBytes(bufferSizeVar.value)
}
} finally {
clang_VirtualFileOverlay_dispose(overlay)
}
}
fun createVfsOverlayFile(virtualPathToReal: Map<Path, Path>): Path {
val bytes = createVfsOverlayFileContents(virtualPathToReal)
return Files.createTempFile("konan", ".vfsoverlay").also {
Files.write(it, bytes)
it.toFile().deleteOnExit()
}
}
tailrec fun Type.unwrapTypedefs(): Type = if (this is Typedef) {
this.def.aliased.unwrapTypedefs()
} else {
this
}
fun Type.canonicalIsPointerToChar(): Boolean {
val unwrappedType = this.unwrapTypedefs()
return unwrappedType is PointerType && unwrappedType.pointeeType.unwrapTypedefs() == CharType
}
internal interface Disposable {
fun dispose()
}
internal inline fun <T : Disposable, R> T.use(block: (T) -> R): R = try {
block(this)
} finally {
this.dispose()
}
@@ -0,0 +1,9 @@
#ifdef __linux__
namespace llvm {
/**
* http://lists.llvm.org/pipermail/llvm-dev/2017-January/109621.html
* We can't rebuild llvm, but we can define symbol missed in llvm build.
*/
int DisableABIBreakingChecks = 1;
}
#endif
@@ -0,0 +1,110 @@
#if defined(__linux__) || defined(__APPLE__)
#include <dlfcn.h>
#if defined(__linux__)
#include <link.h>
#endif
#include <stdio.h>
#include <signal.h>
extern "C" void clang_toggleCrashRecovery(unsigned isEnabled);
constexpr int signalsToCover[] = {
SIGILL, SIGFPE, SIGSEGV, SIGBUS, SIGUSR1, SIGUSR2
};
struct {
void* handler;
bool isSigaction;
} oldSignalHandlers[sizeof(signalsToCover)/sizeof(signalsToCover[0])] = { 0 };
static int mySigaction(int sig, const struct sigaction *act, struct sigaction * oact) {
for (int i = 0; i < sizeof(signalsToCover)/sizeof(signalsToCover[0]); i++) {
if (sig == signalsToCover[i]) return 0;
}
return sigaction(sig, act, oact);
}
static void checkSignalChaining() {
struct sigaction oact;
clang_toggleCrashRecovery(1);
for (int i = 0; i < sizeof(signalsToCover)/sizeof(signalsToCover[0]); i++) {
int sig = signalsToCover[i];
if (sigaction(sig, nullptr, &oact) != 0) continue;
if ((oact.sa_flags & SA_SIGINFO) == 0) {
if (oldSignalHandlers[i].isSigaction) {
fprintf(stderr, "ERROR: improperly changed signal flag for %d\n", sig);
continue;
}
if (oldSignalHandlers[i].handler != (void*)oact.sa_handler) {
fprintf(stderr, "ERROR: improperly changed signal handler for %d\n", sig);
continue;
}
} else {
if (!oldSignalHandlers[i].isSigaction) {
fprintf(stderr, "ERROR: improperly changed signal flag for %d\n", sig);
continue;
}
void* action = (void*)oact.sa_sigaction;
if (oldSignalHandlers[i].handler != action) {
Dl_info info;
const char* soname = "<unknown>";
if (dladdr(action, &info) != 0) {
soname = info.dli_fname;
}
fprintf(stderr, "ERROR: changed signal handler for %d from %p to %p: coming from %s\n",
sig, oldSignalHandlers[i].handler, action, soname);
}
}
}
clang_toggleCrashRecovery(0);
}
__attribute__((constructor))
static void initSignalChaining() {
void** base = 0;
Dl_info info;
for (int i = 0; i < sizeof(signalsToCover)/sizeof(signalsToCover[0]); i++) {
struct sigaction oact;
int sig = signalsToCover[i];
if (sigaction(signalsToCover[i], nullptr, &oact) == 0) {
if ((oact.sa_flags & SA_SIGINFO) == 0) {
oldSignalHandlers[i] = {(void*)oact.sa_handler, false};
} else {
oldSignalHandlers[i] = {(void*)oact.sa_sigaction, true};
}
}
}
if (dladdr((void*)&clang_toggleCrashRecovery, &info) == 0) return;
base = (void**)info.dli_fbase;
// Force resolving of lazy symbols.
clang_toggleCrashRecovery(1);
clang_toggleCrashRecovery(0);
// And then patch GOT.
#if defined(__linux__)
{
// On Linux we have to be a bit tricky, as there's unmapped gap between code and GOT.
struct link_map* linkmap = 0;
if (dladdr1((void*)&clang_toggleCrashRecovery, &info, (void**)&linkmap, RTLD_DL_LINKMAP) == 0) return;
base = (void**)linkmap->l_ld;
}
#endif
for (int index = 0, patched = 0; patched < 1; index++) {
void* value = base[index];
if (value == &sigaction) {
base[index] = (void*)mySigaction;
patched++;
}
if (value == mySigaction) {
patched++;
}
}
checkSignalChaining();
}
#endif // defined(__linux__) || defined(__APPLE__)
@@ -0,0 +1,20 @@
/*
* Copyright 2010-2018 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.
*/
buildscript {
apply from: "$rootDir/kotlin-native/gradle/kotlinGradlePlugin.gradle"
}
@@ -0,0 +1,97 @@
/*
* Copyright 2010-2018 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.
*/
konan.libraries.push ({
arenas: new Map(),
nextArena: 0,
Konan_js_allocateArena: function (array) {
var index = konan_dependencies.env.nextArena++;
konan_dependencies.env.arenas.set(index, array || []);
return index;
},
Konan_js_freeArena: function(arenaIndex) {
var arena = konan_dependencies.env.arenas.get(arenaIndex);
arena.forEach(function(element, index) {
arena[index] = null;
});
konan_dependencies.env.arenas.delete(arenaIndex);
},
Konan_js_pushIntToArena: function (arenaIndex, value) {
var arena = konan_dependencies.env.arenas.get(arenaIndex);
arena.push(value);
return arena.length - 1;
},
Konan_js_addObjectToArena: function (arenaIndex, object) {
var arena = konan_dependencies.env.arenas.get(arenaIndex);
arena.push(object);
return arena.length - 1;
},
Konan_js_wrapLambda: function (functionArenaIndex, index) {
return (function () {
var functionArena = konan_dependencies.env.arenas.get(functionArenaIndex);
// convert Arguments to an array
// to be provided by launcher.js
var argumentArenaIndex = konan_dependencies.env.Konan_js_allocateArena(Array.prototype.slice.call(arguments));
var resultIndex = instance.exports.Konan_js_runLambda(index, argumentArenaIndex, arguments.length);
var result = kotlinObject(argumentArenaIndex, resultIndex);
konan_dependencies.env.Konan_js_freeArena(argumentArenaIndex);
return result;
});
},
Konan_js_getInt: function(arenaIndex, objIndex, propertyNamePtr, propertyNameLength) {
// TODO: The toUTF16String() is to be resolved by launcher.js runtime.
var property = toUTF16String(propertyNamePtr, propertyNameLength);
var value = kotlinObject(arenaIndex, objIndex)[property];
return value;
},
Konan_js_getProperty: function(arenaIndex, objIndex, propertyNamePtr, propertyNameLength) {
// TODO: The toUTF16String() is to be resolved by launcher.js runtime.
var property = toUTF16String(propertyNamePtr, propertyNameLength);
var arena = konan_dependencies.env.arenas.get(arenaIndex);
var value = arena[objIndex][property];
arena.push(value);
return arena.length - 1;
},
Konan_js_setFunction: function (arena, obj, propertyName, propertyNameLength, func) {
var name = toUTF16String(propertyName, propertyNameLength);
kotlinObject(arena, obj)[name] = konan_dependencies.env.Konan_js_wrapLambda(arena, func);
},
Konan_js_setString: function (arena, obj, propertyName, propertyNameLength, stringPtr, stringLength) {
var name = toUTF16String(propertyName, propertyNameLength);
var string = toUTF16String(stringPtr, stringLength);
kotlinObject(arena, obj)[name] = string;
},
});
// TODO: This is just a shorthand notation.
function kotlinObject(arenaIndex, objectIndex) {
var arena = konan_dependencies.env.arenas.get(arenaIndex);
if (typeof arena == "undefined") {
console.log("No arena index " + arenaIndex + "for object" + objectIndex);
console.trace()
}
return arena[objectIndex]
}
function toArena(arenaIndex, object) {
return konan_dependencies.env.Konan_js_addObjectToArena(arenaIndex, object);
}
@@ -0,0 +1,143 @@
/*
* Copyright 2010-2018 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 kotlinx.wasm.jsinterop
import kotlin.native.*
import kotlin.native.internal.ExportForCppRuntime
import kotlinx.cinterop.*
typealias Arena = Int
typealias Object = Int
typealias Pointer = Int
/**
* @Retain annotation is required to preserve functions from internalization and DCE.
*/
@RetainForTarget("wasm32")
@SymbolName("Konan_js_allocateArena")
external public fun allocateArena(): Arena
@RetainForTarget("wasm32")
@SymbolName("Konan_js_freeArena")
external public fun freeArena(arena: Arena)
@RetainForTarget("wasm32")
@SymbolName("Konan_js_pushIntToArena")
external public fun pushIntToArena(arena: Arena, value: Int)
const val upperWord = 0xffffffff.toLong() shl 32
@ExportForCppRuntime
fun doubleUpper(value: Double): Int =
((value.toBits() and upperWord) ushr 32) .toInt()
@ExportForCppRuntime
fun doubleLower(value: Double): Int =
(value.toBits() and 0x00000000ffffffff) .toInt()
@RetainForTarget("wasm32")
@SymbolName("ReturnSlot_getDouble")
external public fun ReturnSlot_getDouble(): Double
@RetainForTarget("wasm32")
@SymbolName("Kotlin_String_utf16pointer")
external public fun stringPointer(message: String): Pointer
@RetainForTarget("wasm32")
@SymbolName("Kotlin_String_utf16length")
external public fun stringLengthBytes(message: String): Int
typealias KtFunction <R> = ((ArrayList<JsValue>)->R)
fun <R> wrapFunction(func: KtFunction<R>): Int {
val ptr: Long = StableRef.create(func).asCPointer().toLong()
return ptr.toInt() // TODO: LP64 unsafe.
}
@RetainForTarget("wasm32")
@ExportForCppRuntime("Konan_js_runLambda")
fun runLambda(pointer: Int, argumentsArena: Arena, argumentsArenaSize: Int): Int {
val arguments = arrayListOf<JsValue>()
for (i in 0 until argumentsArenaSize) {
arguments.add(JsValue(argumentsArena, i));
}
val previousArena = ArenaManager.currentArena
ArenaManager.currentArena = argumentsArena
// TODO: LP64 unsafe: wasm32 passes Int, not Long.
val func = pointer.toLong().toCPointer<CPointed>()!!.asStableRef<KtFunction<JsValue>>().get()
val result = func(arguments)
ArenaManager.currentArena = previousArena
return result.index
}
open class JsValue(val arena: Arena, val index: Object) {
fun getInt(property: String): Int {
return getInt(ArenaManager.currentArena, index, stringPointer(property), stringLengthBytes(property))
}
fun getProperty(property: String): JsValue {
return JsValue(ArenaManager.currentArena, Konan_js_getProperty(ArenaManager.currentArena, index, stringPointer(property), stringLengthBytes(property)))
}
}
open class JsArray(arena: Arena, index: Object): JsValue(arena, index) {
constructor(jsValue: JsValue): this(jsValue.arena, jsValue.index)
operator fun get(index: Int): JsValue {
// TODO: we could pass an integer index to index arrays.
return getProperty(index.toString())
}
val size: Int
get() = this.getInt("length")
}
@RetainForTarget("wasm32")
@SymbolName("Konan_js_getInt")
external public fun getInt(arena: Arena, obj: Object, propertyPtr: Pointer, propertyLen: Int): Int;
@RetainForTarget("wasm32")
@SymbolName("Konan_js_getProperty")
external public fun Konan_js_getProperty(arena: Arena, obj: Object, propertyPtr: Pointer, propertyLen: Int): Int;
@RetainForTarget("wasm32")
@SymbolName("Konan_js_setFunction")
external public fun setFunction(arena: Arena, obj: Object, propertyName: Pointer, propertyLength: Int , function: Int)
@RetainForTarget("wasm32")
@SymbolName("Konan_js_setString")
external public fun setString(arena: Arena, obj: Object, propertyName: Pointer, propertyLength: Int, stringPtr: Pointer, stringLength: Int )
fun setter(obj: JsValue, property: String, string: String) {
setString(obj.arena, obj.index, stringPointer(property), stringLengthBytes(property), stringPointer(string), stringLengthBytes(string))
}
fun setter(obj: JsValue, property: String, lambda: KtFunction<Unit>) {
val pointer = wrapFunction(lambda);
setFunction(obj.arena, obj.index, stringPointer(property), stringLengthBytes(property), pointer)
}
fun JsValue.setter(property: String, lambda: KtFunction<Unit>) {
setter(this, property, lambda)
}
fun JsValue.setter(property: String, string: String) {
setter(this, property, string)
}
object ArenaManager {
val globalArena = allocateArena()
var currentArena = globalArena
}
+18
View File
@@ -0,0 +1,18 @@
# Kotlin-native interop
## Usage
Create file `../gradle.properties` with contents:
llvmInstallPath=/path/to/llvm
Create a Gradle subproject somewhere under `../`, using `../InteropExample` as a template.
To generate the interop stubs and libraries and build all sources you can run
the following command from `../`:
./gradlew InteropExample:build
To run the example (if 'application' plugin is enabled):
./gradlew InteropExample:run
@@ -0,0 +1,93 @@
/*
* 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.
*/
import org.jetbrains.gradle.plugins.tools.lib
import org.jetbrains.gradle.plugins.tools.solib
import org.jetbrains.kotlin.*
import org.jetbrains.kotlin.konan.target.HostManager
import java.io.ByteArrayOutputStream
val kotlinVersion = project.bootstrapKotlinVersion
plugins {
`native`
`kotlin`
}
//apply plugin: 'c'
native {
val isWindows = PlatformInfo.isWindows()
val obj = if (isWindows) "obj" else "o"
val host = rootProject.project(":kotlin-native").extra["hostName"]
val hostLibffiDir = rootProject.project(":kotlin-native").extra["${host}LibffiDir"]
val cflags = mutableListOf("-I$hostLibffiDir/include",
*platformManager.hostPlatform.clang.hostCompilerArgsForJni)
if (!HostManager.hostIsMingw) {
cflags += "-fPIC"
}
suffixes {
(".c" to ".$obj") {
tool(*platformManager.hostPlatform.clang.clangC("").toTypedArray())
flags( *cflags.toTypedArray(), "-c", "-o", ruleOut(), ruleInFirst())
}
}
sourceSet {
"callbacks" {
dir("src/callbacks/c")
}
}
val objSet = sourceSets["callbacks"]!!.transform(".c" to ".$obj")
target(solib("callbacks"), objSet) {
tool(*platformManager.hostPlatform.clang.clangCXX("").toTypedArray())
flags("-shared",
"-o",ruleOut(), *ruleInAll(),
"-L${project(":kotlin-native:libclangext").buildDir}",
"$hostLibffiDir/lib/libffi.a",
"-lclangext")
}
tasks.named(solib("callbacks")).configure {
dependsOn(":kotlin-native:libclangext:${lib("clangext")}")
}
}
dependencies {
implementation(project(":kotlin-native:utilities:basic-utils"))
implementation(project(":kotlin-stdlib"))
implementation(project(":kotlin-reflect"))
}
sourceSets.main.get().java.srcDir("src/jvm/kotlin")
tasks.withType<org.jetbrains.kotlin.gradle.tasks.KotlinCompile>().configureEach {
kotlinOptions {
freeCompilerArgs = listOf("-Xuse-experimental=kotlin.ExperimentalUnsignedTypes",
"-Xuse-experimental=kotlin.Experimental",
"-Xopt-in=kotlin.RequiresOptIn",
"-XXLanguage:+InlineClasses",
"-Xskip-prerelease-check")
allWarningsAsErrors = true
}
}
val nativelibs = project.tasks.create<Copy>("nativelibs") {
dependsOn(solib("callbacks"))
from("$buildDir/")
into("$buildDir/nativelibs/")
}
@@ -0,0 +1,243 @@
#include <stdint.h>
#include <stdlib.h>
#include <assert.h>
#include <jni.h>
#include <ffi.h>
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeVoid
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeVoid(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_void;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeUInt8
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeUInt8(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint8;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeSInt8
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeSInt8(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint8;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeUInt16
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeUInt16(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint16;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeSInt16
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeSInt16(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint16;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeUInt32
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeUInt32(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint32;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeSInt32
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeSInt32(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint32;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeUInt64
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeUInt64(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_uint64;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeSInt64
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeSInt64(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_sint64;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypePointer
* Signature: ()J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypePointer(JNIEnv *env, jclass cls) {
return (jlong) &ffi_type_pointer;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiTypeStruct0
* Signature: (J)J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiTypeStruct0(JNIEnv *env, jclass cls, jlong elements) {
ffi_type* res = malloc(sizeof(ffi_type));
if (res != NULL) {
res->size = 0;
res->alignment = 0;
res->elements = (ffi_type**) elements;
res->type = FFI_TYPE_STRUCT;
}
return (jlong) res;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiCreateCif0
* Signature: (IJJ)J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiCreateCif0(JNIEnv *env, jclass cls, jint nArgs, jlong rType, jlong argTypes) {
ffi_cif* res = malloc(sizeof(ffi_cif));
if (res != NULL) {
ffi_status status = ffi_prep_cif(res, FFI_DEFAULT_ABI, nArgs, (ffi_type*)rType, (ffi_type**)argTypes);
if (status != FFI_OK) {
if (status == FFI_BAD_TYPEDEF) {
return -(jlong)1;
} else if (status == FFI_BAD_ABI) {
return -(jlong)2;
} else {
return -(jlong)3;
}
}
}
return (jlong) res;
}
static JavaVM *vm = NULL;
// Returns the JNI env which can be used by the caller.
// If current thread is not attached to JVM, then it gets attached as daemon.
static JNIEnv* getCurrentEnv() {
JNIEnv* env;
assert(vm != NULL);
jint res = (*vm)->GetEnv(vm, (void**)&env, JNI_VERSION_1_1);
if (res != JNI_OK) {
assert(res == JNI_EDETACHED);
res = (*vm)->AttachCurrentThreadAsDaemon(vm, (void**)&env, NULL);
assert(res == JNI_OK);
}
return env;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm_, void *reserved) {
vm = vm_;
return JNI_VERSION_1_1;
}
// Checks for pending exception. If there is one, describes it and terminates the process.
static void checkException(JNIEnv *env) {
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env);
abort();
}
}
static void ffi_fun(ffi_cif *cif, void *ret, void **args, void *user_data) {
JNIEnv* env = getCurrentEnv();
static jmethodID acceptFun = NULL;
static jclass cls = NULL;
if (acceptFun == NULL) {
// Note: in some cases [FindClass] below may use a classloader different from the one loaded interop classes,
// so stick to JVM-provided class:
jclass clsLocal = (*env)->FindClass(env, "java/util/function/LongConsumer");
checkException(env);
assert(clsLocal != NULL);
cls = (jclass) (*env)->NewGlobalRef(env, clsLocal);
checkException(env);
assert(cls != NULL);
acceptFun = (*env)->GetMethodID(env, cls, "accept", "(J)V");
checkException(env);
assert(acceptFun != NULL);
}
jlong retAndArgs[2] = { (jlong)ret, (jlong)args }; // Unpacked in [ffiClosureImpl].
(*env)->CallVoidMethod(env, (jobject) user_data, acceptFun, (jlong)(intptr_t)&retAndArgs[0]);
checkException(env);
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: ffiCreateClosure0
* Signature: (JLjava/lang/Object;)J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_ffiCreateClosure0(JNIEnv *env, jclass cls, jlong ffiCif, jobject userData) {
jobject userDataGlobalRef = (*env)->NewGlobalRef(env, userData);
if (userDataGlobalRef == NULL) {
return (jlong)0;
}
assert(sizeof(jobject) == sizeof(void*)); // TODO: check statically
void* userDataPtr = (void*) userDataGlobalRef;
void* res;
ffi_closure *closure = ffi_closure_alloc(sizeof(ffi_closure), &res);
if (closure == NULL) {
return (jlong)0;
}
ffi_status status = ffi_prep_closure_loc(closure, (ffi_cif*)ffiCif, ffi_fun, userDataPtr, res);
if (status != FFI_OK) {
return -(jlong)1;
}
return (jlong) res;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: newGlobalRef
* Signature: (Ljava/lang/Object;)J
*/
JNIEXPORT jlong JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_newGlobalRef(JNIEnv *env, jclass cls, jobject obj) {
jobject res = (*env)->NewGlobalRef(env, obj);
return (jlong) res;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: derefGlobalRef
* Signature: (J)Ljava/lang/Object;
*/
JNIEXPORT jobject JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_derefGlobalRef(JNIEnv *env, jclass cls, jlong ref) {
return (jobject) ref;
}
/*
* Class: kotlinx_cinterop_JvmCallbacksKt
* Method: deleteGlobalRef
* Signature: (J)V
*/
JNIEXPORT void JNICALL Java_kotlinx_cinterop_JvmCallbacksKt_deleteGlobalRef(JNIEnv *env, jclass cls, jlong ref) {
(*env)->DeleteGlobalRef(env, (jobject) ref);
}
@@ -0,0 +1,461 @@
/*
* 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 kotlinx.cinterop
import java.util.concurrent.ConcurrentHashMap
import java.util.function.LongConsumer
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KType
import kotlin.reflect.full.companionObjectInstance
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.full.isSubclassOf
import kotlin.reflect.jvm.reflect
internal fun createStablePointer(any: Any): COpaquePointer = newGlobalRef(any).toCPointer()!!
internal fun disposeStablePointer(pointer: COpaquePointer) = deleteGlobalRef(pointer.toLong())
@PublishedApi
internal fun derefStablePointer(pointer: COpaquePointer): Any = derefGlobalRef(pointer.toLong())
private fun getFieldCType(type: KType): CType<*> {
val classifier = type.classifier
if (classifier is KClass<*> && classifier.isSubclassOf(CStructVar::class)) {
return getStructCType(classifier)
}
return getArgOrRetValCType(type)
}
private fun getVariableCType(type: KType): CType<*>? {
val classifier = type.classifier
return when (classifier) {
!is KClass<*> -> null
ByteVarOf::class -> SInt8
ShortVarOf::class -> SInt16
IntVarOf::class -> SInt32
LongVarOf::class -> SInt64
CPointerVarOf::class -> Pointer
// TODO: floats, enums.
else -> if (classifier.isSubclassOf(CStructVar::class)) {
getStructCType(classifier)
} else {
null
}
}
}
private val structTypeCache = ConcurrentHashMap<Class<*>, CType<*>>()
private fun getStructCType(structClass: KClass<*>): CType<*> = structTypeCache.computeIfAbsent(structClass.java) {
// Note that struct classes are not supposed to be user-defined,
// so they don't require to be checked strictly.
val annotations = structClass.annotations
val cNaturalStruct = annotations.filterIsInstance<CNaturalStruct>().firstOrNull() ?:
error("struct ${structClass.simpleName} has custom layout")
val propertiesByName = structClass.declaredMemberProperties.groupBy { it.name }
val fields = cNaturalStruct.fieldNames.map {
propertiesByName[it]!!.single()
}
val fieldCTypes = mutableListOf<CType<*>>()
for (field in fields) {
val lengthAnnotation = field.annotations.filterIsInstance<CLength>().firstOrNull()
if (lengthAnnotation == null) {
val fieldType = getFieldCType(field.returnType)
fieldCTypes.add(fieldType)
} else {
assert(field.returnType.classifier == CPointer::class)
val length = lengthAnnotation.value
if (length != 0) {
val pointed = field.returnType.arguments.single().type!!
val pointedCType = getVariableCType(pointed) ?: TODO("array element type '$pointed'")
// Represent array field as repeated element-typed fields:
repeat(length) {
fieldCTypes.add(pointedCType)
}
}
}
}
@Suppress("DEPRECATION")
val structType = structClass.companionObjectInstance as CVariable.Type
Struct(structType.size, structType.align, fieldCTypes)
}
private fun getStructValueCType(type: KType): CType<*> {
val structClass = type.arguments.singleOrNull()?.type?.classifier as? KClass<*> ?:
error("'$type' type is incomplete")
return getStructCType(structClass)
}
private fun getEnumCType(classifier: KClass<*>): CEnumType? {
val rawValueType = classifier.declaredMemberProperties.single().returnType
val rawValueCType = when (rawValueType.classifier) {
Byte::class -> SInt8
Short::class -> SInt16
Int::class -> SInt32
Long::class -> SInt64
else -> error("'${classifier.simpleName}' has unexpected value type '$rawValueType'")
}
@Suppress("UNCHECKED_CAST")
return CEnumType(rawValueCType as CType<Any>)
}
private fun getArgOrRetValCType(type: KType): CType<*> {
val classifier = type.classifier
val result = when (classifier) {
!is KClass<*> -> null
Unit::class -> Void
Byte::class -> SInt8
Short::class -> SInt16
Int::class -> SInt32
Long::class -> SInt64
CPointer::class -> Pointer
// TODO: floats
CValue::class -> getStructValueCType(type)
else -> if (classifier.isSubclassOf(@Suppress("DEPRECATION") CEnum::class)) {
getEnumCType(classifier)
} else {
null
}
} ?: error("$type is not supported in callback signature")
if (type.isMarkedNullable != (classifier == CPointer::class)) {
if (type.isMarkedNullable) {
error("$type must not be nullable when used in callback signature")
} else {
error("$type must be nullable when used in callback signature")
}
}
return result
}
private fun createStaticCFunction(function: Function<*>): CPointer<CFunction<*>> {
val errorMessage = "staticCFunction must take an unbound, non-capturing function"
if (!isStatic(function)) {
throw IllegalArgumentException(errorMessage)
}
val kFunction = function as? KFunction<*> ?: function.reflect() ?:
throw IllegalArgumentException(errorMessage)
val returnType = getArgOrRetValCType(kFunction.returnType)
val paramTypes = kFunction.parameters.map { getArgOrRetValCType(it.type) }
@Suppress("UNCHECKED_CAST")
return interpretCPointer(createStaticCFunctionImpl(returnType as CType<Any?>, paramTypes, function))!!
}
/**
* Returns `true` if given function is *static* as defined in [staticCFunction].
*/
private fun isStatic(function: Function<*>): Boolean {
// TODO: revise
try {
with(function.javaClass.getDeclaredField("INSTANCE")) {
if (!java.lang.reflect.Modifier.isStatic(modifiers) || !java.lang.reflect.Modifier.isFinal(modifiers)) {
return false
}
isAccessible = true // TODO: undo
return get(null) == function
// If the class has static final "INSTANCE" field, and only the value of this field is accepted,
// then each class is handled at most once, so these checks prevent memory leaks.
}
} catch (e: NoSuchFieldException) {
return false
}
}
private val createdStaticFunctions = ConcurrentHashMap<Class<*>, CPointer<CFunction<*>>>()
@Suppress("UNCHECKED_CAST")
internal fun <F : Function<*>> staticCFunctionImpl(function: F) =
createdStaticFunctions.computeIfAbsent(function.javaClass) {
createStaticCFunction(function)
} as CPointer<CFunction<F>>
private val invokeMethods = (0 .. 22).map { arity ->
Class.forName("kotlin.jvm.functions.Function$arity").getMethod("invoke",
*Array<Class<*>>(arity) { java.lang.Object::class.java })
}
private fun createStaticCFunctionImpl(
returnType: CType<Any?>,
paramTypes: List<CType<*>>,
function: Function<*>
): NativePtr {
val ffiCif = ffiCreateCif(returnType.ffiType, paramTypes.map { it.ffiType })
val arity = paramTypes.size
val pt = paramTypes.toTypedArray()
@Suppress("UNCHECKED_CAST")
val impl: FfiClosureImpl = when (arity) {
0 -> {
val f = function as () -> Any?
ffiClosureImpl(returnType) { _ ->
f()
}
}
1 -> {
val f = function as (Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0))
}
}
2 -> {
val f = function as (Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1))
}
}
3 -> {
val f = function as (Any?, Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1), pt.read(args, 2))
}
}
4 -> {
val f = function as (Any?, Any?, Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1), pt.read(args, 2), pt.read(args, 3))
}
}
5 -> {
val f = function as (Any?, Any?, Any?, Any?, Any?) -> Any?
ffiClosureImpl(returnType) { args ->
f(pt.read(args, 0), pt.read(args, 1), pt.read(args, 2), pt.read(args, 3), pt.read(args, 4))
}
}
else -> {
val invokeMethod = invokeMethods[arity]
ffiClosureImpl(returnType) { args ->
val arguments = Array(arity) { pt.read(args, it) }
invokeMethod.invoke(function, *arguments)
}
}
}
return ffiCreateClosure(ffiCif, impl)
}
@Suppress("NOTHING_TO_INLINE")
private inline fun Array<CType<*>>.read(args: CArrayPointer<COpaquePointerVar>, index: Int) =
this[index].read(args[index].rawValue)
private inline fun ffiClosureImpl(
returnType: CType<Any?>,
crossinline invoke: (args: CArrayPointer<COpaquePointerVar>) -> Any?
): FfiClosureImpl {
// Called through [ffi_fun] when a native function created with [ffiCreateClosure] is invoked.
return LongConsumer { retAndArgsRaw ->
val retAndArgs = retAndArgsRaw.toCPointer<CPointerVar<*>>()!!
// Pointer to memory to be filled with return value of the invoked native function:
val ret = retAndArgs[0]!!
// Pointer to array of pointers to arguments passed to the invoked native function:
val args = retAndArgs[1]!!.reinterpret<COpaquePointerVar>()
val result = invoke(args)
returnType.write(ret.rawValue, result)
}
}
/**
* Describes the bridge between Kotlin type `T` and the corresponding C type of a function's parameter or return value.
* It is supposed to be constructed using the primitive types (such as [SInt32]), the [Struct] combinator
* and the [CEnumType] wrapper.
*
* This description omits the details that are irrelevant for the ABI.
*/
private abstract class CType<T> internal constructor(val ffiType: ffi_type) {
internal constructor(ffiTypePtr: Long) : this(interpretPointed<ffi_type>(ffiTypePtr))
abstract fun read(location: NativePtr): T
abstract fun write(location: NativePtr, value: T): Unit
}
private object Void : CType<Any?>(ffiTypeVoid()) {
override fun read(location: NativePtr) = throw UnsupportedOperationException()
override fun write(location: NativePtr, value: Any?) {
// nothing to do.
}
}
private object SInt8 : CType<Byte>(ffiTypeSInt8()) {
override fun read(location: NativePtr) = interpretPointed<ByteVar>(location).value
override fun write(location: NativePtr, value: Byte) {
interpretPointed<ByteVar>(location).value = value
}
}
private object SInt16 : CType<Short>(ffiTypeSInt16()) {
override fun read(location: NativePtr) = interpretPointed<ShortVar>(location).value
override fun write(location: NativePtr, value: Short) {
interpretPointed<ShortVar>(location).value = value
}
}
private object SInt32 : CType<Int>(ffiTypeSInt32()) {
override fun read(location: NativePtr) = interpretPointed<IntVar>(location).value
override fun write(location: NativePtr, value: Int) {
interpretPointed<IntVar>(location).value = value
}
}
private object SInt64 : CType<Long>(ffiTypeSInt64()) {
override fun read(location: NativePtr) = interpretPointed<LongVar>(location).value
override fun write(location: NativePtr, value: Long) {
interpretPointed<LongVar>(location).value = value
}
}
private object Pointer : CType<CPointer<*>?>(ffiTypePointer()) {
override fun read(location: NativePtr) = interpretPointed<CPointerVar<*>>(location).value
override fun write(location: NativePtr, value: CPointer<*>?) {
interpretPointed<CPointerVar<*>>(location).value = value
}
}
private class Struct(val size: Long, val align: Int, elementTypes: List<CType<*>>) : CType<CValue<*>>(
ffiTypeStruct(
elementTypes.map { it.ffiType }
)
) {
override fun read(location: NativePtr) = interpretPointed<ByteVar>(location).readValue<CStructVar>(size, align)
override fun write(location: NativePtr, value: CValue<*>) = value.write(location)
}
@Suppress("DEPRECATION")
private class CEnumType(private val rawValueCType: CType<Any>) : CType<CEnum>(rawValueCType.ffiType) {
override fun read(location: NativePtr): CEnum {
TODO("enum-typed callback parameters")
}
override fun write(location: NativePtr, value: CEnum) {
rawValueCType.write(location, value.value)
}
}
private typealias FfiClosureImpl = LongConsumer
private typealias UserData = FfiClosureImpl
private val topLevelInitializer = loadKonanLibrary("callbacks")
/**
* Reference to `ffi_type` struct instance.
*/
internal class ffi_type(rawPtr: NativePtr) : COpaque(rawPtr)
/**
* Reference to `ffi_cif` struct instance.
*/
internal class ffi_cif(rawPtr: NativePtr) : COpaque(rawPtr)
private external fun ffiTypeVoid(): Long
private external fun ffiTypeUInt8(): Long
private external fun ffiTypeSInt8(): Long
private external fun ffiTypeUInt16(): Long
private external fun ffiTypeSInt16(): Long
private external fun ffiTypeUInt32(): Long
private external fun ffiTypeSInt32(): Long
private external fun ffiTypeUInt64(): Long
private external fun ffiTypeSInt64(): Long
private external fun ffiTypePointer(): Long
private external fun ffiTypeStruct0(elements: Long): Long
/**
* Allocates and initializes `ffi_type` describing the struct.
*
* @param elements types of the struct elements
*/
private fun ffiTypeStruct(elementTypes: List<ffi_type>): ffi_type {
val elements = nativeHeap.allocArrayOfPointersTo(*elementTypes.toTypedArray(), null)
val res = ffiTypeStruct0(elements.rawValue)
if (res == 0L) {
throw OutOfMemoryError()
}
return interpretPointed(res)
}
private external fun ffiCreateCif0(nArgs: Int, rType: Long, argTypes: Long): Long
/**
* Creates and prepares an `ffi_cif`.
*
* @param returnType native function return value type
* @param paramTypes native function parameter types
*
* @return the initialized `ffi_cif`
*/
private fun ffiCreateCif(returnType: ffi_type, paramTypes: List<ffi_type>): ffi_cif {
val nArgs = paramTypes.size
val argTypes = nativeHeap.allocArrayOfPointersTo(*paramTypes.toTypedArray(), null)
val res = ffiCreateCif0(nArgs, returnType.rawPtr, argTypes.rawValue)
when (res) {
0L -> throw OutOfMemoryError()
-1L -> throw Error("FFI_BAD_TYPEDEF")
-2L -> throw Error("FFI_BAD_ABI")
-3L -> throw Error("libffi error occurred")
}
return interpretPointed(res)
}
private external fun ffiCreateClosure0(ffiCif: Long, userData: Any): Long
/**
* Uses libffi to allocate a native function which will call [impl] when invoked.
*
* @param ffiCif describes the type of the function to create
*/
private fun ffiCreateClosure(ffiCif: ffi_cif, impl: FfiClosureImpl): NativePtr {
val res = ffiCreateClosure0(ffiCif.rawPtr, userData = impl)
when (res) {
0L -> throw OutOfMemoryError()
-1L -> throw Error("libffi error occurred")
}
return res
}
private external fun newGlobalRef(any: Any): Long
private external fun derefGlobalRef(ref: Long): Any
private external fun deleteGlobalRef(ref: Long)
@@ -0,0 +1,120 @@
/*
* 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 kotlinx.cinterop
import org.jetbrains.kotlin.konan.util.nativeMemoryAllocator
import sun.misc.Unsafe
private val NativePointed.address: Long
get() = this.rawPtr
private enum class DataModel(val pointerSize: Long) {
_32BIT(4),
_64BIT(8)
}
private val dataModel: DataModel = when (System.getProperty("sun.arch.data.model")) {
null -> TODO()
"32" -> DataModel._32BIT
"64" -> DataModel._64BIT
else -> throw IllegalStateException()
}
// Must be only used in interop, contains host pointer size, not target!
@PublishedApi
internal val pointerSize: Int = dataModel.pointerSize.toInt()
@PublishedApi
internal object nativeMemUtils {
fun getByte(mem: NativePointed) = unsafe.getByte(mem.address)
fun putByte(mem: NativePointed, value: Byte) = unsafe.putByte(mem.address, value)
fun getShort(mem: NativePointed) = unsafe.getShort(mem.address)
fun putShort(mem: NativePointed, value: Short) = unsafe.putShort(mem.address, value)
fun getInt(mem: NativePointed) = unsafe.getInt(mem.address)
fun putInt(mem: NativePointed, value: Int) = unsafe.putInt(mem.address, value)
fun getLong(mem: NativePointed) = unsafe.getLong(mem.address)
fun putLong(mem: NativePointed, value: Long) = unsafe.putLong(mem.address, value)
fun getFloat(mem: NativePointed) = unsafe.getFloat(mem.address)
fun putFloat(mem: NativePointed, value: Float) = unsafe.putFloat(mem.address, value)
fun getDouble(mem: NativePointed) = unsafe.getDouble(mem.address)
fun putDouble(mem: NativePointed, value: Double) = unsafe.putDouble(mem.address, value)
fun getNativePtr(mem: NativePointed): NativePtr = when (dataModel) {
DataModel._32BIT -> getInt(mem).toLong()
DataModel._64BIT -> getLong(mem)
}
fun putNativePtr(mem: NativePointed, value: NativePtr) = when (dataModel) {
DataModel._32BIT -> putInt(mem, value.toInt())
DataModel._64BIT -> putLong(mem, value)
}
fun getByteArray(source: NativePointed, dest: ByteArray, length: Int) {
unsafe.copyMemory(null, source.address, dest, byteArrayBaseOffset, length.toLong())
}
fun putByteArray(source: ByteArray, dest: NativePointed, length: Int) {
unsafe.copyMemory(source, byteArrayBaseOffset, null, dest.address, length.toLong())
}
fun getCharArray(source: NativePointed, dest: CharArray, length: Int) {
unsafe.copyMemory(null, source.address, dest, charArrayBaseOffset, length.toLong() * 2)
}
fun putCharArray(source: CharArray, dest: NativePointed, length: Int) {
unsafe.copyMemory(source, charArrayBaseOffset, null, dest.address, length.toLong() * 2)
}
fun zeroMemory(dest: NativePointed, length: Int): Unit =
unsafe.setMemory(dest.address, length.toLong(), 0)
fun copyMemory(dest: NativePointed, length: Int, src: NativePointed) =
unsafe.copyMemory(src.address, dest.address, length.toLong())
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
inline fun <reified T> allocateInstance(): T {
return unsafe.allocateInstance(T::class.java) as T
}
internal fun allocRaw(size: Long, align: Int): NativePtr {
val address = unsafe.allocateMemory(size)
if (address % align != 0L) TODO(align.toString())
return address
}
internal fun freeRaw(mem: NativePtr) {
unsafe.freeMemory(mem)
}
fun alloc(size: Long, align: Int) = interpretOpaquePointed(nativeMemoryAllocator.alloc(size, align))
fun free(mem: NativePtr) = nativeMemoryAllocator.free(mem)
private val unsafe = with(Unsafe::class.java.getDeclaredField("theUnsafe")) {
isAccessible = true
return@with this.get(null) as Unsafe
}
private val byteArrayBaseOffset = unsafe.arrayBaseOffset(ByteArray::class.java).toLong()
private val charArrayBaseOffset = unsafe.arrayBaseOffset(CharArray::class.java).toLong()
}
@@ -0,0 +1,145 @@
/*
* 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 kotlinx.cinterop
import java.util.concurrent.ConcurrentHashMap
import kotlin.reflect.full.companionObjectInstance
typealias NativePtr = Long
internal typealias NonNullNativePtr = NativePtr
@PublishedApi internal fun NonNullNativePtr.toNativePtr() = this
internal fun NativePtr.toNonNull(): NonNullNativePtr = this
public val nativeNullPtr: NativePtr = 0L
// TODO: the functions below should eventually be intrinsified
@Suppress("DEPRECATION")
private val typeOfCache = ConcurrentHashMap<Class<*>, CVariable.Type>()
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
inline fun <reified T : CVariable> typeOf() =
@Suppress("DEPRECATION")
typeOfCache.computeIfAbsent(T::class.java) { T::class.companionObjectInstance as CVariable.Type }
/**
* Returns interpretation of entity with given pointer, or `null` if it is null.
*
* @param T must not be abstract
*/
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
inline fun <reified T : NativePointed> interpretNullablePointed(ptr: NativePtr): T? {
if (ptr == nativeNullPtr) {
return null
} else {
val result = nativeMemUtils.allocateInstance<T>()
result.rawPtr = ptr
return result
}
}
/**
* Creates a [CPointer] from the raw pointer of [NativePtr].
*
* @return a [CPointer] representation, or `null` if the [rawValue] represents native `nullptr`.
*/
fun <T : CPointed> interpretCPointer(rawValue: NativePtr) =
if (rawValue == nativeNullPtr) {
null
} else {
CPointer<T>(rawValue)
}
internal fun CPointer<*>.cPointerToString() = "CPointer(raw=0x%x)".format(rawValue)
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.RUNTIME)
annotation class CLength(val value: Int)
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class CNaturalStruct(vararg val fieldNames: String)
fun <R> staticCFunction(function: () -> R): CPointer<CFunction<() -> R>> =
staticCFunctionImpl(function)
fun <P1, R> staticCFunction(function: (P1) -> R): CPointer<CFunction<(P1) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, R> staticCFunction(function: (P1, P2) -> R): CPointer<CFunction<(P1, P2) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, R> staticCFunction(function: (P1, P2, P3) -> R): CPointer<CFunction<(P1, P2, P3) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, R> staticCFunction(function: (P1, P2, P3, P4) -> R): CPointer<CFunction<(P1, P2, P3, P4) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, R> staticCFunction(function: (P1, P2, P3, P4, P5) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R>> =
staticCFunctionImpl(function)
fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, R> staticCFunction(function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R>> =
staticCFunctionImpl(function)
@@ -0,0 +1,122 @@
/*
* 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 kotlinx.cinterop
import org.jetbrains.kotlin.konan.util.KonanHomeProvider
import java.io.File
import java.nio.file.Files
import java.nio.file.Paths
private fun decodeFromUtf8(bytes: ByteArray) = String(bytes)
internal fun encodeToUtf8(str: String) = str.toByteArray()
internal fun CPointer<ByteVar>.toKStringFromUtf8Impl(): String {
val nativeBytes = this
var length = 0
while (nativeBytes[length] != 0.toByte()) {
++length
}
val bytes = ByteArray(length)
nativeMemUtils.getByteArray(nativeBytes.pointed, bytes, length)
return decodeFromUtf8(bytes)
}
fun bitsToFloat(bits: Int): Float = java.lang.Float.intBitsToFloat(bits)
fun bitsToDouble(bits: Long): Double = java.lang.Double.longBitsToDouble(bits)
// TODO: the functions below should eventually be intrinsified
inline fun <reified R : Number> Byte.signExtend(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
java.lang.Short::class.java -> this.toShort() as R
java.lang.Integer::class.java -> this.toInt() as R
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidSignExtension()
}
inline fun <reified R : Number> Short.signExtend(): R = when (R::class.java) {
java.lang.Short::class.java -> this.toShort() as R
java.lang.Integer::class.java -> this.toInt() as R
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidSignExtension()
}
inline fun <reified R : Number> Int.signExtend(): R = when (R::class.java) {
java.lang.Integer::class.java -> this.toInt() as R
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidSignExtension()
}
inline fun <reified R : Number> Long.signExtend(): R = when (R::class.java) {
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidSignExtension()
}
inline fun <reified R : Number> Number.invalidSignExtension(): R {
throw Error("unable to sign extend ${this.javaClass.simpleName} \"${this}\" to ${R::class.java.simpleName}")
}
inline fun <reified R : Number> Byte.narrow(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
else -> this.invalidNarrowing()
}
inline fun <reified R : Number> Short.narrow(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
java.lang.Short::class.java -> this.toShort() as R
else -> this.invalidNarrowing()
}
inline fun <reified R : Number> Int.narrow(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
java.lang.Short::class.java -> this.toShort() as R
java.lang.Integer::class.java -> this.toInt() as R
else -> this.invalidNarrowing()
}
inline fun <reified R : Number> Long.narrow(): R = when (R::class.java) {
java.lang.Byte::class.java -> this.toByte() as R
java.lang.Short::class.java -> this.toShort() as R
java.lang.Integer::class.java -> this.toInt() as R
java.lang.Long::class.java -> this.toLong() as R
else -> this.invalidNarrowing()
}
inline fun <reified R : Number> Number.invalidNarrowing(): R {
throw Error("unable to narrow ${this.javaClass.simpleName} \"${this}\" to ${R::class.java.simpleName}")
}
fun loadKonanLibrary(name: String) {
try {
System.loadLibrary(name)
} catch (e: UnsatisfiedLinkError) {
val fullLibraryName = System.mapLibraryName(name)
val dir = "${KonanHomeProvider.determineKonanHome()}/konan/nativelib"
try {
System.load("$dir/$fullLibraryName")
} catch (e: UnsatisfiedLinkError) {
val tempDir = Files.createTempDirectory(Paths.get(dir), null).toAbsolutePath().toString()
Files.createLink(Paths.get(tempDir, fullLibraryName), Paths.get(dir, fullLibraryName))
// TODO: Does not work on Windows. May be use FILE_FLAG_DELETE_ON_CLOSE?
File(tempDir).deleteOnExit()
File("$tempDir/$fullLibraryName").deleteOnExit()
System.load("$tempDir/$fullLibraryName")
}
}
}
@@ -0,0 +1,316 @@
/*
* 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.
*/
@file:Suppress("FINAL_UPPER_BOUND", "NOTHING_TO_INLINE")
package kotlinx.cinterop
@JvmName("plus\$Byte")
inline operator fun <T : ByteVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 1)
@JvmName("plus\$Byte")
inline operator fun <T : ByteVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Byte")
inline operator fun <T : Byte> CPointer<ByteVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Byte")
inline operator fun <T : Byte> CPointer<ByteVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Byte")
inline operator fun <T : Byte> CPointer<ByteVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Byte")
inline operator fun <T : Byte> CPointer<ByteVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Short")
inline operator fun <T : ShortVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 2)
@JvmName("plus\$Short")
inline operator fun <T : ShortVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Short")
inline operator fun <T : Short> CPointer<ShortVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Short")
inline operator fun <T : Short> CPointer<ShortVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Short")
inline operator fun <T : Short> CPointer<ShortVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Short")
inline operator fun <T : Short> CPointer<ShortVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Int")
inline operator fun <T : IntVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 4)
@JvmName("plus\$Int")
inline operator fun <T : IntVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Int")
inline operator fun <T : Int> CPointer<IntVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Int")
inline operator fun <T : Int> CPointer<IntVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Int")
inline operator fun <T : Int> CPointer<IntVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Int")
inline operator fun <T : Int> CPointer<IntVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Long")
inline operator fun <T : LongVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 8)
@JvmName("plus\$Long")
inline operator fun <T : LongVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Long")
inline operator fun <T : Long> CPointer<LongVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Long")
inline operator fun <T : Long> CPointer<LongVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Long")
inline operator fun <T : Long> CPointer<LongVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Long")
inline operator fun <T : Long> CPointer<LongVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$UByte")
inline operator fun <T : UByteVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 1)
@JvmName("plus\$UByte")
inline operator fun <T : UByteVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$UByte")
inline operator fun <T : UByte> CPointer<UByteVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
inline operator fun <T : UByte> CPointer<UByteVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
inline operator fun <T : UByte> CPointer<UByteVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
inline operator fun <T : UByte> CPointer<UByteVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$UShort")
inline operator fun <T : UShortVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 2)
@JvmName("plus\$UShort")
inline operator fun <T : UShortVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$UShort")
inline operator fun <T : UShort> CPointer<UShortVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
inline operator fun <T : UShort> CPointer<UShortVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$UShort")
inline operator fun <T : UShort> CPointer<UShortVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
inline operator fun <T : UShort> CPointer<UShortVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$UInt")
inline operator fun <T : UIntVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 4)
@JvmName("plus\$UInt")
inline operator fun <T : UIntVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$UInt")
inline operator fun <T : UInt> CPointer<UIntVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
inline operator fun <T : UInt> CPointer<UIntVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$UInt")
inline operator fun <T : UInt> CPointer<UIntVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
inline operator fun <T : UInt> CPointer<UIntVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$ULong")
inline operator fun <T : ULongVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 8)
@JvmName("plus\$ULong")
inline operator fun <T : ULongVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$ULong")
inline operator fun <T : ULong> CPointer<ULongVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
inline operator fun <T : ULong> CPointer<ULongVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$ULong")
inline operator fun <T : ULong> CPointer<ULongVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
inline operator fun <T : ULong> CPointer<ULongVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Float")
inline operator fun <T : FloatVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 4)
@JvmName("plus\$Float")
inline operator fun <T : FloatVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Float")
inline operator fun <T : Float> CPointer<FloatVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Float")
inline operator fun <T : Float> CPointer<FloatVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Float")
inline operator fun <T : Float> CPointer<FloatVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Float")
inline operator fun <T : Float> CPointer<FloatVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("plus\$Double")
inline operator fun <T : DoubleVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * 8)
@JvmName("plus\$Double")
inline operator fun <T : DoubleVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@JvmName("get\$Double")
inline operator fun <T : Double> CPointer<DoubleVarOf<T>>.get(index: Int): T =
(this + index)!!.pointed.value
@JvmName("set\$Double")
inline operator fun <T : Double> CPointer<DoubleVarOf<T>>.set(index: Int, value: T) {
(this + index)!!.pointed.value = value
}
@JvmName("get\$Double")
inline operator fun <T : Double> CPointer<DoubleVarOf<T>>.get(index: Long): T =
(this + index)!!.pointed.value
@JvmName("set\$Double")
inline operator fun <T : Double> CPointer<DoubleVarOf<T>>.set(index: Long, value: T) {
(this + index)!!.pointed.value = value
}
/* Generated by:
#!/bin/bash
function gen {
echo "@JvmName(\"plus\\\$$1\")"
echo "inline operator fun <T : ${1}VarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? ="
echo " interpretCPointer(this.rawValue + index * ${2})"
echo
echo "@JvmName(\"plus\\\$$1\")"
echo "inline operator fun <T : ${1}VarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? ="
echo " this + index.toLong()"
echo
echo "@JvmName(\"get\\\$$1\")"
echo "inline operator fun <T : $1> CPointer<${1}VarOf<T>>.get(index: Int): T ="
echo " (this + index)!!.pointed.value"
echo
echo "@JvmName(\"set\\\$$1\")"
echo "inline operator fun <T : $1> CPointer<${1}VarOf<T>>.set(index: Int, value: T) {"
echo " (this + index)!!.pointed.value = value"
echo '}'
echo
echo "@JvmName(\"get\\\$$1\")"
echo "inline operator fun <T : $1> CPointer<${1}VarOf<T>>.get(index: Long): T ="
echo " (this + index)!!.pointed.value"
echo
echo "@JvmName(\"set\\\$$1\")"
echo "inline operator fun <T : $1> CPointer<${1}VarOf<T>>.set(index: Long, value: T) {"
echo " (this + index)!!.pointed.value = value"
echo '}'
echo
}
gen Byte 1
gen Short 2
gen Int 4
gen Long 8
gen UByte 1
gen UShort 2
gen UInt 4
gen ULong 8
gen Float 4
gen Double 8
*/
@@ -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 kotlinx.cinterop
@Deprecated("Use StableRef<T> instead", ReplaceWith("StableRef<T>"), DeprecationLevel.ERROR)
typealias StableObjPtr = StableRef<*>
/**
* This class provides a way to create a stable handle to any Kotlin object.
* After [converting to CPointer][asCPointer] it can be safely passed to native code e.g. to be received
* in a Kotlin callback.
*
* Any [StableRef] should be manually [disposed][dispose]
*/
@Suppress("NON_PUBLIC_PRIMARY_CONSTRUCTOR_OF_INLINE_CLASS")
public inline class StableRef<out T : Any> @PublishedApi internal constructor(
private val stablePtr: COpaquePointer
) {
companion object {
/**
* Creates a handle for given object.
*/
fun <T : Any> create(any: T) = StableRef<T>(createStablePointer(any))
/**
* Creates [StableRef] from given raw value.
*
* @param value must be a [value] of some [StableRef]
*/
@Deprecated("Use CPointer<*>.asStableRef<T>() instead", ReplaceWith("ptr.asStableRef<T>()"),
DeprecationLevel.ERROR)
fun fromValue(value: COpaquePointer) = value.asStableRef<Any>()
}
@Deprecated("Use .asCPointer() instead", ReplaceWith("this.asCPointer()"), DeprecationLevel.ERROR)
val value: COpaquePointer get() = this.asCPointer()
/**
* Converts the handle to C pointer.
* @see [asStableRef]
*/
fun asCPointer(): COpaquePointer = this.stablePtr
/**
* Disposes the handle. It must not be used after that.
*/
fun dispose() {
disposeStablePointer(this.stablePtr)
}
/**
* Returns the object this handle was [created][StableRef.create] for.
*/
@Suppress("UNCHECKED_CAST")
fun get() = derefStablePointer(this.stablePtr) as T
}
/**
* Converts to [StableRef] this opaque pointer produced by [StableRef.asCPointer].
*/
inline fun <reified T : Any> CPointer<*>.asStableRef(): StableRef<T> = StableRef<T>(this).also { it.get() }
@@ -0,0 +1,506 @@
/*
* Copyright 2010-2019 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 kotlinx.cinterop
/**
* The entity which has an associated native pointer.
* Subtypes are supposed to represent interpretations of the pointed data or code.
*
* This interface is likely to be handled by compiler magic and shouldn't be subtyped by arbitrary classes.
*
* TODO: the behavior of [equals], [hashCode] and [toString] differs on Native and JVM backends.
*/
public open class NativePointed internal constructor(rawPtr: NonNullNativePtr) {
var rawPtr = rawPtr.toNativePtr()
internal set
}
// `null` value of `NativePointed?` is mapped to `nativeNullPtr`.
public val NativePointed?.rawPtr: NativePtr
get() = if (this != null) this.rawPtr else nativeNullPtr
/**
* Returns interpretation of entity with given pointer.
*
* @param T must not be abstract
*/
public inline fun <reified T : NativePointed> interpretPointed(ptr: NativePtr): T = interpretNullablePointed<T>(ptr)!!
private class OpaqueNativePointed(rawPtr: NativePtr) : NativePointed(rawPtr.toNonNull())
public fun interpretOpaquePointed(ptr: NativePtr): NativePointed = interpretPointed<OpaqueNativePointed>(ptr)
public fun interpretNullableOpaquePointed(ptr: NativePtr): NativePointed? = interpretNullablePointed<OpaqueNativePointed>(ptr)
/**
* Changes the interpretation of the pointed data or code.
*/
public inline fun <reified T : NativePointed> NativePointed.reinterpret(): T = interpretPointed(this.rawPtr)
/**
* C data or code.
*/
public abstract class CPointed(rawPtr: NativePtr) : NativePointed(rawPtr.toNonNull())
/**
* Represents a reference to (possibly empty) sequence of C values.
* It can be either a stable pointer [CPointer] or a sequence of immutable values [CValues].
*
* [CValuesRef] is designed to be used as Kotlin representation of pointer-typed parameters of C functions.
* When passing [CPointer] as [CValuesRef] to the Kotlin binding method, the C function receives exactly this pointer.
* Passing [CValues] has nearly the same semantics as passing by value: the C function receives
* the pointer to the temporary copy of these values, and the caller can't observe the modifications to this copy.
* The copy is valid until the C function returns.
* There are also other implementations of [CValuesRef] that provide temporary pointer,
* e.g. Kotlin Native specific [refTo] functions to pass primitive arrays directly to native.
*/
public abstract class CValuesRef<T : CPointed> {
/**
* If this reference is [CPointer], returns this pointer, otherwise
* allocate storage value in the scope and return it.
*/
public abstract fun getPointer(scope: AutofreeScope): CPointer<T>
}
/**
* The (possibly empty) sequence of immutable C values.
* It is self-contained and doesn't depend on native memory.
*/
public abstract class CValues<T : CVariable> : CValuesRef<T>() {
/**
* Copies the values to [placement] and returns the pointer to the copy.
*/
public override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
// TODO: optimize
public override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is CValues<*>) return false
val thisBytes = this.getBytes()
val otherBytes = other.getBytes()
if (thisBytes.size != otherBytes.size) {
return false
}
for (index in 0 .. thisBytes.size - 1) {
if (thisBytes[index] != otherBytes[index]) {
return false
}
}
return true
}
public override fun hashCode(): Int {
var result = 0
for (byte in this.getBytes()) {
result = result * 31 + byte
}
return result
}
public abstract val size: Int
public abstract val align: Int
/**
* Copy the referenced values to [placement] and return placement pointer.
*/
public abstract fun place(placement: CPointer<T>): CPointer<T>
}
public fun <T : CVariable> CValues<T>.placeTo(scope: AutofreeScope) = this.getPointer(scope)
/**
* The single immutable C value.
* It is self-contained and doesn't depend on native memory.
*
* TODO: consider providing an adapter instead of subtyping [CValues].
*/
public abstract class CValue<T : CVariable> : CValues<T>()
/**
* C pointer.
*/
public class CPointer<T : CPointed> internal constructor(@PublishedApi internal val value: NonNullNativePtr) : CValuesRef<T>() {
// TODO: replace by [value].
@Suppress("NOTHING_TO_INLINE")
public inline val rawValue: NativePtr get() = value.toNativePtr()
public override fun equals(other: Any?): Boolean {
if (this === other) {
return true // fast path
}
return (other is CPointer<*>) && (rawValue == other.rawValue)
}
public override fun hashCode(): Int {
return rawValue.hashCode()
}
public override fun toString() = this.cPointerToString()
public override fun getPointer(scope: AutofreeScope) = this
}
/**
* Returns the pointer to this data or code.
*/
public val <T : CPointed> T.ptr: CPointer<T>
get() = interpretCPointer(this.rawPtr)!!
/**
* Returns the corresponding [CPointed].
*
* @param T must not be abstract
*/
public inline val <reified T : CPointed> CPointer<T>.pointed: T
get() = interpretPointed<T>(this.rawValue)
// `null` value of `CPointer?` is mapped to `nativeNullPtr`
public val CPointer<*>?.rawValue: NativePtr
get() = if (this != null) this.rawValue else nativeNullPtr
public fun <T : CPointed> CPointer<*>.reinterpret(): CPointer<T> = interpretCPointer(this.rawValue)!!
public fun <T : CPointed> CPointer<T>?.toLong() = this.rawValue.toLong()
public fun <T : CPointed> Long.toCPointer(): CPointer<T>? = interpretCPointer(nativeNullPtr + this)
/**
* The [CPointed] without any specified interpretation.
*/
public abstract class COpaque(rawPtr: NativePtr) : CPointed(rawPtr) // TODO: should it correspond to COpaquePointer?
/**
* The pointer with an opaque type.
*/
public typealias COpaquePointer = CPointer<out CPointed> // FIXME
/**
* The variable containing a [COpaquePointer].
*/
public typealias COpaquePointerVar = CPointerVarOf<COpaquePointer>
/**
* The C data variable located in memory.
*
* The non-abstract subclasses should represent the (complete) C data type and thus specify size and alignment.
* Each such subclass must have a companion object which is a [Type].
*/
public abstract class CVariable(rawPtr: NativePtr) : CPointed(rawPtr) {
/**
* The (complete) C data type.
*
* @param size the size in bytes of data of this type
* @param align the alignments in bytes that is enough for this data type.
* It may be greater than actually required for simplicity.
*/
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
public open class Type(val size: Long, val align: Int) {
init {
require(size % align == 0L)
}
}
}
@Suppress("DEPRECATION")
public inline fun <reified T : CVariable> sizeOf() = typeOf<T>().size
@Suppress("DEPRECATION")
public inline fun <reified T : CVariable> alignOf() = typeOf<T>().align
/**
* Returns the member of this [CStructVar] which is located by given offset in bytes.
*/
public inline fun <reified T : CPointed> CStructVar.memberAt(offset: Long): T {
return interpretPointed<T>(this.rawPtr + offset)
}
public inline fun <reified T : CVariable> CStructVar.arrayMemberAt(offset: Long): CArrayPointer<T> {
return interpretCPointer<T>(this.rawPtr + offset)!!
}
/**
* The C struct-typed variable located in memory.
*/
public abstract class CStructVar(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
open class Type(size: Long, align: Int) : CVariable.Type(size, align)
}
/**
* The C primitive-typed variable located in memory.
*/
sealed class CPrimitiveVar(rawPtr: NativePtr) : CVariable(rawPtr) {
// aligning by size is obviously enough
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
open class Type(size: Int) : CVariable.Type(size.toLong(), align = size)
}
@Deprecated("Will be removed.")
public interface CEnum {
public val value: Any
}
public abstract class CEnumVar(rawPtr: NativePtr) : CPrimitiveVar(rawPtr)
// generics below are used for typedef support
// these classes are not supposed to be used directly, instead the typealiases are provided.
@Suppress("FINAL_UPPER_BOUND")
public class BooleanVarOf<T : Boolean>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(1)
}
@Suppress("FINAL_UPPER_BOUND")
public class ByteVarOf<T : Byte>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(1)
}
@Suppress("FINAL_UPPER_BOUND")
public class ShortVarOf<T : Short>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(2)
}
@Suppress("FINAL_UPPER_BOUND")
public class IntVarOf<T : Int>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(4)
}
@Suppress("FINAL_UPPER_BOUND")
public class LongVarOf<T : Long>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(8)
}
@Suppress("FINAL_UPPER_BOUND")
public class UByteVarOf<T : UByte>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(1)
}
@Suppress("FINAL_UPPER_BOUND")
public class UShortVarOf<T : UShort>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(2)
}
@Suppress("FINAL_UPPER_BOUND")
public class UIntVarOf<T : UInt>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(4)
}
@Suppress("FINAL_UPPER_BOUND")
public class ULongVarOf<T : ULong>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(8)
}
@Suppress("FINAL_UPPER_BOUND")
public class FloatVarOf<T : Float>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(4)
}
@Suppress("FINAL_UPPER_BOUND")
public class DoubleVarOf<T : Double>(rawPtr: NativePtr) : CPrimitiveVar(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(8)
}
public typealias BooleanVar = BooleanVarOf<Boolean>
public typealias ByteVar = ByteVarOf<Byte>
public typealias ShortVar = ShortVarOf<Short>
public typealias IntVar = IntVarOf<Int>
public typealias LongVar = LongVarOf<Long>
public typealias UByteVar = UByteVarOf<UByte>
public typealias UShortVar = UShortVarOf<UShort>
public typealias UIntVar = UIntVarOf<UInt>
public typealias ULongVar = ULongVarOf<ULong>
public typealias FloatVar = FloatVarOf<Float>
public typealias DoubleVar = DoubleVarOf<Double>
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Boolean> BooleanVarOf<T>.value: T
get() {
val byte = nativeMemUtils.getByte(this)
return byte.toBoolean() as T
}
set(value) = nativeMemUtils.putByte(this, value.toByte())
@Suppress("NOTHING_TO_INLINE")
public inline fun Boolean.toByte(): Byte = if (this) 1 else 0
@Suppress("NOTHING_TO_INLINE")
public inline fun Byte.toBoolean() = (this.toInt() != 0)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Byte> ByteVarOf<T>.value: T
get() = nativeMemUtils.getByte(this) as T
set(value) = nativeMemUtils.putByte(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Short> ShortVarOf<T>.value: T
get() = nativeMemUtils.getShort(this) as T
set(value) = nativeMemUtils.putShort(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Int> IntVarOf<T>.value: T
get() = nativeMemUtils.getInt(this) as T
set(value) = nativeMemUtils.putInt(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Long> LongVarOf<T>.value: T
get() = nativeMemUtils.getLong(this) as T
set(value) = nativeMemUtils.putLong(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : UByte> UByteVarOf<T>.value: T
get() = nativeMemUtils.getByte(this).toUByte() as T
set(value) = nativeMemUtils.putByte(this, value.toByte())
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : UShort> UShortVarOf<T>.value: T
get() = nativeMemUtils.getShort(this).toUShort() as T
set(value) = nativeMemUtils.putShort(this, value.toShort())
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : UInt> UIntVarOf<T>.value: T
get() = nativeMemUtils.getInt(this).toUInt() as T
set(value) = nativeMemUtils.putInt(this, value.toInt())
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : ULong> ULongVarOf<T>.value: T
get() = nativeMemUtils.getLong(this).toULong() as T
set(value) = nativeMemUtils.putLong(this, value.toLong())
// TODO: ensure native floats have the appropriate binary representation
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Float> FloatVarOf<T>.value: T
get() = nativeMemUtils.getFloat(this) as T
set(value) = nativeMemUtils.putFloat(this, value)
@Suppress("FINAL_UPPER_BOUND", "UNCHECKED_CAST")
public var <T : Double> DoubleVarOf<T>.value: T
get() = nativeMemUtils.getDouble(this) as T
set(value) = nativeMemUtils.putDouble(this, value)
public class CPointerVarOf<T : CPointer<*>>(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
}
/**
* The C data variable containing the pointer to `T`.
*/
public typealias CPointerVar<T> = CPointerVarOf<CPointer<T>>
/**
* The value of this variable.
*/
@Suppress("UNCHECKED_CAST")
public inline var <P : CPointer<*>> CPointerVarOf<P>.value: P?
get() = interpretCPointer<CPointed>(nativeMemUtils.getNativePtr(this)) as P?
set(value) = nativeMemUtils.putNativePtr(this, value.rawValue)
/**
* The code or data pointed by the value of this variable.
*
* @param T must not be abstract
*/
public inline var <reified T : CPointed, reified P : CPointer<T>> CPointerVarOf<P>.pointed: T?
get() = this.value?.pointed
set(value) {
this.value = value?.ptr as P?
}
public inline operator fun <reified T : CVariable> CPointer<T>.get(index: Long): T {
val offset = if (index == 0L) {
0L // optimization for JVM impl which uses reflection for now.
} else {
index * sizeOf<T>()
}
return interpretPointed(this.rawValue + offset)
}
public inline operator fun <reified T : CVariable> CPointer<T>.get(index: Int): T = this.get(index.toLong())
@Suppress("NOTHING_TO_INLINE")
@JvmName("plus\$CPointer")
public inline operator fun <T : CPointerVarOf<*>> CPointer<T>?.plus(index: Long): CPointer<T>? =
interpretCPointer(this.rawValue + index * pointerSize)
@Suppress("NOTHING_TO_INLINE")
@JvmName("plus\$CPointer")
public inline operator fun <T : CPointerVarOf<*>> CPointer<T>?.plus(index: Int): CPointer<T>? =
this + index.toLong()
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : CPointer<*>> CPointer<CPointerVarOf<T>>.get(index: Int): T? =
(this + index)!!.pointed.value
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : CPointer<*>> CPointer<CPointerVarOf<T>>.set(index: Int, value: T?) {
(this + index)!!.pointed.value = value
}
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : CPointer<*>> CPointer<CPointerVarOf<T>>.get(index: Long): T? =
(this + index)!!.pointed.value
@Suppress("NOTHING_TO_INLINE")
public inline operator fun <T : CPointer<*>> CPointer<CPointerVarOf<T>>.set(index: Long, value: T?) {
(this + index)!!.pointed.value = value
}
public typealias CArrayPointer<T> = CPointer<T>
public typealias CArrayPointerVar<T> = CPointerVar<T>
/**
* The C function.
*/
public class CFunction<T : Function<*>>(rawPtr: NativePtr) : CPointed(rawPtr)
@@ -0,0 +1,650 @@
/*
* 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 kotlinx.cinterop
public interface NativePlacement {
public fun alloc(size: Long, align: Int): NativePointed
public fun alloc(size: Int, align: Int): NativePointed = alloc(size.toLong(), align)
}
public interface NativeFreeablePlacement : NativePlacement {
public fun free(mem: NativePtr)
}
public fun NativeFreeablePlacement.free(pointer: CPointer<*>) = this.free(pointer.rawValue)
public fun NativeFreeablePlacement.free(pointed: NativePointed) = this.free(pointed.rawPtr)
public object nativeHeap : NativeFreeablePlacement {
override fun alloc(size: Long, align: Int) = nativeMemUtils.alloc(size, align)
override fun free(mem: NativePtr) = nativeMemUtils.free(mem)
}
private typealias Deferred = () -> Unit
public open class DeferScope {
@PublishedApi
internal var topDeferred: Deferred? = null
internal fun executeAllDeferred() {
topDeferred?.let {
it.invoke()
topDeferred = null
}
}
inline fun defer(crossinline block: () -> Unit) {
val currentTop = topDeferred
topDeferred = {
try {
block()
} finally {
// TODO: it is possible to implement chaining without recursion,
// but it would require using an anonymous object here
// which is not yet supported in Kotlin Native inliner.
currentTop?.invoke()
}
}
}
}
public abstract class AutofreeScope : DeferScope(), NativePlacement {
abstract override fun alloc(size: Long, align: Int): NativePointed
}
public open class ArenaBase(private val parent: NativeFreeablePlacement = nativeHeap) : AutofreeScope() {
private var lastChunk: NativePointed? = null
final override fun alloc(size: Long, align: Int): NativePointed {
// Reserve space for a pointer:
val gapForPointer = maxOf(pointerSize, align)
val chunk = parent.alloc(size = gapForPointer + size, align = gapForPointer)
nativeMemUtils.putNativePtr(chunk, lastChunk.rawPtr)
lastChunk = chunk
return interpretOpaquePointed(chunk.rawPtr + gapForPointer.toLong())
}
@PublishedApi
internal fun clearImpl() {
this.executeAllDeferred()
var chunk = lastChunk
while (chunk != null) {
val nextChunk = nativeMemUtils.getNativePtr(chunk)
parent.free(chunk)
chunk = interpretNullableOpaquePointed(nextChunk)
}
}
}
public class Arena(parent: NativeFreeablePlacement = nativeHeap) : ArenaBase(parent) {
fun clear() = this.clearImpl()
}
/**
* Allocates variable of given type.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.alloc(): T =
@Suppress("DEPRECATION")
alloc(typeOf<T>()).reinterpret()
@PublishedApi
@Suppress("DEPRECATION")
internal fun NativePlacement.alloc(type: CVariable.Type): NativePointed =
alloc(type.size, type.align)
/**
* Allocates variable of given type and initializes it applying given block.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.alloc(initialize: T.() -> Unit): T =
alloc<T>().also { it.initialize() }
/**
* Allocates C array of given elements type and length.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.allocArray(length: Long): CArrayPointer<T> =
alloc(sizeOf<T>() * length, alignOf<T>()).reinterpret<T>().ptr
/**
* Allocates C array of given elements type and length.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.allocArray(length: Int): CArrayPointer<T> =
allocArray(length.toLong())
/**
* Allocates C array of given elements type and length, and initializes its elements applying given block.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.allocArray(length: Long,
initializer: T.(index: Long)->Unit): CArrayPointer<T> {
val res = allocArray<T>(length)
(0 .. length - 1).forEach { index ->
res[index].initializer(index)
}
return res
}
/**
* Allocates C array of given elements type and length, and initializes its elements applying given block.
*
* @param T must not be abstract
*/
public inline fun <reified T : CVariable> NativePlacement.allocArray(
length: Int, initializer: T.(index: Int)->Unit): CArrayPointer<T> = allocArray(length.toLong()) { index ->
this.initializer(index.toInt())
}
/**
* Allocates C array of pointers to given elements.
*/
public fun <T : CPointed> NativePlacement.allocArrayOfPointersTo(elements: List<T?>): CArrayPointer<CPointerVar<T>> {
val res = allocArray<CPointerVar<T>>(elements.size)
elements.forEachIndexed { index, value ->
res[index] = value?.ptr
}
return res
}
/**
* Allocates C array of pointers to given elements.
*/
public fun <T : CPointed> NativePlacement.allocArrayOfPointersTo(vararg elements: T?) =
allocArrayOfPointersTo(listOf(*elements))
/**
* Allocates C array of given values.
*/
public inline fun <reified T : CPointer<*>>
NativePlacement.allocArrayOf(vararg elements: T?): CArrayPointer<CPointerVarOf<T>> {
return allocArrayOf(listOf(*elements))
}
/**
* Allocates C array of given values.
*/
public inline fun <reified T : CPointer<*>>
NativePlacement.allocArrayOf(elements: List<T?>): CArrayPointer<CPointerVarOf<T>> {
val res = allocArray<CPointerVarOf<T>>(elements.size)
var index = 0
while (index < elements.size) {
res[index] = elements[index]
++index
}
return res
}
public fun NativePlacement.allocArrayOf(elements: ByteArray): CArrayPointer<ByteVar> {
val result = allocArray<ByteVar>(elements.size)
nativeMemUtils.putByteArray(elements, result.pointed, elements.size)
return result
}
public fun NativePlacement.allocArrayOf(vararg elements: Float): CArrayPointer<FloatVar> {
val res = allocArray<FloatVar>(elements.size)
var index = 0
while (index < elements.size) {
res[index] = elements[index]
++index
}
return res
}
public fun <T : CPointed> NativePlacement.allocPointerTo() = alloc<CPointerVar<T>>()
@PublishedApi
internal class ZeroValue<T: CVariable>(private val sizeBytes: Int, private val alignBytes: Int): CValue<T>() {
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<T>): CPointer<T> {
nativeMemUtils.zeroMemory(interpretPointed(placement.rawValue), sizeBytes)
return placement
}
override val size get() = sizeBytes
override val align get() = alignBytes
}
@Suppress("NOTHING_TO_INLINE")
public inline fun <T : CVariable> zeroValue(size: Int, align: Int): CValue<T> = ZeroValue(size, align)
public inline fun <reified T : CVariable> zeroValue(): CValue<T> = zeroValue<T>(sizeOf<T>().toInt(), alignOf<T>())
public inline fun <reified T : CVariable> cValue(): CValue<T> = zeroValue<T>()
public fun <T : CVariable> CPointed.readValues(size: Int, align: Int): CValues<T> {
val bytes = ByteArray(size)
nativeMemUtils.getByteArray(this, bytes, size)
return object : CValue<T>() {
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<T>): CPointer<T> {
nativeMemUtils.putByteArray(bytes, interpretPointed(placement.rawValue), bytes.size)
return placement
}
override val size get() = size
override val align get() = align
}
}
public inline fun <reified T : CVariable> T.readValues(count: Int): CValues<T> =
this.readValues<T>(size = count * sizeOf<T>().toInt(), align = alignOf<T>())
public fun <T : CVariable> CPointed.readValue(size: Long, align: Int): CValue<T> {
val bytes = ByteArray(size.toInt())
nativeMemUtils.getByteArray(this, bytes, size.toInt())
return object : CValue<T>() {
override fun place(placement: CPointer<T>): CPointer<T> {
nativeMemUtils.putByteArray(bytes, interpretPointed(placement.rawValue), bytes.size)
return placement
}
// Optimization to avoid unneeded virtual calls in base class implementation.
public override fun getPointer(scope: AutofreeScope): CPointer<T> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override val size get() = size.toInt()
override val align get() = align
}
}
@Suppress("DEPRECATION")
@PublishedApi internal fun <T : CVariable> CPointed.readValue(type: CVariable.Type): CValue<T> =
readValue(type.size, type.align)
// Note: can't be declared as property due to possible clash with a struct field.
// TODO: find better name.
@Suppress("DEPRECATION")
public inline fun <reified T : CStructVar> T.readValue(): CValue<T> = this.readValue(typeOf<T>())
public fun <T: CVariable> CValue<T>.write(location: NativePtr) {
this.place(interpretCPointer(location)!!)
}
// TODO: optimize
public fun <T : CVariable> CValues<T>.getBytes(): ByteArray = memScoped {
val result = ByteArray(size)
nativeMemUtils.getByteArray(
source = this@getBytes.placeTo(memScope).reinterpret<ByteVar>().pointed,
dest = result,
length = result.size
)
result
}
/**
* Calls the [block] with temporary copy of this value as receiver.
*/
public inline fun <reified T : CStructVar, R> CValue<T>.useContents(block: T.() -> R): R = memScoped {
this@useContents.placeTo(memScope).pointed.block()
}
public inline fun <reified T : CStructVar> CValue<T>.copy(modify: T.() -> Unit): CValue<T> = useContents {
this.modify()
this.readValue()
}
public inline fun <reified T : CStructVar> cValue(initialize: T.() -> Unit): CValue<T> =
zeroValue<T>().copy(modify = initialize)
public inline fun <reified T : CVariable> createValues(count: Int, initializer: T.(index: Int) -> Unit) = memScoped {
val array = allocArray<T>(count, initializer)
array[0].readValues(count)
}
// TODO: optimize other [cValuesOf] methods:
/**
* Returns sequence of immutable values [CValues] to pass them to C code.
*/
fun cValuesOf(vararg elements: Byte): CValues<ByteVar> = object : CValues<ByteVar>() {
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<ByteVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<ByteVar>): CPointer<ByteVar> {
nativeMemUtils.putByteArray(elements, interpretPointed(placement.rawValue), elements.size)
return placement
}
override val size get() = 1 * elements.size
override val align get() = 1
}
public fun cValuesOf(vararg elements: Short): CValues<ShortVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun cValuesOf(vararg elements: Int): CValues<IntVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun cValuesOf(vararg elements: Long): CValues<LongVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun cValuesOf(vararg elements: Float): CValues<FloatVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun cValuesOf(vararg elements: Double): CValues<DoubleVar> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun <T : CPointed> cValuesOf(vararg elements: CPointer<T>?): CValues<CPointerVar<T>> =
createValues(elements.size) { index -> this.value = elements[index] }
public fun ByteArray.toCValues() = cValuesOf(*this)
public fun ShortArray.toCValues() = cValuesOf(*this)
public fun IntArray.toCValues() = cValuesOf(*this)
public fun LongArray.toCValues() = cValuesOf(*this)
public fun FloatArray.toCValues() = cValuesOf(*this)
public fun DoubleArray.toCValues() = cValuesOf(*this)
public fun <T : CPointed> Array<CPointer<T>?>.toCValues() = cValuesOf(*this)
public fun <T : CPointed> List<CPointer<T>?>.toCValues() = this.toTypedArray().toCValues()
private class CString(val bytes: ByteArray): CValues<ByteVar>() {
override val size get() = bytes.size + 1
override val align get() = 1
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<ByteVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<ByteVar>): CPointer<ByteVar> {
nativeMemUtils.putByteArray(bytes, placement.pointed, bytes.size)
placement[bytes.size] = 0.toByte()
return placement
}
}
private object EmptyCString: CValues<ByteVar>() {
override val size get() = 1
override val align get() = 1
private val placement =
interpretCPointer<ByteVar>(nativeMemUtils.allocRaw(1, 1))!!.also {
it[0] = 0.toByte()
}
override fun getPointer(scope: AutofreeScope): CPointer<ByteVar> {
return placement
}
override fun place(placement: CPointer<ByteVar>): CPointer<ByteVar> {
placement[0] = 0.toByte()
return placement
}
}
/**
* @return the value of zero-terminated UTF-8-encoded C string constructed from given [kotlin.String].
*/
public val String.cstr: CValues<ByteVar>
get() = if (isEmpty()) EmptyCString else CString(encodeToUtf8(this))
/**
* @return the value of zero-terminated UTF-8-encoded C string constructed from given [kotlin.String].
*/
public val String.utf8: CValues<ByteVar>
get() = CString(encodeToUtf8(this))
/**
* Convert this list of Kotlin strings to C array of C strings,
* allocating memory for the array and C strings with given [AutofreeScope].
*/
public fun List<String>.toCStringArray(autofreeScope: AutofreeScope): CPointer<CPointerVar<ByteVar>> =
autofreeScope.allocArrayOf(this.map { it.cstr.getPointer(autofreeScope) })
/**
* Convert this array of Kotlin strings to C array of C strings,
* allocating memory for the array and C strings with given [AutofreeScope].
*/
public fun Array<String>.toCStringArray(autofreeScope: AutofreeScope): CPointer<CPointerVar<ByteVar>> =
autofreeScope.allocArrayOf(this.map { it.cstr.getPointer(autofreeScope) })
private class U16CString(val chars: CharArray): CValues<UShortVar>() {
override val size get() = 2 * (chars.size + 1)
override val align get() = 2
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<UShortVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<UShortVar>): CPointer<UShortVar> {
nativeMemUtils.putCharArray(chars, placement.pointed, chars.size)
// TODO: fix, after KT-29627 is fixed.
nativeMemUtils.putShort((placement + chars.size)!!.pointed, 0)
return placement
}
}
/**
* @return the value of zero-terminated UTF-16-encoded C string constructed from given [kotlin.String].
*/
public val String.wcstr: CValues<UShortVar>
get() = U16CString(this.toCharArray())
/**
* @return the value of zero-terminated UTF-16-encoded C string constructed from given [kotlin.String].
*/
public val String.utf16: CValues<UShortVar>
get() = U16CString(this.toCharArray())
private class U32CString(val chars: CharArray): CValues<IntVar>() {
override val size get() = 4 * (chars.size + 1)
override val align get() = 4
// Optimization to avoid unneeded virtual calls in base class implementation.
override fun getPointer(scope: AutofreeScope): CPointer<IntVar> {
return place(interpretCPointer(scope.alloc(size, align).rawPtr)!!)
}
override fun place(placement: CPointer<IntVar>): CPointer<IntVar> {
var indexIn = 0
var indexOut = 0
while (indexIn < chars.size) {
var value = chars[indexIn++].toInt()
if (value >= 0xd800 && value < 0xdc00) {
// Surrogate pair.
if (indexIn >= chars.size - 1) throw IllegalArgumentException()
indexIn++
val next = chars[indexIn].toInt()
if (next < 0xdc00 || next >= 0xe000) throw IllegalArgumentException()
value = 0x10000 + ((value and 0x3ff) shl 10) + (next and 0x3ff)
}
nativeMemUtils.putInt((placement + indexOut)!!.pointed, value)
indexOut++
}
nativeMemUtils.putInt((placement + indexOut)!!.pointed, 0)
return placement
}
}
/**
* @return the value of zero-terminated UTF-32-encoded C string constructed from given [kotlin.String].
*/
public val String.utf32: CValues<IntVar>
get() = U32CString(this.toCharArray())
// TODO: optimize
/**
* @return the [kotlin.String] decoded from given zero-terminated UTF-8-encoded C string.
*/
public fun CPointer<ByteVar>.toKStringFromUtf8(): String = this.toKStringFromUtf8Impl()
/**
* @return the [kotlin.String] decoded from given zero-terminated UTF-8-encoded C string.
*/
public fun CPointer<ByteVar>.toKString(): String = this.toKStringFromUtf8()
/**
* @return the [kotlin.String] decoded from given zero-terminated UTF-16-encoded C string.
*/
public fun CPointer<ShortVar>.toKStringFromUtf16(): String {
val nativeBytes = this
var length = 0
while (nativeBytes[length] != 0.toShort()) {
++length
}
val chars = CharArray(length)
var index = 0
while (index < length) {
chars[index] = nativeBytes[index].toChar()
++index
}
return chars.concatToString()
}
/**
* @return the [kotlin.String] decoded from given zero-terminated UTF-32-encoded C string.
*/
public fun CPointer<IntVar>.toKStringFromUtf32(): String {
val nativeBytes = this
var fromIndex = 0
var toIndex = 0
while (true) {
val value = nativeBytes[fromIndex++]
if (value == 0) break
toIndex++
if (value >= 0x10000 && value <= 0x10ffff) {
toIndex++
}
}
val length = toIndex
val chars = CharArray(length)
fromIndex = 0
toIndex = 0
while (toIndex < length) {
var value = nativeBytes[fromIndex++]
if (value >= 0x10000 && value <= 0x10ffff) {
chars[toIndex++] = (((value - 0x10000) shr 10) or 0xd800).toChar()
chars[toIndex++] = (((value - 0x10000) and 0x3ff) or 0xdc00).toChar()
} else {
chars[toIndex++] = value.toChar()
}
}
return chars.concatToString()
}
/**
* Decodes a string from the bytes in UTF-8 encoding in this array.
* Bytes following the first occurrence of `0` byte, if it occurs, are not decoded.
*
* Malformed byte sequences are replaced by the replacement char `\uFFFD`.
*/
@OptIn(ExperimentalStdlibApi::class)
@SinceKotlin("1.3")
public fun ByteArray.toKString() : String {
val realEndIndex = realEndIndex(this, 0, this.size)
return decodeToString(0, realEndIndex)
}
/**
* Decodes a string from the bytes in UTF-8 encoding in this array or its subrange.
* Bytes following the first occurrence of `0` byte, if it occurs, are not decoded.
*
* @param startIndex the beginning (inclusive) of the subrange to decode, 0 by default.
* @param endIndex the end (exclusive) of the subrange to decode, size of this array by default.
* @param throwOnInvalidSequence specifies whether to throw an exception on malformed byte sequence or replace it by the replacement char `\uFFFD`.
*
* @throws IndexOutOfBoundsException if [startIndex] is less than zero or [endIndex] is greater than the size of this array.
* @throws IllegalArgumentException if [startIndex] is greater than [endIndex].
* @throws CharacterCodingException if the byte array contains malformed UTF-8 byte sequence and [throwOnInvalidSequence] is true.
*/
@OptIn(ExperimentalStdlibApi::class)
@SinceKotlin("1.3")
public fun ByteArray.toKString(
startIndex: Int = 0,
endIndex: Int = this.size,
throwOnInvalidSequence: Boolean = false
) : String {
checkBoundsIndexes(startIndex, endIndex, this.size)
val realEndIndex = realEndIndex(this, startIndex, endIndex)
return decodeToString(startIndex, realEndIndex, throwOnInvalidSequence)
}
private fun realEndIndex(byteArray: ByteArray, startIndex: Int, endIndex: Int): Int {
var index = startIndex
while (index < endIndex && byteArray[index] != 0.toByte()) {
index++
}
return index
}
private fun checkBoundsIndexes(startIndex: Int, endIndex: Int, size: Int) {
if (startIndex < 0 || endIndex > size) {
throw IndexOutOfBoundsException("startIndex: $startIndex, endIndex: $endIndex, size: $size")
}
if (startIndex > endIndex) {
throw IllegalArgumentException("startIndex: $startIndex > endIndex: $endIndex")
}
}
public class MemScope : ArenaBase() {
val memScope: MemScope
get() = this
val <T: CVariable> CValues<T>.ptr: CPointer<T>
get() = this@ptr.getPointer(this@MemScope)
}
// TODO: consider renaming `memScoped` because it now supports `defer`.
/**
* Runs given [block] providing allocation of memory
* which will be automatically disposed at the end of this scope.
*/
public inline fun <R> memScoped(block: MemScope.()->R): R {
val memScope = MemScope()
try {
return memScope.block()
} finally {
memScope.clearImpl()
}
}
public fun COpaquePointer.readBytes(count: Int): ByteArray {
val result = ByteArray(count)
nativeMemUtils.getByteArray(this.reinterpret<ByteVar>().pointed, result, count)
return result
}
@@ -0,0 +1,22 @@
/*
* 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.
*/
/**
* This package contains API and runtime support for calling C code from Kotlin (aka Kotlin C interop).
*
* TODO: decide about package location.
*/
package kotlinx.cinterop;
@@ -0,0 +1,21 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlinx.cinterop
import kotlin.native.internal.ExportForCppRuntime
public class ForeignException internal constructor(val nativeException: Any?): Exception() {
override val message: String = nativeException?.let {
kotlin_ObjCExport_ExceptionDetails(nativeException)
}?: ""
// Current implementation expects NSException type only, which is ensured by CodeGenerator.
@SymbolName("Kotlin_ObjCExport_ExceptionDetails")
private external fun kotlin_ObjCExport_ExceptionDetails(nativeException: Any): String?
}
@ExportForCppRuntime
internal fun CreateForeignException(payload: NativePtr): Throwable
= ForeignException(interpretObjCPointerOrNull<Any?>(payload))
@@ -0,0 +1,67 @@
/*
* 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 kotlinx.cinterop
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
import kotlin.native.internal.ExportForCompiler
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <R> CPointer<CFunction<() -> R>>.invoke(): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, R> CPointer<CFunction<(P1) -> R>>.invoke(p1: P1): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, R> CPointer<CFunction<(P1, P2) -> R>>.invoke(p1: P1, p2: P2): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, R> CPointer<CFunction<(P1, P2, P3) -> R>>.invoke(p1: P1, p2: P2, p3: P3): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, R> CPointer<CFunction<(P1, P2, P3, P4) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, R> CPointer<CFunction<(P1, P2, P3, P4, P5) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21): R
@TypedIntrinsic(IntrinsicType.INTEROP_FUNPTR_INVOKE) external operator fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, R> CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R>>.invoke(p1: P1, p2: P2, p3: P3, p4: P4, p5: P5, p6: P6, p7: P7, p8: P8, p9: P9, p10: P10, p11: P11, p12: P12, p13: P13, p14: P14, p15: P15, p16: P16, p17: P17, p18: P18, p19: P19, p20: P20, p21: P21, p22: P22): R
@@ -0,0 +1,170 @@
/*
* 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 kotlinx.cinterop
import kotlin.native.*
import kotlin.native.internal.Intrinsic
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
@PublishedApi
internal inline val pointerSize: Int
get() = getPointerSize()
@PublishedApi
@TypedIntrinsic(IntrinsicType.INTEROP_GET_POINTER_SIZE)
internal external fun getPointerSize(): Int
// TODO: do not use singleton because it leads to init-check on any access.
@PublishedApi
internal object nativeMemUtils {
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getByte(mem: NativePointed): Byte
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putByte(mem: NativePointed, value: Byte)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getShort(mem: NativePointed): Short
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putShort(mem: NativePointed, value: Short)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getInt(mem: NativePointed): Int
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putInt(mem: NativePointed, value: Int)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getLong(mem: NativePointed): Long
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putLong(mem: NativePointed, value: Long)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getFloat(mem: NativePointed): Float
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putFloat(mem: NativePointed, value: Float)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getDouble(mem: NativePointed): Double
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putDouble(mem: NativePointed, value: Double)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getNativePtr(mem: NativePointed): NativePtr
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putNativePtr(mem: NativePointed, value: NativePtr)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_PRIMITIVE) external fun getVector(mem: NativePointed): Vector128
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_PRIMITIVE) external fun putVector(mem: NativePointed, value: Vector128)
// TODO: optimize
fun getByteArray(source: NativePointed, dest: ByteArray, length: Int) {
val sourceArray = source.reinterpret<ByteVar>().ptr
var index = 0
while (index < length) {
dest[index] = sourceArray[index]
++index
}
}
// TODO: optimize
fun putByteArray(source: ByteArray, dest: NativePointed, length: Int) {
val destArray = dest.reinterpret<ByteVar>().ptr
var index = 0
while (index < length) {
destArray[index] = source[index]
++index
}
}
// TODO: optimize
fun getCharArray(source: NativePointed, dest: CharArray, length: Int) {
val sourceArray = source.reinterpret<ShortVar>().ptr
var index = 0
while (index < length) {
dest[index] = sourceArray[index].toChar()
++index
}
}
// TODO: optimize
fun putCharArray(source: CharArray, dest: NativePointed, length: Int) {
val destArray = dest.reinterpret<ShortVar>().ptr
var index = 0
while (index < length) {
destArray[index] = source[index].toShort()
++index
}
}
// TODO: optimize
fun zeroMemory(dest: NativePointed, length: Int): Unit {
val destArray = dest.reinterpret<ByteVar>().ptr
var index = 0
while (index < length) {
destArray[index] = 0
++index
}
}
// TODO: optimize
fun copyMemory(dest: NativePointed, length: Int, src: NativePointed): Unit {
val destArray = dest.reinterpret<ByteVar>().ptr
val srcArray = src.reinterpret<ByteVar>().ptr
var index = 0
while (index < length) {
destArray[index] = srcArray[index]
++index
}
}
fun alloc(size: Long, align: Int): NativePointed {
return interpretOpaquePointed(allocRaw(size, align))
}
fun free(mem: NativePtr) {
freeRaw(mem)
}
internal fun allocRaw(size: Long, align: Int): NativePtr {
val ptr = malloc(size, align)
if (ptr == nativeNullPtr) {
throw OutOfMemoryError("unable to allocate native memory")
}
return ptr
}
internal fun freeRaw(mem: NativePtr) {
cfree(mem)
}
}
public fun CPointer<UShortVar>.toKStringFromUtf16(): String {
val nativeBytes = this
var length = 0
while (nativeBytes[length] != 0.toUShort()) {
++length
}
val chars = kotlin.CharArray(length)
var index = 0
while (index < length) {
chars[index] = nativeBytes[index].toShort().toChar()
++index
}
return chars.concatToString()
}
public fun CPointer<ShortVar>.toKString(): String = this.toKStringFromUtf16()
public fun CPointer<UShortVar>.toKString(): String = this.toKStringFromUtf16()
@SymbolName("Kotlin_interop_malloc")
private external fun malloc(size: Long, align: Int): NativePtr
@SymbolName("Kotlin_interop_free")
private external fun cfree(ptr: NativePtr)
@TypedIntrinsic(IntrinsicType.INTEROP_READ_BITS)
external fun readBits(ptr: NativePtr, offset: Long, size: Int, signed: Boolean): Long
@TypedIntrinsic(IntrinsicType.INTEROP_WRITE_BITS)
external fun writeBits(ptr: NativePtr, offset: Long, size: Int, value: Long)
@@ -0,0 +1,28 @@
/*
* 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 kotlinx.cinterop
import kotlin.native.*
@SymbolName("Kotlin_Interop_createStablePointer")
internal external fun createStablePointer(any: Any): COpaquePointer
@SymbolName("Kotlin_Interop_disposeStablePointer")
internal external fun disposeStablePointer(pointer: COpaquePointer)
@PublishedApi
@SymbolName("Kotlin_Interop_derefStablePointer")
internal external fun derefStablePointer(pointer: COpaquePointer): Any
@@ -0,0 +1,123 @@
/*
* 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 kotlinx.cinterop
import kotlin.native.internal.getNativeNullPtr
import kotlin.native.internal.reinterpret
import kotlin.native.internal.Intrinsic
import kotlin.native.internal.VolatileLambda
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
typealias NativePtr = kotlin.native.internal.NativePtr
internal typealias NonNullNativePtr = kotlin.native.internal.NonNullNativePtr
@Suppress("NOTHING_TO_INLINE")
internal inline fun NativePtr.toNonNull() = this.reinterpret<NativePtr, NonNullNativePtr>()
inline val nativeNullPtr: NativePtr
get() = getNativeNullPtr()
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
fun <T : CVariable> typeOf(): CVariable.Type = throw Error("typeOf() is called with erased argument")
/**
* Performs type cast of the native pointer to given interop type, including null values.
*
* @param T must not be abstract
*/
@TypedIntrinsic(IntrinsicType.IDENTITY)
external fun <T : NativePointed> interpretNullablePointed(ptr: NativePtr): T?
/**
* Performs type cast of the [CPointer] from the given raw pointer.
*/
@TypedIntrinsic(IntrinsicType.IDENTITY)
external fun <T : CPointed> interpretCPointer(rawValue: NativePtr): CPointer<T>?
@TypedIntrinsic(IntrinsicType.IDENTITY)
external fun NativePointed.getRawPointer(): NativePtr
@TypedIntrinsic(IntrinsicType.IDENTITY)
external fun CPointer<*>.getRawValue(): NativePtr
internal fun CPointer<*>.cPointerToString() = "CPointer(raw=$rawValue)"
public class Vector128VarOf<T : Vector128>(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : Type(size = 16, align = 16)
}
public typealias Vector128Var = Vector128VarOf<Vector128>
public var <T : Vector128> Vector128VarOf<T>.value: T
get() = nativeMemUtils.getVector(this) as T
set(value) = nativeMemUtils.putVector(this, value)
/**
* Returns a pointer to C function which calls given Kotlin *static* function.
*
* @param function must be *static*, i.e. an (unbound) reference to a Kotlin function or
* a closure which doesn't capture any variable
*/
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <R> staticCFunction(@VolatileLambda function: () -> R): CPointer<CFunction<() -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, R> staticCFunction(@VolatileLambda function: (P1) -> R): CPointer<CFunction<(P1) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, R> staticCFunction(@VolatileLambda function: (P1, P2) -> R): CPointer<CFunction<(P1, P2) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, R> staticCFunction(@VolatileLambda function: (P1, P2, P3) -> R): CPointer<CFunction<(P1, P2, P3) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4) -> R): CPointer<CFunction<(P1, P2, P3, P4) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21) -> R>>
@TypedIntrinsic(IntrinsicType.INTEROP_STATIC_C_FUNCTION) external fun <P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22, R> staticCFunction(@VolatileLambda function: (P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R): CPointer<CFunction<(P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15, P16, P17, P18, P19, P20, P21, P22) -> R>>
@@ -0,0 +1,70 @@
/*
* 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 kotlinx.cinterop
import kotlin.native.internal.Intrinsic
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
internal fun encodeToUtf8(str: String): ByteArray = str.encodeToByteArray()
@SymbolName("Kotlin_CString_toKStringFromUtf8Impl")
internal external fun CPointer<ByteVar>.toKStringFromUtf8Impl(): String
@TypedIntrinsic(IntrinsicType.INTEROP_BITS_TO_FLOAT)
external fun bitsToFloat(bits: Int): Float
@TypedIntrinsic(IntrinsicType.INTEROP_BITS_TO_DOUBLE)
external fun bitsToDouble(bits: Long): Double
// TODO: deprecate.
@TypedIntrinsic(IntrinsicType.INTEROP_SIGN_EXTEND)
external inline fun <reified R : Number> Number.signExtend(): R
// TODO: deprecate.
@TypedIntrinsic(IntrinsicType.INTEROP_NARROW)
external inline fun <reified R : Number> Number.narrow(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> Byte.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> Short.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> Int.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> Long.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> UByte.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> UShort.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> UInt.convert(): R
@TypedIntrinsic(IntrinsicType.INTEROP_CONVERT) external inline fun <reified R : Any> ULong.convert(): R
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER, AnnotationTarget.FILE)
@Retention(AnnotationRetention.SOURCE)
internal annotation class JvmName(val name: String)
fun cValuesOf(vararg elements: UByte): CValues<UByteVar> =
createValues(elements.size) { index -> this.value = elements[index] }
fun cValuesOf(vararg elements: UShort): CValues<UShortVar> =
createValues(elements.size) { index -> this.value = elements[index] }
fun cValuesOf(vararg elements: UInt): CValues<UIntVar> =
createValues(elements.size) { index -> this.value = elements[index] }
fun cValuesOf(vararg elements: ULong): CValues<ULongVar> =
createValues(elements.size) { index -> this.value = elements[index] }
fun UByteArray.toCValues() = cValuesOf(*this)
fun UShortArray.toCValues() = cValuesOf(*this)
fun UIntArray.toCValues() = cValuesOf(*this)
fun ULongArray.toCValues() = cValuesOf(*this)
@@ -0,0 +1,227 @@
/*
* 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.
*/
@file:Suppress("NOTHING_TO_INLINE")
package kotlinx.cinterop
import kotlin.native.*
import kotlin.native.internal.ExportTypeInfo
import kotlin.native.internal.ExportForCppRuntime
import kotlin.native.internal.TypedIntrinsic
import kotlin.native.internal.IntrinsicType
import kotlin.native.internal.FilterExceptions
interface ObjCObject
interface ObjCClass : ObjCObject
interface ObjCClassOf<T : ObjCObject> : ObjCClass // TODO: T should be added to ObjCClass and all meta-classes instead.
typealias ObjCObjectMeta = ObjCClass
interface ObjCProtocol : ObjCObject
@ExportTypeInfo("theForeignObjCObjectTypeInfo")
@kotlin.native.internal.Frozen
internal open class ForeignObjCObject : kotlin.native.internal.ObjCObjectWrapper
abstract class ObjCObjectBase protected constructor() : ObjCObject {
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
annotation class OverrideInit
}
abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {}
fun optional(): Nothing = throw RuntimeException("Do not call me!!!")
@Deprecated(
"Add @OverrideInit to constructor to make it override Objective-C initializer",
level = DeprecationLevel.ERROR
)
@TypedIntrinsic(IntrinsicType.OBJC_INIT_BY)
external fun <T : ObjCObjectBase> T.initBy(constructorCall: T): T
@kotlin.native.internal.ExportForCompiler
private fun ObjCObjectBase.superInitCheck(superInitCallResult: ObjCObject?) {
if (superInitCallResult == null)
throw RuntimeException("Super initialization failed")
if (superInitCallResult.objcPtr() != this.objcPtr())
throw UnsupportedOperationException("Super initializer has replaced object")
}
internal fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T)
// Note: if this is called for non-frozen object on a wrong worker, the program will terminate.
@SymbolName("Kotlin_Interop_refFromObjC")
external fun <T> interpretObjCPointerOrNull(objcPtr: NativePtr): T?
@ExportForCppRuntime
inline fun <T : Any> interpretObjCPointer(objcPtr: NativePtr): T = interpretObjCPointerOrNull<T>(objcPtr)!!
@SymbolName("Kotlin_Interop_refToObjC")
external fun Any?.objcPtr(): NativePtr
@SymbolName("Kotlin_Interop_createKotlinObjectHolder")
external fun createKotlinObjectHolder(any: Any?): NativePtr
// Note: if this is called for non-frozen underlying ref on a wrong worker, the program will terminate.
inline fun <reified T : Any> unwrapKotlinObjectHolder(holder: Any?): T {
return unwrapKotlinObjectHolderImpl(holder!!.objcPtr()) as T
}
@PublishedApi
@SymbolName("Kotlin_Interop_unwrapKotlinObjectHolder")
external internal fun unwrapKotlinObjectHolderImpl(ptr: NativePtr): Any
class ObjCObjectVar<T>(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
}
class ObjCNotImplementedVar<T : Any?>(rawPtr: NativePtr) : CVariable(rawPtr) {
@Deprecated("Use sizeOf<T>() or alignOf<T>() instead.")
@Suppress("DEPRECATION")
companion object : CVariable.Type(pointerSize.toLong(), pointerSize)
}
var <T : Any?> ObjCNotImplementedVar<T>.value: T
get() = TODO()
set(_) = TODO()
typealias ObjCStringVarOf<T> = ObjCNotImplementedVar<T>
typealias ObjCBlockVar<T> = ObjCNotImplementedVar<T>
@TypedIntrinsic(IntrinsicType.OBJC_CREATE_SUPER_STRUCT)
@PublishedApi
internal external fun createObjCSuperStruct(receiver: NativePtr, superClass: NativePtr): NativePtr
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class ExternalObjCClass(val protocolGetter: String = "", val binaryName: String = "")
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCMethod(val selector: String, val encoding: String, val isStret: Boolean = false)
@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCConstructor(val initSelector: String, val designated: Boolean)
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ObjCFactory(val selector: String, val encoding: String, val isStret: Boolean = false)
@Target(AnnotationTarget.FILE)
@Retention(AnnotationRetention.BINARY)
annotation class InteropStubs()
@PublishedApi
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
internal annotation class ObjCMethodImp(val selector: String, val encoding: String)
@PublishedApi
@TypedIntrinsic(IntrinsicType.OBJC_GET_SELECTOR)
internal external fun objCGetSelector(selector: String): COpaquePointer
@kotlin.native.internal.ExportForCppRuntime("Kotlin_Interop_getObjCClass")
private fun getObjCClassByName(name: NativePtr): NativePtr {
val result = objc_lookUpClass(name)
if (result == nativeNullPtr) {
val className = interpretCPointer<ByteVar>(name)!!.toKString()
val message = """Objective-C class '$className' not found.
|Ensure that the containing framework or library was linked.""".trimMargin()
throw RuntimeException(message)
}
return result
}
@kotlin.native.internal.ExportForCompiler
private fun allocObjCObject(clazz: NativePtr): NativePtr {
val rawResult = objc_allocWithZone(clazz)
if (rawResult == nativeNullPtr) {
throw OutOfMemoryError("Unable to allocate Objective-C object")
}
// Note: `objc_allocWithZone` returns retained pointer, and thus it must be balanced by the caller.
return rawResult
}
@TypedIntrinsic(IntrinsicType.OBJC_GET_OBJC_CLASS)
@kotlin.native.internal.ExportForCompiler
private external fun <T : ObjCObject> getObjCClass(): NativePtr
@PublishedApi
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER)
internal external fun getMessenger(superClass: NativePtr): COpaquePointer?
@PublishedApi
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER_STRET)
internal external fun getMessengerStret(superClass: NativePtr): COpaquePointer?
internal class ObjCWeakReferenceImpl : kotlin.native.ref.WeakReferenceImpl() {
@SymbolName("Konan_ObjCInterop_getWeakReference")
external override fun get(): Any?
}
@SymbolName("Konan_ObjCInterop_initWeakReference")
private external fun ObjCWeakReferenceImpl.init(objcPtr: NativePtr)
@kotlin.native.internal.ExportForCppRuntime internal fun makeObjCWeakReferenceImpl(objcPtr: NativePtr): ObjCWeakReferenceImpl {
val result = ObjCWeakReferenceImpl()
result.init(objcPtr)
return result
}
// Konan runtme:
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.ERROR)
@SymbolName("Kotlin_Interop_CreateNSStringFromKString")
external fun CreateNSStringFromKString(str: String?): NativePtr
@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.ERROR)
@SymbolName("Kotlin_Interop_CreateKStringFromNSString")
external fun CreateKStringFromNSString(ptr: NativePtr): String?
@PublishedApi
@SymbolName("Kotlin_Interop_CreateObjCObjectHolder")
internal external fun createObjCObjectHolder(ptr: NativePtr): Any?
// Objective-C runtime:
@SymbolName("objc_retainAutoreleaseReturnValue")
external fun objc_retainAutoreleaseReturnValue(ptr: NativePtr): NativePtr
@SymbolName("Kotlin_objc_autoreleasePoolPush")
external fun objc_autoreleasePoolPush(): NativePtr
@SymbolName("Kotlin_objc_autoreleasePoolPop")
external fun objc_autoreleasePoolPop(ptr: NativePtr)
@SymbolName("Kotlin_objc_allocWithZone")
@FilterExceptions
private external fun objc_allocWithZone(clazz: NativePtr): NativePtr
@SymbolName("Kotlin_objc_retain")
external fun objc_retain(ptr: NativePtr): NativePtr
@SymbolName("Kotlin_objc_release")
external fun objc_release(ptr: NativePtr)
@SymbolName("Kotlin_objc_lookUpClass")
external fun objc_lookUpClass(name: NativePtr): NativePtr
@@ -0,0 +1,41 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package kotlinx.cinterop
import kotlin.native.internal.KClassImpl
import kotlin.reflect.KClass
/**
* If [objCClass] is a class generated to Objective-C header for Kotlin class,
* returns [KClass] for that original Kotlin class.
*
* Otherwise returns `null`.
*/
fun getOriginalKotlinClass(objCClass: ObjCClass): KClass<*>? {
val typeInfo = getTypeInfoForClass(objCClass.objcPtr())
if (typeInfo.isNull()) return null
return KClassImpl<Any>(typeInfo)
}
/**
* If [objCProtocol] is a protocol generated to Objective-C header for Kotlin class,
* returns [KClass] for that original Kotlin class.
*
* Otherwise returns `null`.
*/
fun getOriginalKotlinClass(objCProtocol: ObjCProtocol): KClass<*>? {
val typeInfo = getTypeInfoForProtocol(objCProtocol.objcPtr())
if (typeInfo.isNull()) return null
return KClassImpl<Any>(typeInfo)
}
@SymbolName("Kotlin_ObjCInterop_getTypeInfoForClass")
private external fun getTypeInfoForClass(ptr: NativePtr): NativePtr
@SymbolName("Kotlin_ObjCInterop_getTypeInfoForProtocol")
private external fun getTypeInfoForProtocol(ptr: NativePtr): NativePtr
@@ -0,0 +1,63 @@
/*
* 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 kotlinx.cinterop
inline fun <R> autoreleasepool(block: () -> R): R {
val pool = objc_autoreleasePoolPush()
return try {
block()
} finally {
objc_autoreleasePoolPop(pool)
}
}
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.ERROR)
fun <T : ObjCObject> ObjCObject.reinterpret() = @Suppress("DEPRECATION") this.uncheckedCast<T>()
// TODO: null checks
var <T> ObjCObjectVar<T>.value: T
@Suppress("DEPRECATION") get() =
interpretObjCPointerOrNull<T>(nativeMemUtils.getNativePtr(this)).uncheckedCast<T>()
set(value) = nativeMemUtils.putNativePtr(this, value.objcPtr())
/**
* Makes Kotlin method in Objective-C class accessible through Objective-C dispatch
* to be used as action sent by control in UIKit or AppKit.
*/
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class ObjCAction
/**
* Makes Kotlin property in Objective-C class settable through Objective-C dispatch
* to be used as IB outlet.
*/
@Target(AnnotationTarget.PROPERTY)
@Retention(AnnotationRetention.SOURCE)
annotation class ObjCOutlet
/**
* Makes Kotlin subclass of Objective-C class visible for runtime lookup
* after Kotlin `main` function gets invoked.
*
* Note: runtime lookup can be forced even when the class is referenced statically from
* Objective-C source code by adding `__attribute__((objc_runtime_visible))` to its `@interface`.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.SOURCE)
annotation class ExportObjCClass(val name: String = "")
@@ -0,0 +1,129 @@
/*
* 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 kotlinx.cinterop
import kotlin.native.*
data class Pinned<out T : Any> internal constructor(private val stablePtr: COpaquePointer) {
/**
* Disposes the handle. It must not be [used][get] after that.
*/
fun unpin() {
disposeStablePointer(this.stablePtr)
}
/**
* Returns the underlying pinned object.
*/
fun get(): T = @Suppress("UNCHECKED_CAST") (derefStablePointer(stablePtr) as T)
}
fun <T : Any> T.pin() = Pinned<T>(createStablePointer(this))
inline fun <T : Any, R> T.usePinned(block: (Pinned<T>) -> R): R {
val pinned = this.pin()
return try {
block(pinned)
} finally {
pinned.unpin()
}
}
fun Pinned<ByteArray>.addressOf(index: Int): CPointer<ByteVar> = this.get().addressOfElement(index)
fun ByteArray.refTo(index: Int): CValuesRef<ByteVar> = this.usingPinned { addressOf(index) }
fun Pinned<String>.addressOf(index: Int): CPointer<COpaque> = this.get().addressOfElement(index)
fun String.refTo(index: Int): CValuesRef<COpaque> = this.usingPinned { addressOf(index) }
fun Pinned<CharArray>.addressOf(index: Int): CPointer<COpaque> = this.get().addressOfElement(index)
fun CharArray.refTo(index: Int): CValuesRef<COpaque> = this.usingPinned { addressOf(index) }
fun Pinned<ShortArray>.addressOf(index: Int): CPointer<ShortVar> = this.get().addressOfElement(index)
fun ShortArray.refTo(index: Int): CValuesRef<ShortVar> = this.usingPinned { addressOf(index) }
fun Pinned<IntArray>.addressOf(index: Int): CPointer<IntVar> = this.get().addressOfElement(index)
fun IntArray.refTo(index: Int): CValuesRef<IntVar> = this.usingPinned { addressOf(index) }
fun Pinned<LongArray>.addressOf(index: Int): CPointer<LongVar> = this.get().addressOfElement(index)
fun LongArray.refTo(index: Int): CValuesRef<LongVar> = this.usingPinned { addressOf(index) }
// TODO: pinning of unsigned arrays involves boxing as they are inline classes wrapping signed arrays.
fun Pinned<UByteArray>.addressOf(index: Int): CPointer<UByteVar> = this.get().addressOfElement(index)
fun UByteArray.refTo(index: Int): CValuesRef<UByteVar> = this.usingPinned { addressOf(index) }
fun Pinned<UShortArray>.addressOf(index: Int): CPointer<UShortVar> = this.get().addressOfElement(index)
fun UShortArray.refTo(index: Int): CValuesRef<UShortVar> = this.usingPinned { addressOf(index) }
fun Pinned<UIntArray>.addressOf(index: Int): CPointer<UIntVar> = this.get().addressOfElement(index)
fun UIntArray.refTo(index: Int): CValuesRef<UIntVar> = this.usingPinned { addressOf(index) }
fun Pinned<ULongArray>.addressOf(index: Int): CPointer<ULongVar> = this.get().addressOfElement(index)
fun ULongArray.refTo(index: Int): CValuesRef<ULongVar> = this.usingPinned { addressOf(index) }
fun Pinned<FloatArray>.addressOf(index: Int): CPointer<FloatVar> = this.get().addressOfElement(index)
fun FloatArray.refTo(index: Int): CValuesRef<FloatVar> = this.usingPinned { addressOf(index) }
fun Pinned<DoubleArray>.addressOf(index: Int): CPointer<DoubleVar> = this.get().addressOfElement(index)
fun DoubleArray.refTo(index: Int): CValuesRef<DoubleVar> = this.usingPinned { addressOf(index) }
private inline fun <T : Any, P : CPointed> T.usingPinned(
crossinline block: Pinned<T>.() -> CPointer<P>
) = object : CValuesRef<P>() {
override fun getPointer(scope: AutofreeScope): CPointer<P> {
val pinned = this@usingPinned.pin()
scope.defer { pinned.unpin() }
return pinned.block()
}
}
@SymbolName("Kotlin_Arrays_getByteArrayAddressOfElement")
private external fun ByteArray.addressOfElement(index: Int): CPointer<ByteVar>
@SymbolName("Kotlin_Arrays_getStringAddressOfElement")
private external fun String.addressOfElement(index: Int): CPointer<COpaque>
@SymbolName("Kotlin_Arrays_getCharArrayAddressOfElement")
private external fun CharArray.addressOfElement(index: Int): CPointer<COpaque>
@SymbolName("Kotlin_Arrays_getShortArrayAddressOfElement")
private external fun ShortArray.addressOfElement(index: Int): CPointer<ShortVar>
@SymbolName("Kotlin_Arrays_getIntArrayAddressOfElement")
private external fun IntArray.addressOfElement(index: Int): CPointer<IntVar>
@SymbolName("Kotlin_Arrays_getLongArrayAddressOfElement")
private external fun LongArray.addressOfElement(index: Int): CPointer<LongVar>
@SymbolName("Kotlin_Arrays_getByteArrayAddressOfElement")
private external fun UByteArray.addressOfElement(index: Int): CPointer<UByteVar>
@SymbolName("Kotlin_Arrays_getShortArrayAddressOfElement")
private external fun UShortArray.addressOfElement(index: Int): CPointer<UShortVar>
@SymbolName("Kotlin_Arrays_getIntArrayAddressOfElement")
private external fun UIntArray.addressOfElement(index: Int): CPointer<UIntVar>
@SymbolName("Kotlin_Arrays_getLongArrayAddressOfElement")
private external fun ULongArray.addressOfElement(index: Int): CPointer<ULongVar>
@SymbolName("Kotlin_Arrays_getFloatArrayAddressOfElement")
private external fun FloatArray.addressOfElement(index: Int): CPointer<FloatVar>
@SymbolName("Kotlin_Arrays_getDoubleArrayAddressOfElement")
private external fun DoubleArray.addressOfElement(index: Int): CPointer<DoubleVar>
@@ -0,0 +1,97 @@
package kotlinx.cinterop.internal
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class CStruct(val spelling: String) {
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
annotation class MemberAt(val offset: Long)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.PROPERTY_GETTER)
annotation class ArrayMemberAt(val offset: Long)
@Retention(AnnotationRetention.BINARY)
@Target(
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
annotation class BitField(val offset: Long, val size: Int)
@Retention(AnnotationRetention.BINARY)
annotation class VarType(val size: Long, val align: Int)
}
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@Retention(AnnotationRetention.BINARY)
public annotation class CCall(val id: String) {
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.BINARY)
annotation class CString
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.BINARY)
annotation class WCString
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ReturnsRetained
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
annotation class ConsumesReceiver
@Target(AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.BINARY)
annotation class Consumed
}
/**
* Collection of annotations that allow to store
* constant values.
*/
public object ConstantValue {
@Retention(AnnotationRetention.BINARY)
annotation class Byte(val value: kotlin.Byte)
@Retention(AnnotationRetention.BINARY)
annotation class Short(val value: kotlin.Short)
@Retention(AnnotationRetention.BINARY)
annotation class Int(val value: kotlin.Int)
@Retention(AnnotationRetention.BINARY)
annotation class Long(val value: kotlin.Long)
@Retention(AnnotationRetention.BINARY)
annotation class UByte(val value: kotlin.UByte)
@Retention(AnnotationRetention.BINARY)
annotation class UShort(val value: kotlin.UShort)
@Retention(AnnotationRetention.BINARY)
annotation class UInt(val value: kotlin.UInt)
@Retention(AnnotationRetention.BINARY)
annotation class ULong(val value: kotlin.ULong)
@Retention(AnnotationRetention.BINARY)
annotation class Float(val value: kotlin.Float)
@Retention(AnnotationRetention.BINARY)
annotation class Double(val value: kotlin.Double)
@Retention(AnnotationRetention.BINARY)
annotation class String(val value: kotlin.String)
}
/**
* Denotes property that is an alias to some enum entry.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
public annotation class CEnumEntryAlias(val entryName: String)
/**
* Stores instance size of the type T: CEnumVar.
*/
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
public annotation class CEnumVarTypeSize(val size: Int)
@@ -0,0 +1,59 @@
/*
* 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.
*/
buildscript {
apply from: "$rootDir/kotlin-native/gradle/kotlinGradlePlugin.gradle"
}
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = "org.jetbrains.kotlin.native.interop.gen.jvm.MainKt"
dependencies {
implementation project(":kotlin-native:Interop:Indexer")
implementation project(":kotlin-native:utilities:basic-utils")
api project(path: ":kotlin-native:endorsedLibraries:kotlinx.cli", configuration: "jvmRuntimeElements")
api project(":kotlin-stdlib")
api project(kotlinCompilerModule)
api project(":kotlinx-metadata-klib")
api project(":native:kotlin-native-utils")
implementation(project(":compiler:util"))
implementation(project(":compiler:ir.serialization.common"))
testImplementation "junit:junit:4.12"
testImplementation project(":kotlin-test:kotlin-test-junit")
}
compileKotlin {
kotlinOptions {
freeCompilerArgs = ['-Xopt-in=kotlin.ExperimentalUnsignedTypes', '-Xskip-metadata-version-check']
allWarningsAsErrors=true
}
}
sourceSets{
main {
kotlin {
srcDir("../../shared/src/library/kotlin")
srcDir("../../shared/src/main/kotlin")
srcDir(VersionGeneratorKt.kotlinNativeVersionSrc(project))
}
}
}
@@ -0,0 +1,135 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.native.interop.indexer.*
/**
* objc_msgSend*_stret functions must be used when return value is returned through memory
* pointed by implicit argument, which is passed on the register that would otherwise be used for receiver.
*
* The entire implementation is just the real ABI approximation which is enough for practical cases.
*/
internal fun Type.isStret(target: KonanTarget): Boolean {
val unwrappedType = this.unwrapTypedefs()
val abiInfo: ObjCAbiInfo = when (target) {
KonanTarget.IOS_ARM64,
KonanTarget.TVOS_ARM64,
KonanTarget.MACOS_ARM64 -> DarwinArm64AbiInfo()
KonanTarget.IOS_X64,
KonanTarget.MACOS_X64,
KonanTarget.WATCHOS_X64,
KonanTarget.TVOS_X64 -> DarwinX64AbiInfo()
KonanTarget.WATCHOS_X86 -> DarwinX86AbiInfo()
KonanTarget.IOS_ARM32,
KonanTarget.WATCHOS_ARM32 -> DarwinArm32AbiInfo(target)
else -> error("Cannot generate ObjC stubs for $target.")
}
return abiInfo.shouldUseStret(unwrappedType)
}
/**
* Provides ABI-specific information about target for Objective C interop.
*/
interface ObjCAbiInfo {
fun shouldUseStret(returnType: Type): Boolean
}
class DarwinX64AbiInfo : ObjCAbiInfo {
override fun shouldUseStret(returnType: Type): Boolean {
return when (returnType) {
is RecordType -> returnType.decl.def!!.size > 16 || returnType.hasUnalignedMembers()
else -> false
}
}
}
class DarwinX86AbiInfo : ObjCAbiInfo {
override fun shouldUseStret(returnType: Type): Boolean {
// https://github.com/llvm/llvm-project/blob/6c8a34ed9b49704bdd60838143047c62ba9f2502/clang/lib/CodeGen/TargetInfo.cpp#L1243
return when (returnType) {
is RecordType -> {
val size = returnType.decl.def!!.size
val canBePassedInRegisters = (size == 1L || size == 2L || size == 4L || size == 8L)
return !canBePassedInRegisters
}
else -> false
}
}
}
class DarwinArm32AbiInfo(private val target: KonanTarget) : ObjCAbiInfo {
override fun shouldUseStret(returnType: Type): Boolean = when (target) {
KonanTarget.IOS_ARM32 -> when (returnType) {
is RecordType -> !returnType.isIntegerLikeType()
else -> false
}
// 32-bit watchOS uses armv7k which is effectively Cortex-A7 and
// uses AAPCS16 VPF.
KonanTarget.WATCHOS_ARM32 -> when (returnType) {
is RecordType -> {
// https://github.com/llvm/llvm-project/blob/6c8a34ed9b49704bdd60838143047c62ba9f2502/clang/lib/CodeGen/TargetInfo.cpp#L6165
when {
returnType.decl.def!!.size <= 16 -> false
else -> true
}
}
else -> false
}
else -> error("Unexpected target")
}
}
class DarwinArm64AbiInfo : ObjCAbiInfo {
override fun shouldUseStret(returnType: Type): Boolean {
// On aarch64 stret is never the case, since an implicit argument gets passed on x8.
return false
}
}
private fun Type.isIntegerLikeType(): Boolean = when (this) {
is RecordType -> {
val def = this.decl.def
if (def == null) {
false
} else {
def.size <= 4 &&
def.members.all {
when (it) {
is BitField -> it.type.isIntegerLikeType()
is Field -> it.offset == 0L && it.type.isIntegerLikeType()
is IncompleteField -> false
}
}
}
}
is ObjCPointer, is PointerType, CharType, is BoolType -> true
is IntegerType -> this.size <= 4
is Typedef -> this.def.aliased.isIntegerLikeType()
is EnumType -> this.def.baseType.isIntegerLikeType()
else -> false
}
private fun Type.hasUnalignedMembers(): Boolean = when (this) {
is Typedef -> this.def.aliased.hasUnalignedMembers()
is RecordType -> this.decl.def!!.let { def ->
def.fields.any {
!it.isAligned ||
// Check members of fields too:
it.type.hasUnalignedMembers()
}
}
is ArrayType -> this.elemType.hasUnalignedMembers()
else -> false
// TODO: should the recursive checks be made in indexer when computing `hasUnalignedFields`?
}
@@ -0,0 +1,94 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.FunctionDecl
import org.jetbrains.kotlin.native.interop.indexer.GlobalDecl
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
internal data class CCalleeWrapper(val lines: List<String>)
/**
* Some functions don't have an address (e.g. macros-based or builtins).
* To solve this problem we generate a wrapper function.
*/
internal class CWrappersGenerator(private val context: StubIrContext) {
private var currentFunctionWrapperId = 0
private val packageName =
context.configuration.pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_")
private fun generateFunctionWrapperName(functionName: String): String {
return "${packageName}_${functionName}_wrapper${currentFunctionWrapperId++}"
}
private fun bindSymbolToFunction(symbol: String, function: String): List<String> = listOf(
"const void* $symbol __asm(${symbol.quoteAsKotlinLiteral()});",
"const void* $symbol = &$function;"
)
private data class Parameter(val type: String, val name: String)
private fun createWrapper(
symbolName: String,
wrapperName: String,
returnType: String,
parameters: List<Parameter>,
body: String
): List<String> = listOf(
"__attribute__((always_inline))",
"$returnType $wrapperName(${parameters.joinToString { "${it.type} ${it.name}" }}) {",
body,
"}",
*bindSymbolToFunction(symbolName, wrapperName).toTypedArray()
)
fun generateCCalleeWrapper(function: FunctionDecl, symbolName: String): CCalleeWrapper =
if (function.isVararg) {
CCalleeWrapper(bindSymbolToFunction(symbolName, function.name))
} else {
val wrapperName = generateFunctionWrapperName(function.name)
val returnType = function.returnType.getStringRepresentation()
val parameters = function.parameters.mapIndexed { index, parameter ->
Parameter(parameter.type.getStringRepresentation(), "p$index")
}
val callExpression = "${function.name}(${parameters.joinToString { it.name }});"
val wrapperBody = if (function.returnType.unwrapTypedefs() is VoidType) {
callExpression
} else {
"return $callExpression"
}
val wrapper = createWrapper(symbolName, wrapperName, returnType, parameters, wrapperBody)
CCalleeWrapper(wrapper)
}
fun generateCGlobalGetter(globalDecl: GlobalDecl, symbolName: String): CCalleeWrapper {
val wrapperName = generateFunctionWrapperName("${globalDecl.name}_getter")
val returnType = globalDecl.type.getStringRepresentation()
val wrapperBody = "return ${globalDecl.name};"
val wrapper = createWrapper(symbolName, wrapperName, returnType, emptyList(), wrapperBody)
return CCalleeWrapper(wrapper)
}
fun generateCGlobalByPointerGetter(globalDecl: GlobalDecl, symbolName: String): CCalleeWrapper {
val wrapperName = generateFunctionWrapperName("${globalDecl.name}_getter")
val returnType = "void*"
val wrapperBody = "return &${globalDecl.name};"
val wrapper = createWrapper(symbolName, wrapperName, returnType, emptyList(), wrapperBody)
return CCalleeWrapper(wrapper)
}
fun generateCGlobalSetter(globalDecl: GlobalDecl, symbolName: String): CCalleeWrapper {
val wrapperName = generateFunctionWrapperName("${globalDecl.name}_setter")
val globalType = globalDecl.type.getStringRepresentation()
val parameter = Parameter(globalType, "p1")
val wrapperBody = "${globalDecl.name} = ${parameter.name};"
val wrapper = createWrapper(symbolName, wrapperName, "void", listOf(parameter), wrapperBody)
return CCalleeWrapper(wrapper)
}
}
@@ -0,0 +1,103 @@
/*
* 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.native.interop.gen
interface NativeScope {
val mappingBridgeGenerator: MappingBridgeGenerator
}
class NativeCodeBuilder(val scope: NativeScope) {
val lines = mutableListOf<String>()
fun out(line: String): Unit {
lines.add(line)
}
}
inline fun buildNativeCodeLines(scope: NativeScope, block: NativeCodeBuilder.() -> Unit): List<String> {
val builder = NativeCodeBuilder(scope)
builder.block()
return builder.lines
}
private class Block(val nesting: Int, val start: String, val end: String) {
val prologue = mutableListOf<String>()
val body = mutableListOf<String>()
val epilogue = mutableListOf<String>()
fun indent(line: String) = " ".repeat(nesting) + line
fun indentBraces(line: String) = " ".repeat(nesting - 1) + line
}
class KotlinCodeBuilder(val scope: KotlinScope) {
private val blocks = mutableListOf<Block>()
init {
pushBlock("", "")
}
fun out(line: String) {
currentBlock().body += line
}
private var memScoped = false
fun pushMemScoped() {
if (!memScoped) {
memScoped = true
pushBlock("memScoped {")
}
}
fun getNativePointer(name: String): String {
return "$name?.getPointer(memScope)"
}
fun returnResult(result: String) {
currentBlock().body += "return $result"
}
private fun currentBlock() = blocks.last()
fun pushBlock(start: String, end: String = "}") {
val block = Block(blocks.size, start = start, end = end)
blocks += block
}
private fun emitBlockAndNested(position: Int, lines: MutableList<String>) {
if (position >= blocks.size) return
val block = blocks[position]
if (block.start.isNotEmpty()) lines += block.indentBraces(block.start)
lines += block.prologue.map { block.indent(it) }
lines += block.body.map { block.indent(it) }
emitBlockAndNested(position + 1, lines)
lines += block.epilogue.map { block.indent(it) }
if (block.end.isNotEmpty()) lines += block.indentBraces(block.end)
}
fun build(): List<String> {
val lines = mutableListOf<String>()
emitBlockAndNested(0, lines)
return lines.toList()
}
}
inline fun buildKotlinCodeLines(scope: KotlinScope, block: KotlinCodeBuilder.() -> Unit): List<String> {
val builder = KotlinCodeBuilder(scope)
builder.block()
return builder.build()
}
@@ -0,0 +1,91 @@
/*
* 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.native.interop.gen
val kotlinKeywords = setOf(
"as", "break", "class", "continue", "do", "dynamic", "else", "false", "for", "fun", "if", "in",
"interface", "is", "null", "object", "package", "return", "super", "this", "throw",
"true", "try", "typealias", "val", "var", "when", "while",
// While not technically keywords, those shall be escaped as well.
"_", "__", "___"
)
/**
* The expression written in native language.
*/
typealias NativeExpression = String
/**
* The expression written in Kotlin.
*/
typealias KotlinExpression = String
/**
* For this identifier constructs the string to be parsed by Kotlin as `SimpleName`
* defined [here](https://kotlinlang.org/docs/reference/grammar.html#SimpleName).
*/
fun String.asSimpleName(): String = if (this in kotlinKeywords || this.contains("$")) {
"`$this`"
} else {
this
}
/**
* Yet another mangler, particularly to avoid secondary clash, e.g. when a property
* in prototype (interface) is mangled and that will cause another clash in the class
* which implements this interface.
* Rationale: keep algorithm simple but use the mangling characters which are rare
* in normal code, and keep mangling easy readable.
*/
internal fun mangleSimple(name: String): String {
val reserved = setOf("Companion")
val postfix = "\$"
return if (name in reserved)
"$name$postfix"
else
name
}
/**
* Returns the expression to be parsed by Kotlin as string literal with given contents,
* i.e. transforms `foo$bar` to `"foo\$bar"`.
*/
fun String.quoteAsKotlinLiteral(): KotlinExpression = buildString {
append('"')
this@quoteAsKotlinLiteral.forEach { c ->
when (c) {
in charactersAllowedInKotlinStringLiterals -> append(c)
'$' -> append("\\$")
else -> append("\\u" + "%04X".format(c.toInt()))
}
}
append('"')
}
// TODO: improve literal readability by preserving more characters.
private val charactersAllowedInKotlinStringLiterals: Set<Char> = mutableSetOf<Char>().apply {
addAll('a' .. 'z')
addAll('A' .. 'Z')
addAll('0' .. '9')
addAll(listOf('_', '@', ':', ';', '.', ',', '{', '}', '=', '[', ']', '^', '#', '*', ' ', '(', ')'))
}
val annotationForUnableToImport
get() = "@Deprecated(${"Unable to import this declaration".quoteAsKotlinLiteral()}, level = DeprecationLevel.ERROR)"
@@ -0,0 +1,72 @@
/*
* 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.native.interop.gen
import org.jetbrains.kotlin.konan.library.KonanLibrary
import org.jetbrains.kotlin.native.interop.indexer.*
interface Imports {
fun getPackage(location: Location): String?
}
class PackageInfo(val name: String, val library: KonanLibrary)
class ImportsImpl(internal val headerIdToPackage: Map<HeaderId, PackageInfo>) : Imports {
override fun getPackage(location: Location): String? {
val packageInfo = headerIdToPackage[location.headerId]
?: return null
accessedLibraries += packageInfo.library
return packageInfo.name
}
private val accessedLibraries = mutableSetOf<KonanLibrary>()
val requiredLibraries: Set<KonanLibrary>
get() = accessedLibraries.toSet()
}
class HeaderInclusionPolicyImpl(private val nameGlobs: List<String>) : HeaderInclusionPolicy {
override fun excludeUnused(headerName: String?): Boolean {
if (nameGlobs.isEmpty()) {
return false
}
if (headerName == null) {
// Builtins; included only if no globs are specified:
return true
}
return nameGlobs.all { !headerName.matchesToGlob(it) }
}
}
class HeaderExclusionPolicyImpl(
private val importsImpl: ImportsImpl
) : HeaderExclusionPolicy {
override fun excludeAll(headerId: HeaderId): Boolean {
return headerId in importsImpl.headerIdToPackage
}
}
private fun String.matchesToGlob(glob: String): Boolean =
java.nio.file.FileSystems.getDefault()
.getPathMatcher("glob:$glob").matches(java.nio.file.Paths.get(this))
@@ -0,0 +1,346 @@
/*
* 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.native.interop.gen
import kotlin.reflect.KProperty
interface KotlinScope {
/**
* @return the string to be used to reference the classifier in current scope.
*/
fun reference(classifier: Classifier): String
/**
* @return the string to be used as a name in the declaration of the classifier in current scope.
*/
fun declare(classifier: Classifier): String
/**
* @return the string to be used as a name in the declaration of the property in current scope,
* or `null` if the property with given name can't be declared.
*/
fun declareProperty(receiver: String?, name: String): String?
val mappingBridgeGenerator: MappingBridgeGenerator
}
data class Classifier(
val pkg: String,
val topLevelName: String,
private val nestedNames: List<String> = emptyList()
) {
companion object {
fun topLevel(pkg: String, name: String): Classifier {
assert(!name.contains('.'))
assert(!name.contains('`'))
return Classifier(pkg, name)
}
}
val isTopLevel: Boolean get() = this.nestedNames.isEmpty()
fun nested(name: String): Classifier {
assert(!name.contains('.'))
assert(!name.contains('`'))
return this.copy(nestedNames = nestedNames + name)
}
fun getRelativeFqName(asSimpleName: Boolean = true): String = buildString {
append(topLevelName.run { if (asSimpleName) asSimpleName() else this })
nestedNames.forEach {
append('.')
append(it.run { if (asSimpleName) asSimpleName() else this })
}
}
val fqName: String get() = buildString {
if (pkg.isNotEmpty()) {
append(pkg)
append('.')
}
append(getRelativeFqName())
}
}
val Classifier.type
get() = KotlinClassifierType(this, arguments = emptyList(), nullable = false, underlyingType = null)
fun Classifier.typeWith(vararg arguments: KotlinTypeArgument) =
KotlinClassifierType(this, arguments.toList(), nullable = false, underlyingType = null)
fun Classifier.typeAbbreviation(expandedType: KotlinType) =
KotlinClassifierType(this, arguments = emptyList(), nullable = false, underlyingType = expandedType)
interface KotlinTypeArgument {
/**
* @return the string to be used in the given scope to denote this.
*/
fun render(scope: KotlinScope): String
}
object StarProjection : KotlinTypeArgument {
override fun render(scope: KotlinScope) = "*"
}
interface KotlinType : KotlinTypeArgument {
val classifier: Classifier
fun makeNullableAsSpecified(nullable: Boolean): KotlinType
}
/**
* @property underlyingType is non-null if this type is an alias to another type.
*/
data class KotlinClassifierType(
override val classifier: Classifier,
val arguments: List<KotlinTypeArgument>,
val nullable: Boolean,
val underlyingType: KotlinType?
) : KotlinType {
override fun makeNullableAsSpecified(nullable: Boolean) = if (this.nullable == nullable) {
this
} else {
this.copy(nullable = nullable)
}
override fun render(scope: KotlinScope): String = buildString {
append(scope.reference(classifier))
if (arguments.isNotEmpty()) {
append('<')
arguments.joinTo(this) { it.render(scope) }
append('>')
}
if (nullable) {
append('?')
}
}
}
fun KotlinType.makeNullable() = this.makeNullableAsSpecified(true)
data class KotlinFunctionType(
val parameterTypes: List<KotlinType>,
val returnType: KotlinType,
val nullable: Boolean = false
) : KotlinType {
override fun makeNullableAsSpecified(nullable: Boolean) = if (this.nullable == nullable) {
this
} else {
this.copy(nullable = nullable)
}
override val classifier by lazy {
Classifier.topLevel("kotlin", "Function${parameterTypes.size}")
}
override fun render(scope: KotlinScope) = buildString {
if (nullable) append("(")
append('(')
parameterTypes.joinTo(this) { it.render(scope) }
append(") -> ")
append(returnType.render(scope))
if (nullable) append(")?")
}
}
internal val cnamesStructsPackageName = "cnames.structs"
object KotlinTypes {
val independent = Classifier.topLevel("kotlin.native.internal", "Independent")
val boolean by BuiltInType
val byte by BuiltInType
val short by BuiltInType
val int by BuiltInType
val long by BuiltInType
val uByte by BuiltInType
val uShort by BuiltInType
val uInt by BuiltInType
val uLong by BuiltInType
val float by BuiltInType
val double by BuiltInType
val unit by BuiltInType
val string by BuiltInType
val any by BuiltInType
val list by CollectionClassifier
val mutableList by CollectionClassifier
val set by CollectionClassifier
val map by CollectionClassifier
val nativePtr by InteropType
val vector128 by KotlinNativeType
val cOpaque by InteropType
val cOpaquePointer by InteropType
val cOpaquePointerVar by InteropType
val booleanVarOf by InteropClassifier
val objCObject by InteropClassifier
val objCObjectMeta by InteropClassifier
val objCClass by InteropClassifier
val objCClassOf by InteropClassifier
val objCProtocol by InteropClassifier
val cValuesRef by InteropClassifier
val cPointed by InteropClassifier
val cPointer by InteropClassifier
val cPointerVar by InteropClassifier
val cArrayPointer by InteropClassifier
val cArrayPointerVar by InteropClassifier
val cPointerVarOf by InteropClassifier
val cFunction by InteropClassifier
val objCObjectVar by InteropClassifier
val objCObjectBase by InteropClassifier
val objCObjectBaseMeta by InteropClassifier
val objCBlockVar by InteropClassifier
val objCNotImplementedVar by InteropClassifier
val cValue by InteropClassifier
private open class ClassifierAtPackage(val pkg: String) {
operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): Classifier =
Classifier.topLevel(pkg, property.name.capitalize())
}
private open class TypeAtPackage(val pkg: String) {
operator fun getValue(thisRef: KotlinTypes, property: KProperty<*>): KotlinClassifierType =
Classifier.topLevel(pkg, property.name.capitalize()).type
}
private object BuiltInType : TypeAtPackage("kotlin")
private object CollectionClassifier : ClassifierAtPackage("kotlin.collections")
private object InteropClassifier : ClassifierAtPackage("kotlinx.cinterop")
private object InteropType : TypeAtPackage("kotlinx.cinterop")
private object KotlinNativeType : TypeAtPackage("kotlin.native")
}
abstract class KotlinFile(
val pkg: String,
namesToBeDeclared: List<String>
) : KotlinScope {
// Note: all names are related to classifiers currently.
private val namesToBeDeclared: Set<String>
init {
this.namesToBeDeclared = mutableSetOf()
namesToBeDeclared.forEach {
if (it in this.namesToBeDeclared) {
throw IllegalArgumentException("'$it' is going to be declared twice")
} else {
this.namesToBeDeclared.add(it)
}
}
}
private val importedNameToPkg = mutableMapOf<String, String>()
private val declaredProperties = mutableSetOf<String>()
override fun reference(classifier: Classifier): String = if (classifier.topLevelName in namesToBeDeclared) {
if (classifier.pkg == this.pkg) {
classifier.getRelativeFqName()
} else {
// Don't import if would clash with own declaration:
classifier.fqName
}
} else if (classifier.pkg == this.pkg) {
throw IllegalArgumentException(
"'${classifier.topLevelName}' from the file package was not reserved for declaration"
)
} else {
if (tryImport(classifier)) {
// Is successfully imported:
classifier.getRelativeFqName()
} else {
classifier.fqName
}
}
private fun tryImport(classifier: Classifier): Boolean {
if (classifier.topLevelName in declaredProperties) {
return false
}
return importedNameToPkg.getOrPut(classifier.topLevelName) { classifier.pkg } == classifier.pkg
}
private val alreadyDeclared = mutableSetOf<String>()
override fun declare(classifier: Classifier): String {
if (classifier.pkg != this.pkg) {
throw IllegalArgumentException("wrong package for classifier ${classifier.fqName}; expected '$pkg', got '${classifier.pkg}'")
}
if (!classifier.isTopLevel) {
throw IllegalArgumentException(
"'${classifier.getRelativeFqName()}' is not top-level thus can't be declared at file scope"
)
}
val topLevelName = classifier.topLevelName
if (topLevelName in alreadyDeclared) {
throw IllegalStateException("'$topLevelName' is already declared")
}
alreadyDeclared.add(topLevelName)
return topLevelName
}
override fun declareProperty(receiver: String?, name: String): String? {
val fullName = receiver?.let { "$it.${name}" } ?: name
return if (fullName in declaredProperties || name in namesToBeDeclared || name in importedNameToPkg) {
null
// TODO: using original global name should be preferred to importing the clashed name.
} else {
declaredProperties.add(fullName)
name
}
}
fun buildImports(): List<String> = importedNameToPkg.mapNotNull { (name, pkg) ->
if (pkg == "kotlin" || pkg == "kotlinx.cinterop") {
// Is already imported either by default or with '*':
null
} else {
"import $pkg.${name.asSimpleName()}"
}
}.sorted()
}
internal fun getTopLevelPropertyDeclarationName(scope: KotlinScope, property: PropertyStub): String {
val receiverName = property.receiverType?.underlyingTypeFqName
return getTopLevelPropertyDeclarationName(scope, receiverName, property.name)
}
// Try to use the provided name. If failed, mangle it with underscore and try again:
private tailrec fun getTopLevelPropertyDeclarationName(scope: KotlinScope, receiver: String?, name: String): String =
scope.declareProperty(receiver, name) ?: getTopLevelPropertyDeclarationName(scope, receiver, name + "_")
@@ -0,0 +1,44 @@
/*
* 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.native.interop.gen
import org.jetbrains.kotlin.konan.file.File
internal fun resolveLibraries(staticLibraries: List<String>, libraryPaths: List<String>): List<String> {
val result = mutableListOf<String>()
staticLibraries.forEach { library ->
val resolution = libraryPaths.map { "$it/$library" }
.find { File(it).exists }
if (resolution != null) {
result.add(resolution)
} else {
error("Could not find '$library' binary in neither of $libraryPaths")
}
}
return result
}
internal fun argsToCompiler(staticLibraries: Array<String>, libraryPaths: Array<String>) = argsToCompiler(staticLibraries.toList(), libraryPaths.toList())
internal fun argsToCompiler(staticLibraries: List<String>, libraryPaths: List<String>) =
resolveLibraries(staticLibraries, libraryPaths)
.map { it -> listOf("-include-binary", it) }
.flatten()
.toTypedArray()
@@ -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.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.Type
data class TypedKotlinValue(val type: Type, val value: KotlinExpression)
data class TypedNativeValue(val type: Type, val value: NativeExpression)
/**
* Generates bridges between Kotlin and native, passing arbitrary native-typed values.
*
* It does the same as [SimpleBridgeGenerator] except that it supports any native types, e.g. struct values.
*/
interface MappingBridgeGenerator {
fun kotlinToNative(
builder: KotlinCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
kotlinValues: List<TypedKotlinValue>,
independent: Boolean,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression
fun nativeToKotlin(
builder: NativeCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
nativeValues: List<TypedNativeValue>,
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
): NativeExpression
}
@@ -0,0 +1,207 @@
/*
* 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.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.RecordType
import org.jetbrains.kotlin.native.interop.indexer.Type
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
/**
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
* maps the type using [mirror].
*/
class MappingBridgeGeneratorImpl(
val declarationMapper: DeclarationMapper,
val simpleBridgeGenerator: SimpleBridgeGenerator
) : MappingBridgeGenerator {
override fun kotlinToNative(
builder: KotlinCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
kotlinValues: List<TypedKotlinValue>,
independent: Boolean,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression {
val bridgeArguments = mutableListOf<BridgeTypedKotlinValue>()
kotlinValues.forEach { (type, value) ->
if (type.unwrapTypedefs() is RecordType) {
builder.pushMemScoped()
val bridgeArgument = "$value.getPointer(memScope).rawValue"
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, bridgeArgument))
} else {
val info = mirror(declarationMapper, type).info
bridgeArguments.add(BridgeTypedKotlinValue(info.bridgedType, info.argToBridged(value)))
}
}
val unwrappedReturnType = returnType.unwrapTypedefs()
val kniRetVal = "kniRetVal"
val bridgeReturnType = when (unwrappedReturnType) {
VoidType -> BridgedType.VOID
is RecordType -> {
val mirror = mirror(declarationMapper, returnType)
val tmpVarName = kniRetVal
// We clear in the finally block.
builder.out("val $tmpVarName = nativeHeap.alloc<${mirror.pointedType.render(builder.scope)}>()")
builder.pushBlock(start = "try {", end = "} finally { nativeHeap.free($tmpVarName) }")
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "$tmpVarName.rawPtr"))
BridgedType.VOID
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.bridgedType
}
}
val callExpr = simpleBridgeGenerator.kotlinToNative(
nativeBacked, bridgeReturnType, bridgeArguments, independent
) { bridgeNativeValues ->
val nativeValues = mutableListOf<String>()
kotlinValues.forEachIndexed { index, (type, _) ->
val unwrappedType = type.unwrapTypedefs()
if (unwrappedType is RecordType) {
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
} else {
nativeValues.add(
mirror(declarationMapper, type).info.cFromBridged(
bridgeNativeValues[index], scope, nativeBacked
)
)
}
}
val nativeResult = block(nativeValues)
when (unwrappedReturnType) {
is VoidType -> {
out(nativeResult + ";")
""
}
is RecordType -> {
val kniStructResult = "kniStructResult"
out("${unwrappedReturnType.decl.spelling} $kniStructResult = $nativeResult;")
out("memcpy(${bridgeNativeValues.last()}, &$kniStructResult, sizeof($kniStructResult));")
""
}
else -> {
nativeResult
}
}
}
val result = when (unwrappedReturnType) {
is VoidType -> callExpr
is RecordType -> {
builder.out(callExpr)
"$kniRetVal.readValue()"
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.argFromBridged(callExpr, builder.scope, nativeBacked)
}
}
return result
}
override fun nativeToKotlin(
builder: NativeCodeBuilder,
nativeBacked: NativeBacked,
returnType: Type,
nativeValues: List<TypedNativeValue>,
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
): NativeExpression {
val bridgeArguments = mutableListOf<BridgeTypedNativeValue>()
nativeValues.forEachIndexed { _, (type, value) ->
val bridgeArgument = if (type.unwrapTypedefs() is RecordType) {
BridgeTypedNativeValue(BridgedType.NATIVE_PTR, "&$value")
} else {
val info = mirror(declarationMapper, type).info
BridgeTypedNativeValue(info.bridgedType, value)
}
bridgeArguments.add(bridgeArgument)
}
val unwrappedReturnType = returnType.unwrapTypedefs()
val kniRetVal = "kniRetVal"
val bridgeReturnType = when (unwrappedReturnType) {
VoidType -> BridgedType.VOID
is RecordType -> {
val tmpVarName = kniRetVal
builder.out("${unwrappedReturnType.decl.spelling} $tmpVarName;")
bridgeArguments.add(BridgeTypedNativeValue(BridgedType.NATIVE_PTR, "&$tmpVarName"))
BridgedType.VOID
}
else -> {
val mirror = mirror(declarationMapper, returnType)
mirror.info.bridgedType
}
}
val callExpr = simpleBridgeGenerator.nativeToKotlin(
nativeBacked,
bridgeReturnType,
bridgeArguments
) { bridgeKotlinValues ->
val kotlinValues = mutableListOf<String>()
nativeValues.forEachIndexed { index, (type, _) ->
val mirror = mirror(declarationMapper, type)
if (type.unwrapTypedefs() is RecordType) {
val pointedTypeName = mirror.pointedType.render(this.scope)
kotlinValues.add(
"interpretPointed<$pointedTypeName>(${bridgeKotlinValues[index]}).readValue()"
)
} else {
kotlinValues.add(mirror.info.argFromBridged(bridgeKotlinValues[index], this.scope, nativeBacked))
}
}
val kotlinResult = block(kotlinValues)
when (unwrappedReturnType) {
is RecordType -> {
"$kotlinResult.write(${bridgeKotlinValues.last()})"
}
is VoidType -> {
kotlinResult
}
else -> {
mirror(declarationMapper, returnType).info.argToBridged(kotlinResult)
}
}
}
val result = when (unwrappedReturnType) {
is VoidType -> callExpr
is RecordType -> {
builder.out("$callExpr;")
kniRetVal
}
else -> {
mirror(declarationMapper, returnType).info.cFromBridged(callExpr, builder.scope, nativeBacked)
}
}
return result
}
}
@@ -0,0 +1,578 @@
/*
* 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.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
interface DeclarationMapper {
fun getKotlinClassForPointed(structDecl: StructDecl): Classifier
fun isMappedToStrict(enumDef: EnumDef): Boolean
fun getKotlinNameForValue(enumDef: EnumDef): String
fun getPackageFor(declaration: TypeDeclaration): String
val useUnsignedTypes: Boolean
}
fun DeclarationMapper.isMappedToSigned(integerType: IntegerType): Boolean = integerType.isSigned || !useUnsignedTypes
fun DeclarationMapper.getKotlinClassFor(
objCClassOrProtocol: ObjCClassOrProtocol,
isMeta: Boolean = false
): Classifier {
val pkg = if (objCClassOrProtocol.isForwardDeclaration) {
when (objCClassOrProtocol) {
is ObjCClass -> "objcnames.classes"
is ObjCProtocol -> "objcnames.protocols"
}
} else {
this.getPackageFor(objCClassOrProtocol)
}
val className = objCClassOrProtocol.kotlinClassName(isMeta)
return Classifier.topLevel(pkg, className)
}
fun PrimitiveType.getKotlinType(declarationMapper: DeclarationMapper): KotlinClassifierType = when (this) {
is CharType -> KotlinTypes.byte
is BoolType -> KotlinTypes.boolean
// TODO: C primitive types should probably be generated as type aliases for Kotlin types.
is IntegerType -> if (declarationMapper.isMappedToSigned(this)) {
when (this.size) {
1 -> KotlinTypes.byte
2 -> KotlinTypes.short
4 -> KotlinTypes.int
8 -> KotlinTypes.long
else -> TODO(this.toString())
}
} else {
when (this.size) {
1 -> KotlinTypes.uByte
2 -> KotlinTypes.uShort
4 -> KotlinTypes.uInt
8 -> KotlinTypes.uLong
else -> TODO(this.toString())
}
}
is FloatingType -> when (this.size) {
4 -> KotlinTypes.float
8 -> KotlinTypes.double
else -> TODO(this.toString())
}
is VectorType -> {
/// @todo assert elementType and size here
KotlinTypes.vector128
}
else -> throw NotImplementedError()
}
private fun PrimitiveType.getBridgedType(declarationMapper: DeclarationMapper): BridgedType {
val kotlinType = this.getKotlinType(declarationMapper)
return BridgedType.values().single {
it.kotlinType == kotlinType
}
}
internal val ObjCPointer.isNullable: Boolean
get() = this.nullability != ObjCPointer.Nullability.NonNull
/**
* Describes the Kotlin types used to represent some C type.
*/
sealed class TypeMirror(val pointedType: KotlinClassifierType, val info: TypeInfo) {
/**
* Type to be used in bindings for argument or return value.
*/
abstract val argType: KotlinType
/**
* Mirror for C type to be represented in Kotlin as by-value type.
*/
class ByValue(
pointedType: KotlinClassifierType,
info: TypeInfo,
val valueType: KotlinType,
val nullable: Boolean = (info is TypeInfo.Pointer)
) : TypeMirror(pointedType, info) {
override val argType: KotlinType
get() = valueType.makeNullableAsSpecified(nullable)
}
/**
* Mirror for C type to be represented in Kotlin as by-ref type.
*/
class ByRef(pointedType: KotlinClassifierType, info: TypeInfo) : TypeMirror(pointedType, info) {
override val argType: KotlinType get() = KotlinTypes.cValue.typeWith(pointedType)
}
}
/**
* Describes various type conversions for [TypeMirror].
*/
sealed class TypeInfo {
/**
* The conversion from [TypeMirror.argType] to [bridgedType].
*/
abstract fun argToBridged(expr: KotlinExpression): KotlinExpression
/**
* The conversion from [bridgedType] to [TypeMirror.argType].
*/
abstract fun argFromBridged(
expr: KotlinExpression,
scope: KotlinScope,
nativeBacked: NativeBacked
): KotlinExpression
abstract val bridgedType: BridgedType
open fun cFromBridged(
expr: NativeExpression,
scope: NativeScope,
nativeBacked: NativeBacked
): NativeExpression = expr
open fun cToBridged(expr: NativeExpression): NativeExpression = expr
/**
* If this info is for [TypeMirror.ByValue], then this method describes how to
* construct pointed-type from value type.
*/
abstract fun constructPointedType(valueType: KotlinType): KotlinClassifierType
class Primitive(override val bridgedType: BridgedType, val varClass: Classifier) : TypeInfo() {
override fun argToBridged(expr: KotlinExpression) = expr
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) = expr
override fun constructPointedType(valueType: KotlinType) = varClass.typeWith(valueType)
}
class Boolean : TypeInfo() {
override fun argToBridged(expr: KotlinExpression) = "$expr.toByte()"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
"$expr.toBoolean()"
override val bridgedType: BridgedType get() = BridgedType.BYTE
override fun cFromBridged(expr: NativeExpression, scope: NativeScope, nativeBacked: NativeBacked) =
"($expr) ? 1 : 0"
override fun cToBridged(expr: NativeExpression) = "($expr) ? 1 : 0"
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.booleanVarOf.typeWith(valueType)
}
class Enum(val clazz: Classifier, override val bridgedType: BridgedType) : TypeInfo() {
override fun argToBridged(expr: KotlinExpression) = "$expr.value"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
scope.reference(clazz) + ".byValue($expr)"
override fun constructPointedType(valueType: KotlinType) =
clazz.nested("Var").type // TODO: improve
}
class Pointer(val pointee: KotlinType, val cPointee: Type) : TypeInfo() {
override fun argToBridged(expr: String) = "$expr.rawValue"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
"interpretCPointer<${pointee.render(scope)}>($expr)"
override val bridgedType: BridgedType
get() = BridgedType.NATIVE_PTR
override fun cFromBridged(expr: NativeExpression, scope: NativeScope, nativeBacked: NativeBacked) =
"(${getPointerTypeStringRepresentation(cPointee)})$expr"
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.cPointerVarOf.typeWith(valueType)
}
class ObjCPointerInfo(val kotlinType: KotlinType, val type: ObjCPointer) : TypeInfo() {
override fun argToBridged(expr: String) = "$expr.objcPtr()"
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
"interpretObjCPointerOrNull<${kotlinType.render(scope)}>($expr)" +
if (type.isNullable) "" else "!!"
override val bridgedType: BridgedType
get() = BridgedType.OBJC_POINTER
override fun constructPointedType(valueType: KotlinType) = KotlinTypes.objCObjectVar.typeWith(valueType)
}
class ObjCBlockPointerInfo(val kotlinType: KotlinFunctionType, val type: ObjCBlockPointer) : TypeInfo() {
override val bridgedType: BridgedType
get() = BridgedType.OBJC_POINTER
// When passing Kotlin function as block pointer from Kotlin to native,
// it first gets wrapped by a holder in [argToBridged],
// and then converted to block in [cFromBridged].
override fun argToBridged(expr: KotlinExpression): KotlinExpression = "createKotlinObjectHolder($expr)"
override fun cFromBridged(
expr: NativeExpression,
scope: NativeScope,
nativeBacked: NativeBacked
): NativeExpression {
val mappingBridgeGenerator = scope.mappingBridgeGenerator
val blockParameters = type.parameterTypes.mapIndexed { index, it ->
"p$index" to it.getStringRepresentation()
}.joinToString { "${it.second} ${it.first}" }
val blockReturnType = type.returnType.getStringRepresentation()
val kniFunction = "kniFunction"
val codeBuilder = NativeCodeBuilder(scope)
return buildString {
append("({ ") // Statement expression begins.
append("id $kniFunction = $expr; ") // Note: it gets captured below.
append("($kniFunction == nil) ? nil : ")
append("(id)") // Cast the block to `id`.
append("^$blockReturnType($blockParameters) {") // Block begins.
// As block body, generate the code which simply bridges to Kotlin and calls the Kotlin function:
mappingBridgeGenerator.nativeToKotlin(
codeBuilder,
nativeBacked,
type.returnType,
type.parameterTypes.mapIndexed { index, it ->
TypedNativeValue(it, "p$index")
} + TypedNativeValue(ObjCIdType(ObjCPointer.Nullability.Nullable, emptyList()), kniFunction)
) { kotlinValues ->
val kotlinFunctionType = kotlinType.render(this.scope)
val kotlinFunction = "unwrapKotlinObjectHolder<$kotlinFunctionType>(${kotlinValues.last()})"
"$kotlinFunction(${kotlinValues.dropLast(1).joinToString()})"
}.let {
codeBuilder.out("return $it;")
}
codeBuilder.lines.joinTo(this, separator = " ")
append(" };") // Block ends.
append(" })") // Statement expression ends.
}
}
// When passing block pointer as Kotlin function from native to Kotlin,
// it is converted to Kotlin function in [cFromBridged].
override fun cToBridged(expr: NativeExpression): NativeExpression = expr
override fun argFromBridged(
expr: KotlinExpression,
scope: KotlinScope,
nativeBacked: NativeBacked
): KotlinExpression {
val mappingBridgeGenerator = scope.mappingBridgeGenerator
val funParameters = type.parameterTypes.mapIndexed { index, _ ->
"p$index" to kotlinType.parameterTypes[index]
}.joinToString { "${it.first}: ${it.second.render(scope)}" }
val funReturnType = kotlinType.returnType.render(scope)
val codeBuilder = KotlinCodeBuilder(scope)
val kniBlockPtr = "kniBlockPtr"
// Build the anonymous function expression:
val anonymousFun = buildString {
append("fun($funParameters): $funReturnType {\n") // Anonymous function begins.
// As function body, generate the code which simply bridges to native and calls the block:
mappingBridgeGenerator.kotlinToNative(
codeBuilder,
nativeBacked,
type.returnType,
type.parameterTypes.mapIndexed { index, it ->
TypedKotlinValue(it, "p$index")
} + TypedKotlinValue(PointerType(VoidType), "interpretCPointer<COpaque>($kniBlockPtr)"),
independent = true
) { nativeValues ->
val type = type
val blockType = blockTypeStringRepresentation(type)
val objCBlock = "((__bridge $blockType)${nativeValues.last()})"
"$objCBlock(${nativeValues.dropLast(1).joinToString()})"
}.let {
codeBuilder.returnResult(it)
}
codeBuilder.build().joinTo(this, separator = "\n")
append("}") // Anonymous function ends.
}
val nullOutput = if (type.isNullable) "null" else "throw NullPointerException()"
return "$expr.let { $kniBlockPtr -> if (kniBlockPtr == nativeNullPtr) $nullOutput else $anonymousFun }"
}
override fun constructPointedType(valueType: KotlinType): KotlinClassifierType {
return Classifier.topLevel("kotlinx.cinterop", "ObjCBlockVar").typeWith(valueType)
}
}
class ByRef(val pointed: KotlinType) : TypeInfo() {
override fun argToBridged(expr: String) = error(pointed)
override fun argFromBridged(expr: KotlinExpression, scope: KotlinScope, nativeBacked: NativeBacked) =
error(pointed)
override val bridgedType: BridgedType get() = error(pointed)
override fun cFromBridged(expr: NativeExpression, scope: NativeScope, nativeBacked: NativeBacked) =
error(pointed)
override fun cToBridged(expr: String) = error(pointed)
// TODO: this method must not exist.
override fun constructPointedType(valueType: KotlinType): KotlinClassifierType = error(pointed)
}
}
fun mirrorPrimitiveType(type: PrimitiveType, declarationMapper: DeclarationMapper): TypeMirror.ByValue {
val varClassName = when (type) {
is CharType -> "ByteVar"
is BoolType -> "BooleanVar"
is IntegerType -> if (declarationMapper.isMappedToSigned(type)) {
when (type.size) {
1 -> "ByteVar"
2 -> "ShortVar"
4 -> "IntVar"
8 -> "LongVar"
else -> TODO(type.toString())
}
} else {
when (type.size) {
1 -> "UByteVar"
2 -> "UShortVar"
4 -> "UIntVar"
8 -> "ULongVar"
else -> TODO(type.toString())
}
}
is FloatingType -> when (type.size) {
4 -> "FloatVar"
8 -> "DoubleVar"
else -> TODO(type.toString())
}
is VectorType -> {
"Vector128Var"
}
else -> TODO(type.toString())
}
val varClass = Classifier.topLevel("kotlinx.cinterop", varClassName)
val varClassOf = Classifier.topLevel("kotlinx.cinterop", "${varClassName}Of")
val info = if (type is BoolType) {
TypeInfo.Boolean()
} else {
TypeInfo.Primitive(type.getBridgedType(declarationMapper), varClassOf)
}
return TypeMirror.ByValue(varClass.type, info, type.getKotlinType(declarationMapper))
}
private fun byRefTypeMirror(pointedType: KotlinClassifierType) : TypeMirror.ByRef {
val info = TypeInfo.ByRef(pointedType)
return TypeMirror.ByRef(pointedType, info)
}
fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when (type) {
is PrimitiveType -> mirrorPrimitiveType(type, declarationMapper)
is RecordType -> byRefTypeMirror(declarationMapper.getKotlinClassForPointed(type.decl).type)
is EnumType -> {
val pkg = declarationMapper.getPackageFor(type.def)
val kotlinName = declarationMapper.getKotlinNameForValue(type.def)
.let { mangleSimple(it) } // enum class requires additional mangling
when {
declarationMapper.isMappedToStrict(type.def) -> {
val bridgedType = (type.def.baseType.unwrapTypedefs() as PrimitiveType).getBridgedType(declarationMapper)
val clazz = Classifier.topLevel(pkg, kotlinName)
val info = TypeInfo.Enum(clazz, bridgedType)
TypeMirror.ByValue(clazz.nested("Var").type, info, clazz.type)
}
!type.def.isAnonymous -> {
val baseTypeMirror = mirror(declarationMapper, type.def.baseType)
TypeMirror.ByValue(
Classifier.topLevel(pkg, kotlinName + "Var").typeAbbreviation(baseTypeMirror.pointedType),
baseTypeMirror.info,
Classifier.topLevel(pkg, kotlinName).typeAbbreviation(baseTypeMirror.argType)
)
}
else -> mirror(declarationMapper, type.def.baseType)
}
}
is PointerType -> {
val pointeeType = type.pointeeType
val unwrappedPointeeType = pointeeType.unwrapTypedefs()
if (unwrappedPointeeType is VoidType) {
val info = TypeInfo.Pointer(KotlinTypes.cOpaque, pointeeType)
TypeMirror.ByValue(KotlinTypes.cOpaquePointerVar, info, KotlinTypes.cOpaquePointer)
} else if (unwrappedPointeeType is ArrayType) {
mirror(declarationMapper, pointeeType)
} else {
val pointeeMirror = mirror(declarationMapper, pointeeType)
val info = TypeInfo.Pointer(pointeeMirror.pointedType, pointeeType)
TypeMirror.ByValue(
KotlinTypes.cPointerVar.typeWith(pointeeMirror.pointedType),
info,
KotlinTypes.cPointer.typeWith(pointeeMirror.pointedType)
)
}
}
is ArrayType -> {
// TODO: array type doesn't exactly correspond neither to pointer nor to value.
val elemTypeMirror = mirror(declarationMapper, type.elemType)
if (type.elemType.unwrapTypedefs() is ArrayType) {
elemTypeMirror
} else {
val info = TypeInfo.Pointer(elemTypeMirror.pointedType, type.elemType)
TypeMirror.ByValue(
KotlinTypes.cArrayPointerVar.typeWith(elemTypeMirror.pointedType),
info,
KotlinTypes.cArrayPointer.typeWith(elemTypeMirror.pointedType)
)
}
}
is FunctionType -> byRefTypeMirror(KotlinTypes.cFunction.typeWith(getKotlinFunctionType(declarationMapper, type)))
is Typedef -> {
val baseType = mirror(declarationMapper, type.def.aliased)
val pkg = declarationMapper.getPackageFor(type.def)
val name = type.def.name
when (baseType) {
is TypeMirror.ByValue -> {
val valueType = Classifier.topLevel(pkg, name).typeAbbreviation(baseType.valueType)
val underlyingPointedType = if (baseType.info is TypeInfo.Pointer) {
KotlinTypes.cPointerVarOf.typeWith(valueType)
} else {
baseType.pointedType
}
val pointedType = Classifier.topLevel(pkg, "${name}Var").typeAbbreviation(underlyingPointedType)
TypeMirror.ByValue(
pointedType,
baseType.info,
valueType,
nullable = baseType.nullable)
}
is TypeMirror.ByRef -> TypeMirror.ByRef(
Classifier.topLevel(pkg, name).typeAbbreviation(baseType.pointedType),
baseType.info
)
}
}
is ObjCPointer -> objCPointerMirror(declarationMapper, type)
else -> TODO(type.toString())
}
internal tailrec fun ObjCClass.isNSStringOrSubclass(): Boolean = when (this.name) {
"NSMutableString", // fast path and handling for forward declarations.
"NSString" -> true
else -> {
val baseClass = this.baseClass
if (baseClass != null) {
baseClass.isNSStringOrSubclass()
} else {
false
}
}
}
internal fun ObjCClass.isNSStringSubclass(): Boolean = this.baseClass?.isNSStringOrSubclass() == true
private fun objCPointerMirror(declarationMapper: DeclarationMapper, type: ObjCPointer): TypeMirror.ByValue {
if (type is ObjCObjectPointer && type.def.isNSStringOrSubclass()) {
val valueType = KotlinTypes.string
return objCMirror(valueType, TypeInfo.ObjCPointerInfo(valueType, type), type.isNullable)
}
val valueType = when (type) {
is ObjCIdType -> {
type.protocols.firstOrNull()?.let { declarationMapper.getKotlinClassFor(it) }?.type
?: KotlinTypes.any
}
is ObjCClassPointer -> KotlinTypes.objCClass.type
is ObjCObjectPointer -> {
when (type.def.name) {
"NSArray" -> KotlinTypes.list.typeWith(StarProjection)
"NSMutableArray" -> KotlinTypes.mutableList.typeWith(KotlinTypes.any.makeNullable())
"NSSet" -> KotlinTypes.set.typeWith(StarProjection)
"NSDictionary" -> KotlinTypes.map.typeWith(KotlinTypes.any.makeNullable(), StarProjection)
else -> declarationMapper.getKotlinClassFor(type.def).type
}
}
is ObjCInstanceType -> TODO(type.toString()) // Must have already been handled.
is ObjCBlockPointer -> return objCBlockPointerMirror(declarationMapper, type)
}
return objCMirror(valueType, TypeInfo.ObjCPointerInfo(valueType, type), type.isNullable)
}
private fun objCBlockPointerMirror(declarationMapper: DeclarationMapper, type: ObjCBlockPointer): TypeMirror.ByValue {
val returnType = if (type.returnType.unwrapTypedefs() is VoidType) {
KotlinTypes.unit
} else {
mirror(declarationMapper, type.returnType).argType
}
val kotlinType = KotlinFunctionType(
type.parameterTypes.map { mirror(declarationMapper, it).argType },
returnType
)
val info = TypeInfo.ObjCBlockPointerInfo(kotlinType, type)
return objCMirror(kotlinType, info, type.isNullable)
}
private fun objCMirror(valueType: KotlinType, info: TypeInfo, nullable: Boolean) = TypeMirror.ByValue(
info.constructPointedType(valueType.makeNullableAsSpecified(nullable)),
info,
valueType.makeNullable(), // All typedefs to Objective-C pointers would be nullable for simplicity
nullable
)
fun getKotlinFunctionType(declarationMapper: DeclarationMapper, type: FunctionType): KotlinFunctionType {
val returnType = if (type.returnType.unwrapTypedefs() is VoidType) {
KotlinTypes.unit
} else {
mirror(declarationMapper, type.returnType).argType
}
return KotlinFunctionType(
type.parameterTypes.map { mirror(declarationMapper, it).argType },
returnType,
nullable = false
)
}
@@ -0,0 +1,602 @@
/*
* 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.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
internal fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List<String> {
val selectorParts = this.selector.split(":")
val result = mutableListOf<String>()
fun String.mangled(): String {
var mangled = this
while (mangled in result) {
mangled = "_$mangled"
}
return mangled
}
// The names of all parameters except first must depend only on the selector:
this.parameters.forEachIndexed { index, _ ->
if (index > 0) {
val name = selectorParts[index].takeIf { it.isNotEmpty() } ?: "_$index"
result.add(name.mangled())
}
}
this.parameters.firstOrNull()?.let {
val name = this.getFirstKotlinParameterNameCandidate(forConstructorOrFactory)
result.add(0, name.mangled())
}
if (this.isVariadic) {
result.add("args".mangled())
}
return result
}
private fun ObjCMethod.getFirstKotlinParameterNameCandidate(forConstructorOrFactory: Boolean): String {
if (forConstructorOrFactory) {
val selectorPart = this.selector.takeWhile { it != ':' }.trimStart('_')
if (selectorPart.startsWith("init")) {
selectorPart.removePrefix("init").removePrefix("With")
.takeIf { it.isNotEmpty() }?.let { return it.decapitalize() }
}
}
return this.parameters.first().name?.takeIf { it.isNotEmpty() } ?: "_0"
}
private fun ObjCMethod.getKotlinParameters(
stubIrBuilder: StubsBuildingContext,
forConstructorOrFactory: Boolean
): List<FunctionParameterStub> {
if (this.isInit && this.parameters.isEmpty() && this.selector != "init") {
// Create synthetic Unit parameter, just like Swift does in this case:
val parameterName = this.selector.removePrefix("init").removePrefix("With").decapitalize()
return listOf(FunctionParameterStub(parameterName, KotlinTypes.unit.toStubIrType()))
// Note: this parameter is explicitly handled in compiler.
}
val names = getKotlinParameterNames(forConstructorOrFactory) // TODO: consider refactoring.
val result = mutableListOf<FunctionParameterStub>()
this.parameters.mapIndexedTo(result) { index, it ->
val kotlinType = stubIrBuilder.mirror(it.type).argType
val name = names[index]
val annotations = if (it.nsConsumed) listOf(AnnotationStub.ObjC.Consumed) else emptyList()
FunctionParameterStub(name, kotlinType.toStubIrType(), isVararg = false, annotations = annotations)
}
if (this.isVariadic) {
result += FunctionParameterStub(
names.last(),
KotlinTypes.any.makeNullable().toStubIrType(),
isVararg = true,
annotations = emptyList()
)
}
return result
}
private class ObjCMethodStubBuilder(
private val method: ObjCMethod,
private val container: ObjCContainer,
private val isDesignatedInitializer: Boolean,
override val context: StubsBuildingContext
) : StubElementBuilder {
private val isStret: Boolean
private val stubReturnType: StubType
val annotations = mutableListOf<AnnotationStub>()
private val kotlinMethodParameters: List<FunctionParameterStub>
private val external: Boolean
private val receiver: ReceiverParameterStub?
private val name: String = method.kotlinName
private val origin = StubOrigin.ObjCMethod(method, container)
private val modality: MemberStubModality
private val isOverride: Boolean =
container is ObjCClassOrProtocol && method.isOverride(container)
init {
val returnType = method.getReturnType(container.classOrProtocol)
isStret = returnType.isStret(context.configuration.target)
stubReturnType = if (returnType.unwrapTypedefs() is VoidType) {
KotlinTypes.unit
} else {
context.mirror(returnType).argType
}.toStubIrType()
val methodAnnotation = AnnotationStub.ObjC.Method(
method.selector,
method.encoding,
isStret
)
annotations += buildObjCMethodAnnotations(methodAnnotation)
kotlinMethodParameters = method.getKotlinParameters(context, forConstructorOrFactory = false)
external = (container !is ObjCProtocol)
modality = when (container) {
is ObjCClass -> MemberStubModality.OPEN
is ObjCProtocol -> if (method.isOptional) MemberStubModality.OPEN else MemberStubModality.ABSTRACT
is ObjCCategory -> MemberStubModality.FINAL
}
receiver = if (container is ObjCCategory) {
val receiverType = ClassifierStubType(context.getKotlinClassFor(container.clazz, isMeta = method.isClass))
ReceiverParameterStub(receiverType)
} else null
}
private fun buildObjCMethodAnnotations(main: AnnotationStub): List<AnnotationStub> = listOfNotNull(
main,
AnnotationStub.ObjC.ConsumesReceiver.takeIf { method.nsConsumesSelf },
AnnotationStub.ObjC.ReturnsRetained.takeIf { method.nsReturnsRetained }
)
fun isDefaultConstructor(): Boolean =
method.isInit && method.parameters.isEmpty()
override fun build(): List<FunctionalStub> {
val replacement = if (method.isInit) {
val parameters = method.getKotlinParameters(context, forConstructorOrFactory = true)
when (container) {
is ObjCClass -> {
annotations.add(0, deprecatedInit(
container.kotlinClassName(method.isClass),
kotlinMethodParameters.map { it.name },
factory = false
))
val designated = isDesignatedInitializer ||
context.configuration.disableDesignatedInitializerChecks
val annotations = listOf(AnnotationStub.ObjC.Constructor(method.selector, designated))
val constructor = ConstructorStub(parameters, annotations, isPrimary = false, origin = origin)
constructor
}
is ObjCCategory -> {
assert(!method.isClass)
val clazz = context.getKotlinClassFor(container.clazz, isMeta = false).type
annotations.add(0, deprecatedInit(
clazz.classifier.getRelativeFqName(),
kotlinMethodParameters.map { it.name },
factory = true
))
val factoryAnnotation = AnnotationStub.ObjC.Factory(
method.selector,
method.encoding,
isStret
)
val annotations = buildObjCMethodAnnotations(factoryAnnotation)
val originalReturnType = method.getReturnType(container.clazz)
val typeParameter = TypeParameterStub("T", clazz.toStubIrType())
val returnType = if (originalReturnType is ObjCPointer) {
typeParameter.getStubType(originalReturnType.isNullable)
} else {
// This shouldn't happen actually.
this.stubReturnType
}
val typeArgument = TypeArgumentStub(typeParameter.getStubType(false))
val receiverType = ClassifierStubType(KotlinTypes.objCClassOf, listOf(typeArgument))
val receiver = ReceiverParameterStub(receiverType)
val createMethod = FunctionStub(
"create",
returnType,
parameters,
receiver = receiver,
typeParameters = listOf(typeParameter),
external = true,
origin = StubOrigin.ObjCCategoryInitMethod(method),
annotations = annotations,
modality = MemberStubModality.FINAL
)
createMethod
}
is ObjCProtocol -> null
}
} else {
null
}
return listOfNotNull(
FunctionStub(
name,
stubReturnType,
kotlinMethodParameters.toList(),
origin,
annotations.toList(),
external,
receiver,
modality,
emptyList(),
isOverride),
replacement
)
}
}
internal val ObjCContainer.classOrProtocol: ObjCClassOrProtocol
get() = when (this) {
is ObjCClassOrProtocol -> this
is ObjCCategory -> this.clazz
}
private fun deprecatedInit(className: String, initParameterNames: List<String>, factory: Boolean): AnnotationStub {
val replacement = if (factory) "$className.create" else className
val replacementKind = if (factory) "factory method" else "constructor"
val replaceWith = "$replacement(${initParameterNames.joinToString { it.asSimpleName() }})"
return AnnotationStub.Deprecated("Use $replacementKind instead", replaceWith, DeprecationLevel.ERROR)
}
internal val ObjCMethod.kotlinName: String
get() {
val candidate = selector.split(":").first()
val trimmed = candidate.trimEnd('_')
return if (trimmed == "equals" && parameters.size == 1
|| (trimmed == "hashCode" || trimmed == "toString") && parameters.size == 0) {
candidate + "_"
} else {
candidate
}
}
internal val ObjCClassOrProtocol.protocolsWithSupers: Sequence<ObjCProtocol>
get() = this.protocols.asSequence().flatMap { sequenceOf(it) + it.protocolsWithSupers }
internal val ObjCClassOrProtocol.immediateSuperTypes: Sequence<ObjCClassOrProtocol>
get() {
val baseClass = (this as? ObjCClass)?.baseClass
if (baseClass != null) {
return sequenceOf(baseClass) + this.protocols.asSequence()
}
return this.protocols.asSequence()
}
internal val ObjCClassOrProtocol.selfAndSuperTypes: Sequence<ObjCClassOrProtocol>
get() = sequenceOf(this) + this.superTypes
internal val ObjCClassOrProtocol.superTypes: Sequence<ObjCClassOrProtocol>
get() = this.immediateSuperTypes.flatMap { it.selfAndSuperTypes }.distinct()
internal fun ObjCClassOrProtocol.declaredMethods(isClass: Boolean): Sequence<ObjCMethod> =
this.methods.asSequence().filter { it.isClass == isClass }
@Suppress("UNUSED_PARAMETER")
internal fun Sequence<ObjCMethod>.inheritedTo(container: ObjCClassOrProtocol, isMeta: Boolean): Sequence<ObjCMethod> =
this // TODO: exclude methods that are marked as unavailable in [container].
internal fun ObjCClassOrProtocol.inheritedMethods(isClass: Boolean): Sequence<ObjCMethod> =
this.immediateSuperTypes.flatMap { it.methodsWithInherited(isClass) }
.distinctBy { it.selector }
.inheritedTo(this, isClass)
internal fun ObjCClassOrProtocol.methodsWithInherited(isClass: Boolean): Sequence<ObjCMethod> =
(this.declaredMethods(isClass) + this.inheritedMethods(isClass)).distinctBy { it.selector }
internal fun ObjCClass.getDesignatedInitializerSelectors(result: MutableSet<String>): Set<String> {
// Note: Objective-C initializers act as usual methods and thus are inherited by subclasses.
// Swift considers all super initializers to be available (unless otherwise specified explicitly),
// but seems to consider them as non-designated if class declares its own ones explicitly.
// Simulate the similar behaviour:
val explicitlyDesignatedInitializers = this.methods.filter { it.isExplicitlyDesignatedInitializer && !it.isClass }
if (explicitlyDesignatedInitializers.isNotEmpty()) {
explicitlyDesignatedInitializers.mapTo(result) { it.selector }
} else {
this.declaredMethods(isClass = false).filter { it.isInit }.mapTo(result) { it.selector }
this.baseClass?.getDesignatedInitializerSelectors(result)
}
this.superTypes.filterIsInstance<ObjCProtocol>()
.flatMap { it.declaredMethods(isClass = false) }.filter { it.isInit }
.mapTo(result) { it.selector }
return result
}
internal fun ObjCMethod.isOverride(container: ObjCClassOrProtocol): Boolean =
container.superTypes.any { superType -> superType.methods.any(this::replaces) }
internal abstract class ObjCContainerStubBuilder(
final override val context: StubsBuildingContext,
private val container: ObjCClassOrProtocol,
protected val metaContainerStub: ObjCContainerStubBuilder?
) : StubElementBuilder {
private val isMeta: Boolean get() = metaContainerStub == null
private val designatedInitializerSelectors = if (container is ObjCClass && !isMeta) {
container.getDesignatedInitializerSelectors(mutableSetOf())
} else {
emptySet()
}
private val methods: List<ObjCMethod>
private val properties: List<ObjCProperty>
private val protocolGetter: String?
init {
val superMethods = container.inheritedMethods(isMeta)
// Add all methods declared in the class or protocol:
var methods = container.declaredMethods(isMeta)
// Exclude those which are identically declared in super types:
methods -= superMethods
// Add some special methods from super types:
methods += superMethods.filter { it.returnsInstancetype() || it.isInit }
// Add methods from adopted protocols that must be implemented according to Kotlin rules:
if (container is ObjCClass) {
methods += container.protocolsWithSupers.flatMap { it.declaredMethods(isMeta) }.filter { !it.isOptional }
}
// Add methods inherited from multiple supertypes that must be defined according to Kotlin rules:
methods += container.immediateSuperTypes
.flatMap { superType ->
val methodsWithInherited = superType.methodsWithInherited(isMeta).inheritedTo(container, isMeta)
// Select only those which are represented as non-abstract in Kotlin:
when (superType) {
is ObjCClass -> methodsWithInherited
is ObjCProtocol -> methodsWithInherited.filter { it.isOptional }
}
}
.groupBy { it.selector }
.mapNotNull { (_, inheritedMethods) -> if (inheritedMethods.size > 1) inheritedMethods.first() else null }
this.methods = methods.distinctBy { it.selector }.toList()
this.properties = container.properties.filter { property ->
property.getter.isClass == isMeta &&
// Select only properties that don't override anything:
superMethods.none { property.getter.replaces(it) || property.setter?.replaces(it) ?: false }
}
}
private val methodToStub = methods.map {
it to ObjCMethodStubBuilder(it, container, it.selector in designatedInitializerSelectors, context)
}.toMap()
private val propertyBuilders = properties.mapNotNull {
createObjCPropertyBuilder(context, it, container, this.methodToStub)
}
private val modality = when (container) {
is ObjCClass -> ClassStubModality.OPEN
is ObjCProtocol -> ClassStubModality.INTERFACE
}
private val classifier = context.getKotlinClassFor(container, isMeta)
private val externalObjCAnnotation = when (container) {
is ObjCProtocol -> {
protocolGetter = if (metaContainerStub != null) {
metaContainerStub.protocolGetter!!
} else {
// TODO: handle the case when protocol getter stub can't be compiled.
context.generateNextUniqueId("kniprot_")
}
AnnotationStub.ObjC.ExternalClass(protocolGetter)
}
is ObjCClass -> {
protocolGetter = null
val binaryName = container.binaryName
AnnotationStub.ObjC.ExternalClass("", binaryName ?: "")
}
}
private val interfaces: List<StubType> by lazy {
val interfaces = mutableListOf<StubType>()
if (container is ObjCClass) {
val baseClass = container.baseClass
val baseClassifier = if (baseClass != null) {
context.getKotlinClassFor(baseClass, isMeta)
} else {
if (isMeta) KotlinTypes.objCObjectBaseMeta else KotlinTypes.objCObjectBase
}
interfaces += baseClassifier.type.toStubIrType()
}
container.protocols.forEach {
interfaces += context.getKotlinClassFor(it, isMeta).type.toStubIrType()
}
if (interfaces.isEmpty()) {
assert(container is ObjCProtocol)
val classifier = if (isMeta) KotlinTypes.objCObjectMeta else KotlinTypes.objCObject
interfaces += classifier.type.toStubIrType()
}
if (!isMeta && container.isProtocolClass()) {
// TODO: map Protocol type to ObjCProtocol instead.
interfaces += KotlinTypes.objCProtocol.type.toStubIrType()
}
interfaces
}
private fun buildBody(): Pair<List<PropertyStub>, List<FunctionalStub>> {
val defaultConstructor = if (container is ObjCClass && methodToStub.values.none { it.isDefaultConstructor() }) {
// Always generate default constructor.
// If it is not produced for an init method, then include it manually:
ConstructorStub(
isPrimary = false,
visibility = VisibilityModifier.PROTECTED,
origin = StubOrigin.Synthetic.DefaultConstructor)
} else null
return Pair(
propertyBuilders.flatMap { it.build() },
methodToStub.values.flatMap { it.build() } + listOfNotNull(defaultConstructor)
)
}
protected fun buildClassStub(origin: StubOrigin, companion: ClassStub.Companion? = null): ClassStub {
val (properties, methods) = buildBody()
return ClassStub.Simple(
classifier,
properties = properties,
methods = methods.filterIsInstance<FunctionStub>(),
constructors = methods.filterIsInstance<ConstructorStub>(),
origin = origin,
modality = modality,
annotations = listOf(externalObjCAnnotation),
interfaces = interfaces,
companion = companion
)
}
}
internal sealed class ObjCClassOrProtocolStubBuilder(
context: StubsBuildingContext,
private val container: ObjCClassOrProtocol
) : ObjCContainerStubBuilder(
context,
container,
metaContainerStub = object : ObjCContainerStubBuilder(context, container, metaContainerStub = null) {
override fun build(): List<StubIrElement> {
val origin = when (container) {
is ObjCProtocol -> StubOrigin.ObjCProtocol(container, isMeta = true)
is ObjCClass -> StubOrigin.ObjCClass(container, isMeta = true)
}
return listOf(buildClassStub(origin))
}
}
)
internal class ObjCProtocolStubBuilder(
context: StubsBuildingContext,
private val protocol: ObjCProtocol
) : ObjCClassOrProtocolStubBuilder(context, protocol), StubElementBuilder {
override fun build(): List<StubIrElement> {
val classStub = buildClassStub(StubOrigin.ObjCProtocol(protocol, isMeta = false))
return listOf(*metaContainerStub!!.build().toTypedArray(), classStub)
}
}
internal class ObjCClassStubBuilder(
context: StubsBuildingContext,
private val clazz: ObjCClass
) : ObjCClassOrProtocolStubBuilder(context, clazz), StubElementBuilder {
override fun build(): List<StubIrElement> {
val companionSuper = ClassifierStubType(context.getKotlinClassFor(clazz, isMeta = true))
val objCClassType = KotlinTypes.objCClassOf.typeWith(
context.getKotlinClassFor(clazz, isMeta = false).type
).toStubIrType()
val superClassInit = SuperClassInit(companionSuper)
val companionClassifier = context.getKotlinClassFor(clazz, isMeta = false).nested("Companion")
val companion = ClassStub.Companion(companionClassifier, emptyList(), superClassInit, listOf(objCClassType))
val classStub = buildClassStub(StubOrigin.ObjCClass(clazz, isMeta = false), companion)
return listOf(*metaContainerStub!!.build().toTypedArray(), classStub)
}
}
class GeneratedObjCCategoriesMembers {
private val propertyNames = mutableSetOf<String>()
private val instanceMethodSelectors = mutableSetOf<String>()
private val classMethodSelectors = mutableSetOf<String>()
fun register(method: ObjCMethod): Boolean =
(if (method.isClass) classMethodSelectors else instanceMethodSelectors).add(method.selector)
fun register(property: ObjCProperty): Boolean = propertyNames.add(property.name)
}
internal class ObjCCategoryStubBuilder(
override val context: StubsBuildingContext,
private val category: ObjCCategory
) : StubElementBuilder {
private val generatedMembers = context.generatedObjCCategoriesMembers
.getOrPut(category.clazz, { GeneratedObjCCategoriesMembers() })
private val methodToBuilder = category.methods.filter { generatedMembers.register(it) }.map {
it to ObjCMethodStubBuilder(it, category, isDesignatedInitializer = false, context = context)
}.toMap()
private val methodBuilders get() = methodToBuilder.values
private val propertyBuilders = category.properties.filter { generatedMembers.register(it) }.mapNotNull {
createObjCPropertyBuilder(context, it, category, methodToBuilder)
}
override fun build(): List<StubIrElement> {
val description = "${category.clazz.name} (${category.name})"
val meta = StubContainerMeta(
"// @interface $description",
"// @end; // $description"
)
val container = SimpleStubContainer(
meta = meta,
functions = methodBuilders.flatMap { it.build() },
properties = propertyBuilders.flatMap { it.build() }
)
return listOf(container)
}
}
private fun createObjCPropertyBuilder(
context: StubsBuildingContext,
property: ObjCProperty,
container: ObjCContainer,
methodToStub: Map<ObjCMethod, ObjCMethodStubBuilder>
): ObjCPropertyStubBuilder? {
// Note: the code below assumes that if the property is generated,
// then its accessors are also generated as explicit methods.
val getterStub = methodToStub[property.getter] ?: return null
val setterStub = property.setter?.let { methodToStub[it] ?: return null }
return ObjCPropertyStubBuilder(context, property, container, getterStub, setterStub)
}
private class ObjCPropertyStubBuilder(
override val context: StubsBuildingContext,
private val property: ObjCProperty,
private val container: ObjCContainer,
private val getterBuilder: ObjCMethodStubBuilder,
private val setterMethod: ObjCMethodStubBuilder?
) : StubElementBuilder {
override fun build(): List<PropertyStub> {
val type = property.getType(container.classOrProtocol)
val kotlinType = context.mirror(type).argType
val getter = PropertyAccessor.Getter.ExternalGetter(annotations = getterBuilder.annotations)
val setter = property.setter?.let { PropertyAccessor.Setter.ExternalSetter(annotations = setterMethod!!.annotations) }
val kind = setter?.let { PropertyStub.Kind.Var(getter, it) } ?: PropertyStub.Kind.Val(getter)
val modality = MemberStubModality.FINAL
val receiver = when (container) {
is ObjCClassOrProtocol -> null
is ObjCCategory -> ClassifierStubType(context.getKotlinClassFor(container.clazz, isMeta = property.getter.isClass))
}
val origin = StubOrigin.ObjCProperty(property, container)
return listOf(PropertyStub(mangleSimple(property.name), kotlinType.toStubIrType(), kind, modality, receiver, origin = origin))
}
}
fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String {
val baseClassName = when (this) {
is ObjCClass -> this.name
is ObjCProtocol -> "${this.name}Protocol"
}
return if (isMeta) "${baseClassName}Meta" else baseClassName
}
internal fun ObjCClassOrProtocol.isProtocolClass(): Boolean = when (this) {
is ObjCClass -> (name == "Protocol" || binaryName == "Protocol")
is ObjCProtocol -> false
}
@@ -0,0 +1,101 @@
/*
* 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.native.interop.gen
/**
* The type which has exact counterparts on both Kotlin and native side and can be directly passed through bridges.
*/
enum class BridgedType(val kotlinType: KotlinClassifierType, val convertor: String? = null) {
BYTE(KotlinTypes.byte, "toByte"),
SHORT(KotlinTypes.short, "toShort"),
INT(KotlinTypes.int, "toInt"),
LONG(KotlinTypes.long, "toLong"),
UBYTE(KotlinTypes.uByte, "toUByte"),
USHORT(KotlinTypes.uShort, "toUShort"),
UINT(KotlinTypes.uInt, "toUInt"),
ULONG(KotlinTypes.uLong, "toULong"),
FLOAT(KotlinTypes.float, "toFloat"),
DOUBLE(KotlinTypes.double, "toDouble"),
VECTOR128(KotlinTypes.vector128),
NATIVE_PTR(KotlinTypes.nativePtr),
OBJC_POINTER(KotlinTypes.nativePtr),
VOID(KotlinTypes.unit)
}
data class BridgeTypedKotlinValue(val type: BridgedType, val value: KotlinExpression)
data class BridgeTypedNativeValue(val type: BridgedType, val value: NativeExpression)
/**
* The entity which depends on native bridges.
*/
interface NativeBacked
/**
* Generates simple bridges between Kotlin and native, passing [BridgedType] values.
*/
interface SimpleBridgeGenerator {
val topLevelNativeScope: NativeScope
/**
* Generates the expression to convert given Kotlin values to native counterparts, pass through the bridge,
* use inside the native code produced by [block] and then return the result back.
*
* @param block produces native code lines into the builder and returns the expression to be used as the result.
*/
fun kotlinToNative(
nativeBacked: NativeBacked,
returnType: BridgedType,
kotlinValues: List<BridgeTypedKotlinValue>,
independent: Boolean,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression
/**
* Generates the expression to convert given native values to Kotlin counterparts, pass through the bridge,
* use inside the Kotlin code produced by [block] and then return the result back.
*/
fun nativeToKotlin(
nativeBacked: NativeBacked,
returnType: BridgedType,
nativeValues: List<BridgeTypedNativeValue>,
block: KotlinCodeBuilder.(kotlinValues: List<KotlinExpression>) -> KotlinExpression
): NativeExpression
fun insertNativeBridge(
nativeBacked: NativeBacked,
kotlinLines: List<String>,
nativeLines: List<String>
)
/**
* Prepares all requested native bridges.
*/
fun prepare(): NativeBridges
}
interface NativeBridges {
/**
* @return `true` iff given entity is supported by these bridges,
* i.e. all bridges it depends on can be successfully generated.
*/
fun isSupported(nativeBacked: NativeBacked): Boolean
val kotlinLines: Sequence<String>
val nativeLines: Sequence<String>
}
@@ -0,0 +1,253 @@
/*
* 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.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.CompilationWithPCH
import org.jetbrains.kotlin.native.interop.indexer.Language
import org.jetbrains.kotlin.native.interop.indexer.mapFragmentIsCompilable
internal val INVALID_CLANG_IDENTIFIER_REGEX = "[^a-zA-Z1-9_]".toRegex()
class SimpleBridgeGeneratorImpl(
private val platform: KotlinPlatform,
private val pkgName: String,
private val jvmFileClassName: String,
private val libraryForCStubs: CompilationWithPCH,
override val topLevelNativeScope: NativeScope,
private val topLevelKotlinScope: KotlinScope
) : SimpleBridgeGenerator {
private var nextUniqueId = 0
private val BridgedType.nativeType: String get() = when (platform) {
KotlinPlatform.JVM -> when (this) {
BridgedType.BYTE -> "jbyte"
BridgedType.SHORT -> "jshort"
BridgedType.INT -> "jint"
BridgedType.LONG -> "jlong"
BridgedType.UBYTE -> "jbyte"
BridgedType.USHORT -> "jshort"
BridgedType.UINT -> "jint"
BridgedType.ULONG -> "jlong"
BridgedType.FLOAT -> "jfloat"
BridgedType.DOUBLE -> "jdouble"
BridgedType.VECTOR128 -> TODO()
BridgedType.NATIVE_PTR -> "jlong"
BridgedType.OBJC_POINTER -> TODO()
BridgedType.VOID -> "void"
}
KotlinPlatform.NATIVE -> when (this) {
BridgedType.BYTE -> "int8_t"
BridgedType.SHORT -> "int16_t"
BridgedType.INT -> "int32_t"
BridgedType.LONG -> "int64_t"
BridgedType.UBYTE -> "uint8_t"
BridgedType.USHORT -> "uint16_t"
BridgedType.UINT -> "uint32_t"
BridgedType.ULONG -> "uint64_t"
BridgedType.FLOAT -> "float"
BridgedType.DOUBLE -> "double"
BridgedType.VECTOR128 -> TODO() // "float __attribute__ ((__vector_size__ (16)))"
BridgedType.NATIVE_PTR -> "void*"
BridgedType.OBJC_POINTER -> "id"
BridgedType.VOID -> "void"
}
}
private inner class NativeBridge(val kotlinLines: List<String>, val nativeLines: List<String>)
override fun kotlinToNative(
nativeBacked: NativeBacked,
returnType: BridgedType,
kotlinValues: List<BridgeTypedKotlinValue>,
independent: Boolean,
block: NativeCodeBuilder.(nativeValues: List<NativeExpression>) -> NativeExpression
): KotlinExpression {
val kotlinLines = mutableListOf<String>()
val nativeLines = mutableListOf<String>()
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
val kotlinParameters = kotlinValues.withIndex().joinToString {
"p${it.index}: ${it.value.type.kotlinType.render(topLevelKotlinScope)}"
}
val callExpr = "$kotlinFunctionName(${kotlinValues.joinToString { it.value }})"
val cFunctionParameters = when (platform) {
KotlinPlatform.JVM -> mutableListOf(
"jniEnv" to "JNIEnv*",
"jclss" to "jclass"
)
KotlinPlatform.NATIVE -> mutableListOf()
}
kotlinValues.withIndex().mapTo(cFunctionParameters) {
"p${it.index}" to it.value.type.nativeType
}
val joinedCParameters = cFunctionParameters.joinToString { (name, type) -> "$type $name" }
val cReturnType = returnType.nativeType
val cFunctionHeader = when (platform) {
KotlinPlatform.JVM -> {
val funcFullName = buildString {
if (pkgName.isNotEmpty()) {
append(pkgName)
append('.')
}
append(jvmFileClassName)
append('.')
append(kotlinFunctionName)
}
val functionName = "Java_" + funcFullName.replace("_", "_1").replace('.', '_').replace("$", "_00024")
"JNIEXPORT $cReturnType JNICALL $functionName ($joinedCParameters)"
}
KotlinPlatform.NATIVE -> {
val functionName = pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_") + "_$kotlinFunctionName"
if (independent) kotlinLines.add("@" + topLevelKotlinScope.reference(KotlinTypes.independent))
kotlinLines.add("@SymbolName(${functionName.quoteAsKotlinLiteral()})")
"$cReturnType $functionName ($joinedCParameters)"
}
}
nativeLines.add(cFunctionHeader + " {")
buildNativeCodeLines(topLevelNativeScope) {
val cExpr = block(cFunctionParameters.takeLast(kotlinValues.size).map { (name, _) -> name })
if (returnType != BridgedType.VOID) {
out("return ($cReturnType)$cExpr;")
}
}.forEach {
nativeLines.add(" $it")
}
if (libraryForCStubs.language == Language.OBJECTIVE_C) {
// Prevent Objective-C exceptions from passing to Kotlin:
nativeLines.add(1, "@try {")
nativeLines.add("} @catch (id e) { objc_terminate(); }")
// 'objc_terminate' will report the exception.
// TODO: consider implementing this in bitcode generator.
}
nativeLines.add("}")
val kotlinReturnType = returnType.kotlinType.render(topLevelKotlinScope)
kotlinLines.add("private external fun $kotlinFunctionName($kotlinParameters): $kotlinReturnType")
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
nativeBridges.add(nativeBacked to nativeBridge)
return callExpr
}
override fun nativeToKotlin(
nativeBacked: NativeBacked,
returnType: BridgedType,
nativeValues: List<BridgeTypedNativeValue>,
block: KotlinCodeBuilder.(arguments: List<KotlinExpression>) -> KotlinExpression
): NativeExpression {
if (platform != KotlinPlatform.NATIVE) TODO()
val kotlinLines = mutableListOf<String>()
val nativeLines = mutableListOf<String>()
val kotlinFunctionName = "kniBridge${nextUniqueId++}"
val kotlinParameters = nativeValues.withIndex().map {
"p${it.index}" to it.value.type.kotlinType
}
val joinedKotlinParameters = kotlinParameters.joinToString {
"${it.first}: ${it.second.render(topLevelKotlinScope)}"
}
val cFunctionParameters = nativeValues.withIndex().map {
"p${it.index}" to it.value.type.nativeType
}
val joinedCParameters = cFunctionParameters.joinToString { (name, type) -> "$type $name" }
val cReturnType = returnType.nativeType
val symbolName = pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_") + "_$kotlinFunctionName"
kotlinLines.add("@kotlin.native.internal.ExportForCppRuntime(${symbolName.quoteAsKotlinLiteral()})")
val cFunctionHeader = "$cReturnType $symbolName($joinedCParameters)"
nativeLines.add("$cFunctionHeader;")
val kotlinReturnType = returnType.kotlinType.render(topLevelKotlinScope)
kotlinLines.add("private fun $kotlinFunctionName($joinedKotlinParameters): $kotlinReturnType {")
buildKotlinCodeLines(topLevelKotlinScope) {
var kotlinExpr = block(kotlinParameters.map { (name, _) -> name })
if (returnType == BridgedType.OBJC_POINTER) {
// The Kotlin code may lose the ownership on this pointer after returning from the bridge,
// so retain the pointer and autorelease it:
kotlinExpr = "objc_retainAutoreleaseReturnValue($kotlinExpr)"
// (Objective-C does the same for returned pointers).
}
returnResult(kotlinExpr)
}.forEach {
kotlinLines.add(" $it")
}
kotlinLines.add("}")
insertNativeBridge(nativeBacked, kotlinLines, nativeLines)
return "$symbolName(${nativeValues.joinToString { it.value }})"
}
override fun insertNativeBridge(nativeBacked: NativeBacked, kotlinLines: List<String>, nativeLines: List<String>) {
val nativeBridge = NativeBridge(kotlinLines, nativeLines)
nativeBridges.add(nativeBacked to nativeBridge)
}
private val nativeBridges = mutableListOf<Pair<NativeBacked, NativeBridge>>()
override fun prepare(): NativeBridges {
val includedBridges = mutableListOf<NativeBridge>()
val excludedClients = mutableSetOf<NativeBacked>()
nativeBridges.map { it.second.nativeLines }
.mapFragmentIsCompilable(libraryForCStubs)
.forEachIndexed { index, isCompilable ->
if (!isCompilable) {
excludedClients.add(nativeBridges[index].first)
}
}
nativeBridges.mapNotNullTo(includedBridges) { (nativeBacked, nativeBridge) ->
if (nativeBacked in excludedClients) {
null
} else {
nativeBridge
}
}
// TODO: exclude unused bridges.
return object : NativeBridges {
override val kotlinLines: Sequence<String>
get() = includedBridges.asSequence().flatMap { it.kotlinLines.asSequence() }
override val nativeLines: Sequence<String>
get() = includedBridges.asSequence().flatMap { it.nativeLines.asSequence() }
override fun isSupported(nativeBacked: NativeBacked): Boolean =
nativeBacked !in excludedClients
}
}
}
@@ -0,0 +1,83 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) {
StructDef.Kind.STRUCT -> tryRenderStruct(def)
StructDef.Kind.UNION -> tryRenderUnion(def)
}
private fun tryRenderStruct(def: StructDef): String? {
val isPackedStruct = def.fields.any { !it.isAligned }
var offset = 0L
return buildString {
append("struct")
if (isPackedStruct) append(" __attribute__((packed))")
append(" { ")
def.members.forEachIndexed { index, it ->
val name = "p$index"
val decl = when (it) {
is Field -> {
val defaultAlignment = if (isPackedStruct) 1L else it.typeAlign
val alignment = guessAlignment(offset, it.offsetBytes, defaultAlignment) ?: return null
offset = it.offsetBytes + it.typeSize
tryRenderVar(it.type, name)
?.plus(if (alignment == defaultAlignment) "" else "__attribute__((aligned($alignment)))")
}
is BitField, // TODO: tryRenderVar(it.type, name)?.plus(" : ${it.size}")
is IncompleteField -> null // e.g. flexible array member.
} ?: return null
append("$decl; ")
}
append("}")
}
}
private fun guessAlignment(offset: Long, paddedOffset: Long, defaultAlignment: Long): Long? =
longArrayOf(defaultAlignment, 1L, 2L, 4L, 8L, 16L, 32L).firstOrNull {
alignUp(offset, it) == paddedOffset
}
private fun alignUp(x: Long, alignment: Long): Long = (x + alignment - 1) and ((alignment - 1).inv())
private fun tryRenderUnion(def: StructDef): String? =
if (def.members.any { it.offset != 0L }) null else buildString {
append("union { ")
def.members.forEachIndexed { index, it ->
val decl = when (it) {
is Field -> tryRenderVar(it.type, "p$index")
is BitField, is IncompleteField -> null
} ?: return null
append("$decl; ")
}
append("}")
}
private fun tryRenderVar(type: Type, name: String): String? = when (type) {
CharType, is BoolType -> "char $name"
is IntegerType -> "${type.spelling} $name"
is FloatingType -> "${type.spelling} $name"
is VectorType -> "${type.spelling} $name"
is RecordType -> "${tryRenderStructOrUnion(type.decl.def!!)} $name"
is EnumType -> tryRenderVar(type.def.baseType, name)
is PointerType -> "void* $name"
is ConstArrayType -> tryRenderVar(type.elemType, "$name[${type.length}]")
is IncompleteArrayType -> tryRenderVar(type.elemType, "$name[]")
is Typedef -> tryRenderVar(type.def.aliased, name)
is ObjCPointer -> "void* $name"
else -> null
}
private val Field.offsetBytes: Long get() {
require(this.offset % 8 == 0L)
return this.offset / 8
}
@@ -0,0 +1,529 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.*
// TODO: Replace all usages of these strings with constants.
const val cinteropPackage = "kotlinx.cinterop"
const val cinteropInternalPackage = "$cinteropPackage.internal"
interface StubIrElement {
fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T): R
}
sealed class StubContainer : StubIrElement {
abstract val meta: StubContainerMeta
abstract val classes: List<ClassStub>
abstract val functions: List<FunctionalStub>
abstract val properties: List<PropertyStub>
abstract val typealiases: List<TypealiasStub>
abstract val simpleContainers: List<SimpleStubContainer>
}
/**
* Meta information about [StubContainer].
* For example, can be used for comments in textual representation.
*/
class StubContainerMeta(
val textAtStart: String = "",
val textAtEnd: String = ""
)
class SimpleStubContainer(
override val meta: StubContainerMeta = StubContainerMeta(),
override val classes: List<ClassStub> = emptyList(),
override val functions: List<FunctionalStub> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
override val typealiases: List<TypealiasStub> = emptyList(),
override val simpleContainers: List<SimpleStubContainer> = emptyList()
) : StubContainer() {
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T): R {
return visitor.visitSimpleStubContainer(this, data)
}
}
val StubContainer.children: List<StubIrElement>
get() = (classes as List<StubIrElement>) + properties + functions + typealiases
/**
* Marks that abstract value of such type can be passed as value.
*/
sealed class ValueStub
class TypeParameterStub(
val name: String,
val upperBound: StubType? = null
) {
fun getStubType(nullable: Boolean) =
TypeParameterType(name, nullable = nullable, typeParameterDeclaration = this)
}
interface TypeArgument {
object StarProjection : TypeArgument {
override fun toString(): String =
"*"
}
enum class Variance {
INVARIANT,
IN,
OUT
}
}
class TypeArgumentStub(
val type: StubType,
val variance: TypeArgument.Variance = TypeArgument.Variance.INVARIANT
) : TypeArgument {
override fun toString(): String =
type.toString()
}
/**
* Represents a source of StubIr element.
*/
sealed class StubOrigin {
/**
* Special case when element of IR was generated.
*/
sealed class Synthetic : StubOrigin() {
object CompanionObject : Synthetic()
/**
* Denotes default constructor that was generated and has no real origin.
*/
object DefaultConstructor : Synthetic()
/**
* CEnum.Companion.byValue.
*/
class EnumByValue(val enum: EnumDef) : Synthetic()
/**
* CEnum.value.
*/
class EnumValueField(val enum: EnumDef) : Synthetic()
/**
* E.CEnumVar.value.
*/
class EnumVarValueField(val enum: EnumDef) : Synthetic()
}
class ObjCCategoryInitMethod(
val method: org.jetbrains.kotlin.native.interop.indexer.ObjCMethod
) : StubOrigin()
class ObjCMethod(
val method: org.jetbrains.kotlin.native.interop.indexer.ObjCMethod,
val container: ObjCContainer
) : StubOrigin()
class ObjCProperty(
val property: org.jetbrains.kotlin.native.interop.indexer.ObjCProperty,
val container: ObjCContainer
) : StubOrigin()
class ObjCClass(
val clazz: org.jetbrains.kotlin.native.interop.indexer.ObjCClass,
val isMeta: Boolean
) : StubOrigin()
class ObjCProtocol(
val protocol: org.jetbrains.kotlin.native.interop.indexer.ObjCProtocol,
val isMeta: Boolean
) : StubOrigin()
class Enum(val enum: EnumDef) : StubOrigin()
class EnumEntry(val constant: EnumConstant) : StubOrigin()
class Function(val function: FunctionDecl) : StubOrigin()
class Struct(val struct: StructDecl) : StubOrigin()
class StructMember(
val member: org.jetbrains.kotlin.native.interop.indexer.StructMember
) : StubOrigin()
class Constant(val constantDef: ConstantDef): StubOrigin()
class Global(val global: GlobalDecl) : StubOrigin()
class TypeDef(val typedefDef: TypedefDef) : StubOrigin()
class VarOf(val typeOrigin: StubOrigin) : StubOrigin()
}
interface StubElementWithOrigin : StubIrElement {
val origin: StubOrigin
}
interface AnnotationHolder {
val annotations: List<AnnotationStub>
}
sealed class AnnotationStub(val classifier: Classifier) {
sealed class ObjC(classifier: Classifier) : AnnotationStub(classifier) {
object ConsumesReceiver :
ObjC(cCallClassifier.nested("ConsumesReceiver"))
object ReturnsRetained :
ObjC(cCallClassifier.nested("ReturnsRetained"))
class Method(val selector: String, val encoding: String, val isStret: Boolean = false) :
ObjC(Classifier.topLevel(cinteropPackage, "ObjCMethod"))
class Factory(val selector: String, val encoding: String, val isStret: Boolean = false) :
ObjC(Classifier.topLevel(cinteropPackage, "ObjCFactory"))
object Consumed :
ObjC(cCallClassifier.nested("Consumed"))
class Constructor(val selector: String, val designated: Boolean) :
ObjC(Classifier.topLevel(cinteropPackage, "ObjCConstructor"))
class ExternalClass(val protocolGetter: String = "", val binaryName: String = "") :
ObjC(Classifier.topLevel(cinteropPackage, "ExternalObjCClass"))
}
sealed class CCall(classifier: Classifier) : AnnotationStub(classifier) {
object CString : CCall(cCallClassifier.nested("CString"))
object WCString : CCall(cCallClassifier.nested("WCString"))
class Symbol(val symbolName: String) : CCall(cCallClassifier)
}
class CStruct(val struct: String) : AnnotationStub(cStructClassifier) {
class MemberAt(val offset: Long) : AnnotationStub(cStructClassifier.nested("MemberAt"))
class ArrayMemberAt(val offset: Long) : AnnotationStub(cStructClassifier.nested("ArrayMemberAt"))
class BitField(val offset: Long, val size: Int) : AnnotationStub(cStructClassifier.nested("BitField"))
class VarType(val size: Long, val align: Int) : AnnotationStub(cStructClassifier.nested("VarType"))
}
class CNaturalStruct(val members: List<StructMember>) :
AnnotationStub(Classifier.topLevel(cinteropPackage, "CNaturalStruct"))
class CLength(val length: Long) :
AnnotationStub(Classifier.topLevel(cinteropPackage, "CLength"))
class Deprecated(val message: String, val replaceWith: String, val level: DeprecationLevel) :
AnnotationStub(Classifier.topLevel("kotlin", "Deprecated")) {
companion object {
val unableToImport = Deprecated(
"Unable to import this declaration",
"",
DeprecationLevel.ERROR
)
val deprecatedCVariableCompanion = Deprecated(
"Use sizeOf<T>() or alignOf<T>() instead.",
"",
DeprecationLevel.WARNING
)
val deprecatedCEnumByValue = Deprecated(
"Will be removed.",
"",
DeprecationLevel.WARNING
)
}
}
class CEnumEntryAlias(val entryName: String) :
AnnotationStub(Classifier.topLevel(cinteropInternalPackage, "CEnumEntryAlias"))
class CEnumVarTypeSize(val size: Int) :
AnnotationStub(Classifier.topLevel(cinteropInternalPackage, "CEnumVarTypeSize"))
private companion object {
val cCallClassifier = Classifier.topLevel(cinteropInternalPackage, "CCall")
val cStructClassifier = Classifier.topLevel(cinteropInternalPackage, "CStruct")
}
}
/**
* Compile-time known values.
*/
sealed class ConstantStub : ValueStub()
class StringConstantStub(val value: String) : ConstantStub()
data class IntegralConstantStub(val value: Long, val size: Int, val isSigned: Boolean) : ConstantStub()
data class DoubleConstantStub(val value: Double, val size: Int) : ConstantStub()
data class PropertyStub(
val name: String,
val type: StubType,
val kind: Kind,
val modality: MemberStubModality = MemberStubModality.FINAL,
val receiverType: StubType? = null,
override val annotations: List<AnnotationStub> = emptyList(),
val origin: StubOrigin,
val isOverride: Boolean = false
) : StubIrElement, AnnotationHolder {
sealed class Kind {
class Val(
val getter: PropertyAccessor.Getter
) : Kind()
class Var(
val getter: PropertyAccessor.Getter,
val setter: PropertyAccessor.Setter
) : Kind()
class Constant(val constant: ConstantStub) : Kind()
}
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T): R {
return visitor.visitProperty(this, data)
}
}
enum class ClassStubModality {
INTERFACE, OPEN, ABSTRACT, NONE
}
enum class VisibilityModifier {
PRIVATE, PROTECTED, INTERNAL, PUBLIC
}
class GetConstructorParameter(
val constructorParameterStub: FunctionParameterStub
) : ValueStub()
class SuperClassInit(
val type: StubType,
val arguments: List<ValueStub> = listOf()
)
// TODO: Consider unifying these classes.
sealed class ClassStub : StubContainer(), StubElementWithOrigin, AnnotationHolder {
abstract val superClassInit: SuperClassInit?
abstract val interfaces: List<StubType>
abstract val childrenClasses: List<ClassStub>
abstract val companion : Companion?
abstract val classifier: Classifier
class Simple(
override val classifier: Classifier,
val modality: ClassStubModality,
constructors: List<ConstructorStub> = emptyList(),
methods: List<FunctionStub> = emptyList(),
override val superClassInit: SuperClassInit? = null,
override val interfaces: List<StubType> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
override val origin: StubOrigin,
override val annotations: List<AnnotationStub> = emptyList(),
override val childrenClasses: List<ClassStub> = emptyList(),
override val companion: Companion? = null,
override val simpleContainers: List<SimpleStubContainer> = emptyList()
) : ClassStub() {
override val functions: List<FunctionalStub> = constructors + methods
}
class Companion(
override val classifier: Classifier,
methods: List<FunctionStub> = emptyList(),
override val superClassInit: SuperClassInit? = null,
override val interfaces: List<StubType> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
override val origin: StubOrigin = StubOrigin.Synthetic.CompanionObject,
override val annotations: List<AnnotationStub> = emptyList(),
override val childrenClasses: List<ClassStub> = emptyList(),
override val simpleContainers: List<SimpleStubContainer> = emptyList()
) : ClassStub() {
override val companion: Companion? = null
override val functions: List<FunctionalStub> = methods
}
class Enum(
override val classifier: Classifier,
val entries: List<EnumEntryStub>,
constructors: List<ConstructorStub>,
override val superClassInit: SuperClassInit? = null,
override val interfaces: List<StubType> = emptyList(),
override val properties: List<PropertyStub> = emptyList(),
override val origin: StubOrigin,
override val annotations: List<AnnotationStub> = emptyList(),
override val childrenClasses: List<ClassStub> = emptyList(),
override val companion: Companion?= null,
override val simpleContainers: List<SimpleStubContainer> = emptyList()
) : ClassStub() {
override val functions: List<FunctionalStub> = constructors
}
override val meta: StubContainerMeta = StubContainerMeta()
override val classes: List<ClassStub>
get() = childrenClasses + listOfNotNull(companion)
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitClass(this, data)
override val typealiases: List<TypealiasStub> = emptyList()
}
class ReceiverParameterStub(
val type: StubType
)
class FunctionParameterStub(
val name: String,
val type: StubType,
override val annotations: List<AnnotationStub> = emptyList(),
val isVararg: Boolean = false
) : AnnotationHolder
enum class MemberStubModality {
OPEN,
FINAL,
ABSTRACT
}
interface FunctionalStub : AnnotationHolder, StubIrElement, NativeBacked {
val parameters: List<FunctionParameterStub>
}
sealed class PropertyAccessor : FunctionalStub {
sealed class Getter : PropertyAccessor() {
override val parameters: List<FunctionParameterStub> = emptyList()
class SimpleGetter(
override val annotations: List<AnnotationStub> = emptyList(),
val constant: ConstantStub? = null
) : Getter()
class GetConstructorParameter(
val constructorParameter: FunctionParameterStub,
override val annotations: List<AnnotationStub> = emptyList()
) : Getter()
class ExternalGetter(
override val annotations: List<AnnotationStub> = emptyList()
) : Getter()
class ArrayMemberAt(
val offset: Long
) : Getter() {
override val parameters: List<FunctionParameterStub> = emptyList()
override val annotations: List<AnnotationStub> = emptyList()
}
class MemberAt(
val offset: Long,
val typeArguments: List<TypeArgumentStub> = emptyList(),
val hasValueAccessor: Boolean
) : Getter() {
override val annotations: List<AnnotationStub> = emptyList()
}
class ReadBits(
val offset: Long,
val size: Int,
val signed: Boolean
) : Getter() {
override val annotations: List<AnnotationStub> = emptyList()
}
class InterpretPointed(val cGlobalName:String, pointedType: StubType) : Getter() {
override val annotations: List<AnnotationStub> = emptyList()
val typeParameters: List<StubType> = listOf(pointedType)
}
class GetEnumEntry(
val enumEntryStub: EnumEntryStub,
override val annotations: List<AnnotationStub> = emptyList()
) : Getter()
}
sealed class Setter : PropertyAccessor() {
override val parameters: List<FunctionParameterStub> = emptyList()
class SimpleSetter(
override val annotations: List<AnnotationStub> = emptyList()
) : Setter()
class ExternalSetter(
override val annotations: List<AnnotationStub> = emptyList()
) : Setter()
class MemberAt(
val offset: Long,
override val annotations: List<AnnotationStub> = emptyList(),
val typeArguments: List<TypeArgumentStub> = emptyList()
) : Setter()
class WriteBits(
val offset: Long,
val size: Int,
override val annotations: List<AnnotationStub> = emptyList()
) : Setter()
}
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitPropertyAccessor(this, data)
}
data class FunctionStub(
val name: String,
val returnType: StubType,
override val parameters: List<FunctionParameterStub>,
override val origin: StubOrigin,
override val annotations: List<AnnotationStub>,
val external: Boolean = false,
val receiver: ReceiverParameterStub?,
val modality: MemberStubModality,
val typeParameters: List<TypeParameterStub> = emptyList(),
val isOverride: Boolean = false,
val hasStableParameterNames: Boolean = true
) : StubElementWithOrigin, FunctionalStub {
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitFunction(this, data)
}
// TODO: should we support non-trivial constructors?
class ConstructorStub(
override val parameters: List<FunctionParameterStub> = emptyList(),
override val annotations: List<AnnotationStub> = emptyList(),
val isPrimary: Boolean,
val visibility: VisibilityModifier = VisibilityModifier.PUBLIC,
val origin: StubOrigin
) : FunctionalStub {
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitConstructor(this, data)
}
class EnumEntryStub(
val name: String,
val constant: IntegralConstantStub,
val origin: StubOrigin.EnumEntry,
val ordinal: Int
)
class TypealiasStub(
val alias: Classifier,
val aliasee: StubType,
val origin: StubOrigin
) : StubIrElement {
override fun <T, R> accept(visitor: StubIrVisitor<T, R>, data: T) =
visitor.visitTypealias(this, data)
}
@@ -0,0 +1,319 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
class BridgeBuilderResult(
val kotlinFile: KotlinFile,
val nativeBridges: NativeBridges,
val propertyAccessorBridgeBodies: Map<PropertyAccessor, String>,
val functionBridgeBodies: Map<FunctionStub, List<String>>,
val excludedStubs: Set<StubIrElement>
)
/**
* Generates [NativeBridges] and corresponding function bodies and property accessors.
*/
class StubIrBridgeBuilder(
private val context: StubIrContext,
private val builderResult: StubIrBuilderResult) {
private val globalAddressExpressions = mutableMapOf<Pair<String, PropertyAccessor>, KotlinExpression>()
private val wrapperGenerator = CWrappersGenerator(context)
private fun getGlobalAddressExpression(cGlobalName: String, accessor: PropertyAccessor) =
globalAddressExpressions.getOrPut(Pair(cGlobalName, accessor)) {
simpleBridgeGenerator.kotlinToNative(
nativeBacked = accessor,
returnType = BridgedType.NATIVE_PTR,
kotlinValues = emptyList(),
independent = false
) {
"&$cGlobalName"
}
}
private val declarationMapper = builderResult.declarationMapper
private val kotlinFile = object : KotlinFile(
context.configuration.pkgName,
namesToBeDeclared = builderResult.stubs.computeNamesToBeDeclared(context.configuration.pkgName)
) {
override val mappingBridgeGenerator: MappingBridgeGenerator
get() = this@StubIrBridgeBuilder.mappingBridgeGenerator
}
private val simpleBridgeGenerator: SimpleBridgeGenerator =
SimpleBridgeGeneratorImpl(
context.platform,
context.configuration.pkgName,
context.jvmFileClassName,
context.libraryForCStubs,
topLevelNativeScope = object : NativeScope {
override val mappingBridgeGenerator: MappingBridgeGenerator
get() = this@StubIrBridgeBuilder.mappingBridgeGenerator
},
topLevelKotlinScope = kotlinFile
)
private val mappingBridgeGenerator: MappingBridgeGenerator =
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator)
private val propertyAccessorBridgeBodies = mutableMapOf<PropertyAccessor, String>()
private val functionBridgeBodies = mutableMapOf<FunctionStub, List<String>>()
private val excludedStubs = mutableSetOf<StubIrElement>()
private val bridgeGeneratingVisitor = object : StubIrVisitor<StubContainer?, Unit> {
override fun visitClass(element: ClassStub, data: StubContainer?) {
element.annotations.filterIsInstance<AnnotationStub.ObjC.ExternalClass>().firstOrNull()?.let {
val origin = element.origin
if (it.protocolGetter.isNotEmpty() && origin is StubOrigin.ObjCProtocol && !origin.isMeta) {
val protocol = (element.origin as StubOrigin.ObjCProtocol).protocol
// TODO: handle the case when protocol getter stub can't be compiled.
generateProtocolGetter(it.protocolGetter, protocol)
}
}
element.children.forEach {
it.accept(this, element)
}
}
override fun visitTypealias(element: TypealiasStub, data: StubContainer?) {
}
override fun visitFunction(element: FunctionStub, data: StubContainer?) {
try {
when {
element.external -> tryProcessCCallAnnotation(element)
element.isOptionalObjCMethod() -> { }
element.origin is StubOrigin.Synthetic.EnumByValue -> { }
data != null && data.isInterface -> { }
else -> generateBridgeBody(element)
}
} catch (e: Throwable) {
context.log("Warning: cannot generate bridge for ${element.name}.")
excludedStubs += element
}
}
private fun tryProcessCCallAnnotation(function: FunctionStub) {
val origin = function.origin as? StubOrigin.Function
?: return
val cCallAnnotation = function.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: return
val wrapper = wrapperGenerator.generateCCalleeWrapper(origin.function, cCallAnnotation.symbolName)
simpleBridgeGenerator.insertNativeBridge(function, emptyList(), wrapper.lines)
}
override fun visitProperty(element: PropertyStub, data: StubContainer?) {
try {
when (val kind = element.kind) {
is PropertyStub.Kind.Constant -> {
}
is PropertyStub.Kind.Val -> {
visitPropertyAccessor(kind.getter, data)
}
is PropertyStub.Kind.Var -> {
visitPropertyAccessor(kind.getter, data)
visitPropertyAccessor(kind.setter, data)
}
}
} catch (e: Throwable) {
context.log("Warning: cannot generate bridge for ${element.name}.")
excludedStubs += element
}
}
override fun visitConstructor(constructorStub: ConstructorStub, data: StubContainer?) {
}
override fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: StubContainer?) {
when (propertyAccessor) {
is PropertyAccessor.Getter.SimpleGetter -> {
when (propertyAccessor) {
in builderResult.bridgeGenerationComponents.getterToBridgeInfo -> {
val extra = builderResult.bridgeGenerationComponents.getterToBridgeInfo.getValue(propertyAccessor)
val typeInfo = extra.typeInfo
propertyAccessorBridgeBodies[propertyAccessor] = typeInfo.argFromBridged(simpleBridgeGenerator.kotlinToNative(
nativeBacked = propertyAccessor,
returnType = typeInfo.bridgedType,
kotlinValues = emptyList(),
independent = false
) {
typeInfo.cToBridged(expr = extra.cGlobalName)
}, kotlinFile, nativeBacked = propertyAccessor)
}
in builderResult.bridgeGenerationComponents.arrayGetterInfo -> {
val extra = builderResult.bridgeGenerationComponents.arrayGetterInfo.getValue(propertyAccessor)
val typeInfo = extra.typeInfo
val getAddressExpression = getGlobalAddressExpression(extra.cGlobalName, propertyAccessor)
propertyAccessorBridgeBodies[propertyAccessor] = typeInfo.argFromBridged(getAddressExpression, kotlinFile, nativeBacked = propertyAccessor) + "!!"
}
}
}
is PropertyAccessor.Getter.ReadBits -> {
val extra = builderResult.bridgeGenerationComponents.getterToBridgeInfo.getValue(propertyAccessor)
val rawType = extra.typeInfo.bridgedType
val readBits = "readBits(this.rawPtr, ${propertyAccessor.offset}, ${propertyAccessor.size}, ${propertyAccessor.signed}).${rawType.convertor!!}()"
val getExpr = extra.typeInfo.argFromBridged(readBits, kotlinFile, object : NativeBacked {})
propertyAccessorBridgeBodies[propertyAccessor] = getExpr
}
is PropertyAccessor.Setter.SimpleSetter -> when (propertyAccessor) {
in builderResult.bridgeGenerationComponents.setterToBridgeInfo -> {
val extra = builderResult.bridgeGenerationComponents.setterToBridgeInfo.getValue(propertyAccessor)
val typeInfo = extra.typeInfo
val bridgedValue = BridgeTypedKotlinValue(typeInfo.bridgedType, typeInfo.argToBridged("value"))
val setter = simpleBridgeGenerator.kotlinToNative(
nativeBacked = propertyAccessor,
returnType = BridgedType.VOID,
kotlinValues = listOf(bridgedValue),
independent = false
) { nativeValues ->
out("${extra.cGlobalName} = ${typeInfo.cFromBridged(
nativeValues.single(),
scope,
nativeBacked = propertyAccessor
)};")
""
}
propertyAccessorBridgeBodies[propertyAccessor] = setter
}
}
is PropertyAccessor.Setter.WriteBits -> {
val extra = builderResult.bridgeGenerationComponents.setterToBridgeInfo.getValue(propertyAccessor)
val rawValue = extra.typeInfo.argToBridged("value")
propertyAccessorBridgeBodies[propertyAccessor] = "writeBits(this.rawPtr, ${propertyAccessor.offset}, ${propertyAccessor.size}, $rawValue.toLong())"
}
is PropertyAccessor.Getter.InterpretPointed -> {
val getAddressExpression = getGlobalAddressExpression(propertyAccessor.cGlobalName, propertyAccessor)
propertyAccessorBridgeBodies[propertyAccessor] = getAddressExpression
}
is PropertyAccessor.Getter.ExternalGetter -> {
if (propertyAccessor in builderResult.wrapperGenerationComponents.getterToWrapperInfo) {
val extra = builderResult.wrapperGenerationComponents.getterToWrapperInfo.getValue(propertyAccessor)
val cCallAnnotation = propertyAccessor.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: error("external getter for ${extra.global.name} wasn't marked with @CCall")
val wrapper = if (extra.passViaPointer) {
wrapperGenerator.generateCGlobalByPointerGetter(extra.global, cCallAnnotation.symbolName)
} else {
wrapperGenerator.generateCGlobalGetter(extra.global, cCallAnnotation.symbolName)
}
simpleBridgeGenerator.insertNativeBridge(propertyAccessor, emptyList(), wrapper.lines)
}
}
is PropertyAccessor.Setter.ExternalSetter -> {
if (propertyAccessor in builderResult.wrapperGenerationComponents.setterToWrapperInfo) {
val extra = builderResult.wrapperGenerationComponents.setterToWrapperInfo.getValue(propertyAccessor)
val cCallAnnotation = propertyAccessor.annotations.firstIsInstanceOrNull<AnnotationStub.CCall.Symbol>()
?: error("external setter for ${extra.global.name} wasn't marked with @CCall")
val wrapper = wrapperGenerator.generateCGlobalSetter(extra.global, cCallAnnotation.symbolName)
simpleBridgeGenerator.insertNativeBridge(propertyAccessor, emptyList(), wrapper.lines)
}
}
}
}
override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: StubContainer?) {
simpleStubContainer.classes.forEach {
it.accept(this, simpleStubContainer)
}
simpleStubContainer.functions.forEach {
it.accept(this, simpleStubContainer)
}
simpleStubContainer.properties.forEach {
it.accept(this, simpleStubContainer)
}
simpleStubContainer.typealiases.forEach {
it.accept(this, simpleStubContainer)
}
simpleStubContainer.simpleContainers.forEach {
it.accept(this, simpleStubContainer)
}
}
}
private fun isCValuesRef(type: StubType): Boolean =
(type as? ClassifierStubType)?.let { it.classifier == KotlinTypes.cValuesRef }
?: false
private fun generateBridgeBody(function: FunctionStub) {
assert(context.platform == KotlinPlatform.JVM) { "Function ${function.name} was not marked as external." }
assert(function.origin is StubOrigin.Function) { "Can't create bridge for ${function.name}" }
val origin = function.origin as StubOrigin.Function
val bodyGenerator = KotlinCodeBuilder(scope = kotlinFile)
val bridgeArguments = mutableListOf<TypedKotlinValue>()
var isVararg = false
function.parameters.forEachIndexed { index, parameter ->
isVararg = isVararg or parameter.isVararg
val parameterName = parameter.name.asSimpleName()
val bridgeArgument = when {
parameter in builderResult.bridgeGenerationComponents.cStringParameters -> {
bodyGenerator.pushMemScoped()
"$parameterName?.cstr?.getPointer(memScope)"
}
parameter in builderResult.bridgeGenerationComponents.wCStringParameters -> {
bodyGenerator.pushMemScoped()
"$parameterName?.wcstr?.getPointer(memScope)"
}
isCValuesRef(parameter.type) -> {
bodyGenerator.pushMemScoped()
bodyGenerator.getNativePointer(parameterName)
}
else -> {
parameterName
}
}
bridgeArguments += TypedKotlinValue(origin.function.parameters[index].type, bridgeArgument)
}
// TODO: Improve assertion message.
assert(!isVararg || context.platform != KotlinPlatform.NATIVE) {
"Function ${function.name} was processed incorrectly."
}
val result = mappingBridgeGenerator.kotlinToNative(
bodyGenerator,
function,
origin.function.returnType,
bridgeArguments,
independent = false
) { nativeValues ->
"${origin.function.name}(${nativeValues.joinToString()})"
}
bodyGenerator.returnResult(result)
functionBridgeBodies[function] = bodyGenerator.build()
}
private fun generateProtocolGetter(protocolGetterName: String, protocol: ObjCProtocol) {
val builder = NativeCodeBuilder(simpleBridgeGenerator.topLevelNativeScope)
val nativeBacked = object : NativeBacked {}
with(builder) {
out("Protocol* $protocolGetterName() {")
out(" return @protocol(${protocol.name});")
out("}")
}
simpleBridgeGenerator.insertNativeBridge(nativeBacked, emptyList(), builder.lines)
}
fun build(): BridgeBuilderResult {
bridgeGeneratingVisitor.visitSimpleStubContainer(builderResult.stubs, null)
return BridgeBuilderResult(
kotlinFile,
simpleBridgeGenerator.prepare(),
propertyAccessorBridgeBodies.toMap(),
functionBridgeBodies.toMap(),
excludedStubs.toSet()
)
}
}
@@ -0,0 +1,407 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.GenerationMode
import org.jetbrains.kotlin.native.interop.gen.jvm.InteropConfiguration
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
/**
* Components that are not passed via StubIr but required for bridge generation.
*/
class BridgeGenerationInfo(val cGlobalName: String, val typeInfo: TypeInfo)
/**
* Additional components that are required to generate bridges.
* TODO: Metadata-based interop should not depend on these components.
*/
interface BridgeGenerationComponents {
val setterToBridgeInfo: Map<PropertyAccessor.Setter, BridgeGenerationInfo>
val getterToBridgeInfo: Map<PropertyAccessor.Getter, BridgeGenerationInfo>
val arrayGetterInfo: Map<PropertyAccessor.Getter, BridgeGenerationInfo>
val enumToTypeMirror: Map<ClassStub.Enum, TypeMirror>
val wCStringParameters: Set<FunctionParameterStub>
val cStringParameters: Set<FunctionParameterStub>
}
class BridgeGenerationComponentsBuilder {
val getterToBridgeInfo = mutableMapOf<PropertyAccessor.Getter, BridgeGenerationInfo>()
val setterToBridgeInfo = mutableMapOf<PropertyAccessor.Setter, BridgeGenerationInfo>()
val arrayGetterBridgeInfo = mutableMapOf<PropertyAccessor.Getter, BridgeGenerationInfo>()
val enumToTypeMirror = mutableMapOf<ClassStub.Enum, TypeMirror>()
val wCStringParameters = mutableSetOf<FunctionParameterStub>()
val cStringParameters = mutableSetOf<FunctionParameterStub>()
fun build(): BridgeGenerationComponents = object : BridgeGenerationComponents {
override val getterToBridgeInfo =
this@BridgeGenerationComponentsBuilder.getterToBridgeInfo.toMap()
override val setterToBridgeInfo =
this@BridgeGenerationComponentsBuilder.setterToBridgeInfo.toMap()
override val enumToTypeMirror =
this@BridgeGenerationComponentsBuilder.enumToTypeMirror.toMap()
override val wCStringParameters: Set<FunctionParameterStub> =
this@BridgeGenerationComponentsBuilder.wCStringParameters.toSet()
override val cStringParameters: Set<FunctionParameterStub> =
this@BridgeGenerationComponentsBuilder.cStringParameters.toSet()
override val arrayGetterInfo: Map<PropertyAccessor.Getter, BridgeGenerationInfo> =
this@BridgeGenerationComponentsBuilder.arrayGetterBridgeInfo.toMap()
}
}
/**
* Components that are not passed via StubIr but required for generation of wrappers.
*/
class WrapperGenerationInfo(val global: GlobalDecl, val passViaPointer: Boolean = false)
interface WrapperGenerationComponents {
val getterToWrapperInfo: Map<PropertyAccessor.Getter.ExternalGetter, WrapperGenerationInfo>
val setterToWrapperInfo: Map<PropertyAccessor.Setter.ExternalSetter, WrapperGenerationInfo>
}
class WrapperGenerationComponentsBuilder {
val getterToWrapperInfo = mutableMapOf<PropertyAccessor.Getter.ExternalGetter, WrapperGenerationInfo>()
val setterToWrapperInfo = mutableMapOf<PropertyAccessor.Setter.ExternalSetter, WrapperGenerationInfo>()
fun build(): WrapperGenerationComponents = object : WrapperGenerationComponents {
override val getterToWrapperInfo = this@WrapperGenerationComponentsBuilder.getterToWrapperInfo.toMap()
override val setterToWrapperInfo = this@WrapperGenerationComponentsBuilder.setterToWrapperInfo.toMap()
}
}
/**
* Common part of all [StubIrBuilder] implementations.
*/
interface StubsBuildingContext {
val configuration: InteropConfiguration
fun mirror(type: Type): TypeMirror
val declarationMapper: DeclarationMapper
fun generateNextUniqueId(prefix: String): String
val generatedObjCCategoriesMembers: MutableMap<ObjCClass, GeneratedObjCCategoriesMembers>
val platform: KotlinPlatform
/**
* In some cases StubIr should be different for metadata and sourcecode modes.
* For example, it is impossible to represent call to superclass constructor in
* metadata directly and arguments should be passed via annotations instead.
*/
val generationMode: GenerationMode
fun isStrictEnum(enumDef: EnumDef): Boolean
val macroConstantsByName: Map<String, MacroDef>
fun tryCreateIntegralStub(type: Type, value: Long): IntegralConstantStub?
fun tryCreateDoubleStub(type: Type, value: Double): DoubleConstantStub?
val bridgeComponentsBuilder: BridgeGenerationComponentsBuilder
val wrapperComponentsBuilder: WrapperGenerationComponentsBuilder
fun getKotlinClassFor(objCClassOrProtocol: ObjCClassOrProtocol, isMeta: Boolean = false): Classifier
fun getKotlinClassForPointed(structDecl: StructDecl): Classifier
fun isOverloading(func: FunctionDecl): Boolean
}
/**
*
*/
internal interface StubElementBuilder {
val context: StubsBuildingContext
fun build(): List<StubIrElement>
}
class StubsBuildingContextImpl(
private val stubIrContext: StubIrContext
) : StubsBuildingContext {
override val configuration: InteropConfiguration = stubIrContext.configuration
override val platform: KotlinPlatform = stubIrContext.platform
override val generationMode: GenerationMode = stubIrContext.generationMode
val imports: Imports = stubIrContext.imports
private val nativeIndex: NativeIndex = stubIrContext.nativeIndex
private var theCounter = 0
private val uniqFunctions = mutableSetOf<String>()
override fun isOverloading(func: FunctionDecl) = !uniqFunctions.add(func.name) // TODO: params & return type.
override fun generateNextUniqueId(prefix: String) =
prefix + pkgName.replace('.', '_') + theCounter++
override fun mirror(type: Type): TypeMirror = mirror(declarationMapper, type)
/**
* Indicates whether this enum should be represented as Kotlin enum.
*/
override fun isStrictEnum(enumDef: EnumDef): Boolean = with(enumDef) {
if (this.isAnonymous) {
return false
}
val name = this.kotlinName
if (name in configuration.strictEnums) {
return true
}
if (name in configuration.nonStrictEnums) {
return false
}
// Let the simple heuristic decide:
return !this.constants.any { it.isExplicitlyDefined }
}
override val generatedObjCCategoriesMembers = mutableMapOf<ObjCClass, GeneratedObjCCategoriesMembers>()
override val declarationMapper = object : DeclarationMapper {
override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier {
val baseName = structDecl.kotlinName
val pkg = when (platform) {
KotlinPlatform.JVM -> pkgName
KotlinPlatform.NATIVE -> if (structDecl.def == null) {
cnamesStructsPackageName // to be imported as forward declaration.
} else {
getPackageFor(structDecl)
}
}
return Classifier.topLevel(pkg, baseName)
}
override fun isMappedToStrict(enumDef: EnumDef): Boolean = isStrictEnum(enumDef)
override fun getKotlinNameForValue(enumDef: EnumDef): String = enumDef.kotlinName
override fun getPackageFor(declaration: TypeDeclaration): String {
return imports.getPackage(declaration.location) ?: pkgName
}
override val useUnsignedTypes: Boolean
get() = when (platform) {
KotlinPlatform.JVM -> false
KotlinPlatform.NATIVE -> true
}
}
override val macroConstantsByName: Map<String, MacroDef> =
(nativeIndex.macroConstants + nativeIndex.wrappedMacros).associateBy { it.name }
/**
* The name to be used for this enum in Kotlin
*/
val EnumDef.kotlinName: String
get() = if (spelling.startsWith("enum ")) {
spelling.substringAfter(' ')
} else {
assert (!isAnonymous)
spelling
}
private val pkgName: String
get() = configuration.pkgName
/**
* The name to be used for this struct in Kotlin
*/
val StructDecl.kotlinName: String
get() = stubIrContext.getKotlinName(this)
override fun tryCreateIntegralStub(type: Type, value: Long): IntegralConstantStub? {
val integerType = when (val unwrappedType = type.unwrapTypedefs()) {
is IntegerType -> unwrappedType
CharType -> IntegerType(1, true, "char")
else -> return null
}
val size = integerType.size
if (size != 1 && size != 2 && size != 4 && size != 8) return null
return IntegralConstantStub(value, size, declarationMapper.isMappedToSigned(integerType))
}
override fun tryCreateDoubleStub(type: Type, value: Double): DoubleConstantStub? {
val unwrappedType = type.unwrapTypedefs() as? FloatingType ?: return null
val size = unwrappedType.size
if (size != 4 && size != 8) return null
return DoubleConstantStub(value, size)
}
override val bridgeComponentsBuilder = BridgeGenerationComponentsBuilder()
override val wrapperComponentsBuilder = WrapperGenerationComponentsBuilder()
override fun getKotlinClassFor(objCClassOrProtocol: ObjCClassOrProtocol, isMeta: Boolean): Classifier {
return declarationMapper.getKotlinClassFor(objCClassOrProtocol, isMeta)
}
override fun getKotlinClassForPointed(structDecl: StructDecl): Classifier {
val classifier = declarationMapper.getKotlinClassForPointed(structDecl)
return classifier
}
}
data class StubIrBuilderResult(
val stubs: SimpleStubContainer,
val declarationMapper: DeclarationMapper,
val bridgeGenerationComponents: BridgeGenerationComponents,
val wrapperGenerationComponents: WrapperGenerationComponents
)
/**
* Produces [StubIrBuilderResult] for given [KotlinPlatform] using [InteropConfiguration].
*/
class StubIrBuilder(private val context: StubIrContext) {
private val configuration = context.configuration
private val nativeIndex: NativeIndex = context.nativeIndex
private val classes = mutableListOf<ClassStub>()
private val functions = mutableListOf<FunctionStub>()
private val globals = mutableListOf<PropertyStub>()
private val typealiases = mutableListOf<TypealiasStub>()
private val containers = mutableListOf<SimpleStubContainer>()
private fun addStubs(stubs: List<StubIrElement>) = stubs.forEach(this::addStub)
private fun addStub(stub: StubIrElement) {
when(stub) {
is ClassStub -> classes += stub
is FunctionStub -> functions += stub
is PropertyStub -> globals += stub
is TypealiasStub -> typealiases += stub
is SimpleStubContainer -> containers += stub
else -> error("Unexpected stub: $stub")
}
}
private val excludedFunctions: Set<String>
get() = configuration.excludedFunctions
private val excludedMacros: Set<String>
get() = configuration.excludedMacros
private val buildingContext = StubsBuildingContextImpl(context)
fun build(): StubIrBuilderResult {
nativeIndex.objCProtocols.filter { !it.isForwardDeclaration }.forEach { generateStubsForObjCProtocol(it) }
nativeIndex.objCClasses.filter { !it.isForwardDeclaration && !it.isNSStringSubclass()} .forEach { generateStubsForObjCClass(it) }
nativeIndex.objCCategories.filter { !it.clazz.isNSStringSubclass() }.forEach { generateStubsForObjCCategory(it) }
nativeIndex.structs.forEach { generateStubsForStruct(it) }
nativeIndex.enums.forEach { generateStubsForEnum(it) }
nativeIndex.functions.filter { it.name !in excludedFunctions }.forEach { generateStubsForFunction(it) }
nativeIndex.typedefs.forEach { generateStubsForTypedef(it) }
nativeIndex.globals.filter { it.name !in excludedFunctions }.forEach { generateStubsForGlobal(it) }
nativeIndex.macroConstants.filter { it.name !in excludedMacros }.forEach { generateStubsForMacroConstant(it) }
nativeIndex.wrappedMacros.filter { it.name !in excludedMacros }.forEach { generateStubsForWrappedMacro(it) }
val meta = StubContainerMeta()
val stubs = SimpleStubContainer(
meta,
classes.toList(),
functions.toList(),
globals.toList(),
typealiases.toList(),
containers.toList()
)
return StubIrBuilderResult(
stubs,
buildingContext.declarationMapper,
buildingContext.bridgeComponentsBuilder.build(),
buildingContext.wrapperComponentsBuilder.build()
)
}
private fun generateStubsForWrappedMacro(macro: WrappedMacroDef) {
try {
generateStubsForGlobal(GlobalDecl(macro.name, macro.type, isConst = true))
} catch (e: Throwable) {
context.log("Warning: cannot generate stubs for macro ${macro.name}")
}
}
private fun generateStubsForMacroConstant(constant: ConstantDef) {
try {
addStubs(MacroConstantStubBuilder(buildingContext, constant).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate stubs for constant ${constant.name}")
}
}
private fun generateStubsForEnum(enumDef: EnumDef) {
try {
addStubs(EnumStubBuilder(buildingContext, enumDef).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate definition for enum ${enumDef.spelling}")
}
}
private fun generateStubsForFunction(func: FunctionDecl) {
try {
addStubs(FunctionStubBuilder(buildingContext, func, skipOverloads = true).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate stubs for function ${func.name}")
}
}
private fun generateStubsForStruct(decl: StructDecl) {
try {
addStubs(StructStubBuilder(buildingContext, decl).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate definition for struct ${decl.spelling}")
}
}
private fun generateStubsForTypedef(typedefDef: TypedefDef) {
try {
addStubs(TypedefStubBuilder(buildingContext, typedefDef).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate typedef ${typedefDef.name}")
}
}
private fun generateStubsForGlobal(global: GlobalDecl) {
try {
addStubs(GlobalStubBuilder(buildingContext, global).build())
} catch (e: Throwable) {
context.log("Warning: cannot generate stubs for global ${global.name}")
}
}
private fun generateStubsForObjCProtocol(objCProtocol: ObjCProtocol) {
addStubs(ObjCProtocolStubBuilder(buildingContext, objCProtocol).build())
}
private fun generateStubsForObjCClass(objCClass: ObjCClass) {
addStubs(ObjCClassStubBuilder(buildingContext, objCClass).build())
}
private fun generateStubsForObjCCategory(objCCategory: ObjCCategory) {
addStubs(ObjCCategoryStubBuilder(buildingContext, objCCategory).build())
}
}
@@ -0,0 +1,171 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the LICENSE file.
*/
package org.jetbrains.kotlin.native.interop.gen
import kotlinx.metadata.klib.KlibModuleMetadata
import org.jetbrains.kotlin.native.interop.gen.jvm.GenerationMode
import org.jetbrains.kotlin.native.interop.gen.jvm.InteropConfiguration
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
import java.io.File
import java.util.*
class StubIrContext(
val log: (String) -> Unit,
val configuration: InteropConfiguration,
val nativeIndex: NativeIndex,
val imports: Imports,
val platform: KotlinPlatform,
val generationMode: GenerationMode,
val libName: String
) {
val libraryForCStubs = configuration.library.copy(
includes = mutableListOf<String>().apply {
add("stdint.h")
add("string.h")
if (platform == KotlinPlatform.JVM) {
add("jni.h")
}
addAll(configuration.library.includes)
},
compilerArgs = configuration.library.compilerArgs,
additionalPreambleLines = configuration.library.additionalPreambleLines +
when (configuration.library.language) {
Language.C -> emptyList()
Language.OBJECTIVE_C -> listOf("void objc_terminate();")
}
).precompileHeaders()
// TODO: Used only for JVM.
val jvmFileClassName = if (configuration.pkgName.isEmpty()) {
libName
} else {
configuration.pkgName.substringAfterLast('.')
}
val validPackageName = configuration.pkgName.split(".").joinToString(".") {
if (it.matches(VALID_PACKAGE_NAME_REGEX)) it else "`$it`"
}
private val anonymousStructKotlinNames = mutableMapOf<StructDecl, String>()
private val forbiddenStructNames = run {
val typedefNames = nativeIndex.typedefs.map { it.name }
typedefNames.toSet()
}
/**
* The name to be used for this struct in Kotlin
*/
fun getKotlinName(decl: StructDecl): String {
val spelling = decl.spelling
if (decl.isAnonymous) {
val names = anonymousStructKotlinNames
return names.getOrPut(decl) {
"anonymousStruct${names.size + 1}"
}
}
val strippedCName = if (spelling.startsWith("struct ") || spelling.startsWith("union ")) {
spelling.substringAfter(' ')
} else {
spelling
}
// TODO: don't mangle struct names because it wouldn't work if the struct
// is imported into another interop library.
return if (strippedCName !in forbiddenStructNames) strippedCName else (strippedCName + "Struct")
}
fun addManifestProperties(properties: Properties) {
val exportForwardDeclarations = configuration.exportForwardDeclarations.toMutableList()
nativeIndex.structs
.filter { it.def == null }
.mapTo(exportForwardDeclarations) {
"$cnamesStructsPackageName.${getKotlinName(it)}"
}
properties["exportForwardDeclarations"] = exportForwardDeclarations.joinToString(" ")
// TODO: consider exporting Objective-C class and protocol forward refs.
}
companion object {
private val VALID_PACKAGE_NAME_REGEX = "[a-zA-Z0-9_.]+".toRegex()
}
}
class StubIrDriver(
private val context: StubIrContext,
private val options: DriverOptions
) {
data class DriverOptions(
val entryPoint: String?,
val moduleName: String,
val outCFile: File,
val outKtFileCreator: () -> File
)
sealed class Result {
object SourceCode : Result()
class Metadata(val metadata: KlibModuleMetadata): Result()
}
fun run(): Result {
val (entryPoint, moduleName, outCFile, outKtFile) = options
val builderResult = StubIrBuilder(context).build()
val bridgeBuilderResult = StubIrBridgeBuilder(context, builderResult).build()
outCFile.bufferedWriter().use {
emitCFile(context, it, entryPoint, bridgeBuilderResult.nativeBridges)
}
return when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
emitSourceCode(outKtFile(), builderResult, bridgeBuilderResult)
}
GenerationMode.METADATA -> emitMetadata(builderResult, moduleName, bridgeBuilderResult)
}
}
private fun emitSourceCode(
outKtFile: File, builderResult: StubIrBuilderResult, bridgeBuilderResult: BridgeBuilderResult
): Result.SourceCode {
outKtFile.bufferedWriter().use { ktFile ->
StubIrTextEmitter(context, builderResult, bridgeBuilderResult).emit(ktFile)
}
return Result.SourceCode
}
private fun emitMetadata(
builderResult: StubIrBuilderResult, moduleName: String, bridgeBuilderResult: BridgeBuilderResult
) = Result.Metadata(StubIrMetadataEmitter(context, builderResult, moduleName, bridgeBuilderResult).emit())
private fun emitCFile(context: StubIrContext, cFile: Appendable, entryPoint: String?, nativeBridges: NativeBridges) {
val out = { it: String -> cFile.appendLine(it) }
context.libraryForCStubs.preambleLines.forEach {
out(it)
}
out("")
out("// NOTE THIS FILE IS AUTO-GENERATED")
out("")
nativeBridges.nativeLines.forEach { out(it) }
if (entryPoint != null) {
out("extern int Konan_main(int argc, char** argv);")
out("")
out("__attribute__((__used__))")
out("int $entryPoint(int argc, char** argv) {")
out(" return Konan_main(argc, argv);")
out("}")
}
}
}

Some files were not shown because too many files have changed in this diff Show More