Use compiler-generated C stubs to call Objective-C methods
Also * Use compiler-generated C stubs when overriding all Objective-C methods * Support block types in C stubs generator
This commit is contained in:
committed by
SvyatoslavScherbina
parent
68efc823f1
commit
54881b421c
@@ -42,7 +42,7 @@ fun optional(): Nothing = throw RuntimeException("Do not call me!!!")
|
||||
|
||||
@Deprecated(
|
||||
"Add @OverrideInit to constructor to make it override Objective-C initializer",
|
||||
level = DeprecationLevel.WARNING
|
||||
level = DeprecationLevel.ERROR
|
||||
)
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_INIT_BY)
|
||||
external fun <T : ObjCObjectBase> T.initBy(constructorCall: T): T
|
||||
@@ -56,8 +56,7 @@ private fun ObjCObjectBase.superInitCheck(superInitCallResult: ObjCObject?) {
|
||||
throw UnsupportedOperationException("Super initializer has replaced object")
|
||||
}
|
||||
|
||||
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING)
|
||||
fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T) // TODO: make private
|
||||
internal fun <T : Any?> Any?.uncheckedCast(): T = @Suppress("UNCHECKED_CAST") (this as T)
|
||||
|
||||
@SymbolName("Kotlin_Interop_refFromObjC")
|
||||
external fun <T> interpretObjCPointerOrNull(objcPtr: NativePtr): T?
|
||||
@@ -93,8 +92,9 @@ var <T : Any?> ObjCNotImplementedVar<T>.value: T
|
||||
typealias ObjCStringVarOf<T> = ObjCNotImplementedVar<T>
|
||||
typealias ObjCBlockVar<T> = ObjCNotImplementedVar<T>
|
||||
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_GET_RECEIVER_OR_SUPER)
|
||||
external fun getReceiverOrSuper(receiver: NativePtr, superClass: NativePtr): COpaquePointer?
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_CREATE_SUPER_STRUCT)
|
||||
@PublishedApi
|
||||
internal external fun createObjCSuperStruct(receiver: NativePtr, superClass: NativePtr): NativePtr
|
||||
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@@ -102,11 +102,7 @@ annotation class ExternalObjCClass(val protocolGetter: String = "", val binaryNa
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ObjCMethod(val selector: String, val bridge: String)
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ObjCBridge(val selector: String, val encoding: String, val imp: String = "")
|
||||
annotation class ObjCMethod(val selector: String, val encoding: String, val isStret: Boolean = false)
|
||||
|
||||
@Target(AnnotationTarget.CONSTRUCTOR)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@@ -114,7 +110,7 @@ annotation class ObjCConstructor(val initSelector: String, val designated: Boole
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class ObjCFactory(val bridge: String)
|
||||
annotation class ObjCFactory(val selector: String, val encoding: String, val isStret: Boolean = false)
|
||||
|
||||
@Target(AnnotationTarget.FILE)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@@ -158,10 +154,13 @@ private fun allocObjCObject(clazz: NativePtr): NativePtr {
|
||||
@kotlin.native.internal.ExportForCompiler
|
||||
private external fun <T : ObjCObject> getObjCClass(): NativePtr
|
||||
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER)
|
||||
external fun getMessenger(superClass: NativePtr): COpaquePointer?
|
||||
internal external fun getMessenger(superClass: NativePtr): COpaquePointer?
|
||||
|
||||
@PublishedApi
|
||||
@TypedIntrinsic(IntrinsicType.OBJC_GET_MESSENGER_STRET)
|
||||
external fun getMessengerStret(superClass: NativePtr): COpaquePointer?
|
||||
internal external fun getMessengerStret(superClass: NativePtr): COpaquePointer?
|
||||
|
||||
|
||||
internal class ObjCWeakReferenceImpl : kotlin.native.ref.WeakReferenceImpl() {
|
||||
@@ -180,14 +179,18 @@ private external fun ObjCWeakReferenceImpl.init(objcPtr: NativePtr)
|
||||
|
||||
// Konan runtme:
|
||||
|
||||
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.WARNING)
|
||||
@Deprecated("Use plain Kotlin cast of String to NSString", level = DeprecationLevel.ERROR)
|
||||
@SymbolName("Kotlin_Interop_CreateNSStringFromKString")
|
||||
external fun CreateNSStringFromKString(str: String?): NativePtr
|
||||
|
||||
@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.WARNING)
|
||||
@Deprecated("Use plain Kotlin cast of NSString to String", level = DeprecationLevel.ERROR)
|
||||
@SymbolName("Kotlin_Interop_CreateKStringFromNSString")
|
||||
external fun CreateKStringFromNSString(ptr: NativePtr): String?
|
||||
|
||||
@PublishedApi
|
||||
@SymbolName("Kotlin_Interop_CreateObjCObjectHolder")
|
||||
internal external fun createObjCObjectHolder(ptr: NativePtr): Any?
|
||||
|
||||
// Objective-C runtime:
|
||||
|
||||
@SymbolName("objc_retainAutoreleaseReturnValue")
|
||||
|
||||
@@ -25,7 +25,7 @@ inline fun <R> autoreleasepool(block: () -> R): R {
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING)
|
||||
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.ERROR)
|
||||
fun <T : ObjCObject> ObjCObject.reinterpret() = @Suppress("DEPRECATION") this.uncheckedCast<T>()
|
||||
|
||||
// TODO: null checks
|
||||
|
||||
+24
-197
@@ -19,7 +19,6 @@ package org.jetbrains.kotlin.native.interop.gen
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.native.interop.gen.jvm.StubGenerator
|
||||
import org.jetbrains.kotlin.native.interop.indexer.*
|
||||
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||
|
||||
private fun ObjCMethod.getKotlinParameterNames(forConstructorOrFactory: Boolean = false): List<String> {
|
||||
val selectorParts = this.selector.split(":")
|
||||
@@ -74,12 +73,11 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
override fun generate(context: StubGenerationContext): Sequence<String> =
|
||||
if (context.nativeBridges.isSupported(this)) {
|
||||
val result = mutableListOf<String>()
|
||||
result.add(objCMethodAnnotation)
|
||||
if (method.nsConsumesSelf) result.add("@CCall.ConsumesReceiver")
|
||||
if (method.nsReturnsRetained) result.add("@CCall.ReturnsRetained")
|
||||
result.addAll(objCMethodAnnotations)
|
||||
result.add(header)
|
||||
|
||||
if (method.isInit) {
|
||||
// TODO: generate only constructor/factory in this case.
|
||||
val kotlinScope = stubGenerator.kotlinFile
|
||||
|
||||
val newParameterNames = method.getKotlinParameterNames(forConstructorOrFactory = true)
|
||||
@@ -132,26 +130,13 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
}
|
||||
|
||||
result.add("")
|
||||
result.add("@ObjCFactory".applyToStrings(bridgeName))
|
||||
result.addAll(buildObjCMethodAnnotations("@ObjCFactory"))
|
||||
result.add("external fun <T : $className> $receiver.create($parameters): $returnType")
|
||||
}
|
||||
is ObjCProtocol -> {} // Nothing to do.
|
||||
}
|
||||
}
|
||||
|
||||
val objCBridge = "@ObjCBridge".applyToStrings(
|
||||
*mutableListOf<String>().apply {
|
||||
add(method.selector)
|
||||
add(method.encoding)
|
||||
addIfNotNull(implementationTemplate)
|
||||
}.toTypedArray()
|
||||
)
|
||||
|
||||
context.addTopLevelDeclaration(
|
||||
listOf("@kotlin.native.internal.ExportForCompiler", objCBridge)
|
||||
+ block(bridgeHeader, bodyLines)
|
||||
)
|
||||
|
||||
result.asSequence()
|
||||
} else {
|
||||
sequenceOf(
|
||||
@@ -160,48 +145,18 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
)
|
||||
}
|
||||
|
||||
private val bodyLines: List<String>
|
||||
private val kotlinParameters: List<KotlinParameter>
|
||||
private val kotlinReturnType: String
|
||||
private val header: String
|
||||
private val implementationTemplate: String?
|
||||
internal val bridgeName: String
|
||||
private val bridgeHeader: String
|
||||
internal val objCMethodAnnotation: String
|
||||
internal val objCMethodAnnotations: List<String>
|
||||
private val isStret: Boolean
|
||||
|
||||
init {
|
||||
val bodyGenerator = KotlinCodeBuilder(scope = stubGenerator.kotlinFile)
|
||||
|
||||
kotlinParameters = mutableListOf()
|
||||
val kotlinObjCBridgeParameters = mutableListOf<KotlinParameter>()
|
||||
val nativeBridgeArguments = mutableListOf<TypedKotlinValue>()
|
||||
|
||||
val kniReceiverParameter = "kniR"
|
||||
val kniSuperClassParameter = "kniSC"
|
||||
val kniSelectorParameter = "kniSEL"
|
||||
|
||||
val voidPtr = PointerType(VoidType)
|
||||
|
||||
val returnType = method.getReturnType(container.classOrProtocol)
|
||||
|
||||
val messengerGetter =
|
||||
if (returnType.isStret(stubGenerator.configuration.target)) "getMessengerStret" else "getMessenger"
|
||||
|
||||
kotlinObjCBridgeParameters.add(KotlinParameter(kniSuperClassParameter, KotlinTypes.nativePtr))
|
||||
nativeBridgeArguments.add(TypedKotlinValue(voidPtr, "$messengerGetter($kniSuperClassParameter)"))
|
||||
|
||||
if (method.nsConsumesSelf) {
|
||||
// TODO: do this later due to possible exceptions
|
||||
bodyGenerator.out("objc_retain($kniReceiverParameter)")
|
||||
}
|
||||
|
||||
kotlinObjCBridgeParameters.add(KotlinParameter(kniReceiverParameter, KotlinTypes.nativePtr))
|
||||
nativeBridgeArguments.add(
|
||||
TypedKotlinValue(voidPtr,
|
||||
"getReceiverOrSuper($kniReceiverParameter, $kniSuperClassParameter)"))
|
||||
|
||||
kotlinObjCBridgeParameters.add(KotlinParameter(kniSelectorParameter, KotlinTypes.cOpaquePointer))
|
||||
nativeBridgeArguments.add(TypedKotlinValue(voidPtr, kniSelectorParameter))
|
||||
isStret = returnType.isStret(stubGenerator.configuration.target)
|
||||
|
||||
val kotlinParameterNames = method.getKotlinParameterNames()
|
||||
|
||||
@@ -211,9 +166,6 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
val kotlinType = stubGenerator.mirror(it.type).argType
|
||||
val annotatedName = if (it.nsConsumed) "@CCall.Consumed $name" else name
|
||||
kotlinParameters.add(KotlinParameter(annotatedName, kotlinType))
|
||||
|
||||
kotlinObjCBridgeParameters.add(KotlinParameter(name, kotlinType))
|
||||
nativeBridgeArguments.add(TypedKotlinValue(it.type, name.asSimpleName()))
|
||||
}
|
||||
|
||||
this.kotlinReturnType = if (returnType.unwrapTypedefs() is VoidType) {
|
||||
@@ -222,55 +174,7 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
stubGenerator.mirror(returnType).argType
|
||||
}.render(stubGenerator.kotlinFile)
|
||||
|
||||
val result = stubGenerator.mappingBridgeGenerator.kotlinToNative(
|
||||
bodyGenerator,
|
||||
this@ObjCMethodStub,
|
||||
returnType,
|
||||
nativeBridgeArguments,
|
||||
independent = when (container) {
|
||||
is ObjCClassOrProtocol -> true // Every proper instance has this method in its method table.
|
||||
is ObjCCategory -> false // Method is contributed by native dependency.
|
||||
}
|
||||
) { nativeValues ->
|
||||
val messengerParameterTypes = mutableListOf<String>()
|
||||
messengerParameterTypes.add("void*")
|
||||
messengerParameterTypes.add("SEL")
|
||||
method.parameters.forEach {
|
||||
messengerParameterTypes.add(it.getTypeStringRepresentation())
|
||||
}
|
||||
|
||||
val messengerReturnType = returnType.getStringRepresentation()
|
||||
|
||||
val messengerType =
|
||||
"$messengerReturnType (* ${method.cAttributes}) (${messengerParameterTypes.joinToString()})"
|
||||
|
||||
val messenger = "(($messengerType) ${nativeValues.first()})"
|
||||
|
||||
val messengerArguments = nativeValues.drop(1)
|
||||
|
||||
"$messenger(${messengerArguments.joinToString()})"
|
||||
}
|
||||
bodyGenerator.returnResult(result)
|
||||
|
||||
this.implementationTemplate = if (needsImplementationTemplate()) {
|
||||
genImplementationTemplate(stubGenerator)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
this.bodyLines = bodyGenerator.build()
|
||||
|
||||
bridgeName = "objcKniBridge${stubGenerator.nextUniqueId()}"
|
||||
objCMethodAnnotation = "@ObjCMethod".applyToStrings(method.selector, bridgeName)
|
||||
|
||||
this.bridgeHeader = buildString {
|
||||
append("internal fun ")
|
||||
append(bridgeName)
|
||||
append('(')
|
||||
kotlinObjCBridgeParameters.renderParametersTo(stubGenerator.kotlinFile, this)
|
||||
append("): ")
|
||||
append(kotlinReturnType)
|
||||
}
|
||||
objCMethodAnnotations = buildObjCMethodAnnotations("@ObjCMethod")
|
||||
|
||||
val joinedKotlinParameters = kotlinParameters.renderParameters(stubGenerator.kotlinFile)
|
||||
|
||||
@@ -302,24 +206,26 @@ class ObjCMethodStub(private val stubGenerator: StubGenerator,
|
||||
}
|
||||
}
|
||||
|
||||
private fun needsImplementationTemplate(): Boolean =
|
||||
method.getReturnType(container.classOrProtocol).isBlockPointer() ||
|
||||
method.parameters.any { it.type.isBlockPointer() }
|
||||
private fun buildObjCMethodAnnotations(main: String) = listOfNotNull(
|
||||
buildObjCMethodAnnotation(main),
|
||||
"@CCall.ConsumesReceiver".takeIf { method.nsConsumesSelf },
|
||||
"@CCall.ReturnsRetained".takeIf { method.nsReturnsRetained }
|
||||
)
|
||||
|
||||
private fun genImplementationTemplate(stubGenerator: StubGenerator): String = when (container) {
|
||||
is ObjCClassOrProtocol -> {
|
||||
val codeBuilder = NativeCodeBuilder(stubGenerator.simpleBridgeGenerator.topLevelNativeScope)
|
||||
|
||||
val result = codeBuilder.genMethodImp(stubGenerator, this, method, container)
|
||||
stubGenerator.simpleBridgeGenerator.insertNativeBridge(this, emptyList(), codeBuilder.lines)
|
||||
result
|
||||
private fun buildObjCMethodAnnotation(annotation: String) = buildString {
|
||||
append(annotation)
|
||||
append('(')
|
||||
append(method.selector.quoteAsKotlinLiteral())
|
||||
append(", ")
|
||||
append(method.encoding.quoteAsKotlinLiteral())
|
||||
if (isStret) {
|
||||
append(", ")
|
||||
append("true")
|
||||
}
|
||||
is ObjCCategory -> ""
|
||||
append(')')
|
||||
}
|
||||
}
|
||||
|
||||
private fun Type.isBlockPointer() = this.unwrapTypedefs() is ObjCBlockPointer
|
||||
|
||||
private fun deprecatedInit(className: String, initParameterNames: List<String>, factory: Boolean): String {
|
||||
val replacement = if (factory) "$className.create" else className
|
||||
val replacementKind = if (factory) "factory method" else "constructor"
|
||||
@@ -720,11 +626,11 @@ class ObjCPropertyStub(
|
||||
}
|
||||
val result = mutableListOf(
|
||||
"$modifiers$kind $receiver${property.name.asSimpleName()}: $kotlinType",
|
||||
" ${getterStub.objCMethodAnnotation} external get"
|
||||
" ${getterStub.objCMethodAnnotations.joinToString(" ")} external get"
|
||||
)
|
||||
|
||||
property.setter?.let {
|
||||
result.add(" ${setterStub!!.objCMethodAnnotation} external set")
|
||||
result.add(" ${setterStub!!.objCMethodAnnotations.joinToString(" ")} external set")
|
||||
}
|
||||
|
||||
return result.asSequence()
|
||||
@@ -741,85 +647,6 @@ fun ObjCClassOrProtocol.kotlinClassName(isMeta: Boolean): String {
|
||||
return if (isMeta) "${baseClassName}Meta" else baseClassName
|
||||
}
|
||||
|
||||
private fun Parameter.getTypeStringRepresentation() =
|
||||
(if (this.nsConsumed) "__attribute__((ns_consumed)) " else "") + type.getStringRepresentation()
|
||||
|
||||
private fun ObjCMethod.getSelfTypeStringRepresentation() = if (this.nsConsumesSelf) {
|
||||
"__attribute__((ns_consumed)) id"
|
||||
} else {
|
||||
"id"
|
||||
}
|
||||
|
||||
val ObjCMethod.cAttributes get() = if (this.nsReturnsRetained) {
|
||||
"__attribute__((ns_returns_retained)) "
|
||||
} else {
|
||||
""
|
||||
}
|
||||
|
||||
private fun NativeCodeBuilder.genMethodImp(
|
||||
stubGenerator: StubGenerator,
|
||||
nativeBacked: NativeBacked,
|
||||
method: ObjCMethod,
|
||||
container: ObjCClassOrProtocol
|
||||
): String {
|
||||
|
||||
val returnType = method.getReturnType(container)
|
||||
val cReturnType = returnType.getStringRepresentation()
|
||||
|
||||
val bridgeArguments = mutableListOf<TypedNativeValue>()
|
||||
|
||||
val parameters = mutableListOf<Pair<String, String>>()
|
||||
|
||||
parameters.add("self" to method.getSelfTypeStringRepresentation())
|
||||
|
||||
val receiverType = ObjCIdType(ObjCPointer.Nullability.NonNull, protocols = emptyList())
|
||||
bridgeArguments.add(TypedNativeValue(receiverType, "self"))
|
||||
|
||||
parameters.add("_cmd" to "SEL")
|
||||
|
||||
method.parameters.forEachIndexed { index, parameter ->
|
||||
val name = "p$index"
|
||||
parameters.add(name to parameter.getTypeStringRepresentation())
|
||||
bridgeArguments.add(TypedNativeValue(parameter.type, name))
|
||||
}
|
||||
|
||||
val functionName = "knimi_" + stubGenerator.pkgName.replace('.', '_') + stubGenerator.nextUniqueId()
|
||||
val functionAttr = method.cAttributes
|
||||
|
||||
out("$cReturnType $functionName(${parameters.joinToString { it.second + " " + it.first }}) $functionAttr{")
|
||||
|
||||
val callExpr = stubGenerator.mappingBridgeGenerator.nativeToKotlin(
|
||||
this,
|
||||
nativeBacked,
|
||||
returnType,
|
||||
bridgeArguments
|
||||
) { kotlinValues ->
|
||||
|
||||
val kotlinReceiverType = stubGenerator.declarationMapper
|
||||
.getKotlinClassFor(container, isMeta = method.isClass)
|
||||
.type.render(stubGenerator.kotlinFile)
|
||||
|
||||
val kotlinRawReceiver = kotlinValues.first()
|
||||
val kotlinReceiver = "$kotlinRawReceiver.uncheckedCast<$kotlinReceiverType>()"
|
||||
|
||||
val namedArguments = kotlinValues.drop(1).zip(method.getKotlinParameterNames()) { value, name ->
|
||||
"${name.asSimpleName()} = $value"
|
||||
}
|
||||
|
||||
"${kotlinReceiver}.${method.kotlinName.asSimpleName()}(${namedArguments.joinToString()})"
|
||||
}
|
||||
|
||||
if (returnType.unwrapTypedefs() is VoidType) {
|
||||
out(" $callExpr;")
|
||||
} else {
|
||||
out(" return $callExpr;")
|
||||
}
|
||||
|
||||
out("}")
|
||||
|
||||
return functionName
|
||||
}
|
||||
|
||||
private fun genProtocolGetter(
|
||||
stubGenerator: StubGenerator,
|
||||
nativeBacked: NativeBacked,
|
||||
|
||||
+16
-3
@@ -6,8 +6,10 @@ import org.jetbrains.kotlin.cli.common.messages.MessageCollector
|
||||
import org.jetbrains.kotlin.konan.exec.Command
|
||||
import org.jetbrains.kotlin.konan.file.*
|
||||
import org.jetbrains.kotlin.konan.target.ClangArgs
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
|
||||
class CStubsManager {
|
||||
class CStubsManager(private val target: KonanTarget) {
|
||||
|
||||
fun getUniqueName(prefix: String) = "$prefix${counter++}"
|
||||
|
||||
@@ -18,13 +20,24 @@ class CStubsManager {
|
||||
fun compile(clang: ClangArgs, messageCollector: MessageCollector, verbose: Boolean): File? {
|
||||
if (stubs.isEmpty()) return null
|
||||
|
||||
val cSource = createTempFile("cstubs", ".c").deleteOnExit()
|
||||
val compilerOptions = mutableListOf<String>()
|
||||
val sourceFileExtension = when (target.family) {
|
||||
Family.OSX, Family.IOS -> {
|
||||
compilerOptions += "-fobjc-arc"
|
||||
".m" // TODO: consider managing C and Objective-C stubs separately.
|
||||
}
|
||||
else -> ".c"
|
||||
}
|
||||
val cSource = createTempFile("cstubs", sourceFileExtension).deleteOnExit()
|
||||
cSource.writeLines(stubs.flatMap { it.lines })
|
||||
|
||||
val bitcode = createTempFile("cstubs", ".bc").deleteOnExit()
|
||||
|
||||
val cSourcePath = cSource.absolutePath
|
||||
val clangCommand = clang.clangC(cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath)
|
||||
|
||||
val clangCommand = clang.clangC(*compilerOptions.toTypedArray(), "-O2",
|
||||
cSourcePath, "-emit-llvm", "-c", "-o", bitcode.absolutePath)
|
||||
|
||||
val result = Command(clangCommand).getResult(withErrors = true)
|
||||
if (result.exitCode != 0) {
|
||||
reportCompilationErrors(cSourcePath, result, messageCollector, verbose)
|
||||
|
||||
+1
-1
@@ -316,7 +316,7 @@ internal class Context(config: KonanConfig) : KonanBackendContext(config) {
|
||||
lateinit var bitcodeFileName: String
|
||||
lateinit var library: KonanLibraryWriter
|
||||
|
||||
val cStubsManager = CStubsManager()
|
||||
val cStubsManager = CStubsManager(config.target)
|
||||
|
||||
val coverage = CoverageManager(this)
|
||||
|
||||
|
||||
+11
-58
@@ -5,17 +5,13 @@
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.findPackageView
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getArgumentValueOrNull
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValueOrNull
|
||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.expressions.IrConst
|
||||
import org.jetbrains.kotlin.ir.types.classifierOrFail
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -35,7 +31,6 @@ internal val externalObjCClassFqName = interopPackageName.child(Name.identifier(
|
||||
private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod"))
|
||||
private val objCConstructorFqName = FqName("kotlinx.cinterop.ObjCConstructor")
|
||||
private val objCFactoryFqName = interopPackageName.child(Name.identifier("ObjCFactory"))
|
||||
private val objCBridgeFqName = interopPackageName.child(Name.identifier("ObjCBridge"))
|
||||
|
||||
@Deprecated("Use IR version rather than descriptor version")
|
||||
fun ClassDescriptor.isObjCClass(): Boolean =
|
||||
@@ -91,52 +86,21 @@ fun ClassDescriptor.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.i
|
||||
fun IrClass.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExternalObjCClass()
|
||||
|
||||
|
||||
data class ObjCMethodInfo(val bridge: FunctionDescriptor,
|
||||
val selector: String,
|
||||
data class ObjCMethodInfo(val selector: String,
|
||||
val encoding: String,
|
||||
val imp: String?)
|
||||
|
||||
private fun CallableDescriptor.getBridgeAnnotation() =
|
||||
this.annotations.findAnnotation(objCBridgeFqName)
|
||||
val isStret: Boolean)
|
||||
|
||||
private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? {
|
||||
assert (this.kind.isReal)
|
||||
|
||||
val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null
|
||||
val packageView = this.findPackageView()
|
||||
|
||||
val bridgeName = methodAnnotation.getStringValue("bridge")
|
||||
|
||||
return objCMethodInfoByBridge(packageView, bridgeName)
|
||||
return objCMethodInfo(methodAnnotation)
|
||||
}
|
||||
|
||||
private fun objCMethodInfoByBridge(packageView: PackageViewDescriptor, bridgeName: String): ObjCMethodInfo {
|
||||
val bridge = packageView.memberScope
|
||||
.getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND)
|
||||
.single()
|
||||
|
||||
val bridgeAnnotation = bridge.getBridgeAnnotation()!!
|
||||
return ObjCMethodInfo(
|
||||
bridge = bridge,
|
||||
selector = bridgeAnnotation.getStringValue("selector"),
|
||||
encoding = bridgeAnnotation.getStringValue("encoding"),
|
||||
imp = bridgeAnnotation.getStringValueOrNull("imp")
|
||||
)
|
||||
}
|
||||
|
||||
fun IrSimpleFunction.objCMethodArgValue(argName: String): String? {
|
||||
val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null
|
||||
methodAnnotation.symbol.owner.valueParameters.forEachIndexed { index, parameter ->
|
||||
if (parameter.name.asString() == argName) {
|
||||
val bridgeArgument = methodAnnotation.getValueArgument(index) as IrConst<kotlin.String>
|
||||
return bridgeArgument.value
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
fun IrSimpleFunction.hasObjCMethodAnnotation() =
|
||||
this.annotations.findAnnotation(objCMethodFqName) != null
|
||||
private fun objCMethodInfo(annotation: AnnotationDescriptor) = ObjCMethodInfo(
|
||||
selector = annotation.getStringValue("selector"),
|
||||
encoding = annotation.getStringValue("encoding"),
|
||||
isStret = annotation.getArgumentValueOrNull<Boolean>("isStret") ?: false
|
||||
)
|
||||
|
||||
/**
|
||||
* @param onlyExternal indicates whether to accept overriding methods from Kotlin classes
|
||||
@@ -237,15 +201,6 @@ fun ConstructorDescriptor.objCConstructorIsDesignated(): Boolean {
|
||||
}
|
||||
|
||||
|
||||
fun ConstructorDescriptor.getObjCInitMethod(): FunctionDescriptor? {
|
||||
return this.annotations.findAnnotation(objCConstructorFqName)?.let {
|
||||
val initSelector = it.getStringValue("initSelector")
|
||||
this.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence()
|
||||
.filterIsInstance<FunctionDescriptor>()
|
||||
.single { it.getExternalObjCMethodInfo()?.selector == initSelector }
|
||||
}
|
||||
}
|
||||
|
||||
val IrConstructor.isObjCConstructor get() = this.descriptor.annotations.hasAnnotation(objCConstructorFqName)
|
||||
|
||||
fun IrConstructor.getObjCInitMethod(): IrSimpleFunction? {
|
||||
@@ -263,9 +218,7 @@ val IrFunction.hasObjCMethodAnnotation get() = this.descriptor.annotations.hasAn
|
||||
|
||||
fun FunctionDescriptor.getObjCFactoryInitMethodInfo(): ObjCMethodInfo? {
|
||||
val factoryAnnotation = this.annotations.findAnnotation(objCFactoryFqName) ?: return null
|
||||
val bridgeName = factoryAnnotation.getStringValue("bridge")
|
||||
|
||||
return objCMethodInfoByBridge(this.findPackageView(), bridgeName)
|
||||
return objCMethodInfo(factoryAnnotation)
|
||||
}
|
||||
|
||||
fun inferObjCSelector(descriptor: FunctionDescriptor): String = if (descriptor.valueParameters.isEmpty()) {
|
||||
|
||||
+1
@@ -9,4 +9,5 @@ object RuntimeNames {
|
||||
val exportTypeInfoAnnotation = FqName("kotlin.native.internal.ExportTypeInfo")
|
||||
val cCall = FqName("kotlinx.cinterop.internal.CCall")
|
||||
val independent = FqName("kotlin.native.internal.Independent")
|
||||
val filterExceptions = FqName("kotlin.native.internal.FilterExceptions")
|
||||
}
|
||||
|
||||
+521
-73
@@ -1,13 +1,18 @@
|
||||
package org.jetbrains.kotlin.backend.konan.cgen
|
||||
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedClassDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedSimpleFunctionDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.WrappedValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.backend.common.lower.at
|
||||
import org.jetbrains.kotlin.backend.common.lower.irNot
|
||||
import org.jetbrains.kotlin.backend.konan.PrimitiveBinaryType
|
||||
import org.jetbrains.kotlin.backend.konan.RuntimeNames
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.createAnnotation
|
||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.backend.konan.isObjCMetaClass
|
||||
import org.jetbrains.kotlin.builtins.UnsignedType
|
||||
import org.jetbrains.kotlin.descriptors.ClassKind
|
||||
import org.jetbrains.kotlin.descriptors.Modality
|
||||
import org.jetbrains.kotlin.descriptors.Visibilities
|
||||
import org.jetbrains.kotlin.descriptors.annotations.Annotations
|
||||
@@ -16,21 +21,27 @@ import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.declarations.*
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrClassImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrFunctionImpl
|
||||
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
|
||||
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
|
||||
import org.jetbrains.kotlin.ir.expressions.IrCall
|
||||
import org.jetbrains.kotlin.ir.expressions.IrExpression
|
||||
import org.jetbrains.kotlin.ir.expressions.IrSpreadElement
|
||||
import org.jetbrains.kotlin.ir.expressions.IrVararg
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrClassSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrConstructorSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrSimpleFunctionSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
|
||||
import org.jetbrains.kotlin.ir.types.*
|
||||
import org.jetbrains.kotlin.ir.util.*
|
||||
import org.jetbrains.kotlin.konan.target.Family
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.types.Variance
|
||||
import org.jetbrains.kotlin.util.OperatorNameConventions
|
||||
|
||||
internal interface KotlinStubs {
|
||||
val irBuiltIns: IrBuiltIns
|
||||
@@ -39,16 +50,18 @@ internal interface KotlinStubs {
|
||||
fun addKotlin(declaration: IrDeclaration)
|
||||
fun addC(lines: List<String>)
|
||||
fun getUniqueCName(prefix: String): String
|
||||
fun getUniqueKotlinFunctionReferenceClassName(prefix: String): String
|
||||
|
||||
fun reportError(location: IrElement, message: String): Nothing
|
||||
}
|
||||
|
||||
private class KotlinToCCallBuilder(
|
||||
val irBuilder: IrBuilderWithScope,
|
||||
cBridgeName: String,
|
||||
val stubs: KotlinStubs
|
||||
) {
|
||||
|
||||
val cBridgeName = stubs.getUniqueCName("knbridge")
|
||||
|
||||
val symbols: KonanSymbols get() = stubs.symbols
|
||||
|
||||
val bridgeCallBuilder = KotlinCallBuilder(irBuilder, symbols)
|
||||
@@ -71,6 +84,14 @@ private fun KotlinToCCallBuilder.addArgument(
|
||||
parameter: IrValueParameter?
|
||||
) {
|
||||
val argumentPassing = mapParameter(type, variadic, parameter, argument)
|
||||
addArgument(argument, argumentPassing, variadic)
|
||||
}
|
||||
|
||||
private fun KotlinToCCallBuilder.addArgument(
|
||||
argument: IrExpression,
|
||||
argumentPassing: KotlinToCArgumentPassing,
|
||||
variadic: Boolean
|
||||
) {
|
||||
val cArgument = with(argumentPassing) { passValue(argument) }
|
||||
cCallBuilder.arguments += cArgument.expression
|
||||
if (!variadic) cFunctionBuilder.addParameter(cArgument.type)
|
||||
@@ -87,10 +108,7 @@ private fun KotlinToCCallBuilder.buildKotlinBridgeCall(transformCall: (IrCall) -
|
||||
internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWithScope, isInvoke: Boolean): IrExpression {
|
||||
require(expression.dispatchReceiver == null)
|
||||
|
||||
val cBridgeName = this.getUniqueCName("knbridge")
|
||||
val callBuilder = KotlinToCCallBuilder(builder, cBridgeName, this)
|
||||
|
||||
val cFunctionBuilder = callBuilder.cFunctionBuilder
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this)
|
||||
|
||||
val callee = expression.symbol.owner as IrSimpleFunction
|
||||
|
||||
@@ -136,15 +154,13 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
|
||||
else -> error(it)
|
||||
}
|
||||
}
|
||||
cFunctionBuilder.variadic = true
|
||||
callBuilder.cFunctionBuilder.variadic = true
|
||||
} else {
|
||||
callBuilder.addArgument(argument!!, parameter.type, variadic = false, parameter = parameter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val cLines = mutableListOf<String>()
|
||||
|
||||
val returnValuePassing = if (isInvoke) {
|
||||
val returnType = expression.getTypeArgument(expression.typeArgumentsCount - 1)!!
|
||||
mapReturnType(returnType, TypeLocation.FunctionCallResult(expression), signature = null)
|
||||
@@ -152,27 +168,126 @@ internal fun KotlinStubs.generateCCall(expression: IrCall, builder: IrBuilderWit
|
||||
mapReturnType(callee.returnType, TypeLocation.FunctionCallResult(expression), signature = callee)
|
||||
}
|
||||
|
||||
val result = with(returnValuePassing) {
|
||||
callBuilder.returnValue(callBuilder.cCallBuilder.build(targetFunctionName))
|
||||
}
|
||||
val result = callBuilder.buildCall(targetFunctionName, returnValuePassing)
|
||||
|
||||
val targetFunctionVariable = CVariable(CTypes.pointer(callBuilder.cFunctionBuilder.getType()), targetFunctionName)
|
||||
|
||||
val targetFunctionVariable = CVariable(CTypes.pointer(cFunctionBuilder.getType()), targetFunctionName)
|
||||
if (isInvoke) {
|
||||
callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable = ${targetPtrParameter!!};")
|
||||
} else {
|
||||
val cCallSymbolName = callee.getAnnotationArgumentValue<String>(cCall, "id")!!
|
||||
cLines += "extern const $targetFunctionVariable __asm(\"$cCallSymbolName\");" // Exported from cinterop stubs.
|
||||
this.addC(listOf("extern const $targetFunctionVariable __asm(\"$cCallSymbolName\");")) // Exported from cinterop stubs.
|
||||
}
|
||||
|
||||
cLines += "${callBuilder.bridgeBuilder.buildCSignature(cBridgeName)} {"
|
||||
cLines += callBuilder.cBridgeBodyLines
|
||||
cLines += "}"
|
||||
|
||||
this.addC(cLines)
|
||||
callBuilder.emitCBridge()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun KotlinToCCallBuilder.emitCBridge() {
|
||||
val cLines = mutableListOf<String>()
|
||||
|
||||
cLines += "${bridgeBuilder.buildCSignature(cBridgeName)} {"
|
||||
cLines += cBridgeBodyLines
|
||||
cLines += "}"
|
||||
|
||||
stubs.addC(cLines)
|
||||
}
|
||||
|
||||
private fun KotlinToCCallBuilder.buildCall(
|
||||
targetFunctionName: String,
|
||||
returnValuePassing: ValueReturning
|
||||
): IrExpression = with(returnValuePassing) {
|
||||
returnValue(cCallBuilder.build(targetFunctionName))
|
||||
}
|
||||
|
||||
|
||||
internal fun KotlinStubs.generateObjCCall(
|
||||
builder: IrBuilderWithScope,
|
||||
method: IrSimpleFunction,
|
||||
isStret: Boolean,
|
||||
selector: String,
|
||||
call: IrFunctionAccessExpression,
|
||||
superQualifier: IrClassSymbol?,
|
||||
receiver: IrExpression,
|
||||
arguments: List<IrExpression>
|
||||
): IrExpression {
|
||||
|
||||
val callBuilder = KotlinToCCallBuilder(builder, this)
|
||||
|
||||
val superClass = superQualifier?.let { builder.getObjCClass(symbols, it) }
|
||||
?: builder.irNullNativePtr(symbols)
|
||||
|
||||
val messenger = builder.irCall(if (isStret) {
|
||||
symbols.interopGetMessengerStret
|
||||
} else {
|
||||
symbols.interopGetMessenger
|
||||
}.owner).apply {
|
||||
putValueArgument(0, superClass) // TODO: check superClass statically.
|
||||
}
|
||||
|
||||
val targetPtrParameter = callBuilder.passThroughBridge(
|
||||
messenger,
|
||||
symbols.interopCPointer.typeWithStarProjections,
|
||||
CTypes.voidPtr
|
||||
).name
|
||||
val targetFunctionName = "targetPtr"
|
||||
|
||||
val preparedReceiver = if (method.consumesReceiver()) {
|
||||
builder.irCall(symbols.interopObjCRetain.owner).apply {
|
||||
putValueArgument(0, receiver)
|
||||
}
|
||||
} else {
|
||||
receiver
|
||||
}
|
||||
|
||||
val receiverOrSuper = if (superQualifier != null) {
|
||||
builder.irCall(symbols.interopCreateObjCSuperStruct.owner).apply {
|
||||
putValueArgument(0, preparedReceiver)
|
||||
putValueArgument(1, superClass)
|
||||
}
|
||||
} else {
|
||||
preparedReceiver
|
||||
}
|
||||
|
||||
callBuilder.cCallBuilder.arguments += callBuilder.passThroughBridge(
|
||||
receiverOrSuper, symbols.nativePtrType, CTypes.voidPtr).name
|
||||
callBuilder.cFunctionBuilder.addParameter(CTypes.voidPtr)
|
||||
|
||||
callBuilder.cCallBuilder.arguments += "@selector($selector)"
|
||||
callBuilder.cFunctionBuilder.addParameter(CTypes.voidPtr)
|
||||
|
||||
(0 until method.valueParameters.size).forEach {
|
||||
val parameter = method.valueParameters[it]
|
||||
callBuilder.addArgument(
|
||||
arguments[it],
|
||||
type = parameter.type,
|
||||
variadic = false,
|
||||
parameter = parameter
|
||||
)
|
||||
}
|
||||
|
||||
val returnValuePassing =
|
||||
mapReturnType(method.returnType, TypeLocation.FunctionCallResult(call), signature = method)
|
||||
|
||||
val result = callBuilder.buildCall(targetFunctionName, returnValuePassing)
|
||||
|
||||
val targetFunctionVariable = CVariable(CTypes.pointer(callBuilder.cFunctionBuilder.getType()), targetFunctionName)
|
||||
callBuilder.cBridgeBodyLines.add(0, "$targetFunctionVariable = $targetPtrParameter;")
|
||||
|
||||
callBuilder.emitCBridge()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.getObjCClass(symbols: KonanSymbols, symbol: IrClassSymbol): IrExpression {
|
||||
val classDescriptor = symbol.descriptor
|
||||
assert(!classDescriptor.isObjCMetaClass())
|
||||
return irCall(symbols.interopGetObjCClass, symbols.nativePtrType, listOf(symbol.typeWithStarProjections))
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.irNullNativePtr(symbols: KonanSymbols) = irCall(symbols.getNativeNullPtr.owner)
|
||||
|
||||
private class CCallbackBuilder(
|
||||
val stubs: KotlinStubs,
|
||||
val location: IrElement,
|
||||
@@ -212,36 +327,48 @@ private fun CCallbackBuilder.addParameter(it: IrValueParameter, functionParamete
|
||||
TypeLocation.FunctionPointerParameter(cFunctionBuilder.numberOfParameters, location)
|
||||
}
|
||||
|
||||
val valuePassing = mapParameter(it, typeLocation)
|
||||
val valuePassing = stubs.mapType(
|
||||
it.type,
|
||||
retained = it.isConsumed(),
|
||||
variadic = false,
|
||||
location = typeLocation
|
||||
)
|
||||
|
||||
val kotlinArgument = with(valuePassing) { receiveValue() }
|
||||
kotlinCallBuilder.arguments += kotlinArgument
|
||||
}
|
||||
|
||||
private fun CCallbackBuilder.build(function: IrSimpleFunction, signature: IrSimpleFunction): String {
|
||||
val kotlinCall = kotlinCallBuilder.build(function)
|
||||
|
||||
val typeLocation = if (isObjCMethod) {
|
||||
TypeLocation.ObjCMethodReturnValue(function)
|
||||
} else {
|
||||
TypeLocation.FunctionPointerReturnValue(location)
|
||||
}
|
||||
val valueReturning = stubs.mapReturnType(signature.returnType, typeLocation, signature)
|
||||
buildValueReturn(function, valueReturning)
|
||||
return buildCFunction()
|
||||
}
|
||||
|
||||
with(stubs.mapReturnType(signature.returnType, typeLocation, signature)) {
|
||||
private fun CCallbackBuilder.buildValueReturn(function: IrSimpleFunction, valueReturning: ValueReturning) {
|
||||
val kotlinCall = kotlinCallBuilder.build(function)
|
||||
with(valueReturning) {
|
||||
returnValue(kotlinCall)
|
||||
}
|
||||
|
||||
val cLines = mutableListOf<String>()
|
||||
|
||||
val kotlinBridge = bridgeBuilder.buildKotlinBridge()
|
||||
kotlinBridge.body = bridgeBuilder.kotlinIrBuilder.irBlockBody {
|
||||
kotlinBridgeStatements.forEach { +it }
|
||||
}
|
||||
stubs.addKotlin(kotlinBridge)
|
||||
|
||||
stubs.addC(listOf("${buildCBridge()};"))
|
||||
}
|
||||
|
||||
private fun CCallbackBuilder.buildCFunction(): String {
|
||||
val result = stubs.getUniqueCName("kncfun")
|
||||
|
||||
cLines += "${buildCBridge()};"
|
||||
val cLines = mutableListOf<String>()
|
||||
|
||||
cLines += "${cFunctionBuilder.buildSignature(result)} {"
|
||||
cLines += cBodyLines
|
||||
cLines += "}"
|
||||
@@ -427,42 +554,91 @@ private fun KotlinToCCallBuilder.mapParameter(
|
||||
classifier == symbols.string && parameter?.isWCStringParameter() == true ->
|
||||
WCStringArgumentPassing()
|
||||
|
||||
else -> stubs.mapType(type, TypeLocation.FunctionArgument(argument))
|
||||
else -> stubs.mapType(
|
||||
type,
|
||||
retained = parameter?.isConsumed() ?: false,
|
||||
variadic = variadic,
|
||||
location = TypeLocation.FunctionArgument(argument)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CCallbackBuilder.mapParameter(
|
||||
parameter: IrValueParameter,
|
||||
typeLocation: TypeLocation
|
||||
): ValuePassing = when {
|
||||
stubs.isObjCReferenceType(parameter.type) ->
|
||||
ObjCReferenceValuePassing(symbols, parameter.type, retained = parameter.isConsumed())
|
||||
|
||||
else -> stubs.mapType(parameter.type, typeLocation)
|
||||
}
|
||||
|
||||
private sealed class TypeLocation(val element: IrElement) {
|
||||
class FunctionArgument(val argument: IrExpression) : TypeLocation(argument)
|
||||
class FunctionCallResult(val call: IrCall) : TypeLocation(call)
|
||||
class FunctionCallResult(val call: IrFunctionAccessExpression) : TypeLocation(call)
|
||||
|
||||
class FunctionPointerParameter(val index: Int, element: IrElement) : TypeLocation(element)
|
||||
class FunctionPointerReturnValue(element: IrElement) : TypeLocation(element)
|
||||
|
||||
class ObjCMethodParameter(val index: Int, element: IrElement) : TypeLocation(element)
|
||||
class ObjCMethodReturnValue(element: IrElement) : TypeLocation(element)
|
||||
|
||||
class BlockParameter(val index: Int, val blockLocation: TypeLocation) : TypeLocation(blockLocation.element)
|
||||
class BlockReturnValue(val blockLocation: TypeLocation) : TypeLocation(blockLocation.element)
|
||||
}
|
||||
|
||||
private fun KotlinStubs.mapReturnType(type: IrType, location: TypeLocation, signature: IrSimpleFunction?): ValueReturning =
|
||||
when {
|
||||
type.isUnit() -> VoidReturning
|
||||
isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type, signature?.returnsRetained() ?: false)
|
||||
else -> mapType(type, location)
|
||||
private fun KotlinStubs.mapReturnType(
|
||||
type: IrType,
|
||||
location: TypeLocation,
|
||||
signature: IrSimpleFunction?
|
||||
): ValueReturning = when {
|
||||
type.isUnit() -> VoidReturning
|
||||
else -> mapType(type, retained = signature?.returnsRetained() ?: false, variadic = false, location = location)
|
||||
}
|
||||
|
||||
private fun KotlinStubs.mapBlockType(
|
||||
type: IrType,
|
||||
retained: Boolean,
|
||||
location: TypeLocation
|
||||
): ObjCBlockPointerValuePassing {
|
||||
type as IrSimpleType
|
||||
require(type.classifier == symbols.functions[type.arguments.size - 1])
|
||||
val returnTypeArgument = type.arguments.last()
|
||||
val valueReturning = when (returnTypeArgument) {
|
||||
is IrTypeProjection -> if (returnTypeArgument.variance == Variance.INVARIANT) {
|
||||
mapReturnType(returnTypeArgument.type, TypeLocation.BlockReturnValue(location), null)
|
||||
} else {
|
||||
reportUnsupportedType("${returnTypeArgument.variance.label}-variance of return type", type, location)
|
||||
}
|
||||
is IrStarProjection -> reportUnsupportedType("* as return type", type, location)
|
||||
else -> error(returnTypeArgument)
|
||||
}
|
||||
val parameterValuePassings = type.arguments.dropLast(1).mapIndexed { index, argument ->
|
||||
when (argument) {
|
||||
is IrTypeProjection -> if (argument.variance == Variance.INVARIANT) {
|
||||
mapType(
|
||||
argument.type,
|
||||
retained = false,
|
||||
variadic = false,
|
||||
location = TypeLocation.BlockParameter(index, location)
|
||||
)
|
||||
} else {
|
||||
reportUnsupportedType("${argument.variance.label}-variance of ${index + 1} parameter type", type, location)
|
||||
}
|
||||
is IrStarProjection -> reportUnsupportedType("* as ${index + 1} parameter type", type, location)
|
||||
else -> error(argument)
|
||||
}
|
||||
}
|
||||
return ObjCBlockPointerValuePassing(
|
||||
this,
|
||||
location.element,
|
||||
type,
|
||||
valueReturning,
|
||||
parameterValuePassings,
|
||||
retained
|
||||
)
|
||||
}
|
||||
|
||||
private fun KotlinStubs.mapType(type: IrType, location: TypeLocation): ValuePassing =
|
||||
mapType(type, { reportUnsupportedType(it, type, location) })
|
||||
private fun KotlinStubs.mapType(type: IrType, retained: Boolean, variadic: Boolean, location: TypeLocation): ValuePassing =
|
||||
mapType(type, retained, variadic, location, { reportUnsupportedType(it, type, location) })
|
||||
|
||||
private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) -> Nothing): ValuePassing = when {
|
||||
private fun KotlinStubs.mapType(
|
||||
type: IrType,
|
||||
retained: Boolean,
|
||||
variadic: Boolean,
|
||||
typeLocation: TypeLocation,
|
||||
reportUnsupportedType: (String) -> Nothing
|
||||
): ValuePassing = when {
|
||||
type.isBoolean() -> BooleanValuePassing(
|
||||
cBoolType(target) ?: reportUnsupportedType("unavailable on target platform"),
|
||||
irBuiltIns
|
||||
@@ -486,7 +662,11 @@ private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) ->
|
||||
.filterIsInstance<IrProperty>()
|
||||
.single { it.name.asString() == "value" }
|
||||
|
||||
CEnumValuePassing(enumClass, value, mapType(value.getter!!.returnType, reportUnsupportedType) as SimpleValuePassing)
|
||||
CEnumValuePassing(
|
||||
enumClass,
|
||||
value,
|
||||
mapType(value.getter!!.returnType, retained, variadic, typeLocation) as SimpleValuePassing
|
||||
)
|
||||
}
|
||||
|
||||
type.classifierOrNull == symbols.interopCValue -> if (type.isNullable()) {
|
||||
@@ -499,8 +679,13 @@ private fun KotlinStubs.mapType(type: IrType, reportUnsupportedType: (String) ->
|
||||
?: reportUnsupportedType("not a structure or too complex"))
|
||||
}
|
||||
|
||||
type.isFunction() -> reportUnsupportedType("")
|
||||
isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type, retained = false)
|
||||
type.isFunction() -> if (variadic){
|
||||
reportUnsupportedType("not supported as variadic argument")
|
||||
} else {
|
||||
mapBlockType(type, retained = retained, location = typeLocation)
|
||||
}
|
||||
|
||||
isObjCReferenceType(type) -> ObjCReferenceValuePassing(symbols, type, retained = retained)
|
||||
|
||||
else -> reportUnsupportedType("doesn't correspond to any C type")
|
||||
}
|
||||
@@ -532,6 +717,8 @@ private interface KotlinToCArgumentPassing {
|
||||
}
|
||||
|
||||
private interface ValueReturning {
|
||||
val cType: CType
|
||||
|
||||
fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression
|
||||
fun CCallbackBuilder.returnValue(expression: IrExpression)
|
||||
}
|
||||
@@ -543,7 +730,8 @@ private interface ValuePassing : KotlinToCArgumentPassing, ValueReturning {
|
||||
private abstract class SimpleValuePassing : ValuePassing {
|
||||
abstract val kotlinBridgeType: IrType
|
||||
abstract val cBridgeType: CType
|
||||
abstract val cType: CType
|
||||
override abstract val cType: CType
|
||||
open val callbackParameterCType get() = cType
|
||||
|
||||
abstract fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression
|
||||
open fun IrBuilderWithScope.kotlinCallbackResultToBridged(expression: IrExpression): IrExpression =
|
||||
@@ -568,7 +756,7 @@ private abstract class SimpleValuePassing : ValuePassing {
|
||||
}
|
||||
|
||||
override fun CCallbackBuilder.receiveValue(): IrExpression {
|
||||
val cParameter = cFunctionBuilder.addParameter(cType)
|
||||
val cParameter = cFunctionBuilder.addParameter(callbackParameterCType)
|
||||
val cBridgeArgument = cToBridged(cParameter.name)
|
||||
val kotlinParameter = passThroughBridge(cBridgeArgument, cBridgeType, kotlinBridgeType)
|
||||
return with(bridgeBuilder.kotlinIrBuilder) {
|
||||
@@ -639,7 +827,7 @@ private class BooleanValuePassing(override val cType: CType, private val irBuilt
|
||||
override fun cToBridged(expression: String): String = cBridgeType.cast(expression)
|
||||
}
|
||||
|
||||
private class StructValuePassing(private val kotlinClass: IrClass, private val cType: CType) : ValuePassing {
|
||||
private class StructValuePassing(private val kotlinClass: IrClass, override val cType: CType) : ValuePassing {
|
||||
override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression {
|
||||
val cBridgeValue = passThroughBridge(
|
||||
cValuesRefToPointer(expression),
|
||||
@@ -783,30 +971,282 @@ private class ObjCReferenceValuePassing(
|
||||
// TODO: optimize by using specialized Kotlin-to-ObjC converter.
|
||||
}
|
||||
|
||||
override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression {
|
||||
fun wrapObjCPtr(ptr: IrExpression) = irCall(symbols.interopInterpretObjCPointerOrNull, listOf(type)).apply {
|
||||
putValueArgument(0, ptr)
|
||||
}
|
||||
|
||||
return if (retained) {
|
||||
irBlock(startOffset, endOffset) {
|
||||
val ptrVar = irTemporary(expression)
|
||||
val resultVar = irTemporary(wrapObjCPtr(irGet(ptrVar)))
|
||||
+irCall(symbols.interopObjCRelease.owner).apply {
|
||||
putValueArgument(0, irGet(ptrVar))
|
||||
override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression =
|
||||
convertPossiblyRetainedObjCPointer(symbols, retained, expression) {
|
||||
irCall(symbols.interopInterpretObjCPointerOrNull, listOf(type)).apply {
|
||||
putValueArgument(0, it)
|
||||
}
|
||||
+irGet(resultVar)
|
||||
}
|
||||
} else {
|
||||
wrapObjCPtr(expression)
|
||||
}
|
||||
}
|
||||
|
||||
override fun bridgedToC(expression: String): String = expression
|
||||
override fun cToBridged(expression: String): String = expression
|
||||
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.convertPossiblyRetainedObjCPointer(
|
||||
symbols: KonanSymbols,
|
||||
retained: Boolean,
|
||||
pointer: IrExpression,
|
||||
convert: (IrExpression) -> IrExpression
|
||||
): IrExpression = if (retained) {
|
||||
irBlock(startOffset, endOffset) {
|
||||
val ptrVar = irTemporary(pointer)
|
||||
val resultVar = irTemporary(convert(irGet(ptrVar)))
|
||||
+irCall(symbols.interopObjCRelease.owner).apply {
|
||||
putValueArgument(0, irGet(ptrVar))
|
||||
}
|
||||
+irGet(resultVar)
|
||||
}
|
||||
} else {
|
||||
convert(pointer)
|
||||
}
|
||||
|
||||
private class ObjCBlockPointerValuePassing(
|
||||
val stubs: KotlinStubs,
|
||||
private val location: IrElement,
|
||||
private val functionType: IrSimpleType,
|
||||
private val valueReturning: ValueReturning,
|
||||
private val parameterValuePassings: List<ValuePassing>,
|
||||
private val retained: Boolean
|
||||
) : SimpleValuePassing() {
|
||||
val symbols get() = stubs.symbols
|
||||
|
||||
override val kotlinBridgeType: IrType
|
||||
get() = symbols.nativePtrType
|
||||
override val cBridgeType: CType
|
||||
get() = CTypes.id
|
||||
override val cType: CType
|
||||
get() = CTypes.id
|
||||
|
||||
/**
|
||||
* Callback can receive stack-allocated block. Using block type for parameter and passing it as `id` to the bridge
|
||||
* makes Objective-C compiler generate proper copying to heap.
|
||||
*/
|
||||
override val callbackParameterCType: CType
|
||||
get() = CTypes.blockPointer(
|
||||
CTypes.function(
|
||||
valueReturning.cType,
|
||||
parameterValuePassings.map { it.cType },
|
||||
variadic = false
|
||||
))
|
||||
|
||||
override fun IrBuilderWithScope.kotlinToBridged(expression: IrExpression): IrExpression =
|
||||
irCall(symbols.interopCreateKotlinObjectHolder.owner).apply {
|
||||
putValueArgument(0, expression)
|
||||
}
|
||||
|
||||
override fun IrBuilderWithScope.bridgedToKotlin(expression: IrExpression, symbols: KonanSymbols): IrExpression =
|
||||
irLetS(expression) { blockPointerVarSymbol ->
|
||||
val blockPointerVar = blockPointerVarSymbol.owner
|
||||
irIfThenElse(
|
||||
functionType.makeNullable(),
|
||||
condition = irCall(symbols.areEqualByValue.getValue(PrimitiveBinaryType.POINTER).owner).apply {
|
||||
putValueArgument(0, irGet(blockPointerVar))
|
||||
putValueArgument(1, irNullNativePtr(symbols))
|
||||
},
|
||||
thenPart = irNull(),
|
||||
elsePart = convertPossiblyRetainedObjCPointer(symbols, retained, irGet(blockPointerVar)) {
|
||||
createKotlinFunctionObject(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
private object OBJC_BLOCK_FUNCTION_IMPL : IrDeclarationOriginImpl("OBJC_BLOCK_FUNCTION_IMPL")
|
||||
|
||||
private fun IrBuilderWithScope.createKotlinFunctionObject(blockPointer: IrExpression): IrExpression {
|
||||
val constructor = generateKotlinFunctionClass()
|
||||
return irCall(constructor).apply {
|
||||
putValueArgument(0, blockPointer)
|
||||
}
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.generateKotlinFunctionClass(): IrConstructorImpl {
|
||||
val symbols = stubs.symbols
|
||||
|
||||
val classDescriptor = WrappedClassDescriptor()
|
||||
val irClass = IrClassImpl(
|
||||
startOffset, endOffset,
|
||||
OBJC_BLOCK_FUNCTION_IMPL, IrClassSymbolImpl(classDescriptor),
|
||||
Name.identifier(stubs.getUniqueKotlinFunctionReferenceClassName("BlockFunctionImpl")),
|
||||
ClassKind.CLASS, Visibilities.PRIVATE, Modality.FINAL,
|
||||
isCompanion = false, isInner = false, isData = false, isExternal = false, isInline = false
|
||||
)
|
||||
classDescriptor.bind(irClass)
|
||||
irClass.createParameterDeclarations()
|
||||
|
||||
irClass.superTypes += stubs.irBuiltIns.anyType
|
||||
irClass.superTypes += functionType.makeNotNull()
|
||||
|
||||
val blockHolderField = createField(
|
||||
startOffset, endOffset,
|
||||
OBJC_BLOCK_FUNCTION_IMPL,
|
||||
stubs.irBuiltIns.anyType,
|
||||
Name.identifier("blockHolder"),
|
||||
isMutable = false, owner = irClass
|
||||
)
|
||||
irClass.addChild(blockHolderField)
|
||||
|
||||
val constructorDescriptor = WrappedClassConstructorDescriptor()
|
||||
val constructor = IrConstructorImpl(
|
||||
startOffset, endOffset,
|
||||
OBJC_BLOCK_FUNCTION_IMPL,
|
||||
IrConstructorSymbolImpl(constructorDescriptor),
|
||||
Name.special("<init>"),
|
||||
Visibilities.PUBLIC,
|
||||
irClass.defaultType,
|
||||
isInline = false, isExternal = false, isPrimary = true
|
||||
)
|
||||
constructorDescriptor.bind(constructor)
|
||||
irClass.addChild(constructor)
|
||||
|
||||
val constructorParameterDescriptor = WrappedValueParameterDescriptor()
|
||||
val constructorParameter = IrValueParameterImpl(
|
||||
startOffset, endOffset,
|
||||
OBJC_BLOCK_FUNCTION_IMPL,
|
||||
IrValueParameterSymbolImpl(constructorParameterDescriptor),
|
||||
Name.identifier("blockPointer"),
|
||||
0,
|
||||
symbols.nativePtrType,
|
||||
varargElementType = null, isCrossinline = false, isNoinline = false
|
||||
)
|
||||
constructorParameterDescriptor.bind(constructorParameter)
|
||||
constructor.valueParameters += constructorParameter
|
||||
constructorParameter.parent = constructor
|
||||
|
||||
constructor.body = irBuilder(stubs.irBuiltIns, constructor.symbol).irBlockBody(startOffset, endOffset) {
|
||||
+irDelegatingConstructorCall(symbols.any.owner.constructors.single())
|
||||
+irSetField(irGet(irClass.thisReceiver!!), blockHolderField,
|
||||
irCall(symbols.interopCreateObjCObjectHolder.owner).apply {
|
||||
putValueArgument(0, irGet(constructorParameter))
|
||||
})
|
||||
}
|
||||
|
||||
val parameterCount = parameterValuePassings.size
|
||||
assert(functionType.arguments.size == parameterCount + 1)
|
||||
|
||||
val overriddenInvokeMethod = (functionType.classifier.owner as IrClass).simpleFunctions()
|
||||
.single { it.name == OperatorNameConventions.INVOKE }
|
||||
|
||||
val invokeMethodDescriptor = WrappedSimpleFunctionDescriptor()
|
||||
val invokeMethod = IrFunctionImpl(
|
||||
startOffset, endOffset,
|
||||
OBJC_BLOCK_FUNCTION_IMPL,
|
||||
IrSimpleFunctionSymbolImpl(invokeMethodDescriptor),
|
||||
overriddenInvokeMethod.name,
|
||||
Visibilities.PUBLIC, Modality.FINAL,
|
||||
returnType = functionType.arguments.last().typeOrNull!!,
|
||||
isInline = false, isExternal = false, isTailrec = false, isSuspend = false
|
||||
)
|
||||
invokeMethodDescriptor.bind(invokeMethod)
|
||||
invokeMethod.overriddenSymbols += overriddenInvokeMethod.symbol
|
||||
irClass.addChild(invokeMethod)
|
||||
invokeMethod.createDispatchReceiverParameter()
|
||||
|
||||
(0 until parameterCount).mapTo(invokeMethod.valueParameters) { index ->
|
||||
val parameterDescriptor = WrappedValueParameterDescriptor()
|
||||
val parameter = IrValueParameterImpl(
|
||||
startOffset, endOffset,
|
||||
OBJC_BLOCK_FUNCTION_IMPL,
|
||||
IrValueParameterSymbolImpl(parameterDescriptor),
|
||||
Name.identifier("p$index"),
|
||||
index,
|
||||
functionType.arguments[index].typeOrNull!!,
|
||||
varargElementType = null, isCrossinline = false, isNoinline = false
|
||||
)
|
||||
parameterDescriptor.bind(parameter)
|
||||
parameter.parent = invokeMethod
|
||||
parameter
|
||||
}
|
||||
|
||||
invokeMethod.body = irBuilder(stubs.irBuiltIns, invokeMethod.symbol).irBlockBody(startOffset, endOffset) {
|
||||
val blockPointer = irCall(symbols.interopObjCObjectRawValueGetter.owner).apply {
|
||||
extensionReceiver = irGetField(irGet(invokeMethod.dispatchReceiverParameter!!), blockHolderField)
|
||||
}
|
||||
|
||||
val arguments = (0 until parameterCount).map { index ->
|
||||
irGet(invokeMethod.valueParameters[index])
|
||||
}
|
||||
|
||||
+irReturn(callBlock(blockPointer, arguments))
|
||||
}
|
||||
|
||||
irClass.addFakeOverrides()
|
||||
|
||||
stubs.addKotlin(irClass)
|
||||
return constructor
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.callBlock(blockPtr: IrExpression, arguments: List<IrExpression>): IrExpression {
|
||||
val callBuilder = KotlinToCCallBuilder(this, stubs)
|
||||
|
||||
val rawBlockPointerParameter = callBuilder.passThroughBridge(blockPtr, blockPtr.type, CTypes.id)
|
||||
val blockVariableName = "block"
|
||||
|
||||
arguments.forEachIndexed { index, argument ->
|
||||
callBuilder.addArgument(argument, parameterValuePassings[index], variadic = false)
|
||||
}
|
||||
|
||||
val result = callBuilder.buildCall(blockVariableName, valueReturning)
|
||||
|
||||
val blockVariableType = CTypes.blockPointer(callBuilder.cFunctionBuilder.getType())
|
||||
val blockVariable = CVariable(blockVariableType, blockVariableName)
|
||||
callBuilder.cBridgeBodyLines.add(0, "$blockVariable = ${rawBlockPointerParameter.name};")
|
||||
|
||||
callBuilder.emitCBridge()
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
override fun bridgedToC(expression: String): String {
|
||||
val callbackBuilder = CCallbackBuilder(stubs, location, isObjCMethod = false)
|
||||
val kotlinFunctionHolder = "kotlinFunctionHolder"
|
||||
|
||||
callbackBuilder.cBridgeCallBuilder.arguments += kotlinFunctionHolder
|
||||
val (kotlinFunctionHolderParameter, _) =
|
||||
callbackBuilder.bridgeBuilder.addParameter(symbols.nativePtrType, CTypes.id)
|
||||
|
||||
callbackBuilder.kotlinCallBuilder.arguments += with(callbackBuilder.bridgeBuilder.kotlinIrBuilder) {
|
||||
// TODO: consider casting to [functionType].
|
||||
irCall(symbols.interopUnwrapKotlinObjectHolderImpl.owner).apply {
|
||||
putValueArgument(0, irGet(kotlinFunctionHolderParameter) )
|
||||
}
|
||||
}
|
||||
|
||||
parameterValuePassings.forEach {
|
||||
callbackBuilder.kotlinCallBuilder.arguments += with(it) {
|
||||
callbackBuilder.receiveValue()
|
||||
}
|
||||
}
|
||||
|
||||
assert(functionType.isFunction())
|
||||
val invokeFunction = (functionType.classifier.owner as IrClass)
|
||||
.simpleFunctions().single { it.name == OperatorNameConventions.INVOKE }
|
||||
|
||||
callbackBuilder.buildValueReturn(invokeFunction, valueReturning)
|
||||
|
||||
val block = buildString {
|
||||
append('^')
|
||||
append(callbackBuilder.cFunctionBuilder.buildSignature(""))
|
||||
append(" { ")
|
||||
callbackBuilder.cBodyLines.forEach {
|
||||
append(it)
|
||||
append(' ')
|
||||
}
|
||||
append(" }")
|
||||
}
|
||||
val blockAsId = if (retained) {
|
||||
"(__bridge id)(__bridge_retained void*)$block" // Retain and convert to id.
|
||||
} else {
|
||||
"(id)$block"
|
||||
}
|
||||
|
||||
return "({ id $kotlinFunctionHolder = $expression; $kotlinFunctionHolder ? $blockAsId : (id)0; })"
|
||||
}
|
||||
|
||||
override fun cToBridged(expression: String) = expression
|
||||
|
||||
}
|
||||
|
||||
private class WCStringArgumentPassing : KotlinToCArgumentPassing {
|
||||
|
||||
override fun KotlinToCCallBuilder.passValue(expression: IrExpression): CExpression {
|
||||
@@ -869,6 +1309,9 @@ private fun KotlinToCCallBuilder.cValuesRefToPointer(
|
||||
}
|
||||
|
||||
private object VoidReturning : ValueReturning {
|
||||
override val cType: CType
|
||||
get() = CTypes.void
|
||||
|
||||
override fun KotlinToCCallBuilder.returnValue(expression: String): IrExpression {
|
||||
bridgeBuilder.setReturnType(irBuilder.context.irBuiltIns.unitType, CTypes.void)
|
||||
cFunctionBuilder.setReturnType(CTypes.void)
|
||||
@@ -887,15 +1330,20 @@ private object VoidReturning : ValueReturning {
|
||||
internal fun CType.cast(expression: String): String = "((${this.render("")})$expression)"
|
||||
|
||||
private fun KotlinStubs.reportUnsupportedType(reason: String, type: IrType, location: TypeLocation): Nothing {
|
||||
val typeLocation: String = when (location) {
|
||||
// TODO: report errors in frontend instead.
|
||||
fun TypeLocation.render(): String = when (this) {
|
||||
is TypeLocation.FunctionArgument -> ""
|
||||
is TypeLocation.FunctionCallResult -> " of return value"
|
||||
is TypeLocation.FunctionPointerParameter -> " of callback parameter ${location.index + 1}"
|
||||
is TypeLocation.FunctionPointerParameter -> " of callback parameter ${index + 1}"
|
||||
is TypeLocation.FunctionPointerReturnValue -> " of callback return value"
|
||||
is TypeLocation.ObjCMethodParameter -> " of overridden Objective-C method parameter"
|
||||
is TypeLocation.ObjCMethodReturnValue -> " of overridden Objective-C method return value"
|
||||
is TypeLocation.BlockParameter -> " of ${index + 1} parameter in Objective-C block type${blockLocation.render()}"
|
||||
is TypeLocation.BlockReturnValue -> " of return value of Objective-C block type${blockLocation.render()}"
|
||||
}
|
||||
|
||||
val typeLocation: String = location.render()
|
||||
|
||||
reportError(location.element, "type ${type.toKotlinType()}$typeLocation is not supported here" +
|
||||
if (reason.isNotEmpty()) ": $reason" else "")
|
||||
}
|
||||
|
||||
+4
@@ -127,6 +127,10 @@ private fun createKotlinBridge(
|
||||
isSuspend = false
|
||||
)
|
||||
bridgeDescriptor.bind(bridge)
|
||||
if (isExternal) {
|
||||
bridge.annotations +=
|
||||
irCall(startOffset, endOffset, symbols.filterExceptions.owner.constructors.single(), emptyList())
|
||||
}
|
||||
return bridge
|
||||
}
|
||||
|
||||
|
||||
+6
@@ -14,6 +14,10 @@ internal object CTypes {
|
||||
fun function(returnType: CType, parameterTypes: List<CType>, variadic: Boolean): CType =
|
||||
FunctionCType(returnType, parameterTypes, variadic)
|
||||
|
||||
fun blockPointer(pointee: CType): CType = object : CType {
|
||||
override fun render(name: String): String = pointee.render("^$name")
|
||||
}
|
||||
|
||||
val void = simple("void")
|
||||
val voidPtr = pointer(void)
|
||||
val signedChar = simple("signed char")
|
||||
@@ -28,6 +32,8 @@ internal object CTypes {
|
||||
val double = simple("double")
|
||||
val C99Bool = simple("_Bool")
|
||||
val char = simple("char")
|
||||
|
||||
val id = simple("id")
|
||||
}
|
||||
|
||||
private class SimpleCType(private val type: String) : CType {
|
||||
|
||||
+11
@@ -184,6 +184,7 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
|
||||
val arrayList = symbolTable.referenceClass(getArrayListClassDescriptor(context))
|
||||
|
||||
val symbolName = topLevelClass(RuntimeNames.symbolName)
|
||||
val filterExceptions = topLevelClass(RuntimeNames.filterExceptions)
|
||||
val exportForCppRuntime = topLevelClass(RuntimeNames.exportForCppRuntime)
|
||||
|
||||
val objCMethodImp = symbolTable.referenceClass(context.interopBuiltIns.objCMethodImp)
|
||||
@@ -218,6 +219,16 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable, val
|
||||
|
||||
val interopObjcRetainAutoreleaseReturnValue = interopFunction("objc_retainAutoreleaseReturnValue")
|
||||
|
||||
val interopCreateObjCObjectHolder = interopFunction("createObjCObjectHolder")
|
||||
|
||||
val interopCreateKotlinObjectHolder = interopFunction("createKotlinObjectHolder")
|
||||
val interopUnwrapKotlinObjectHolderImpl = interopFunction("unwrapKotlinObjectHolderImpl")
|
||||
|
||||
val interopCreateObjCSuperStruct = interopFunction("createObjCSuperStruct")
|
||||
|
||||
val interopGetMessenger = interopFunction("getMessenger")
|
||||
val interopGetMessengerStret = interopFunction("getMessengerStret")
|
||||
|
||||
val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass)
|
||||
|
||||
val interopObjCObjectSuperInitCheck =
|
||||
|
||||
+59
-7
@@ -531,6 +531,53 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
return LLVMBuildLandingPad(builder, landingpadType, personalityFunction, numClauses, name)!!
|
||||
}
|
||||
|
||||
fun filteringExceptionHandler(codeContext: CodeContext): ExceptionHandler {
|
||||
val lpBlock = basicBlockInFunction("filteringExceptionHandler", position())
|
||||
|
||||
appendingTo(lpBlock) {
|
||||
val landingpad = gxxLandingpad(2)
|
||||
LLVMAddClause(landingpad, kotlinExceptionRtti.llvm)
|
||||
LLVMAddClause(landingpad, LLVMConstNull(kInt8Ptr))
|
||||
|
||||
val fatalForeignExceptionBlock = basicBlock("fatalForeignException", position())
|
||||
val forwardKotlinExceptionBlock = basicBlock("forwardKotlinException", position())
|
||||
|
||||
val isKotlinException = icmpEq(
|
||||
extractValue(landingpad, 1),
|
||||
call(context.llvm.llvmEhTypeidFor, listOf(kotlinExceptionRtti.llvm))
|
||||
)
|
||||
|
||||
condBr(isKotlinException, forwardKotlinExceptionBlock, fatalForeignExceptionBlock)
|
||||
|
||||
appendingTo(forwardKotlinExceptionBlock) {
|
||||
// Rethrow Kotlin exception to real handler.
|
||||
codeContext.genThrow(extractKotlinException(landingpad))
|
||||
}
|
||||
|
||||
appendingTo(fatalForeignExceptionBlock) {
|
||||
val exceptionRecord = extractValue(landingpad, 0)
|
||||
call(context.llvm.cxaBeginCatchFunction, listOf(exceptionRecord))
|
||||
terminate()
|
||||
}
|
||||
}
|
||||
|
||||
return object : ExceptionHandler.Local() {
|
||||
override val unwind: LLVMBasicBlockRef
|
||||
get() = lpBlock
|
||||
}
|
||||
}
|
||||
|
||||
fun terminate() {
|
||||
call(context.llvm.cxxStdTerminate, emptyList())
|
||||
|
||||
// Note: unreachable instruction to be generated here, but debug information is improper in this case.
|
||||
val loopBlock = basicBlock("loop", position())
|
||||
br(loopBlock)
|
||||
appendingTo(loopBlock) {
|
||||
br(loopBlock)
|
||||
}
|
||||
}
|
||||
|
||||
fun kotlinExceptionHandler(
|
||||
block: FunctionGenerationContext.(exception: LLVMValueRef) -> Unit
|
||||
): ExceptionHandler {
|
||||
@@ -554,6 +601,10 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
// FIXME: properly handle C++ exceptions: currently C++ exception can be thrown out from try-finally
|
||||
// bypassing the finally block.
|
||||
|
||||
return extractKotlinException(landingpadResult)
|
||||
}
|
||||
|
||||
private fun extractKotlinException(landingpadResult: LLVMValueRef): LLVMValueRef {
|
||||
val exceptionRecord = extractValue(landingpadResult, 0, "er")
|
||||
|
||||
// __cxa_begin_catch returns pointer to C++ exception object.
|
||||
@@ -908,14 +959,8 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
LLVMSetCleanup(landingpad, 1)
|
||||
|
||||
forwardingForeignExceptionsTerminatedWith?.let { terminator ->
|
||||
val kotlinExceptionRtti = constPointer(importGlobal(
|
||||
"_ZTI9ObjHolder", // typeinfo for ObjHolder
|
||||
int8TypePtr,
|
||||
origin = context.stdlibModule.llvmSymbolOrigin
|
||||
))
|
||||
|
||||
// Catch all but Kotlin exceptions.
|
||||
val clause = ConstArray(int8TypePtr, listOf(kotlinExceptionRtti.bitcast(int8TypePtr)))
|
||||
val clause = ConstArray(int8TypePtr, listOf(kotlinExceptionRtti))
|
||||
LLVMAddClause(landingpad, clause.llvm)
|
||||
|
||||
val bbCleanup = basicBlock("forwardException", null)
|
||||
@@ -951,6 +996,13 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
slotsPhi = null
|
||||
}
|
||||
|
||||
private val kotlinExceptionRtti: ConstPointer
|
||||
get() = constPointer(importGlobal(
|
||||
"_ZTI9ObjHolder", // typeinfo for ObjHolder
|
||||
int8TypePtr,
|
||||
origin = context.stdlibModule.llvmSymbolOrigin
|
||||
)).bitcast(int8TypePtr)
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
/**
|
||||
|
||||
+12
@@ -465,6 +465,12 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
||||
else -> "__gxx_personality_v0"
|
||||
}
|
||||
|
||||
val cxxStdTerminate = externalNounwindFunction(
|
||||
"_ZSt9terminatev", // mangled C++ 'std::terminate'
|
||||
functionType(voidType, false),
|
||||
origin = context.standardLlvmSymbolsOrigin
|
||||
)
|
||||
|
||||
val gxxPersonalityFunction = externalNounwindFunction(
|
||||
personalityFunctionName,
|
||||
functionType(int32Type, true),
|
||||
@@ -490,6 +496,12 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
||||
"cold", "noreturn", "nounwind"
|
||||
)
|
||||
|
||||
val llvmEhTypeidFor = llvmIntrinsic(
|
||||
"llvm.eh.typeid.for",
|
||||
functionType(int32Type, false, int8TypePtr),
|
||||
"nounwind", "readnone"
|
||||
)
|
||||
|
||||
val usedFunctions = mutableListOf<LLVMValueRef>()
|
||||
val usedGlobals = mutableListOf<LLVMValueRef>()
|
||||
val compilerUsedGlobals = mutableListOf<LLVMValueRef>()
|
||||
|
||||
+8
-12
@@ -53,7 +53,7 @@ internal enum class IntrinsicType {
|
||||
OBJC_GET_MESSENGER,
|
||||
OBJC_GET_MESSENGER_STRET,
|
||||
OBJC_GET_OBJC_CLASS,
|
||||
OBJC_GET_RECEIVER_OR_SUPER,
|
||||
OBJC_CREATE_SUPER_STRUCT,
|
||||
OBJC_INIT_BY,
|
||||
OBJC_GET_SELECTOR,
|
||||
// Other
|
||||
@@ -224,7 +224,7 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
|
||||
IntrinsicType.OBJC_GET_MESSENGER -> emitObjCGetMessenger(args, isStret = false)
|
||||
IntrinsicType.OBJC_GET_MESSENGER_STRET -> emitObjCGetMessenger(args, isStret = true)
|
||||
IntrinsicType.OBJC_GET_OBJC_CLASS -> emitGetObjCClass(callSite)
|
||||
IntrinsicType.OBJC_GET_RECEIVER_OR_SUPER -> emitGetReceiverOrSuper(args)
|
||||
IntrinsicType.OBJC_CREATE_SUPER_STRUCT -> emitObjCCreateSuperStruct(args)
|
||||
IntrinsicType.GET_CLASS_TYPE_INFO -> emitGetClassTypeInfo(callSite)
|
||||
IntrinsicType.INTEROP_READ_BITS -> emitReadBits(args)
|
||||
IntrinsicType.INTEROP_WRITE_BITS -> emitWriteBits(args)
|
||||
@@ -433,20 +433,16 @@ internal class IntrinsicGenerator(private val environment: IntrinsicGeneratorEnv
|
||||
}
|
||||
}
|
||||
|
||||
private fun FunctionGenerationContext.emitGetReceiverOrSuper(args: List<LLVMValueRef>): LLVMValueRef {
|
||||
private fun FunctionGenerationContext.emitObjCCreateSuperStruct(args: List<LLVMValueRef>): LLVMValueRef {
|
||||
assert(args.size == 2)
|
||||
val receiver = args[0]
|
||||
val superClass = args[1]
|
||||
|
||||
val superClassIsNull = icmpEq(superClass, kNullInt8Ptr)
|
||||
|
||||
return ifThenElse(superClassIsNull, receiver) {
|
||||
val structType = structType(kInt8Ptr, kInt8Ptr)
|
||||
val ptr = alloca(structType)
|
||||
store(receiver, LLVMBuildGEP(builder, ptr, cValuesOf(kImmZero, kImmZero), 2, "")!!)
|
||||
store(superClass, LLVMBuildGEP(builder, ptr, cValuesOf(kImmZero, kImmOne), 2, "")!!)
|
||||
bitcast(int8TypePtr, ptr)
|
||||
}
|
||||
val structType = structType(kInt8Ptr, kInt8Ptr)
|
||||
val ptr = alloca(structType)
|
||||
store(receiver, LLVMBuildGEP(builder, ptr, cValuesOf(kImmZero, kImmZero), 2, "")!!)
|
||||
store(superClass, LLVMBuildGEP(builder, ptr, cValuesOf(kImmZero, kImmOne), 2, "")!!)
|
||||
return bitcast(int8TypePtr, ptr)
|
||||
}
|
||||
|
||||
// TODO: Find better place for these guys.
|
||||
|
||||
+10
-3
@@ -2147,7 +2147,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
|
||||
private fun call(function: IrFunction, llvmFunction: LLVMValueRef, args: List<LLVMValueRef>,
|
||||
resultLifetime: Lifetime): LLVMValueRef {
|
||||
val result = call(llvmFunction, args, resultLifetime)
|
||||
val exceptionHandler = if (function.hasAnnotation(RuntimeNames.filterExceptions)) {
|
||||
functionGenerationContext.filteringExceptionHandler(currentCodeContext)
|
||||
} else {
|
||||
currentCodeContext.exceptionHandler
|
||||
}
|
||||
|
||||
val result = call(llvmFunction, args, resultLifetime, exceptionHandler)
|
||||
if (!function.isSuspend && function.returnType.isNothing()) {
|
||||
functionGenerationContext.unreachable()
|
||||
}
|
||||
@@ -2175,8 +2181,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
}
|
||||
|
||||
private fun call(function: LLVMValueRef, args: List<LLVMValueRef>,
|
||||
resultLifetime: Lifetime = Lifetime.IRRELEVANT): LLVMValueRef {
|
||||
return functionGenerationContext.call(function, args, resultLifetime, currentCodeContext.exceptionHandler)
|
||||
resultLifetime: Lifetime = Lifetime.IRRELEVANT,
|
||||
exceptionHandler: ExceptionHandler = currentCodeContext.exceptionHandler): LLVMValueRef {
|
||||
return functionGenerationContext.call(function, args, resultLifetime, exceptionHandler)
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
+1
-20
@@ -14,7 +14,6 @@ import org.jetbrains.kotlin.backend.konan.ir.*
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
|
||||
import org.jetbrains.kotlin.ir.util.constructors
|
||||
import org.jetbrains.kotlin.ir.util.simpleFunctions
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
|
||||
internal class KotlinObjCClassInfoGenerator(override val context: Context) : ContextUtils {
|
||||
@@ -70,8 +69,7 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con
|
||||
objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0))
|
||||
}
|
||||
|
||||
private fun IrClass.generateMethodDescs(): List<ObjCMethodDesc> =
|
||||
this.generateOverridingMethodDescs() + this.generateImpMethodDescs()
|
||||
private fun IrClass.generateMethodDescs(): List<ObjCMethodDesc> = this.generateImpMethodDescs()
|
||||
|
||||
private fun generateInstanceMethodDescs(
|
||||
irClass: IrClass
|
||||
@@ -113,23 +111,6 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con
|
||||
staticData.cStringLiteral(encoding)
|
||||
)
|
||||
|
||||
private fun generateMethodDesc(info: ObjCMethodInfo) = info.imp?.let { imp ->
|
||||
ObjCMethodDesc(
|
||||
info.selector,
|
||||
info.encoding,
|
||||
context.llvm.externalFunction(
|
||||
imp,
|
||||
functionType(voidType),
|
||||
origin = info.bridge.llvmSymbolOrigin,
|
||||
independent = true
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun IrClass.generateOverridingMethodDescs(): List<ObjCMethodDesc> =
|
||||
this.simpleFunctions().filter { it.isReal }
|
||||
.mapNotNull { it.getObjCMethodInfo() }.mapNotNull { generateMethodDesc(it) }
|
||||
|
||||
private fun IrClass.generateImpMethodDescs(): List<ObjCMethodDesc> = this.declarations
|
||||
.filterIsInstance<IrSimpleFunction>()
|
||||
.mapNotNull {
|
||||
|
||||
+99
-69
@@ -11,10 +11,7 @@ import org.jetbrains.kotlin.backend.common.peek
|
||||
import org.jetbrains.kotlin.backend.common.pop
|
||||
import org.jetbrains.kotlin.backend.common.push
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.KotlinStubs
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.generateCCall
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.generateCFunctionAndFakeKotlinExternalFunction
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.generateCFunctionPointer
|
||||
import org.jetbrains.kotlin.backend.konan.cgen.*
|
||||
import org.jetbrains.kotlin.backend.konan.getInlinedClass
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenFunctions
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||
@@ -28,7 +25,6 @@ import org.jetbrains.kotlin.builtins.UnsignedTypes
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.TypeParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl
|
||||
@@ -56,7 +52,48 @@ import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||
|
||||
internal class InteropLoweringPart1(val context: Context) : IrBuildingTransformer(context), FileLoweringPass {
|
||||
internal abstract class BaseInteropIrTransformer(private val context: Context) : IrBuildingTransformer(context) {
|
||||
|
||||
protected inline fun <T> generateWithStubs(element: IrElement? = null, block: KotlinStubs.() -> T): T =
|
||||
createKotlinStubs(element).block()
|
||||
|
||||
protected fun createKotlinStubs(element: IrElement?): KotlinStubs {
|
||||
val location = if (element != null) {
|
||||
element.getCompilerMessageLocation(irFile)
|
||||
} else {
|
||||
builder.getCompilerMessageLocation()
|
||||
}
|
||||
|
||||
return object : KotlinStubs {
|
||||
override val irBuiltIns get() = context.irBuiltIns
|
||||
override val symbols get() = context.ir.symbols
|
||||
|
||||
override fun addKotlin(declaration: IrDeclaration) {
|
||||
addTopLevel(declaration)
|
||||
}
|
||||
|
||||
override fun addC(lines: List<String>) {
|
||||
context.cStubsManager.addStub(location, lines)
|
||||
}
|
||||
|
||||
override fun getUniqueCName(prefix: String) =
|
||||
"_${context.moduleDescriptor.namePrefix}_${context.cStubsManager.getUniqueName(prefix)}"
|
||||
|
||||
override fun getUniqueKotlinFunctionReferenceClassName(prefix: String) =
|
||||
"$prefix${context.functionReferenceCount++}"
|
||||
|
||||
override val target get() = context.config.target
|
||||
|
||||
override fun reportError(location: IrElement, message: String): Nothing =
|
||||
context.reportCompilationError(message, irFile, location)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract val irFile: IrFile
|
||||
protected abstract fun addTopLevel(declaration: IrDeclaration)
|
||||
}
|
||||
|
||||
internal class InteropLoweringPart1(val context: Context) : BaseInteropIrTransformer(context), FileLoweringPass {
|
||||
|
||||
private val symbols get() = context.ir.symbols
|
||||
private val symbolTable get() = symbols.symbolTable
|
||||
@@ -64,6 +101,15 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
lateinit var currentFile: IrFile
|
||||
|
||||
private val topLevelInitializers = mutableListOf<IrExpression>()
|
||||
private val newTopLevelDeclarations = mutableListOf<IrDeclaration>()
|
||||
|
||||
override val irFile: IrFile
|
||||
get() = currentFile
|
||||
|
||||
override fun addTopLevel(declaration: IrDeclaration) {
|
||||
declaration.parent = currentFile
|
||||
newTopLevelDeclarations += declaration
|
||||
}
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
currentFile = irFile
|
||||
@@ -71,6 +117,9 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
|
||||
topLevelInitializers.forEach { irFile.addTopLevelInitializer(it, context, false) }
|
||||
topLevelInitializers.clear()
|
||||
|
||||
irFile.addChildren(newTopLevelDeclarations)
|
||||
newTopLevelDeclarations.clear()
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.callAlloc(classPtr: IrExpression): IrExpression =
|
||||
@@ -530,7 +579,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
|
||||
assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass)
|
||||
|
||||
val initMethod = expression.descriptor.getObjCInitMethod()!!
|
||||
val initMethod = expression.symbol.owner.getObjCInitMethod()!!
|
||||
|
||||
if (!expression.symbol.owner.objCConstructorIsDesignated()) {
|
||||
context.reportCompilationError(
|
||||
@@ -549,7 +598,9 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
initMethodInfo,
|
||||
superQualifier = symbolTable.referenceClass(expression.descriptor.constructedClass),
|
||||
receiver = builder.getRawPtr(builder.irGet(constructedClass.thisReceiver!!)),
|
||||
arguments = initMethod.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
arguments = initMethod.valueParameters.map { expression.getValueArgument(it.index)!! },
|
||||
call = expression,
|
||||
method = initMethod
|
||||
)
|
||||
|
||||
val superConstructor = symbolTable.referenceConstructor(
|
||||
@@ -577,25 +628,24 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
return expression
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.genLoweredObjCMethodCall(info: ObjCMethodInfo, superQualifier: IrClassSymbol?,
|
||||
receiver: IrExpression, arguments: List<IrExpression>): IrExpression {
|
||||
|
||||
val superClass = superQualifier?.let { getObjCClass(it) } ?:
|
||||
irCall(symbols.getNativeNullPtr, symbols.nativePtrType)
|
||||
|
||||
val bridge = symbols.lazySymbolTable.referenceSimpleFunction(info.bridge)
|
||||
return irCall(bridge, symbolTable.translateErased(info.bridge.returnType!!)).apply {
|
||||
putValueArgument(0, superClass)
|
||||
putValueArgument(1, receiver)
|
||||
putValueArgument(2, irCall(symbols.interopObjCGetSelector.owner).apply {
|
||||
putValueArgument(0, irString(info.selector))
|
||||
})
|
||||
|
||||
assert(arguments.size + 3 == info.bridge.valueParameters.size)
|
||||
arguments.forEachIndexed { index, argument ->
|
||||
putValueArgument(index + 3, argument)
|
||||
}
|
||||
}
|
||||
private fun IrBuilderWithScope.genLoweredObjCMethodCall(
|
||||
info: ObjCMethodInfo,
|
||||
superQualifier: IrClassSymbol?,
|
||||
receiver: IrExpression,
|
||||
arguments: List<IrExpression>,
|
||||
call: IrFunctionAccessExpression,
|
||||
method: IrSimpleFunction
|
||||
): IrExpression = generateWithStubs(call) {
|
||||
this.generateObjCCall(
|
||||
this@genLoweredObjCMethodCall,
|
||||
method,
|
||||
info.isStret,
|
||||
info.selector,
|
||||
call,
|
||||
superQualifier,
|
||||
receiver,
|
||||
arguments
|
||||
)
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
@@ -603,19 +653,20 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
|
||||
val descriptor = expression.descriptor.original
|
||||
|
||||
if (descriptor is ConstructorDescriptor) {
|
||||
val initMethod = descriptor.getObjCInitMethod()
|
||||
val callee = expression.symbol.owner
|
||||
if (callee is IrConstructor) {
|
||||
val initMethod = callee.getObjCInitMethod()
|
||||
|
||||
if (initMethod != null) {
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
assert(expression.extensionReceiver == null)
|
||||
assert(expression.dispatchReceiver == null)
|
||||
|
||||
val constructedClass = descriptor.constructedClass
|
||||
val constructedClass = callee.constructedClass
|
||||
val initMethodInfo = initMethod.getExternalObjCMethodInfo()!!
|
||||
return builder.at(expression).run {
|
||||
val classPtr = getObjCClass(symbolTable.referenceClass(constructedClass))
|
||||
irForceNotNull(callAllocAndInit(classPtr, initMethodInfo, arguments))
|
||||
val classPtr = getObjCClass(constructedClass.symbol)
|
||||
irForceNotNull(callAllocAndInit(classPtr, initMethodInfo, arguments, expression, initMethod))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -626,7 +677,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
|
||||
return builder.at(expression).run {
|
||||
val classPtr = getRawPtr(expression.extensionReceiver!!)
|
||||
callAllocAndInit(classPtr, initMethodInfo, arguments)
|
||||
callAllocAndInit(classPtr, initMethodInfo, arguments, expression, callee as IrSimpleFunction)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -662,7 +713,9 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
methodInfo,
|
||||
superQualifier = expression.superQualifierSymbol,
|
||||
receiver = builder.getRawPtr(expression.dispatchReceiver ?: expression.extensionReceiver!!),
|
||||
arguments = arguments
|
||||
arguments = arguments,
|
||||
call = expression,
|
||||
method = callee as IrSimpleFunction
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -722,15 +775,19 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
|
||||
private fun IrBuilderWithScope.callAllocAndInit(
|
||||
classPtr: IrExpression,
|
||||
initMethodInfo: ObjCMethodInfo,
|
||||
arguments: List<IrExpression>
|
||||
): IrExpression = irBlock {
|
||||
arguments: List<IrExpression>,
|
||||
call: IrFunctionAccessExpression,
|
||||
initMethod: IrSimpleFunction
|
||||
): IrExpression = irBlock(startOffset, endOffset) {
|
||||
val allocated = irTemporaryVar(callAlloc(classPtr))
|
||||
|
||||
val initCall = genLoweredObjCMethodCall(
|
||||
initMethodInfo,
|
||||
superQualifier = null,
|
||||
receiver = irGet(allocated),
|
||||
arguments = arguments
|
||||
arguments = arguments,
|
||||
call = call,
|
||||
method = initMethod
|
||||
)
|
||||
|
||||
+IrTryImpl(startOffset, endOffset, initCall.type).apply {
|
||||
@@ -759,43 +816,16 @@ internal class InteropLoweringPart2(val context: Context) : FileLoweringPass {
|
||||
}
|
||||
}
|
||||
|
||||
private class InteropTransformer(val context: Context, val irFile: IrFile) : IrBuildingTransformer(context) {
|
||||
private class InteropTransformer(val context: Context, override val irFile: IrFile) : BaseInteropIrTransformer(context) {
|
||||
|
||||
val newTopLevelDeclarations = mutableListOf<IrDeclaration>()
|
||||
|
||||
val interop = context.interopBuiltIns
|
||||
val symbols = context.ir.symbols
|
||||
|
||||
private inline fun <T> generateWithStubs(element: IrElement? = null, block: KotlinStubs.() -> T): T =
|
||||
createKotlinStubs(element).block()
|
||||
|
||||
private fun createKotlinStubs(element: IrElement?): KotlinStubs {
|
||||
val location = if (element != null) {
|
||||
element.getCompilerMessageLocation(irFile)
|
||||
} else {
|
||||
builder.getCompilerMessageLocation()
|
||||
}
|
||||
|
||||
return object : KotlinStubs {
|
||||
override val irBuiltIns get() = context.irBuiltIns
|
||||
override val symbols get() = context.ir.symbols
|
||||
|
||||
override fun addKotlin(declaration: IrDeclaration) {
|
||||
newTopLevelDeclarations += declaration
|
||||
}
|
||||
|
||||
override fun addC(lines: List<String>) {
|
||||
context.cStubsManager.addStub(location, lines)
|
||||
}
|
||||
|
||||
override fun getUniqueCName(prefix: String) =
|
||||
"_${context.moduleDescriptor.namePrefix}_${context.cStubsManager.getUniqueName(prefix)}"
|
||||
|
||||
override val target get() = context.config.target
|
||||
|
||||
override fun reportError(location: IrElement, message: String): Nothing =
|
||||
context.reportCompilationError(message, irFile, location)
|
||||
}
|
||||
override fun addTopLevel(declaration: IrDeclaration) {
|
||||
declaration.parent = irFile
|
||||
newTopLevelDeclarations += declaration
|
||||
}
|
||||
|
||||
override fun visitClass(declaration: IrClass): IrStatement {
|
||||
@@ -804,7 +834,7 @@ private class InteropTransformer(val context: Context, val irFile: IrFile) : IrB
|
||||
val imps = declaration.simpleFunctions().filter { it.isReal }.flatMap { function ->
|
||||
function.overriddenSymbols.mapNotNull {
|
||||
val info = it.owner.getExternalObjCMethodInfo()
|
||||
if (info == null || info.imp != null) {
|
||||
if (info == null) {
|
||||
null
|
||||
} else {
|
||||
generateWithStubs(it.owner) {
|
||||
|
||||
@@ -3112,13 +3112,14 @@ task interop_opengl_teapot(type: RunStandaloneKonanTest) {
|
||||
|
||||
task interop_objc_smoke(type: RunInteropKonanTest) {
|
||||
disabled = !isAppleTarget(project)
|
||||
goldValue = "84\nFoo\nHello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" +
|
||||
goldValue = "84\nFoo\nDeallocated\n" +
|
||||
"Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\n" +
|
||||
"true\ntrue\n" +
|
||||
"Global string\nAnother global string\nnull\nglobal object\n" +
|
||||
"5\n" +
|
||||
"String macro\n" +
|
||||
"CFString macro\n" +
|
||||
"Deallocated\nDeallocated\nDeallocated\n"
|
||||
"Deallocated\nDeallocated\n"
|
||||
|
||||
if (isAppleTarget(project)) {
|
||||
source = "interop/objc/smoke.kt"
|
||||
|
||||
@@ -99,6 +99,7 @@ int callPlusOneBlock(id<BlockConsumer> blockConsumer, int argument) {
|
||||
-(id)returnRetained:(id)obj __attribute__((ns_returns_retained));
|
||||
-(void)consume:(id) __attribute__((ns_consumed)) obj;
|
||||
-(void)consumeSelf __attribute__((ns_consumes_self));
|
||||
-(void (^)(void))returnRetainedBlock:(void (^)(void))block __attribute__((ns_returns_retained));
|
||||
@end;
|
||||
|
||||
extern BOOL unexpectedDeallocation;
|
||||
@@ -106,11 +107,41 @@ extern BOOL unexpectedDeallocation;
|
||||
@interface MustNotBeDeallocated : NSObject
|
||||
@end;
|
||||
|
||||
@interface CustomRetainMethodsImpl : MustNotBeDeallocated <CustomRetainMethods>
|
||||
@end;
|
||||
|
||||
static MustNotBeDeallocated* retainedObj;
|
||||
static void (^retainedBlock)(void);
|
||||
|
||||
void useCustomRetainMethods(id<CustomRetainMethods> p) {
|
||||
MustNotBeDeallocated* obj = [MustNotBeDeallocated new];
|
||||
CFBridgingRetain(obj); // Over-retain to detect possible over-release.
|
||||
retainedObj = obj; // Retain to detect possible over-release.
|
||||
[p returnRetained:obj];
|
||||
|
||||
[p consume:p];
|
||||
[p consumeSelf];
|
||||
}
|
||||
|
||||
MustNotBeDeallocated* capturedObj = [MustNotBeDeallocated new];
|
||||
retainedBlock = ^{ [capturedObj description]; }; // Retain to detect possible over-release.
|
||||
[p returnRetainedBlock:retainedBlock]();
|
||||
}
|
||||
|
||||
NSObject* createNSObject() {
|
||||
return [NSObject new];
|
||||
}
|
||||
|
||||
@protocol ExceptionThrower
|
||||
-(void)throwException;
|
||||
@end;
|
||||
|
||||
@interface ExceptionThrowerManager : NSObject
|
||||
+(void)throwExceptionWith:(id<ExceptionThrower>)thrower;
|
||||
@end;
|
||||
|
||||
@interface Blocks : NSObject
|
||||
+(BOOL)blockIsNull:(void (^)(void))block;
|
||||
+(int (^)(int, int, int, int))same:(int (^)(int, int, int, int))block;
|
||||
|
||||
@property (class) void (^nullBlock)(void);
|
||||
@property (class) void (^notNullBlock)(void);
|
||||
@end;
|
||||
@@ -18,6 +18,9 @@ fun run() {
|
||||
testTypeOps()
|
||||
testConversions()
|
||||
testWeakRefs()
|
||||
testExceptions()
|
||||
testBlocks()
|
||||
testCustomRetain()
|
||||
|
||||
assertEquals(2, ForwardDeclaredEnum.TWO.value)
|
||||
|
||||
@@ -88,24 +91,6 @@ fun run() {
|
||||
override fun create() = autoreleasepool { NSObject() }
|
||||
})
|
||||
|
||||
assertEquals(222, callProvidedBlock(object : NSObject(), BlockProviderProtocol {
|
||||
override fun block(): (Int) -> Int = { it * 2 }
|
||||
}, 111))
|
||||
|
||||
assertEquals(322, callPlusOneBlock(object : NSObject(), BlockConsumerProtocol {
|
||||
override fun callBlock(block: ((Int) -> Int)?, argument: Int) = block!!(argument)
|
||||
}, 321))
|
||||
|
||||
autoreleasepool {
|
||||
useCustomRetainMethods(object : Foo(), CustomRetainMethodsProtocol {
|
||||
override fun returnRetained(obj: Any?) = obj
|
||||
override fun consume(obj: Any?) {}
|
||||
override fun consumeSelf() {}
|
||||
})
|
||||
}
|
||||
|
||||
assertFalse(unexpectedDeallocation)
|
||||
|
||||
}
|
||||
|
||||
fun MutablePairProtocol.swap() {
|
||||
@@ -185,7 +170,7 @@ fun testMethodsOfAny(kotlinObject: Any, equalNsObject: NSObject, otherObject: An
|
||||
}
|
||||
|
||||
fun testWeakRefs() {
|
||||
testWeakReference({ NSObject.new()!! })
|
||||
testWeakReference({ createNSObject()!! })
|
||||
|
||||
createAndAbandonWeakRef(NSObject())
|
||||
|
||||
@@ -213,6 +198,64 @@ fun createAndAbandonWeakRef(obj: NSObject) {
|
||||
WeakReference(obj)
|
||||
}
|
||||
|
||||
fun testExceptions() {
|
||||
assertFailsWith<MyException> {
|
||||
ExceptionThrowerManager.throwExceptionWith(object : NSObject(), ExceptionThrowerProtocol {
|
||||
override fun throwException() {
|
||||
throw MyException()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fun testBlocks() {
|
||||
assertTrue(Blocks.blockIsNull(null))
|
||||
assertFalse(Blocks.blockIsNull({}))
|
||||
|
||||
assertEquals(null, Blocks.nullBlock)
|
||||
assertNotEquals(null, Blocks.notNullBlock)
|
||||
|
||||
assertEquals(10, Blocks.same({ a, b, c, d -> a + b + c + d })!!(1, 2, 3, 4))
|
||||
|
||||
assertEquals(222, callProvidedBlock(object : NSObject(), BlockProviderProtocol {
|
||||
override fun block(): (Int) -> Int = { it * 2 }
|
||||
}, 111))
|
||||
|
||||
assertEquals(322, callPlusOneBlock(object : NSObject(), BlockConsumerProtocol {
|
||||
override fun callBlock(block: ((Int) -> Int)?, argument: Int) = block!!(argument)
|
||||
}, 321))
|
||||
}
|
||||
|
||||
private lateinit var retainedMustNotBeDeallocated: MustNotBeDeallocated
|
||||
|
||||
fun testCustomRetain() {
|
||||
fun test() {
|
||||
useCustomRetainMethods(object : Foo(), CustomRetainMethodsProtocol {
|
||||
override fun returnRetained(obj: Any?) = obj
|
||||
override fun consume(obj: Any?) {}
|
||||
override fun consumeSelf() {}
|
||||
override fun returnRetainedBlock(block: (() -> Unit)?) = block
|
||||
})
|
||||
|
||||
CustomRetainMethodsImpl().let {
|
||||
it.returnRetained(Any())
|
||||
retainedMustNotBeDeallocated = MustNotBeDeallocated() // Retain to detect possible over-release.
|
||||
it.consume(retainedMustNotBeDeallocated)
|
||||
it.consumeSelf()
|
||||
it.returnRetainedBlock({})!!()
|
||||
}
|
||||
}
|
||||
|
||||
autoreleasepool {
|
||||
test()
|
||||
kotlin.native.internal.GC.collect()
|
||||
}
|
||||
|
||||
assertFalse(unexpectedDeallocation)
|
||||
}
|
||||
|
||||
private class MyException : Throwable()
|
||||
|
||||
fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply {
|
||||
elements.forEach {
|
||||
this.addObject(it as ObjCObject)
|
||||
|
||||
@@ -76,3 +76,47 @@ BOOL unexpectedDeallocation = NO;
|
||||
unexpectedDeallocation = YES;
|
||||
}
|
||||
@end;
|
||||
|
||||
static CustomRetainMethodsImpl* retainedCustomRetainMethodsImpl;
|
||||
|
||||
@implementation CustomRetainMethodsImpl
|
||||
-(id)returnRetained:(id)obj __attribute__((ns_returns_retained)) {
|
||||
return obj;
|
||||
}
|
||||
|
||||
-(void)consume:(id) __attribute__((ns_consumed)) obj {
|
||||
}
|
||||
|
||||
-(void)consumeSelf __attribute__((ns_consumes_self)) {
|
||||
retainedCustomRetainMethodsImpl = self; // Retain to detect possible over-release.
|
||||
}
|
||||
|
||||
-(void (^)(void))returnRetainedBlock:(void (^)(void))block __attribute__((ns_returns_retained)) {
|
||||
return block;
|
||||
}
|
||||
@end;
|
||||
|
||||
@implementation ExceptionThrowerManager
|
||||
+(void)throwExceptionWith:(id<ExceptionThrower>)thrower {
|
||||
[thrower throwException];
|
||||
}
|
||||
@end;
|
||||
|
||||
@implementation Blocks
|
||||
+(BOOL)blockIsNull:(void (^)(void))block {
|
||||
return block == nil;
|
||||
}
|
||||
|
||||
+(int (^)(int, int, int, int))same:(int (^)(int, int, int, int))block {
|
||||
return block;
|
||||
}
|
||||
|
||||
+(void (^)(void)) nullBlock {
|
||||
return nil;
|
||||
}
|
||||
|
||||
+(void (^)(void)) notNullBlock {
|
||||
return ^{};
|
||||
}
|
||||
|
||||
@end;
|
||||
@@ -643,6 +643,12 @@ extern "C" ALWAYS_INLINE OBJ_GETTER(Kotlin_Interop_refFromObjC, id obj) {
|
||||
RETURN_RESULT_OF(Kotlin_ObjCExport_refFromObjC, obj);
|
||||
}
|
||||
|
||||
extern "C" OBJ_GETTER(Kotlin_Interop_CreateObjCObjectHolder, id obj) {
|
||||
RuntimeAssert(obj != nullptr, "wrapped object must not be null");
|
||||
const TypeInfo* typeInfo = theForeignObjCObjectTypeInfo;
|
||||
RETURN_RESULT_OF(AllocInstanceWithAssociatedObject, typeInfo, objc_retain(obj));
|
||||
}
|
||||
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj) {
|
||||
if (obj == nullptr) RETURN_OBJ(nullptr);
|
||||
id convertible = (id<ConvertibleToKotlin>)obj;
|
||||
|
||||
@@ -112,4 +112,14 @@ internal annotation class TypedIntrinsic(val kind: String)
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
annotation class Independent
|
||||
annotation class Independent
|
||||
|
||||
/**
|
||||
* Indicates that `@SymbolName external` function can throw foreign exception to be filtered on callsite.
|
||||
*
|
||||
* Note: this annotation describes rather behaviour of the (direct) call than that of the function.
|
||||
* E.g. it doesn't have any effect when calling the function virtually. TODO: rework.
|
||||
*/
|
||||
@Target(AnnotationTarget.FUNCTION)
|
||||
@Retention(AnnotationRetention.BINARY)
|
||||
@PublishedApi internal annotation class FilterExceptions
|
||||
@@ -40,7 +40,7 @@ class IntrinsicType {
|
||||
const val OBJC_GET_MESSENGER = "OBJC_GET_MESSENGER"
|
||||
const val OBJC_GET_MESSENGER_STRET = "OBJC_GET_MESSENGER_STRET"
|
||||
const val OBJC_GET_OBJC_CLASS = "OBJC_GET_OBJC_CLASS"
|
||||
const val OBJC_GET_RECEIVER_OR_SUPER = "OBJC_GET_RECEIVER_OR_SUPER"
|
||||
const val OBJC_CREATE_SUPER_STRUCT = "OBJC_CREATE_SUPER_STRUCT"
|
||||
const val OBJC_INIT_BY = "OBJC_INIT_BY"
|
||||
const val OBJC_GET_SELECTOR = "OBJC_GET_SELECTOR"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user