Automatically exclude interop functions having non-compilable C stubs
This commit is contained in:
committed by
SvyatoslavScherbina
parent
05dae83185
commit
c8f070f90d
+1
-1
@@ -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
|
||||
|
||||
+101
-11
@@ -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 }
|
||||
}
|
||||
+31
-8
@@ -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()
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
+3
-7
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user