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 03ba6c8a35f..83b8400cdd9 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 @@ -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() } -private class NativeIndexImpl : NativeIndex() { +internal class NativeIndexImpl : NativeIndex() { private data class DeclarationID(val usr: String) - val structById = mutableMapOf() + private val structById = mutableMapOf() override val structs: List get() = structById.values.toList() - val enumById = mutableMapOf() + private val enumById = mutableMapOf() override val enums: List get() = enumById.values.toList() - val typedefById = mutableMapOf() + private val typedefById = mutableMapOf() override val typedefs: List get() = typedefById.values.toList() @@ -44,12 +43,14 @@ private class NativeIndexImpl : NativeIndex() { override val functions: List get() = functionByName.values.toList() - fun getDeclarationId(cursor: CXCursor): DeclarationID = memScoped { + override val macroConstants = mutableListOf() + + 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): 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() - - 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() + + 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) } } \ No newline at end of file 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 new file mode 100644 index 00000000000..90f63759de4 --- /dev/null +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/MacroConstants.kt @@ -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, + typeConverter: TypeConverter +): List { + + // 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 { + val result = mutableSetOf() + 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 +} \ No newline at end of file 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 73681c415ef..e896b48fb32 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 @@ -1,11 +1,15 @@ package org.jetbrains.kotlin.native.interop.indexer -import java.io.File +enum class Language { + C +} + +class NativeLibrary(val includes: List, val compilerArgs: List, val language: Language) /** * Retrieves the definitions from given C header file using given compiler arguments (e.g. defines). */ -fun buildNativeIndex(headerFile: File, args: List): 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 abstract val typedefs: List abstract val functions: List + abstract val macroConstants: List } /** @@ -75,6 +80,10 @@ class FunctionDecl(val name: String, val parameters: List, 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 diff --git a/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt new file mode 100644 index 00000000000..7efbdfc2b84 --- /dev/null +++ b/Interop/Indexer/src/main/kotlin/org/jetbrains/kotlin/native/interop/indexer/Utils.kt @@ -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, + 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 { + 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.toNativeStringArray(placement: NativePlacement): CArray> { + return placement.allocArray(this.size) { index -> + this.value = this@toNativeStringArray[index].toCString(placement)!!.asCharPtr() + } +} + +internal val NativeLibrary.preambleLines: List + 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 + ) +} \ No newline at end of file diff --git a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt index cfd1b49d7a7..9dc03cf9639 100644 --- a/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt +++ b/Interop/Runtime/src/main/kotlin/kotlinx/cinterop/Utils.kt @@ -177,7 +177,7 @@ class CString private constructor(override val rawPtr: NativePtr) : CPointed { return decodeFromUtf8(bytes) // TODO: encoding } - fun asCharPtr() = reinterpret() + fun asCharPtr() = reinterpret().ptr } fun CString.Companion.fromString(str: String?, placement: NativePlacement): CString? { 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 4e6867d71cb..f9a16c20b07 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 @@ -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 { 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 8d327b953c7..2efd554357f 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 @@ -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, compilerOpts: List): 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 -}