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)
if (getCompileErrors(translationUnit).firstOrNull() == null) {
if (!translationUnit.hasCompileErrors()) {
return processCodeSnippet(translationUnit, name, typeConverter)
} else {
return null
@@ -93,21 +93,39 @@ internal fun parseTranslationUnit(
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 {
clang_formatDiagnostic(it, clang_defaultDiagnosticDisplayOptions()).convertAndDispose()
internal data class Diagnostic(val severity: CXDiagnosticSeverity, val format: String,
val location: CValue<CXSourceLocation>)
internal fun CXTranslationUnit.getDiagnostics(): Sequence<Diagnostic> {
val numDiagnostics = clang_getNumDiagnostics(this)
return (0 until numDiagnostics).asSequence()
.map { index ->
val diagnostic = clang_getDiagnostic(this, index)
try {
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 {
val firstError = getCompileErrors(this).firstOrNull() ?: return this
val firstError = this.getCompileErrors().firstOrNull() ?: return this
throw Error(firstError)
}
@@ -202,4 +220,76 @@ internal fun NativeLibrary.includesDeclaration(cursor: CValue<CXCursor>): Boolea
} else {
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)
}
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 &&
// Neither takes nor returns structs by value:
!this.returnsRecord() && this.parameters.all { it.type.unwrapTypedefs() !is RecordType })
@@ -1224,13 +1245,7 @@ class StubGenerator(
private fun generateStubs(): List<StubsFragment> {
val stubs = mutableListOf<StubsFragment>()
functionsToBind.forEach {
try {
stubs.add(generateStubsForFunction(it))
} catch (e: Throwable) {
log("Warning: cannot generate stubs for function ${it.name}")
}
}
stubs.addAll(generateStubsForFunctions(functionsToBind))
nativeIndex.macroConstants.forEach {
try {
@@ -1345,13 +1360,21 @@ class StubGenerator(
else -> throw NotImplementedError(kotlinJniBridgeType)
}
private val libraryForCStubs = configuration.library.copy(
val libraryForCStubs = configuration.library.copy(
includes = mutableListOf<String>().apply {
add("stdint.h")
if (platform == KotlinPlatform.JVM) {
add("jni.h")
}
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 compilerOpts =
config.getSpaceSeparated("compilerOpts") +
defaultOpts + additionalCompilerOpts
defaultOpts + additionalCompilerOpts
val compiler = "clang"
val language = Language.C
val excludeSystemLibs = config.getProperty("excludeSystemLibs")?.toBoolean() ?: false
@@ -360,11 +360,7 @@ private fun processLib(konanHome: String,
val outOFile = createTempFile(suffix = ".o")
val javaHome = System.getProperty("java.home")
val compilerArgsForJniIncludes = listOf("", "linux", "darwin").map { "-I$javaHome/../include/$it" }.toTypedArray()
val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *compilerOpts.toTypedArray(),
*compilerArgsForJniIncludes,
val compilerCmd = arrayOf("$llvmInstallPath/bin/$compiler", *gen.libraryForCStubs.compilerArgs.toTypedArray(),
"-c", outCFile.path, "-o", outOFile.path)
runCmd(compilerCmd, workDir, verbose)
@@ -380,7 +376,7 @@ private fun processLib(konanHome: String,
} else if (flavor == KotlinPlatform.NATIVE) {
val outBcName = libName + ".bc"
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)
runCmd(compilerCmd, workDir, verbose)