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
@@ -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