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 431c8808777..12a68d02c7a 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 @@ -145,6 +145,7 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { get() = functionById.values override val macroConstants = mutableListOf() + override val wrappedMacros = mutableListOf() private val globalById = mutableMapOf() @@ -904,7 +905,7 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() { fun buildNativeIndexImpl(library: NativeLibrary): NativeIndex { val result = NativeIndexImpl(library) indexDeclarations(result) - findMacroConstants(result) + findMacros(result) return result } diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt index e1ef75446c7..d55102f3a41 100644 --- a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt @@ -23,13 +23,13 @@ import java.io.File /** * Finds all "macro constants" and registers them as [NativeIndex.constants] in given index. */ -internal fun findMacroConstants(nativeIndex: NativeIndexImpl) { - val names = collectMacroConstantsNames(nativeIndex) +internal fun findMacros(nativeIndex: NativeIndexImpl) { + val names = collectMacroNames(nativeIndex) // TODO: apply user-defined filters. + val macros = expandMacros(nativeIndex.library, names, typeConverter = { nativeIndex.convertType(it) }) - val constants = expandMacroConstants(nativeIndex.library, names, typeConverter = { nativeIndex.convertType(it) }) - - nativeIndex.macroConstants.addAll(constants) + macros.filterIsInstanceTo(nativeIndex.macroConstants) + macros.filterIsInstanceTo(nativeIndex.wrappedMacros) } private typealias TypeConverter = (CValue) -> Type @@ -40,11 +40,11 @@ private typealias TypeConverter = (CValue) -> Type * * @return the list of constants. */ -private fun expandMacroConstants( +private fun expandMacros( originalLibrary: NativeLibrary, names: List, typeConverter: TypeConverter -): List { +): List { // Each macro is expanded by parsing it separately with the library; // so precompile library headers to significantly speed up the parsing: @@ -53,11 +53,14 @@ private fun expandMacroConstants( val index = clang_createIndex(excludeDeclarationsFromPCH = 1, displayDiagnostics = 0)!! try { val sourceFile = library.createTempSource() - val translationUnit = parseTranslationUnit(index, sourceFile, library.compilerArgs, options = 0) + // 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. + val compilerArgs = library.compilerArgs + listOf("-Werror=implicit-function-declaration") + val translationUnit = parseTranslationUnit(index, sourceFile, compilerArgs, options = 0) try { translationUnit.ensureNoCompileErrors() return names.mapNotNull { - expandMacroAsConstant(library, translationUnit, sourceFile, it, typeConverter) + expandMacro(library, translationUnit, sourceFile, it, typeConverter) } } finally { clang_disposeTranslationUnit(translationUnit) @@ -74,13 +77,13 @@ private fun expandMacroConstants( * * As a side effect, modifies the [sourceFile] and reparses the [translationUnit]. */ -private fun expandMacroAsConstant( +private fun expandMacro( library: NativeLibrary, translationUnit: CXTranslationUnit, sourceFile: File, name: String, typeConverter: TypeConverter -): ConstantDef? { +): MacroDef? { reparseWithCodeSnippet(library, translationUnit, sourceFile, name) @@ -95,7 +98,11 @@ private fun expandMacroAsConstant( * Adds the code snippet to be then processed with [processCodeSnippet] to the [sourceFile] * and reparses the [translationUnit]. * - * The code snippet should allow to extract the constant value using libclang API. + * - If the 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 reparseWithCodeSnippet(library: NativeLibrary, translationUnit: CXTranslationUnit, sourceFile: File, @@ -110,7 +117,7 @@ private fun reparseWithCodeSnippet(library: NativeLibrary, val codeSnippetLines = when (library.language) { // Note: __auto_type is a GNU extension which is supported by clang. Language.C, Language.OBJECTIVE_C -> listOf( - "const __auto_type KNI_INDEXER_VARIABLE = $name;" + "void kni_indexer_function() { __auto_type KNI_INDEXER_VARIABLE = $name; }" ) } @@ -127,9 +134,10 @@ private fun processCodeSnippet( translationUnit: CXTranslationUnit, name: String, typeConverter: TypeConverter -): ConstantDef? { +): MacroDef? { - var state = VisitorState.EXPECT_VARIABLE + val kindsToSkip = listOf(CXCursorKind.CXCursor_FunctionDecl, CXCursorKind.CXCursor_CompoundStmt) + var state = VisitorState.EXPECT_NODES_TO_SKIP var evalResultOrNull: CXEvalResult? = null var typeOrNull: Type? = null @@ -137,15 +145,8 @@ private fun processCodeSnippet( val kind = cursor.kind when { state == VisitorState.EXPECT_VARIABLE && kind == CXCursorKind.CXCursor_VarDecl -> { - val evalResult = clang_Cursor_Evaluate(cursor) - - if (evalResult != null) { - evalResultOrNull = evalResult - state = VisitorState.EXPECT_VARIABLE_VALUE - } else { - state = VisitorState.INVALID - } - + evalResultOrNull = clang_Cursor_Evaluate(cursor) + state = VisitorState.EXPECT_VARIABLE_VALUE CXChildVisitResult.CXChildVisit_Recurse } @@ -155,6 +156,15 @@ private fun processCodeSnippet( 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 @@ -169,30 +179,40 @@ private fun processCodeSnippet( return null } - val evalResult = evalResultOrNull!! val type = typeOrNull!! - val evalResultKind = clang_EvalResult_getKind(evalResult) + 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)) - return when (evalResultKind) { - CXEvalResultKind.CXEval_Int -> - IntegerConstantDef(name, type, clang_EvalResult_getAsLongLong(evalResult)) + CXEvalResultKind.CXEval_Float -> + FloatingConstantDef(name, type, clang_EvalResult_getAsDouble(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_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 + CXEvalResultKind.CXEval_Other, + CXEvalResultKind.CXEval_UnExposed -> null + } } } finally { @@ -201,11 +221,12 @@ private fun processCodeSnippet( } enum class VisitorState { + EXPECT_NODES_TO_SKIP, EXPECT_VARIABLE, EXPECT_VARIABLE_VALUE, EXPECT_END, INVALID } -private fun collectMacroConstantsNames(nativeIndex: NativeIndexImpl): List { +private fun collectMacroNames(nativeIndex: NativeIndexImpl): List { val result = mutableSetOf() val index = clang_createIndex(excludeDeclarationsFromPCH = 0, displayDiagnostics = 0)!! try { 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 39eff61a6a7..762849974c1 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 @@ -67,6 +67,7 @@ abstract class NativeIndex { abstract val typedefs: Collection abstract val functions: Collection abstract val macroConstants: Collection + abstract val wrappedMacros: Collection abstract val globals: Collection abstract val includedHeaders: Collection } @@ -190,13 +191,16 @@ class FunctionDecl(val name: String, val parameters: List, val return */ class TypedefDef(val aliased: Type, val name: String, override val location: Location) : TypeDeclaration -abstract class ConstantDef(val name: String, val type: Type) +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 GlobalDecl(val name: String, val type: Type, val isConst: Boolean) +class WrappedMacroDef(name: String, val type: Type) : MacroDef(name) +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/jvm/InteropConfiguration.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/InteropConfiguration.kt index 34a21c67432..b83a5112575 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/InteropConfiguration.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/InteropConfiguration.kt @@ -26,6 +26,7 @@ class InteropConfiguration( val library: NativeLibrary, val pkgName: String, val excludedFunctions: Set, + val excludedMacros: Set, val strictEnums: Set, val nonStrictEnums: Set, val noStringConversion: Set, 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 8f824030749..e781f30fa2b 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 @@ -57,6 +57,9 @@ class StubGenerator( val excludedFunctions: Set get() = configuration.excludedFunctions + val excludedMacros: Set + get() = configuration.excludedMacros + val noStringConversion: Set get() = configuration.noStringConversion @@ -838,7 +841,7 @@ class StubGenerator( ObjCCategoryStub(this, it) } - nativeIndex.macroConstants.forEach { + nativeIndex.macroConstants.filter { it.name !in excludedMacros }.forEach { try { stubs.add( generateKotlinFragmentBy { generateConstant(it) } @@ -848,6 +851,16 @@ class StubGenerator( } } + nativeIndex.wrappedMacros.filter { it.name !in excludedMacros }.forEach { + try { + stubs.add( + GlobalVariableStub(GlobalDecl(it.name, it.type, isConst = true), this) + ) + } catch (e: Throwable) { + log("Warning: cannot generate stubs for macro ${it.name}") + } + } + nativeIndex.globals.filter { it.name !in excludedFunctions }.forEach { try { stubs.add( diff --git a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt index e338432082e..84d643c1535 100644 --- a/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt +++ b/Interop/StubGenerator/src/main/kotlin/org/jetbrains/kotlin/native/interop/gen/jvm/main.kt @@ -207,6 +207,7 @@ private fun processCLib(args: Array): Array? { 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 + arguments.staticLibrary val libraryPaths = def.config.libraryPaths + arguments.libraryPath val fqParts = (arguments.pkg ?: def.config.packageName)?.let { @@ -231,6 +232,7 @@ private fun processCLib(args: Array): Array? { library = library, pkgName = outKtPkg, excludedFunctions = excludedFunctions, + excludedMacros = excludedMacros, strictEnums = def.config.strictEnums.toSet(), nonStrictEnums = def.config.nonStrictEnums.toSet(), noStringConversion = def.config.noStringConversion.toSet(), diff --git a/backend.native/tests/interop/basics/cmacros.def b/backend.native/tests/interop/basics/cmacros.def index 20a26e75f4a..1f086802395 100644 --- a/backend.native/tests/interop/basics/cmacros.def +++ b/backend.native/tests/interop/basics/cmacros.def @@ -1,4 +1,29 @@ +excludedMacros = EXCLUDED --- +int* ptr_call() { + return (int*) 1; +} + +int int_call() { + return 42; +} + +void void_call() {} + +int arg_call(int x) { + return x; +} + +typedef struct { + int value +} struct_t; + +struct_t getStruct() { + return (struct_t){ 1 }; +} + +int global_var = 5; + #define ZERO 0 #define ONE 1 #define MAX_LONG 9223372036854775807 @@ -10,5 +35,26 @@ #define ONE_POINT_ZERO 1.0 #define ONE_POINT_FIVE 1.5f +#define NULL_PTR ((void*)0) +#define VOID_PTR ((void*)1) +#define INT_PTR ((int*)1) +#define PTR_SUM (INT_PTR + 1) +#define PTR_SUM_EXPECTED (sizeof(int) + 1) + +#define PTR_CALL ptr_call() +#define INT_CALL int_call() +#define CALL_SUM (int_call() + int_call()) +#define GLOBAL_VAR (global_var) + +// This one should be excluded: +#define EXCLUDED 42 + +// These ones should be ignored: +#define VOID_CALL (void_call()) +#define ARG_CALL(x) (arg_call(x)) +#define GET_STRUCT (getStruct()) +#define UNDECLARED (undeclared()) + #define BAD1 bar #define BAD2 5; +#define BAD3 { foo(); } diff --git a/backend.native/tests/interop/basics/macros.kt b/backend.native/tests/interop/basics/macros.kt index 0b3af3275e3..c28b931ee30 100644 --- a/backend.native/tests/interop/basics/macros.kt +++ b/backend.native/tests/interop/basics/macros.kt @@ -5,6 +5,7 @@ import kotlin.test.* import cmacros.* +import kotlinx.cinterop.* fun main(args: Array) { assertEquals("foo", FOO_STRING) @@ -21,4 +22,20 @@ fun main(args: Array) { assertEquals(1.5f, onePointFive) assertEquals(1.0, onePointZero) + + val nullPtr: COpaquePointer? = NULL_PTR + val voidPtr: COpaquePointer? = VOID_PTR + val intPtr: CPointer? = INT_PTR + val ptrSum: CPointer? = PTR_SUM + val ptrCall: CPointer? = PTR_CALL + + assertEquals(null, nullPtr) + assertEquals(1L, voidPtr.rawValue.toLong()) + assertEquals(1L, intPtr.rawValue.toLong()) + assertEquals(PTR_SUM_EXPECTED.toLong(), ptrSum.rawValue.toLong()) + assertEquals(1L, ptrCall.rawValue.toLong()) + + assertEquals(42, INT_CALL) + assertEquals(84, CALL_SUM) + assertEquals(5, GLOBAL_VAR) } diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt index f472da07331..b7b78d68e82 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/util/DefFile.kt @@ -64,6 +64,10 @@ class DefFile(val file:File?, val config:DefFileConfig, val manifestAddendProper properties.getSpaceSeparated("excludedFunctions") } + val excludedMacros by lazy { + properties.getSpaceSeparated("excludedMacros") + } + val staticLibraries by lazy { properties.getSpaceSeparated("staticLibraries") }