Interop: extract constants defined as (preprocessor) macros
Support all macros extending to integer and floating-point constant expressions. Also do some refactoring.
This commit is contained in:
committed by
SvyatoslavScherbina
parent
01dd86d3b5
commit
f8d54fc8f7
+65
-68
@@ -4,7 +4,6 @@ import clang.*
|
||||
import clang.CXIdxEntityKind.*
|
||||
import clang.CXTypeKind.*
|
||||
import kotlinx.cinterop.*
|
||||
import java.io.File
|
||||
|
||||
private class StructDeclImpl(spelling: String) : StructDecl(spelling) {
|
||||
override var def: StructDefImpl? = null
|
||||
@@ -20,21 +19,21 @@ private class EnumDefImpl(spelling: String, type: PrimitiveType) : EnumDef(spell
|
||||
override val values = mutableListOf<EnumValue>()
|
||||
}
|
||||
|
||||
private class NativeIndexImpl : NativeIndex() {
|
||||
internal class NativeIndexImpl : NativeIndex() {
|
||||
|
||||
private data class DeclarationID(val usr: String)
|
||||
|
||||
val structById = mutableMapOf<DeclarationID, StructDeclImpl>()
|
||||
private val structById = mutableMapOf<DeclarationID, StructDeclImpl>()
|
||||
|
||||
override val structs: List<StructDecl>
|
||||
get() = structById.values.toList()
|
||||
|
||||
val enumById = mutableMapOf<DeclarationID, EnumDefImpl>()
|
||||
private val enumById = mutableMapOf<DeclarationID, EnumDefImpl>()
|
||||
|
||||
override val enums: List<EnumDef>
|
||||
get() = enumById.values.toList()
|
||||
|
||||
val typedefById = mutableMapOf<DeclarationID, TypedefDef>()
|
||||
private val typedefById = mutableMapOf<DeclarationID, TypedefDef>()
|
||||
|
||||
override val typedefs: List<TypedefDef>
|
||||
get() = typedefById.values.toList()
|
||||
@@ -44,12 +43,14 @@ private class NativeIndexImpl : NativeIndex() {
|
||||
override val functions: List<FunctionDecl>
|
||||
get() = functionByName.values.toList()
|
||||
|
||||
fun getDeclarationId(cursor: CXCursor): DeclarationID = memScoped {
|
||||
override val macroConstants = mutableListOf<ConstantDef>()
|
||||
|
||||
private fun getDeclarationId(cursor: CXCursor): DeclarationID = memScoped {
|
||||
val usr = clang_getCursorUSR(cursor, memScope).convertAndDispose()
|
||||
DeclarationID(usr)
|
||||
}
|
||||
|
||||
fun getStructDeclAt(cursor: CXCursor): StructDeclImpl {
|
||||
private fun getStructDeclAt(cursor: CXCursor): StructDeclImpl {
|
||||
val declId = getDeclarationId(cursor)
|
||||
|
||||
return structById.getOrPut(declId) {
|
||||
@@ -62,7 +63,7 @@ private class NativeIndexImpl : NativeIndex() {
|
||||
}
|
||||
}
|
||||
|
||||
fun getEnumDefAt(cursor: CXCursor): EnumDefImpl {
|
||||
private fun getEnumDefAt(cursor: CXCursor): EnumDefImpl {
|
||||
if (clang_isCursorDefinition(cursor) == 0) {
|
||||
TODO("support enum forward declarations")
|
||||
}
|
||||
@@ -101,7 +102,7 @@ private class NativeIndexImpl : NativeIndex() {
|
||||
|
||||
fun getTypedef(type: CXType): Type = memScoped {
|
||||
val declCursor = clang_getTypeDeclaration(type, memScope)
|
||||
val name = clang_getCursorSpelling(declCursor, memScope).convertAndDispose()
|
||||
val name = getCursorSpelling(declCursor)
|
||||
|
||||
val underlying = convertType(clang_getTypedefDeclUnderlyingType(declCursor, memScope))
|
||||
|
||||
@@ -160,7 +161,16 @@ private class NativeIndexImpl : NativeIndex() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun convertCursorType(cursor: CXCursor) = memScoped {
|
||||
convertType(clang_getCursorType(cursor, memScope))
|
||||
}
|
||||
|
||||
fun convertType(type: CXType): Type = memScoped {
|
||||
val primitiveType = convertUnqualifiedPrimitiveType(type)
|
||||
if (primitiveType != UnsupportedType) {
|
||||
return primitiveType
|
||||
}
|
||||
|
||||
val kind = type.kind.value
|
||||
return when (kind) {
|
||||
CXType_Elaborated -> convertType(clang_Type_getNamedType(type, memScope))
|
||||
@@ -174,24 +184,8 @@ private class NativeIndexImpl : NativeIndex() {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: is e.g. CXType_Int guaranteed to be int32_t?
|
||||
CXType_Void -> VoidType
|
||||
|
||||
CXType_Char_U, CXType_UChar -> UInt8Type
|
||||
CXType_Char_S, CXType_SChar -> Int8Type
|
||||
|
||||
CXType_UShort -> UInt16Type
|
||||
CXType_Short -> Int16Type
|
||||
|
||||
CXType_UInt -> UInt32Type
|
||||
CXType_Int -> Int32Type
|
||||
|
||||
CXType_ULong -> UIntPtrType
|
||||
CXType_Long -> IntPtrType
|
||||
|
||||
CXType_ULongLong -> UInt64Type
|
||||
CXType_LongLong -> Int64Type
|
||||
|
||||
CXType_Typedef -> getTypedef(type)
|
||||
|
||||
CXType_Record -> RecordType(getStructDeclAt(clang_getTypeDeclaration(type, memScope)))
|
||||
@@ -236,7 +230,7 @@ private class NativeIndexImpl : NativeIndex() {
|
||||
when (kind) {
|
||||
CXIdxEntity_Field -> {
|
||||
val name = entityName!!
|
||||
val type = convertType(clang_getCursorType(cursor, memScope))
|
||||
val type = convertCursorType(cursor)
|
||||
val offset = clang_Cursor_getOffsetOfField(cursor)
|
||||
|
||||
val container = info.semanticContainer.pointed!!
|
||||
@@ -249,7 +243,7 @@ private class NativeIndexImpl : NativeIndex() {
|
||||
if (clang_isCursorDefinition(cursor) != 0) {
|
||||
val type = clang_getCursorType(cursor, memScope)
|
||||
val size = clang_Type_getSizeOf(type)
|
||||
val align = clang_Type_getAlignOf(clang_getCursorType(cursor, memScope)).toInt()
|
||||
val align = clang_Type_getAlignOf(type).toInt()
|
||||
val hasNaturalLayout = structHasNaturalLayout(cursor)
|
||||
structDecl.def = StructDefImpl(size, align, structDecl, hasNaturalLayout)
|
||||
}
|
||||
@@ -261,8 +255,8 @@ private class NativeIndexImpl : NativeIndex() {
|
||||
val argNum = clang_Cursor_getNumArguments(cursor)
|
||||
val args = (0 .. argNum - 1).map {
|
||||
val argCursor = clang_Cursor_getArgument(cursor, it, memScope)
|
||||
val argName = clang_getCursorSpelling(argCursor, memScope).convertAndDispose()
|
||||
val type = convertType(clang_getCursorType(argCursor, memScope))
|
||||
val argName = getCursorSpelling(argCursor)
|
||||
val type = convertCursorType(argCursor)
|
||||
Parameter(argName, type)
|
||||
}
|
||||
|
||||
@@ -293,50 +287,53 @@ private class NativeIndexImpl : NativeIndex() {
|
||||
|
||||
}
|
||||
|
||||
fun CXString.convertAndDispose(): String {
|
||||
try {
|
||||
return clang_getCString(this)!!.asCString().toString()
|
||||
} finally {
|
||||
clang_disposeString(this)
|
||||
}
|
||||
fun buildNativeIndexImpl(library: NativeLibrary): NativeIndex {
|
||||
val result = NativeIndexImpl()
|
||||
indexDeclarations(library, result)
|
||||
findMacroConstants(library, result)
|
||||
return result
|
||||
}
|
||||
|
||||
fun buildNativeIndexImpl(headerFile: File, args: List<String>): NativeIndex {
|
||||
// TODO: dispose all allocated memory and resources
|
||||
val args1 = args.map { CString.fromString(it, nativeHeap)!!.asCharPtr() }.toTypedArray()
|
||||
|
||||
val index = clang_createIndex(0, 0)
|
||||
val indexAction = clang_IndexAction_create(index)
|
||||
val callbacks = nativeHeap.alloc<IndexerCallbacks>()
|
||||
|
||||
val res = NativeIndexImpl()
|
||||
val nativeIndexPtr = StableObjPtr.create(res)
|
||||
val clientData = nativeIndexPtr.value
|
||||
|
||||
private fun indexDeclarations(library: NativeLibrary, nativeIndex: NativeIndexImpl) {
|
||||
val index = clang_createIndex(0, 0)!!
|
||||
val indexAction = clang_IndexAction_create(index)!!
|
||||
try {
|
||||
with(callbacks) {
|
||||
abortQuery.value = null
|
||||
diagnostic.value = null
|
||||
enteredMainFile.value = null
|
||||
ppIncludedFile.value = null
|
||||
importedASTFile.value = null
|
||||
startedTranslationUnit.value = null
|
||||
indexDeclaration.value = staticCFunction { clientData, info ->
|
||||
val nativeIndex = StableObjPtr.fromValue(clientData!!).get() as NativeIndexImpl
|
||||
nativeIndex.indexDeclaration(info!!.pointed)
|
||||
val translationUnit = library.parse(index).ensureNoCompileErrors()
|
||||
try {
|
||||
memScoped {
|
||||
val callbacks = alloc<IndexerCallbacks>()
|
||||
|
||||
val nativeIndexPtr = StableObjPtr.create(nativeIndex)
|
||||
val clientData = nativeIndexPtr.value
|
||||
|
||||
try {
|
||||
with(callbacks) {
|
||||
abortQuery.value = null
|
||||
diagnostic.value = null
|
||||
enteredMainFile.value = null
|
||||
ppIncludedFile.value = null
|
||||
importedASTFile.value = null
|
||||
startedTranslationUnit.value = null
|
||||
indexDeclaration.value = staticCFunction { clientData, info ->
|
||||
val nativeIndex = StableObjPtr.fromValue(clientData!!).get() as NativeIndexImpl
|
||||
nativeIndex.indexDeclaration(info!!.pointed)
|
||||
}
|
||||
indexEntityReference.value = null
|
||||
}
|
||||
|
||||
clang_indexTranslationUnit(indexAction, clientData,
|
||||
callbacks.ptr, IndexerCallbacks.size.toInt(),
|
||||
0, translationUnit)
|
||||
|
||||
} finally {
|
||||
nativeIndexPtr.dispose()
|
||||
}
|
||||
}
|
||||
indexEntityReference.value = null
|
||||
} finally {
|
||||
clang_disposeTranslationUnit(translationUnit)
|
||||
}
|
||||
|
||||
val commandLineArgs = nativeHeap.allocArrayOfPointersTo(*args1)[0].ptr
|
||||
|
||||
clang_indexSourceFile(indexAction, clientData, callbacks.ptr, IndexerCallbacks.size.toInt(),
|
||||
0, headerFile.path, commandLineArgs, args1.size, null, 0, null, 0)
|
||||
|
||||
|
||||
return res
|
||||
|
||||
} finally {
|
||||
nativeIndexPtr.dispose()
|
||||
clang_IndexAction_dispose(indexAction)
|
||||
clang_disposeIndex(index)
|
||||
}
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
package org.jetbrains.kotlin.native.interop.indexer
|
||||
|
||||
import clang.*
|
||||
import kotlinx.cinterop.memScoped
|
||||
import java.io.File
|
||||
|
||||
/**
|
||||
* Finds all "macro constants" and registers them as [NativeIndex.constants] in given index.
|
||||
*/
|
||||
internal fun findMacroConstants(library: NativeLibrary, nativeIndex: NativeIndexImpl) {
|
||||
val names = collectMacroConstantsNames(library)
|
||||
// TODO: apply user-defined filters.
|
||||
|
||||
val constants = expandMacroConstants(library, names, typeConverter = nativeIndex::convertType)
|
||||
|
||||
nativeIndex.macroConstants.addAll(constants)
|
||||
}
|
||||
|
||||
private typealias TypeConverter = (CXType) -> Type
|
||||
|
||||
/**
|
||||
* For each name expands the macro with this name declared in the library,
|
||||
* checking if it gets expanded to a constant expression.
|
||||
*
|
||||
* @return the list of constants.
|
||||
*/
|
||||
private fun expandMacroConstants(
|
||||
originalLibrary: NativeLibrary,
|
||||
names: List<String>,
|
||||
typeConverter: TypeConverter
|
||||
): List<ConstantDef> {
|
||||
|
||||
// Each macro is expanded by parsing it separately with the library;
|
||||
// so precompile library headers to significantly speed up the parsing:
|
||||
val library = originalLibrary.precompileHeaders()
|
||||
|
||||
val index = clang_createIndex(excludeDeclarationsFromPCH = 1, displayDiagnostics = 0)!!
|
||||
try {
|
||||
val sourceFile = library.createTempSource()
|
||||
val translationUnit = parseTranslationUnit(index, sourceFile, library.compilerArgs, options = 0)
|
||||
.ensureNoCompileErrors()
|
||||
|
||||
try {
|
||||
return names.mapNotNull {
|
||||
expandMacroAsConstant(library, translationUnit, sourceFile, it, typeConverter)
|
||||
}
|
||||
} finally {
|
||||
clang_disposeTranslationUnit(translationUnit)
|
||||
}
|
||||
|
||||
} finally {
|
||||
clang_disposeIndex(index)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands the macro [name] defined in [library].
|
||||
* Returns the resulting constant or `null` if the result is not a constant (expression).
|
||||
*
|
||||
* As a side effect, modifies the [sourceFile] and reparses the [translationUnit].
|
||||
*/
|
||||
private fun expandMacroAsConstant(
|
||||
library: NativeLibrary,
|
||||
translationUnit: CXTranslationUnit,
|
||||
sourceFile: File,
|
||||
name: String,
|
||||
typeConverter: TypeConverter
|
||||
): ConstantDef? {
|
||||
|
||||
reparseWithCodeSnippet(library, translationUnit, sourceFile, name)
|
||||
|
||||
if (getCompileErrors(translationUnit).firstOrNull() == null) {
|
||||
return processCodeSnippet(translationUnit, name, typeConverter)
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
private fun reparseWithCodeSnippet(library: NativeLibrary,
|
||||
translationUnit: CXTranslationUnit, sourceFile: File,
|
||||
name: String) {
|
||||
|
||||
// TODO: consider using CXUnsavedFile instead of writing the modified file to OS file system.
|
||||
sourceFile.bufferedWriter().use { writer ->
|
||||
writer.appendPreamble(library)
|
||||
|
||||
// Note: clang_Cursor_Evaluate permits expression to have side-effects,
|
||||
// so the code pattern should force the constant evaluation that corresponds to language rules.
|
||||
val codeSnippetLines = when (library.language) {
|
||||
// Note: __auto_type is a GNU extension which is supported by clang.
|
||||
Language.C -> listOf(
|
||||
"const __auto_type KNI_INDEXER_VARIABLE = $name;",
|
||||
// Clang evaluate API doesn't provide a way to get a `long long` value yet;
|
||||
// so extract such values from the enum declaration:
|
||||
"enum { KNI_INDEXER_ENUM_CONST = (long long)KNI_INDEXER_VARIABLE };"
|
||||
)
|
||||
}
|
||||
|
||||
codeSnippetLines.forEach { writer.append(it) }
|
||||
}
|
||||
clang_reparseTranslationUnit(translationUnit, 0, null, 0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks that [translationUnit] is parsed exactly as expected for the code appended by [reparseWithCodeSnippet],
|
||||
* and returns the constant on success.
|
||||
*/
|
||||
private fun processCodeSnippet(
|
||||
translationUnit: CXTranslationUnit,
|
||||
name: String,
|
||||
typeConverter: TypeConverter
|
||||
): ConstantDef? {
|
||||
|
||||
var state = VisitorState.EXPECT_VARIABLE
|
||||
var evalResultKind: CXEvalResultKind? = null
|
||||
var type: Type? = null
|
||||
var longValue: Long? = null
|
||||
var doubleValue: Double? = null
|
||||
|
||||
val visitor: CursorVisitor = { cursor: CXCursor, parent: CXCursor ->
|
||||
val kind = cursor.kind.value
|
||||
when {
|
||||
state == VisitorState.EXPECT_VARIABLE && kind == CXCursorKind.CXCursor_VarDecl -> {
|
||||
state = VisitorState.EXPECT_VARIABLE_VALUE
|
||||
val evalResult = clang_Cursor_Evaluate(cursor)
|
||||
if (evalResult != null) {
|
||||
try {
|
||||
evalResultKind = clang_EvalResult_getKind(evalResult)
|
||||
if (evalResultKind == CXEvalResultKind.CXEval_Float) {
|
||||
doubleValue = clang_EvalResult_getAsDouble(evalResult)
|
||||
}
|
||||
} finally {
|
||||
clang_EvalResult_dispose(evalResult)
|
||||
}
|
||||
}
|
||||
CXChildVisitResult.CXChildVisit_Recurse
|
||||
}
|
||||
|
||||
state == VisitorState.EXPECT_VARIABLE_VALUE && clang_isExpression(kind) != 0 -> {
|
||||
state = VisitorState.EXPECT_ENUM
|
||||
type = memScoped {
|
||||
val cursorType = clang_getCursorType(cursor, memScope)
|
||||
typeConverter(cursorType)
|
||||
}
|
||||
CXChildVisitResult.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
state == VisitorState.EXPECT_ENUM && kind == CXCursorKind.CXCursor_EnumDecl -> {
|
||||
state = VisitorState.EXPECT_ENUM_CONST
|
||||
CXChildVisitResult.CXChildVisit_Recurse
|
||||
}
|
||||
|
||||
state == VisitorState.EXPECT_ENUM_CONST && kind == CXCursorKind.CXCursor_EnumConstantDecl -> {
|
||||
state = VisitorState.EXPECT_ENUM_CONST_VALUE
|
||||
if (evalResultKind == CXEvalResultKind.CXEval_Int) {
|
||||
longValue = clang_getEnumConstantDeclValue(cursor)
|
||||
}
|
||||
CXChildVisitResult.CXChildVisit_Recurse
|
||||
}
|
||||
|
||||
state == VisitorState.EXPECT_ENUM_CONST_VALUE && clang_isExpression(kind) != 0 -> {
|
||||
state = VisitorState.EXPECT_END
|
||||
CXChildVisitResult.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
else -> {
|
||||
state = VisitorState.INVALID
|
||||
CXChildVisitResult.CXChildVisit_Break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
visitChildren(translationUnit, visitor)
|
||||
|
||||
if (state != VisitorState.EXPECT_END) {
|
||||
return null
|
||||
}
|
||||
return when (evalResultKind!!) {
|
||||
CXEvalResultKind.CXEval_Int -> IntegerConstantDef(name, type!!, longValue!!)
|
||||
CXEvalResultKind.CXEval_Float -> FloatingConstantDef(name, type!!, doubleValue!!)
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
enum class VisitorState {
|
||||
EXPECT_VARIABLE, EXPECT_VARIABLE_VALUE,
|
||||
EXPECT_ENUM, EXPECT_ENUM_CONST, EXPECT_ENUM_CONST_VALUE,
|
||||
EXPECT_END, INVALID
|
||||
}
|
||||
|
||||
private fun collectMacroConstantsNames(library: NativeLibrary): List<String> {
|
||||
val result = mutableSetOf<String>()
|
||||
val index = clang_createIndex(excludeDeclarationsFromPCH = 0, displayDiagnostics = 0)!!
|
||||
try {
|
||||
// Include macros into AST:
|
||||
val options = CXTranslationUnit_Flags.CXTranslationUnit_DetailedPreprocessingRecord.value
|
||||
|
||||
val translationUnit = library.parse(index, options).ensureNoCompileErrors()
|
||||
try {
|
||||
visitChildren(translationUnit) { cursor, parent ->
|
||||
if (cursor.kind.value == CXCursorKind.CXCursor_MacroDefinition && canMacroBeConstant(cursor)) {
|
||||
val spelling = getCursorSpelling(cursor)
|
||||
result.add(spelling)
|
||||
}
|
||||
CXChildVisitResult.CXChildVisit_Continue
|
||||
}
|
||||
|
||||
} finally {
|
||||
clang_disposeTranslationUnit(translationUnit)
|
||||
}
|
||||
} finally {
|
||||
clang_disposeIndex(index)
|
||||
}
|
||||
|
||||
return result.toList()
|
||||
}
|
||||
|
||||
private fun canMacroBeConstant(cursor: CXCursor): Boolean {
|
||||
|
||||
if (clang_Cursor_isMacroFunctionLike(cursor) != 0) {
|
||||
return false
|
||||
}
|
||||
|
||||
// TODO: check number of tokens and filter out empty definitions;
|
||||
// Requires updating to 3.9.1 due to https://bugs.llvm.org//show_bug.cgi?id=9069
|
||||
|
||||
return true
|
||||
}
|
||||
+14
-2
@@ -1,11 +1,15 @@
|
||||
package org.jetbrains.kotlin.native.interop.indexer
|
||||
|
||||
import java.io.File
|
||||
enum class Language {
|
||||
C
|
||||
}
|
||||
|
||||
class NativeLibrary(val includes: List<String>, val compilerArgs: List<String>, val language: Language)
|
||||
|
||||
/**
|
||||
* Retrieves the definitions from given C header file using given compiler arguments (e.g. defines).
|
||||
*/
|
||||
fun buildNativeIndex(headerFile: File, args: List<String>): NativeIndex = buildNativeIndexImpl(headerFile, args)
|
||||
fun buildNativeIndex(library: NativeLibrary): NativeIndex = buildNativeIndexImpl(library)
|
||||
|
||||
/**
|
||||
* This class describes the IR of definitions from C header file(s).
|
||||
@@ -15,6 +19,7 @@ abstract class NativeIndex {
|
||||
abstract val enums: List<EnumDef>
|
||||
abstract val typedefs: List<TypedefDef>
|
||||
abstract val functions: List<FunctionDecl>
|
||||
abstract val macroConstants: List<ConstantDef>
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,6 +80,10 @@ class FunctionDecl(val name: String, val parameters: List<Parameter>, val return
|
||||
*/
|
||||
class TypedefDef(val aliased: Type, val name: String)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
/**
|
||||
* C type.
|
||||
@@ -100,6 +109,9 @@ object UIntPtrType : PrimitiveType
|
||||
object Int64Type : PrimitiveType
|
||||
object UInt64Type : PrimitiveType
|
||||
|
||||
object Float32Type : PrimitiveType
|
||||
object Float64Type : PrimitiveType
|
||||
|
||||
data class RecordType(val decl: StructDecl) : Type
|
||||
|
||||
data class EnumType(val def: EnumDef) : Type
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
package org.jetbrains.kotlin.native.interop.indexer
|
||||
|
||||
import clang.*
|
||||
import kotlinx.cinterop.*
|
||||
import java.io.File
|
||||
|
||||
internal fun CXString.convertAndDispose(): String {
|
||||
try {
|
||||
return clang_getCString(this)!!.asCString().toString()
|
||||
} finally {
|
||||
clang_disposeString(this)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getCursorSpelling(cursor: CXCursor) = memScoped {
|
||||
clang_getCursorSpelling(cursor, memScope).convertAndDispose()
|
||||
}
|
||||
|
||||
internal fun convertUnqualifiedPrimitiveType(type: CXType): Type = when (type.kind.value) {
|
||||
// TODO: is e.g. CXType_Int guaranteed to be int32_t?
|
||||
|
||||
CXTypeKind.CXType_Char_U, CXTypeKind.CXType_UChar -> UInt8Type
|
||||
CXTypeKind.CXType_Char_S, CXTypeKind.CXType_SChar -> Int8Type
|
||||
|
||||
CXTypeKind.CXType_UShort -> UInt16Type
|
||||
CXTypeKind.CXType_Short -> Int16Type
|
||||
|
||||
CXTypeKind.CXType_UInt -> UInt32Type
|
||||
CXTypeKind.CXType_Int -> Int32Type
|
||||
|
||||
CXTypeKind.CXType_ULong -> UIntPtrType
|
||||
CXTypeKind.CXType_Long -> IntPtrType
|
||||
|
||||
CXTypeKind.CXType_ULongLong -> UInt64Type
|
||||
CXTypeKind.CXType_LongLong -> Int64Type
|
||||
|
||||
CXTypeKind.CXType_Float -> Float32Type
|
||||
CXTypeKind.CXType_Double -> Float64Type
|
||||
|
||||
else -> UnsupportedType
|
||||
}
|
||||
|
||||
internal fun parseTranslationUnit(
|
||||
index: CXIndex,
|
||||
sourceFile: File,
|
||||
compilerArgs: List<String>,
|
||||
options: Int
|
||||
): CXTranslationUnit {
|
||||
memScoped {
|
||||
val result = clang_parseTranslationUnit(
|
||||
index,
|
||||
sourceFile.absolutePath,
|
||||
compilerArgs.toNativeStringArray(memScope)[0].ptr, compilerArgs.size,
|
||||
null, 0,
|
||||
options
|
||||
)!!
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
internal fun NativeLibrary.parse(index: CXIndex, options: Int = 0): CXTranslationUnit =
|
||||
parseTranslationUnit(index, this.createTempSource(), this.compilerArgs, options)
|
||||
|
||||
internal fun getCompileErrors(translationUnit: CXTranslationUnit): Sequence<String> {
|
||||
val numDiagnostics = clang_getNumDiagnostics(translationUnit)
|
||||
return (0 .. numDiagnostics - 1).asSequence()
|
||||
.map { index -> clang_getDiagnostic(translationUnit, index)!! }
|
||||
.filter {
|
||||
val severity = clang_getDiagnosticSeverity(it)
|
||||
severity == CXDiagnosticSeverity.CXDiagnostic_Error ||
|
||||
severity == CXDiagnosticSeverity.CXDiagnostic_Fatal
|
||||
}.map {
|
||||
memScoped {
|
||||
clang_formatDiagnostic(it, clang_defaultDiagnosticDisplayOptions(), memScope).convertAndDispose()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal fun CXTranslationUnit.ensureNoCompileErrors(): CXTranslationUnit {
|
||||
val firstError = getCompileErrors(this).firstOrNull() ?: return this
|
||||
throw Error(firstError)
|
||||
}
|
||||
|
||||
internal typealias CursorVisitor = (cursor: CXCursor, parent: CXCursor) -> CXChildVisitResult
|
||||
|
||||
internal fun visitChildren(parent: CXCursor, visitor: CursorVisitor) {
|
||||
val visitorPtr = StableObjPtr.create(visitor)
|
||||
val clientData = visitorPtr.value
|
||||
clang_visitChildren(parent, staticCFunction { cursor, parent, clientData ->
|
||||
val visitor = StableObjPtr.fromValue(clientData!!).get() as CursorVisitor
|
||||
visitor(cursor, parent)
|
||||
}, clientData)
|
||||
}
|
||||
|
||||
internal fun visitChildren(translationUnit: CXTranslationUnit, visitor: CursorVisitor) {
|
||||
memScoped {
|
||||
visitChildren(clang_getTranslationUnitCursor(translationUnit, memScope), visitor)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun List<String>.toNativeStringArray(placement: NativePlacement): CArray<CPointerVar<CInt8Var>> {
|
||||
return placement.allocArray(this.size) { index ->
|
||||
this.value = this@toNativeStringArray[index].toCString(placement)!!.asCharPtr()
|
||||
}
|
||||
}
|
||||
|
||||
internal val NativeLibrary.preambleLines: List<String>
|
||||
get() = this.includes.map { "#include <$it>" }
|
||||
|
||||
internal fun Appendable.appendPreamble(library: NativeLibrary) = this.apply {
|
||||
library.preambleLines.forEach {
|
||||
this.appendln(it)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates temporary source file which includes the library.
|
||||
*/
|
||||
internal fun NativeLibrary.createTempSource(): File {
|
||||
val suffix = when (language) {
|
||||
Language.C -> ".c"
|
||||
}
|
||||
val result = createTempFile(suffix = suffix)
|
||||
result.deleteOnExit()
|
||||
|
||||
result.bufferedWriter().use { writer ->
|
||||
writer.appendPreamble(this)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Precompiles the headers of this library.
|
||||
*
|
||||
* @return the library which includes the precompiled header instead of original ones.
|
||||
*/
|
||||
internal fun NativeLibrary.precompileHeaders(): NativeLibrary {
|
||||
val precompiledHeader = createTempFile(suffix = ".pch").apply { this.deleteOnExit() }
|
||||
|
||||
val index = clang_createIndex(excludeDeclarationsFromPCH = 0, displayDiagnostics = 0)!!
|
||||
try {
|
||||
val options = CXTranslationUnit_Flags.CXTranslationUnit_ForSerialization.value
|
||||
val translationUnit = this.parse(index, options).ensureNoCompileErrors()
|
||||
try {
|
||||
clang_saveTranslationUnit(translationUnit, precompiledHeader.absolutePath, 0)
|
||||
} finally {
|
||||
clang_disposeTranslationUnit(translationUnit)
|
||||
}
|
||||
} finally {
|
||||
clang_disposeIndex(index)
|
||||
}
|
||||
|
||||
return NativeLibrary(
|
||||
includes = emptyList(),
|
||||
compilerArgs = this.compilerArgs + listOf("-include-pch", precompiledHeader.absolutePath),
|
||||
language = this.language
|
||||
)
|
||||
}
|
||||
@@ -177,7 +177,7 @@ class CString private constructor(override val rawPtr: NativePtr) : CPointed {
|
||||
return decodeFromUtf8(bytes) // TODO: encoding
|
||||
}
|
||||
|
||||
fun asCharPtr() = reinterpret<CInt8Var>()
|
||||
fun asCharPtr() = reinterpret<CInt8Var>().ptr
|
||||
}
|
||||
|
||||
fun CString.Companion.fromString(str: String?, placement: NativePlacement): CString? {
|
||||
|
||||
+78
-2
@@ -96,6 +96,8 @@ class StubGenerator(
|
||||
})
|
||||
}
|
||||
|
||||
private val macroConstantsByName = nativeIndex.macroConstants.associateBy { it.name }
|
||||
|
||||
/**
|
||||
* The output currently used by the generator.
|
||||
* Should append line separator after any usage.
|
||||
@@ -155,6 +157,8 @@ class StubGenerator(
|
||||
is UIntPtrType -> "uintptr_t"
|
||||
is Int64Type -> "int64_t"
|
||||
is UInt64Type -> "uint64_t"
|
||||
is Float32Type -> "float"
|
||||
is Float64Type -> "double"
|
||||
|
||||
is PointerType -> {
|
||||
val pointeeType = this.pointeeType
|
||||
@@ -190,6 +194,8 @@ class StubGenerator(
|
||||
is Int32Type, is UInt32Type -> "Int"
|
||||
is IntPtrType, is UIntPtrType, // TODO: 64-bit specific
|
||||
is Int64Type, is UInt64Type -> "Long"
|
||||
is Float32Type -> "Float"
|
||||
is Float64Type -> "Double"
|
||||
|
||||
else -> throw NotImplementedError()
|
||||
}
|
||||
@@ -300,6 +306,8 @@ class StubGenerator(
|
||||
is Int32Type, is UInt32Type -> "CInt32Var"
|
||||
is IntPtrType, is UIntPtrType, // TODO: 64-bit specific
|
||||
is Int64Type, is UInt64Type -> "CInt64Var"
|
||||
is Float32Type -> "CFloat32Var"
|
||||
is Float64Type -> "CFloat64Var"
|
||||
else -> TODO(type.toString())
|
||||
}
|
||||
|
||||
@@ -551,8 +559,13 @@ class StubGenerator(
|
||||
}
|
||||
|
||||
out("// ${e.spelling}")
|
||||
e.values.forEach {
|
||||
out("val ${it.name}: ${e.baseType.kotlinType} = ${it.value}")
|
||||
for (value in e.values) {
|
||||
if (value.name in macroConstantsByName) {
|
||||
// Macro "overrides" the original enum constant.
|
||||
continue
|
||||
}
|
||||
|
||||
out("val ${value.name}: ${e.baseType.kotlinType} = ${value.value}")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,6 +770,64 @@ class StubGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
private fun integerLiteral(type: Type, value: Long): String? {
|
||||
if (value == Long.MIN_VALUE) {
|
||||
return "${value + 1} - 1" // Workaround for "The value is out of range" compile error.
|
||||
}
|
||||
|
||||
val unwrappedType = type.unwrapTypedefs()
|
||||
if (unwrappedType !is PrimitiveType) {
|
||||
return null
|
||||
}
|
||||
|
||||
val narrowedValue: Number = when (unwrappedType.kotlinType) {
|
||||
"Byte" -> value.toByte()
|
||||
"Short" -> value.toShort()
|
||||
"Int" -> value.toInt()
|
||||
"Long" -> value
|
||||
else -> return null
|
||||
}
|
||||
|
||||
return narrowedValue.toString()
|
||||
}
|
||||
|
||||
private fun floatingLiteral(type: Type, value: Double): String? {
|
||||
return when (type.unwrapTypedefs()) {
|
||||
Float32Type -> {
|
||||
val floatValue = value.toFloat()
|
||||
val bits = java.lang.Float.floatToRawIntBits(floatValue)
|
||||
"bitsToFloat($bits) /* == $floatValue */"
|
||||
}
|
||||
Float64Type -> {
|
||||
val bits = java.lang.Double.doubleToRawLongBits(value)
|
||||
"bitsToDouble($bits) /* == $value */"
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateConstant(constant: ConstantDef) {
|
||||
val literal = when (constant) {
|
||||
is IntegerConstantDef -> integerLiteral(constant.type, constant.value) ?: return
|
||||
is FloatingConstantDef -> floatingLiteral(constant.type, constant.value) ?: return
|
||||
else -> {
|
||||
// Not supported yet, ignore:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val kotlinType = mirror(constant.type).argType
|
||||
|
||||
// TODO: improve value rendering.
|
||||
|
||||
// TODO: consider using `const` modifier.
|
||||
// It is not currently possible for floating literals.
|
||||
// Also it provokes constant propagation which can reduce binary compatibility
|
||||
// when replacing interop stubs without recompiling the application.
|
||||
|
||||
out("val ${constant.name}: $kotlinType = $literal")
|
||||
}
|
||||
|
||||
private val FunctionDecl.stubSymbolName: String
|
||||
get() {
|
||||
require(platform == KotlinPlatform.NATIVE)
|
||||
@@ -803,6 +874,11 @@ class StubGenerator(
|
||||
}
|
||||
}
|
||||
|
||||
nativeIndex.macroConstants.forEach {
|
||||
generateConstant(it)
|
||||
}
|
||||
out("")
|
||||
|
||||
nativeIndex.structs.forEach { s ->
|
||||
try {
|
||||
transaction {
|
||||
|
||||
+5
-16
@@ -1,6 +1,8 @@
|
||||
package org.jetbrains.kotlin.native.interop.gen.jvm
|
||||
|
||||
import org.jetbrains.kotlin.native.interop.indexer.Language
|
||||
import org.jetbrains.kotlin.native.interop.indexer.NativeIndex
|
||||
import org.jetbrains.kotlin.native.interop.indexer.NativeLibrary
|
||||
import org.jetbrains.kotlin.native.interop.indexer.buildNativeIndex
|
||||
import java.io.File
|
||||
import java.lang.IllegalArgumentException
|
||||
@@ -118,7 +120,8 @@ private fun processLib(ktGenRoot: String,
|
||||
|
||||
val headerFiles = config.getSpaceSeparated("headers") + additionalHeaders
|
||||
val compilerOpts = config.getSpaceSeparated("compilerOpts") + additionalCompilerOpts
|
||||
val compiler = config.getProperty("compiler") ?: "clang"
|
||||
val compiler = "clang"
|
||||
val language = Language.C
|
||||
val linkerOpts = config.getSpaceSeparated("linkerOpts").toTypedArray() + additionalLinkerOpts
|
||||
val linker = args["-linker"]?.singleOrNull() ?: config.getProperty("linker") ?: "clang"
|
||||
val excludedFunctions = config.getSpaceSeparated("excludedFunctions").toSet()
|
||||
@@ -135,7 +138,7 @@ private fun processLib(ktGenRoot: String,
|
||||
|
||||
val libName = fqParts.joinToString("") + "stubs"
|
||||
|
||||
val nativeIndex = buildNativeIndex(headerFiles, compilerOpts)
|
||||
val nativeIndex = buildNativeIndex(NativeLibrary(headerFiles, compilerOpts, language))
|
||||
|
||||
val gen = StubGenerator(nativeIndex, outKtPkg, libName, excludedFunctions, generateShims, platform)
|
||||
|
||||
@@ -200,17 +203,3 @@ private fun processLib(ktGenRoot: String,
|
||||
|
||||
outCFile.delete()
|
||||
}
|
||||
|
||||
private fun buildNativeIndex(headerFiles: List<String>, compilerOpts: List<String>): NativeIndex {
|
||||
val tempHeaderFile = createTempFile(suffix = ".h")
|
||||
tempHeaderFile.deleteOnExit()
|
||||
tempHeaderFile.writer().buffered().use { reader ->
|
||||
headerFiles.forEach {
|
||||
reader.appendln("#include <$it>")
|
||||
}
|
||||
}
|
||||
|
||||
val res = buildNativeIndex(tempHeaderFile, compilerOpts)
|
||||
println(res)
|
||||
return res
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user