Support global variables in interop (#903)
This commit is contained in:
committed by
GitHub
parent
7a4aae5ff3
commit
7795a7856a
+18
@@ -159,6 +159,11 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
|||||||
|
|
||||||
override val macroConstants = mutableListOf<ConstantDef>()
|
override val macroConstants = mutableListOf<ConstantDef>()
|
||||||
|
|
||||||
|
private val globalById = mutableMapOf<DeclarationID, GlobalDecl>()
|
||||||
|
|
||||||
|
override val globals: Collection<GlobalDecl>
|
||||||
|
get() = globalById.values
|
||||||
|
|
||||||
override lateinit var includedHeaders: List<HeaderId>
|
override lateinit var includedHeaders: List<HeaderId>
|
||||||
|
|
||||||
private fun getDeclarationId(cursor: CValue<CXCursor>): DeclarationID {
|
private fun getDeclarationId(cursor: CValue<CXCursor>): DeclarationID {
|
||||||
@@ -646,6 +651,19 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
|||||||
getEnumDefAt(cursor)
|
getEnumDefAt(cursor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CXIdxEntity_Variable -> {
|
||||||
|
if (info.semanticContainer!!.pointed.cursor.kind == CXCursorKind.CXCursor_TranslationUnit) {
|
||||||
|
// Top-level variable.
|
||||||
|
globalById.getOrPut(getDeclarationId(cursor)) {
|
||||||
|
GlobalDecl(
|
||||||
|
name = entityName!!,
|
||||||
|
type = convertCursorType(cursor),
|
||||||
|
isConst = clang_isConstQualifiedType(clang_getCursorType(cursor)) != 0
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CXIdxEntity_ObjCClass -> {
|
CXIdxEntity_ObjCClass -> {
|
||||||
if (isAvailable(cursor) &&
|
if (isAvailable(cursor) &&
|
||||||
cursor.kind != CXCursorKind.CXCursor_ObjCClassRef /* not a forward declaration */) {
|
cursor.kind != CXCursorKind.CXCursor_ObjCClassRef /* not a forward declaration */) {
|
||||||
|
|||||||
+3
@@ -66,6 +66,7 @@ abstract class NativeIndex {
|
|||||||
abstract val typedefs: Collection<TypedefDef>
|
abstract val typedefs: Collection<TypedefDef>
|
||||||
abstract val functions: Collection<FunctionDecl>
|
abstract val functions: Collection<FunctionDecl>
|
||||||
abstract val macroConstants: Collection<ConstantDef>
|
abstract val macroConstants: Collection<ConstantDef>
|
||||||
|
abstract val globals: Collection<GlobalDecl>
|
||||||
abstract val includedHeaders: Collection<HeaderId>
|
abstract val includedHeaders: Collection<HeaderId>
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -189,6 +190,8 @@ abstract class ConstantDef(val name: String, val type: Type)
|
|||||||
class IntegerConstantDef(name: String, type: Type, val value: Long) : ConstantDef(name, type)
|
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 FloatingConstantDef(name: String, type: Type, val value: Double) : ConstantDef(name, type)
|
||||||
|
|
||||||
|
class GlobalDecl(val name: String, val type: Type, val isConst: Boolean)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* C type.
|
* C type.
|
||||||
|
|||||||
+100
@@ -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.gen.jvm.StubGenerator
|
||||||
|
import org.jetbrains.kotlin.native.interop.indexer.ArrayType
|
||||||
|
import org.jetbrains.kotlin.native.interop.indexer.GlobalDecl
|
||||||
|
|
||||||
|
class GlobalVariableStub(global: GlobalDecl, stubGenerator: StubGenerator) : KotlinStub, NativeBacked {
|
||||||
|
|
||||||
|
val getAddressExpression: KotlinExpression
|
||||||
|
val header: String
|
||||||
|
val getter: KotlinExpression
|
||||||
|
val setter: KotlinExpression?
|
||||||
|
|
||||||
|
init {
|
||||||
|
getAddressExpression = stubGenerator.simpleBridgeGenerator.kotlinToNative(
|
||||||
|
nativeBacked = this,
|
||||||
|
returnType = BridgedType.NATIVE_PTR,
|
||||||
|
kotlinValues = emptyList()
|
||||||
|
) {
|
||||||
|
"&${global.name}"
|
||||||
|
}
|
||||||
|
|
||||||
|
val kotlinScope = stubGenerator.kotlinFile
|
||||||
|
|
||||||
|
// TODO: consider sharing the logic below with field generator.
|
||||||
|
|
||||||
|
val kotlinType: KotlinType
|
||||||
|
|
||||||
|
val mirror = mirror(stubGenerator.declarationMapper, global.type)
|
||||||
|
val unwrappedType = global.type.unwrapTypedefs()
|
||||||
|
|
||||||
|
if (unwrappedType is ArrayType) {
|
||||||
|
kotlinType = (mirror as TypeMirror.ByValue).valueType
|
||||||
|
getter = mirror.info.argFromBridged(getAddressExpression, kotlinScope) + "!!"
|
||||||
|
setter = null
|
||||||
|
} else {
|
||||||
|
val pointedTypeName = mirror.pointedType.render(kotlinScope)
|
||||||
|
val storagePointed = "interpretPointed<$pointedTypeName>($getAddressExpression)"
|
||||||
|
if (mirror is TypeMirror.ByValue) {
|
||||||
|
kotlinType = mirror.argType
|
||||||
|
val valueProperty = "$storagePointed.value"
|
||||||
|
getter = valueProperty
|
||||||
|
setter = if (global.isConst) null else "$valueProperty = value"
|
||||||
|
} else {
|
||||||
|
kotlinType = mirror.pointedType
|
||||||
|
getter = storagePointed
|
||||||
|
setter = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
header = buildString {
|
||||||
|
append(if (setter != null) "var" else "val")
|
||||||
|
append(" ")
|
||||||
|
append(getDeclarationName(kotlinScope, global.name))
|
||||||
|
append(": ")
|
||||||
|
append(kotlinType.render(kotlinScope))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to use the provided name. If failed, mangle it with underscore and try again:
|
||||||
|
private tailrec fun getDeclarationName(scope: KotlinScope, name: String): String =
|
||||||
|
scope.declareProperty(name) ?: getDeclarationName(scope, name + "_")
|
||||||
|
|
||||||
|
override fun generate(context: StubGenerationContext): Sequence<String> {
|
||||||
|
val lines = mutableListOf<String>()
|
||||||
|
if (context.nativeBridges.isSupported(this)) {
|
||||||
|
lines.add(header)
|
||||||
|
lines.add(" get() = $getter")
|
||||||
|
if (setter != null) {
|
||||||
|
lines.add(" set(value) { $setter }")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lines.add(annotationForUnableToImport)
|
||||||
|
lines.add(header)
|
||||||
|
lines.add(" get() = TODO()")
|
||||||
|
if (setter != null) {
|
||||||
|
lines.add(" set(value) = TODO()")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return lines.asSequence()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
+25
-2
@@ -28,6 +28,12 @@ interface KotlinScope {
|
|||||||
* @return the string to be used as a name in the declaration of the classifier in current scope.
|
* @return the string to be used as a name in the declaration of the classifier in current scope.
|
||||||
*/
|
*/
|
||||||
fun declare(classifier: Classifier): String
|
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(name: String): String?
|
||||||
}
|
}
|
||||||
|
|
||||||
data class Classifier(
|
data class Classifier(
|
||||||
@@ -210,6 +216,7 @@ class KotlinFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private val importedNameToPkg = mutableMapOf<String, String>()
|
private val importedNameToPkg = mutableMapOf<String, String>()
|
||||||
|
private val declaredProperties = mutableSetOf<String>()
|
||||||
|
|
||||||
override fun reference(classifier: Classifier): String = if (classifier.topLevelName in namesToBeDeclared) {
|
override fun reference(classifier: Classifier): String = if (classifier.topLevelName in namesToBeDeclared) {
|
||||||
if (classifier.pkg == this.pkg) {
|
if (classifier.pkg == this.pkg) {
|
||||||
@@ -223,8 +230,7 @@ class KotlinFile(
|
|||||||
"'${classifier.topLevelName}' from the file package was not reserved for declaration"
|
"'${classifier.topLevelName}' from the file package was not reserved for declaration"
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
val pkg = importedNameToPkg.getOrPut(classifier.topLevelName) { classifier.pkg }
|
if (tryImport(classifier)) {
|
||||||
if (pkg == classifier.pkg) {
|
|
||||||
// Is successfully imported:
|
// Is successfully imported:
|
||||||
classifier.relativeFqName
|
classifier.relativeFqName
|
||||||
} else {
|
} else {
|
||||||
@@ -232,6 +238,14 @@ class KotlinFile(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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>()
|
private val alreadyDeclared = mutableSetOf<String>()
|
||||||
|
|
||||||
override fun declare(classifier: Classifier): String {
|
override fun declare(classifier: Classifier): String {
|
||||||
@@ -254,6 +268,15 @@ class KotlinFile(
|
|||||||
return topLevelName.asSimpleName()
|
return topLevelName.asSimpleName()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun declareProperty(name: String): String? =
|
||||||
|
if (name 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(name)
|
||||||
|
name.asSimpleName()
|
||||||
|
}
|
||||||
|
|
||||||
fun buildImports(): List<String> = importedNameToPkg.mapNotNull { (name, pkg) ->
|
fun buildImports(): List<String> = importedNameToPkg.mapNotNull { (name, pkg) ->
|
||||||
if (pkg == "kotlin" || pkg == "kotlinx.cinterop") {
|
if (pkg == "kotlin" || pkg == "kotlinx.cinterop") {
|
||||||
// Is already imported either by default or with '*':
|
// Is already imported either by default or with '*':
|
||||||
|
|||||||
+10
@@ -800,6 +800,16 @@ class StubGenerator(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
nativeIndex.globals.forEach {
|
||||||
|
try {
|
||||||
|
stubs.add(
|
||||||
|
GlobalVariableStub(it, this)
|
||||||
|
)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
log("Warning: cannot generate stubs for global ${it.name}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nativeIndex.structs.forEach { s ->
|
nativeIndex.structs.forEach { s ->
|
||||||
try {
|
try {
|
||||||
stubs.add(
|
stubs.add(
|
||||||
|
|||||||
@@ -2079,6 +2079,11 @@ kotlinNativeInterop {
|
|||||||
flavor 'native'
|
flavor 'native'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cglobals {
|
||||||
|
defFile 'interop/basics/cglobals.def'
|
||||||
|
flavor 'native'
|
||||||
|
}
|
||||||
|
|
||||||
cfunptr {
|
cfunptr {
|
||||||
defFile 'interop/basics/cfunptr.def'
|
defFile 'interop/basics/cfunptr.def'
|
||||||
flavor 'native'
|
flavor 'native'
|
||||||
@@ -2160,6 +2165,12 @@ task interop_funptr(type: RunInteropKonanTest) {
|
|||||||
interop = 'cfunptr'
|
interop = 'cfunptr'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task interop_globals(type: RunInteropKonanTest) {
|
||||||
|
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
|
||||||
|
source = "interop/basics/globals.kt"
|
||||||
|
interop = 'cglobals'
|
||||||
|
}
|
||||||
|
|
||||||
task interop_echo_server(type: RunInteropKonanTest) {
|
task interop_echo_server(type: RunInteropKonanTest) {
|
||||||
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
|
disabled = (project.testTarget == 'wasm32') // No interop for wasm yet.
|
||||||
if (!isMac()) {
|
if (!isMac()) {
|
||||||
|
|||||||
@@ -0,0 +1,28 @@
|
|||||||
|
---
|
||||||
|
const int g1 = 42;
|
||||||
|
|
||||||
|
int g2 = 17;
|
||||||
|
|
||||||
|
struct S {
|
||||||
|
int x;
|
||||||
|
} g3 = { 128 };
|
||||||
|
|
||||||
|
int g4[2] = { 13, 14 };
|
||||||
|
|
||||||
|
int g5[2][2] = { 15, 16, 17, 18 };
|
||||||
|
|
||||||
|
struct S* const g6 = &g3;
|
||||||
|
|
||||||
|
void foo() {
|
||||||
|
// Test that local vars are not treated as global ones.
|
||||||
|
float g1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test non-compilable variable:
|
||||||
|
typedef int MyInt;
|
||||||
|
MyInt g7;
|
||||||
|
#define g7 bad macro
|
||||||
|
|
||||||
|
// Test property name mangling:
|
||||||
|
struct g1 {};
|
||||||
|
struct g1_ {};
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import kotlinx.cinterop.*
|
||||||
|
import cglobals.*
|
||||||
|
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
assert(g1__ == 42)
|
||||||
|
|
||||||
|
assert(g2 == 17)
|
||||||
|
g2 = 42
|
||||||
|
assert(g2 == 42)
|
||||||
|
|
||||||
|
assert(g3.x == 128)
|
||||||
|
g3.x = 7
|
||||||
|
assert(g3.x == 7)
|
||||||
|
|
||||||
|
assert(g4[1] == 14)
|
||||||
|
g4[1] = 15
|
||||||
|
assert(g4[1] == 15)
|
||||||
|
|
||||||
|
assert(g5[0] == 15)
|
||||||
|
assert(g5[3] == 18)
|
||||||
|
g5[0] = 16
|
||||||
|
assert(g5[0] == 16)
|
||||||
|
|
||||||
|
assert(g6 == g3.ptr)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user