Avoid reparsing C headers in cinterop whenever possible (#3082)

Thus optimize it.
This commit is contained in:
SvyatoslavScherbina
2019-06-24 14:38:04 +03:00
committed by GitHub
parent ba19ead041
commit ab29d5e073
9 changed files with 97 additions and 55 deletions
@@ -947,18 +947,23 @@ internal class NativeIndexImpl(val library: NativeLibrary, val verbose: Boolean
}
fun buildNativeIndexImpl(library: NativeLibrary, verbose: Boolean): NativeIndex {
fun buildNativeIndexImpl(library: NativeLibrary, verbose: Boolean): IndexerResult {
val result = NativeIndexImpl(library, verbose)
indexDeclarations(result)
return result
val compilation = indexDeclarations(result)
return IndexerResult(result, compilation)
}
private fun indexDeclarations(nativeIndex: NativeIndexImpl) {
private fun indexDeclarations(nativeIndex: NativeIndexImpl): CompilationWithPCH {
withIndex { index ->
val translationUnit = nativeIndex.library.parse(index, options = CXTranslationUnit_DetailedPreprocessingRecord)
val translationUnit = nativeIndex.library.parse(
index,
options = CXTranslationUnit_DetailedPreprocessingRecord or CXTranslationUnit_ForSerialization
)
try {
translationUnit.ensureNoCompileErrors()
val compilation = nativeIndex.library.withPrecompiledHeader(translationUnit)
val headers = getFilteredHeaders(nativeIndex, index, translationUnit)
nativeIndex.includedHeaders = headers.map {
@@ -991,7 +996,9 @@ private fun indexDeclarations(nativeIndex: NativeIndexImpl) {
CXChildVisitResult.CXChildVisit_Continue
}
findMacros(nativeIndex, translationUnit, headers)
findMacros(nativeIndex, compilation, translationUnit, headers)
return compilation
} finally {
clang_disposeTranslationUnit(translationUnit)
}
@@ -23,10 +23,15 @@ import java.io.File
/**
* Finds all "macro constants" and registers them as [NativeIndex.constants] in given index.
*/
internal fun findMacros(nativeIndex: NativeIndexImpl, translationUnit: CXTranslationUnit, headers: Set<CXFile?>) {
internal fun findMacros(
nativeIndex: NativeIndexImpl,
compilation: CompilationWithPCH,
translationUnit: CXTranslationUnit,
headers: Set<CXFile?>
) {
val names = collectMacroNames(nativeIndex, translationUnit, headers)
// TODO: apply user-defined filters.
val macros = expandMacros(nativeIndex.library, names, typeConverter = { nativeIndex.convertType(it) })
val macros = expandMacros(compilation, names, typeConverter = { nativeIndex.convertType(it) })
macros.filterIsInstanceTo(nativeIndex.macroConstants)
macros.filterIsInstanceTo(nativeIndex.wrappedMacros)
@@ -38,18 +43,16 @@ private typealias TypeConverter = (CValue<CXType>) -> Type
* For each name expands the macro with this name declared in the library,
* checking if it gets expanded to a constant expression.
*
* Note: in the worst case this method parses the code against the library a lot of times,
* so it requires library headers precompiled to significantly speed up the parsing and avoid visiting headers' AST.
*
* @return the list of constants.
*/
private fun expandMacros(
originalLibrary: NativeLibrary,
library: CompilationWithPCH,
names: List<String>,
typeConverter: TypeConverter
): List<MacroDef> {
// In the worst case code is parsed against the library a lot of times;
// so precompile library headers to significantly speed up the parsing and avoid visiting headers' AST:
val library = originalLibrary.precompileHeaders()
withIndex(excludeDeclarationsFromPCH = true) { index ->
val sourceFile = library.createTempSource()
val compilerArgs = library.compilerArgs.toMutableList()
@@ -94,7 +97,7 @@ private fun expandMacros(
* As a side effect, modifies the [sourceFile] and reparses the [translationUnit].
*/
private fun tryExpandMacros(
library: NativeLibrary,
library: CompilationWithPCH,
translationUnit: CXTranslationUnit,
sourceFile: File,
names: List<String>,
@@ -158,7 +161,7 @@ private const val CODE_SNIPPET_FUNCTION_NAME_PREFIX = "kni_indexer_function_"
* generate a bridge for this macro.
* - Otherwise the macro is skipped.
*/
private fun reparseWithCodeSnippets(library: NativeLibrary,
private fun reparseWithCodeSnippets(library: CompilationWithPCH,
translationUnit: CXTranslationUnit, sourceFile: File,
names: List<String>) {
@@ -53,9 +53,9 @@ internal open class ModularCompilation(compilation: Compilation): Compilation by
}
private fun getModulesASTFiles(index: CXIndex, compilation: ModularCompilation, modules: List<String>): List<String> {
val compilationWithImports = object : Compilation by compilation {
override val additionalPreambleLines = modules.map { "@import $it;" } + compilation.additionalPreambleLines
}
val compilationWithImports = compilation.copy(
additionalPreambleLines = modules.map { "@import $it;" } + compilation.additionalPreambleLines
)
val result = linkedSetOf<String>()
@@ -57,7 +57,22 @@ interface Compilation {
val language: Language
}
// TODO: consider replacing inheritance by composition and turning Compilation into final class.
data class CompilationWithPCH(
override val compilerArgs: List<String>,
override val language: Language
) : Compilation {
constructor(compilerArgs: List<String>, precompiledHeader: String, language: Language)
: this(compilerArgs + listOf("-include-pch", precompiledHeader), language)
override val includes: List<String>
get() = emptyList()
override val additionalPreambleLines: List<String>
get() = emptyList()
}
// TODO: Compilation hierarchy seems to require some refactoring.
data class NativeLibrary(override val includes: List<String>,
override val additionalPreambleLines: List<String>,
@@ -68,10 +83,12 @@ data class NativeLibrary(override val includes: List<String>,
val headerExclusionPolicy: HeaderExclusionPolicy,
val headerFilter: NativeLibraryHeaderFilter) : Compilation
data class IndexerResult(val index: NativeIndex, val compilation: CompilationWithPCH)
/**
* Retrieves the definitions from given C header file using given compiler arguments (e.g. defines).
*/
fun buildNativeIndex(library: NativeLibrary, verbose: Boolean): NativeIndex = buildNativeIndexImpl(library, verbose)
fun buildNativeIndex(library: NativeLibrary, verbose: Boolean): IndexerResult = buildNativeIndexImpl(library, verbose)
/**
* This class describes the IR of definitions from C header file(s).
@@ -241,30 +241,46 @@ internal fun Compilation.createTempSource(): File {
return result
}
fun Compilation.copy(
includes: List<String> = this.includes,
additionalPreambleLines: List<String> = this.additionalPreambleLines,
compilerArgs: List<String> = this.compilerArgs,
language: Language = this.language
): Compilation = CompilationImpl(
includes = includes,
additionalPreambleLines = additionalPreambleLines,
compilerArgs = compilerArgs,
language = language
)
data class CompilationImpl(
override val includes: List<String>,
override val additionalPreambleLines: List<String>,
override val compilerArgs: List<String>,
override val language: Language
) : Compilation
/**
* 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() }
withIndex { index ->
val options = CXTranslationUnit_ForSerialization
val translationUnit = this.parse(index, options)
try {
translationUnit.ensureNoCompileErrors()
clang_saveTranslationUnit(translationUnit, precompiledHeader.absolutePath, 0)
} finally {
clang_disposeTranslationUnit(translationUnit)
}
fun Compilation.precompileHeaders(): CompilationWithPCH = withIndex { index ->
val options = CXTranslationUnit_ForSerialization
val translationUnit = this.parse(index, options)
try {
translationUnit.ensureNoCompileErrors()
withPrecompiledHeader(translationUnit)
} finally {
clang_disposeTranslationUnit(translationUnit)
}
}
return this.copy(
includes = emptyList(),
additionalPreambleLines = emptyList(),
compilerArgs = this.compilerArgs + listOf("-include-pch", precompiledHeader.absolutePath)
)
internal fun Compilation.withPrecompiledHeader(translationUnit: CXTranslationUnit): CompilationWithPCH {
val precompiledHeader = createTempFile(suffix = ".pch").apply { this.deleteOnExit() }
clang_saveTranslationUnit(translationUnit, precompiledHeader.absolutePath, 0)
return CompilationWithPCH(this.compilerArgs, precompiledHeader.absolutePath, this.language)
}
internal fun NativeLibrary.includesDeclaration(cursor: CValue<CXCursor>): Boolean {
@@ -289,10 +305,9 @@ internal fun CXTranslationUnit.getErrorLineNumbers(): Sequence<Int> =
/**
* For each list of lines, checks if the code fragment composed from these lines is compilable against given library.
*/
fun List<List<String>>.mapFragmentIsCompilable(originalLibrary: NativeLibrary): List<Boolean> {
val library = originalLibrary
fun List<List<String>>.mapFragmentIsCompilable(originalLibrary: CompilationWithPCH): List<Boolean> {
val library: CompilationWithPCH = originalLibrary
.copy(compilerArgs = originalLibrary.compilerArgs + "-ferror-limit=0")
.precompileHeaders()
val indicesOfNonCompilable = mutableSetOf<Int>()
@@ -17,15 +17,15 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.gen.jvm.KotlinPlatform
import org.jetbrains.kotlin.native.interop.indexer.CompilationWithPCH
import org.jetbrains.kotlin.native.interop.indexer.Language
import org.jetbrains.kotlin.native.interop.indexer.NativeLibrary
import org.jetbrains.kotlin.native.interop.indexer.mapFragmentIsCompilable
class SimpleBridgeGeneratorImpl(
private val platform: KotlinPlatform,
private val pkgName: String,
private val jvmFileClassName: String,
private val libraryForCStubs: NativeLibrary,
private val libraryForCStubs: CompilationWithPCH,
override val topLevelNativeScope: NativeScope,
private val topLevelKotlinScope: KotlinScope
) : SimpleBridgeGenerator {
@@ -17,13 +17,13 @@
package org.jetbrains.kotlin.native.interop.gen.jvm
import org.jetbrains.kotlin.konan.target.KonanTarget
import org.jetbrains.kotlin.native.interop.indexer.NativeLibrary
import org.jetbrains.kotlin.native.interop.indexer.CompilationWithPCH
/**
* Describes the native library and the options for adjusting the Kotlin API to be generated for this library.
*/
class InteropConfiguration(
val library: NativeLibrary,
val library: CompilationWithPCH,
val pkgName: String,
val excludedFunctions: Set<String>,
val excludedMacros: Set<String>,
@@ -965,7 +965,7 @@ class StubGenerator(
Language.C -> emptyList()
Language.OBJECTIVE_C -> listOf("void objc_terminate();")
}
)
).precompileHeaders()
/**
* Produces to [out] the contents of C source file to be compiled into native lib used for Kotlin bindings impl.
@@ -220,8 +220,10 @@ private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> =
val library = buildNativeLibrary(tool, def, argParser, imports)
val (nativeIndex, compilation) = buildNativeIndex(library, verbose)
val configuration = InteropConfiguration(
library = library,
library = compilation,
pkgName = outKtPkg,
excludedFunctions = excludedFunctions,
excludedMacros = excludedMacros,
@@ -233,8 +235,6 @@ private fun processCLib(args: Array<String>, additionalArgs: Map<String, Any> =
target = tool.target
)
val nativeIndex = buildNativeIndex(library, verbose)
val gen = StubGenerator(nativeIndex, configuration, libName, verbose, flavor, imports)
outKtFile.parentFile.mkdirs()
@@ -330,12 +330,12 @@ internal fun buildNativeLibrary(
})
}
val compilation = object : Compilation {
override val includes = headerFiles
override val additionalPreambleLines = def.defHeaderLines
override val compilerArgs = compilerOpts + tool.platformCompilerOpts
override val language = language
}
val compilation = CompilationImpl(
includes = headerFiles,
additionalPreambleLines = def.defHeaderLines,
compilerArgs = compilerOpts + tool.platformCompilerOpts,
language = language
)
val headerFilter: NativeLibraryHeaderFilter
val includes: List<String>