Unify interop with Objective-C in both directions

Also support casts to Objective-C types
This commit is contained in:
Svyatoslav Scherbina
2018-04-03 17:01:37 +03:00
committed by SvyatoslavScherbina
parent 38f896649c
commit 77139d58f8
28 changed files with 548 additions and 252 deletions
@@ -22,44 +22,14 @@ interface ObjCObject
interface ObjCClass : ObjCObject
typealias ObjCObjectMeta = ObjCClass
abstract class ObjCObjectBase protected constructor() : ObjCObject {
final override fun equals(other: Any?): Boolean {
val thisAny: Any = this
return thisAny.equals(other) // Call it virtually because ObjCObjectBase is a fake type.
}
final override fun hashCode(): Int = ObjCHashCode(this.rawPtr())
final override fun toString(): String = ObjCToString(this.rawPtr())
}
@ExportTypeInfo("theForeignObjCObjectTypeInfo")
internal open class ForeignObjCObject
abstract class ObjCObjectBase protected constructor() : ObjCObject
abstract class ObjCObjectBaseMeta protected constructor() : ObjCObjectBase(), ObjCObjectMeta {}
fun optional(): Nothing = throw RuntimeException("Do not call me!!!")
/**
* The runtime representation of any [ObjCObject].
*/
@ExportTypeInfo("theObjCPointerHolderTypeInfo")
class ObjCPointerHolder(inline val rawPtr: NativePtr) {
init {
assert(rawPtr != nativeNullPtr)
objc_retain(rawPtr)
}
final override fun equals(other: Any?): Boolean =
if (other is ObjCPointerHolder) {
ObjCEquals(this.rawPtr, other.rawPtr)
} else {
other == this
}
final override fun hashCode(): Int = ObjCHashCode(this.rawPtr)
final override fun toString(): String = ObjCToString(this.rawPtr)
}
@konan.internal.Intrinsic
@konan.internal.ExportForCompiler
private external fun ObjCObject.initFromPtr(ptr: NativePtr)
@konan.internal.Intrinsic
external fun <T : ObjCObjectBase> T.initBy(constructorCall: T): T
@@ -72,19 +42,13 @@ 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
inline fun <T : ObjCObject?> interpretObjCPointerOrNull(rawPtr: NativePtr): T? = if (rawPtr != nativeNullPtr) {
ObjCPointerHolder(rawPtr).uncheckedCast<T>()
} else {
null
}
@SymbolName("Kotlin_Interop_refFromObjC")
external fun <T : ObjCObject?> interpretObjCPointerOrNull(rawPtr: NativePtr): T?
inline fun <T : ObjCObject> interpretObjCPointer(rawPtr: NativePtr): T = if (rawPtr != nativeNullPtr) {
ObjCPointerHolder(rawPtr).uncheckedCast<T>()
} else {
throw NullPointerException()
}
inline fun <T : ObjCObject> interpretObjCPointer(rawPtr: NativePtr): T = interpretObjCPointerOrNull<T>(rawPtr)!!
@SymbolName("Kotlin_Interop_refToObjC")
external fun ObjCObject?.rawPtr(): NativePtr
@@ -119,7 +83,7 @@ typealias ObjCBlockVar<T> = ObjCNotImplementedVar<T>
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class ExternalObjCClass()
annotation class ExternalObjCClass(val protocolGetter: String = "")
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.BINARY)
@@ -155,18 +119,15 @@ private fun getObjCClassByName(name: NativePtr): NativePtr {
}
@konan.internal.ExportForCompiler
private fun <T : ObjCObject> allocObjCObject(clazz: NativePtr): T {
private fun allocObjCObject(clazz: NativePtr): NativePtr {
val rawResult = objc_allocWithZone(clazz)
if (rawResult == nativeNullPtr) {
throw OutOfMemoryError("Unable to allocate Objective-C object")
}
val result = interpretObjCPointerOrNull<T>(rawResult)!!
// `objc_allocWithZone` returns retained pointer. Balance it:
objc_release(rawResult)
// TODO: do not retain this pointer in `interpretObjCPointerOrNull` instead.
// Note: `objc_allocWithZone` returns retained pointer, and thus it must be balanced by the caller.
return result
return rawResult
}
@konan.internal.Intrinsic
@@ -25,12 +25,14 @@ inline fun <R> autoreleasepool(block: () -> R): R {
}
}
// FIXME: implement a checked cast instead.
fun <T : ObjCObject> ObjCObject.reinterpret() = this.uncheckedCast<T>()
@Deprecated("Use plain Kotlin cast", ReplaceWith("this as T"), DeprecationLevel.WARNING)
fun <T : ObjCObject> ObjCObject.reinterpret() = @Suppress("DEPRECATION") this.uncheckedCast<T>()
// TODO: null checks
var <T : ObjCObject?> ObjCObjectVar<T>.value: T
get() = interpretObjCPointerOrNull<T>(nativeMemUtils.getNativePtr(this)).uncheckedCast<T>()
@Suppress("DEPRECATION") get() =
interpretObjCPointerOrNull<T>(nativeMemUtils.getNativePtr(this)).uncheckedCast<T>()
set(value) = nativeMemUtils.putNativePtr(this, value.rawPtr())
/**
@@ -92,8 +92,8 @@ private tailrec fun convertArgument(
is CEnum -> convertArgument(argument.value, isVariadic, location, additionalPlacement)
is ObjCPointerHolder -> {
location.reinterpret<COpaquePointerVar>()[0] = interpretCPointer(argument.rawPtr)
is ForeignObjCObject -> {
location.reinterpret<COpaquePointerVar>()[0] = interpretCPointer((argument as ObjCObject).rawPtr())
FFI_TYPE_KIND_POINTER
}
@@ -470,8 +470,23 @@ fun mirror(declarationMapper: DeclarationMapper, type: Type): TypeMirror = when
else -> TODO(type.toString())
}
internal tailrec fun ObjCClass.isNSStringOrSubclass(): Boolean = when (this.name) {
"NSMutableString", // fast path and handling for forward declarations.
"NSString" -> true
else -> {
val baseClass = this.baseClass
if (baseClass != null) {
baseClass.isNSStringOrSubclass()
} else {
false
}
}
}
internal fun ObjCClass.isNSStringSubclass(): Boolean = this.baseClass?.isNSStringOrSubclass() == true
private fun objCPointerMirror(declarationMapper: DeclarationMapper, type: ObjCPointer): TypeMirror.ByValue {
if (type is ObjCObjectPointer && type.def.name == "NSString") {
if (type is ObjCObjectPointer && type.def.isNSStringOrSubclass()) {
val info = TypeInfo.NSString(type)
return objCMirror(KotlinTypes.string, info, type.isNullable)
}
@@ -112,13 +112,13 @@ class ObjCMethodStub(stubGenerator: StubGenerator,
if (method.nsConsumesSelf) {
// TODO: do this later due to possible exceptions
bodyGenerator.out("objc_retain($kniReceiverParameter.rawPtr())")
bodyGenerator.out("objc_retain($kniReceiverParameter)")
}
kotlinObjCBridgeParameters.add(kniReceiverParameter to KotlinTypes.objCObject.type)
kotlinObjCBridgeParameters.add(kniReceiverParameter to KotlinTypes.nativePtr)
nativeBridgeArguments.add(
TypedKotlinValue(voidPtr,
"getReceiverOrSuper($kniReceiverParameter.rawPtr(), $kniSuperClassParameter)"))
"getReceiverOrSuper($kniReceiverParameter, $kniSuperClassParameter)"))
val kotlinParameterNames = method.getKotlinParameterNames()
@@ -289,11 +289,16 @@ private fun ObjCMethod.isOverride(container: ObjCClassOrProtocol): Boolean =
abstract class ObjCContainerStub(stubGenerator: StubGenerator,
private val container: ObjCClassOrProtocol,
private val isMeta: Boolean) : KotlinStub {
protected val metaContainerStub: ObjCContainerStub?
) : KotlinStub {
private val isMeta: Boolean get() = metaContainerStub == null
private val methods: List<ObjCMethod>
private val properties: List<ObjCProperty>
private val protocolGetter: String?
init {
val superMethods = container.inheritedMethods(isMeta)
@@ -376,7 +381,26 @@ abstract class ObjCContainerStub(stubGenerator: StubGenerator,
val supersString = supers.joinToString { it.render(stubGenerator.kotlinFile) }
val classifier = stubGenerator.declarationMapper.getKotlinClassFor(container, isMeta)
val name = stubGenerator.kotlinFile.declare(classifier)
this.classHeader = "@ExternalObjCClass $keywords $name : $supersString"
val externalObjCClassAnnotationName = "@ExternalObjCClass"
val externalObjCClassAnnotation: String
if (container is ObjCProtocol) {
protocolGetter = if (metaContainerStub != null) {
metaContainerStub.protocolGetter!!
} else {
val nativeBacked = object : NativeBacked {}
// TODO: handle the case when protocol getter stub can't be compiled.
genProtocolGetter(stubGenerator, nativeBacked, container)
}
externalObjCClassAnnotation = externalObjCClassAnnotationName.applyToStrings(protocolGetter)
} else {
protocolGetter = null
externalObjCClassAnnotation = externalObjCClassAnnotationName
}
this.classHeader = "$externalObjCClassAnnotation $keywords $name : $supersString"
}
open fun generateBody(context: StubGenerationContext): Sequence<String> {
@@ -403,13 +427,10 @@ open class ObjCClassOrProtocolStub(
) : ObjCContainerStub(
stubGenerator,
container,
isMeta = false
metaContainerStub = object : ObjCContainerStub(stubGenerator, container, metaContainerStub = null) {}
) {
private val metaClassStub =
object : ObjCContainerStub(stubGenerator, container, isMeta = true) {}
override fun generate(context: StubGenerationContext) =
metaClassStub.generate(context) + "" + super.generate(context)
metaContainerStub!!.generate(context) + "" + super.generate(context)
}
class ObjCProtocolStub(stubGenerator: StubGenerator, protocol: ObjCProtocol) :
@@ -485,11 +506,11 @@ class ObjCPropertyStub(
}
val result = mutableListOf(
"$modifiers$kind $receiver${property.name.asSimpleName()}: $kotlinType",
" get() = ${getterStub.bridgeName}(nativeNullPtr, this)"
" get() = ${getterStub.bridgeName}(nativeNullPtr, this.rawPtr())"
)
property.setter?.let {
result.add(" set(value) = ${setterStub!!.bridgeName}(nativeNullPtr, this, value)")
result.add(" set(value) = ${setterStub!!.bridgeName}(nativeNullPtr, this.rawPtr(), value)")
}
return result.asSequence()
@@ -585,3 +606,22 @@ private fun NativeCodeBuilder.genMethodImp(
return functionName
}
private fun genProtocolGetter(
stubGenerator: StubGenerator,
nativeBacked: NativeBacked,
protocol: ObjCProtocol
): String {
val functionName = "kniprot_" + stubGenerator.pkgName.replace('.', '_') + stubGenerator.nextUniqueId()
val builder = NativeCodeBuilder(stubGenerator.simpleBridgeGenerator.topLevelNativeScope)
with(builder) {
out("Protocol* $functionName() {")
out(" return @protocol(${protocol.name});")
out("}")
}
stubGenerator.simpleBridgeGenerator.insertNativeBridge(nativeBacked, emptyList(), builder.lines)
return functionName
}
@@ -802,12 +802,12 @@ class StubGenerator(
}
nativeIndex.objCClasses.forEach {
if (!it.isForwardDeclaration) {
if (!it.isForwardDeclaration && !it.isNSStringSubclass()) {
stubs.add(ObjCClassStub(this, it))
}
}
nativeIndex.objCCategories.mapTo(stubs) {
nativeIndex.objCCategories.filter { !it.clazz.isNSStringSubclass() }.mapTo(stubs) {
ObjCCategoryStub(this, it)
}
@@ -886,6 +886,7 @@ class StubGenerator(
add("MANY_INTERFACES_MEMBER_NOT_IMPLEMENTED") // Workaround for multiple-inherited properties.
add("EXTENSION_SHADOWED_BY_MEMBER") // For Objective-C categories represented as extensions.
add("REDUNDANT_NULLABLE") // This warning appears due to Obj-C typedef nullability incomplete support.
add("DEPRECATION") // For uncheckedCast.
}
}
@@ -69,7 +69,7 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
val objCNamesProtocols = objCNames.child(Name.identifier("protocols"))
}
private val packageScope = builtIns.builtInsModule.getPackage(FqNames.packageName).memberScope
val packageScope = builtIns.builtInsModule.getPackage(FqNames.packageName).memberScope
val getPointerSize = packageScope.getContributedFunctions("getPointerSize").single()
@@ -161,12 +161,6 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
}.toMap()
val objCObject = packageScope.getContributedClass("ObjCObject")
val objCPointerHolder = packageScope.getContributedClass("ObjCPointerHolder")
val objCPointerHolderValue = objCPointerHolder.unsubstitutedMemberScope
.getContributedDescriptors().filterIsInstance<PropertyDescriptor>().single()
val objCObjectInitFromPtr = packageScope.getContributedFunctions("initFromPtr").single()
val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single()
@@ -25,11 +25,12 @@ import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
import org.jetbrains.kotlin.resolve.descriptorUtil.*
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.typeUtil.supertypes
private val interopPackageName = InteropBuiltIns.FqNames.packageName
private val objCObjectFqName = interopPackageName.child(Name.identifier("ObjCObject"))
private val objCClassFqName = interopPackageName.child(Name.identifier("ObjCClass"))
private val externalObjCClassFqName = interopPackageName.child(Name.identifier("ExternalObjCClass"))
internal val externalObjCClassFqName = interopPackageName.child(Name.identifier("ExternalObjCClass"))
private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod"))
private val objCBridgeFqName = interopPackageName.child(Name.identifier("ObjCBridge"))
@@ -38,10 +39,7 @@ fun ClassDescriptor.isObjCClass(): Boolean =
this.containingDeclaration.fqNameSafe != interopPackageName
fun KotlinType.isObjCObjectType(): Boolean =
TypeUtils.getClassDescriptor(this)
?.getAllSuperClassifiers()
?.any { it.fqNameSafe == objCObjectFqName }
?: false
(this.supertypes() + this).any { TypeUtils.getClassDescriptor(it)?.fqNameSafe == objCObjectFqName }
fun ClassDescriptor.isExternalObjCClass(): Boolean = this.isObjCClass() &&
this.parentsWithSelf.filterIsInstance<ClassDescriptor>().any {
@@ -27,6 +27,7 @@ internal fun CommonBackendContext.reportCompilationError(message: String, irFile
internal fun CommonBackendContext.reportCompilationError(message: String) {
report(null, null, message, true)
throw KonanCompilationException()
}
internal fun CommonBackendContext.reportCompilationWarning(message: String) {
@@ -108,10 +108,13 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym
val interopAllocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject)
val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass)
val interopObjCRelease = symbolTable.referenceSimpleFunction(
context.interopBuiltIns.packageScope
.getContributedFunctions(Name.identifier("objc_release"), NoLookupLocation.FROM_BACKEND)
.single()
)
val interopObjCObjectInitFromPtr =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectInitFromPtr)
val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass)
val interopObjCObjectSuperInitCheck =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectSuperInitCheck)
@@ -132,12 +135,6 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym
val interopCreateNSStringFromKString =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.CreateNSStringFromKString)
val objCPointerHolder = symbolTable.referenceClass(context.interopBuiltIns.objCPointerHolder)
val objCPointerHolderValueGetter =
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCPointerHolderValue.getter!!)
val allocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject)
val objCExportTrapOnUndeclaredException =
symbolTable.referenceSimpleFunction(context.builtIns.konanInternal.getContributedFunctions(
Name.identifier("trapOnUndeclaredException"),
@@ -47,6 +47,7 @@ internal fun IrFunction.isObjCClassMethod() = this.descriptor.isObjCClassMethod(
internal fun IrFunction.canObjCClassMethodBeCalledVirtually(overridden: IrFunction) =
this.descriptor.canObjCClassMethodBeCalledVirtually(overridden.descriptor)
internal fun IrClass.isObjCClass() = this.descriptor.isObjCClass()
internal fun IrClass.isObjCMetaClass() = this.descriptor.isObjCMetaClass()
internal fun IrFunction.isExternalObjCClassMethod() = this.descriptor.isExternalObjCClassMethod()
internal val IrDeclaration.llvmSymbolOrigin get() = this.descriptor.llvmSymbolOrigin
@@ -24,6 +24,8 @@ import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.backend.konan.descriptors.stdlibModule
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.backend.konan.irasdescriptors.*
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCDataGenerator
import org.jetbrains.kotlin.konan.target.KonanTarget
internal class CodeGenerator(override val context: Context) : ContextUtils {
@@ -85,18 +87,19 @@ internal class CodeGenerator(override val context: Context) : ContextUtils {
}
fun typeInfoForAllocation(constructedClass: ClassDescriptor): LLVMValueRef {
val descriptorForTypeInfo = if (constructedClass.isObjCClass()) {
context.ir.symbols.objCPointerHolder.owner
} else {
constructedClass
}
return typeInfoValue(descriptorForTypeInfo)
assert(!constructedClass.isObjCClass())
return typeInfoValue(constructedClass)
}
fun generateLocationInfo(locationInfo: LocationInfo): DILocationRef? {
return LLVMCreateLocation(LLVMGetModuleContext(context.llvmModule), locationInfo.line, locationInfo.line, locationInfo.scope)
}
val objCDataGenerator = when (context.config.target) {
KonanTarget.IOS_ARM64, KonanTarget.IOS_X64, KonanTarget.MACOS_X64 -> ObjCDataGenerator(this)
else -> null
}
}
internal sealed class ExceptionHandler {
@@ -430,6 +433,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
return LLVMBlockAddress(function, bbLabel)!!
}
fun not(arg: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildNot(builder, arg, name)!!
fun and(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildAnd(builder, arg0, arg1, name)!!
fun or(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildOr(builder, arg0, arg1, name)!!
fun xor(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildXor(builder, arg0, arg1, name)!!
@@ -613,7 +617,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
val owner = descriptor.containingDeclaration as ClassDescriptor
val typeInfoPtr: LLVMValueRef = if (owner.isObjCClass()) {
val typeInfoPtr: LLVMValueRef = if (descriptor.getObjCMethodInfo() != null) {
call(context.llvm.getObjCKotlinTypeInfo, listOf(receiver))
} else {
val typeInfoOrMetaPtr = structGep(receiver, 0 /* typeInfoOrMeta_ */)
@@ -655,6 +659,20 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
return codegen.theUnitInstanceRef.llvm
}
if (descriptor.isCompanion) {
val parent = descriptor.parent as IrClass
if (parent.isObjCClass()) {
// TODO: cache it too.
return call(
codegen.llvmFunction(context.ir.symbols.interopInterpretObjCPointer.owner),
listOf(getObjCClass(parent, exceptionHandler)),
Lifetime.GLOBAL,
exceptionHandler
)
}
}
val objectPtr = codegen.getObjectInstanceStorage(descriptor, shared)
val bbCurrent = currentBlock
val bbInit= basicBlock("label_init", locationInfo)
@@ -708,6 +726,53 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
)
}
// TODO: get rid of exceptionHandler argument by ensuring that all called functions are non-throwing.
fun getObjCClass(irClass: IrClass, exceptionHandler: ExceptionHandler): LLVMValueRef {
assert(!irClass.isInterface)
return if (irClass.isExternalObjCClass()) {
context.llvm.imports.add(irClass.llvmSymbolOrigin)
if (irClass.isObjCMetaClass()) {
val name = irClass.name.asString().removeSuffix("Meta")
val objCClass = load(codegen.objCDataGenerator!!.genClassRef(name).llvm)
val getClass = context.llvm.externalFunction(
"object_getClass",
functionType(int8TypePtr, false, int8TypePtr),
origin = context.standardLlvmSymbolsOrigin
)
call(getClass, listOf(objCClass), exceptionHandler = exceptionHandler)
} else {
load(codegen.objCDataGenerator!!.genClassRef(irClass.name.asString()).llvm)
}
} else {
if (irClass.isObjCMetaClass()) {
error("type-checking against Kotlin classes inheriting Objective-C meta-classes isn't supported yet")
}
val objCDeclarations = context.llvmDeclarations.forClass(irClass).objCDeclarations!!
val classPointerGlobal = objCDeclarations.classPointerGlobal.llvmGlobal
val storedClass = this.load(classPointerGlobal)
val storedClassIsNotNull = this.icmpNe(storedClass, kNullInt8Ptr)
return this.ifThenElse(storedClassIsNotNull, storedClass) {
val newClass = call(
context.llvm.createKotlinObjCClass,
listOf(objCDeclarations.classInfoGlobal.llvmGlobal),
exceptionHandler = exceptionHandler
)
this.store(newClass, classPointerGlobal)
newClass
}
}
}
fun resetDebugLocation() {
if (!context.shouldContainDebugInfo()) return
currentPositionHolder.resetBuilderDebugLocation()
@@ -417,8 +417,12 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") }
val missingInitImp by lazy { importRtFunction("MissingInitImp") }
val Kotlin_Interop_DoesObjectConformToProtocol by lazyRtFunction
val Kotlin_Interop_IsObjectKindOfClass by lazyRtFunction
val Kotlin_ObjCExport_refToObjC by lazyRtFunction
val Kotlin_ObjCExport_refFromObjC by lazyRtFunction
val Kotlin_ObjCExport_CreateNSStringFromKString by lazyRtFunction
val Kotlin_Interop_CreateNSArrayFromKList by lazyRtFunction
val Kotlin_Interop_CreateNSMutableArrayFromKList by lazyRtFunction
val Kotlin_Interop_CreateNSSetFromKSet by lazyRtFunction
@@ -431,15 +435,6 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
val Kotlin_ObjCExport_RethrowExceptionAsNSError by lazyRtFunction
val Kotlin_ObjCExport_RethrowNSErrorAsException by lazyRtFunction
val objCExportEnabled = if (context.config.produce.isNativeBinary) {
// Note: this defines the global declared in runtime (if any).
staticData.placeGlobal("objCExportEnabled", Int8(0), isExported = true).also {
it.setConstant(true)
}
} else {
null
}
val tlsMode by lazy {
when (target) {
KonanTarget.WASM32,
@@ -340,7 +340,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
declaration.acceptChildrenVoid(this)
// Note: it is here because it also generates some bitcode.
ObjCExport(context).produceObjCFramework()
ObjCExport(codegen).produce()
codegen.objCDataGenerator?.finishModule()
appendLlvmUsed("llvm.used", context.llvm.usedFunctions + context.llvm.usedGlobals)
appendLlvmUsed("llvm.compiler.used", context.llvm.compilerUsedGlobals)
@@ -1247,8 +1249,24 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
assert(!KotlinBuiltIns.isPrimitiveType(dstDescriptor.defaultType) &&
!KotlinBuiltIns.isPrimitiveType(value.argument.type))
val dstTypeInfo = codegen.typeInfoValue(dstDescriptor) // Get TypeInfo for dst type.
val srcArg = evaluateExpression(value.argument) // Evaluate src expression.
if (dstDescriptor.defaultType.isObjCObjectType()) {
with(functionGenerationContext) {
ifThen(not(genInstanceOf(srcArg, dstDescriptor))) {
callDirect(
context.ir.symbols.ThrowTypeCastException.owner,
emptyList(),
Lifetime.GLOBAL
)
}
}
return srcArg
}
// Note: the code above would actually work for any classes.
// However, the code generated below is shorter. Consider it to be a specialization.
val dstTypeInfo = codegen.typeInfoValue(dstDescriptor) // Get TypeInfo for dst type.
val srcObjInfoPtr = functionGenerationContext.bitcast(codegen.kObjHeaderPtr, srcArg) // Cast src to ObjInfoPtr.
val args = listOf(srcObjInfoPtr, dstTypeInfo) // Create arg list.
call(context.llvm.checkInstanceFunction, args) // Check if dst is subclass of src.
@@ -1295,6 +1313,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
//-------------------------------------------------------------------------//
private fun genInstanceOf(obj: LLVMValueRef, dstClass: IrClass): LLVMValueRef {
if (dstClass.defaultType.isObjCObjectType()) {
return genInstanceOfObjC(obj, dstClass)
}
val dstTypeInfo = codegen.typeInfoValue(dstClass) // Get TypeInfo for dst type.
val srcObjInfoPtr = functionGenerationContext.bitcast(codegen.kObjHeaderPtr, obj) // Cast src to ObjInfoPtr.
val args = listOf(srcObjInfoPtr, dstTypeInfo) // Create arg list.
@@ -1303,11 +1325,57 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
return LLVMBuildTrunc(functionGenerationContext.builder, result, kInt1, "")!! // Truncate result to boolean
}
private fun genInstanceOfObjC(obj: LLVMValueRef, dstClass: IrClass): LLVMValueRef {
val objCObject = callDirect(
context.ir.symbols.interopObjCObjectRawValueGetter.owner,
listOf(obj),
Lifetime.IRRELEVANT
)
return if (dstClass.isObjCClass()) {
if (dstClass.isInterface) {
val isMeta = if (dstClass.isObjCMetaClass()) Int8(1) else Int8(0)
call(
context.llvm.Kotlin_Interop_DoesObjectConformToProtocol,
listOf(
objCObject,
genGetObjCProtocol(dstClass),
isMeta.llvm
)
)
} else {
call(
context.llvm.Kotlin_Interop_IsObjectKindOfClass,
listOf(objCObject, genGetObjCClass(dstClass))
)
}.let {
functionGenerationContext.icmpNe(it, Int8(0).llvm)
}
} else {
// e.g. ObjCObject, ObjCObjectBase etc.
if (dstClass.isObjCMetaClass()) {
val isClass = context.llvm.externalFunction(
"object_isClass",
functionType(int8Type, false, int8TypePtr),
context.standardLlvmSymbolsOrigin
)
call(isClass, listOf(objCObject)).let {
functionGenerationContext.icmpNe(it, Int8(0).llvm)
}
} else {
kTrue
}
}
}
//-------------------------------------------------------------------------//
private fun evaluateNotInstanceOf(value: IrTypeOperatorCall): LLVMValueRef {
val instanceOfResult = evaluateInstanceOf(value)
return LLVMBuildNot(functionGenerationContext.builder, instanceOfResult, "")!!
return functionGenerationContext.not(instanceOfResult)
}
//-------------------------------------------------------------------------//
@@ -1399,7 +1467,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
return if (classDescriptor.isObjCClass()) {
assert(classDescriptor.isKotlinObjCClass())
val objCPtr = callDirect(context.ir.symbols.objCPointerHolderValueGetter.owner,
val objCPtr = callDirect(context.ir.symbols.interopObjCObjectRawValueGetter.owner,
listOf(objectPtr), Lifetime.IRRELEVANT)
val objCDeclarations = context.llvmDeclarations.forClass(classDescriptor).objCDeclarations!!
@@ -1992,9 +2060,19 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
assert(args.isEmpty())
functionGenerationContext.allocArray(codegen.typeInfoValue(constructedClass), count = kImmZero,
lifetime = resultLifetime(callee))
} else if (constructedClass.isKotlinObjCClass()) {
callDirect(context.ir.symbols.allocObjCObject.owner, listOf(genGetObjCClass(constructedClass)),
resultLifetime(callee))
} else if (constructedClass.isObjCClass()) {
assert(constructedClass.isKotlinObjCClass()) // Calls to other ObjC class constructors must be lowered.
val symbols = context.ir.symbols
val rawPtr = callDirect(
symbols.interopAllocObjCObject.owner,
listOf(genGetObjCClass(constructedClass)),
Lifetime.IRRELEVANT
)
callDirect(symbols.interopInterpretObjCPointer.owner, listOf(rawPtr), resultLifetime(callee)).also {
// Balance pointer retained by alloc:
callDirect(symbols.interopObjCRelease.owner, listOf(rawPtr), Lifetime.IRRELEVANT)
}
} else {
functionGenerationContext.allocInstance(constructedClass, resultLifetime(callee))
}
@@ -2066,10 +2144,6 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
}
interop.objCObjectInitFromPtr -> {
genObjCObjectInitFromPtr(args)
}
interop.getObjCReceiverOrSuper -> {
genGetObjCReceiverOrSuper(args)
}
@@ -2226,36 +2300,24 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
}
private fun genGetObjCClass(classDescriptor: ClassDescriptor): LLVMValueRef {
assert(!classDescriptor.isInterface)
return functionGenerationContext.getObjCClass(classDescriptor, currentCodeContext.exceptionHandler)
}
return if (classDescriptor.isExternalObjCClass()) {
context.llvm.imports.add(classDescriptor.llvmSymbolOrigin)
private fun genGetObjCProtocol(irClass: IrClass): LLVMValueRef {
// Note: this function will return the same result for Obj-C protocol and corresponding meta-class.
val lookUpFunction = context.llvm.externalFunction("Kotlin_Interop_getObjCClass",
functionType(int8TypePtr, false, int8TypePtr),
origin = context.standardLlvmSymbolsOrigin
)
assert(irClass.isInterface)
assert(irClass.isExternalObjCClass())
call(lookUpFunction,
listOf(codegen.staticData.cStringLiteral(classDescriptor.name.asString()).llvm))
} else {
val objCDeclarations = context.llvmDeclarations.forClass(classDescriptor).objCDeclarations!!
val classPointerGlobal = objCDeclarations.classPointerGlobal.llvmGlobal
val gen = functionGenerationContext
val storedClass = gen.load(classPointerGlobal)
val storedClassIsNotNull = gen.icmpNe(storedClass, kNullInt8Ptr)
return gen.ifThenElse(storedClassIsNotNull, storedClass) {
val newClass = call(context.llvm.createKotlinObjCClass,
listOf(objCDeclarations.classInfoGlobal.llvmGlobal))
gen.store(newClass, classPointerGlobal)
newClass
}
}
val annotation = irClass.annotations.findAnnotation(externalObjCClassFqName)!!
val protocolGetterName = annotation.getStringValue("protocolGetter")
val protocolGetter = context.llvm.externalFunction(
protocolGetterName,
functionType(int8TypePtr, false),
irClass.llvmSymbolOrigin
)
return call(protocolGetter, emptyList())
}
private fun genGetObjCMessenger(args: List<LLVMValueRef>, isLU: Boolean): LLVMValueRef {
@@ -2302,11 +2364,6 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
return gen.bitcast(int8TypePtr, messenger)
}
private fun genObjCObjectInitFromPtr(args: List<LLVMValueRef>): LLVMValueRef {
return callDirect(context.ir.symbols.objCPointerHolder.owner.constructors.single(),
args, Lifetime.IRRELEVANT)
}
private fun genGetObjCReceiverOrSuper(args: List<LLVMValueRef>): LLVMValueRef {
val gen = functionGenerationContext
@@ -24,7 +24,7 @@ import org.jetbrains.kotlin.backend.konan.llvm.*
internal open class ObjCCodeGenerator(val codegen: CodeGenerator) {
val context = codegen.context
val dataGenerator = ObjCDataGenerator(codegen)
val dataGenerator = codegen.objCDataGenerator!!
fun FunctionGenerationContext.genSelector(selector: String): LLVMValueRef {
val selectorRef = dataGenerator.genSelectorRef(selector)
@@ -216,10 +216,6 @@ internal class ObjCExportCodeGenerator(
emitSortedAdapters(placedClassAdapters, "Kotlin_ObjCExport_sortedClassAdapters")
emitSortedAdapters(placedInterfaceAdapters, "Kotlin_ObjCExport_sortedProtocolAdapters")
context.llvm.objCExportEnabled!!.setInitializer(Int8(1))
dataGenerator.finishModule() // TODO: move to appropriate place.
}
private val impType = pointerType(functionType(int8TypePtr, true, int8TypePtr, int8TypePtr))
@@ -408,7 +404,7 @@ private fun ObjCExportCodeGenerator.emitKotlinFunctionAdaptersToBlock() {
private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() {
setObjCExportTypeInfo(
context.builtIns.string,
constPointer(codegen.llvmFunction(context.ir.symbols.interopCreateNSStringFromKString.owner))
constPointer(context.llvm.Kotlin_ObjCExport_CreateNSStringFromKString)
)
setObjCExportTypeInfo(
@@ -445,11 +441,6 @@ private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() {
emitBoxConverter(it)
}
setObjCExportTypeInfo(
context.interopBuiltIns.objCPointerHolder,
constPointer(codegen.llvmFunction(context.ir.symbols.objCPointerHolderValueGetter.owner))
)
emitFunctionConverters()
emitKotlinFunctionAdaptersToBlock()
@@ -23,9 +23,10 @@ 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.descriptors.getStringValue
import org.jetbrains.kotlin.backend.konan.descriptors.allOverriddenDescriptors
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.backend.konan.irasdescriptors.isReal
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptorImpl
@@ -80,16 +81,6 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
private fun IrBuilderWithScope.getObjCClass(classSymbol: IrClassSymbol): IrExpression {
val classDescriptor = classSymbol.descriptor
assert(!classDescriptor.isObjCMetaClass())
if (classDescriptor.isExternalObjCClass()) {
val companionObject = classDescriptor.companionObjectDescriptor!!
if (companionObject.unsubstitutedPrimaryConstructor != scope.scopeOwner) {
// Optimization: get class pointer from companion object thus avoiding lookup by name.
return irCall(symbols.interopObjCObjectRawValueGetter).apply {
extensionReceiver = irGetObject(symbolTable.referenceClass(companionObject))
}
}
}
return irCall(symbols.interopGetObjCClass, listOf(classDescriptor.defaultType))
}
@@ -349,6 +340,29 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
)
}
val methodsOfAny =
context.ir.symbols.any.owner.declarations.filterIsInstance<IrSimpleFunction>().toSet()
irClass.declarations.filterIsInstance<IrSimpleFunction>().filter { it.isReal }.forEach { method ->
val overriddenMethodOfAny = method.allOverriddenDescriptors.firstOrNull {
it in methodsOfAny
}
if (overriddenMethodOfAny != null) {
val correspondingObjCMethod = when (method.name.asString()) {
"toString" -> "'description'"
"hashCode" -> "'hash'"
"equals" -> "'isEqual:'"
else -> "corresponding Objective-C method"
}
context.report(
method,
"can't override '${method.name}', override $correspondingObjCMethod instead",
isError = true
)
}
}
}
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
@@ -367,18 +381,10 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
if (classContainer is ClassDescriptor && classContainer.isObjCClass() &&
constructedClassDescriptor == classContainer.companionObjectDescriptor) {
val outerConstructedClass = outerClasses[outerClasses.lastIndex - 1]
assert (outerConstructedClass.descriptor == classContainer)
// Note: it is actually not used; getting values of such objects is handled by code generator
// in [FunctionGenerationContext.getObjectValue].
assert(expression.getArguments().isEmpty())
return builder.irBlock(expression) {
+expression // Required for the IR to be valid, will be ignored in codegen.
+irCall(symbols.interopObjCObjectInitFromPtr).apply {
extensionReceiver = irGet(constructedClass.thisReceiver!!.symbol)
putValueArgument(0, getObjCClass(outerConstructedClass.symbol))
}
}
return expression
}
}
@@ -398,7 +404,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
val initCall = builder.genLoweredObjCMethodCall(
initMethodInfo,
superQualifier = symbolTable.referenceClass(expression.descriptor.constructedClass),
receiver = builder.irGet(constructedClass.thisReceiver!!.symbol),
receiver = builder.getRawPtr(builder.irGet(constructedClass.thisReceiver!!.symbol)),
arguments = initMethod.valueParameters.map { expression.getValueArgument(it)!! }
)
@@ -451,14 +457,21 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
assert(expression.extensionReceiver == null)
assert(expression.dispatchReceiver == null)
builder.at(expression)
return builder.genLoweredObjCMethodCall(
initMethod.getExternalObjCMethodInfo()!!,
superQualifier = null,
receiver = builder.callAlloc(symbolTable.referenceClass(descriptor.constructedClass)),
arguments = arguments
)
return builder.irBlock(expression) {
val allocated = irTemporaryVar(callAlloc(symbolTable.referenceClass(descriptor.constructedClass)))
val result = irTemporaryVar(
genLoweredObjCMethodCall(
initMethod.getExternalObjCMethodInfo()!!,
superQualifier = null,
receiver = irGet(allocated.symbol),
arguments = arguments
)
)
+irCall(symbols.interopObjCRelease).apply {
putValueArgument(0, irGet(allocated.symbol)) // Balance pointer retained by alloc.
}
+irGet(result.symbol)
}
}
}
@@ -493,7 +506,7 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
return builder.genLoweredObjCMethodCall(
methodInfo,
superQualifier = expression.superQualifierSymbol,
receiver = expression.dispatchReceiver ?: expression.extensionReceiver!!,
receiver = builder.getRawPtr(expression.dispatchReceiver ?: expression.extensionReceiver!!),
arguments = arguments
)
}
@@ -519,6 +532,11 @@ internal class InteropLoweringPart1(val context: Context) : IrBuildingTransforme
else -> expression
}
}
private fun IrBuilderWithScope.getRawPtr(receiver: IrExpression) =
irCall(symbols.interopObjCObjectRawValueGetter).apply {
extensionReceiver = receiver
}
}
/**
@@ -16,10 +16,12 @@
package org.jetbrains.kotlin.backend.konan.objcexport
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments
import org.jetbrains.kotlin.backend.konan.isNativeBinary
import org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator
import org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGenerator
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
@@ -29,19 +31,51 @@ import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.name.isSubpackageOf
internal class ObjCExport(val context: Context) {
internal class ObjCExport(val codegen: CodeGenerator) {
val context get() = codegen.context
private val target get() = context.config.target
internal fun produceObjCFramework() {
if (context.config.produce != CompilerOutputKind.FRAMEWORK) return
internal fun produce() {
if (target != KonanTarget.IOS_ARM64 && target != KonanTarget.IOS_X64 && target != KonanTarget.MACOS_X64) return
val headerGenerator = ObjCExportHeaderGenerator(context)
if (!context.config.produce.isNativeBinary) return // TODO: emit RTTI to the same modules as classes belong to.
val objCCodeGenerator: ObjCExportCodeGenerator
val generatedClasses: Set<ClassDescriptor>
val topLevelDeclarations: Map<FqName, List<CallableMemberDescriptor>>
if (context.config.produce == CompilerOutputKind.FRAMEWORK) {
val headerGenerator = ObjCExportHeaderGenerator(context)
produceFrameworkSpecific(headerGenerator)
generatedClasses = headerGenerator.generatedClasses
topLevelDeclarations = headerGenerator.topLevel
objCCodeGenerator = ObjCExportCodeGenerator(codegen, headerGenerator.namer, headerGenerator.mapper)
} else {
// TODO: refactor ObjCExport* to handle this case on a general basis.
val mapper = object : ObjCExportMapper() {
override fun getCategoryMembersFor(descriptor: ClassDescriptor): List<CallableMemberDescriptor> =
emptyList()
override fun isSpecialMapped(descriptor: ClassDescriptor): Boolean =
error("shouldn't reach here")
}
val namer = ObjCExportNamer(context, mapper)
objCCodeGenerator = ObjCExportCodeGenerator(codegen, namer, mapper)
generatedClasses = emptySet()
topLevelDeclarations = emptyMap()
}
objCCodeGenerator.emitRtti(generatedClasses = generatedClasses, topLevel = topLevelDeclarations)
}
private fun produceFrameworkSpecific(headerGenerator: ObjCExportHeaderGenerator) {
headerGenerator.translateModule()
val namer = headerGenerator.namer
val mapper = headerGenerator.mapper
val framework = File(context.config.outputFile)
val frameworkContents = when (target) {
KonanTarget.IOS_ARM64, KonanTarget.IOS_X64 -> framework
@@ -79,9 +113,6 @@ internal class ObjCExport(val context: Context) {
framework.child(child).createAsSymlink("Versions/Current/$child")
}
}
val objCCodeGenerator = ObjCExportCodeGenerator(CodeGenerator(context), namer, mapper)
objCCodeGenerator.emitRtti(headerGenerator.generatedClasses, headerGenerator.topLevel)
}
private fun emitInfoPlist(frameworkContents: File, name: String) {
@@ -17,6 +17,8 @@
package org.jetbrains.kotlin.ir.util
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.konan.KonanBackendContext
import org.jetbrains.kotlin.backend.konan.KonanCompilationException
import org.jetbrains.kotlin.backend.konan.descriptors.synthesizedName
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
@@ -358,7 +360,7 @@ tailrec fun IrDeclaration.getContainingFile(): IrFile? {
}
}
fun CommonBackendContext.report(declaration: IrDeclaration, message: String, isError: Boolean) {
internal fun KonanBackendContext.report(declaration: IrDeclaration, message: String, isError: Boolean) {
val irFile = declaration.getContainingFile()
this.report(
declaration,
@@ -371,4 +373,5 @@ fun CommonBackendContext.report(declaration: IrDeclaration, message: String, isE
},
isError
)
if (isError) throw KonanCompilationException()
}
@@ -1,2 +1,3 @@
language = Objective-C
headerFilter = **/smoke.h
headers = Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h
headerFilter = **/smoke.h Foundation/NSArray.h Foundation/NSValue.h Foundation/NSString.h
+59 -2
View File
@@ -1,5 +1,6 @@
import kotlinx.cinterop.*
import objcSmoke.*
import kotlin.test.*
fun main(args: Array<String>) {
autoreleasepool {
@@ -8,6 +9,9 @@ fun main(args: Array<String>) {
}
fun run() {
testTypeOps()
testConversions()
println(
getSupplier(
invoke1(42) { it * 2 }
@@ -44,7 +48,7 @@ fun run() {
}
// hashCode (directly):
if (foo.hashCode() == foo.hash().toInt()) {
if (foo.hashCode() == foo.hash().let { it.toInt() xor (it shr 32).toInt() }) {
// toString (virtually):
println(listOf(foo, pair).map { it.toString() }.min() == foo.description())
}
@@ -94,4 +98,57 @@ class MutablePairImpl(first: Int, second: Int) : NSObject(), MutablePairProtocol
override fun update(index: Int, sub: Int) {
elements[index] -= sub
}
}
}
fun testTypeOps() {
assertTrue(99.asAny() is NSNumber)
assertTrue(null.asAny() is NSNumber?)
assertFalse(null.asAny() is NSNumber)
assertFalse("".asAny() is NSNumber)
assertTrue("bar".asAny() is NSString)
assertTrue(Foo.asAny() is FooMeta)
assertTrue(Foo.asAny() is NSObjectMeta)
assertTrue(Foo.asAny() is NSObject)
assertFalse(Foo.asAny() is Foo)
assertTrue(NSString.asAny() is NSCopyingProtocolMeta)
assertFalse(NSString.asAny() is NSCopyingProtocol)
assertTrue(NSValue.asAny() is NSObjectProtocolMeta)
assertFalse(NSValue.asAny() is NSObjectProtocol) // Must be true, but not implemented properly yet.
assertEquals(3, ("foo" as NSString).length())
assertEquals(4, ((1..4).joinToString("") as NSString).length())
assertEquals(2, (listOf(0, 1) as NSArray).count())
assertEquals(42L, (42 as NSNumber).longLongValue())
assertFails { "bar" as NSNumber }
assertFails { 42 as NSArray }
assertFails { listOf(1) as NSString }
assertFails { NSObject() as Bar }
assertFails { NSObject() as NSValue }
MutablePairImpl(1, 2).asAny() as MutablePairProtocol
assertFails { MutablePairImpl(1, 2).asAny() as Foo }
}
fun testConversions() {
testMethodsOfAny(emptyList<Nothing>(), NSArray())
testMethodsOfAny(listOf(1, "foo"), nsArrayOf(1, "foo"))
testMethodsOfAny(42, NSNumber.numberWithInt(42), 17)
}
fun testMethodsOfAny(kotlinObject: Any, equalNsObject: NSObject, otherObject: Any = Any()) {
assertEquals(kotlinObject.hashCode(), equalNsObject.hashCode())
assertEquals(kotlinObject.toString(), equalNsObject.toString())
assertEquals(kotlinObject, equalNsObject)
assertEquals(equalNsObject, kotlinObject)
assertNotEquals(equalNsObject, otherObject)
}
fun nsArrayOf(vararg elements: Any): NSArray = NSMutableArray().apply {
elements.forEach {
this.addObject(it as ObjCObject)
}
}
fun Any?.asAny(): Any? = this
+1 -7
View File
@@ -418,12 +418,6 @@ inline void runDeallocationHooks(ObjHeader* obj) {
if (obj->has_meta_object()) {
ObjHeader::destroyMetaObject(&obj->typeInfoOrMeta_);
}
#if KONAN_OBJC_INTEROP
if (obj->type_info() == theObjCPointerHolderTypeInfo) {
void* objcPtr = *reinterpret_cast<void**>(obj + 1); // TODO: use more reliable layout description
objc_release(objcPtr);
}
#endif
}
inline void runDeallocationHooks(ContainerHeader* container) {
@@ -828,7 +822,7 @@ void ObjHeader::destroyMetaObject(TypeInfo** location) {
}
#ifdef KONAN_OBJC_INTEROP
Kotlin_ObjCExport_releaseAssociatedObject(meta->associatedObject);
Kotlin_ObjCExport_releaseAssociatedObject(meta->associatedObject_);
#endif
konanFreeMemory(meta);
+1 -1
View File
@@ -246,7 +246,7 @@ struct MetaObjHeader {
ObjHeader* counter_;
#ifdef KONAN_OBJC_INTEROP
void* associatedObject;
void* associatedObject_;
#endif
};
+3 -2
View File
@@ -12,11 +12,12 @@ extern "C" id objc_retain(id self);
extern "C" void objc_release(id self);
inline static id GetAssociatedObject(ObjHeader* obj) {
return (id)obj->meta_object()->associatedObject;
return (id)obj->meta_object()->associatedObject_;
}
// Note: this function shall not be used on shared objects.
inline static void SetAssociatedObject(ObjHeader* obj, id value) {
obj->meta_object()->associatedObject = (void*)value;
obj->meta_object()->associatedObject_ = (void*)value;
}
extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj);
+78 -23
View File
@@ -16,6 +16,7 @@
#import "Types.h"
#import "Memory.h"
#include "Natives.h"
#if KONAN_OBJC_INTEROP
@@ -36,9 +37,6 @@
#import "Utils.h"
#import "Exceptions.h"
// Note: defined by a compiler-generated bitcode.
extern "C" const uint8_t objCExportEnabled;
struct ObjCToKotlinMethodAdapter {
const char* selector;
const char* encoding;
@@ -111,6 +109,22 @@ inline static OBJ_GETTER(AllocInstanceWithAssociatedObject, const TypeInfo* type
static Class getOrCreateClass(const TypeInfo* typeInfo);
static void initializeClass(Class clazz);
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject);
static inline id AtomicSetAssociatedObject(ObjHeader* obj, id associatedObject) {
if (!obj->container()->permanentOrFrozen()) {
SetAssociatedObject(obj, associatedObject);
return associatedObject;
} else {
void* old = __sync_val_compare_and_swap(&obj->meta_object()->associatedObject_, nullptr, (void*)associatedObject);
if (old == nullptr) {
return associatedObject;
} else {
Kotlin_ObjCExport_releaseAssociatedObject((void*)associatedObject);
return (id)old;
}
}
}
@interface KotlinBase : NSObject <ConvertibleToKotlin, NSCopying>
@end;
@@ -154,13 +168,14 @@ static void initializeClass(Class clazz);
KotlinBase* result = [super allocWithZone:nil];
// TODO: should we call NSObject.init ?
UpdateRef(&result->kotlinObj, obj);
[result autorelease];
if (!obj->permanent()) {
SetAssociatedObject(obj, result);
return AtomicSetAssociatedObject(obj, result);
} else {
// TODO: permanent objects should probably be supported as custom types.
return result;
}
// TODO: permanent objects should probably be supported as custom types.
return [result autorelease];
}
-(instancetype)retain {
@@ -194,7 +209,7 @@ static void initializeClass(Class clazz);
@end;
extern "C" void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject) {
extern "C" ALWAYS_INLINE void Kotlin_ObjCExport_releaseAssociatedObject(void* associatedObject) {
if (associatedObject != nullptr) {
[((id)associatedObject) releaseAsAssociatedObject];
}
@@ -210,6 +225,27 @@ extern "C" id Kotlin_ObjCExport_convertUnit(ObjHeader* unitInstance) {
return instance;
}
extern "C" id objc_retainBlock(id self);
extern "C" id objc_retainAutoreleaseReturnValue(id self);
extern "C" id Kotlin_ObjCExport_CreateNSStringFromKString(ObjHeader* str) {
KChar* utf16Chars = CharArrayAddressOfElementAt(str->array(), 0);
auto numBytes = str->array()->count_ * sizeof(KChar);
if (str->permanent()) {
return [[[NSString alloc] initWithBytesNoCopy:utf16Chars
length:numBytes
encoding:NSUTF16LittleEndianStringEncoding
freeWhenDone:NO] autorelease];
} else {
// TODO: consider making NSString subclass to avoid copying here.
NSString* result = [[NSString alloc] initWithBytes:utf16Chars
length:numBytes
encoding:NSUTF16LittleEndianStringEncoding];
return objc_retainAutoreleaseReturnValue(AtomicSetAssociatedObject(str, result));
}
}
static const ObjCTypeAdapter* findAdapterByName(
const char* name,
const ObjCTypeAdapter** sortedAdapters,
@@ -315,9 +351,6 @@ static void initializeClass(Class clazz) {
@interface NSObject (NSObjectToKotlin) <ConvertibleToKotlin>
@end;
extern "C" id objc_retainBlock(id self);
extern "C" id objc_retainAutoreleaseReturnValue(id self);
@implementation NSObject (NSObjectToKotlin)
-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT {
const TypeInfo* typeInfo = getOrCreateTypeInfo(object_getClass(self));
@@ -494,16 +527,19 @@ static const TypeInfo* getFunctionTypeInfoForBlock(id block) {
static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj);
extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) {
template <bool retainAutorelease>
static ALWAYS_INLINE id Kotlin_ObjCExport_refToObjCImpl(ObjHeader* obj) {
if (obj == nullptr) return nullptr;
if (obj->has_meta_object()) {
id associatedObject = GetAssociatedObject(obj);
if (associatedObject != nullptr) {
return objc_retainAutoreleaseReturnValue(associatedObject);
return retainAutorelease ? objc_retainAutoreleaseReturnValue(associatedObject) : associatedObject;
}
}
// TODO: propagate [retainAutorelease] to the code below.
convertReferenceToObjC converter = (convertReferenceToObjC)obj->type_info()->writableInfo_->objCExport.convert;
if (converter != nullptr) {
return converter(obj);
@@ -512,14 +548,18 @@ extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) {
return Kotlin_ObjCExport_refToObjC_slowpath(obj);
}
extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) {
// TODO: in some cases (e.g. when converting a bridge argument) performing retain-autorelease is not necessary.
return Kotlin_ObjCExport_refToObjCImpl<true>(obj);
}
extern "C" ALWAYS_INLINE id Kotlin_Interop_refToObjC(ObjHeader* obj) {
if (obj == nullptr) {
return nullptr;
} else if (!objCExportEnabled || obj->type_info() == theObjCPointerHolderTypeInfo) {
return *reinterpret_cast<id*>(obj + 1); // First field.
} else {
return Kotlin_ObjCExport_refToObjC(obj);
}
return Kotlin_ObjCExport_refToObjCImpl<false>(obj);
}
extern "C" ALWAYS_INLINE OBJ_GETTER(Kotlin_Interop_refFromObjC, id obj) {
// TODO: consider removing this function.
RETURN_RESULT_OF(Kotlin_ObjCExport_refFromObjC, obj);
}
extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj) {
@@ -679,6 +719,16 @@ static const TypeInfo* getMostSpecificKotlinClass(const TypeInfo* typeInfo) {
return result;
}
static int getVtableSize(const TypeInfo* typeInfo) {
for (const TypeInfo* current = typeInfo; current != nullptr; current = current->superType_) {
auto typeAdapter = getTypeAdapter(current);
if (typeAdapter != nullptr) return typeAdapter->kotlinVtableSize;
}
RuntimeAssert(false, "");
return -1;
}
static void insertOrReplace(KStdVector<MethodTableRecord>& methodTable, MethodNameHash nameSignature, void* entryPoint) {
MethodTableRecord record = {nameSignature, entryPoint};
@@ -704,7 +754,7 @@ static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType) {
const ObjCTypeAdapter* superTypeAdapter = getTypeAdapter(superType);
const void * const * superVtable = nullptr;
int superVtableSize = getTypeAdapter(getMostSpecificKotlinClass(superType))->kotlinVtableSize;
int superVtableSize = getVtableSize(superType);
const MethodTableRecord* superMethodTable = nullptr;
int superMethodTableSize = 0;
@@ -802,7 +852,7 @@ static const TypeInfo* getOrCreateTypeInfo(Class clazz) {
Class superClass = class_getSuperclass(clazz);
const TypeInfo* superType = superClass == nullptr ?
theAnyTypeInfo :
theForeignObjCObjectTypeInfo :
getOrCreateTypeInfo(superClass);
LockGuard<SimpleMutex> lockGuard(typeInfoCreationMutex);
@@ -920,9 +970,14 @@ static void checkLoadedOnce() {
#else
extern "C" ALWAYS_INLINE id Kotlin_Interop_refToObjC(ObjHeader* obj) {
extern "C" ALWAYS_INLINE void* Kotlin_Interop_refToObjC(ObjHeader* obj) {
RuntimeAssert(false, "Unavailable operation");
return nullptr;
}
extern "C" ALWAYS_INLINE OBJ_GETTER(Kotlin_Interop_refFromObjC, void* obj) {
RuntimeAssert(false, "Unavailable operation");
RETURN_OBJ(nullptr);
}
#endif // KONAN_OBJC_INTEROP
+5 -3
View File
@@ -43,10 +43,12 @@ static inline void SetKotlinTypeInfo(Class clazz, const TypeInfo* typeInfo) {
GetKotlinClassData(clazz)->typeInfo = typeInfo;
}
const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) RUNTIME_NOTHROW;
const TypeInfo* GetObjCKotlinTypeInfo(ObjHeader* obj) RUNTIME_NOTHROW;
const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) {
void* objcPtr = *reinterpret_cast<void * const *>(obj + 1); // TODO: use more reliable layout description
const TypeInfo* GetObjCKotlinTypeInfo(ObjHeader* obj) {
RuntimeAssert(obj->has_meta_object(), "");
void* objcPtr = obj->meta_object()->associatedObject_;
RuntimeAssert(objcPtr != nullptr, "");
Class clazz = object_getClass(reinterpret_cast<id>(objcPtr));
return GetKotlinClassData(clazz)->typeInfo;
}
+24 -8
View File
@@ -44,18 +44,22 @@ namespace {
extern "C" {
id Kotlin_Interop_CreateNSStringFromKString(const ObjHeader* str) {
id Kotlin_ObjCExport_CreateNSStringFromKString(ObjHeader* str);
id Kotlin_Interop_CreateNSStringFromKString(ObjHeader* str) {
// Note: this function is just a bit specialized [Kotlin_Interop_refToObjC].
if (str == nullptr) {
return nullptr;
}
const KChar* utf16Chars = CharArrayAddressOfElementAt(str->array(), 0);
if (str->has_meta_object()) {
void* associatedObject = str->meta_object()->associatedObject_;
if (associatedObject != nullptr) {
return (id)associatedObject;
}
}
NSString* result = [[[getNSStringClass() alloc] initWithBytes:utf16Chars
length:str->array()->count_*sizeof(KChar)
encoding:NSUTF16LittleEndianStringEncoding] autorelease];
return result;
return Kotlin_ObjCExport_CreateNSStringFromKString(str);
}
OBJ_GETTER(Kotlin_Interop_CreateKStringFromNSString, NSString* str) {
@@ -78,7 +82,7 @@ OBJ_GETTER(Kotlin_Interop_ObjCToString, id <NSObject> ptr) {
}
KInt Kotlin_Interop_ObjCHashCode(id <NSObject> ptr) {
KLong hash = ptr.hash;
uint64_t hash = ptr.hash;
return (KInt)(hash ^ (hash >> 32));
}
@@ -149,6 +153,18 @@ KRef Kotlin_Interop_unwrapKotlinObjectHolder(id holder) {
return [((KotlinObjectHolder*)holder) ref];
}
KBoolean Kotlin_Interop_DoesObjectConformToProtocol(id obj, void* prot, BOOL isMeta) {
BOOL objectIsClass = class_isMetaClass(object_getClass(obj));
if ((isMeta && !objectIsClass) || (!isMeta && objectIsClass)) return false;
// TODO: handle root classes properly.
return [((id<NSObject>)obj) conformsToProtocol:(Protocol*)prot];
}
KBoolean Kotlin_Interop_IsObjectKindOfClass(id obj, void* cls) {
return [((id<NSObject>)obj) isKindOfClass:(Class)cls];
}
} // extern "C"
#else // KONAN_OBJC_INTEROP
+1 -1
View File
@@ -83,7 +83,7 @@ extern const TypeInfo* theBooleanArrayTypeInfo;
extern const TypeInfo* theStringTypeInfo;
extern const TypeInfo* theThrowableTypeInfo;
extern const TypeInfo* theUnitTypeInfo;
extern const TypeInfo* theObjCPointerHolderTypeInfo;
extern const TypeInfo* theForeignObjCObjectTypeInfo;
KBoolean IsInstance(const ObjHeader* obj, const TypeInfo* type_info) RUNTIME_PURE;
void CheckCast(const ObjHeader* obj, const TypeInfo* type_info);