diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt index 6700c7b19e9..544a41577d9 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Indexer.kt @@ -159,6 +159,11 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { override val macroConstants = mutableListOf() + private val globalById = mutableMapOf() + + override val globals: Collection + get() = globalById.values + override lateinit var includedHeaders: List private fun getDeclarationId(cursor: CValue): DeclarationID { @@ -646,6 +651,19 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { 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 -> { if (isAvailable(cursor) && cursor.kind != CXCursorKind.CXCursor_ObjCClassRef /* not a forward declaration */) { diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt index 0aed7b0c44c..a23ba8530a6 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/NativeIndex.kt @@ -66,6 +66,7 @@ abstract class NativeIndex { abstract val typedefs: Collection abstract val functions: Collection abstract val macroConstants: Collection + abstract val globals: Collection abstract val includedHeaders: Collection } @@ -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 FloatingConstantDef(name: String, type: Type, val value: Double) : ConstantDef(name, type) +class GlobalDecl(val name: String, val type: Type, val isConst: Boolean) + /** * C type. diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt new file mode 100644 index 00000000000..a2ff61ed0a9 --- /dev/null +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/GlobalVariableStub.kt @@ -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 { + val lines = mutableListOf() + 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() + } + +} \ No newline at end of file diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt index 7f05fab7a36..abf5b5bda3f 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/KotlinCodeModel.kt @@ -28,6 +28,12 @@ interface KotlinScope { * @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(name: String): String? } data class Classifier( @@ -210,6 +216,7 @@ class KotlinFile( } private val importedNameToPkg = mutableMapOf() + private val declaredProperties = mutableSetOf() override fun reference(classifier: Classifier): String = if (classifier.topLevelName in namesToBeDeclared) { if (classifier.pkg == this.pkg) { @@ -223,8 +230,7 @@ class KotlinFile( "'${classifier.topLevelName}' from the file package was not reserved for declaration" ) } else { - val pkg = importedNameToPkg.getOrPut(classifier.topLevelName) { classifier.pkg } - if (pkg == classifier.pkg) { + if (tryImport(classifier)) { // Is successfully imported: classifier.relativeFqName } 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() override fun declare(classifier: Classifier): String { @@ -254,6 +268,15 @@ class KotlinFile( 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 = importedNameToPkg.mapNotNull { (name, pkg) -> if (pkg == "kotlin" || pkg == "kotlinx.cinterop") { // Is already imported either by default or with '*': diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt index 908c35143cd..af6bf265399 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/StubGenerator.kt @@ -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 -> try { stubs.add( diff --git a/backend.native/tests/build.gradle b/backend.native/tests/build.gradle index e3486ac33cc..a21847087f0 100644 --- a/backend.native/tests/build.gradle +++ b/backend.native/tests/build.gradle @@ -2079,6 +2079,11 @@ kotlinNativeInterop { flavor 'native' } + cglobals { + defFile 'interop/basics/cglobals.def' + flavor 'native' + } + cfunptr { defFile 'interop/basics/cfunptr.def' flavor 'native' @@ -2160,6 +2165,12 @@ task interop_funptr(type: RunInteropKonanTest) { 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) { disabled = (project.testTarget == 'wasm32') // No interop for wasm yet. if (!isMac()) { diff --git a/backend.native/tests/interop/basics/cglobals.def b/backend.native/tests/interop/basics/cglobals.def new file mode 100644 index 00000000000..6472aea84e1 --- /dev/null +++ b/backend.native/tests/interop/basics/cglobals.def @@ -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_ {}; diff --git a/backend.native/tests/interop/basics/globals.kt b/backend.native/tests/interop/basics/globals.kt new file mode 100644 index 00000000000..5329a43d82b --- /dev/null +++ b/backend.native/tests/interop/basics/globals.kt @@ -0,0 +1,25 @@ +import kotlinx.cinterop.* +import cglobals.* + +fun main(args: Array) { + 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) +}