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:
Svyatoslav Scherbina
2017-02-16 13:37:00 +07:00
committed by SvyatoslavScherbina
parent 01dd86d3b5
commit f8d54fc8f7
7 changed files with 557 additions and 89 deletions
@@ -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)
}
}
@@ -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
}
@@ -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? {
@@ -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 {
@@ -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
}