Support pointer macros in cinterop (#2279)

This commit is contained in:
Ilya Matveev
2018-11-04 13:58:37 +07:00
committed by GitHub
parent a05ad78784
commit 51f3123e9c
9 changed files with 158 additions and 49 deletions
@@ -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
}
@@ -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 {
@@ -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.
@@ -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>,
@@ -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(
@@ -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")
}