Move everything under kotlin-native folder

I was forced to manually do update the following files, because otherwise
they would be ignored according .gitignore settings. Probably they
should be deleted from repo.

Interop/.idea/compiler.xml
Interop/.idea/gradle.xml
Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_runtime_1_0_3.xml
Interop/.idea/libraries/Gradle__org_jetbrains_kotlin_kotlin_stdlib_1_0_3.xml
Interop/.idea/modules.xml
Interop/.idea/modules/Indexer/Indexer.iml
Interop/.idea/modules/Runtime/Runtime.iml
Interop/.idea/modules/StubGenerator/StubGenerator.iml
backend.native/backend.native.iml
backend.native/bc.frontend/bc.frontend.iml
backend.native/cli.bc/cli.bc.iml
backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt
backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2NativeCompilerArguments.kt
backend.native/tests/link/lib/foo.kt
backend.native/tests/link/lib/foo2.kt
backend.native/tests/teamcity-test.property
This commit is contained in:
Stanislav Erokhin
2020-10-27 21:00:28 +03:00
parent 91e4162dad
commit f624800b84
2830 changed files with 0 additions and 0 deletions
+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>
+173
View File
@@ -0,0 +1,173 @@
/*
* 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 {
ext.rootBuildDirectory = file('../..')
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
dependencies {
classpath "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
}
}
apply plugin: 'kotlin'
apply plugin: org.jetbrains.kotlin.NativeInteropPlugin
apply plugin: 'c'
apply plugin: 'cpp'
import org.jetbrains.kotlin.konan.target.ClangArgs
final Project libclangextProject = project(":libclangext")
final String libclangextTask = libclangextProject.path + ":build"
File libclangextDir = new File(libclangextProject.buildDir, "libs/clangext/static")
final boolean libclangextIsEnabled = libclangextProject.isEnabled
final String libclang
if (isWindows()) {
libclang = "bin/libclang.dll"
} else {
libclang = "lib/${System.mapLibraryName("clang")}"
}
List<String> cflags = [
"-I$llvmDir/include",
"-I${project(":libclangext").projectDir.absolutePath + "/src/main/include"}"
]*.toString()
List<String> ldflags = ["$llvmDir/$libclang", "-L$libclangextDir.absolutePath", "-lclangext"]*.toString()
if (libclangextIsEnabled) {
assert(isMac())
ldflags.addAll(['-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'])
List<String> llvmLibs = [
"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"
].collect { "$llvmDir/lib/lib${it}.a".toString() }
ldflags.addAll(llvmLibs)
ldflags.addAll(['-lpthread', '-lz', '-lm', '-lcurses'])
}
model {
components {
clangstubs(NativeLibrarySpec) {
sources {
c.source.srcDir 'prebuilt/nativeInteropStubs/c'
cpp.source.srcDir 'src/nativeInteropStubs/cpp'
}
binaries.all {
cCompiler.args hostPlatform.clang.hostCompilerArgsForJni
cCompiler.args.addAll(cflags)
}
binaries.withType(SharedLibraryBinarySpec) {
linker.args.addAll(ldflags)
}
}
}
toolChains {
clang(Clang) {
eachPlatform {
cppCompiler.withArguments(ClangArgs.&filterGradleNativeSoftwareFlags)
cCompiler.withArguments(ClangArgs.&filterGradleNativeSoftwareFlags)
}
}
}
}
sourceSets {
main {
kotlin {
srcDirs 'prebuilt/nativeInteropStubs/kotlin'
}
}
}
repositories {
maven {
url buildKotlinCompilerRepo
}
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
compile project(':Interop:Runtime')
}
task nativelibs(type: Copy) {
dependsOn 'clangstubsSharedLibrary'
from "$buildDir/libs/clangstubs/shared/"
into "$buildDir/nativelibs/"
}
classes.dependsOn nativelibs
kotlinNativeInterop {
clang {
defFile 'clang.def'
compilerOpts cflags
linkerOpts ldflags
genTask.dependsOn libclangextTask
genTask.inputs.dir libclangextDir
}
}
compileKotlin {
kotlinOptions {
allWarningsAsErrors=true
}
}
tasks.matching { it.name == 'linkClangstubsSharedLibrary' }.all {
it.dependsOn libclangextTask
it.inputs.dir libclangextDir
}
task 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,138 @@
package org.jetbrains.kotlin.native.interop.indexer
import clang.*
import kotlinx.cinterop.*
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) }) {
createTempDir("ModuleCache")
} else {
null
}
override val compilerArgs: List<String> = compilation.compilerArgs +
listOfNotNull("-fmodules", moduleCacheDirectory?.let { "$moduleCacheFlag${it.absolutePath}" })
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,770 @@
/*
* 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(createTempFile(suffix = sourceFile.name), 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 = createTempFile(suffix = ".${language.sourceFileExtension}")
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 = createTempFile(suffix = ".pch").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 createTempFile(prefix = "konan", suffix = ".vfsoverlay").apply {
bufferedWriter().use {
writeBytes(bytes)
}
deleteOnExit()
}.toPath()
}
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/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,92 @@
/*
* 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.
*/
apply plugin: 'kotlin'
apply plugin: 'c'
buildscript {
ext.rootBuildDirectory = file('../..')
apply from: "$rootBuildDirectory/gradle/kotlinGradlePlugin.gradle"
dependencies {
classpath "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
classpath "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
}
}
import org.jetbrains.kotlin.konan.target.ClangArgs
model {
components {
callbacks(NativeLibrarySpec) {
sources.c.source {
srcDir 'src/callbacks/c'
include '**/*.c'
}
binaries.all {
def host = rootProject.ext.hostName
def hostLibffiDir = rootProject.ext.get("${host}LibffiDir")
cCompiler.args hostPlatform.clang.hostCompilerArgsForJni
cCompiler.args "-I$hostLibffiDir/include"
linker.args "$hostLibffiDir/lib/libffi.a"
}
}
}
toolChains {
clang(Clang) {
eachPlatform {
cCompiler.withArguments(ClangArgs.&filterGradleNativeSoftwareFlags)
}
}
}
}
repositories {
maven {
url buildKotlinCompilerRepo
}
}
dependencies {
compile project(":utilities:basic-utils")
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlinVersion"
}
sourceSets.main.kotlin.srcDirs += "src/jvm/kotlin"
compileKotlin {
kotlinOptions {
freeCompilerArgs = ['-Xuse-experimental=kotlin.ExperimentalUnsignedTypes', '-Xuse-experimental=kotlin.Experimental',
'-Xopt-in=kotlin.RequiresOptIn', "-XXLanguage:+InlineClasses"]
allWarningsAsErrors=true
}
}
task nativelibs(type: Copy) {
dependsOn 'callbacksSharedLibrary'
from "$buildDir/libs/callbacks/shared/"
into "$buildDir/nativelibs/"
}
classes.dependsOn 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,118 @@
/*
* 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 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
}
fun alloc(size: Long, align: Int): NativePointed {
val address = unsafe.allocateMemory(
if (size == 0L) 1L else size // It is a hack: `sun.misc.Unsafe` can't allocate zero bytes
)
if (address % align != 0L) TODO(align.toString())
return interpretOpaquePointed(address)
}
fun free(mem: NativePtr) {
unsafe.freeMemory(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,109 @@
/*
* 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
internal fun decodeFromUtf8(bytes: ByteArray) = String(bytes)
internal fun encodeToUtf8(str: String) = str.toByteArray()
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 = createTempDir(directory = File(dir)).absolutePath
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,643 @@
/*
* 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
}
}
/**
* @return the value of zero-terminated UTF-8-encoded C string constructed from given [kotlin.String].
*/
public val String.cstr: CValues<ByteVar>
get() = 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 {
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)
}
/**
* @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 String(chars)
}
/**
* @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 String(chars)
}
/**
* 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,162 @@
/*
* 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 {
val ptr = malloc(size, align)
if (ptr == nativeNullPtr) {
throw OutOfMemoryError("unable to allocate native memory")
}
return interpretOpaquePointed(ptr)
}
fun free(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 String(chars)
}
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,68 @@
/*
* 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 decodeFromUtf8(bytes: ByteArray): String = bytes.decodeToString()
internal fun encodeToUtf8(str: String): ByteArray = str.encodeToByteArray()
@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(value) = 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,52 @@
/*
* 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/gradle/kotlinGradlePlugin.gradle"
}
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = "org.jetbrains.kotlin.native.interop.gen.jvm.MainKt"
repositories {
maven {
url buildKotlinCompilerRepo
}
}
dependencies {
implementation project(":Interop:Indexer")
implementation project(":utilities:basic-utils")
api project(path: ":endorsedLibraries:kotlinx.cli", configuration: "jvmRuntimeElements")
api "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
api "org.jetbrains.kotlin:kotlin-compiler:$kotlinVersion"
api "org.jetbrains.kotlin:kotlin-native-shared:$konanVersion"
api "org.jetbrains.kotlinx:kotlinx-metadata-klib:$metadataVersion"
testImplementation "junit:junit:4.12"
testImplementation "org.jetbrains.kotlin:kotlin-test-junit:$buildKotlinVersion"
testImplementation "org.jetbrains.kotlin:kotlin-test:$buildKotlinVersion"
}
compileKotlin {
kotlinOptions {
freeCompilerArgs = ['-Xopt-in=kotlin.ExperimentalUnsignedTypes', '-Xskip-metadata-version-check']
allWarningsAsErrors=true
}
}
@@ -0,0 +1,134 @@
/*
* 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 -> 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("}")
}
}
}
@@ -0,0 +1,727 @@
/*
* 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.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.*
internal class MacroConstantStubBuilder(
override val context: StubsBuildingContext,
private val constant: ConstantDef
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val kotlinName = constant.name
val origin = StubOrigin.Constant(constant)
val declaration = when (constant) {
is IntegerConstantDef -> {
val literal = context.tryCreateIntegralStub(constant.type, constant.value) ?: return emptyList()
val kotlinType = context.mirror(constant.type).argType.toStubIrType()
when (context.platform) {
KotlinPlatform.NATIVE -> PropertyStub(kotlinName, kotlinType, PropertyStub.Kind.Constant(literal), origin = origin)
// No reason to make it const val with backing field on Kotlin/JVM yet:
KotlinPlatform.JVM -> {
val getter = PropertyAccessor.Getter.SimpleGetter(constant = literal)
PropertyStub(kotlinName, kotlinType, PropertyStub.Kind.Val(getter), origin = origin)
}
}
}
is FloatingConstantDef -> {
val literal = context.tryCreateDoubleStub(constant.type, constant.value) ?: return emptyList()
val kind = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
PropertyStub.Kind.Val(PropertyAccessor.Getter.SimpleGetter(constant = literal))
}
GenerationMode.METADATA -> {
PropertyStub.Kind.Constant(literal)
}
}
val kotlinType = context.mirror(constant.type).argType.toStubIrType()
PropertyStub(kotlinName, kotlinType, kind, origin = origin)
}
is StringConstantDef -> {
val literal = StringConstantStub(constant.value)
val kind = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
PropertyStub.Kind.Val(PropertyAccessor.Getter.SimpleGetter(constant = literal))
}
GenerationMode.METADATA -> {
PropertyStub.Kind.Constant(literal)
}
}
PropertyStub(kotlinName, KotlinTypes.string.toStubIrType(), kind, origin = origin)
}
else -> return emptyList()
}
return listOf(declaration)
}
}
internal class StructStubBuilder(
override val context: StubsBuildingContext,
private val decl: StructDecl
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val platform = context.platform
val def = decl.def ?: return generateForwardStruct(decl)
val structAnnotation: AnnotationStub? = if (platform == KotlinPlatform.JVM) {
if (def.kind == StructDef.Kind.STRUCT && def.fieldsHaveDefaultAlignment()) {
AnnotationStub.CNaturalStruct(def.members)
} else {
null
}
} else {
tryRenderStructOrUnion(def)?.let {
AnnotationStub.CStruct(it)
}
}
val classifier = context.getKotlinClassForPointed(decl)
val fields: List<PropertyStub?> = def.fields.map { field ->
try {
assert(field.name.isNotEmpty())
assert(field.offset % 8 == 0L)
val offset = field.offset / 8
val fieldRefType = context.mirror(field.type)
val unwrappedFieldType = field.type.unwrapTypedefs()
val origin = StubOrigin.StructMember(field)
val fieldName = mangleSimple(field.name)
if (unwrappedFieldType is ArrayType) {
val type = (fieldRefType as TypeMirror.ByValue).valueType
val annotations = if (platform == KotlinPlatform.JVM) {
val length = getArrayLength(unwrappedFieldType)
// TODO: @CLength should probably be used on types instead of properties.
listOf(AnnotationStub.CLength(length))
} else {
emptyList()
}
val getter = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> PropertyAccessor.Getter.ArrayMemberAt(offset)
GenerationMode.METADATA -> PropertyAccessor.Getter.ExternalGetter(listOf(AnnotationStub.CStruct.ArrayMemberAt(offset)))
}
val kind = PropertyStub.Kind.Val(getter)
// TODO: Should receiver be added?
PropertyStub(fieldName, type.toStubIrType(), kind, annotations = annotations, origin = origin)
} else {
val pointedType = fieldRefType.pointedType.toStubIrType()
val pointedTypeArgument = TypeArgumentStub(pointedType)
if (fieldRefType is TypeMirror.ByValue) {
val getter: PropertyAccessor.Getter
val setter: PropertyAccessor.Setter
when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
getter = PropertyAccessor.Getter.MemberAt(offset, typeArguments = listOf(pointedTypeArgument), hasValueAccessor = true)
setter = PropertyAccessor.Setter.MemberAt(offset, typeArguments = listOf(pointedTypeArgument))
}
GenerationMode.METADATA -> {
getter = PropertyAccessor.Getter.ExternalGetter(listOf(AnnotationStub.CStruct.MemberAt(offset)))
setter = PropertyAccessor.Setter.ExternalSetter(listOf(AnnotationStub.CStruct.MemberAt(offset)))
}
}
val kind = PropertyStub.Kind.Var(getter, setter)
PropertyStub(fieldName, fieldRefType.argType.toStubIrType(), kind, origin = origin)
} else {
val accessor = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> PropertyAccessor.Getter.MemberAt(offset, hasValueAccessor = false)
GenerationMode.METADATA -> PropertyAccessor.Getter.ExternalGetter(listOf(AnnotationStub.CStruct.MemberAt(offset)))
}
val kind = PropertyStub.Kind.Val(accessor)
PropertyStub(fieldName, pointedType, kind, origin = origin)
}
}
} catch (e: Throwable) {
null
}
}
val bitFields: List<PropertyStub> = def.bitFields.map { field ->
val typeMirror = context.mirror(field.type)
val typeInfo = typeMirror.info
val kotlinType = typeMirror.argType
val signed = field.type.isIntegerTypeSigned()
val fieldName = mangleSimple(field.name)
val kind = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
val readBits = PropertyAccessor.Getter.ReadBits(field.offset, field.size, signed)
val writeBits = PropertyAccessor.Setter.WriteBits(field.offset, field.size)
context.bridgeComponentsBuilder.getterToBridgeInfo[readBits] = BridgeGenerationInfo("", typeInfo)
context.bridgeComponentsBuilder.setterToBridgeInfo[writeBits] = BridgeGenerationInfo("", typeInfo)
PropertyStub.Kind.Var(readBits, writeBits)
}
GenerationMode.METADATA -> {
val readBits = PropertyAccessor.Getter.ExternalGetter(listOf(AnnotationStub.CStruct.BitField(field.offset, field.size)))
val writeBits = PropertyAccessor.Setter.ExternalSetter(listOf(AnnotationStub.CStruct.BitField(field.offset, field.size)))
PropertyStub.Kind.Var(readBits, writeBits)
}
}
PropertyStub(fieldName, kotlinType.toStubIrType(), kind, origin = StubOrigin.StructMember(field))
}
val superClass = context.platform.getRuntimeType("CStructVar")
require(superClass is ClassifierStubType)
val rawPtrConstructorParam = FunctionParameterStub("rawPtr", context.platform.getRuntimeType("NativePtr"))
val origin = StubOrigin.Struct(decl)
val primaryConstructor = ConstructorStub(
parameters = listOf(rawPtrConstructorParam),
isPrimary = true,
annotations = emptyList(),
origin = origin
)
val superClassInit = SuperClassInit(superClass, listOf(GetConstructorParameter(rawPtrConstructorParam)))
val companionSuper = superClass.nested("Type")
val typeSize = listOf(IntegralConstantStub(def.size, 4, true), IntegralConstantStub(def.align.toLong(), 4, true))
val companionSuperInit = SuperClassInit(companionSuper, typeSize)
val companionClassifier = classifier.nested("Companion")
val annotation = AnnotationStub.CStruct.VarType(def.size, def.align).takeIf {
context.generationMode == GenerationMode.METADATA
}
val companion = ClassStub.Companion(
companionClassifier,
superClassInit = companionSuperInit,
annotations = listOfNotNull(annotation, AnnotationStub.Deprecated.deprecatedCVariableCompanion)
)
return listOf(ClassStub.Simple(
classifier,
origin = origin,
properties = fields.filterNotNull() + if (platform == KotlinPlatform.NATIVE) bitFields else emptyList(),
constructors = listOf(primaryConstructor),
methods = emptyList(),
modality = ClassStubModality.NONE,
annotations = listOfNotNull(structAnnotation),
superClassInit = superClassInit,
companion = companion
))
}
private fun getArrayLength(type: ArrayType): Long {
val unwrappedElementType = type.elemType.unwrapTypedefs()
val elementLength = if (unwrappedElementType is ArrayType) {
getArrayLength(unwrappedElementType)
} else {
1L
}
val elementCount = when (type) {
is ConstArrayType -> type.length
is IncompleteArrayType -> 0L
else -> TODO(type.toString())
}
return elementLength * elementCount
}
private tailrec fun Type.isIntegerTypeSigned(): Boolean = when (this) {
is IntegerType -> this.isSigned
is BoolType -> false
is EnumType -> this.def.baseType.isIntegerTypeSigned()
is Typedef -> this.def.aliased.isIntegerTypeSigned()
else -> error(this)
}
/**
* Produces to [out] the definition of Kotlin class representing the reference to given forward (incomplete) struct.
*/
private fun generateForwardStruct(s: StructDecl): List<StubIrElement> = when (context.platform) {
KotlinPlatform.JVM -> {
val classifier = context.getKotlinClassForPointed(s)
val superClass = context.platform.getRuntimeType("COpaque")
val rawPtrConstructorParam = FunctionParameterStub("rawPtr", context.platform.getRuntimeType("NativePtr"))
val superClassInit = SuperClassInit(superClass, listOf(GetConstructorParameter(rawPtrConstructorParam)))
val origin = StubOrigin.Struct(s)
val primaryConstructor = ConstructorStub(listOf(rawPtrConstructorParam), emptyList(), isPrimary = true, origin = origin)
listOf(ClassStub.Simple(
classifier,
ClassStubModality.NONE,
constructors = listOf(primaryConstructor),
superClassInit = superClassInit,
origin = origin))
}
KotlinPlatform.NATIVE -> emptyList()
}
}
internal class EnumStubBuilder(
override val context: StubsBuildingContext,
private val enumDef: EnumDef
) : StubElementBuilder {
private val classifier = (context.mirror(EnumType(enumDef)) as TypeMirror.ByValue).valueType.classifier
private val baseTypeMirror = context.mirror(enumDef.baseType)
private val baseType = baseTypeMirror.argType.toStubIrType()
override fun build(): List<StubIrElement> {
if (!context.isStrictEnum(enumDef)) {
return generateEnumAsConstants(enumDef)
}
val constructorParameter = FunctionParameterStub("value", baseType)
val valueProperty = PropertyStub(
name = "value",
type = baseType,
kind = PropertyStub.Kind.Val(PropertyAccessor.Getter.GetConstructorParameter(constructorParameter)),
modality = MemberStubModality.OPEN,
origin = StubOrigin.Synthetic.EnumValueField(enumDef),
isOverride = true)
val canonicalsByValue = enumDef.constants
.groupingBy { it.value }
.reduce { _, accumulator, element ->
if (element.isMoreCanonicalThan(accumulator)) {
element
} else {
accumulator
}
}
val (canonicalConstants, aliasConstants) = enumDef.constants.partition { canonicalsByValue[it.value] == it }
val canonicalEntriesWithAliases = canonicalConstants
.sortedBy { it.value } // TODO: Is it stable enough?
.mapIndexed { index, constant ->
val literal = context.tryCreateIntegralStub(enumDef.baseType, constant.value)
?: error("Cannot create enum value ${constant.value} of type ${enumDef.baseType}")
val entry = EnumEntryStub(mangleSimple(constant.name), literal, StubOrigin.EnumEntry(constant), index)
val aliases = aliasConstants
.filter { it.value == constant.value }
.map { constructAliasProperty(it, entry) }
entry to aliases
}
val origin = StubOrigin.Enum(enumDef)
val primaryConstructor = ConstructorStub(
parameters = listOf(constructorParameter),
annotations = emptyList(),
isPrimary = true,
origin = origin,
visibility = VisibilityModifier.PRIVATE
)
val byValueFunction = FunctionStub(
name = "byValue",
returnType = ClassifierStubType(classifier),
parameters = listOf(FunctionParameterStub("value", baseType)),
origin = StubOrigin.Synthetic.EnumByValue(enumDef),
receiver = null,
modality = MemberStubModality.FINAL,
annotations = listOf(AnnotationStub.Deprecated.deprecatedCEnumByValue)
)
val companion = ClassStub.Companion(
classifier = classifier.nested("Companion"),
properties = canonicalEntriesWithAliases.flatMap { it.second },
methods = listOf(byValueFunction)
)
val enumVarClass = constructEnumVarClass().takeIf { context.generationMode == GenerationMode.METADATA }
val kotlinEnumType = ClassifierStubType(Classifier.topLevel("kotlin", "Enum"),
listOf(TypeArgumentStub(ClassifierStubType(classifier))))
val enum = ClassStub.Enum(
classifier = classifier,
superClassInit = SuperClassInit(kotlinEnumType),
entries = canonicalEntriesWithAliases.map { it.first },
companion = companion,
constructors = listOf(primaryConstructor),
properties = listOf(valueProperty),
origin = origin,
interfaces = listOf(context.platform.getRuntimeType("CEnum")),
childrenClasses = listOfNotNull(enumVarClass)
)
context.bridgeComponentsBuilder.enumToTypeMirror[enum] = baseTypeMirror
return listOf(enum)
}
private fun constructAliasProperty(enumConstant: EnumConstant, entry: EnumEntryStub): PropertyStub {
val aliasAnnotation = AnnotationStub.CEnumEntryAlias(entry.name)
.takeIf { context.generationMode == GenerationMode.METADATA }
return PropertyStub(
enumConstant.name,
ClassifierStubType(classifier),
kind = PropertyStub.Kind.Val(PropertyAccessor.Getter.GetEnumEntry(entry)),
origin = StubOrigin.EnumEntry(enumConstant),
annotations = listOfNotNull(aliasAnnotation)
)
}
private fun constructEnumVarClass(): ClassStub.Simple {
val enumVarClassifier = classifier.nested("Var")
val rawPtrConstructorParam = FunctionParameterStub("rawPtr", context.platform.getRuntimeType("NativePtr"))
val superClass = context.platform.getRuntimeType("CEnumVar")
require(superClass is ClassifierStubType)
val primaryConstructor = ConstructorStub(
parameters = listOf(rawPtrConstructorParam),
isPrimary = true,
annotations = emptyList(),
origin = StubOrigin.Synthetic.DefaultConstructor
)
val superClassInit = SuperClassInit(superClass, listOf(GetConstructorParameter(rawPtrConstructorParam)))
val baseIntegerTypeSize = when (val unwrappedType = enumDef.baseType.unwrapTypedefs()) {
is IntegerType -> unwrappedType.size.toLong()
CharType -> 1L
else -> error("Incorrect base type for enum ${classifier.fqName}")
}
val typeSize = IntegralConstantStub(baseIntegerTypeSize, 4, true)
val companionSuper = (context.platform.getRuntimeType("CPrimitiveVar") as ClassifierStubType).nested("Type")
val varSizeAnnotation = AnnotationStub.CEnumVarTypeSize(baseIntegerTypeSize.toInt())
.takeIf { context.generationMode == GenerationMode.METADATA }
val companion = ClassStub.Companion(
classifier = enumVarClassifier.nested("Companion"),
superClassInit = SuperClassInit(companionSuper, listOf(typeSize)),
annotations = listOfNotNull(varSizeAnnotation, AnnotationStub.Deprecated.deprecatedCVariableCompanion)
)
val valueProperty = PropertyStub(
name = "value",
type = ClassifierStubType(classifier),
kind = PropertyStub.Kind.Var(
PropertyAccessor.Getter.ExternalGetter(),
PropertyAccessor.Setter.ExternalSetter()
),
origin = StubOrigin.Synthetic.EnumVarValueField(enumDef)
)
return ClassStub.Simple(
classifier = enumVarClassifier,
constructors = listOf(primaryConstructor),
superClassInit = superClassInit,
companion = companion,
modality = ClassStubModality.NONE,
origin = StubOrigin.VarOf(StubOrigin.Enum(enumDef)),
properties = listOf(valueProperty)
)
}
private fun EnumConstant.isMoreCanonicalThan(other: EnumConstant): Boolean = with(other.name.toLowerCase()) {
contains("min") || contains("max") ||
contains("first") || contains("last") ||
contains("begin") || contains("end")
}
/**
* Produces to [out] the Kotlin definitions for given enum which shouldn't be represented as Kotlin enum.
*/
private fun generateEnumAsConstants(enumDef: EnumDef): List<StubIrElement> {
// TODO: if this enum defines e.g. a type of struct field, then it should be generated inside the struct class
// to prevent name clashing
val entries = mutableListOf<PropertyStub>()
val typealiases = mutableListOf<TypealiasStub>()
val constants = enumDef.constants.filter {
// Macro "overrides" the original enum constant.
it.name !in context.macroConstantsByName
}
val kotlinType: KotlinType
val baseKotlinType = context.mirror(enumDef.baseType).argType
val meta = if (enumDef.isAnonymous) {
kotlinType = baseKotlinType
StubContainerMeta(textAtStart = if (constants.isNotEmpty()) "// ${enumDef.spelling}:" else "")
} else {
val typeMirror = context.mirror(EnumType(enumDef))
if (typeMirror !is TypeMirror.ByValue) {
error("unexpected enum type mirror: $typeMirror")
}
val varTypeName = typeMirror.info.constructPointedType(typeMirror.valueType)
val varTypeClassifier = typeMirror.pointedType.classifier
val valueTypeClassifier = typeMirror.valueType.classifier
val origin = StubOrigin.Enum(enumDef)
typealiases += TypealiasStub(varTypeClassifier, varTypeName.toStubIrType(), StubOrigin.VarOf(origin))
typealiases += TypealiasStub(valueTypeClassifier, baseKotlinType.toStubIrType(), origin)
kotlinType = typeMirror.valueType
StubContainerMeta()
}
for (constant in constants) {
val literal = context.tryCreateIntegralStub(enumDef.baseType, constant.value) ?: continue
val kind = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
val getter = PropertyAccessor.Getter.SimpleGetter(constant = literal)
PropertyStub.Kind.Val(getter)
}
GenerationMode.METADATA -> {
PropertyStub.Kind.Constant(literal)
}
}
entries += PropertyStub(
constant.name,
kotlinType.toStubIrType(),
kind,
MemberStubModality.FINAL,
null,
origin = StubOrigin.EnumEntry(constant)
)
}
val container = SimpleStubContainer(
meta,
properties = entries.toList(),
typealiases = typealiases.toList()
)
return listOf(container)
}
}
internal class FunctionStubBuilder(
override val context: StubsBuildingContext,
private val func: FunctionDecl,
private val skipOverloads: Boolean = false
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val platform = context.platform
val parameters = mutableListOf<FunctionParameterStub>()
var hasStableParameterNames = true
func.parameters.forEachIndexed { index, parameter ->
val parameterName = parameter.name.let {
if (it == null || it.isEmpty()) {
hasStableParameterNames = false
"arg$index"
} else {
it
}
}
val representAsValuesRef = representCFunctionParameterAsValuesRef(parameter.type)
parameters += when {
representCFunctionParameterAsString(func, parameter.type) -> {
val annotations = when (platform) {
KotlinPlatform.JVM -> emptyList()
KotlinPlatform.NATIVE -> listOf(AnnotationStub.CCall.CString)
}
val type = KotlinTypes.string.makeNullable().toStubIrType()
val functionParameterStub = FunctionParameterStub(parameterName, type, annotations)
context.bridgeComponentsBuilder.cStringParameters += functionParameterStub
functionParameterStub
}
representCFunctionParameterAsWString(func, parameter.type) -> {
val annotations = when (platform) {
KotlinPlatform.JVM -> emptyList()
KotlinPlatform.NATIVE -> listOf(AnnotationStub.CCall.WCString)
}
val type = KotlinTypes.string.makeNullable().toStubIrType()
val functionParameterStub = FunctionParameterStub(parameterName, type, annotations)
context.bridgeComponentsBuilder.wCStringParameters += functionParameterStub
functionParameterStub
}
representAsValuesRef != null -> {
FunctionParameterStub(parameterName, representAsValuesRef.toStubIrType())
}
else -> {
val mirror = context.mirror(parameter.type)
val type = mirror.argType.toStubIrType()
FunctionParameterStub(parameterName, type)
}
}
}
val returnType = if (func.returnsVoid()) {
KotlinTypes.unit
} else {
context.mirror(func.returnType).argType
}.toStubIrType()
if (skipOverloads && context.isOverloading(func))
return emptyList()
val annotations: List<AnnotationStub>
val mustBeExternal: Boolean
if (platform == KotlinPlatform.JVM) {
annotations = emptyList()
mustBeExternal = false
} else {
if (func.isVararg) {
val type = KotlinTypes.any.makeNullable().toStubIrType()
parameters += FunctionParameterStub("variadicArguments", type, isVararg = true)
}
annotations = listOf(AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${func.name}"))
mustBeExternal = true
}
val functionStub = FunctionStub(
func.name,
returnType,
parameters.toList(),
StubOrigin.Function(func),
annotations,
mustBeExternal,
null,
MemberStubModality.FINAL,
hasStableParameterNames = hasStableParameterNames
)
return listOf(functionStub)
}
private fun FunctionDecl.returnsVoid(): Boolean = this.returnType.unwrapTypedefs() is VoidType
private fun representCFunctionParameterAsValuesRef(type: Type): KotlinType? {
val pointeeType = when (type) {
is PointerType -> type.pointeeType
is ArrayType -> type.elemType
else -> return null
}
val unwrappedPointeeType = pointeeType.unwrapTypedefs()
if (unwrappedPointeeType is VoidType) {
// Represent `void*` as `CValuesRef<*>?`:
return KotlinTypes.cValuesRef.typeWith(StarProjection).makeNullable()
}
if (unwrappedPointeeType is FunctionType) {
// Don't represent function pointer as `CValuesRef<T>?` currently:
return null
}
if (unwrappedPointeeType is ArrayType) {
return representCFunctionParameterAsValuesRef(pointeeType)
}
return KotlinTypes.cValuesRef.typeWith(context.mirror(pointeeType).pointedType).makeNullable()
}
private val platformWStringTypes = setOf("LPCWSTR")
private val noStringConversion: Set<String>
get() = context.configuration.noStringConversion
private fun Type.isAliasOf(names: Set<String>): Boolean {
var type = this
while (type is Typedef) {
if (names.contains(type.def.name)) return true
type = type.def.aliased
}
return false
}
private fun representCFunctionParameterAsString(function: FunctionDecl, type: Type): Boolean {
val unwrappedType = type.unwrapTypedefs()
return unwrappedType is PointerType && unwrappedType.pointeeIsConst &&
unwrappedType.pointeeType.unwrapTypedefs() == CharType &&
!noStringConversion.contains(function.name)
}
// We take this approach as generic 'const short*' shall not be used as String.
private fun representCFunctionParameterAsWString(function: FunctionDecl, type: Type) = type.isAliasOf(platformWStringTypes)
&& !noStringConversion.contains(function.name)
}
internal class GlobalStubBuilder(
override val context: StubsBuildingContext,
private val global: GlobalDecl
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val mirror = context.mirror(global.type)
val unwrappedType = global.type.unwrapTypedefs()
val origin = StubOrigin.Global(global)
val kotlinType: KotlinType
val kind: PropertyStub.Kind
if (unwrappedType is ArrayType) {
kotlinType = (mirror as TypeMirror.ByValue).valueType
val getter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Getter.SimpleGetter().also {
val extra = BridgeGenerationInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.arrayGetterBridgeInfo[it] = extra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
}
}
kind = PropertyStub.Kind.Val(getter)
} else {
when (mirror) {
is TypeMirror.ByValue -> {
kotlinType = mirror.argType
val getter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Getter.SimpleGetter().also {
val getterExtra = BridgeGenerationInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.getterToBridgeInfo[it] = getterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
}
}
kind = if (global.isConst) {
PropertyStub.Kind.Val(getter)
} else {
val setter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Setter.SimpleSetter().also {
val setterExtra = BridgeGenerationInfo(global.name, mirror.info)
context.bridgeComponentsBuilder.setterToBridgeInfo[it] = setterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_setter")
PropertyAccessor.Setter.ExternalSetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.setterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
}
}
PropertyStub.Kind.Var(getter, setter)
}
}
is TypeMirror.ByRef -> {
kotlinType = mirror.pointedType
val getter = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
PropertyAccessor.Getter.InterpretPointed(global.name, kotlinType.toStubIrType())
}
GenerationMode.METADATA -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global, passViaPointer = true)
}
}
}
kind = PropertyStub.Kind.Val(getter)
}
}
}
return listOf(PropertyStub(global.name, kotlinType.toStubIrType(), kind, origin = origin))
}
}
internal class TypedefStubBuilder(
override val context: StubsBuildingContext,
private val typedefDef: TypedefDef
) : StubElementBuilder {
override fun build(): List<StubIrElement> {
val mirror = context.mirror(Typedef(typedefDef))
val baseMirror = context.mirror(typedefDef.aliased)
val varType = mirror.pointedType.classifier
val origin = StubOrigin.TypeDef(typedefDef)
return when (baseMirror) {
is TypeMirror.ByValue -> {
val valueType = (mirror as TypeMirror.ByValue).valueType
val varTypeAliasee = mirror.info.constructPointedType(valueType)
val valueTypeAliasee = baseMirror.valueType
listOf(
TypealiasStub(varType, varTypeAliasee.toStubIrType(), StubOrigin.VarOf(origin)),
TypealiasStub(valueType.classifier, valueTypeAliasee.toStubIrType(), origin)
)
}
is TypeMirror.ByRef -> {
val varTypeAliasee = baseMirror.pointedType
listOf(TypealiasStub(varType, varTypeAliasee.toStubIrType(), origin))
}
}
}
}
@@ -0,0 +1,100 @@
/*
* 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.ObjCProtocol
private val StubOrigin.ObjCMethod.isOptional: Boolean
get() = container is ObjCProtocol && method.isOptional
fun FunctionStub.isOptionalObjCMethod(): Boolean = this.origin is StubOrigin.ObjCMethod &&
this.origin.isOptional
val StubContainer.isInterface: Boolean
get() = if (this is ClassStub.Simple) {
modality == ClassStubModality.INTERFACE
} else {
false
}
/**
* Compute which names will be declared by [StubContainer] in the given [pkgName]
*/
fun StubContainer.computeNamesToBeDeclared(pkgName: String): List<String> {
fun checkPackageCorrectness(classifier: Classifier) {
assert(classifier.pkg == pkgName) {
"""Wrong classifier package.
|Expected: $pkgName
|Got: ${classifier.pkg}
|""".trimMargin()
}
}
val classNames = classes.mapNotNull {
when (it) {
is ClassStub.Simple -> it.classifier
is ClassStub.Companion -> null
is ClassStub.Enum -> it.classifier
}
}.onEach { checkPackageCorrectness(it) }.map { it.topLevelName }
val typealiasNames = typealiases
.onEach { checkPackageCorrectness(it.alias) }
.map { it.alias.topLevelName }
val namesFromNestedContainers = simpleContainers
.flatMap { it.computeNamesToBeDeclared(pkgName) }
return classNames + typealiasNames + namesFromNestedContainers
}
val StubContainer.defaultMemberModality: MemberStubModality
get() = when (this) {
is SimpleStubContainer -> MemberStubModality.FINAL
is ClassStub.Simple -> if (this.modality == ClassStubModality.INTERFACE) {
MemberStubModality.ABSTRACT
} else {
MemberStubModality.FINAL
}
is ClassStub.Companion -> MemberStubModality.FINAL
is ClassStub.Enum -> MemberStubModality.FINAL
}
/**
* Returns constructor that should be rendered in class header.
*/
val ClassStub.explicitPrimaryConstructor: ConstructorStub?
get() = functions.filterIsInstance<ConstructorStub>().firstOrNull(ConstructorStub::isPrimary)
fun ClassStub.nestedName(): String =
classifier.getRelativeFqName().substringAfterLast('.')
fun ConstantStub.determineConstantAnnotationClassifier(): Classifier = when (this) {
is StringConstantStub -> "String"
is IntegralConstantStub -> when (size) {
1 -> if (isSigned) "Byte" else "UByte"
2 -> if (isSigned) "Short" else "UShort"
4 -> if (isSigned) "Int" else "UInt"
8 -> if (isSigned) "Long" else "ULong"
else -> error("Integral constant with unexpected size of $size.")
}
is DoubleConstantStub -> when (size) {
4 -> "Float"
8 -> "Double"
else -> error("Real constant with unexpected size of $size.")
}
}.let { Classifier.topLevel(cinteropInternalPackage, "ConstantValue").nested(it) }
/**
* Returns the original name of the given type.
*/
val StubType.underlyingTypeFqName: String
get() = when (this) {
is ClassifierStubType -> classifier.fqName
is AbbreviatedType -> underlyingType.underlyingTypeFqName
is FunctionalType -> classifier.fqName
is TypeParameterType -> name
}
@@ -0,0 +1,600 @@
/*
* 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.*
import kotlinx.metadata.klib.*
import org.jetbrains.kotlin.metadata.serialization.Interner
import org.jetbrains.kotlin.utils.addIfNotNull
class StubIrMetadataEmitter(
private val context: StubIrContext,
private val builderResult: StubIrBuilderResult,
private val moduleName: String,
private val bridgeBuilderResult: BridgeBuilderResult
) {
fun emit(): KlibModuleMetadata {
val annotations = emptyList<KmAnnotation>()
val fragments = emitModuleFragments()
return KlibModuleMetadata(moduleName, fragments, annotations)
}
private fun emitModuleFragments(): List<KmModuleFragment> =
ModuleMetadataEmitter(
context.configuration.pkgName,
builderResult.stubs,
bridgeBuilderResult
).emit().let { kmModuleFragment ->
// We need to create module fragment for each part of package name.
val pkgName = context.configuration.pkgName
val fakePackages = pkgName.mapIndexedNotNull { idx, char ->
if (char == '.') idx else null
}.map { dotPosition ->
KmModuleFragment().also {
it.fqName = pkgName.substring(0, dotPosition)
}
}
fakePackages + kmModuleFragment
}
}
/**
* Translates single [StubContainer] to [KmModuleFragment].
*/
internal class ModuleMetadataEmitter(
private val packageFqName: String,
private val module: SimpleStubContainer,
private val bridgeBuilderResult: BridgeBuilderResult
) {
fun emit(): KmModuleFragment {
val context = VisitingContext(bridgeBuilderResult = bridgeBuilderResult)
val elements = KmElements(visitor.visitSimpleStubContainer(module, context))
return writeModule(elements)
}
private fun writeModule(elements: KmElements) = KmModuleFragment().also { km ->
km.fqName = packageFqName
km.classes += elements.classes.toList()
km.className += elements.classes.map(KmClass::name)
km.pkg = writePackage(elements)
}
private fun writePackage(elements: KmElements) = KmPackage().also { km ->
km.fqName = packageFqName
km.typeAliases += elements.typeAliases.toList()
km.properties += elements.properties.toList()
km.functions += elements.functions.toList()
}
/**
* StubIr translation result. Since Km* classes don't have common hierarchy we need
* to use list of Any.
*/
private class KmElements(result: List<Any>) {
val classes: List<KmClass> = result.filterIsInstance<List<KmClass>>().flatten()
val properties: List<KmProperty> = result.filterIsInstance<KmProperty>()
val typeAliases: List<KmTypeAlias> = result.filterIsInstance<KmTypeAlias>()
val functions: List<KmFunction> = result.filterIsInstance<KmFunction>()
val constructors: List<KmConstructor> = result.filterIsInstance<KmConstructor>()
}
/**
* Used to pass data between parents and children when visiting StubIr elements.
*/
private data class VisitingContext(
val container: StubContainer? = null,
val typeParametersInterner: Interner<TypeParameterStub> = Interner(),
val bridgeBuilderResult: BridgeBuilderResult
) {
inline fun <R> withMappingExtensions(block: MappingExtensions.() -> R) =
with (MappingExtensions(typeParametersInterner, bridgeBuilderResult), block)
}
private fun isTopLevelContainer(container: StubContainer?): Boolean =
container == null
private fun getPropertyNameInScope(property: PropertyStub, container: StubContainer?): String =
if (isTopLevelContainer(container)) {
getTopLevelPropertyDeclarationName(bridgeBuilderResult.kotlinFile, property)
} else {
property.name
}
private val visitor = object : StubIrVisitor<VisitingContext, Any?> {
override fun visitClass(element: ClassStub, data: VisitingContext): List<KmClass> {
val classVisitingContext = VisitingContext(
container = element,
typeParametersInterner = Interner(data.typeParametersInterner),
bridgeBuilderResult = data.bridgeBuilderResult
)
val children = element.children + if (element is ClassStub.Companion) {
listOf(ConstructorStub(isPrimary = true, visibility = VisibilityModifier.PRIVATE, origin = StubOrigin.Synthetic.DefaultConstructor))
} else emptyList()
val elements = KmElements(children.mapNotNull { it.accept(this, classVisitingContext) })
val kmClass = data.withMappingExtensions {
KmClass().also { km ->
element.annotations.mapTo(km.annotations) { it.map() }
km.flags = element.flags
km.name = element.classifier.fqNameSerialized
element.superClassInit?.let { km.supertypes += it.type.map() }
element.interfaces.mapTo(km.supertypes) { it.map() }
element.classes.mapTo(km.nestedClasses) { it.nestedName() }
km.typeAliases += elements.typeAliases.toList()
km.properties += elements.properties.toList()
km.functions += elements.functions.toList()
km.constructors += elements.constructors.toList()
km.companionObject = element.companion?.nestedName()
if (element is ClassStub.Enum) {
element.entries.mapTo(km.klibEnumEntries) { mapEnumEntry(it, classVisitingContext) }
}
}
}
// Metadata stores classes as flat list.
return listOf(kmClass) + elements.classes
}
override fun visitTypealias(element: TypealiasStub, data: VisitingContext): KmTypeAlias =
data.withMappingExtensions {
KmTypeAlias(element.flags, element.alias.topLevelName).also { km ->
km.underlyingType = element.aliasee.map(shouldExpandTypeAliases = false)
km.expandedType = element.aliasee.map()
}
}
override fun visitFunction(element: FunctionStub, data: VisitingContext) =
data.withMappingExtensions {
val function = if (bridgeBuilderResult.nativeBridges.isSupported(element)) {
element
} else {
element.copy(
external = false,
annotations = listOf(AnnotationStub.Deprecated.unableToImport)
)
}
KmFunction(function.flags, function.name).also { km ->
km.receiverParameterType = function.receiver?.type?.map()
function.typeParameters.mapTo(km.typeParameters) { it.map() }
function.parameters.mapTo(km.valueParameters) { it.map() }
function.annotations.mapTo(km.annotations) { it.map() }
km.returnType = function.returnType.map()
}
}
override fun visitProperty(element: PropertyStub, data: VisitingContext) =
data.withMappingExtensions {
val property = when (val bridgeSupportedKind = element.bridgeSupportedKind) {
null -> element.copy(
kind = PropertyStub.Kind.Val(PropertyAccessor.Getter.SimpleGetter()),
annotations = listOf(AnnotationStub.Deprecated.unableToImport)
)
element.kind -> element
else -> element.copy(kind = bridgeSupportedKind)
}
val name = getPropertyNameInScope(property, data.container)
KmProperty(property.flags, name, property.getterFlags, property.setterFlags).also { km ->
property.annotations.mapTo(km.annotations) { it.map() }
km.receiverParameterType = property.receiverType?.map()
km.returnType = property.type.map()
val kind = property.kind
if (kind is PropertyStub.Kind.Var) {
kind.setter.annotations.mapTo(km.setterAnnotations) { it.map() }
// TODO: Maybe it's better to explicitly add setter parameter in stub.
km.setterParameter = FunctionParameterStub("value", property.type).map()
}
km.getterAnnotations += when (kind) {
is PropertyStub.Kind.Val -> kind.getter.annotations.map { it.map() }
is PropertyStub.Kind.Var -> kind.getter.annotations.map { it.map() }
is PropertyStub.Kind.Constant -> emptyList()
}
if (kind is PropertyStub.Kind.Constant) {
km.compileTimeValue = kind.constant.mapToAnnotationArgument()
}
}
}
override fun visitConstructor(constructorStub: ConstructorStub, data: VisitingContext) =
data.withMappingExtensions {
KmConstructor(constructorStub.flags).apply {
constructorStub.parameters.mapTo(valueParameters, { it.map() })
constructorStub.annotations.mapTo(annotations, { it.map() })
}
}
override fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: VisitingContext) {
// TODO("not implemented")
}
override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: VisitingContext): List<Any> =
simpleStubContainer.children.mapNotNull { it.accept(this, data) } +
simpleStubContainer.simpleContainers.flatMap { visitSimpleStubContainer(it, data) }
private fun mapEnumEntry(enumEntry: EnumEntryStub, data: VisitingContext): KlibEnumEntry =
data.withMappingExtensions {
KlibEnumEntry(
name = enumEntry.name,
ordinal = enumEntry.ordinal,
annotations = mutableListOf(enumEntry.constant.mapToConstantAnnotation())
)
}
}
}
/**
* Collection of extension functions that simplify translation of
* StubIr elements to Kotlin Metadata.
*/
private class MappingExtensions(
private val typeParametersInterner: Interner<TypeParameterStub>,
private val bridgeBuilderResult: BridgeBuilderResult
) {
private fun flagsOfNotNull(vararg flags: Flag?): Flags =
flagsOf(*listOfNotNull(*flags).toTypedArray())
private fun <K, V> mapOfNotNull(vararg entries: Pair<K, V>?): Map<K, V> =
listOfNotNull(*entries).toMap()
private val VisibilityModifier.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC.takeIf { this == VisibilityModifier.PUBLIC },
Flag.IS_PROTECTED.takeIf { this == VisibilityModifier.PROTECTED },
Flag.IS_INTERNAL.takeIf { this == VisibilityModifier.INTERNAL },
Flag.IS_PRIVATE.takeIf { this == VisibilityModifier.PRIVATE }
)
private val MemberStubModality.flags: Flags
get() = flagsOfNotNull(
Flag.IS_FINAL.takeIf { this == MemberStubModality.FINAL },
Flag.IS_OPEN.takeIf { this == MemberStubModality.OPEN },
Flag.IS_ABSTRACT.takeIf { this == MemberStubModality.ABSTRACT }
)
val FunctionStub.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC,
Flag.Function.IS_EXTERNAL.takeIf { this.external },
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.Function.HAS_NON_STABLE_PARAMETER_NAMES.takeIf { !this.hasStableParameterNames }
) or modality.flags
val Classifier.fqNameSerialized: String
get() = buildString {
if (pkg.isNotEmpty()) {
append(pkg.replace('.', '/'))
append('/')
}
// Nested classes should dot-separated.
append(getRelativeFqName(asSimpleName = false))
}
val PropertyStub.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC,
Flag.Property.IS_DECLARATION,
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.Property.HAS_CONSTANT.takeIf { kind is PropertyStub.Kind.Constant },
Flag.Property.HAS_GETTER,
Flag.Property.HAS_SETTER.takeIf { kind is PropertyStub.Kind.Var },
when (kind) {
is PropertyStub.Kind.Val -> null
is PropertyStub.Kind.Var -> Flag.Property.IS_VAR
is PropertyStub.Kind.Constant -> Flag.Property.IS_CONST
}
) or modality.flags
val PropertyStub.getterFlags: Flags
get() = when (val kind = kind) {
is PropertyStub.Kind.Val -> kind.getter.flags(modality)
is PropertyStub.Kind.Var -> kind.getter.flags(modality)
is PropertyStub.Kind.Constant -> kind.flags
}
val PropertyStub.Kind.Constant.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC,
Flag.IS_FINAL
)
private fun PropertyAccessor.Getter.flags(propertyModality: MemberStubModality): Flags =
flagsOfNotNull(
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.IS_PUBLIC,
Flag.PropertyAccessor.IS_NOT_DEFAULT,
Flag.PropertyAccessor.IS_EXTERNAL.takeIf { this is PropertyAccessor.Getter.ExternalGetter }
) or propertyModality.flags
val PropertyStub.setterFlags: Flags
get() = when (val kind = kind) {
is PropertyStub.Kind.Var -> kind.setter.flags(modality)
else -> flagsOf()
}
private fun PropertyAccessor.Setter.flags(propertyModality: MemberStubModality): Flags =
flagsOfNotNull(
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.IS_PUBLIC,
Flag.PropertyAccessor.IS_NOT_DEFAULT,
Flag.PropertyAccessor.IS_EXTERNAL.takeIf { this is PropertyAccessor.Setter.ExternalSetter }
) or propertyModality.flags
val StubType.flags: Flags
get() = flagsOfNotNull(
Flag.Type.IS_NULLABLE.takeIf { nullable }
)
val TypealiasStub.flags: Flags
get() = flagsOfNotNull(
Flag.IS_PUBLIC
)
val FunctionParameterStub.flags: Flags
get() = flagsOfNotNull(
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() }
)
val ClassStub.flags: Flags
get() = flagsOfNotNull(
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() },
Flag.IS_PUBLIC,
Flag.IS_OPEN.takeIf { this is ClassStub.Simple && modality == ClassStubModality.OPEN },
Flag.IS_FINAL.takeIf { this is ClassStub.Simple && modality == ClassStubModality.NONE },
Flag.IS_ABSTRACT.takeIf { this is ClassStub.Simple
&& (modality == ClassStubModality.ABSTRACT || modality == ClassStubModality.INTERFACE) },
Flag.Class.IS_INTERFACE.takeIf { this is ClassStub.Simple && modality == ClassStubModality.INTERFACE },
Flag.Class.IS_COMPANION_OBJECT.takeIf { this is ClassStub.Companion },
Flag.Class.IS_CLASS.takeIf { this is ClassStub.Simple && modality != ClassStubModality.INTERFACE },
Flag.Class.IS_ENUM_CLASS.takeIf { this is ClassStub.Enum }
)
val ConstructorStub.flags: Flags
get() = flagsOfNotNull(
Flag.Constructor.IS_SECONDARY.takeIf { !isPrimary },
Flag.HAS_ANNOTATIONS.takeIf { annotations.isNotEmpty() }
) or visibility.flags
fun AnnotationStub.map(): KmAnnotation {
fun Pair<String, String>.asAnnotationArgument() =
(first to KmAnnotationArgument.StringValue(second)).takeIf { second.isNotEmpty() }
fun replaceWith(replaceWith: String) = KmAnnotationArgument.AnnotationValue(KmAnnotation(
Classifier.topLevel("kotlin", "ReplaceWith").fqNameSerialized,
mapOfNotNull(
"imports" to KmAnnotationArgument.ArrayValue(emptyList()),
("expression" to replaceWith).asAnnotationArgument()
)
))
fun deprecationLevel(level: DeprecationLevel) = KmAnnotationArgument.EnumValue(
Classifier.topLevel("kotlin", "DeprecationLevel").fqNameSerialized,
level.name
)
val args = when (this) {
AnnotationStub.ObjC.ConsumesReceiver -> emptyMap()
AnnotationStub.ObjC.ReturnsRetained -> emptyMap()
is AnnotationStub.ObjC.Method -> mapOfNotNull(
("selector" to selector).asAnnotationArgument(),
("encoding" to encoding).asAnnotationArgument(),
("isStret" to KmAnnotationArgument.BooleanValue(isStret))
)
is AnnotationStub.ObjC.Factory -> mapOfNotNull(
("selector" to selector).asAnnotationArgument(),
("encoding" to encoding).asAnnotationArgument(),
("isStret" to KmAnnotationArgument.BooleanValue(isStret))
)
AnnotationStub.ObjC.Consumed -> emptyMap()
is AnnotationStub.ObjC.Constructor -> mapOfNotNull(
("designated" to KmAnnotationArgument.BooleanValue(designated)),
("initSelector" to selector).asAnnotationArgument()
)
is AnnotationStub.ObjC.ExternalClass -> mapOfNotNull(
("protocolGetter" to protocolGetter).asAnnotationArgument(),
("binaryName" to binaryName).asAnnotationArgument()
)
AnnotationStub.CCall.CString -> emptyMap()
AnnotationStub.CCall.WCString -> emptyMap()
is AnnotationStub.CCall.Symbol -> mapOfNotNull(
("id" to symbolName).asAnnotationArgument()
)
is AnnotationStub.CStruct -> mapOfNotNull(
("spelling" to struct).asAnnotationArgument()
)
is AnnotationStub.CNaturalStruct ->
error("@CNaturalStruct should not be used for Kotlin/Native interop")
is AnnotationStub.CLength -> mapOfNotNull(
"value" to KmAnnotationArgument.LongValue(length)
)
is AnnotationStub.Deprecated -> mapOfNotNull(
("message" to message).asAnnotationArgument(),
("replaceWith" to replaceWith(replaceWith)),
("level" to deprecationLevel(level))
)
is AnnotationStub.CEnumEntryAlias -> mapOfNotNull(
("entryName" to entryName).asAnnotationArgument()
)
is AnnotationStub.CEnumVarTypeSize -> mapOfNotNull(
("size" to KmAnnotationArgument.IntValue(size))
)
is AnnotationStub.CStruct.MemberAt -> mapOfNotNull(
("offset" to KmAnnotationArgument.LongValue(offset))
)
is AnnotationStub.CStruct.ArrayMemberAt -> mapOfNotNull(
("offset" to KmAnnotationArgument.LongValue(offset))
)
is AnnotationStub.CStruct.BitField -> mapOfNotNull(
("offset" to KmAnnotationArgument.LongValue(offset)),
("size" to KmAnnotationArgument.IntValue(size))
)
is AnnotationStub.CStruct.VarType -> mapOfNotNull(
("size" to KmAnnotationArgument.LongValue(size)),
("align" to KmAnnotationArgument.IntValue(align))
)
}
return KmAnnotation(classifier.fqNameSerialized, args)
}
/**
* @param shouldExpandTypeAliases describes how should we write type aliases.
* If [shouldExpandTypeAliases] is true then type alias-based types are written as
* ```
* Type {
* abbreviatedType = AbbreviatedType.abbreviatedClassifier
* classifier = AbbreviatedType.underlyingType
* arguments = AbbreviatedType.underlyingType.typeArguments
* }
* ```
* So we basically replacing type alias with underlying class.
* Otherwise:
* ```
* Type {
* classifier = AbbreviatedType.abbreviatedClassifier
* }
* ```
* As of 25 Nov 2019, the latter form is used only for KmTypeAlias.underlyingType.
*/
// TODO: Add caching if needed.
fun StubType.map(shouldExpandTypeAliases: Boolean = true): KmType = when (this) {
is AbbreviatedType -> {
val typeAliasClassifier = KmClassifier.TypeAlias(abbreviatedClassifier.fqNameSerialized)
val typeArguments = typeArguments.map { it.map(shouldExpandTypeAliases) }
val abbreviatedType = KmType(flags).also { km ->
km.classifier = typeAliasClassifier
km.arguments += typeArguments
}
if (shouldExpandTypeAliases) {
// Abbreviated and expanded types have the same nullability.
KmType(flags).also { km ->
km.abbreviatedType = abbreviatedType
val kmUnderlyingType = underlyingType.map(true)
km.arguments += kmUnderlyingType.arguments
km.classifier = kmUnderlyingType.classifier
}
} else {
abbreviatedType
}
}
is ClassifierStubType -> KmType(flags).also { km ->
typeArguments.mapTo(km.arguments) { it.map(shouldExpandTypeAliases) }
km.classifier = KmClassifier.Class(classifier.fqNameSerialized)
}
is FunctionalType -> KmType(flags).also { km ->
typeArguments.mapTo(km.arguments) { it.map(shouldExpandTypeAliases) }
km.classifier = KmClassifier.Class(classifier.fqNameSerialized)
}
is TypeParameterType -> KmType(flags).also { km ->
km.classifier = KmClassifier.TypeParameter(id)
}
}
fun FunctionParameterStub.map(): KmValueParameter =
KmValueParameter(flags, name).also { km ->
val kmType = type.map()
if (isVararg) {
km.varargElementType = kmType
km.type = ClassifierStubType(
Classifier.topLevel("kotlin", "Array"),
listOf(TypeArgumentStub(type))
).map()
} else {
km.type = kmType
}
annotations.mapTo(km.annotations, { it.map() })
}
fun TypeParameterStub.map(): KmTypeParameter =
KmTypeParameter(flagsOf(), name, id, KmVariance.INVARIANT).also { km ->
km.upperBounds.addIfNotNull(upperBound?.map())
}
private fun TypeArgument.map(expanded: Boolean = true): KmTypeProjection = when (this) {
TypeArgument.StarProjection -> KmTypeProjection.STAR
is TypeArgumentStub -> KmTypeProjection(variance.map(), type.map(expanded))
else -> error("Unexpected TypeArgument: $this")
}
private fun TypeArgument.Variance.map(): KmVariance = when (this) {
TypeArgument.Variance.INVARIANT -> KmVariance.INVARIANT
TypeArgument.Variance.IN -> KmVariance.IN
TypeArgument.Variance.OUT -> KmVariance.OUT
}
fun ConstantStub.mapToAnnotationArgument(): KmAnnotationArgument<*> = when (this) {
is StringConstantStub -> KmAnnotationArgument.StringValue(value)
is IntegralConstantStub -> when (size) {
1 -> if (isSigned) {
KmAnnotationArgument.ByteValue(value.toByte())
} else {
KmAnnotationArgument.UByteValue(value.toByte())
}
2 -> if (isSigned) {
KmAnnotationArgument.ShortValue(value.toShort())
} else {
KmAnnotationArgument.UShortValue(value.toShort())
}
4 -> if (isSigned) {
KmAnnotationArgument.IntValue(value.toInt())
} else {
KmAnnotationArgument.UIntValue(value.toInt())
}
8 -> if (isSigned) {
KmAnnotationArgument.LongValue(value)
} else {
KmAnnotationArgument.ULongValue(value)
}
else -> error("Integral constant of value $value with unexpected size of $size.")
}
is DoubleConstantStub -> when (size) {
4 -> KmAnnotationArgument.FloatValue(value.toFloat())
8 -> KmAnnotationArgument.DoubleValue(value)
else -> error("Floating-point constant of value $value with unexpected size of $size.")
}
}
fun ConstantStub.mapToConstantAnnotation(): KmAnnotation =
KmAnnotation(
determineConstantAnnotationClassifier().fqNameSerialized,
mapOf("value" to mapToAnnotationArgument())
)
private val TypeParameterType.id: Int
get() = typeParameterDeclaration.id
private val TypeParameterStub.id: Int
get() = typeParametersInterner.intern(this)
/**
* Sometimes we can't generate bridge for getter or setter.
* For example, it may happen due to bug in libclang which may
* erroneously skip `const` qualifier of global variable.
*
* In this case we should change effective property's kind to either `val`
* or even omit the declaration at all.
*/
val PropertyStub.bridgeSupportedKind: PropertyStub.Kind?
get() = when (kind) {
is PropertyStub.Kind.Var -> {
val isGetterSupported = bridgeBuilderResult.nativeBridges.isSupported(kind.getter)
val isSetterSupported = bridgeBuilderResult.nativeBridges.isSupported(kind.setter)
when {
isGetterSupported && isSetterSupported -> kind
!isGetterSupported -> null
else -> PropertyStub.Kind.Val(kind.getter)
}
}
is PropertyStub.Kind.Val -> {
val isGetterSupported = bridgeBuilderResult.nativeBridges.isSupported(kind.getter)
if (isGetterSupported) {
kind
} else {
null
}
}
is PropertyStub.Kind.Constant -> kind
}
}
@@ -0,0 +1,663 @@
/*
* 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.addIfNotNull
import java.lang.IllegalStateException
/**
* Emits stubs and bridge functions as *.kt and *.c files.
* Many unintuitive printings are made for compatability with previous version of stub generator.
*
* [omitEmptyLines] is useful for testing output (e.g. diff calculating).
*/
class StubIrTextEmitter(
private val context: StubIrContext,
private val builderResult: StubIrBuilderResult,
private val bridgeBuilderResult: BridgeBuilderResult,
private val omitEmptyLines: Boolean = false
) {
private val kotlinFile = bridgeBuilderResult.kotlinFile
private val nativeBridges = bridgeBuilderResult.nativeBridges
private val propertyAccessorBridgeBodies = bridgeBuilderResult.propertyAccessorBridgeBodies
private val functionBridgeBodies = bridgeBuilderResult.functionBridgeBodies
private val pkgName: String
get() = context.configuration.pkgName
private val StubContainer.isTopLevelContainer: Boolean
get() = this == builderResult.stubs || this in builderResult.stubs.simpleContainers
/**
* The output currently used by the generator.
* Should append line separator after any usage.
*/
private var out: (String) -> Unit = {
throw IllegalStateException()
}
private fun emitEmptyLine() {
if (!omitEmptyLines) {
out("")
}
}
private fun <R> withOutput(output: (String) -> Unit, action: () -> R): R {
val oldOut = out
out = output
try {
return action()
} finally {
out = oldOut
}
}
private fun <R> withOutput(appendable: Appendable, action: () -> R): R {
return withOutput({ appendable.appendLine(it) }, action)
}
private fun generateLinesBy(action: () -> Unit): List<String> {
val result = mutableListOf<String>()
withOutput({ result.add(it) }, action)
return result
}
private fun generateKotlinFragmentBy(block: () -> Unit): Sequence<String> {
val lines = generateLinesBy(block)
return lines.asSequence()
}
private fun <R> indent(action: () -> R): R {
val oldOut = out
return withOutput({ oldOut(" $it") }, action)
}
private fun <R> block(header: String, body: () -> R): R {
out("$header {")
val res = indent {
body()
}
out("}")
return res
}
private fun emitKotlinFileHeader() {
if (context.platform == KotlinPlatform.JVM) {
out("@file:JvmName(${context.jvmFileClassName.quoteAsKotlinLiteral()})")
}
if (context.platform == KotlinPlatform.NATIVE) {
out("@file:kotlinx.cinterop.InteropStubs")
}
val suppress = mutableListOf("UNUSED_VARIABLE", "UNUSED_EXPRESSION").apply {
add("DEPRECATION") // CVariable.Type and CEnum companion deprecations.
if (context.configuration.library.language == Language.OBJECTIVE_C) {
add("CONFLICTING_OVERLOADS")
add("RETURN_TYPE_MISMATCH_ON_INHERITANCE")
add("PROPERTY_TYPE_MISMATCH_ON_INHERITANCE") // Multiple-inheriting property with conflicting types
add("VAR_TYPE_MISMATCH_ON_INHERITANCE") // Multiple-inheriting mutable property with conflicting types
add("RETURN_TYPE_MISMATCH_ON_OVERRIDE")
add("WRONG_MODIFIER_CONTAINING_DECLARATION") // For `final val` in interface.
add("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
add("UNUSED_PARAMETER") // For constructors.
add("MANY_IMPL_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties.
add("MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties.
add("EXTENSION_SHADOWED_BY_MEMBER") // For Objective-C categories represented as extensions.
add("REDUNDANT_NULLABLE") // This warning appears due to Obj-C typedef nullability incomplete support.
add("DEPRECATION") // For uncheckedCast.
add("DEPRECATION_ERROR") // For initializers.
}
}
out("@file:Suppress(${suppress.joinToString { it.quoteAsKotlinLiteral() }})")
if (pkgName != "") {
out("package ${context.validPackageName}")
out("")
}
if (context.platform == KotlinPlatform.NATIVE) {
out("import kotlin.native.SymbolName")
out("import kotlinx.cinterop.internal.*")
}
out("import kotlinx.cinterop.*")
kotlinFile.buildImports().forEach {
out(it)
}
out("")
out("// NOTE THIS FILE IS AUTO-GENERATED")
}
fun emit(ktFile: Appendable) {
// Stubs generation may affect imports list so do it before header generation.
val stubLines = generateKotlinFragmentBy {
printer.visitSimpleStubContainer(builderResult.stubs, null)
}
withOutput(ktFile) {
emitKotlinFileHeader()
stubLines.forEach(out)
nativeBridges.kotlinLines.forEach(out)
if (context.platform == KotlinPlatform.JVM)
out("private val loadLibrary = loadKonanLibrary(\"${context.libName}\")")
}
}
private val printer = object : StubIrVisitor<StubContainer?, Unit> {
override fun visitClass(element: ClassStub, data: StubContainer?) {
element.annotations.forEach {
out(renderAnnotation(it))
}
val header = renderClassHeader(element)
when {
element.children.isEmpty() -> out(header)
else -> block(header) {
if (element is ClassStub.Enum) {
emitEnumEntries(element)
}
element.children
// We render a primary constructor as part of a header.
.filterNot { it is ConstructorStub && it.isPrimary }
.forEach {
emitEmptyLine()
it.accept(this, element)
}
if (element is ClassStub.Enum) {
emitEnumVarClass(element)
}
}
}
}
override fun visitTypealias(element: TypealiasStub, data: StubContainer?) {
val alias = renderClassifierDeclaration(element.alias)
val aliasee = renderStubType(element.aliasee)
out("typealias $alias = $aliasee")
}
override fun visitFunction(element: FunctionStub, data: StubContainer?) {
if (element in bridgeBuilderResult.excludedStubs) return
val header = run {
val parameters = element.parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val receiver = element.receiver?.let { renderFunctionReceiver(it) + "." } ?: ""
val typeParameters = renderTypeParameters(element.typeParameters)
val override = if (element.isOverride) "override " else ""
val modality = renderMemberModality(element.modality, data)
"$override${modality}fun$typeParameters $receiver${element.name.asSimpleName()}$parameters: ${renderStubType(element.returnType)}"
}
if (!nativeBridges.isSupported(element)) {
sequenceOf(
annotationForUnableToImport,
"$header = throw UnsupportedOperationException()"
).forEach(out)
return
}
element.annotations.forEach {
out(renderAnnotation(it))
}
when {
element.external -> out("external $header")
element.isOptionalObjCMethod() -> out("$header = optional()")
element.origin is StubOrigin.Synthetic.EnumByValue ->
out("$header = values().find { it.value == value }!!")
data != null && data.isInterface -> out(header)
else -> block(header) {
functionBridgeBodies.getValue(element).forEach(out)
}
}
}
override fun visitProperty(element: PropertyStub, data: StubContainer?) =
emitProperty(element, data)
override fun visitConstructor(constructorStub: ConstructorStub, data: StubContainer?) {
constructorStub.annotations.forEach {
out(renderAnnotation(it))
}
val visibility = renderVisibilityModifier(constructorStub.visibility)
out("${visibility}constructor(${constructorStub.parameters.joinToString { renderFunctionParameter(it) }}) {}")
}
override fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: StubContainer?) {
}
override fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: StubContainer?) {
if (simpleStubContainer.meta.textAtStart.isNotEmpty()) {
out(simpleStubContainer.meta.textAtStart)
}
simpleStubContainer.classes.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
simpleStubContainer.functions.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
simpleStubContainer.properties.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
simpleStubContainer.typealiases.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
simpleStubContainer.simpleContainers.forEach {
emitEmptyLine()
it.accept(this, simpleStubContainer)
}
if (simpleStubContainer.meta.textAtEnd.isNotEmpty()) {
out(simpleStubContainer.meta.textAtEnd)
}
}
}
// About method naming convention:
// - "emit" prefix means that method will call `out` by itself.
// - "render" prefix means that method returns string that should be emitted by caller.
private fun emitEnumEntries(enum: ClassStub.Enum) {
enum.entries.forEach {
out(renderEnumEntry(it) + ",")
}
out(";")
}
private fun emitEnumVarClass(enum: ClassStub.Enum) {
val simpleKotlinName = enum.classifier.topLevelName.asSimpleName()
val typeMirror = builderResult.bridgeGenerationComponents.enumToTypeMirror.getValue(enum)
val basePointedTypeName = typeMirror.pointedType.render(kotlinFile)
block("class Var(rawPtr: NativePtr) : CEnumVar(rawPtr)") {
out("@Deprecated(\"Use sizeOf<T>() or alignOf<T>() instead.\")")
out("companion object : Type(sizeOf<$basePointedTypeName>().toInt())")
out("var value: $simpleKotlinName")
out(" get() = byValue(this.reinterpret<$basePointedTypeName>().value)")
out(" set(value) { this.reinterpret<$basePointedTypeName>().value = value.value }")
}
}
private fun emitProperty(element: PropertyStub, owner: StubContainer?) {
if (element in bridgeBuilderResult.excludedStubs) return
val override = if (element.isOverride) "override " else ""
val modality = "$override${renderMemberModality(element.modality, owner)}"
val receiver = if (element.receiverType != null) "${renderStubType(element.receiverType)}." else ""
val name = if (owner?.isTopLevelContainer == true) {
getTopLevelPropertyDeclarationName(kotlinFile, element).asSimpleName()
} else {
element.name.asSimpleName()
}
val header = "$receiver$name: ${renderStubType(element.type)}"
if (element.kind is PropertyStub.Kind.Val && !nativeBridges.isSupported(element.kind.getter)
|| element.kind is PropertyStub.Kind.Var && !nativeBridges.isSupported(element.kind.getter)) {
out(annotationForUnableToImport)
out("val $header")
out(" get() = TODO()")
} else {
element.annotations.forEach {
out(renderAnnotation(it))
}
when (val kind = element.kind) {
is PropertyStub.Kind.Constant -> {
out("${modality}const val $header = ${renderValueUsage(kind.constant)}")
}
is PropertyStub.Kind.Val -> {
val shouldWriteInline = kind.getter.let {
(it is PropertyAccessor.Getter.SimpleGetter && it.constant != null)
// We should render access to constructor parameter inline.
// Otherwise, it may be access to the property itself. (val f: Any get() = f)
|| it is PropertyAccessor.Getter.GetConstructorParameter
}
if (shouldWriteInline) {
out("${modality}val $header ${renderGetter(kind.getter)}")
} else {
out("${modality}val $header")
indent {
out(renderGetter(kind.getter))
}
}
}
is PropertyStub.Kind.Var -> {
val isSupported = nativeBridges.isSupported(kind.setter)
val variableKind = if (isSupported) "var" else "val"
out("$modality$variableKind $header")
indent {
out(renderGetter(kind.getter))
if (isSupported) {
out(renderSetter(kind.setter))
}
}
}
}
}
}
private fun renderFunctionReceiver(receiver: ReceiverParameterStub): String {
return renderStubType(receiver.type)
}
private fun renderFunctionParameter(parameter: FunctionParameterStub): String {
val annotations = if (parameter.annotations.isEmpty())
""
else
parameter.annotations.joinToString(separator = " ") { renderAnnotation(it) } + " "
val vararg = if (parameter.isVararg) "vararg " else ""
return "$annotations$vararg${parameter.name.asSimpleName()}: ${renderStubType(parameter.type)}"
}
private fun renderMemberModality(modality: MemberStubModality, container: StubContainer?): String =
if (container?.defaultMemberModality == modality) {
""
} else
when (modality) {
MemberStubModality.OPEN -> "open "
MemberStubModality.FINAL -> "final "
MemberStubModality.ABSTRACT -> "abstract "
}
private fun renderVisibilityModifier(visibilityModifier: VisibilityModifier) = when (visibilityModifier) {
VisibilityModifier.PRIVATE -> "private "
VisibilityModifier.PROTECTED -> "protected "
VisibilityModifier.INTERNAL -> "internal "
VisibilityModifier.PUBLIC -> ""
}
private fun renderClassHeader(classStub: ClassStub): String {
val modality = when (classStub) {
is ClassStub.Simple -> renderClassStubModality(classStub.modality)
is ClassStub.Companion -> ""
is ClassStub.Enum -> "enum class "
}
val className = when (classStub) {
is ClassStub.Simple -> renderClassifierDeclaration(classStub.classifier)
is ClassStub.Companion -> "companion object"
is ClassStub.Enum -> renderClassifierDeclaration(classStub.classifier)
}
val constructorParams = classStub.explicitPrimaryConstructor?.parameters?.let(this::renderConstructorParams) ?: ""
val inheritance = mutableListOf<String>().apply {
// Enum inheritance is implicit.
if (classStub !is ClassStub.Enum) {
addIfNotNull(classStub.superClassInit?.let { renderSuperInit(it) })
}
addAll(classStub.interfaces.map { renderStubType(it) })
}.let { if (it.isNotEmpty()) " : ${it.joinToString()}" else "" }
return "$modality$className$constructorParams$inheritance"
}
private fun renderClassifierDeclaration(classifier: Classifier): String =
kotlinFile.declare(classifier).asSimpleName()
private fun renderClassStubModality(classStubModality: ClassStubModality): String = when (classStubModality) {
ClassStubModality.INTERFACE -> "interface "
ClassStubModality.OPEN -> "open class "
ClassStubModality.ABSTRACT -> "abstract class "
ClassStubModality.NONE -> "class "
}
private fun renderConstructorParams(parameters: List<FunctionParameterStub>): String =
if (parameters.isEmpty()) {
""
} else {
parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
}
private fun renderSuperInit(superClassInit: SuperClassInit): String {
val parameters = superClassInit.arguments.joinToString(prefix = "(", postfix = ")") { renderValueUsage(it) }
return "${renderStubType(superClassInit.type)}$parameters"
}
private fun renderStubType(stubType: StubType): String {
val nullable = if (stubType.nullable) "?" else ""
return when (stubType) {
is ClassifierStubType -> {
val classifier = kotlinFile.reference(stubType.classifier)
val typeArguments = renderTypeArguments(stubType.typeArguments)
"$classifier$typeArguments$nullable"
}
is FunctionalType -> buildString {
if (stubType.nullable) append("(")
append('(')
stubType.parameterTypes.joinTo(this) { renderStubType(it) }
append(") -> ")
append(renderStubType(stubType.returnType))
if (stubType.nullable) append(")?")
}
is TypeParameterType -> "${stubType.name}$nullable"
is AbbreviatedType -> {
val classifier = kotlinFile.reference(stubType.abbreviatedClassifier)
val typeArguments = renderTypeArguments(stubType.typeArguments)
"$classifier$typeArguments$nullable"
}
}
}
private fun renderValueUsage(value: ValueStub): String = when (value) {
is StringConstantStub -> value.value.quoteAsKotlinLiteral()
is IntegralConstantStub -> renderIntegralConstant(value)!!
is DoubleConstantStub -> renderDoubleConstant(value)!!
is GetConstructorParameter -> value.constructorParameterStub.name
}
private fun renderAnnotation(annotationStub: AnnotationStub): String = when (annotationStub) {
AnnotationStub.ObjC.ConsumesReceiver -> "@CCall.ConsumesReceiver"
AnnotationStub.ObjC.ReturnsRetained -> "@CCall.ReturnsRetained"
is AnnotationStub.ObjC.Method -> {
val stret = if (annotationStub.isStret) ", true" else ""
val selector = annotationStub.selector.quoteAsKotlinLiteral()
val encoding = annotationStub.encoding.quoteAsKotlinLiteral()
"@ObjCMethod($selector, $encoding$stret)"
}
is AnnotationStub.ObjC.Factory -> {
val stret = if (annotationStub.isStret) ", true" else ""
val selector = annotationStub.selector.quoteAsKotlinLiteral()
val encoding = annotationStub.encoding.quoteAsKotlinLiteral()
"@ObjCFactory($selector, $encoding$stret)"
}
AnnotationStub.ObjC.Consumed ->
"@CCall.Consumed"
is AnnotationStub.ObjC.Constructor ->
"@ObjCConstructor(${annotationStub.selector.quoteAsKotlinLiteral()}, ${annotationStub.designated})"
is AnnotationStub.ObjC.ExternalClass -> {
val protocolGetter = annotationStub.protocolGetter.quoteAsKotlinLiteral()
val binaryName = annotationStub.binaryName.quoteAsKotlinLiteral()
"@ExternalObjCClass" + when {
annotationStub.protocolGetter.isEmpty() && annotationStub.binaryName.isEmpty() -> ""
annotationStub.protocolGetter.isEmpty() -> "(\"\", $binaryName)"
annotationStub.binaryName.isEmpty() -> "($protocolGetter)"
else -> "($protocolGetter, $binaryName)"
}
}
AnnotationStub.CCall.CString ->
"@CCall.CString"
AnnotationStub.CCall.WCString ->
"@CCall.WCString"
is AnnotationStub.CCall.Symbol ->
"@CCall(${annotationStub.symbolName.quoteAsKotlinLiteral()})"
is AnnotationStub.CStruct ->
"@CStruct(${annotationStub.struct.quoteAsKotlinLiteral()})"
is AnnotationStub.CNaturalStruct ->
"@CNaturalStruct(${annotationStub.members.joinToString { it.name.quoteAsKotlinLiteral() }})"
is AnnotationStub.CLength ->
"@CLength(${annotationStub.length})"
is AnnotationStub.Deprecated ->
"@Deprecated(${annotationStub.message.quoteAsKotlinLiteral()}, " +
"ReplaceWith(${annotationStub.replaceWith.quoteAsKotlinLiteral()}), " +
"DeprecationLevel.${annotationStub.level.name})"
is AnnotationStub.CEnumEntryAlias,
is AnnotationStub.CEnumVarTypeSize,
is AnnotationStub.CStruct.MemberAt,
is AnnotationStub.CStruct.ArrayMemberAt,
is AnnotationStub.CStruct.BitField,
is AnnotationStub.CStruct.VarType ->
error("${annotationStub.classifier.fqName} annotation is unsupported in textual mode")
}
private fun renderEnumEntry(enumEntryStub: EnumEntryStub): String =
"${enumEntryStub.name.asSimpleName()}(${renderValueUsage(enumEntryStub.constant)})"
private fun renderGetter(accessor: PropertyAccessor.Getter): String {
val annotations = accessor.annotations.joinToString(separator = "") { renderAnnotation(it) + " " }
return annotations + when (accessor) {
is PropertyAccessor.Getter.ExternalGetter -> {
"external get"
}
is PropertyAccessor.Getter.GetConstructorParameter -> "= ${renderPropertyAccessorBody(accessor)}"
else -> {
"get() = ${renderPropertyAccessorBody(accessor)}"
}
}
}
private fun renderSetter(accessor: PropertyAccessor.Setter): String {
val annotations = accessor.annotations.joinToString(separator = "") { renderAnnotation(it) + " " }
return annotations + if (accessor is PropertyAccessor.Setter.ExternalSetter) {
"external set"
} else {
"set(value) { ${renderPropertyAccessorBody(accessor)} }"
}
}
private fun renderPropertyAccessorBody(accessor: PropertyAccessor): String = when (accessor) {
is PropertyAccessor.Getter.SimpleGetter -> {
when {
accessor in propertyAccessorBridgeBodies -> propertyAccessorBridgeBodies.getValue(accessor)
accessor.constant != null -> renderValueUsage(accessor.constant)
else -> error("Bridge body for getter was not generated")
}
}
is PropertyAccessor.Getter.GetConstructorParameter -> accessor.constructorParameter.name
is PropertyAccessor.Getter.ArrayMemberAt -> "arrayMemberAt(${accessor.offset})"
is PropertyAccessor.Getter.MemberAt -> {
val typeArguments = renderTypeArguments(accessor.typeArguments)
val valueAccess = if (accessor.hasValueAccessor) ".value" else ""
"memberAt$typeArguments(${accessor.offset})$valueAccess"
}
is PropertyAccessor.Getter.ReadBits -> {
propertyAccessorBridgeBodies.getValue(accessor)
}
is PropertyAccessor.Getter.GetEnumEntry -> accessor.enumEntryStub.name
is PropertyAccessor.Setter.SimpleSetter -> when {
accessor in propertyAccessorBridgeBodies -> propertyAccessorBridgeBodies.getValue(accessor)
else -> error("Bridge body for setter was not generated")
}
is PropertyAccessor.Setter.MemberAt -> {
if (accessor.typeArguments.isEmpty()) {
error("Unexpected memberAt setter without type parameters!")
} else {
val typeArguments = renderTypeArguments(accessor.typeArguments)
"memberAt$typeArguments(${accessor.offset}).value = value"
}
}
is PropertyAccessor.Setter.WriteBits -> {
propertyAccessorBridgeBodies.getValue(accessor)
}
is PropertyAccessor.Getter.InterpretPointed -> {
val typeParameters = accessor.typeParameters.joinToString(prefix = "<", postfix = ">") { renderStubType(it) }
val getAddressExpression = propertyAccessorBridgeBodies.getValue(accessor)
"interpretPointed$typeParameters($getAddressExpression)"
}
is PropertyAccessor.Getter.ExternalGetter,
is PropertyAccessor.Setter.ExternalSetter -> error("External property accessor shouldn't have a body!")
}
private fun renderIntegralConstant(integralValue: IntegralConstantStub): String? {
val (value, size, isSigned) = integralValue
return if (isSigned) {
if (value == Long.MIN_VALUE) {
return "${value + 1} - 1" // Workaround for "The value is out of range" compile error.
}
val narrowedValue: Number = when (size) {
1 -> value.toByte()
2 -> value.toShort()
4 -> value.toInt()
8 -> value
else -> return null
}
narrowedValue.toString()
} else {
// Note: stub generator is built and run with different ABI versions,
// so Kotlin unsigned types can't be used here currently.
val narrowedValue: String = when (size) {
1 -> (value and 0xFF).toString()
2 -> (value and 0xFFFF).toString()
4 -> (value and 0xFFFFFFFF).toString()
8 -> java.lang.Long.toUnsignedString(value)
else -> return null
}
"${narrowedValue}u"
}
}
private fun renderDoubleConstant(doubleValue: DoubleConstantStub): String? {
val (value, size) = doubleValue
return when (size) {
4 -> {
val floatValue = value.toFloat()
val bits = java.lang.Float.floatToRawIntBits(floatValue)
"bitsToFloat($bits) /* == $floatValue */"
}
8 -> {
val bits = java.lang.Double.doubleToRawLongBits(value)
"bitsToDouble($bits) /* == $value */"
}
else -> null
}
}
private fun renderTypeArguments(typeArguments: List<TypeArgument>) = if (typeArguments.isNotEmpty()) {
typeArguments.joinToString(", ", "<", ">") { renderTypeArgument(it) }
} else {
""
}
private fun renderTypeArgument(typeArgument: TypeArgument) = when (typeArgument) {
is TypeArgumentStub -> {
val variance = when (typeArgument.variance) {
TypeArgument.Variance.INVARIANT -> ""
TypeArgument.Variance.IN -> "in "
TypeArgument.Variance.OUT -> "out "
}
"$variance${renderStubType(typeArgument.type)}"
}
TypeArgument.StarProjection -> "*"
else -> error("Unexpected type argument: $typeArgument")
}
private fun renderTypeParameters(typeParameters: List<TypeParameterStub>) = if (typeParameters.isNotEmpty()) {
typeParameters.joinToString(", ", " <", ">") { renderTypeParameter(it) }
} else {
""
}
private fun renderTypeParameter(typeParameterStub: TypeParameterStub): String {
val name = typeParameterStub.name
return typeParameterStub.upperBound?.let {
"$name : ${renderStubType(it)}"
} ?: name
}
}
@@ -0,0 +1,223 @@
/*
* 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.utils.addToStdlib.ifNotEmpty
sealed class StubType {
abstract val nullable: Boolean
abstract val typeArguments: List<TypeArgument>
}
/**
* Wrapper over [Classifier].
*/
class ClassifierStubType(
val classifier: Classifier,
override val typeArguments: List<TypeArgument> = emptyList(),
override val nullable: Boolean = false
) : StubType() {
fun nested(name: String): ClassifierStubType =
ClassifierStubType(classifier.nested(name))
override fun toString(): String =
"${classifier.topLevelName}${typeArguments.ifNotEmpty { joinToString(prefix = "<", postfix = ">") } ?: ""}"
}
class AbbreviatedType(
val underlyingType: StubType,
val abbreviatedClassifier: Classifier,
override val typeArguments: List<TypeArgument>,
override val nullable: Boolean = false
) : StubType() {
override fun toString(): String =
"${abbreviatedClassifier.topLevelName}${typeArguments.ifNotEmpty { joinToString(prefix = "<", postfix = ">") } ?: ""}"
}
/**
* @return type from kotlinx.cinterop package
*/
fun KotlinPlatform.getRuntimeType(name: String, nullable: Boolean = false): StubType {
val classifier = Classifier.topLevel(cinteropPackage, name)
PredefinedTypesHandler.tryExpandPlatformDependentTypealias(classifier, this, nullable)?.let { return it }
return ClassifierStubType(classifier, nullable = nullable)
}
/**
* Functional type from kotlin package: ([parameterTypes]) -> [returnType]
*/
class FunctionalType(
val parameterTypes: List<StubType>, // TODO: Use TypeArguments.
val returnType: StubType,
override val nullable: Boolean = false
) : StubType() {
val classifier: Classifier =
Classifier.topLevel("kotlin", "Function${parameterTypes.size}")
override val typeArguments: List<TypeArgument> by lazy {
listOf(*parameterTypes.toTypedArray(), returnType).map { TypeArgumentStub(it) }
}
}
class TypeParameterType(
val name: String,
override val nullable: Boolean,
val typeParameterDeclaration: TypeParameterStub
) : StubType() {
override val typeArguments: List<TypeArgument> = emptyList()
}
fun KotlinType.toStubIrType(): StubType = when (this) {
is KotlinFunctionType -> this.toStubIrType()
is KotlinClassifierType -> this.toStubIrType()
else -> error("Unexpected KotlinType: $this")
}
private fun KotlinFunctionType.toStubIrType(): StubType =
FunctionalType(parameterTypes.map(KotlinType::toStubIrType), returnType.toStubIrType(), nullable)
private fun KotlinClassifierType.toStubIrType(): StubType {
val typeArguments = arguments.map(KotlinTypeArgument::toStubIrType)
PredefinedTypesHandler.tryExpandPredefinedTypealias(classifier, nullable, typeArguments)?.let { return it }
return if (underlyingType == null) {
ClassifierStubType(classifier, typeArguments, nullable)
} else {
AbbreviatedType(underlyingType.toStubIrType(), classifier, typeArguments, nullable)
}
}
private fun KotlinTypeArgument.toStubIrType(): TypeArgument = when (this) {
is KotlinType -> TypeArgumentStub(this.toStubIrType())
StarProjection -> TypeArgument.StarProjection
else -> error("Unexpected KotlinTypeArgument: $this")
}
/**
* Types that come from kotlinx.cinterop require special handling because we
* don't have explicit information about their structure.
* For example, to be able to produce metadata-based interop library we need to know
* that ByteVar is a typealias to ByteVarOf<Byte>.
*/
private object PredefinedTypesHandler {
private const val cInteropPackage = "kotlinx.cinterop"
private val nativePtrClassifier = Classifier.topLevel(cInteropPackage, "NativePtr")
private val primitives = setOf(
KotlinTypes.boolean,
KotlinTypes.byte, KotlinTypes.short, KotlinTypes.int, KotlinTypes.long,
KotlinTypes.uByte, KotlinTypes.uShort, KotlinTypes.uInt, KotlinTypes.uLong,
KotlinTypes.float, KotlinTypes.double,
KotlinTypes.vector128
)
/**
* kotlinx.cinterop.{primitive}Var -> kotlin.{primitive}
*/
private val primitiveVarClassifierToPrimitiveType: Map<Classifier, KotlinClassifierType> =
primitives.associateBy {
val typeVar = "${it.classifier.topLevelName}Var"
Classifier.topLevel(cInteropPackage, typeVar)
}
/**
* @param primitiveType primitive type from kotlin package.
* @return kotlinx.cinterop.[primitiveType]VarOf<[primitiveType]>
*/
private fun getVarOfTypeFor(primitiveType: KotlinClassifierType, nullable: Boolean): ClassifierStubType {
val typeVarOf = "${primitiveType.classifier.topLevelName}VarOf"
val classifier = Classifier.topLevel(cInteropPackage, typeVarOf)
return ClassifierStubType(classifier, listOf(TypeArgumentStub(primitiveType.toStubIrType())), nullable = nullable)
}
private fun expandCOpaquePointerVar(nullable: Boolean): AbbreviatedType {
val typeArgument = TypeArgumentStub(expandCOpaquePointer(nullable=false))
val underlyingType = ClassifierStubType(
KotlinTypes.cPointerVarOf, listOf(typeArgument), nullable = nullable
)
return AbbreviatedType(underlyingType, KotlinTypes.cOpaquePointerVar.classifier, emptyList(), nullable)
}
private fun expandCOpaquePointer(nullable: Boolean): AbbreviatedType {
val typeArgument = TypeArgumentStub(ClassifierStubType(KotlinTypes.cPointed), TypeArgument.Variance.OUT)
val underlyingType = ClassifierStubType(
KotlinTypes.cPointer, listOf(typeArgument), nullable = nullable
)
return AbbreviatedType(underlyingType, KotlinTypes.cOpaquePointer.classifier, emptyList(), nullable)
}
private fun expandCPointerVar(typeArguments: List<TypeArgument>, nullable: Boolean): AbbreviatedType {
require(typeArguments.size == 1) { "CPointerVar has only one type argument." }
val cPointer = ClassifierStubType(KotlinTypes.cPointer, typeArguments)
val cPointerVarOfTypeArgument = TypeArgumentStub(cPointer)
val underlyingType = ClassifierStubType(
KotlinTypes.cPointerVarOf, listOf(cPointerVarOfTypeArgument), nullable = nullable
)
return AbbreviatedType(underlyingType, KotlinTypes.cPointerVar, typeArguments, nullable)
}
/**
* @param primitiveVarType one of kotlinx.cinterop.{primitive}Var types.
* @return typealias in terms of StubIR types.
*/
private fun expandPrimitiveVarType(primitiveVarClassifier: Classifier, nullable: Boolean): AbbreviatedType {
val primitiveType = primitiveVarClassifierToPrimitiveType.getValue(primitiveVarClassifier)
val underlyingType = getVarOfTypeFor(primitiveType, nullable)
return AbbreviatedType(underlyingType, primitiveVarClassifier, listOf(), nullable)
}
private fun expandNativePtr(platform: KotlinPlatform, nullable: Boolean): StubType {
val underlyingTypeClassifier = when (platform) {
KotlinPlatform.JVM -> KotlinTypes.long.classifier
KotlinPlatform.NATIVE -> Classifier.topLevel("kotlin.native.internal", "NativePtr")
}
val underlyingType = ClassifierStubType(underlyingTypeClassifier, nullable = nullable)
return AbbreviatedType(underlyingType, nativePtrClassifier, listOf(), nullable)
}
private fun expandObjCObjectMeta(typeArguments: List<TypeArgument>, nullable: Boolean): AbbreviatedType {
require(typeArguments.isEmpty())
val objCClass = ClassifierStubType(KotlinTypes.objCClass, emptyList(), nullable)
return AbbreviatedType(objCClass, KotlinTypes.objCObjectMeta, emptyList(), nullable)
}
private fun expandCArrayPointer(typeArguments: List<TypeArgument>, nullable: Boolean): AbbreviatedType {
val cPointer = ClassifierStubType(KotlinTypes.cPointer, typeArguments)
return AbbreviatedType(cPointer, KotlinTypes.cArrayPointer, typeArguments, nullable)
}
private fun expandObjCBlockVar(typeArguments: List<TypeArgument>, nullable: Boolean): AbbreviatedType {
val underlyingType = ClassifierStubType(KotlinTypes.objCNotImplementedVar, typeArguments, nullable)
return AbbreviatedType(underlyingType, KotlinTypes.objCBlockVar, typeArguments, nullable)
}
/**
* @return [ClassifierStubType] if [classifier] is a typealias from [kotlinx.cinterop] package.
*/
fun tryExpandPredefinedTypealias(classifier: Classifier, nullable: Boolean, typeArguments: List<TypeArgument>): AbbreviatedType? =
when (classifier) {
in primitiveVarClassifierToPrimitiveType.keys -> expandPrimitiveVarType(classifier, nullable)
KotlinTypes.cOpaquePointer.classifier -> expandCOpaquePointer(nullable)
KotlinTypes.cOpaquePointerVar.classifier -> expandCOpaquePointerVar(nullable)
KotlinTypes.cPointerVar -> expandCPointerVar(typeArguments, nullable)
KotlinTypes.objCObjectMeta -> expandObjCObjectMeta(typeArguments, nullable)
KotlinTypes.cArrayPointer -> expandCArrayPointer(typeArguments, nullable)
KotlinTypes.objCBlockVar -> expandObjCBlockVar(typeArguments, nullable)
else -> null
}
/**
* Variant of [tryExpandPredefinedTypealias] with [platform]-dependent result.
*/
fun tryExpandPlatformDependentTypealias(
classifier: Classifier, platform: KotlinPlatform, nullable: Boolean
): StubType? =
when (classifier) {
nativePtrClassifier -> expandNativePtr(platform, nullable)
else -> null
}
}
@@ -0,0 +1,22 @@
/*
* 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
interface StubIrVisitor<T, R> {
fun visitClass(element: ClassStub, data: T): R
fun visitTypealias(element: TypealiasStub, data: T): R
fun visitFunction(element: FunctionStub, data: T): R
fun visitProperty(element: PropertyStub, data: T): R
fun visitConstructor(constructorStub: ConstructorStub, data: T): R
fun visitPropertyAccessor(propertyAccessor: PropertyAccessor, data: T): R
fun visitSimpleStubContainer(simpleStubContainer: SimpleStubContainer, data: T): R
}
@@ -0,0 +1,100 @@
/*
* 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.*
val EnumDef.isAnonymous: Boolean
get() = spelling.contains("(anonymous ") // TODO: it is a hack
val StructDecl.isAnonymous: Boolean
get() = spelling.contains("(anonymous ") // TODO: it is a hack
/**
* Returns the expression which could be used for this type in C code.
* Note: the resulting string doesn't exactly represent this type, but it is enough for current purposes.
*
* TODO: use libclang to implement?
*/
fun Type.getStringRepresentation(): String = when (this) {
VoidType -> "void"
CharType -> "char"
CBoolType -> "_Bool"
ObjCBoolType -> "BOOL"
is IntegerType -> this.spelling
is FloatingType -> this.spelling
is VectorType -> this.spelling
is PointerType -> getPointerTypeStringRepresentation(this.pointeeType)
is ArrayType -> getPointerTypeStringRepresentation(this.elemType)
is RecordType -> this.decl.spelling
is EnumType -> if (this.def.isAnonymous) {
this.def.baseType.getStringRepresentation()
} else {
this.def.spelling
}
is Typedef -> this.def.aliased.getStringRepresentation()
is ObjCPointer -> when (this) {
is ObjCIdType -> "id$protocolQualifier"
is ObjCClassPointer -> "Class$protocolQualifier"
is ObjCObjectPointer -> "${def.name}$protocolQualifier*"
is ObjCInstanceType -> TODO(this.toString()) // Must have already been handled.
is ObjCBlockPointer -> "id"
}
else -> throw NotImplementedError()
}
fun getPointerTypeStringRepresentation(pointee: Type): String =
(getStringRepresentationOfPointee(pointee) ?: "void") + "*"
private fun getStringRepresentationOfPointee(type: Type): String? {
val unwrapped = type.unwrapTypedefs()
return when (unwrapped) {
is PrimitiveType -> unwrapped.getStringRepresentation()
is PointerType -> getStringRepresentationOfPointee(unwrapped.pointeeType)?.plus("*")
is RecordType -> if (unwrapped.decl.isAnonymous || unwrapped.decl.spelling == "struct __va_list_tag") {
null
} else {
unwrapped.decl.spelling
}
else -> null
}
}
private val ObjCQualifiedPointer.protocolQualifier: String
get() = if (this.protocols.isEmpty()) "" else " <${protocols.joinToString { it.name }}>"
fun blockTypeStringRepresentation(type: ObjCBlockPointer): String {
return buildString {
append(type.returnType.getStringRepresentation())
append("(^)")
append("(")
val blockParameters = if (type.parameterTypes.isEmpty()) {
"void"
} else {
type.parameterTypes.joinToString { it.getStringRepresentation() }
}
append(blockParameters)
append(")")
}
}
@@ -0,0 +1,164 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.konan.util.DefFile
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.gen.jvm.buildNativeLibrary
import org.jetbrains.kotlin.native.interop.gen.jvm.prepareTool
import org.jetbrains.kotlin.native.interop.indexer.NativeLibraryHeaders
import org.jetbrains.kotlin.native.interop.indexer.getHeaderPaths
import org.jetbrains.kotlin.native.interop.tool.CInteropArguments
import java.io.File
fun defFileDependencies(args: Array<String>) {
val defFiles = mutableListOf<File>()
val targets = mutableListOf<String>()
var index = 0
while (index < args.size) {
val arg = args[index]
++index
when (arg) {
"-target" -> {
targets += args[index]
++index
}
else -> {
defFiles.add(File(arg))
}
}
}
defFileDependencies(makeDependencyAssigner(targets, defFiles))
}
private fun makeDependencyAssigner(targets: List<String>, defFiles: List<File>) =
CompositeDependencyAssigner(targets.map { makeDependencyAssignerForTarget(it, defFiles) })
private fun makeDependencyAssignerForTarget(target: String, defFiles: List<File>): SingleTargetDependencyAssigner {
val tool = prepareTool(target, KotlinPlatform.NATIVE)
val cinteropArguments = CInteropArguments()
cinteropArguments.argParser.parse(arrayOf())
val libraries = defFiles.associateWith {
buildNativeLibrary(
tool,
DefFile(it, tool.substitutions),
cinteropArguments,
ImportsImpl(emptyMap())
).getHeaderPaths()
}
return SingleTargetDependencyAssigner(libraries)
}
private fun defFileDependencies(dependencyAssigner: DependencyAssigner) {
while (!dependencyAssigner.isDone()) {
val (file, depends) = dependencyAssigner.getReady().entries.sortedBy { it.key }.first()
dependencyAssigner.markDone(file)
patchDepends(file, depends.sorted())
println("${file.name} done.")
}
}
private fun patchDepends(file: File, newDepends: List<String>) {
val defFileLines = file.readLines()
val dependsLine = buildString {
append("depends =")
newDepends.forEach {
append(" ")
append(it)
}
}
val newDefFileLines = listOf(dependsLine) + defFileLines.filter { !it.startsWith("depends =") }
file.bufferedWriter().use { writer ->
newDefFileLines.forEach { writer.appendLine(it) }
}
}
private interface DependencyAssigner {
fun isDone(): Boolean
fun getReady(): Map<File, Set<String>>
fun markDone(file: File)
}
private class CompositeDependencyAssigner(val dependencyAssigners: List<DependencyAssigner>) : DependencyAssigner {
override fun isDone(): Boolean = dependencyAssigners.all { it.isDone() }
override fun getReady(): Map<File, Set<String>> {
return dependencyAssigners.map { it.getReady() }.reduce { left, right ->
(left.keys intersect right.keys)
.associateWith { left.getValue(it) union right.getValue(it) }
}.also {
require(it.isNotEmpty()) { "incompatible dependencies" } // TODO: add more info.
}
}
override fun markDone(file: File) {
dependencyAssigners.forEach { it.markDone(file) }
}
}
private class SingleTargetDependencyAssigner(
defFilesToHeaders: Map<File, NativeLibraryHeaders<String>>
) : DependencyAssigner {
private val pendingDefFilesToHeaders = defFilesToHeaders.toMutableMap()
private val processedHeadersToDefFiles = mutableMapOf<String, File>()
init {
val ownedHeaders = pendingDefFilesToHeaders.values.flatMap { it.ownHeaders }
val unownedHeadersToDefFiles = mutableMapOf<String, File>()
pendingDefFilesToHeaders.forEach { (def, lib) ->
(lib.importedHeaders - ownedHeaders).forEach {
unownedHeadersToDefFiles.putIfAbsent(it, def)
}
}
if (unownedHeadersToDefFiles.isNotEmpty()) {
error("Unowned headers:\n" +
unownedHeadersToDefFiles.entries.joinToString("\n") { "${it.key}\n imported by: ${it.value.name}" })
}
}
override fun isDone(): Boolean = pendingDefFilesToHeaders.isEmpty()
override fun getReady(): Map<File, Set<String>> {
val result = mutableMapOf<File, Set<String>>()
defFiles@for ((defFile, headers) in pendingDefFilesToHeaders) {
val depends = mutableSetOf<String>()
headers@for (header in (headers.ownHeaders + headers.importedHeaders)) {
val dependency = processedHeadersToDefFiles[header]
?: if (header in headers.ownHeaders) continue@headers else continue@defFiles
depends.add(dependency.nameWithoutExtension)
}
result[defFile] = depends
}
if (result.isEmpty()) {
pendingDefFilesToHeaders.entries.forEach { (def, headers) ->
println(def.name)
println("Own headers:")
headers.ownHeaders.forEach { println(it) }
println("Unowned imported headers:")
headers.importedHeaders.forEach { if (it !in processedHeadersToDefFiles) println(it) }
println()
}
error("Cyclic dependency? Remaining libs:\n" + pendingDefFilesToHeaders.keys.joinToString("\n") { it.name })
}
return result
}
override fun markDone(file: File) {
val headers = pendingDefFilesToHeaders.remove(file)!!
headers.ownHeaders.forEach {
processedHeadersToDefFiles.putIfAbsent(it, file)
}
}
}
@@ -0,0 +1,136 @@
/*
* 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.tool
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.*
const val HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX = "headerFilterAdditionalSearchPrefix"
const val NODEFAULTLIBS_DEPRECATED = "nodefaultlibs"
const val NODEFAULTLIBS = "no-default-libs"
const val NOENDORSEDLIBS = "no-endorsed-libs"
const val PURGE_USER_LIBS = "Xpurge-user-libs"
const val TEMP_DIR = "Xtemporary-files-dir"
const val NOPACK = "nopack"
const val COMPILE_SOURCES = "Xcompile-source"
const val SHORT_MODULE_NAME = "Xshort-module-name"
const val FOREIGN_EXCEPTION_MODE = "Xforeign-exception-mode"
// TODO: unify camel and snake cases.
// Possible solution is to accept both cases
open class CommonInteropArguments(val argParser: ArgParser) {
val verbose by argParser.option(ArgType.Boolean, description = "Enable verbose logging output").default(false)
val pkg by argParser.option(ArgType.String, description = "place generated bindings to the package")
val output by argParser.option(ArgType.String, shortName = "o", description = "specifies the resulting library file")
.default("nativelib")
val libraryPath by argParser.option(ArgType.String, description = "add a library search path")
.multiple().delimiter(",")
val staticLibrary by argParser.option(ArgType.String, description = "embed static library to the result")
.multiple().delimiter(",")
val library by argParser.option(ArgType.String, shortName = "l", description = "library to use for building")
.multiple()
val repo by argParser.option(ArgType.String, shortName = "r", description = "repository to resolve dependencies")
.multiple()
val mode by argParser.option(ArgType.Choice(listOf(MODE_METADATA, MODE_SOURCECODE)), description = "the way interop library is generated")
.default(DEFAULT_MODE)
val nodefaultlibs by argParser.option(ArgType.Boolean, NODEFAULTLIBS,
description = "don't link the libraries from dist/klib automatically").default(false)
val nodefaultlibsDeprecated by argParser.option(ArgType.Boolean, NODEFAULTLIBS_DEPRECATED,
description = "don't link the libraries from dist/klib automatically",
deprecatedWarning = "Old form of flag. Please, use $NODEFAULTLIBS.").default(false)
val noendorsedlibs by argParser.option(ArgType.Boolean, NOENDORSEDLIBS,
description = "don't link the endorsed libraries from dist automatically").default(false)
val purgeUserLibs by argParser.option(ArgType.Boolean, PURGE_USER_LIBS,
description = "don't link unused libraries even explicitly specified").default(false)
val nopack by argParser.option(ArgType.Boolean, fullName = NOPACK,
description = "Don't pack the produced library into a klib file").default(false)
val tempDir by argParser.option(ArgType.String, TEMP_DIR,
description = "save temporary files to the given directory")
val kotlincOption by argParser.option(ArgType.String, "Xkotlinc-option",
description = "additional kotlinc compiler option").multiple()
companion object {
const val MODE_SOURCECODE = "sourcecode"
const val MODE_METADATA = "metadata"
const val DEFAULT_MODE = MODE_METADATA
}
}
open class CInteropArguments(argParser: ArgParser =
ArgParser("cinterop",
prefixStyle = ArgParser.OptionPrefixStyle.JVM)): CommonInteropArguments(argParser) {
val target by argParser.option(ArgType.String, description = "native target to compile to").default("host")
val def by argParser.option(ArgType.String, description = "the library definition file")
val header by argParser.option(ArgType.String, description = "header file to produce kotlin bindings for")
.multiple().delimiter(",")
val headerFilterPrefix by argParser.option(ArgType.String, HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX, "hfasp",
"header file to produce kotlin bindings for").multiple().delimiter(",")
val compilerOpts by argParser.option(ArgType.String,
description = "additional compiler options (allows to add several options separated by spaces)",
deprecatedWarning = "-compilerOpts is deprecated. Please use -compiler-options.")
.multiple().delimiter(" ")
val compilerOptions by argParser.option(ArgType.String, "compiler-options",
description = "additional compiler options (allows to add several options separated by spaces)")
.multiple().delimiter(" ")
val linkerOpts = argParser.option(ArgType.String, "linkerOpts",
description = "additional linker options (allows to add several options separated by spaces)",
deprecatedWarning = "-linkerOpts is deprecated. Please use -linker-options.")
.multiple().delimiter(" ")
val linkerOptions = argParser.option(ArgType.String, "linker-options",
description = "additional linker options (allows to add several options separated by spaces)")
.multiple().delimiter(" ")
val compilerOption by argParser.option(ArgType.String, "compiler-option",
description = "additional compiler option").multiple()
val linkerOption = argParser.option(ArgType.String, "linker-option",
description = "additional linker option").multiple()
val linker by argParser.option(ArgType.String, description = "use specified linker")
val compileSource by argParser.option(ArgType.String,
fullName = COMPILE_SOURCES,
description = "additional C/C++ sources to be compiled into resulting library"
).multiple()
val sourceCompileOptions by argParser.option(ArgType.String,
fullName = "Xsource-compiler-option",
description = "compiler options for sources provided via -$COMPILE_SOURCES"
).multiple()
val shortModuleName by argParser.option(ArgType.String,
fullName = SHORT_MODULE_NAME,
description = "A short name used to denote this library in the IDE"
)
val moduleName by argParser.option(ArgType.String,
fullName = "Xmodule-name",
description = "A full name of the library used for dependency resolution"
)
val foreignExceptionMode by argParser.option(ArgType.String, FOREIGN_EXCEPTION_MODE,
description = "Handle native exception in Kotlin: <terminate|objc-wrap>")
}
class JSInteropArguments(argParser: ArgParser = ArgParser("jsinterop",
prefixStyle = ArgParser.OptionPrefixStyle.JVM)): CommonInteropArguments(argParser) {
val target by argParser.option(ArgType.Choice(listOf("wasm32")),
description = "wasm target to compile to").default("wasm32")
}
internal fun warn(msg: String) {
println("warning: $msg")
}
@@ -0,0 +1,13 @@
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.native.interop.tool.CommonInteropArguments
enum class GenerationMode {
SOURCE_CODE, METADATA
}
fun parseGenerationMode(mode: String): GenerationMode? = when(mode) {
CommonInteropArguments.MODE_METADATA -> GenerationMode.METADATA
CommonInteropArguments.MODE_SOURCECODE -> GenerationMode.SOURCE_CODE
else -> null
}
@@ -0,0 +1,41 @@
/*
* 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.jvm
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.native.interop.indexer.CompilationWithPCH
/**
* Describes the native library and the options for adjusting the Kotlin API to be generated for this library.
*/
class InteropConfiguration(
val library: CompilationWithPCH,
val pkgName: String,
val excludedFunctions: Set<String>,
val excludedMacros: Set<String>,
val strictEnums: Set<String>,
val nonStrictEnums: Set<String>,
val noStringConversion: Set<String>,
val exportForwardDeclarations: List<String>,
val disableDesignatedInitializerChecks: Boolean,
val target: KonanTarget
)
enum class KotlinPlatform {
JVM,
NATIVE
}
@@ -0,0 +1,108 @@
/*
* 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.jvm
import kotlinx.metadata.*
import kotlinx.metadata.klib.KlibModuleFragmentWriteStrategy
import kotlinx.metadata.klib.KlibModuleMetadata
import kotlinx.metadata.klib.className
import kotlinx.metadata.klib.fqName
import org.jetbrains.kotlin.backend.common.serialization.KlibIrVersion
import org.jetbrains.kotlin.backend.common.serialization.metadata.KlibMetadataVersion
import org.jetbrains.kotlin.konan.CURRENT
import org.jetbrains.kotlin.konan.CompilerVersion
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.library.impl.KonanLibraryWriterImpl
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.library.KotlinLibraryVersioning
import org.jetbrains.kotlin.library.KotlinAbiVersion
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.SerializedMetadata
import org.jetbrains.kotlin.library.impl.BuiltInsPlatform
import org.jetbrains.kotlin.util.removeSuffixIfPresent
import java.util.*
fun createInteropLibrary(
metadata: KlibModuleMetadata,
outputPath: String,
moduleName: String,
nativeBitcodeFiles: List<String>,
target: KonanTarget,
manifest: Properties,
dependencies: List<KotlinLibrary>,
nopack: Boolean,
shortName: String?,
staticLibraries: List<String>
) {
val version = KotlinLibraryVersioning(
libraryVersion = null,
abiVersion = KotlinAbiVersion.CURRENT,
compilerVersion = CompilerVersion.CURRENT.toString(),
metadataVersion = KlibMetadataVersion.INSTANCE.toString(),
irVersion = KlibIrVersion.INSTANCE.toString()
)
val outputPathWithoutExtension = outputPath.removeSuffixIfPresent(".klib")
KonanLibraryWriterImpl(
File(outputPathWithoutExtension),
moduleName,
version,
target,
BuiltInsPlatform.NATIVE,
nopack = nopack,
shortName = shortName
).apply {
val serializedMetadata = metadata.write(ChunkingWriteStrategy())
addMetadata(SerializedMetadata(serializedMetadata.header, serializedMetadata.fragments, serializedMetadata.fragmentNames))
nativeBitcodeFiles.forEach(this::addNativeBitcode)
addManifestAddend(manifest)
addLinkDependencies(dependencies)
staticLibraries.forEach(this::addIncludedBinary)
commit()
}
}
// TODO: Consider adding it to kotlinx-metadata-klib.
class ChunkingWriteStrategy(
private val classesChunkSize: Int = 128,
private val packagesChunkSize: Int = 128
) : KlibModuleFragmentWriteStrategy {
override fun processPackageParts(parts: List<KmModuleFragment>): List<KmModuleFragment> {
if (parts.isEmpty()) return emptyList()
val fqName = parts.first().fqName
?: error("KmModuleFragment should have a not-null fqName!")
val classFragments = parts.flatMap(KmModuleFragment::classes)
.chunked(classesChunkSize) { chunk ->
KmModuleFragment().also { fragment ->
fragment.fqName = fqName
fragment.classes += chunk
chunk.mapTo(fragment.className, KmClass::name)
}
}
val packageFragments = parts.mapNotNull(KmModuleFragment::pkg)
.flatMap { it.functions + it.typeAliases + it.properties }
.chunked(packagesChunkSize) { chunk ->
KmModuleFragment().also { fragment ->
fragment.fqName = fqName
fragment.pkg = KmPackage().also { pkg ->
pkg.fqName = fqName
pkg.properties += chunk.filterIsInstance<KmProperty>()
pkg.functions += chunk.filterIsInstance<KmFunction>()
pkg.typeAliases += chunk.filterIsInstance<KmTypeAlias>()
}
}
}
val result = classFragments + packageFragments
return if (result.isEmpty()) {
// We still need to emit empty packages because they may
// represent parts of package declaration (e.g. platform.[]).
// Tooling (e.g. `klib contents`) expects this kind of behavior.
parts
} else {
result
}
}
}
@@ -0,0 +1,57 @@
/*
* 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.tool
import org.jetbrains.kotlin.konan.target.HostManager
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.target.PlatformManager
import org.jetbrains.kotlin.konan.target.customerDistribution
import org.jetbrains.kotlin.konan.util.KonanHomeProvider
import org.jetbrains.kotlin.konan.util.defaultTargetSubstitutions
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
class ToolConfig(userProvidedTargetName: String?, flavor: KotlinPlatform) {
private val konanHome = KonanHomeProvider.determineKonanHome()
private val distribution = customerDistribution(konanHome)
private val platformManager = PlatformManager(distribution)
private val targetManager = platformManager.targetManager(userProvidedTargetName)
private val host = HostManager.host
val target = targetManager.target
private val platform = platformManager.platform(target)
val clang = platform.clang
val substitutions = defaultTargetSubstitutions(target)
fun downloadDependencies() = platform.downloadDependencies()
val defaultCompilerOpts =
platform.clang.targetLibclangArgs.toList()
val platformCompilerOpts = if (flavor == KotlinPlatform.JVM)
platform.clang.hostCompilerArgsForJni.toList() else emptyList()
val llvmHome = platform.absoluteLlvmHome
val sysRoot = platform.absoluteTargetSysRoot
val libclang = when (host) {
KonanTarget.MINGW_X64 -> "$llvmHome/bin/libclang.dll"
else -> "$llvmHome/lib/${System.mapLibraryName("clang")}"
}
}
@@ -0,0 +1,527 @@
/*
* 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.jvm
import org.jetbrains.kotlin.konan.TempFiles
import org.jetbrains.kotlin.konan.exec.Command
import org.jetbrains.kotlin.konan.util.DefFile
import org.jetbrains.kotlin.native.interop.gen.*
import org.jetbrains.kotlin.native.interop.gen.wasm.processIdlLib
import org.jetbrains.kotlin.native.interop.indexer.*
import org.jetbrains.kotlin.native.interop.tool.*
import kotlinx.cli.ArgParser
import kotlinx.cli.ArgType
import kotlinx.cli.default
import kotlinx.cli.required
import org.jetbrains.kotlin.konan.ForeignExceptionMode
import org.jetbrains.kotlin.konan.library.*
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.konan.target.Distribution
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.konan.util.KonanHomeProvider
import org.jetbrains.kotlin.library.KotlinLibrary
import org.jetbrains.kotlin.library.packageFqName
import org.jetbrains.kotlin.library.resolver.impl.KotlinLibraryResolverImpl
import org.jetbrains.kotlin.library.resolver.impl.libraryResolver
import org.jetbrains.kotlin.library.toUnresolvedLibraries
import org.jetbrains.kotlin.util.removeSuffixIfPresent
import java.io.File
import java.lang.IllegalArgumentException
import java.nio.file.*
import java.util.*
data class InternalInteropOptions(val generated: String, val natives: String, val manifest: String? = null,
val cstubsName: String? = null)
fun main(args: Array<String>) {
// Adding flavor option for interop plugin.
class FullCInteropArguments: CInteropArguments() {
val flavor by argParser.option(ArgType.Choice(listOf("jvm", "native", "wasm")), description = "Interop target")
.default("jvm")
val generated by argParser.option(ArgType.String, description = "place generated bindings to the directory")
.required()
val natives by argParser.option(ArgType.String, description = "where to put the built native files")
.required()
}
val arguments = FullCInteropArguments()
arguments.argParser.parse(args)
val flavorName = arguments.flavor
processCLib(flavorName, arguments, InternalInteropOptions(arguments.generated, arguments.natives))
}
fun interop(
flavor: String, args: Array<String>,
additionalArgs: InternalInteropOptions
): Array<String>? = when(flavor) {
"jvm", "native" -> {
val cinteropArguments = CInteropArguments()
cinteropArguments.argParser.parse(args)
processCLib(flavor, cinteropArguments, additionalArgs)
}
"wasm" -> processIdlLib(args, additionalArgs)
else -> error("Unexpected flavor")
}
// Options, whose values are space-separated and can be escaped.
val escapedOptions = setOf("-compilerOpts", "-linkerOpts", "-compiler-options", "-linker-options")
private fun String.asArgList(key: String) =
if (escapedOptions.contains(key))
this.split(Regex("(?<!\\\\)\\Q \\E")).filter { it.isNotEmpty() }.map { it.replace("\\ ", " ") }
else
listOf(this)
private fun <T> Collection<T>.atMostOne(): T? {
return when (this.size) {
0 -> null
1 -> this.iterator().next()
else -> throw IllegalArgumentException("Collection has more than one element.")
}
}
private fun List<String>?.isTrue(): Boolean {
// The rightmost wins, null != "true".
return this?.last() == "true"
}
private fun runCmd(command: Array<String>, verbose: Boolean = false) {
Command(*command).getOutputLines(true).let { lines ->
if (verbose) lines.forEach(::println)
}
}
private fun Properties.storeProperties(file: File) {
file.outputStream().use {
this.store(it, null)
}
}
private fun Properties.putAndRunOnReplace(key: Any, newValue: Any, beforeReplace: (Any, Any, Any) -> Unit) {
val oldValue = this[key]
if (oldValue != null && oldValue != newValue) {
beforeReplace(key, oldValue, newValue)
}
this[key] = newValue
}
private fun selectNativeLanguage(config: DefFile.DefFileConfig): Language {
val languages = mapOf(
"C" to Language.C,
"Objective-C" to Language.OBJECTIVE_C
)
val language = config.language ?: return Language.C
return languages[language] ?:
error("Unexpected language '$language'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
}
private fun parseImports(dependencies: List<KotlinLibrary>): ImportsImpl =
dependencies.filterIsInstance<KonanLibrary>().mapNotNull { library ->
// TODO: handle missing properties?
library.packageFqName?.let { packageFqName ->
val headerIds = library.includedHeaders
headerIds.map { HeaderId(it) to PackageInfo(packageFqName, library) }
}
}.reversed().flatten().toMap().let(::ImportsImpl)
fun getCompilerFlagsForVfsOverlay(headerFilterPrefix: Array<String>, def: DefFile): List<String> {
val relativeToRoot = mutableMapOf<Path, Path>() // TODO: handle clashes
val filteredIncludeDirs = headerFilterPrefix .map { Paths.get(it) }
if (filteredIncludeDirs.isNotEmpty()) {
val headerFilterGlobs = def.config.headerFilter
if (headerFilterGlobs.isEmpty()) {
error("'$HEADER_FILTER_ADDITIONAL_SEARCH_PREFIX' option requires " +
"'headerFilter' to be specified in .def file")
}
relativeToRoot += findFilesByGlobs(roots = filteredIncludeDirs, globs = headerFilterGlobs)
}
if (relativeToRoot.isEmpty()) {
return emptyList()
}
val virtualRoot = Paths.get(System.getProperty("java.io.tmpdir")).resolve("konanSystemInclude")
val virtualPathToReal = relativeToRoot.map { (relativePath, realRoot) ->
virtualRoot.resolve(relativePath) to realRoot.resolve(relativePath)
}.toMap()
val vfsOverlayFile = createVfsOverlayFile(virtualPathToReal)
return listOf("-I${virtualRoot.toAbsolutePath()}", "-ivfsoverlay", vfsOverlayFile.toAbsolutePath().toString())
}
private fun findFilesByGlobs(roots: List<Path>, globs: List<String>): Map<Path, Path> {
val relativeToRoot = mutableMapOf<Path, Path>()
val pathMatchers = globs.map { FileSystems.getDefault().getPathMatcher("glob:$it") }
roots.reversed()
.filter { path ->
return@filter when {
path.toFile().exists() -> true
else -> { warn("$path doesn't exist"); false }
}
}
.forEach { root ->
// TODO: don't scan the entire tree, skip subdirectories according to globs.
Files.walk(root, FileVisitOption.FOLLOW_LINKS).forEach { path ->
val relativePath = root.relativize(path)
if (!Files.isDirectory(path) && pathMatchers.any { it.matches(relativePath) }) {
relativeToRoot[relativePath] = root
}
}
}
return relativeToRoot
}
private fun processCLib(flavorName: String, cinteropArguments: CInteropArguments,
additionalArgs: InternalInteropOptions): Array<String>? {
val ktGenRoot = additionalArgs.generated
val nativeLibsDir = additionalArgs.natives
val flavor = KotlinPlatform.values().single { it.name.equals(flavorName, ignoreCase = true) }
val defFile = cinteropArguments.def?.let { File(it) }
val manifestAddend = additionalArgs.manifest?.let { File(it) }
if (defFile == null && cinteropArguments.pkg == null) {
cinteropArguments.argParser.printError("-def or -pkg should be provided!")
}
val tool = prepareTool(cinteropArguments.target, flavor)
val def = DefFile(defFile, tool.substitutions)
val isLinkerOptsSetByUser = (cinteropArguments.linkerOpts.valueOrigin == ArgParser.ValueOrigin.SET_BY_USER) ||
(cinteropArguments.linkerOptions.valueOrigin == ArgParser.ValueOrigin.SET_BY_USER) ||
(cinteropArguments.linkerOption.valueOrigin == ArgParser.ValueOrigin.SET_BY_USER)
if (flavorName == "native" && isLinkerOptsSetByUser) {
warn("-linker-option(s)/-linkerOpts option is not supported by cinterop. Please add linker options to .def file or binary compilation instead.")
}
val additionalLinkerOpts = cinteropArguments.linkerOpts.value.toTypedArray() + cinteropArguments.linkerOption.value.toTypedArray() +
cinteropArguments.linkerOptions.value.toTypedArray()
val verbose = cinteropArguments.verbose
val language = selectNativeLanguage(def.config)
val entryPoint = def.config.entryPoints.atMostOne()
val linkerOpts =
def.config.linkerOpts.toTypedArray() +
tool.defaultCompilerOpts +
additionalLinkerOpts
val linkerName = cinteropArguments.linker ?: def.config.linker
val linker = "${tool.llvmHome}/bin/$linkerName"
val compiler = "${tool.llvmHome}/bin/clang"
val excludedFunctions = def.config.excludedFunctions.toSet()
val excludedMacros = def.config.excludedMacros.toSet()
val staticLibraries = def.config.staticLibraries + cinteropArguments.staticLibrary.toTypedArray()
val libraryPaths = def.config.libraryPaths + cinteropArguments.libraryPath.toTypedArray()
val fqParts = (cinteropArguments.pkg ?: def.config.packageName)?.split('.')
?: defFile!!.name.split('.').reversed().drop(1)
val outKtPkg = fqParts.joinToString(".")
val mode = run {
val providedMode = parseGenerationMode(cinteropArguments.mode)
?: error ("Unexpected interop generation mode: ${cinteropArguments.mode}")
if (providedMode == GenerationMode.METADATA && flavor == KotlinPlatform.JVM) {
warn("Metadata mode isn't supported for Kotlin/JVM! Falling back to sourcecode.")
GenerationMode.SOURCE_CODE
} else {
providedMode
}
}
val resolver = getLibraryResolver(cinteropArguments, tool.target)
val allLibraryDependencies = when (flavor) {
KotlinPlatform.NATIVE -> resolveDependencies(resolver, cinteropArguments)
else -> listOf()
}
val libName = additionalArgs.cstubsName ?: fqParts.joinToString("") + "stubs"
val tempFiles = TempFiles(libName, cinteropArguments.tempDir)
val imports = parseImports(allLibraryDependencies)
val library = buildNativeLibrary(tool, def, cinteropArguments, imports)
val (nativeIndex, compilation) = buildNativeIndex(library, verbose)
// Our current approach to arm64_32 support is to compile armv7k version of bitcode
// for arm64_32. That's the reason for this substitution.
// TODO: Add proper support with the next LLVM update.
val target = when (tool.target) {
KonanTarget.WATCHOS_ARM64 -> KonanTarget.WATCHOS_ARM32
else -> tool.target
}
val klibSuffix = CompilerOutputKind.LIBRARY.suffix(target)
val moduleName = cinteropArguments.moduleName
?: File(cinteropArguments.output).name.removeSuffixIfPresent(klibSuffix)
val configuration = InteropConfiguration(
library = compilation,
pkgName = outKtPkg,
excludedFunctions = excludedFunctions,
excludedMacros = excludedMacros,
strictEnums = def.config.strictEnums.toSet(),
nonStrictEnums = def.config.nonStrictEnums.toSet(),
noStringConversion = def.config.noStringConversion.toSet(),
exportForwardDeclarations = def.config.exportForwardDeclarations,
disableDesignatedInitializerChecks = def.config.disableDesignatedInitializerChecks,
target = target
)
File(nativeLibsDir).mkdirs()
val outCFile = tempFiles.create(libName, ".${language.sourceFileExtension}")
val logger = if (verbose) {
{ message: String -> println(message) }
} else {
{}
}
val stubIrContext = StubIrContext(logger, configuration, nativeIndex, imports, flavor, mode, libName)
val stubIrOutput = run {
val outKtFileCreator = {
val outKtFileName = fqParts.last() + ".kt"
val outKtFileRelative = (fqParts + outKtFileName).joinToString("/")
val file = File(ktGenRoot, outKtFileRelative)
file.parentFile.mkdirs()
file
}
val driverOptions = StubIrDriver.DriverOptions(entryPoint, moduleName, File(outCFile.absolutePath), outKtFileCreator)
val stubIrDriver = StubIrDriver(stubIrContext, driverOptions)
stubIrDriver.run()
}
// TODO: if a library has partially included headers, then it shouldn't be used as a dependency.
def.manifestAddendProperties["includedHeaders"] = nativeIndex.includedHeaders.joinToString(" ") { it.value }
def.manifestAddendProperties.putAndRunOnReplace("package", outKtPkg) {
_, oldValue, newValue ->
warn("The package value `$oldValue` specified in .def file is overridden with explicit $newValue")
}
def.manifestAddendProperties["interop"] = "true"
if (stubIrOutput is StubIrDriver.Result.Metadata) {
def.manifestAddendProperties["ir_provider"] = KLIB_INTEROP_IR_PROVIDER_IDENTIFIER
}
stubIrContext.addManifestProperties(def.manifestAddendProperties)
// cinterop command line option overrides def file property
val foreignExceptionMode = cinteropArguments.foreignExceptionMode?: def.config.foreignExceptionMode
foreignExceptionMode?.let {
def.manifestAddendProperties[ForeignExceptionMode.manifestKey] =
ForeignExceptionMode.byValue(it).value // may throw IllegalArgumentException
}
manifestAddend?.parentFile?.mkdirs()
manifestAddend?.let { def.manifestAddendProperties.storeProperties(it) }
val compilerArgs = stubIrContext.libraryForCStubs.compilerArgs.toTypedArray()
val nativeOutputPath: String = when (flavor) {
KotlinPlatform.JVM -> {
val outOFile = tempFiles.create(libName,".o")
val compilerCmd = arrayOf(compiler, *compilerArgs,
"-c", outCFile.absolutePath, "-o", outOFile.absolutePath)
runCmd(compilerCmd, verbose)
val outLib = File(nativeLibsDir, System.mapLibraryName(libName))
val linkerCmd = arrayOf(linker,
outOFile.absolutePath, "-shared", "-o", outLib.absolutePath,
*linkerOpts)
runCmd(linkerCmd, verbose)
outOFile.absolutePath
}
KotlinPlatform.NATIVE -> {
val outLib = File(nativeLibsDir, "$libName.bc")
val compilerCmd = arrayOf(compiler, *compilerArgs,
"-emit-llvm", "-c", outCFile.absolutePath, "-o", outLib.absolutePath)
runCmd(compilerCmd, verbose)
outLib.absolutePath
}
}
val compiledFiles = compileSources(nativeLibsDir, tool, cinteropArguments)
return when (stubIrOutput) {
is StubIrDriver.Result.SourceCode -> {
val bitcodePaths = compiledFiles.map { listOf("-native-library", it) }.flatten()
argsToCompiler(staticLibraries, libraryPaths) + bitcodePaths
}
is StubIrDriver.Result.Metadata -> {
val stdlibDependency = resolver.resolveWithDependencies(
emptyList(),
noDefaultLibs = true,
noEndorsedLibs = true
).getFullList()
createInteropLibrary(
metadata = stubIrOutput.metadata,
nativeBitcodeFiles = compiledFiles + nativeOutputPath,
target = tool.target,
moduleName = moduleName,
outputPath = cinteropArguments.output,
manifest = def.manifestAddendProperties,
dependencies = stdlibDependency + imports.requiredLibraries.toList(),
nopack = cinteropArguments.nopack,
shortName = cinteropArguments.shortModuleName,
staticLibraries = resolveLibraries(staticLibraries, libraryPaths)
)
return null
}
}
}
private fun compileSources(
nativeLibsDir: String,
toolConfig: ToolConfig,
cinteropArguments: CInteropArguments
): List<String> = cinteropArguments.compileSource.mapIndexed { index, source ->
// Mangle file name to avoid collisions.
val mangledFileName = "${index}_${File(source).nameWithoutExtension}"
val outputFileName = "$nativeLibsDir/${mangledFileName}.bc"
val compilerArgs = cinteropArguments.sourceCompileOptions.toTypedArray()
val compilerCmd = toolConfig.clang.clangCXX(*compilerArgs, source, "-emit-llvm", "-c", "-o", outputFileName)
runCmd(compilerCmd.toTypedArray(), verbose = cinteropArguments.verbose)
outputFileName
}
private fun getLibraryResolver(
cinteropArguments: CInteropArguments, target: KonanTarget
): KotlinLibraryResolverImpl<KonanLibrary> {
val libraries = cinteropArguments.library
val repos = cinteropArguments.repo
return defaultResolver(
repos,
libraries.filter { it.contains(org.jetbrains.kotlin.konan.file.File.separator) },
target,
Distribution(KonanHomeProvider.determineKonanHome())
).libraryResolver()
}
private fun resolveDependencies(
resolver: KotlinLibraryResolverImpl<KonanLibrary>, cinteropArguments: CInteropArguments
): List<KotlinLibrary> {
val libraries = cinteropArguments.library
val noDefaultLibs = cinteropArguments.nodefaultlibs || cinteropArguments.nodefaultlibsDeprecated
val noEndorsedLibs = cinteropArguments.noendorsedlibs
return resolver.resolveWithDependencies(
libraries.toUnresolvedLibraries,
noStdLib = false,
noDefaultLibs = noDefaultLibs,
noEndorsedLibs = noEndorsedLibs
).getFullList()
}
internal fun prepareTool(target: String?, flavor: KotlinPlatform): ToolConfig {
val tool = ToolConfig(target, flavor)
tool.downloadDependencies()
System.load(tool.libclang)
return tool
}
internal fun buildNativeLibrary(
tool: ToolConfig,
def: DefFile,
arguments: CInteropArguments,
imports: ImportsImpl
): NativeLibrary {
val additionalHeaders = (arguments.header).toTypedArray()
val additionalCompilerOpts = (arguments.compilerOpts +
arguments.compilerOptions + arguments.compilerOption).toTypedArray()
val headerFiles = def.config.headers + additionalHeaders
val language = selectNativeLanguage(def.config)
val compilerOpts: List<String> = mutableListOf<String>().apply {
addAll(def.config.compilerOpts)
addAll(tool.defaultCompilerOpts)
// We compile with -O2 because Clang may insert inline asm in bitcode at -O0.
// It is undesirable in case of watchos_arm64 since we target armv7k
// for this target instead of arm64_32 because it is not supported in LLVM 8.
//
// Note that PCH and the *.c file should be compiled with the same optimization level.
add("-O2")
addAll(additionalCompilerOpts)
addAll(getCompilerFlagsForVfsOverlay(arguments.headerFilterPrefix.toTypedArray(), def))
addAll(when (language) {
Language.C -> emptyList()
Language.OBJECTIVE_C -> {
// "Objective-C" within interop means "Objective-C with ARC":
listOf("-fobjc-arc")
// Using this flag here has two effects:
// 1. The headers are parsed with ARC enabled, thus the API is visible correctly.
// 2. The generated Objective-C stubs are compiled with ARC enabled, so reference counting
// calls are inserted automatically.
}
})
}
val compilation = CompilationImpl(
includes = headerFiles,
additionalPreambleLines = def.defHeaderLines,
compilerArgs = compilerOpts + tool.platformCompilerOpts,
language = language
)
val headerFilter: NativeLibraryHeaderFilter
val includes: List<String>
val modules = def.config.modules
if (modules.isEmpty()) {
val excludeDependentModules = def.config.excludeDependentModules
val headerFilterGlobs = def.config.headerFilter
val headerInclusionPolicy = HeaderInclusionPolicyImpl(headerFilterGlobs)
headerFilter = NativeLibraryHeaderFilter.NameBased(headerInclusionPolicy, excludeDependentModules)
includes = headerFiles
} else {
require(language == Language.OBJECTIVE_C) { "cinterop supports 'modules' only when 'language = Objective-C'" }
require(headerFiles.isEmpty()) { "cinterop doesn't support having headers and modules specified at the same time" }
require(def.config.headerFilter.isEmpty()) { "cinterop doesn't support 'headerFilter' with 'modules'" }
val modulesInfo = getModulesInfo(compilation, modules)
headerFilter = NativeLibraryHeaderFilter.Predefined(modulesInfo.ownHeaders)
includes = modulesInfo.topLevelHeaders
}
val excludeSystemLibs = def.config.excludeSystemLibs
val headerExclusionPolicy = HeaderExclusionPolicyImpl(imports)
return NativeLibrary(
includes = includes,
additionalPreambleLines = compilation.additionalPreambleLines,
compilerArgs = compilation.compilerArgs,
headerToIdMapper = HeaderToIdMapper(sysRoot = tool.sysRoot),
language = compilation.language,
excludeSystemLibs = excludeSystemLibs,
headerExclusionPolicy = headerExclusionPolicy,
headerFilter = headerFilter
)
}
@@ -0,0 +1,410 @@
package org.jetbrains.kotlin.native.interop.gen.wasm
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.native.interop.gen.argsToCompiler
import org.jetbrains.kotlin.native.interop.gen.wasm.idl.*
import org.jetbrains.kotlin.native.interop.gen.jvm.InternalInteropOptions
import org.jetbrains.kotlin.native.interop.tool.JSInteropArguments
fun kotlinHeader(packageName: String): String {
return "package $packageName\n" +
"import kotlinx.wasm.jsinterop.*\n"
}
fun Type.toKotlinType(argName: String? = null): String = when (this) {
is idlVoid -> "Unit"
is idlInt -> "Int"
is idlFloat -> "Float"
is idlDouble -> "Double"
is idlString -> "String"
is idlObject -> "JsValue"
is idlFunction -> "KtFunction<R${argName!!}>"
is idlInterfaceRef -> name
else -> error("Unexpected type")
}
fun Arg.wasmMapping(): String = when (type) {
is idlVoid -> error("An arg can not be idlVoid")
is idlInt -> name
is idlFloat -> name
is idlDouble -> "doubleUpper($name), doubleLower($name)"
is idlString -> "stringPointer($name), stringLengthBytes($name)"
is idlObject -> TODO("implement me")
is idlFunction -> "wrapFunction<R$name>($name), ArenaManager.currentArena"
is idlInterfaceRef -> TODO("Implement me")
else -> error("Unexpected type")
}
fun Type.wasmReturnArg(): String =
when (this) {
is idlVoid -> "ArenaManager.currentArena" // TODO: optimize.
is idlInt -> "ArenaManager.currentArena"
is idlFloat -> "ArenaManager.currentArena"
is idlDouble -> "ArenaManager.currentArena"
is idlString -> "ArenaManager.currentArena"
is idlObject -> "ArenaManager.currentArena"
is idlFunction -> "ArenaManager.currentArena"
is idlInterfaceRef -> "ArenaManager.currentArena"
else -> error("Unexpected type")
}
val Operation.wasmReturnArg: String get() = returnType.wasmReturnArg()
val Attribute.wasmReturnArg: String get() = type.wasmReturnArg()
fun Arg.wasmArgNames(): List<String> = when (type) {
is idlVoid -> error("An arg can not be idlVoid")
is idlInt -> listOf(name)
is idlFloat -> listOf(name)
is idlDouble -> listOf("${name}Upper", "${name}Lower")
is idlString -> listOf("${name}Ptr", "${name}Len")
is idlObject -> TODO("implement me (idlObject)")
is idlFunction -> listOf("${name}Index", "${name}ResultArena")
is idlInterfaceRef -> TODO("Implement me (idlInterfaceRef)")
else -> error("Unexpected type")
}
fun Type.wasmReturnMapping(value: String): String = when (this) {
is idlVoid -> ""
is idlInt -> value
is idlFloat -> value
is idlDouble -> value
is idlString -> "TODO(\"Implement me\")"
is idlObject -> "JsValue(ArenaManager.currentArena, $value)"
is idlFunction -> "TODO(\"Implement me\")"
is idlInterfaceRef -> "$name(ArenaManager.currentArena, $value)"
else -> error("Unexpected type")
}
fun wasmFunctionName(functionName: String, interfaceName: String)
= "knjs__${interfaceName}_$functionName"
fun wasmSetterName(propertyName: String, interfaceName: String)
= "knjs_set__${interfaceName}_$propertyName"
fun wasmGetterName(propertyName: String, interfaceName: String)
= "knjs_get__${interfaceName}_$propertyName"
val Operation.kotlinTypeParameters: String get() {
val lambdaRetTypes = args.filter { it.type is idlFunction }
.map { "R${it.name}" }. joinToString(", ")
return if (lambdaRetTypes == "") "" else "<$lambdaRetTypes>"
}
val Interface.wasmReceiverArgs get() =
if (isGlobal) emptyList()
else listOf("this.arena", "this.index")
fun Member.wasmReceiverArgs(parent: Interface) =
if (isStatic) emptyList()
else parent.wasmReceiverArgs
fun Type.generateKotlinCall(name: String, wasmArgList: String) =
"$name($wasmArgList)"
fun Type.generateKotlinCallWithReturn(name: String, wasmArgList: String) =
when(this) {
is idlVoid -> " ${generateKotlinCall(name, wasmArgList)}\n"
is idlDouble -> " ${generateKotlinCall(name, wasmArgList)}\n" +
" val wasmRetVal = ReturnSlot_getDouble()\n"
else -> " val wasmRetVal = ${generateKotlinCall(name, wasmArgList)}\n"
}
fun Operation.generateKotlinCallWithReturn(parent_name: String, wasmArgList: String) =
returnType.generateKotlinCallWithReturn(
wasmFunctionName(name, parent_name),
wasmArgList)
fun Attribute.generateKotlinGetterCallWithReturn(parent_name: String, wasmArgList: String) =
type.generateKotlinCallWithReturn(
wasmGetterName(name, parent_name),
wasmArgList)
fun Operation.generateKotlin(parent: Interface): String {
val argList = args.map {
"${it.name}: ${it.type.toKotlinType(it.name)}"
}.joinToString(", ")
val wasmArgList = (wasmReceiverArgs(parent) + args.map(Arg::wasmMapping) + wasmReturnArg).joinToString(", ")
// TODO: there can be multiple Rs.
return " fun $kotlinTypeParameters $name(" +
argList +
"): ${returnType.toKotlinType()} {\n" +
generateKotlinCallWithReturn(parent.name, wasmArgList) +
" return ${returnType.wasmReturnMapping("wasmRetVal")}\n"+
" }\n\n"
}
fun Attribute.generateKotlinSetter(parent: Interface): String {
val kotlinType = type.toKotlinType(name)
return " set(value: $kotlinType) {\n" +
" ${wasmSetterName(name, parent.name)}(" +
(wasmReceiverArgs(parent) + Arg("value", type).wasmMapping()).joinToString(", ") +
")\n" +
" }\n\n"
}
fun Attribute.generateKotlinGetter(parent: Interface): String {
val wasmArgList = (wasmReceiverArgs(parent) + wasmReturnArg).joinToString(", ")
return " get() {\n" +
generateKotlinGetterCallWithReturn(parent.name, wasmArgList) +
" return ${type.wasmReturnMapping("wasmRetVal")}\n"+
" }\n\n"
}
fun Attribute.generateKotlin(parent: Interface): String {
val kotlinType = type.toKotlinType(name)
val varOrVal = if (readOnly) "val" else "var"
return " $varOrVal $name: $kotlinType\n" +
generateKotlinGetter(parent) +
if (!readOnly) generateKotlinSetter(parent) else ""
}
val Interface.wasmTypedReceiverArgs get() =
if (isGlobal) emptyList()
else listOf("arena: Int", "index: Int")
fun Member.wasmTypedReceiverArgs(parent: Interface) =
if (isStatic) emptyList() else parent.wasmTypedReceiverArgs
fun Operation.generateWasmStub(parent: Interface): String {
val wasmName = wasmFunctionName(this.name, parent.name)
val allArgs = (wasmTypedReceiverArgs(parent) + args.toList().wasmTypedMapping() + wasmTypedReturnMapping).joinToString(", ")
return "@SymbolName(\"$wasmName\")\n" +
"external public fun $wasmName($allArgs): ${returnType.wasmReturnTypeMapping()}\n\n"
}
fun Attribute.generateWasmSetterStub(parent: Interface): String {
val wasmSetter = wasmSetterName(this.name, parent.name)
val allArgs = (wasmTypedReceiverArgs(parent) + Arg("value", this.type).wasmTypedMapping()).joinToString(", ")
return "@SymbolName(\"$wasmSetter\")\n" +
"external public fun $wasmSetter($allArgs): Unit\n\n"
}
fun Attribute.generateWasmGetterStub(parent: Interface): String {
val wasmGetter = wasmGetterName(this.name, parent.name)
val allArgs = (wasmTypedReceiverArgs(parent) + wasmTypedReturnMapping).joinToString(", ")
return "@SymbolName(\"$wasmGetter\")\n" +
"external public fun $wasmGetter($allArgs): Int\n\n"
}
fun Attribute.generateWasmStubs(parent: Interface) =
generateWasmGetterStub(parent) +
if (!readOnly) generateWasmSetterStub(parent) else ""
// TODO: consider using virtual methods
fun Member.generateKotlin(parent: Interface): String = when (this) {
is Operation -> this.generateKotlin(parent)
is Attribute -> this.generateKotlin(parent)
else -> error("Unexpected member")
}
// TODO: consider using virtual methods
fun Member.generateWasmStub(parent: Interface) =
when (this) {
is Operation -> this.generateWasmStub(parent)
is Attribute -> this.generateWasmStubs(parent)
else -> error("Unexpected member")
}
fun Arg.wasmTypedMapping()
= this.wasmArgNames().map { "$it: Int" } .joinToString(", ")
// TODO: Optimize for simple types.
fun Type.wasmTypedReturnMapping(): String = "resultArena: Int"
val Operation.wasmTypedReturnMapping get() = returnType.wasmTypedReturnMapping()
val Attribute.wasmTypedReturnMapping get() = type.wasmTypedReturnMapping()
fun List<Arg>.wasmTypedMapping():List<String>
= this.map(Arg::wasmTypedMapping)
// TODO: more complex return types, such as returning a pair of Ints
// will require a more complex approach.
fun Type.wasmReturnTypeMapping()
= if (this == idlVoid) "Unit" else "Int"
fun Interface.generateMemberWasmStubs() =
members.map {
it.generateWasmStub(this)
}.joinToString("")
fun Interface.generateKotlinMembers() =
members.filterNot { it.isStatic } .map {
it.generateKotlin(this)
}.joinToString("")
fun Interface.generateKotlinCompanion() =
" companion object {\n" +
members.filter { it.isStatic } .map {
it.generateKotlin(this)
}.joinToString("") +
" }\n"
fun Interface.generateKotlinClassHeader() =
"open class $name(arena: Int, index: Int): JsValue(arena, index) {\n" +
" constructor(jsValue: JsValue): this(jsValue.arena, jsValue.index)\n"
fun Interface.generateKotlinClassFooter() =
"}\n"
fun Interface.generateKotlinClassConverter() =
"val JsValue.as$name: $name\n" +
" get() {\n" +
" return $name(this.arena, this.index)\n"+
" }\n"
fun Interface.generateKotlin(): String {
fun unlessGlobal(value: () -> String): String {
return if (this.isGlobal) "" else value()
}
return generateMemberWasmStubs() +
unlessGlobal { generateKotlinClassHeader() } +
generateKotlinMembers() +
unlessGlobal {
generateKotlinCompanion() +
generateKotlinClassFooter() +
generateKotlinClassConverter()
}
}
fun generateKotlin(pkg: String, interfaces: List<Interface>) =
kotlinHeader(pkg) +
interfaces.map {
it.generateKotlin()
}.joinToString("\n") +
if (pkg == "kotlinx.interop.wasm.dom") // TODO: make it a general solution.
"fun <R> setInterval(interval: Int, lambda: KtFunction<R>) = setInterval(lambda, interval)\n"
else ""
/////////////////////////////////////////////////////////
fun Arg.composeWasmArgs(): String = when (type) {
is idlVoid -> error("An arg can not be idlVoid")
is idlInt -> ""
is idlFloat -> ""
is idlDouble ->
" var $name = twoIntsToDouble(${name}Upper, ${name}Lower);\n"
is idlString ->
" var $name = toUTF16String(${name}Ptr, ${name}Len);\n"
is idlObject -> TODO("implement me")
is idlFunction ->
" var $name = konan_dependencies.env.Konan_js_wrapLambda(lambdaResultArena, ${name}Index);\n"
is idlInterfaceRef -> TODO("Implement me")
else -> error("Unexpected type")
}
val Interface.receiver get() =
if (isGlobal) "" else "kotlinObject(arena, obj)."
fun Member.receiver(parent: Interface) =
if (isStatic) "${parent.name}." else parent.receiver
val Interface.wasmReceiverArgName get() =
if (isGlobal) emptyList() else listOf("arena", "obj")
fun Member.wasmReceiverArgName(parent: Interface) =
if (isStatic) emptyList() else parent.wasmReceiverArgName
val Operation.wasmReturnArgName get() =
returnType.wasmReturnArgName
val Attribute.wasmReturnArgName get() =
type.wasmReturnArgName
val Type.wasmReturnArgName get() =
when (this) {
is idlVoid -> emptyList()
is idlInt -> emptyList()
is idlFloat -> emptyList()
is idlDouble -> emptyList()
is idlString -> listOf("resultArena")
is idlObject -> listOf("resultArena")
is idlInterfaceRef -> listOf("resultArena")
else -> error("Unexpected type: $this")
}
val Type.wasmReturnExpression get() =
when(this) {
is idlVoid -> ""
is idlInt -> "result"
is idlFloat -> "result" // TODO: can we really pass floats as is?
is idlDouble -> "doubleToReturnSlot(result)"
is idlString -> "toArena(resultArena, result)"
is idlObject -> "toArena(resultArena, result)"
is idlInterfaceRef -> "toArena(resultArena, result)"
else -> error("Unexpected type: $this")
}
fun Operation.generateJs(parent: Interface): String {
val allArgs = wasmReceiverArgName(parent) + args.map { it.wasmArgNames() }.flatten() + wasmReturnArgName
val wasmMapping = allArgs.joinToString(", ")
val argList = args.map { it.name }. joinToString(", ")
val composedArgsList = args.map { it.composeWasmArgs() }. joinToString("")
return "\n ${wasmFunctionName(this.name, parent.name)}: function($wasmMapping) {\n" +
composedArgsList +
" var result = ${receiver(parent)}$name($argList);\n" +
" return ${returnType.wasmReturnExpression};\n" +
" }"
}
fun Attribute.generateJsSetter(parent: Interface): String {
val valueArg = Arg("value", type)
val allArgs = wasmReceiverArgName(parent) + valueArg.wasmArgNames()
val wasmMapping = allArgs.joinToString(", ")
return "\n ${wasmSetterName(name, parent.name)}: function($wasmMapping) {\n" +
valueArg.composeWasmArgs() +
" ${receiver(parent)}$name = value;\n" +
" }"
}
fun Attribute.generateJsGetter(parent: Interface): String {
val allArgs = wasmReceiverArgName(parent) + wasmReturnArgName
val wasmMapping = allArgs.joinToString(", ")
return "\n ${wasmGetterName(name, parent.name)}: function($wasmMapping) {\n" +
" var result = ${receiver(parent)}$name;\n" +
" return ${type.wasmReturnExpression};\n" +
" }"
}
fun Attribute.generateJs(parent: Interface) =
generateJsGetter(parent) +
if (!readOnly) ",\n${generateJsSetter(parent)}" else ""
fun Member.generateJs(parent: Interface): String = when (this) {
is Operation -> this.generateJs(parent)
is Attribute -> this.generateJs(parent)
else -> error("Unexpected member")
}
fun generateJs(interfaces: List<Interface>): String =
"konan.libraries.push ({\n" +
interfaces.map { interf ->
interf.members.map { member ->
member.generateJs(interf)
}
}.flatten() .joinToString(",\n") +
"\n})\n"
const val idlMathPackage = "kotlinx.interop.wasm.math"
const val idlDomPackage = "kotlinx.interop.wasm.dom"
fun processIdlLib(args: Array<String>, additionalArgs: InternalInteropOptions): Array<String> {
val jsInteropArguments = JSInteropArguments()
jsInteropArguments.argParser.parse(args)
// TODO: Refactor me.
val ktGenRoot = File(additionalArgs.generated).mkdirs()
val nativeLibsDir = File(additionalArgs.natives).mkdirs()
val idl = when (jsInteropArguments.pkg) {
idlMathPackage -> idlMath
idlDomPackage -> idlDom
else -> throw IllegalArgumentException("Please choose either $idlMathPackage or $idlDomPackage for -pkg argument")
}
File(ktGenRoot, "kotlin_stubs.kt").writeText(generateKotlin(jsInteropArguments.pkg!!, idl))
File(nativeLibsDir, "js_stubs.js").writeText(generateJs(idl))
File((additionalArgs.manifest)!!).writeText("") // The manifest is currently unused for wasm.
return argsToCompiler(jsInteropArguments.staticLibrary.toTypedArray(), jsInteropArguments.libraryPath.toTypedArray())
}
@@ -0,0 +1,50 @@
package org.jetbrains.kotlin.native.interop.gen.wasm.idl
// This shall be an output of Web IDL parser.
val idlDom = listOf(
Interface("Context",
Attribute("lineWidth", idlInt),
Attribute("fillStyle", idlString),
Attribute("strokeStyle", idlString),
Operation("lineTo", idlVoid, Arg("x", idlInt), Arg("y", idlInt)),
Operation("moveTo", idlVoid, Arg("x", idlInt), Arg("y", idlInt)),
Operation("beginPath", idlVoid),
Operation("stroke", idlVoid),
Operation("fillRect", idlVoid, Arg("x", idlInt), Arg("y", idlInt), Arg("width", idlInt), Arg("height", idlInt)),
Operation("fillText", idlVoid, Arg("test", idlString), Arg("x", idlInt), Arg("y", idlInt), Arg("maxWidth", idlInt)),
Operation("fill", idlVoid),
Operation("closePath", idlVoid)
),
Interface("DOMRect",
Attribute("left", idlInt),
Attribute("right", idlInt),
Attribute("top", idlInt),
Attribute("bottom", idlInt)
),
Interface("Canvas",
Operation("getContext", idlInterfaceRef("Context"), Arg("context", idlString)),
Operation("getBoundingClientRect", idlInterfaceRef("DOMRect"))
),
Interface("Document",
Operation("getElementById", idlObject, Arg("id", idlString))
),
Interface("MouseEvent",
Attribute("clientX", idlInt, readOnly = true),
Attribute("clientY", idlInt, readOnly = true)
),
Interface("Response",
Operation("json", idlObject)
),
Interface("Promise",
Operation("then", idlInterfaceRef("Promise"), Arg("lambda", idlFunction))
),
Interface("__Global",
Attribute("document", idlInterfaceRef("Document"), readOnly = true),
Operation("fetch", idlInterfaceRef("Promise"), Arg("url", idlString)),
Operation("setInterval", idlVoid, Arg("lambda", idlFunction), Arg("interval", idlInt))
)
)
@@ -0,0 +1,37 @@
package org.jetbrains.kotlin.native.interop.gen.wasm.idl
// This is (as of now) a poor man's IDL representation.
interface Type
interface Member {
val isStatic: Boolean get() = false
}
object idlVoid: Type
object idlInt: Type
object idlFloat: Type
object idlDouble: Type
object idlString: Type
object idlObject: Type
object idlFunction: Type
data class Attribute(val name: String, val type: Type,
val readOnly: Boolean = false,
override val isStatic: Boolean = false): Member
data class Arg(val name: String, val type: Type)
class Operation(val name: String, val returnType: Type,
override val isStatic: Boolean = false,
vararg val args: Arg): Member {
constructor(name: String, returnType: Type, vararg args: Arg) :
this(name, returnType, false, *args)
}
data class idlInterfaceRef(val name: String): Type
class Interface(val name: String, vararg val members: Member) {
val isGlobal = (name == "__Global")
}
@@ -0,0 +1,73 @@
/*
* 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 org.jetbrains.kotlin.native.interop.gen.wasm.idl
// There are no WebIDL descriptions of Math,
// so in any case this one will be a part of the project.
// Although, may be in the form of our own WebIDL source.
val idlMath = listOf(
Interface("Math",
Attribute("E", idlDouble, readOnly = true, isStatic = true),
Attribute("LN2", idlDouble, readOnly = true, isStatic = true),
Attribute("LN10", idlDouble, readOnly = true, isStatic = true),
Attribute("LOG2E", idlDouble, readOnly = true, isStatic = true),
Attribute("LOG10E", idlDouble, readOnly = true, isStatic = true),
Attribute("PI", idlDouble, readOnly = true, isStatic = true),
Attribute("SQRT1_2", idlDouble, readOnly = true, isStatic = true),
Attribute("SQRT2", idlDouble, readOnly = true, isStatic = true),
Operation("abs", idlDouble, true, Arg("x", idlDouble)),
Operation("acos", idlDouble, true, Arg("x", idlDouble)),
Operation("acosh", idlDouble, true, Arg("x", idlDouble)),
Operation("asin", idlDouble, true, Arg("x", idlDouble)),
Operation("asinh", idlDouble, true, Arg("x", idlDouble)),
Operation("atan", idlDouble, true, Arg("x", idlDouble)),
Operation("atanh", idlDouble, true, Arg("x", idlDouble)),
Operation("atan2", idlDouble, true, Arg("y", idlDouble), Arg("x", idlDouble)),
Operation("cbrt", idlDouble, true, Arg("x", idlDouble)),
Operation("ceil", idlDouble, true, Arg("x", idlDouble)),
Operation("clz32", idlDouble, true, Arg("x", idlDouble)),
Operation("cos", idlDouble, true, Arg("x", idlDouble)),
Operation("cosh", idlDouble, true, Arg("x", idlDouble)),
Operation("exp", idlDouble, true, Arg("x", idlDouble)),
Operation("expm1", idlDouble, true, Arg("x", idlDouble)),
Operation("floor", idlDouble, true, Arg("x", idlDouble)),
Operation("fround", idlDouble, true, Arg("x", idlDouble)),
//Operation("imul(x, y),
Operation("log", idlDouble, true, Arg("x", idlDouble)),
Operation("log1p", idlDouble, true, Arg("x", idlDouble)),
Operation("log10", idlDouble, true, Arg("x", idlDouble)),
Operation("log2", idlDouble, true, Arg("x", idlDouble)),
Operation("pow", idlDouble, true, Arg("x", idlDouble), Arg("y", idlDouble)),
Operation("random", idlDouble, true),
Operation("round", idlDouble, true, Arg("x", idlDouble)),
Operation("sign", idlDouble, true, Arg("x", idlDouble)),
Operation("sin", idlDouble, true, Arg("x", idlDouble)),
Operation("sinh", idlDouble, true, Arg("x", idlDouble)),
Operation("sqrt", idlDouble, true, Arg("x", idlDouble)),
Operation("tan", idlDouble, true, Arg("x", idlDouble)),
Operation("tanh", idlDouble, true, Arg("x", idlDouble)),
Operation("trunc", idlDouble, true, Arg("x", idlDouble)),
// Actually these functions have vararg parameter.
// But their kotlin analogs have only 2 parameters so we don't need to support varargs here.
Operation("hypot", idlDouble, true, Arg("x", idlDouble), Arg("y", idlDouble)),
Operation("max", idlDouble, true, Arg("x", idlDouble), Arg("y", idlDouble)),
Operation("min", idlDouble, true, Arg("x", idlDouble), Arg("y", idlDouble))
)
)
@@ -0,0 +1,102 @@
package org.jetbrains.kotlin.native.interop.gen
import kotlinx.metadata.KmAnnotationArgument
import kotlinx.metadata.KmClassifier
import kotlinx.metadata.KmModuleFragment
import kotlinx.metadata.klib.compileTimeValue
import kotlinx.metadata.klib.uniqId
import org.jetbrains.kotlin.native.interop.indexer.FunctionDecl
import org.jetbrains.kotlin.native.interop.indexer.IntegerConstantDef
import org.jetbrains.kotlin.native.interop.indexer.IntegerType
import org.junit.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue
class StubIrToMetadataTests {
companion object {
val intStubType = ClassifierStubType(Classifier.topLevel("kotlin", "Int"))
val intType = IntegerType(4, true, "int")
}
private fun createTrivialFunction(name: String): FunctionStub {
val cDeclaration = FunctionDecl(name, emptyList(), intType, "", false, false)
val origin = StubOrigin.Function(cDeclaration)
return FunctionStub(
name = cDeclaration.name,
returnType = intStubType,
parameters = listOf(),
origin = origin,
annotations = emptyList(),
external = true,
receiver = null,
modality = MemberStubModality.FINAL
)
}
private fun createTrivialIntegerConstantProperty(name: String, value: Long): PropertyStub {
val origin = StubOrigin.Constant(IntegerConstantDef(name, intType, value))
return PropertyStub(
name = name,
type = intStubType,
kind = PropertyStub.Kind.Constant(IntegralConstantStub(value, intType.size, true)),
origin = origin
)
}
private fun createMetadata(
fqName: String,
functions: List<FunctionStub> = emptyList(),
properties: List<PropertyStub> = emptyList()
) = SimpleStubContainer(functions = functions, properties = properties)
.let { ModuleMetadataEmitter(fqName, it).emit() }
.also(this::checkUniqIdPresence)
private fun checkUniqIdPresence(metadata: KmModuleFragment) {
metadata.classes.forEach { assertNotNull(it.uniqId) }
metadata.pkg?.let { pkg ->
pkg.functions.forEach { assertNotNull(it.uniqId) }
pkg.properties.forEach { assertNotNull(it.uniqId) }
pkg.typeAliases.forEach { assertNotNull(it.uniqId) }
}
}
@Test
fun `single simple function`() {
val packageName = "single_function"
val function = createTrivialFunction("hello")
val metadata = createMetadata(packageName, functions = listOf(function))
with (metadata) {
assertEquals(packageName, packageName)
assertTrue(classes.isEmpty())
assertNotNull(pkg)
assertTrue(pkg!!.functions.size == 1)
val kmFunction = pkg!!.functions[0]
assertEquals(kmFunction.name, function.name)
assertEquals(0, kmFunction.valueParameters.size)
val returnTypeClassifier = kmFunction.returnType.classifier
assertTrue(returnTypeClassifier is KmClassifier.Class)
assertEquals("kotlin/Int", returnTypeClassifier.name)
}
}
@Test
fun `single constant`() {
val property = createTrivialIntegerConstantProperty("meaning", 42)
val metadata = createMetadata("single_property", properties = listOf(property))
with (metadata) {
assertNotNull(pkg)
assertTrue(pkg!!.properties.size == 1)
val kmProperty = pkg!!.properties[0]
assertEquals(kmProperty.name, property.name)
val compileTimeValue = kmProperty.compileTimeValue
assertNotNull(compileTimeValue)
assertTrue(compileTimeValue is KmAnnotationArgument.IntValue)
assertEquals(42, compileTimeValue.value)
}
}
}