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:
Svyatoslav Scherbina
2019-03-13 11:13:28 +03:00
committed by SvyatoslavScherbina
parent 68efc823f1
commit 54881b421c
24 changed files with 963 additions and 484 deletions
@@ -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
@@ -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,
@@ -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)
@@ -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)
@@ -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()) {
@@ -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")
}
@@ -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 "")
}
@@ -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
}
@@ -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 {
@@ -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 =
@@ -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)
//-------------------------------------------------------------------------//
/**
@@ -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>()
@@ -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.
@@ -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)
}
//-------------------------------------------------------------------------//
@@ -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 {
@@ -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) {
+3 -2
View File
@@ -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"
+33 -2
View File
@@ -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;
+62 -19
View File
@@ -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)
+44
View File
@@ -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;
+6
View File
@@ -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"