Unify interop with Objective-C in both directions
Also support casts to Objective-C types
This commit is contained in:
committed by
SvyatoslavScherbina
parent
38f896649c
commit
77139d58f8
+1
-7
@@ -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()
|
||||
|
||||
|
||||
+3
-5
@@ -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 {
|
||||
|
||||
+1
@@ -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) {
|
||||
|
||||
+6
-9
@@ -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"),
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+72
-7
@@ -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()
|
||||
|
||||
+4
-9
@@ -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,
|
||||
|
||||
+99
-42
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -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)
|
||||
|
||||
+1
-10
@@ -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()
|
||||
|
||||
+50
-32
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+42
-11
@@ -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) {
|
||||
|
||||
+4
-1
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user