Implement very basic Objective-C interop
Also refactor StubGenerator
This commit is contained in:
committed by
SvyatoslavScherbina
parent
a38835cb46
commit
d3185e3e19
@@ -61,6 +61,7 @@ sourceSets {
|
||||
srcDir 'compiler/ir/backend.native/src/'
|
||||
srcDir "${rootProject.projectDir}/shared/src/main/kotlin"
|
||||
}
|
||||
resources.srcDir 'compiler/ir/backend.native/resources/'
|
||||
}
|
||||
cli_bc {
|
||||
java.srcDir 'cli.bc/src'
|
||||
@@ -213,7 +214,8 @@ task jars(type: Jar) {
|
||||
from 'build/classes/cli_bc',
|
||||
'build/classes/compiler',
|
||||
'build/classes/hashInteropStubs',
|
||||
'build/classes/llvmInteropStubs'
|
||||
'build/classes/llvmInteropStubs',
|
||||
'build/resources/compiler'
|
||||
|
||||
dependsOn 'build', ':runtime:hostRuntime', 'external_jars'
|
||||
}
|
||||
|
||||
+1
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.backend.konan.ObjCOverridabilityCondition
|
||||
+26
@@ -17,6 +17,7 @@
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -100,6 +101,31 @@ internal class InteropBuiltIns(builtIns: KonanBuiltIns) {
|
||||
val signExtend = packageScope.getContributedFunctions("signExtend").single()
|
||||
|
||||
val narrow = packageScope.getContributedFunctions("narrow").single()
|
||||
|
||||
val objCObject = packageScope.getContributedClassifier("ObjCObject") as ClassDescriptor
|
||||
val objCPointerHolder = packageScope.getContributedClassifier("ObjCPointerHolder") as ClassDescriptor
|
||||
|
||||
val objCPointerHolderValue = objCPointerHolder.unsubstitutedMemberScope
|
||||
.getContributedDescriptors().filterIsInstance<PropertyDescriptor>().single()
|
||||
|
||||
val objCObjectInitFromPtr = packageScope.getContributedFunctions("initFromPtr").single()
|
||||
val objCObjectInitFrom = packageScope.getContributedFunctions("initFrom").single()
|
||||
|
||||
val allocObjCObject = packageScope.getContributedFunctions("allocObjCObject").single()
|
||||
|
||||
val getObjCClass = packageScope.getContributedFunctions("getObjCClass").single()
|
||||
|
||||
val objCObjectRawPtr = packageScope.getContributedVariables("rawPtr").single {
|
||||
val extensionReceiverType = it.extensionReceiverParameter?.type
|
||||
extensionReceiverType != null && !extensionReceiverType.isMarkedNullable &&
|
||||
TypeUtils.getClassDescriptor(extensionReceiverType) == objCObject
|
||||
}
|
||||
|
||||
val getObjCReceiverOrSuper = packageScope.getContributedFunctions("getReceiverOrSuper").single()
|
||||
|
||||
val getObjCMessenger = packageScope.getContributedFunctions("getMessenger").single()
|
||||
val getObjCMessengerLU = packageScope.getContributedFunctions("getMessengerLU").single()
|
||||
|
||||
}
|
||||
|
||||
private fun MemberScope.getContributedVariables(name: String) =
|
||||
|
||||
+156
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.getStringValue
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.*
|
||||
|
||||
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"))
|
||||
private val objCMethodFqName = interopPackageName.child(Name.identifier("ObjCMethod"))
|
||||
private val objCBridgeFqName = interopPackageName.child(Name.identifier("ObjCBridge"))
|
||||
|
||||
fun ClassDescriptor.isObjCClass(): Boolean =
|
||||
this.getAllSuperClassifiers().any { it.fqNameSafe == objCObjectFqName } &&
|
||||
this.containingDeclaration.fqNameSafe != interopPackageName
|
||||
|
||||
fun ClassDescriptor.isExternalObjCClass(): Boolean = this.isObjCClass() &&
|
||||
this.parentsWithSelf.filterIsInstance<ClassDescriptor>().any {
|
||||
it.annotations.findAnnotation(externalObjCClassFqName) != null
|
||||
}
|
||||
|
||||
fun ClassDescriptor.isObjCMetaClass(): Boolean = this.getAllSuperClassifiers().any {
|
||||
it.fqNameSafe == objCClassFqName
|
||||
}
|
||||
|
||||
fun FunctionDescriptor.isObjCClassMethod() =
|
||||
this.containingDeclaration.let { it is ClassDescriptor && it.isObjCClass() }
|
||||
|
||||
fun FunctionDescriptor.isExternalObjCClassMethod() =
|
||||
this.containingDeclaration.let { it is ClassDescriptor && it.isExternalObjCClass() }
|
||||
|
||||
// Special case: methods from Kotlin Objective-C classes can be called virtually from bridges.
|
||||
fun FunctionDescriptor.canObjCClassMethodBeCalledVirtually(overriddenDescriptor: FunctionDescriptor) =
|
||||
overriddenDescriptor.isOverridable && this.kind.isReal && !this.isExternalObjCClassMethod()
|
||||
|
||||
fun ClassDescriptor.isKotlinObjCClass(): Boolean = this.isObjCClass() && !this.isExternalObjCClass()
|
||||
|
||||
data class ObjCMethodInfo(val descriptor: FunctionDescriptor,
|
||||
val bridge: FunctionDescriptor,
|
||||
val selector: String,
|
||||
val encoding: String,
|
||||
val imp: String)
|
||||
|
||||
private fun CallableDescriptor.getBridgeAnnotation() =
|
||||
this.annotations.findAnnotation(objCBridgeFqName)
|
||||
|
||||
private fun FunctionDescriptor.decodeObjCMethodAnnotation(): ObjCMethodInfo? {
|
||||
assert (this.kind.isReal)
|
||||
|
||||
val methodAnnotation = this.annotations.findAnnotation(objCMethodFqName) ?: return null
|
||||
val packageFragment = this.parents.filterIsInstance<PackageFragmentDescriptor>().single()
|
||||
|
||||
val bridgeName = methodAnnotation.getStringValue("bridge")
|
||||
|
||||
val bridge = packageFragment.getMemberScope()
|
||||
.getContributedFunctions(Name.identifier(bridgeName), NoLookupLocation.FROM_BACKEND)
|
||||
.single()
|
||||
|
||||
val bridgeAnnotation = bridge.getBridgeAnnotation()!!
|
||||
return ObjCMethodInfo(
|
||||
descriptor = this,
|
||||
bridge = bridge,
|
||||
selector = methodAnnotation.getStringValue("selector"),
|
||||
encoding = bridgeAnnotation.getStringValue("encoding"),
|
||||
imp = bridgeAnnotation.getStringValue("imp")
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param onlyExternal indicates whether to accept overriding methods from Kotlin classes
|
||||
*/
|
||||
private fun FunctionDescriptor.getObjCMethodInfo(onlyExternal: Boolean): ObjCMethodInfo? {
|
||||
if (this.kind.isReal) {
|
||||
this.decodeObjCMethodAnnotation()?.let { return it }
|
||||
|
||||
if (onlyExternal) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return this.overriddenDescriptors.asSequence().mapNotNull { it.getObjCMethodInfo(onlyExternal) }.firstOrNull()
|
||||
}
|
||||
|
||||
fun FunctionDescriptor.getExternalObjCMethodInfo(): ObjCMethodInfo? = this.getObjCMethodInfo(onlyExternal = true)
|
||||
|
||||
fun FunctionDescriptor.getObjCMethodInfo(): ObjCMethodInfo? = this.getObjCMethodInfo(onlyExternal = false)
|
||||
|
||||
/**
|
||||
* Describes method overriding rules for Objective-C methods.
|
||||
*
|
||||
* This class is applied at [org.jetbrains.kotlin.resolve.OverridingUtil] as configured with
|
||||
* `META-INF/services/org.jetbrains.kotlin.resolve.ExternalOverridabilityCondition` resource.
|
||||
*/
|
||||
class ObjCOverridabilityCondition : ExternalOverridabilityCondition {
|
||||
|
||||
override fun getContract() = ExternalOverridabilityCondition.Contract.BOTH
|
||||
|
||||
override fun isOverridable(
|
||||
superDescriptor: CallableDescriptor,
|
||||
subDescriptor: CallableDescriptor,
|
||||
subClassDescriptor: ClassDescriptor?
|
||||
): ExternalOverridabilityCondition.Result {
|
||||
|
||||
assert(superDescriptor.name == subDescriptor.name)
|
||||
|
||||
val superClass = superDescriptor.containingDeclaration as? ClassDescriptor
|
||||
if (superClass == null || !superClass.isObjCClass()) {
|
||||
return ExternalOverridabilityCondition.Result.UNKNOWN
|
||||
}
|
||||
|
||||
return if (areSelectorsEqual(superDescriptor, subDescriptor)) {
|
||||
ExternalOverridabilityCondition.Result.OVERRIDABLE
|
||||
} else {
|
||||
ExternalOverridabilityCondition.Result.INCOMPATIBLE
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun areSelectorsEqual(first: CallableDescriptor, second: CallableDescriptor): Boolean {
|
||||
// The original Objective-C method selector is represented as
|
||||
// function name and parameter names (except first).
|
||||
|
||||
if (first.valueParameters.size != second.valueParameters.size) {
|
||||
return false
|
||||
}
|
||||
|
||||
first.valueParameters.forEachIndexed { index, parameter ->
|
||||
if (index > 0 && parameter.name != second.valueParameters[index].name) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
}
|
||||
+9
-3
@@ -16,7 +16,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan.descriptors
|
||||
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.*
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.functionName
|
||||
import org.jetbrains.kotlin.backend.konan.llvm.localHash
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
@@ -35,9 +35,15 @@ internal class OverriddenFunctionDescriptor(val descriptor: FunctionDescriptor,
|
||||
get() = descriptor.target.bridgeDirectionsTo(overriddenDescriptor)
|
||||
|
||||
val canBeCalledVirtually: Boolean
|
||||
get() {
|
||||
if (overriddenDescriptor.isObjCClassMethod()) {
|
||||
return descriptor.canObjCClassMethodBeCalledVirtually(this.overriddenDescriptor)
|
||||
}
|
||||
|
||||
// We check that either method is open, or one of declarations it overrides is open.
|
||||
get() = overriddenDescriptor.isOverridable
|
||||
|| DescriptorUtils.getAllOverriddenDeclarations(overriddenDescriptor).any { it.isOverridable }
|
||||
return (overriddenDescriptor.isOverridable
|
||||
|| DescriptorUtils.getAllOverriddenDeclarations(overriddenDescriptor).any { it.isOverridable })
|
||||
}
|
||||
|
||||
val inheritsBridge: Boolean
|
||||
get() = !descriptor.kind.isReal
|
||||
|
||||
+7
@@ -28,6 +28,7 @@ import org.jetbrains.kotlin.builtins.getFunctionalClassKind
|
||||
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||
import org.jetbrains.kotlin.builtins.isSuspendFunctionType
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor
|
||||
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
@@ -308,3 +309,9 @@ internal val FunctionDescriptor.needsInlining: Boolean
|
||||
internal val FunctionDescriptor.needsSerializedIr: Boolean
|
||||
get() = (this.needsInlining && this.isExported())
|
||||
|
||||
fun AnnotationDescriptor.getStringValue(name: String): String {
|
||||
val constantValue = this.allValueArguments.entries.single {
|
||||
it.key.asString() == name
|
||||
}.value
|
||||
return constantValue.value as String
|
||||
}
|
||||
|
||||
+12
@@ -49,6 +49,18 @@ internal class KonanSymbols(context: Context, val symbolTable: SymbolTable): Sym
|
||||
|
||||
val interopCPointerGetRawValue = symbolTable.referenceSimpleFunction(context.interopBuiltIns.cPointerGetRawValue)
|
||||
|
||||
val interopAllocObjCObject = symbolTable.referenceSimpleFunction(context.interopBuiltIns.allocObjCObject)
|
||||
|
||||
val interopGetObjCClass = symbolTable.referenceSimpleFunction(context.interopBuiltIns.getObjCClass)
|
||||
|
||||
val interopObjCObjectInitFromPtr =
|
||||
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectInitFromPtr)
|
||||
|
||||
val interopObjCObjectInitFrom = symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectInitFrom)
|
||||
|
||||
val interopObjCObjectRawValueGetter =
|
||||
symbolTable.referenceSimpleFunction(context.interopBuiltIns.objCObjectRawPtr.getter!!)
|
||||
|
||||
val getNativeNullPtr = symbolTable.referenceSimpleFunction(context.builtIns.getNativeNullPtr)
|
||||
|
||||
val boxFunctions = ValueType.values().associate {
|
||||
|
||||
+14
-2
@@ -18,7 +18,10 @@ package org.jetbrains.kotlin.backend.konan.llvm
|
||||
|
||||
import llvm.LLVMTypeRef
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isAbstract
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.isUnit
|
||||
import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo
|
||||
import org.jetbrains.kotlin.backend.konan.isExternalObjCClass
|
||||
import org.jetbrains.kotlin.backend.konan.isValueType
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
@@ -148,8 +151,14 @@ private val FunctionDescriptor.signature: String
|
||||
|
||||
// TODO: rename to indicate that it has signature included
|
||||
internal val FunctionDescriptor.functionName: String
|
||||
get() = with(this.original) { // basic support for generics
|
||||
"$name$signature"
|
||||
get() {
|
||||
with(this.original) { // basic support for generics
|
||||
this.getObjCMethodInfo()?.let {
|
||||
return "objc:${it.selector}"
|
||||
}
|
||||
|
||||
return "$name$signature"
|
||||
}
|
||||
}
|
||||
|
||||
internal val FunctionDescriptor.symbolName: String
|
||||
@@ -228,3 +237,6 @@ internal val ClassDescriptor.objectInstanceFieldSymbolName: String
|
||||
|
||||
return "kobjref:$fqNameSafe"
|
||||
}
|
||||
|
||||
internal val ClassDescriptor.typeInfoHasVtableAttached: Boolean
|
||||
get() = !this.isAbstract() && !this.isExternalObjCClass()
|
||||
|
||||
+27
@@ -376,6 +376,33 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
||||
return LLVMBuildLandingPad(builder, landingpadType, personalityFunction, numClauses, name)!!
|
||||
}
|
||||
|
||||
inline fun ifThenElse(
|
||||
condition: LLVMValueRef,
|
||||
thenValue: LLVMValueRef,
|
||||
elseBlock: () -> LLVMValueRef
|
||||
): LLVMValueRef {
|
||||
val resultType = thenValue.type
|
||||
|
||||
val bbExit = basicBlock(locationInfo = position())
|
||||
val resultPhi = appendingTo(bbExit) {
|
||||
phi(resultType)
|
||||
}
|
||||
|
||||
val bbElse = basicBlock(locationInfo = position())
|
||||
|
||||
condBr(condition, bbExit, bbElse)
|
||||
assignPhis(resultPhi to thenValue)
|
||||
|
||||
appendingTo(bbElse) {
|
||||
val elseValue = elseBlock()
|
||||
br(bbExit)
|
||||
assignPhis(resultPhi to elseValue)
|
||||
}
|
||||
|
||||
positionAtEnd(bbExit)
|
||||
return resultPhi
|
||||
}
|
||||
|
||||
internal fun debugLocation(locationInfo: LocationInfo): DILocationRef? {
|
||||
if (!context.shouldContainDebugInfo()) return null
|
||||
update(currentBlock, locationInfo)
|
||||
|
||||
+3
@@ -326,6 +326,9 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
||||
val throwExceptionFunction = importRtFunction("ThrowException")
|
||||
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
|
||||
|
||||
val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") }
|
||||
val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") }
|
||||
|
||||
private val personalityFunctionName = when (context.config.targetManager.target) {
|
||||
KonanTarget.MINGW -> "__gxx_personality_seh0"
|
||||
else -> "__gxx_personality_v0"
|
||||
|
||||
+167
-10
@@ -39,6 +39,7 @@ import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptChildrenVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.acceptVoid
|
||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.DescriptorUtils
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
@@ -156,6 +157,8 @@ internal fun verifyModule(llvmModule: LLVMModuleRef, current: String = "") {
|
||||
internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid {
|
||||
val generator = RTTIGenerator(context)
|
||||
|
||||
val kotlinObjCClassInfoGenerator = KotlinObjCClassInfoGenerator(context)
|
||||
|
||||
override fun visitElement(element: IrElement) {
|
||||
element.acceptChildrenVoid(this)
|
||||
}
|
||||
@@ -163,12 +166,17 @@ internal class RTTIGeneratorVisitor(context: Context) : IrElementVisitorVoid {
|
||||
override fun visitClass(declaration: IrClass) {
|
||||
super.visitClass(declaration)
|
||||
|
||||
if (declaration.descriptor.isIntrinsic) {
|
||||
val descriptor = declaration.descriptor
|
||||
if (descriptor.isIntrinsic) {
|
||||
// do not generate any code for intrinsic classes as they require special handling
|
||||
return
|
||||
}
|
||||
|
||||
generator.generate(declaration.descriptor)
|
||||
generator.generate(descriptor)
|
||||
|
||||
if (descriptor.isKotlinObjCClass()) {
|
||||
kotlinObjCClassInfoGenerator.generate(descriptor)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -734,7 +742,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
functionGenerationContext.condBr(condition, bbExit, bbInit)
|
||||
|
||||
functionGenerationContext.positionAtEnd(bbInit)
|
||||
val typeInfo = codegen.typeInfoValue(value.descriptor)
|
||||
val typeInfo = typeInfoForAllocation(value.descriptor)
|
||||
val initFunction = value.descriptor.constructors.first { it.valueParameters.size == 0 }
|
||||
val ctor = codegen.llvmFunction(initFunction)
|
||||
val args = listOf(objectPtr, typeInfo, ctor)
|
||||
@@ -1350,13 +1358,32 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
private fun fieldPtrOfClass(thisPtr: LLVMValueRef, value: PropertyDescriptor): LLVMValueRef {
|
||||
val fieldInfo = context.llvmDeclarations.forField(value)
|
||||
|
||||
val classDescriptor = value.containingDeclaration as ClassDescriptor
|
||||
val typePtr = pointerType(fieldInfo.classBodyType)
|
||||
val objectPtr = LLVMBuildGEP(functionGenerationContext.builder, thisPtr, cValuesOf(kImmOne), 1, "")
|
||||
val typedObjPtr = functionGenerationContext.bitcast(typePtr, objectPtr!!)
|
||||
val fieldPtr = LLVMBuildStructGEP(functionGenerationContext.builder, typedObjPtr, fieldInfo.index, "")
|
||||
|
||||
val bodyPtr = getObjectBodyPtr(classDescriptor, thisPtr)
|
||||
|
||||
val typedBodyPtr = functionGenerationContext.bitcast(typePtr, bodyPtr)
|
||||
val fieldPtr = LLVMBuildStructGEP(functionGenerationContext.builder, typedBodyPtr, fieldInfo.index, "")
|
||||
return fieldPtr!!
|
||||
}
|
||||
|
||||
private fun getObjectBodyPtr(classDescriptor: ClassDescriptor, objectPtr: LLVMValueRef): LLVMValueRef {
|
||||
return if (classDescriptor.isObjCClass()) {
|
||||
assert(classDescriptor.isKotlinObjCClass())
|
||||
|
||||
val objCPtr = callDirect(context.interopBuiltIns.objCPointerHolderValue.getter!!,
|
||||
listOf(objectPtr), Lifetime.IRRELEVANT)
|
||||
|
||||
val objCDeclarations = context.llvmDeclarations.forClass(classDescriptor).objCDeclarations!!
|
||||
val bodyOffset = functionGenerationContext.load(objCDeclarations.bodyOffsetGlobal.llvmGlobal)
|
||||
|
||||
functionGenerationContext.gep(objCPtr, bodyOffset)
|
||||
} else {
|
||||
LLVMBuildGEP(functionGenerationContext.builder, objectPtr, cValuesOf(kImmOne), 1, "")!!
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
private fun evaluateConst(value: IrConst<*>): LLVMValueRef {
|
||||
@@ -1871,7 +1898,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
functionGenerationContext.allocArray(codegen.typeInfoValue(constructedClass), count = kImmZero,
|
||||
lifetime = resultLifetime(callee))
|
||||
} else {
|
||||
functionGenerationContext.allocInstance(codegen.typeInfoValue(constructedClass), resultLifetime(callee))
|
||||
functionGenerationContext.allocInstance(typeInfoForAllocation(constructedClass), resultLifetime(callee))
|
||||
}
|
||||
evaluateSimpleFunctionCall(callee.descriptor,
|
||||
listOf(thisValue) + args, Lifetime.IRRELEVANT /* constructor doesn't return anything */)
|
||||
@@ -1879,6 +1906,15 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
}
|
||||
}
|
||||
|
||||
private fun typeInfoForAllocation(constructedClass: ClassDescriptor): LLVMValueRef {
|
||||
val descriptorForTypeInfo = if (constructedClass.isObjCClass()) {
|
||||
context.interopBuiltIns.objCPointerHolder
|
||||
} else {
|
||||
constructedClass
|
||||
}
|
||||
return codegen.typeInfoValue(descriptorForTypeInfo)
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
|
||||
private fun evaluateIntrinsicCall(callee: IrCall, args: List<LLVMValueRef>): LLVMValueRef {
|
||||
@@ -1934,10 +1970,122 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
LLVMBuildSExt(functionGenerationContext.builder, intPtrValue, resultType, "")!!
|
||||
}
|
||||
}
|
||||
interop.objCObjectInitFromPtr -> {
|
||||
genObjCObjectInitFromPtr(args)
|
||||
}
|
||||
|
||||
interop.getObjCReceiverOrSuper -> {
|
||||
genGetObjCReceiverOrSuper(args)
|
||||
}
|
||||
|
||||
interop.getObjCClass -> {
|
||||
val typeArgument = callee.getTypeArgument(descriptor.typeParameters.single())
|
||||
val classDescriptor = TypeUtils.getClassDescriptor(typeArgument!!)!!
|
||||
genGetObjCClass(classDescriptor)
|
||||
}
|
||||
|
||||
interop.getObjCMessenger -> {
|
||||
genGetObjCMessenger(args, isLU = false)
|
||||
}
|
||||
interop.getObjCMessengerLU -> {
|
||||
genGetObjCMessenger(args, isLU = true)
|
||||
}
|
||||
|
||||
else -> TODO(callee.descriptor.original.toString())
|
||||
}
|
||||
}
|
||||
|
||||
private fun genGetObjCClass(classDescriptor: ClassDescriptor): LLVMValueRef {
|
||||
assert(!classDescriptor.isInterface)
|
||||
|
||||
return if (classDescriptor.isExternalObjCClass()) {
|
||||
val lookUpFunction = context.llvm.externalFunction("objc_lookUpClass",
|
||||
functionType(int8TypePtr, false, int8TypePtr))
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun genGetObjCMessenger(args: List<LLVMValueRef>, isLU: Boolean): LLVMValueRef {
|
||||
val gen = functionGenerationContext
|
||||
|
||||
// 'LU' means "large or unaligned".
|
||||
|
||||
// objc_msgSend*_stret functions must be used when return value is returned through memory
|
||||
// pointed by implicit argument, which is passed on the register that would otherwise be used for receiver.
|
||||
// On aarch64 it is never the case, since such implicit argument gets passed on x8.
|
||||
// On x86_64 it is the case if the return value takes more than 16 bytes or is the structure with
|
||||
// unaligned fields (there are some complicated exceptions currently ignored). The latter condition
|
||||
// is "encoded" by stub generator by emitting either `getMessenger` or `getMessengerLU` intrinsic call.
|
||||
val isStret = when (context.config.targetManager.target) {
|
||||
KonanTarget.MACBOOK, KonanTarget.IPHONE_SIM -> isLU // x86_64
|
||||
KonanTarget.IPHONE -> false // aarch64
|
||||
else -> TODO()
|
||||
}
|
||||
|
||||
val messengerNameSuffix = if (isStret) "_stret" else ""
|
||||
|
||||
val functionType = functionType(int8TypePtr, true, int8TypePtr, int8TypePtr)
|
||||
|
||||
val normalMessenger = context.llvm.externalFunction("objc_msgSend$messengerNameSuffix", functionType)
|
||||
val superMessenger = context.llvm.externalFunction("objc_msgSendSuper$messengerNameSuffix", functionType)
|
||||
|
||||
val superClass = args.single()
|
||||
val messenger = LLVMBuildSelect(gen.builder,
|
||||
If = gen.icmpEq(superClass, kNullInt8Ptr),
|
||||
Then = normalMessenger,
|
||||
Else = superMessenger,
|
||||
Name = ""
|
||||
)!!
|
||||
|
||||
return gen.bitcast(int8TypePtr, messenger)
|
||||
}
|
||||
|
||||
private fun genObjCObjectInitFromPtr(args: List<LLVMValueRef>): LLVMValueRef {
|
||||
return callDirect(context.interopBuiltIns.objCPointerHolder.unsubstitutedPrimaryConstructor!!,
|
||||
args, Lifetime.IRRELEVANT)
|
||||
}
|
||||
|
||||
private fun genGetObjCReceiverOrSuper(args: List<LLVMValueRef>): LLVMValueRef {
|
||||
val gen = functionGenerationContext
|
||||
|
||||
assert(args.size == 2)
|
||||
val receiver = args[0]
|
||||
val superClass = args[1]
|
||||
|
||||
val superClassIsNull = gen.icmpEq(superClass, kNullInt8Ptr)
|
||||
|
||||
return gen.ifThenElse(superClassIsNull, receiver) {
|
||||
val structType = structType(kInt8Ptr, kInt8Ptr)
|
||||
val ptr = gen.alloca(structType)
|
||||
gen.store(receiver,
|
||||
LLVMBuildGEP(gen.builder, ptr, cValuesOf(kImmZero, kImmZero), 2, "")!!)
|
||||
|
||||
gen.store(superClass,
|
||||
LLVMBuildGEP(gen.builder, ptr, cValuesOf(kImmZero, kImmOne), 2, "")!!)
|
||||
|
||||
gen.bitcast(int8TypePtr, ptr)
|
||||
}
|
||||
}
|
||||
|
||||
//-------------------------------------------------------------------------//
|
||||
private val kImmZero = LLVMConstInt(LLVMInt32Type(), 0, 1)!!
|
||||
private val kImmOne = LLVMConstInt(LLVMInt32Type(), 1, 1)!!
|
||||
@@ -2008,11 +2156,16 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
fun callVirtual(descriptor: FunctionDescriptor, args: List<LLVMValueRef>,
|
||||
resultLifetime: Lifetime): LLVMValueRef {
|
||||
assert(LLVMTypeOf(args[0]) == codegen.kObjHeaderPtr)
|
||||
val typeInfoPtrPtr = LLVMBuildStructGEP(functionGenerationContext.builder, args[0], 0 /* type_info */, "")!!
|
||||
val typeInfoPtr = functionGenerationContext.load(typeInfoPtrPtr)
|
||||
assert (typeInfoPtr.type == codegen.kTypeInfoPtr)
|
||||
|
||||
val owner = descriptor.containingDeclaration as ClassDescriptor
|
||||
|
||||
val typeInfoPtr: LLVMValueRef = if (owner.isObjCClass()) {
|
||||
call(context.llvm.getObjCKotlinTypeInfo, listOf(args.first()))
|
||||
} else {
|
||||
val typeInfoPtrPtr = LLVMBuildStructGEP(functionGenerationContext.builder, args[0], 0 /* type_info */, "")!!
|
||||
functionGenerationContext.load(typeInfoPtrPtr)
|
||||
}
|
||||
assert (typeInfoPtr.type == codegen.kTypeInfoPtr)
|
||||
val llvmMethod = if (!owner.isInterface) {
|
||||
// If this is a virtual method of the class - we can call via vtable.
|
||||
val index = context.getVtableBuilder(owner).vtableIndex(descriptor)
|
||||
@@ -2068,6 +2221,10 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
val constructedClass = functionGenerationContext.constructedClass!!
|
||||
val thisPtr = currentCodeContext.genGetValue(constructedClass.thisAsReceiverParameter)
|
||||
|
||||
if (constructedClass.isObjCClass()) {
|
||||
return codegen.theUnitInstanceRef.llvm
|
||||
}
|
||||
|
||||
val thisPtrArgType = codegen.getLLVMType(descriptor.allParameters[0].type)
|
||||
val thisPtrArg = if (thisPtr.type == thisPtrArgType) {
|
||||
thisPtr
|
||||
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan.llvm
|
||||
|
||||
import llvm.LLVMStoreSizeOfType
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.ObjCMethodInfo
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.contributedMethods
|
||||
import org.jetbrains.kotlin.backend.konan.getObjCMethodInfo
|
||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.ConstructorDescriptor
|
||||
import org.jetbrains.kotlin.descriptors.isFinalClass
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
|
||||
|
||||
internal class KotlinObjCClassInfoGenerator(override val context: Context) : ContextUtils {
|
||||
fun generate(descriptor: ClassDescriptor) {
|
||||
assert(descriptor.isFinalClass)
|
||||
|
||||
val objCLLvmDeclarations = context.llvmDeclarations.forClass(descriptor).objCDeclarations!!
|
||||
|
||||
val instanceMethods = descriptor.generateMethodDescs()
|
||||
|
||||
val companionObjectDescriptor = descriptor.companionObjectDescriptor
|
||||
val classMethods = companionObjectDescriptor?.generateMethodDescs() ?: emptyList()
|
||||
|
||||
val superclassName = descriptor.getSuperClassNotAny()!!.name.asString()
|
||||
val protocolNames = descriptor.getSuperInterfaces().map { it.name.asString().removeSuffix("Protocol") }
|
||||
|
||||
val bodySize =
|
||||
LLVMStoreSizeOfType(llvmTargetData, context.llvmDeclarations.forClass(descriptor).bodyType).toInt()
|
||||
|
||||
val className = if (descriptor.isExported()) {
|
||||
staticData.cStringLiteral(descriptor.fqNameSafe.asString())
|
||||
} else {
|
||||
NullPointer(int8Type) // Generate as anonymous.
|
||||
}
|
||||
|
||||
val info = Struct(runtime.kotlinObjCClassInfo,
|
||||
className,
|
||||
|
||||
staticData.cStringLiteral(superclassName),
|
||||
staticData.placeGlobalConstArray("", int8TypePtr,
|
||||
protocolNames.map { staticData.cStringLiteral(it) } + NullPointer(int8Type)),
|
||||
|
||||
staticData.placeGlobalConstArray("", runtime.objCMethodDescription, instanceMethods),
|
||||
Int32(instanceMethods.size),
|
||||
|
||||
staticData.placeGlobalConstArray("", runtime.objCMethodDescription, classMethods),
|
||||
Int32(classMethods.size),
|
||||
|
||||
Int32(bodySize),
|
||||
objCLLvmDeclarations.bodyOffsetGlobal.pointer,
|
||||
|
||||
descriptor.typeInfoPtr,
|
||||
companionObjectDescriptor?.typeInfoPtr ?: NullPointer(runtime.typeInfoType),
|
||||
|
||||
objCLLvmDeclarations.classPointerGlobal.pointer
|
||||
)
|
||||
|
||||
objCLLvmDeclarations.classInfoGlobal.setInitializer(info)
|
||||
|
||||
objCLLvmDeclarations.classPointerGlobal.setInitializer(NullPointer(int8Type))
|
||||
objCLLvmDeclarations.bodyOffsetGlobal.setInitializer(Int32(0))
|
||||
}
|
||||
|
||||
private fun generateMethodDesc(info: ObjCMethodInfo): ConstValue {
|
||||
return Struct(runtime.objCMethodDescription,
|
||||
staticData.cStringLiteral(info.selector),
|
||||
staticData.cStringLiteral(info.encoding),
|
||||
constPointer(context.llvm.externalFunction(info.imp, functionType(voidType))).bitcast(int8TypePtr)
|
||||
)
|
||||
}
|
||||
|
||||
private fun ClassDescriptor.generateMethodDescs(): List<ConstValue> =
|
||||
this.unsubstitutedMemberScope.contributedMethods.filter {
|
||||
it.kind.isReal && it !is ConstructorDescriptor
|
||||
}.mapNotNull { it.getObjCMethodInfo() }.map { generateMethodDesc(it) }
|
||||
}
|
||||
+37
-3
@@ -21,6 +21,7 @@ import llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.KonanConfigKeys
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.*
|
||||
import org.jetbrains.kotlin.backend.konan.isKotlinObjCClass
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.serialization.deserialization.descriptors.*
|
||||
import org.jetbrains.kotlin.ir.IrElement
|
||||
@@ -75,10 +76,17 @@ internal class ClassLlvmDeclarations(
|
||||
val fields: List<PropertyDescriptor>, // TODO: it is not an LLVM declaration.
|
||||
val typeInfoGlobal: StaticData.Global,
|
||||
val typeInfo: ConstPointer,
|
||||
val singletonDeclarations: SingletonLlvmDeclarations?)
|
||||
val singletonDeclarations: SingletonLlvmDeclarations?,
|
||||
val objCDeclarations: KotlinObjCClassLlvmDeclarations?)
|
||||
|
||||
internal class SingletonLlvmDeclarations(val instanceFieldRef: LLVMValueRef)
|
||||
|
||||
internal class KotlinObjCClassLlvmDeclarations(
|
||||
val classPointerGlobal: StaticData.Global,
|
||||
val classInfoGlobal: StaticData.Global,
|
||||
val bodyOffsetGlobal: StaticData.Global
|
||||
)
|
||||
|
||||
internal class FunctionLlvmDeclarations(val llvmFunction: LLVMValueRef)
|
||||
|
||||
internal class FieldLlvmDeclarations(val index: Int, val classBodyType: LLVMTypeRef)
|
||||
@@ -236,7 +244,7 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
||||
"ktype:$internalName"
|
||||
}
|
||||
|
||||
if (!descriptor.isAbstract()) {
|
||||
if (descriptor.typeInfoHasVtableAttached) {
|
||||
// Create the special global consisting of TypeInfo and vtable.
|
||||
|
||||
val typeInfoGlobalName = "ktypeglobal:$internalName"
|
||||
@@ -273,7 +281,14 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
||||
null
|
||||
}
|
||||
|
||||
return ClassLlvmDeclarations(bodyType, fields, typeInfoGlobal, typeInfoPtr, singletonDeclarations)
|
||||
val objCDeclarations = if (descriptor.isKotlinObjCClass()) {
|
||||
createKotlinObjCClassDeclarations(descriptor)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
return ClassLlvmDeclarations(bodyType, fields, typeInfoGlobal, typeInfoPtr,
|
||||
singletonDeclarations, objCDeclarations)
|
||||
}
|
||||
|
||||
private fun createSingletonDeclarations(
|
||||
@@ -299,6 +314,23 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
||||
return SingletonLlvmDeclarations(instanceFieldRef)
|
||||
}
|
||||
|
||||
private fun createKotlinObjCClassDeclarations(descriptor: ClassDescriptor): KotlinObjCClassLlvmDeclarations {
|
||||
val internalName = qualifyInternalName(descriptor)
|
||||
|
||||
val classPointerGlobal = staticData.createGlobal(int8TypePtr, "kobjcclassptr:$internalName")
|
||||
|
||||
val classInfoGlobal = staticData.createGlobal(
|
||||
context.llvm.runtime.kotlinObjCClassInfo,
|
||||
"kobjcclassinfo:$internalName"
|
||||
).apply {
|
||||
setConstant(true)
|
||||
}
|
||||
|
||||
val bodyOffsetGlobal = staticData.createGlobal(int32Type, "kobjcbodyoffs:$internalName")
|
||||
|
||||
return KotlinObjCClassLlvmDeclarations(classPointerGlobal, classInfoGlobal, bodyOffsetGlobal)
|
||||
}
|
||||
|
||||
override fun visitField(declaration: IrField) {
|
||||
super.visitField(declaration)
|
||||
|
||||
@@ -330,6 +362,8 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
||||
override fun visitFunction(declaration: IrFunction) {
|
||||
super.visitFunction(declaration)
|
||||
|
||||
if (!declaration.descriptor.kind.isReal) return
|
||||
|
||||
val descriptor = declaration.descriptor
|
||||
val llvmFunctionType = getLlvmFunctionType(descriptor)
|
||||
|
||||
|
||||
+10
-2
@@ -19,6 +19,7 @@ package org.jetbrains.kotlin.backend.konan.llvm
|
||||
import llvm.*
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.descriptors.*
|
||||
import org.jetbrains.kotlin.backend.konan.isExternalObjCClassMethod
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
@@ -166,11 +167,18 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
||||
|
||||
val typeInfoGlobal = llvmDeclarations.typeInfoGlobal
|
||||
|
||||
val typeInfoGlobalValue = if (classDesc.isAbstract()) {
|
||||
val typeInfoGlobalValue = if (!classDesc.typeInfoHasVtableAttached) {
|
||||
typeInfo
|
||||
} else {
|
||||
// TODO: compile-time resolution limits binary compatibility
|
||||
val vtableEntries = context.getVtableBuilder(classDesc).vtableEntries.map { it.implementation.entryPointAddress }
|
||||
val vtableEntries = context.getVtableBuilder(classDesc).vtableEntries.map {
|
||||
val implementation = it.implementation
|
||||
if (implementation.isExternalObjCClassMethod()) {
|
||||
NullPointer(int8Type)
|
||||
} else {
|
||||
implementation.entryPointAddress
|
||||
}
|
||||
}
|
||||
val vtable = ConstArray(int8TypePtr, vtableEntries)
|
||||
Struct(typeInfo, vtable)
|
||||
}
|
||||
|
||||
+3
@@ -53,4 +53,7 @@ class Runtime(bitcodeFile: String) {
|
||||
val dataLayout = LLVMGetDataLayout(llvmModule)!!.toKString()
|
||||
|
||||
val targetData = LLVMCreateTargetData(dataLayout)!!
|
||||
|
||||
val kotlinObjCClassInfo by lazy { getStructType("KotlinObjCClassInfo") }
|
||||
val objCMethodDescription by lazy { getStructType("ObjCMethodDescription") }
|
||||
}
|
||||
|
||||
+4
@@ -152,6 +152,10 @@ internal class StaticData(override val context: Context): ContextUtils {
|
||||
}
|
||||
|
||||
private val stringLiterals = mutableMapOf<String, ConstPointer>()
|
||||
private val cStringLiterals = mutableMapOf<String, ConstPointer>()
|
||||
|
||||
fun cStringLiteral(value: String) =
|
||||
cStringLiterals.getOrPut(value) { placeCStringLiteral(value) }
|
||||
|
||||
fun kotlinStringLiteral(type: KotlinType, value: IrConst<String>) =
|
||||
stringLiterals.getOrPut(value.value) { createKotlinStringLiteral(type, value) }
|
||||
|
||||
+6
@@ -41,3 +41,9 @@ internal fun StaticData.createAlias(name: String, aliasee: ConstPointer): ConstP
|
||||
val alias = LLVMAddAlias(context.llvmModule, aliasee.llvmType, aliasee.llvm, name)!!
|
||||
return constPointer(alias)
|
||||
}
|
||||
|
||||
internal fun StaticData.placeCStringLiteral(value: String): ConstPointer {
|
||||
val chars = value.toByteArray(Charsets.UTF_8).map { Int8(it) } + Int8(0)
|
||||
|
||||
return placeGlobalConstArray("", int8Type, chars)
|
||||
}
|
||||
+257
-15
@@ -20,40 +20,282 @@ import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
|
||||
import org.jetbrains.kotlin.backend.common.lower.IrBuildingTransformer
|
||||
import org.jetbrains.kotlin.backend.common.lower.at
|
||||
import org.jetbrains.kotlin.backend.konan.Context
|
||||
import org.jetbrains.kotlin.backend.konan.ValueType
|
||||
import org.jetbrains.kotlin.backend.konan.isRepresentedAs
|
||||
import org.jetbrains.kotlin.backend.konan.reportCompilationError
|
||||
import org.jetbrains.kotlin.backend.common.lower.irBlock
|
||||
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.isInterface
|
||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
||||
import org.jetbrains.kotlin.ir.builders.IrBuilder
|
||||
import org.jetbrains.kotlin.ir.builders.irCall
|
||||
import org.jetbrains.kotlin.descriptors.*
|
||||
import org.jetbrains.kotlin.ir.IrStatement
|
||||
import org.jetbrains.kotlin.ir.builders.*
|
||||
import org.jetbrains.kotlin.ir.declarations.IrClass
|
||||
import org.jetbrains.kotlin.ir.declarations.IrFile
|
||||
import org.jetbrains.kotlin.ir.expressions.*
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrConstImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrFunctionReferenceImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.IrGetObjectValueImpl
|
||||
import org.jetbrains.kotlin.ir.expressions.impl.*
|
||||
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
|
||||
import org.jetbrains.kotlin.ir.util.defaultType
|
||||
import org.jetbrains.kotlin.ir.util.functions
|
||||
import org.jetbrains.kotlin.ir.util.getArguments
|
||||
import org.jetbrains.kotlin.ir.visitors.IrElementTransformerVoid
|
||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||
import org.jetbrains.kotlin.name.FqName
|
||||
import org.jetbrains.kotlin.name.Name
|
||||
import org.jetbrains.kotlin.resolve.OverridingUtil
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||
import org.jetbrains.kotlin.types.KotlinType
|
||||
import org.jetbrains.kotlin.types.TypeUtils
|
||||
import org.jetbrains.kotlin.types.typeUtil.isSubtypeOf
|
||||
|
||||
internal class InteropLoweringPart1(val context: Context) : IrElementTransformerVoid(), FileLoweringPass {
|
||||
internal class InteropLoweringPart1(val context: Context) : IrBuildingTransformer(context), FileLoweringPass {
|
||||
|
||||
private val symbols get() = context.ir.symbols
|
||||
private val symbolTable get() = symbols.symbolTable
|
||||
|
||||
lateinit var currentFile: IrFile
|
||||
|
||||
override fun lower(irFile: IrFile) {
|
||||
currentFile = irFile
|
||||
irFile.transformChildrenVoid(this)
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.callAlloc(classSymbol: IrClassSymbol): IrExpression {
|
||||
return irCall(symbols.interopAllocObjCObject, listOf(classSymbol.descriptor.defaultType)).apply {
|
||||
putValueArgument(0, getObjCClass(classSymbol))
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
|
||||
private val outerClasses = mutableListOf<IrClass>()
|
||||
|
||||
override fun visitClass(declaration: IrClass): IrStatement {
|
||||
if (declaration.descriptor.isKotlinObjCClass()) {
|
||||
checkKotlinObjCClass(declaration)
|
||||
}
|
||||
|
||||
outerClasses.push(declaration)
|
||||
try {
|
||||
return super.visitClass(declaration)
|
||||
} finally {
|
||||
outerClasses.pop()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkKotlinObjCClass(irClass: IrClass) {
|
||||
val kind = irClass.descriptor.kind
|
||||
if (kind != ClassKind.CLASS && kind != ClassKind.OBJECT) {
|
||||
context.reportCompilationError(
|
||||
"Only classes are supported as subtypes of Objective-C types",
|
||||
currentFile, irClass
|
||||
)
|
||||
}
|
||||
|
||||
if (!irClass.descriptor.isFinalClass) {
|
||||
context.reportCompilationError(
|
||||
"Non-final Kotlin subclasses of Objective-C classes are not yet supported",
|
||||
currentFile, irClass
|
||||
)
|
||||
}
|
||||
|
||||
var hasObjCClassSupertype = false
|
||||
irClass.descriptor.defaultType.constructor.supertypes.forEach {
|
||||
val descriptor = it.constructor.declarationDescriptor as ClassDescriptor
|
||||
if (!descriptor.isObjCClass()) {
|
||||
context.reportCompilationError(
|
||||
"Mixing Kotlin and Objective-C supertypes is not supported",
|
||||
currentFile, irClass
|
||||
)
|
||||
}
|
||||
|
||||
if (descriptor.kind == ClassKind.CLASS) {
|
||||
hasObjCClassSupertype = true
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasObjCClassSupertype) {
|
||||
context.reportCompilationError(
|
||||
"Kotlin implementation of Objective-C protocol must have Objective-C superclass (e.g. NSObject)",
|
||||
currentFile, irClass
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun visitDelegatingConstructorCall(expression: IrDelegatingConstructorCall): IrExpression {
|
||||
expression.transformChildrenVoid()
|
||||
|
||||
builder.at(expression)
|
||||
|
||||
val constructedClass = outerClasses.peek()!!
|
||||
val constructedClassDescriptor = constructedClass.descriptor
|
||||
|
||||
if (!constructedClassDescriptor.isObjCClass()) {
|
||||
return expression
|
||||
}
|
||||
|
||||
constructedClassDescriptor.containingDeclaration.let { classContainer ->
|
||||
if (classContainer is ClassDescriptor && classContainer.isObjCClass() &&
|
||||
constructedClassDescriptor == classContainer.companionObjectDescriptor) {
|
||||
|
||||
val outerConstructedClass = outerClasses[outerClasses.lastIndex - 1]
|
||||
assert (outerConstructedClass.descriptor == classContainer)
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!constructedClassDescriptor.isExternalObjCClass() &&
|
||||
expression.descriptor.constructedClass.isExternalObjCClass()) {
|
||||
|
||||
// Calling super constructor from Kotlin Objective-C class.
|
||||
|
||||
assert(constructedClassDescriptor.getSuperClassNotAny() == expression.descriptor.constructedClass)
|
||||
|
||||
val initMethod = getObjCInitMethod(expression.descriptor)!!
|
||||
val initMethodInfo = initMethod.getExternalObjCMethodInfo()!!
|
||||
|
||||
assert(expression.dispatchReceiver == null)
|
||||
assert(expression.extensionReceiver == null)
|
||||
|
||||
val initCall = builder.genLoweredObjCMethodCall(
|
||||
initMethodInfo,
|
||||
superQualifier = symbolTable.referenceClass(expression.descriptor.constructedClass),
|
||||
receiver = builder.callAlloc(constructedClass.symbol),
|
||||
arguments = initMethod.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
)
|
||||
|
||||
val superConstructor = symbolTable.referenceConstructor(
|
||||
expression.descriptor.constructedClass.constructors.single { it.valueParameters.size == 0 }
|
||||
)
|
||||
|
||||
return builder.irBlock(expression) {
|
||||
// Required for the IR to be valid, will be ignored in codegen:
|
||||
+IrDelegatingConstructorCallImpl(startOffset, endOffset, superConstructor, superConstructor.descriptor)
|
||||
|
||||
+irCall(symbols.interopObjCObjectInitFrom).apply {
|
||||
extensionReceiver = irGet(constructedClass.thisReceiver!!.symbol)
|
||||
putValueArgument(0, initCall)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return expression
|
||||
}
|
||||
|
||||
private fun IrBuilderWithScope.genLoweredObjCMethodCall(info: ObjCMethodInfo, superQualifier: IrClassSymbol?,
|
||||
receiver: IrExpression, arguments: List<IrExpression>): IrExpression {
|
||||
|
||||
val superClass = superQualifier?.let { getObjCClass(it) } ?:
|
||||
irCall(symbols.getNativeNullPtr)
|
||||
|
||||
val bridge = symbolTable.referenceSimpleFunction(info.bridge)
|
||||
return irCall(bridge).apply {
|
||||
putValueArgument(0, superClass)
|
||||
putValueArgument(1, receiver)
|
||||
|
||||
assert(arguments.size + 2 == info.bridge.valueParameters.size)
|
||||
arguments.forEachIndexed { index, argument ->
|
||||
putValueArgument(index + 2, argument)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getObjCInitMethod(descriptor: ConstructorDescriptor): FunctionDescriptor? {
|
||||
return descriptor.annotations.findAnnotation(FqName("kotlinx.cinterop.ObjCConstructor"))?.let {
|
||||
val initSelector = it.getStringValue("initSelector")
|
||||
descriptor.constructedClass.unsubstitutedMemberScope.getContributedDescriptors().asSequence()
|
||||
.filterIsInstance<FunctionDescriptor>()
|
||||
.single { it.getExternalObjCMethodInfo()?.selector == initSelector }
|
||||
}
|
||||
}
|
||||
|
||||
override fun visitCall(expression: IrCall): IrExpression {
|
||||
expression.transformChildrenVoid()
|
||||
|
||||
return when (expression.descriptor.original) {
|
||||
val descriptor = expression.descriptor.original
|
||||
|
||||
if (descriptor is ConstructorDescriptor) {
|
||||
val initMethod = getObjCInitMethod(descriptor)
|
||||
|
||||
if (initMethod != null) {
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
descriptor.getExternalObjCMethodInfo()?.let { methodInfo ->
|
||||
val isInteropStubsFile =
|
||||
currentFile.fileAnnotations.any { it.fqName == FqName("kotlinx.cinterop.InteropStubs") }
|
||||
|
||||
// Special case: bridge from Objective-C method implementation template to Kotlin method;
|
||||
// handled in CodeGeneratorVisitor.callVirtual.
|
||||
val useKotlinDispatch = isInteropStubsFile &&
|
||||
builder.scope.scopeOwner.annotations.hasAnnotation(FqName("konan.internal.ExportForCppRuntime"))
|
||||
|
||||
if (!useKotlinDispatch) {
|
||||
val arguments = descriptor.valueParameters.map { expression.getValueArgument(it)!! }
|
||||
assert(expression.extensionReceiver == null)
|
||||
|
||||
if (expression.superQualifier?.isObjCMetaClass() == true) {
|
||||
context.reportCompilationError(
|
||||
"Super calls to Objective-C meta classes are not supported yet",
|
||||
currentFile, expression
|
||||
)
|
||||
}
|
||||
|
||||
if (expression.superQualifier?.isInterface == true) {
|
||||
context.reportCompilationError(
|
||||
"Super calls to Objective-C protocols are not allowed",
|
||||
currentFile, expression
|
||||
)
|
||||
}
|
||||
|
||||
builder.at(expression)
|
||||
return builder.genLoweredObjCMethodCall(
|
||||
methodInfo,
|
||||
superQualifier = expression.superQualifierSymbol,
|
||||
receiver = expression.dispatchReceiver!!,
|
||||
arguments = arguments
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return when (descriptor) {
|
||||
context.interopBuiltIns.typeOf -> {
|
||||
val typeArgument = expression.getSingleTypeArgument()
|
||||
val classDescriptor = TypeUtils.getClassDescriptor(typeArgument)
|
||||
@@ -65,8 +307,8 @@ internal class InteropLoweringPart1(val context: Context) : IrElementTransformer
|
||||
error("native variable class $classDescriptor must have the companion object")
|
||||
|
||||
IrGetObjectValueImpl(
|
||||
expression.startOffset, expression.endOffset,
|
||||
companionObjectDescriptor.defaultType, companionObjectDescriptor
|
||||
expression.startOffset, expression.endOffset, companionObjectDescriptor.defaultType,
|
||||
symbolTable.referenceClass(companionObjectDescriptor)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ libffiDir.osx = libffi-3.2.1-2-darwin-macos
|
||||
llvmLtoFlags.osx =
|
||||
llvmLtoOptFlags.osx = -O3 -function-sections
|
||||
llvmLtoNooptFlags.osx = -O1
|
||||
linkerKonanFlags.osx = -lc++
|
||||
linkerKonanFlags.osx = -lc++ -lobjc
|
||||
linkerOptimizationFlags.osx = -dead_strip
|
||||
linkerDebugFlags.osx = -S
|
||||
osVersionMinFlagLd.osx = -macosx_version_min
|
||||
@@ -63,7 +63,7 @@ llvmLtoFlags.ios =
|
||||
llvmLtoOptFlags.ios = -O3 -function-sections
|
||||
linkerDebugFlags.ios = -S
|
||||
llvmLtoNooptFlags.ios = -O1
|
||||
linkerKonanFlags.ios = -lc++
|
||||
linkerKonanFlags.ios = -lc++ -lobjc
|
||||
linkerOptimizationFlags.ios = -dead_strip
|
||||
osVersionMinFlagLd.ios = -iphoneos_version_min
|
||||
osVersionMinFlagClang.ios = -miphoneos-version-min
|
||||
@@ -83,7 +83,7 @@ libffiDir.ios_sim = libffi-3.2.1-2-darwin-ios-sim
|
||||
llvmLtoFlags.ios_sim =
|
||||
llvmLtoOptFlags.ios_sim = -O3 -function-sections
|
||||
llvmLtoNooptFlags.ios_sim = -O1
|
||||
linkerKonanFlags.ios_sim = -lc++
|
||||
linkerKonanFlags.ios_sim = -lc++ -lobjc
|
||||
linkerOptimizationFlags.ios_sim = -dead_strip
|
||||
linkerDebugFlags.ios_sim = -S
|
||||
osVersionMinFlagLd.ios_sim = -ios_simulator_version_min
|
||||
|
||||
@@ -1995,6 +1995,13 @@ kotlinNativeInterop {
|
||||
linkerOpts '-framework', 'OpenGL', '-framework', 'GLUT'
|
||||
flavor 'native'
|
||||
}
|
||||
|
||||
objcSmoke {
|
||||
defFile 'interop/objc/objcSmoke.def'
|
||||
headers "$projectDir/interop/objc/smoke.h"
|
||||
linkerOpts "-L$buildDir", "-lobjcsmoke"
|
||||
flavor 'native'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2057,4 +2064,19 @@ if (isMac()) {
|
||||
source = "interop/basics/opengl_teapot.kt"
|
||||
interop = 'opengl'
|
||||
}
|
||||
|
||||
task interop_objc_smoke(type: RunInteropKonanTest) {
|
||||
goldValue = "Hello, World!\nKotlin says: Hello, everybody!\nHello from Kotlin\n2, 1\nDeallocated\nDeallocated\n"
|
||||
|
||||
source = "interop/objc/smoke.kt"
|
||||
interop = 'objcSmoke'
|
||||
|
||||
doFirst {
|
||||
execClang {
|
||||
args "$projectDir/interop/objc/smoke.m"
|
||||
args "-lobjc", '-fobjc-arc'
|
||||
args '-fPIC', '-shared', '-o', "$buildDir/libobjcsmoke.dylib"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
language = Objective-C
|
||||
headerFilter = **/smoke.h
|
||||
@@ -0,0 +1,24 @@
|
||||
#import <objc/NSObject.h>
|
||||
|
||||
@protocol Printer
|
||||
@required
|
||||
-(void)print:(const char*)string;
|
||||
@end;
|
||||
|
||||
@interface Foo : NSObject
|
||||
@property NSString* name;
|
||||
-(void)hello;
|
||||
-(void)helloWithPrinter:(id <Printer>)printer;
|
||||
@end;
|
||||
|
||||
@protocol MutablePair
|
||||
@required
|
||||
@property (readonly) int first;
|
||||
@property (readonly) int second;
|
||||
|
||||
-(void)update:(int)index add:(int)delta;
|
||||
-(void)update:(int)index sub:(int)delta;
|
||||
|
||||
@end;
|
||||
|
||||
void replacePairElements(id <MutablePair> pair, int first, int second);
|
||||
@@ -0,0 +1,55 @@
|
||||
import kotlinx.cinterop.*
|
||||
import objcSmoke.*
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
autoreleasepool {
|
||||
run()
|
||||
}
|
||||
}
|
||||
|
||||
fun run() {
|
||||
val foo = Foo()
|
||||
foo.hello()
|
||||
foo.name = "everybody"
|
||||
foo.helloWithPrinter(object : NSObject(), PrinterProtocol {
|
||||
override fun print(string: CPointer<ByteVar>?) {
|
||||
println("Kotlin says: " + string?.toKString())
|
||||
}
|
||||
})
|
||||
|
||||
Bar().hello()
|
||||
|
||||
val pair = MutablePairImpl(42, 17)
|
||||
replacePairElements(pair, 1, 2)
|
||||
pair.swap()
|
||||
println("${pair.first}, ${pair.second}")
|
||||
}
|
||||
|
||||
fun MutablePairProtocol.swap() {
|
||||
update(0, add = second)
|
||||
update(1, sub = first)
|
||||
update(0, add = second)
|
||||
update(1, sub = second*2)
|
||||
}
|
||||
|
||||
class Bar : Foo() {
|
||||
override fun helloWithPrinter(printer: PrinterProtocol) = memScoped {
|
||||
printer.print("Hello from Kotlin".cstr.getPointer(memScope))
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("CONFLICTING_OVERLOADS")
|
||||
class MutablePairImpl(first: Int, second: Int) : NSObject(), MutablePairProtocol {
|
||||
private var elements = intArrayOf(first, second)
|
||||
|
||||
override fun first() = elements.first()
|
||||
override fun second() = elements.last()
|
||||
|
||||
override fun update(index: Int, add: Int) {
|
||||
elements[index] += add
|
||||
}
|
||||
|
||||
override fun update(index: Int, sub: Int) {
|
||||
elements[index] -= sub
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
#import <stdio.h>
|
||||
#import <Foundation/NSString.h>
|
||||
#import "smoke.h"
|
||||
|
||||
@interface CPrinter : NSObject <Printer>
|
||||
-(void)print:(const char*)string;
|
||||
@end;
|
||||
|
||||
@implementation CPrinter
|
||||
-(void)print:(const char*)string {
|
||||
printf("%s\n", string);
|
||||
fflush(stdout);
|
||||
}
|
||||
@end;
|
||||
|
||||
@implementation Foo
|
||||
|
||||
@synthesize name;
|
||||
|
||||
-(instancetype)init {
|
||||
if (self = [super init]) {
|
||||
self.name = @"World";
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
-(void)hello {
|
||||
CPrinter* printer = [[CPrinter alloc] init];
|
||||
[self helloWithPrinter:printer];
|
||||
}
|
||||
|
||||
-(void)helloWithPrinter:(id <Printer>)printer {
|
||||
NSString* message = [NSString stringWithFormat:@"Hello, %@!", self.name];
|
||||
[printer print:message.UTF8String];
|
||||
}
|
||||
|
||||
-(void)dealloc {
|
||||
printf("Deallocated\n");
|
||||
}
|
||||
|
||||
@end;
|
||||
|
||||
void replacePairElements(id <MutablePair> pair, int first, int second) {
|
||||
[pair update:0 add:(first - pair.first)];
|
||||
[pair update:1 sub:(pair.second - second)];
|
||||
}
|
||||
Reference in New Issue
Block a user