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)
|
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
|
||||||
|
|||||||
+101
-11
@@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,3 +221,75 @@ internal fun NativeLibrary.includesDeclaration(cursor: CValue<CXCursor>): Boolea
|
|||||||
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 }
|
||||||
|
}
|
||||||
+31
-8
@@ -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()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
+2
-6
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user