[Kotlin/Native][Interop] Provide pure c wrappers over cpp for skia interop

This commit is contained in:
Vladimir Ivanov
2019-08-19 11:44:20 +03:00
committed by Alexander Gorshenev
parent 61825e9aec
commit 5f582ad28a
31 changed files with 1525 additions and 66 deletions
@@ -41,7 +41,7 @@ typealias KotlinExpression = String
fun String.asSimpleName(): String = if (this in kotlinKeywords || this.contains("$")) {
"`$this`"
} else {
this
this.replace(':', '_')
}
/**
@@ -16,10 +16,7 @@
package org.jetbrains.kotlin.native.interop.gen
import org.jetbrains.kotlin.native.interop.indexer.RecordType
import org.jetbrains.kotlin.native.interop.indexer.Type
import org.jetbrains.kotlin.native.interop.indexer.VoidType
import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
import org.jetbrains.kotlin.native.interop.indexer.*
/**
* The [MappingBridgeGenerator] implementation which uses [SimpleBridgeGenerator] as the backend and
@@ -27,7 +24,8 @@ import org.jetbrains.kotlin.native.interop.indexer.unwrapTypedefs
*/
class MappingBridgeGeneratorImpl(
val declarationMapper: DeclarationMapper,
val simpleBridgeGenerator: SimpleBridgeGenerator
val simpleBridgeGenerator: SimpleBridgeGenerator,
val language: Language
) : MappingBridgeGenerator {
override fun kotlinToNative(
@@ -40,7 +38,12 @@ class MappingBridgeGeneratorImpl(
): KotlinExpression {
val bridgeArguments = mutableListOf<BridgeTypedKotlinValue>()
kotlinValues.forEach { (type, value) ->
if (nativeBacked is FunctionStub && nativeBacked.isCxxInstanceMember()) {
bridgeArguments.add(BridgeTypedKotlinValue(BridgedType.NATIVE_PTR, "rawPtr"))
kotlinValues.drop(1)
} else {
kotlinValues
}.forEach { (type, value) ->
if (type.unwrapTypedefs() is RecordType) {
builder.pushMemScoped()
val bridgeArgument = "$value.getPointer(memScope).rawValue"
@@ -79,6 +82,25 @@ class MappingBridgeGeneratorImpl(
val unwrappedType = type.unwrapTypedefs()
if (unwrappedType is RecordType) {
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
} else if (language == Language.CPP) {
// C++ is more restrictive wrt type conversion
val cppRefTypePrefix = if (unwrappedType is PointerType && unwrappedType.isLVReference) "*" else ""
when { /// TODO Move this cludge to mirror()
type is Typedef ->
nativeValues.add("(${type.def.name})${bridgeNativeValues[index]}")
type is PointerType && type.spelling != null ->
nativeValues.add("(${type.spelling})$cppRefTypePrefix${bridgeNativeValues[index]}")
unwrappedType is EnumType ->
nativeValues.add("(${unwrappedType.def.spelling})${bridgeNativeValues[index]}")
unwrappedType is RecordType ->
nativeValues.add("*(${unwrappedType.decl.spelling}*)${bridgeNativeValues[index]}")
else ->
nativeValues.add(cppRefTypePrefix +
mirror(declarationMapper, type).info.cFromBridged(
bridgeNativeValues[index], scope, nativeBacked
)
)
}
} else {
nativeValues.add(
mirror(declarationMapper, type).info.cFromBridged(
@@ -90,18 +112,27 @@ class MappingBridgeGeneratorImpl(
val nativeResult = block(nativeValues)
when (unwrappedReturnType) {
is VoidType -> {
when {
unwrappedReturnType is VoidType -> {
out(nativeResult + ";")
""
}
is RecordType -> {
unwrappedReturnType is RecordType -> {
val kniStructResult = "kniStructResult"
out("${unwrappedReturnType.decl.spelling} $kniStructResult = $nativeResult;")
out("memcpy(${bridgeNativeValues.last()}, &$kniStructResult, sizeof($kniStructResult));")
if (language == Language.CPP) {
// use copy/move constructor to create object in place.
out("new(${bridgeNativeValues.last()}) ${unwrappedReturnType.decl.spelling}($nativeResult);")
} else {
out("${unwrappedReturnType.decl.spelling} $kniStructResult = $nativeResult;")
out("memcpy(${bridgeNativeValues.last()}, &$kniStructResult, sizeof($kniStructResult));")
// The following would be better, but won't work in case of const fields: C99 6.3.2.1p1
// out("*(${unwrappedReturnType.decl.spelling}*) ${bridgeNativeValues.last()} = $nativeResult;")
}
""
}
unwrappedReturnType is PointerType && unwrappedReturnType.isLVReference ->
"&$nativeResult"
else -> {
nativeResult
}
@@ -204,4 +235,4 @@ class MappingBridgeGeneratorImpl(
return result
}
}
}
@@ -120,10 +120,11 @@ class SimpleBridgeGeneratorImpl(
"JNIEXPORT $cReturnType JNICALL $functionName ($joinedCParameters)"
}
KotlinPlatform.NATIVE -> {
val externCPrefix = if (libraryForCStubs.language == Language.CPP) "extern \"C\" " else ""
val functionName = pkgName.replace(INVALID_CLANG_IDENTIFIER_REGEX, "_") + "_$kotlinFunctionName"
if (independent) kotlinLines.add("@" + topLevelKotlinScope.reference(KotlinTypes.independent))
kotlinLines.add("@SymbolName(${functionName.quoteAsKotlinLiteral()})")
"$cReturnType $functionName ($joinedCParameters)"
"$externCPrefix$cReturnType $functionName ($joinedCParameters)"
}
}
nativeLines.add(cFunctionHeader + " {")
@@ -5,6 +5,7 @@ import org.jetbrains.kotlin.native.interop.indexer.*
fun tryRenderStructOrUnion(def: StructDef): String? = when (def.kind) {
StructDef.Kind.STRUCT -> tryRenderStruct(def)
StructDef.Kind.UNION -> tryRenderUnion(def)
StructDef.Kind.CLASS -> tryRenderStruct(def)
}
private fun tryRenderStruct(def: StructDef): String? {
@@ -63,7 +63,7 @@ class StubIrBridgeBuilder(
)
private val mappingBridgeGenerator: MappingBridgeGenerator =
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator)
MappingBridgeGeneratorImpl(declarationMapper, simpleBridgeGenerator, context.libraryForCStubs.language)
private val propertyAccessorBridgeBodies = mutableMapOf<PropertyAccessor, String>()
private val functionBridgeBodies = mutableMapOf<FunctionStub, List<String>>()
@@ -260,6 +260,9 @@ class StubIrBridgeBuilder(
isVararg = isVararg or parameter.isVararg
val parameterName = parameter.name.asSimpleName()
val bridgeArgument = when {
function.isCxxInstanceMember() && index == 0 -> {
"rawPtr"
}
parameter in builderResult.bridgeGenerationComponents.cStringParameters -> {
bodyGenerator.pushMemScoped()
"$parameterName?.cstr?.getPointer(memScope)"
@@ -289,7 +292,18 @@ class StubIrBridgeBuilder(
bridgeArguments,
independent = false
) { nativeValues ->
"${origin.function.name}(${nativeValues.joinToString()})"
with (origin.function) {
when {
isCxxInstanceMethod ->
"(${nativeValues[0]})->${name}(${nativeValues.drop(1).joinToString()})"
isCxxConstructor ->
"new(${nativeValues[0]}) ${cxxReceiverClass!!.spelling}(${nativeValues.drop(1).joinToString()})"
isCxxDestructor ->
"(${nativeValues[0]})->~${cxxReceiverClass!!.spelling?.substringAfterLast(':')}()"
else ->
"${fullName}(${nativeValues.joinToString()})"
}
}
}
bodyGenerator.returnResult(result)
functionBridgeBodies[function] = bodyGenerator.build()
@@ -28,12 +28,15 @@ class StubIrContext(
if (platform == KotlinPlatform.JVM) {
add("jni.h")
}
if (configuration.library.language == Language.CPP) {
add("new")
}
addAll(configuration.library.includes)
},
compilerArgs = configuration.library.compilerArgs,
additionalPreambleLines = configuration.library.additionalPreambleLines +
when (configuration.library.language) {
Language.C -> emptyList()
Language.C, Language.CPP -> emptyList()
Language.OBJECTIVE_C -> listOf("void objc_terminate();")
}
).precompileHeaders()
@@ -80,6 +80,17 @@ internal class StructStubBuilder(
}
val classifier = context.getKotlinClassForPointed(decl)
var methods: List<FunctionStub> =
def.methods
.filter { it.isCxxInstanceMethod }
.map { func ->
try {
(FunctionStubBuilder(context, func).build() as List<FunctionStub>).single()
} catch (e: Throwable) {
null
}
}.filterNotNull()
val fields: List<PropertyStub?> = def.fields.map { field ->
try {
assert(field.name.isNotEmpty())
@@ -179,18 +190,33 @@ internal class StructStubBuilder(
val annotation = AnnotationStub.CStruct.VarType(def.size, def.align).takeIf {
context.generationMode == GenerationMode.METADATA
}
val companion = ClassStub.Companion(
companionClassifier,
superClassInit = companionSuperInit,
annotations = listOfNotNull(annotation, AnnotationStub.Deprecated.deprecatedCVariableCompanion)
)
var classMethods: List<FunctionStub> =
def.methods
.filter { !it.isCxxInstanceMethod }
.map { func ->
try {
(FunctionStubBuilder(context, func).build() as List<FunctionStub>).single()
} catch (e: Throwable) {
null
}
}.filterNotNull()
val classFields = def.staticFields
.map { field -> (GlobalStubBuilder(context, field).build() as List<PropertyStub>).single() }
val companion = ClassStub.Companion(
companionClassifier,
superClassInit = companionSuperInit,
annotations = listOfNotNull(annotation, AnnotationStub.Deprecated.deprecatedCVariableCompanion),
properties = classFields,
methods = classMethods
)
return listOf(ClassStub.Simple(
classifier,
origin = origin,
properties = fields.filterNotNull() + if (platform == KotlinPlatform.NATIVE) bitFields else emptyList(),
constructors = listOf(primaryConstructor),
methods = emptyList(),
methods = methods,
modality = ClassStubModality.NONE,
annotations = listOfNotNull(structAnnotation),
superClassInit = superClassInit,
@@ -628,12 +654,12 @@ internal class GlobalStubBuilder(
val getter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Getter.SimpleGetter().also {
val extra = BridgeGenerationInfo(global.name, mirror.info)
val extra = BridgeGenerationInfo(global.fullName, mirror.info)
context.bridgeComponentsBuilder.arrayGetterBridgeInfo[it] = extra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.fullName}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
@@ -647,12 +673,12 @@ internal class GlobalStubBuilder(
val getter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Getter.SimpleGetter().also {
val getterExtra = BridgeGenerationInfo(global.name, mirror.info)
val getterExtra = BridgeGenerationInfo(global.fullName, mirror.info)
context.bridgeComponentsBuilder.getterToBridgeInfo[it] = getterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.fullName}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
@@ -664,12 +690,12 @@ internal class GlobalStubBuilder(
val setter = when (context.platform) {
KotlinPlatform.JVM -> {
PropertyAccessor.Setter.SimpleSetter().also {
val setterExtra = BridgeGenerationInfo(global.name, mirror.info)
val setterExtra = BridgeGenerationInfo(global.fullName, mirror.info)
context.bridgeComponentsBuilder.setterToBridgeInfo[it] = setterExtra
}
}
KotlinPlatform.NATIVE -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_setter")
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.fullName}_setter")
PropertyAccessor.Setter.ExternalSetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.setterToWrapperInfo[it] = WrapperGenerationInfo(global)
}
@@ -682,10 +708,10 @@ internal class GlobalStubBuilder(
kotlinType = mirror.pointedType
val getter = when (context.generationMode) {
GenerationMode.SOURCE_CODE -> {
PropertyAccessor.Getter.InterpretPointed(global.name, kotlinType.toStubIrType())
PropertyAccessor.Getter.InterpretPointed(global.fullName, kotlinType.toStubIrType())
}
GenerationMode.METADATA -> {
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.name}_getter")
val cCallAnnotation = AnnotationStub.CCall.Symbol("${context.generateNextUniqueId("knifunptr_")}_${global.fullName}_getter")
PropertyAccessor.Getter.ExternalGetter(listOf(cCallAnnotation)).also {
context.wrapperComponentsBuilder.getterToWrapperInfo[it] = WrapperGenerationInfo(global, passViaPointer = true)
}
@@ -12,6 +12,17 @@ private val StubOrigin.ObjCMethod.isOptional: Boolean
fun FunctionStub.isOptionalObjCMethod(): Boolean = this.origin is StubOrigin.ObjCMethod &&
this.origin.isOptional
fun FunctionStub.isCxxInstanceMember(): Boolean = this.origin is StubOrigin.Function &&
this.origin.function.isCxxInstanceMethod
fun FunctionStub.qualifiedName(): String =
if (this.origin is StubOrigin.Function && !this.origin.function.isCxxMethod) {
this.origin.function.fullName
} else {
name
}
val StubContainer.isInterface: Boolean
get() = if (this is ClassStub.Simple) {
modality == ClassStubModality.INTERFACE
@@ -97,4 +108,4 @@ val StubType.underlyingTypeFqName: String
is AbbreviatedType -> underlyingType.underlyingTypeFqName
is FunctionalType -> classifier.fqName
is TypeParameterType -> name
}
}
@@ -184,7 +184,8 @@ class StubIrTextEmitter(
if (element in bridgeBuilderResult.excludedStubs) return
val header = run {
val parameters = element.parameters.joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val parameters = (if (element.isCxxInstanceMember()) element.parameters.drop(1) else element.parameters).
joinToString(prefix = "(", postfix = ")") { renderFunctionParameter(it) }
val receiver = element.receiver?.let { renderFunctionReceiver(it) + "." } ?: ""
val typeParameters = renderTypeParameters(element.typeParameters)
val override = if (element.isOverride) "override " else ""
@@ -660,4 +661,4 @@ class StubIrTextEmitter(
"$name : ${renderStubType(it)}"
} ?: name
}
}
}
@@ -121,16 +121,20 @@ private fun Properties.putAndRunOnReplace(key: Any, newValue: Any, beforeReplace
this[key] = newValue
}
private fun selectNativeLanguage(config: DefFile.DefFileConfig): Language {
private fun selectNativeLanguage(config: DefFile.DefFileConfig, hintIsCPP: Boolean = false): Language {
val languages = mapOf(
"C" to Language.C,
"C++" to Language.CPP,
"Objective-C" to Language.OBJECTIVE_C
)
val language = config.language ?: return Language.C
val lang = config.language?.let {
languages[it]
?: error("Unexpected language '${config.language}'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
} ?: Language.C
return if (lang == Language.C && hintIsCPP) Language.CPP else lang
return languages[language] ?:
error("Unexpected language '$language'. Possible values are: ${languages.keys.joinToString { "'$it'" }}")
}
private fun parseImports(dependencies: List<KotlinLibrary>): ImportsImpl =
@@ -220,8 +224,6 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
cinteropArguments.linkerOptions.value.toTypedArray()
val verbose = cinteropArguments.verbose
val language = selectNativeLanguage(def.config)
val entryPoint = def.config.entryPoints.atMostOne()
val linkerOpts =
def.config.linkerOpts.toTypedArray() +
@@ -294,7 +296,7 @@ private fun processCLib(flavor: KotlinPlatform, cinteropArguments: CInteropArgum
File(nativeLibsDir).mkdirs()
val outCFile = tempFiles.create(libName, ".${language.sourceFileExtension}")
val outCFile = tempFiles.create(libName, ".${library.language.sourceFileExtension}")
val logger = if (verbose) {
{ message: String -> println(message) }
@@ -451,6 +453,14 @@ internal fun prepareTool(target: String?, flavor: KotlinPlatform): ToolConfig {
return tool
}
private fun isCxxOptions(opts: List<String>) : Boolean {
if (opts.size >= 2) opts.reduce args@{ prev, that ->
if (prev == "-x" && that == "c++") return@isCxxOptions true
return@args that
}
return false
}
internal fun buildNativeLibrary(
tool: ToolConfig,
def: DefFile,
@@ -462,7 +472,8 @@ internal fun buildNativeLibrary(
arguments.compilerOptions + arguments.compilerOption).toTypedArray()
val headerFiles = def.config.headers + additionalHeaders
val language = selectNativeLanguage(def.config)
val cppOptions = isCxxOptions(def.config.compilerOpts + additionalCompilerOpts)
val language = selectNativeLanguage(def.config, cppOptions)
val compilerOpts: List<String> = mutableListOf<String>().apply {
addAll(def.config.compilerOpts)
addAll(tool.defaultCompilerOpts)
@@ -476,6 +487,7 @@ internal fun buildNativeLibrary(
addAll(getCompilerFlagsForVfsOverlay(arguments.headerFilterPrefix.toTypedArray(), def))
addAll(when (language) {
Language.C -> emptyList()
Language.CPP -> if (cppOptions) emptyList() else listOf("-x", "c++")
Language.OBJECTIVE_C -> {
// "Objective-C" within interop means "Objective-C with ARC":
listOf("-fobjc-arc")