Support pointer macros in cinterop (#2279)
This commit is contained in:
+2
-1
@@ -145,6 +145,7 @@ internal class NativeIndexImpl(val library: NativeLibrary) : NativeIndex() {
|
||||
get() = functionById.values
|
||||
|
||||
override val macroConstants = mutableListOf<ConstantDef>()
|
||||
override val wrappedMacros = mutableListOf<WrappedMacroDef>()
|
||||
|
||||
private val globalById = mutableMapOf<DeclarationID, GlobalDecl>()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
+66
-45
@@ -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<CXType>) -> Type
|
||||
@@ -40,11 +40,11 @@ private typealias TypeConverter = (CValue<CXType>) -> Type
|
||||
*
|
||||
* @return the list of constants.
|
||||
*/
|
||||
private fun expandMacroConstants(
|
||||
private fun expandMacros(
|
||||
originalLibrary: NativeLibrary,
|
||||
names: List<String>,
|
||||
typeConverter: TypeConverter
|
||||
): List<ConstantDef> {
|
||||
): List<MacroDef> {
|
||||
|
||||
// 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<String> {
|
||||
private fun collectMacroNames(nativeIndex: NativeIndexImpl): List<String> {
|
||||
val result = mutableSetOf<String>()
|
||||
val index = clang_createIndex(excludeDeclarationsFromPCH = 0, displayDiagnostics = 0)!!
|
||||
try {
|
||||
|
||||
+6
-2
@@ -67,6 +67,7 @@ abstract class NativeIndex {
|
||||
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>
|
||||
}
|
||||
@@ -190,13 +191,16 @@ class FunctionDecl(val name: String, val parameters: List<Parameter>, 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.
|
||||
|
||||
+1
@@ -26,6 +26,7 @@ class InteropConfiguration(
|
||||
val library: NativeLibrary,
|
||||
val pkgName: String,
|
||||
val excludedFunctions: Set<String>,
|
||||
val excludedMacros: Set<String>,
|
||||
val strictEnums: Set<String>,
|
||||
val nonStrictEnums: Set<String>,
|
||||
val noStringConversion: Set<String>,
|
||||
|
||||
+14
-1
@@ -57,6 +57,9 @@ class StubGenerator(
|
||||
val excludedFunctions: Set<String>
|
||||
get() = configuration.excludedFunctions
|
||||
|
||||
val excludedMacros: Set<String>
|
||||
get() = configuration.excludedMacros
|
||||
|
||||
val noStringConversion: Set<String>
|
||||
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(
|
||||
|
||||
+2
@@ -207,6 +207,7 @@ private fun processCLib(args: Array<String>): Array<String>? {
|
||||
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<String>): Array<String>? {
|
||||
library = library,
|
||||
pkgName = outKtPkg,
|
||||
excludedFunctions = excludedFunctions,
|
||||
excludedMacros = excludedMacros,
|
||||
strictEnums = def.config.strictEnums.toSet(),
|
||||
nonStrictEnums = def.config.nonStrictEnums.toSet(),
|
||||
noStringConversion = def.config.noStringConversion.toSet(),
|
||||
|
||||
@@ -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(); }
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
import kotlin.test.*
|
||||
import cmacros.*
|
||||
import kotlinx.cinterop.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
assertEquals("foo", FOO_STRING)
|
||||
@@ -21,4 +22,20 @@ fun main(args: Array<String>) {
|
||||
|
||||
assertEquals(1.5f, onePointFive)
|
||||
assertEquals(1.0, onePointZero)
|
||||
|
||||
val nullPtr: COpaquePointer? = NULL_PTR
|
||||
val voidPtr: COpaquePointer? = VOID_PTR
|
||||
val intPtr: CPointer<IntVar>? = INT_PTR
|
||||
val ptrSum: CPointer<IntVar>? = PTR_SUM
|
||||
val ptrCall: CPointer<IntVar>? = 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)
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user