Automatically exclude interop functions having non-compilable C stubs

This commit is contained in:
Svyatoslav Scherbina
2017-04-11 16:20:02 +03:00
committed by SvyatoslavScherbina
parent 05dae83185
commit c8f070f90d
4 changed files with 136 additions and 27 deletions
@@ -85,7 +85,7 @@ private fun expandMacroAsConstant(
reparseWithCodeSnippet(library, translationUnit, sourceFile, name) reparseWithCodeSnippet(library, translationUnit, sourceFile, name)
if (getCompileErrors(translationUnit).firstOrNull() == null) { if (!translationUnit.hasCompileErrors()) {
return processCodeSnippet(translationUnit, name, typeConverter) return processCodeSnippet(translationUnit, name, typeConverter)
} else { } else {
return null return null
@@ -93,21 +93,39 @@ internal fun parseTranslationUnit(
internal fun NativeLibrary.parse(index: CXIndex, options: Int = 0): CXTranslationUnit = internal fun NativeLibrary.parse(index: CXIndex, options: Int = 0): CXTranslationUnit =
parseTranslationUnit(index, this.createTempSource(), this.compilerArgs, options) parseTranslationUnit(index, this.createTempSource(), this.compilerArgs, options)
internal fun getCompileErrors(translationUnit: CXTranslationUnit): Sequence<String> { internal data class Diagnostic(val severity: CXDiagnosticSeverity, val format: String,
val numDiagnostics = clang_getNumDiagnostics(translationUnit) val location: CValue<CXSourceLocation>)
return (0 .. numDiagnostics - 1).asSequence()
.map { index -> clang_getDiagnostic(translationUnit, index)!! } internal fun CXTranslationUnit.getDiagnostics(): Sequence<Diagnostic> {
.filter { val numDiagnostics = clang_getNumDiagnostics(this)
val severity = clang_getDiagnosticSeverity(it) return (0 until numDiagnostics).asSequence()
severity == CXDiagnosticSeverity.CXDiagnostic_Error || .map { index ->
severity == CXDiagnosticSeverity.CXDiagnostic_Fatal val diagnostic = clang_getDiagnostic(this, index)
}.map { try {
clang_formatDiagnostic(it, clang_defaultDiagnosticDisplayOptions()).convertAndDispose() val severity = clang_getDiagnosticSeverity(diagnostic)
val format = clang_formatDiagnostic(diagnostic, clang_defaultDiagnosticDisplayOptions())
.convertAndDispose()
val location = clang_getDiagnosticLocation(diagnostic)
Diagnostic(severity, format, location)
} finally {
clang_disposeDiagnostic(diagnostic)
}
} }
} }
internal fun CXTranslationUnit.getCompileErrors(): Sequence<String> =
getDiagnostics().filter { it.isError() }.map { it.format }
private fun Diagnostic.isError() = (severity == CXDiagnosticSeverity.CXDiagnostic_Error) ||
(severity == CXDiagnosticSeverity.CXDiagnostic_Fatal)
internal fun CXTranslationUnit.hasCompileErrors() = (this.getCompileErrors().firstOrNull() != null)
internal fun CXTranslationUnit.ensureNoCompileErrors(): CXTranslationUnit { internal fun CXTranslationUnit.ensureNoCompileErrors(): CXTranslationUnit {
val firstError = getCompileErrors(this).firstOrNull() ?: return this val firstError = this.getCompileErrors().firstOrNull() ?: return this
throw Error(firstError) throw Error(firstError)
} }
@@ -202,4 +220,76 @@ internal fun NativeLibrary.includesDeclaration(cursor: CValue<CXCursor>): Boolea
} else { } else {
true true
} }
}
private fun CXTranslationUnit.getErrorLineNumbers(): Sequence<Int> =
getDiagnostics().filter {
it.isError()
}.map {
memScoped {
val lineNumberVar = alloc<IntVar>()
clang_getFileLocation(it.location, null, lineNumberVar.ptr, null, null)
lineNumberVar.value
}
}
/**
* 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
.copy(compilerArgs = originalLibrary.compilerArgs + "-ferror-limit=0")
.precompileHeaders()
val indicesOfNonCompilable = mutableSetOf<Int>()
val fragmentsToCheck = this.withIndex().toMutableList()
val index = clang_createIndex(excludeDeclarationsFromPCH = 1, displayDiagnostics = 0)!!
try {
val sourceFile = library.createTempSource()
val translationUnit = parseTranslationUnit(index, sourceFile, library.compilerArgs, options = 0)
.ensureNoCompileErrors()
try {
while (fragmentsToCheck.isNotEmpty()) {
// Combine all fragments to be checked in a single file:
sourceFile.bufferedWriter().use { writer ->
writer.appendPreamble(library)
fragmentsToCheck.forEach {
it.value.forEach {
assert(!it.contains('\n'))
writer.appendln(it)
}
}
}
clang_reparseTranslationUnit(translationUnit, 0, null, 0)
val errorLineNumbers = translationUnit.getErrorLineNumbers().toSet()
// Retain only those fragments that contain compilation error locations:
var lastLineNumber = library.preambleLines.size
fragmentsToCheck.retainAll {
val firstLineNumber = lastLineNumber + 1
lastLineNumber += it.value.size
(firstLineNumber .. lastLineNumber).any { it in errorLineNumbers }
}
if (fragmentsToCheck.isNotEmpty()) {
// The first fragment is now known to be non-compilable.
val firstFragment = fragmentsToCheck.removeAt(0)
indicesOfNonCompilable.add(firstFragment.index)
}
// The remaining fragments was potentially influenced by the first one,
// and thus require to be checked again.
}
} finally {
clang_disposeTranslationUnit(translationUnit)
}
} finally {
clang_disposeIndex(index)
}
return this.indices.map { it !in indicesOfNonCompilable }
} }
@@ -768,6 +768,27 @@ class StubGenerator(
return StubsFragment(kotlinStubLines, nativeStubLines) return StubsFragment(kotlinStubLines, nativeStubLines)
} }
private fun generateStubsForFunctions(functions: List<FunctionDecl>): List<StubsFragment> {
val stubs = functions.mapNotNull {
try {
generateStubsForFunction(it)
} catch (e: Throwable) {
log("Warning: cannot generate stubs for function ${it.name}")
null
}
}
return stubs.map { it.nativeStubLines }
.mapFragmentIsCompilable(libraryForCStubs)
.mapIndexedNotNull { index, stubIsCompilable ->
if (stubIsCompilable) {
stubs[index]
} else {
null
}
}
}
private fun FunctionDecl.generateAsFfiVarargs(): Boolean = (platform == KotlinPlatform.NATIVE && this.isVararg && private fun FunctionDecl.generateAsFfiVarargs(): Boolean = (platform == KotlinPlatform.NATIVE && this.isVararg &&
// Neither takes nor returns structs by value: // Neither takes nor returns structs by value:
!this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType }) !this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType })
@@ -1224,13 +1245,7 @@ class StubGenerator(
private fun generateStubs(): List<StubsFragment> { private fun generateStubs(): List<StubsFragment> {
val stubs = mutableListOf<StubsFragment>() val stubs = mutableListOf<StubsFragment>()
functionsToBind.forEach { stubs.addAll(generateStubsForFunctions(functionsToBind))
try {
stubs.add(generateStubsForFunction(it))
} catch (e: Throwable) {
log("Warning: cannot generate stubs for function ${it.name}")
}
}
nativeIndex.macroConstants.forEach { nativeIndex.macroConstants.forEach {
try { try {
@@ -1345,13 +1360,21 @@ class StubGenerator(
else -> throw NotImplementedError(kotlinJniBridgeType) else -> throw NotImplementedError(kotlinJniBridgeType)
} }
private val libraryForCStubs = configuration.library.copy( val libraryForCStubs = configuration.library.copy(
includes = mutableListOf<String>().apply { includes = mutableListOf<String>().apply {
add("stdint.h") add("stdint.h")
if (platform == KotlinPlatform.JVM) { if (platform == KotlinPlatform.JVM) {
add("jni.h") add("jni.h")
} }
addAll(configuration.library.includes) addAll(configuration.library.includes)
},
compilerArgs = configuration.library.compilerArgs + when (platform) {
KotlinPlatform.JVM -> listOf("", "linux", "darwin").map {
val javaHome = System.getProperty("java.home")
"-I$javaHome/../include/$it"
}
KotlinPlatform.NATIVE -> emptyList()
} }
) )
@@ -306,7 +306,7 @@ private fun processLib(konanHome: String,
val headerFiles = config.getSpaceSeparated("headers") + additionalHeaders val headerFiles = config.getSpaceSeparated("headers") + additionalHeaders
val compilerOpts = val compilerOpts =
config.getSpaceSeparated("compilerOpts") + config.getSpaceSeparated("compilerOpts") +
defaultOpts + additionalCompilerOpts defaultOpts + additionalCompilerOpts
val compiler = "clang" val compiler = "clang"
val language = Language.C val language = Language.C
val excludeSystemLibs = config.getProperty("excludeSystemLibs")?.toBoolean() ?: false val excludeSystemLibs = config.getProperty("excludeSystemLibs")?.toBoolean() ?: false
@@ -360,11 +360,7 @@ private fun processLib(konanHome: String,
val outOFile = createTempFile(suffix = ".o") val outOFile = createTempFile(suffix = ".o")
val javaHome = System.getProperty("java.home") val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *gen.libraryForCStubs.compilerArgs.toTypedArray(),
val compilerArgsForJniIncludes = listOf("", "linux", "darwin").map { "-I$javaHome/../include/$it" }.toTypedArray()
val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(),
*compilerArgsForJniIncludes,
"-c", outCFile.path, "-o", outOFile.path) "-c", outCFile.path, "-o", outOFile.path)
runCmd(compilerCmd, workDir, verbose) runCmd(compilerCmd, workDir, verbose)
@@ -380,7 +376,7 @@ private fun processLib(konanHome: String,
} else if (flavor == KotlinPlatform.NATIVE) { } else if (flavor == KotlinPlatform.NATIVE) {
val outBcName = libName + ".bc" val outBcName = libName + ".bc"
val outLib = nativeLibsDir + "/" + outBcName val outLib = nativeLibsDir + "/" + outBcName
val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(), val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *gen.libraryForCStubs.compilerArgs.toTypedArray(),
"-emit-llvm", "-c", outCFile.path, "-o", outLib) "-emit-llvm", "-c", outCFile.path, "-o", outLib)
runCmd(compilerCmd, workDir, verbose) runCmd(compilerCmd, workDir, verbose)