Implement -produce framework option
It compiles Kotlin code to Objective-C framework (also importable to Swift)
This commit is contained in:
committed by
SvyatoslavScherbina
parent
6114a5b35c
commit
d745e80135
@@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* 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 kotlinx.cinterop
|
||||||
|
|
||||||
|
import konan.internal.ExportForCppRuntime
|
||||||
|
|
||||||
|
// TODO: it it actually not related to cinterop.
|
||||||
|
|
||||||
|
@ExportTypeInfo("theNSArrayListTypeInfo")
|
||||||
|
internal class NSArrayList : AbstractList<Any?> {
|
||||||
|
|
||||||
|
// FIXME: override methods of Any.
|
||||||
|
|
||||||
|
@konan.internal.ExportForCppRuntime("Kotlin_NSArrayList_constructor")
|
||||||
|
constructor() : super()
|
||||||
|
|
||||||
|
override val size: Int get() = getSize()
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_NSArrayList_getSize")
|
||||||
|
private external fun getSize(): Int
|
||||||
|
|
||||||
|
@SymbolName("Kotlin_NSArrayList_getElement")
|
||||||
|
external override fun get(index: Int): Any?
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime private fun Kotlin_List_get(list: List<*>, index: Int): Any? = list.get(index)
|
||||||
|
@ExportForCppRuntime private fun Kotlin_List_getSize(list: List<*>): Int = list.size
|
||||||
+52
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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.ir.KonanSymbols
|
||||||
|
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
|
||||||
|
import org.jetbrains.kotlin.ir.util.getPropertyGetter
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
|
internal fun KonanSymbols.getTypeConversion(
|
||||||
|
actualType: KotlinType,
|
||||||
|
expectedType: KotlinType
|
||||||
|
): IrSimpleFunctionSymbol? {
|
||||||
|
val actualValueType = actualType.correspondingValueType
|
||||||
|
val expectedValueType = expectedType.correspondingValueType
|
||||||
|
|
||||||
|
return when {
|
||||||
|
actualValueType == expectedValueType -> null
|
||||||
|
|
||||||
|
actualValueType == null && expectedValueType != null -> {
|
||||||
|
// This may happen in the following cases:
|
||||||
|
// 1. `actualType` is `Nothing`;
|
||||||
|
// 2. `actualType` is incompatible.
|
||||||
|
|
||||||
|
this.getUnboxFunction(expectedValueType)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualValueType != null && expectedValueType == null -> {
|
||||||
|
this.boxFunctions[actualValueType]!!
|
||||||
|
}
|
||||||
|
|
||||||
|
else -> throw IllegalArgumentException("actual type is $actualType, expected $expectedType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun KonanSymbols.getUnboxFunction(valueType: ValueType): IrSimpleFunctionSymbol =
|
||||||
|
this.unboxFunctions[valueType]
|
||||||
|
?: this.boxClasses[valueType]!!.getPropertyGetter("value")!! as IrSimpleFunctionSymbol
|
||||||
+6
@@ -22,6 +22,11 @@ import org.jetbrains.kotlin.backend.konan.llvm.parseBitcodeFile
|
|||||||
import org.jetbrains.kotlin.backend.konan.util.getValueOrNull
|
import org.jetbrains.kotlin.backend.konan.util.getValueOrNull
|
||||||
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||||
|
|
||||||
|
val CompilerOutputKind.isNativeBinary: Boolean get() = when (this) {
|
||||||
|
CompilerOutputKind.PROGRAM, CompilerOutputKind.DYNAMIC, CompilerOutputKind.FRAMEWORK -> true
|
||||||
|
CompilerOutputKind.LIBRARY, CompilerOutputKind.BITCODE -> false
|
||||||
|
}
|
||||||
|
|
||||||
internal fun produceOutput(context: Context) {
|
internal fun produceOutput(context: Context) {
|
||||||
|
|
||||||
val llvmModule = context.llvmModule!!
|
val llvmModule = context.llvmModule!!
|
||||||
@@ -29,6 +34,7 @@ internal fun produceOutput(context: Context) {
|
|||||||
|
|
||||||
when (config.get(KonanConfigKeys.PRODUCE)) {
|
when (config.get(KonanConfigKeys.PRODUCE)) {
|
||||||
CompilerOutputKind.DYNAMIC,
|
CompilerOutputKind.DYNAMIC,
|
||||||
|
CompilerOutputKind.FRAMEWORK,
|
||||||
CompilerOutputKind.PROGRAM -> {
|
CompilerOutputKind.PROGRAM -> {
|
||||||
val program = context.config.outputName
|
val program = context.config.outputName
|
||||||
val output = "$program.kt.bc"
|
val output = "$program.kt.bc"
|
||||||
|
|||||||
+1
-1
@@ -65,7 +65,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
|||||||
prepareDependencies(configuration.getBoolean(KonanConfigKeys.CHECK_DEPENDENCIES))
|
prepareDependencies(configuration.getBoolean(KonanConfigKeys.CHECK_DEPENDENCIES))
|
||||||
}
|
}
|
||||||
|
|
||||||
private val produce = configuration.get(KonanConfigKeys.PRODUCE)!!
|
internal val produce get() = configuration.get(KonanConfigKeys.PRODUCE)!!
|
||||||
private val suffix = produce.suffix(targetManager.target)
|
private val suffix = produce.suffix(targetManager.target)
|
||||||
val outputName = configuration.get(KonanConfigKeys.OUTPUT)?.removeSuffixIfPresent(suffix) ?: produce.name.toLowerCase()
|
val outputName = configuration.get(KonanConfigKeys.OUTPUT)?.removeSuffixIfPresent(suffix) ?: produce.name.toLowerCase()
|
||||||
val outputFile = outputName.suffixIfNot(produce.suffix(targetManager.target))
|
val outputFile = outputName.suffixIfNot(produce.suffix(targetManager.target))
|
||||||
|
|||||||
+2
-4
@@ -87,10 +87,8 @@ object KonanPhases {
|
|||||||
|
|
||||||
// Don't serialize anything to a final executable.
|
// Don't serialize anything to a final executable.
|
||||||
KonanPhase.SERIALIZER.enabled =
|
KonanPhase.SERIALIZER.enabled =
|
||||||
(get(PRODUCE) == CompilerOutputKind.LIBRARY)
|
(config.produce == CompilerOutputKind.LIBRARY)
|
||||||
KonanPhase.LINK_STAGE.enabled =
|
KonanPhase.LINK_STAGE.enabled = config.produce.isNativeBinary
|
||||||
(get(PRODUCE) == CompilerOutputKind.PROGRAM ||
|
|
||||||
get(PRODUCE) == CompilerOutputKind.DYNAMIC)
|
|
||||||
|
|
||||||
KonanPhase.TEST_PROCESSOR.enabled = getBoolean(GENERATE_TEST_RUNNER)
|
KonanPhase.TEST_PROCESSOR.enabled = getBoolean(GENERATE_TEST_RUNNER)
|
||||||
|
|
||||||
|
|||||||
+10
-3
@@ -239,7 +239,8 @@ internal class LinkStage(val context: Context) {
|
|||||||
|
|
||||||
private val optimize = config.get(KonanConfigKeys.OPTIMIZATION) ?: false
|
private val optimize = config.get(KonanConfigKeys.OPTIMIZATION) ?: false
|
||||||
private val debug = config.get(KonanConfigKeys.DEBUG) ?: false
|
private val debug = config.get(KonanConfigKeys.DEBUG) ?: false
|
||||||
private val dynamic = config.get(KonanConfigKeys.PRODUCE) == CompilerOutputKind.DYNAMIC
|
private val dynamic = context.config.produce == CompilerOutputKind.DYNAMIC ||
|
||||||
|
context.config.produce == CompilerOutputKind.FRAMEWORK
|
||||||
private val nomain = config.get(KonanConfigKeys.NOMAIN) ?: false
|
private val nomain = config.get(KonanConfigKeys.NOMAIN) ?: false
|
||||||
private val emitted = context.bitcodeFileName
|
private val emitted = context.bitcodeFileName
|
||||||
private val libraries = context.llvm.librariesToLink
|
private val libraries = context.llvm.librariesToLink
|
||||||
@@ -325,10 +326,16 @@ internal class LinkStage(val context: Context) {
|
|||||||
// So we stick to "-alias _main _konan_main" on Mac.
|
// So we stick to "-alias _main _konan_main" on Mac.
|
||||||
// And just do the same on Linux.
|
// And just do the same on Linux.
|
||||||
private val entryPointSelector: List<String>
|
private val entryPointSelector: List<String>
|
||||||
get() = if (nomain) emptyList() else platform.entrySelector
|
get() = if (nomain || dynamic) emptyList() else platform.entrySelector
|
||||||
|
|
||||||
private fun link(objectFiles: List<ObjectFile>, includedBinaries: List<String>, libraryProvidedLinkerFlags: List<String>): ExecutableFile? {
|
private fun link(objectFiles: List<ObjectFile>, includedBinaries: List<String>, libraryProvidedLinkerFlags: List<String>): ExecutableFile? {
|
||||||
val executable = context.config.outputFile
|
val executable = if (context.config.produce != CompilerOutputKind.FRAMEWORK) {
|
||||||
|
context.config.outputFile
|
||||||
|
} else {
|
||||||
|
val framework = File(context.config.outputFile)
|
||||||
|
framework.mkdirs()
|
||||||
|
framework.child(framework.name.removeSuffix(".framework")).absolutePath
|
||||||
|
}
|
||||||
|
|
||||||
val linkCommand = platform.linkCommand(objectFiles, executable, optimize, debug, dynamic) +
|
val linkCommand = platform.linkCommand(objectFiles, executable, optimize, debug, dynamic) +
|
||||||
platform.targetLibffi +
|
platform.targetLibffi +
|
||||||
|
|||||||
-2
@@ -130,8 +130,6 @@ internal class ClassVtablesBuilder(val classDescriptor: ClassDescriptor, val con
|
|||||||
}
|
}
|
||||||
|
|
||||||
val methodTableEntries: List<OverriddenFunctionDescriptor> by lazy {
|
val methodTableEntries: List<OverriddenFunctionDescriptor> by lazy {
|
||||||
assert(!classDescriptor.isAbstract())
|
|
||||||
|
|
||||||
classDescriptor.sortedContributedMethods
|
classDescriptor.sortedContributedMethods
|
||||||
.flatMap { method -> method.allOverriddenDescriptors.map { OverriddenFunctionDescriptor(method, it) } }
|
.flatMap { method -> method.allOverriddenDescriptors.map { OverriddenFunctionDescriptor(method, it) } }
|
||||||
.filter { it.canBeCalledVirtually }
|
.filter { it.canBeCalledVirtually }
|
||||||
|
|||||||
+6
@@ -242,6 +242,12 @@ internal val ClassDescriptor.typeInfoSymbolName: String
|
|||||||
return "ktype:" + this.fqNameSafe.toString()
|
return "ktype:" + this.fqNameSafe.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal val ClassDescriptor.writableTypeInfoSymbolName: String
|
||||||
|
get() {
|
||||||
|
assert (this.isExported())
|
||||||
|
return "ktypew:" + this.fqNameSafe.toString()
|
||||||
|
}
|
||||||
|
|
||||||
internal val theUnitInstanceName = "kobj:kotlin.Unit"
|
internal val theUnitInstanceName = "kobj:kotlin.Unit"
|
||||||
|
|
||||||
internal val ClassDescriptor.objectInstanceFieldSymbolName: String
|
internal val ClassDescriptor.objectInstanceFieldSymbolName: String
|
||||||
|
|||||||
+53
@@ -20,6 +20,8 @@ package org.jetbrains.kotlin.backend.konan.llvm
|
|||||||
import kotlinx.cinterop.*
|
import kotlinx.cinterop.*
|
||||||
import llvm.*
|
import llvm.*
|
||||||
import org.jetbrains.kotlin.backend.konan.Context
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||||
|
import org.jetbrains.kotlin.backend.konan.isObjCClass
|
||||||
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassConstructorDescriptor
|
||||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||||
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
import org.jetbrains.kotlin.descriptors.ClassDescriptor
|
||||||
@@ -76,6 +78,17 @@ internal inline fun<R> generateFunction(codegen: CodeGenerator, function: LLVMVa
|
|||||||
generateFunctionBody(FunctionGenerationContext(function, codegen), code)
|
generateFunctionBody(FunctionGenerationContext(function, codegen), code)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal inline fun generateFunction(
|
||||||
|
codegen: CodeGenerator,
|
||||||
|
functionType: LLVMTypeRef,
|
||||||
|
name: String,
|
||||||
|
block: FunctionGenerationContext.(FunctionGenerationContext) -> Unit
|
||||||
|
): LLVMValueRef {
|
||||||
|
val function = LLVMAddFunction(codegen.context.llvmModule, name, functionType)!!
|
||||||
|
generateFunction(codegen, function, block)
|
||||||
|
return function
|
||||||
|
}
|
||||||
|
|
||||||
inline private fun <R> generateFunctionBody(functionGenerationContext: FunctionGenerationContext, code: FunctionGenerationContext.(FunctionGenerationContext) -> R) {
|
inline private fun <R> generateFunctionBody(functionGenerationContext: FunctionGenerationContext, code: FunctionGenerationContext.(FunctionGenerationContext) -> R) {
|
||||||
functionGenerationContext.prologue()
|
functionGenerationContext.prologue()
|
||||||
functionGenerationContext.code(functionGenerationContext)
|
functionGenerationContext.code(functionGenerationContext)
|
||||||
@@ -184,6 +197,8 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun param(index: Int): LLVMValueRef = LLVMGetParam(this.function, index)!!
|
||||||
|
|
||||||
fun load(value: LLVMValueRef, name: String = ""): LLVMValueRef {
|
fun load(value: LLVMValueRef, name: String = ""): LLVMValueRef {
|
||||||
val result = LLVMBuildLoad(builder, value, name)!!
|
val result = LLVMBuildLoad(builder, value, name)!!
|
||||||
// Use loadSlot() API for that.
|
// Use loadSlot() API for that.
|
||||||
@@ -352,6 +367,7 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
|||||||
|
|
||||||
fun and(arg0: LLVMValueRef, arg1: LLVMValueRef, name: String = ""): LLVMValueRef = LLVMBuildAnd(builder, arg0, arg1, 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 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)!!
|
||||||
|
|
||||||
fun zext(arg: LLVMValueRef, type: LLVMTypeRef): LLVMValueRef =
|
fun zext(arg: LLVMValueRef, type: LLVMTypeRef): LLVMValueRef =
|
||||||
LLVMBuildZExt(builder, arg, type, "")!!
|
LLVMBuildZExt(builder, arg, type, "")!!
|
||||||
@@ -401,6 +417,8 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
|||||||
fun gep(base: LLVMValueRef, index: LLVMValueRef, name: String = ""): LLVMValueRef {
|
fun gep(base: LLVMValueRef, index: LLVMValueRef, name: String = ""): LLVMValueRef {
|
||||||
return LLVMBuildGEP(builder, base, cValuesOf(index), 1, name)!!
|
return LLVMBuildGEP(builder, base, cValuesOf(index), 1, name)!!
|
||||||
}
|
}
|
||||||
|
fun structGep(base: LLVMValueRef, index: Int, name: String = ""): LLVMValueRef =
|
||||||
|
LLVMBuildStructGEP(builder, base, index, name)!!
|
||||||
|
|
||||||
fun gxxLandingpad(numClauses: Int, name: String = ""): LLVMValueRef {
|
fun gxxLandingpad(numClauses: Int, name: String = ""): LLVMValueRef {
|
||||||
val personalityFunction = LLVMConstBitCast(context.llvm.gxxPersonalityFunction, int8TypePtr)
|
val personalityFunction = LLVMConstBitCast(context.llvm.gxxPersonalityFunction, int8TypePtr)
|
||||||
@@ -462,6 +480,41 @@ internal class FunctionGenerationContext(val function: LLVMValueRef,
|
|||||||
return switch
|
return switch
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun lookupVirtualImpl(receiver: LLVMValueRef, descriptor: FunctionDescriptor): LLVMValueRef {
|
||||||
|
assert(LLVMTypeOf(receiver) == codegen.kObjHeaderPtr)
|
||||||
|
|
||||||
|
val owner = descriptor.containingDeclaration as ClassDescriptor
|
||||||
|
|
||||||
|
val typeInfoPtr: LLVMValueRef = if (owner.isObjCClass()) {
|
||||||
|
call(context.llvm.getObjCKotlinTypeInfo, listOf(receiver))
|
||||||
|
} else {
|
||||||
|
val typeInfoPtrPtr = LLVMBuildStructGEP(builder, receiver, 0 /* type_info */, "")!!
|
||||||
|
load(typeInfoPtrPtr)
|
||||||
|
}
|
||||||
|
|
||||||
|
assert (typeInfoPtr.type == codegen.kTypeInfoPtr) { LLVMPrintTypeToString(typeInfoPtr.type)!!.toKString() }
|
||||||
|
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)
|
||||||
|
|
||||||
|
val vtablePlace = gep(typeInfoPtr, Int32(1).llvm) // typeInfoPtr + 1
|
||||||
|
val vtable = bitcast(kInt8PtrPtr, vtablePlace)
|
||||||
|
|
||||||
|
val slot = gep(vtable, Int32(index).llvm)
|
||||||
|
load(slot)
|
||||||
|
} else {
|
||||||
|
// Otherwise, call by hash.
|
||||||
|
// TODO: optimize by storing interface number in lower bits of 'this' pointer
|
||||||
|
// when passing object as an interface. This way we can use those bits as index
|
||||||
|
// for an additional per-interface vtable.
|
||||||
|
val methodHash = codegen.functionHash(descriptor) // Calculate hash of the method to be invoked
|
||||||
|
val lookupArgs = listOf(typeInfoPtr, methodHash) // Prepare args for lookup
|
||||||
|
call(context.llvm.lookupOpenMethodFunction, lookupArgs)
|
||||||
|
}
|
||||||
|
val functionPtrType = pointerType(codegen.getLlvmFunctionType(descriptor)) // Construct type of the method to be invoked
|
||||||
|
return bitcast(functionPtrType, llvmMethod) // Cast method address to the type
|
||||||
|
}
|
||||||
|
|
||||||
fun resetDebugLocation() {
|
fun resetDebugLocation() {
|
||||||
if (!context.shouldContainDebugInfo()) return
|
if (!context.shouldContainDebugInfo()) return
|
||||||
if (!currentPositionHolder.isAfterTerminator)
|
if (!currentPositionHolder.isAfterTerminator)
|
||||||
|
|||||||
+33
@@ -24,6 +24,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.DeserializedKonanModule
|
|||||||
import org.jetbrains.kotlin.backend.konan.descriptors.LlvmSymbolOrigin
|
import org.jetbrains.kotlin.backend.konan.descriptors.LlvmSymbolOrigin
|
||||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||||
import org.jetbrains.kotlin.backend.konan.hash.GlobalHash
|
import org.jetbrains.kotlin.backend.konan.hash.GlobalHash
|
||||||
|
import org.jetbrains.kotlin.backend.konan.isNativeBinary
|
||||||
import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader
|
import org.jetbrains.kotlin.backend.konan.library.KonanLibraryReader
|
||||||
import org.jetbrains.kotlin.backend.konan.library.impl.LibraryReaderImpl
|
import org.jetbrains.kotlin.backend.konan.library.impl.LibraryReaderImpl
|
||||||
import org.jetbrains.kotlin.backend.konan.library.withResolvedDependencies
|
import org.jetbrains.kotlin.backend.konan.library.withResolvedDependencies
|
||||||
@@ -37,6 +38,8 @@ import org.jetbrains.kotlin.name.Name
|
|||||||
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
|
import kotlin.properties.ReadOnlyProperty
|
||||||
|
import kotlin.reflect.KProperty
|
||||||
|
|
||||||
internal sealed class SlotType {
|
internal sealed class SlotType {
|
||||||
// Frame local arena slot can be used.
|
// Frame local arena slot can be used.
|
||||||
@@ -371,11 +374,28 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
|||||||
val checkInstanceFunction = importRtFunction("CheckInstance")
|
val checkInstanceFunction = importRtFunction("CheckInstance")
|
||||||
val throwExceptionFunction = importRtFunction("ThrowException")
|
val throwExceptionFunction = importRtFunction("ThrowException")
|
||||||
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
|
val appendToInitalizersTail = importRtFunction("AppendToInitializersTail")
|
||||||
|
val initRuntimeIfNeeded = importRtFunction("Kotlin_initRuntimeIfNeeded")
|
||||||
|
|
||||||
val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") }
|
val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") }
|
||||||
val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") }
|
val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") }
|
||||||
val missingInitImp by lazy { importRtFunction("MissingInitImp") }
|
val missingInitImp by lazy { importRtFunction("MissingInitImp") }
|
||||||
|
|
||||||
|
val Kotlin_ObjCExport_refToObjC by lazyRtFunction
|
||||||
|
val Kotlin_ObjCExport_refFromObjC by lazyRtFunction
|
||||||
|
val Kotlin_Interop_CreateNSStringFromKString by lazyRtFunction
|
||||||
|
val Kotlin_Interop_CreateNSArrayFromKList by lazyRtFunction
|
||||||
|
val Kotlin_ObjCExport_GetAssociatedObject by lazyRtFunction
|
||||||
|
val Kotlin_ObjCExport_AbstractMethodCalled by lazyRtFunction
|
||||||
|
|
||||||
|
val kObjectReservedTailSize = if (context.config.produce.isNativeBinary) {
|
||||||
|
// Note: this defines the global declared in runtime (if any).
|
||||||
|
staticData.placeGlobal("kObjectReservedTailSize", Int32(0), isExported = true).also {
|
||||||
|
it.setConstant(true)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
private val personalityFunctionName = when (context.config.targetManager.target) {
|
private val personalityFunctionName = when (context.config.targetManager.target) {
|
||||||
KonanTarget.MINGW -> "__gxx_personality_seh0"
|
KonanTarget.MINGW -> "__gxx_personality_seh0"
|
||||||
else -> "__gxx_personality_v0"
|
else -> "__gxx_personality_v0"
|
||||||
@@ -400,6 +420,19 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) {
|
|||||||
val memsetFunction = importMemset()
|
val memsetFunction = importMemset()
|
||||||
|
|
||||||
val usedFunctions = mutableListOf<LLVMValueRef>()
|
val usedFunctions = mutableListOf<LLVMValueRef>()
|
||||||
|
val usedGlobals = mutableListOf<LLVMValueRef>()
|
||||||
|
val compilerUsedGlobals = mutableListOf<LLVMValueRef>()
|
||||||
val staticInitializers = mutableListOf<LLVMValueRef>()
|
val staticInitializers = mutableListOf<LLVMValueRef>()
|
||||||
val fileInitializers = mutableListOf<IrField>()
|
val fileInitializers = mutableListOf<IrField>()
|
||||||
|
|
||||||
|
private object lazyRtFunction {
|
||||||
|
operator fun provideDelegate(
|
||||||
|
thisRef: Llvm, property: KProperty<*>
|
||||||
|
) = object : ReadOnlyProperty<Llvm, LLVMValueRef> {
|
||||||
|
|
||||||
|
val value by lazy { thisRef.importRtFunction(property.name) }
|
||||||
|
|
||||||
|
override fun getValue(thisRef: Llvm, property: KProperty<*>): LLVMValueRef = value
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-32
@@ -25,6 +25,7 @@ import org.jetbrains.kotlin.backend.konan.*
|
|||||||
import org.jetbrains.kotlin.backend.konan.descriptors.*
|
import org.jetbrains.kotlin.backend.konan.descriptors.*
|
||||||
import org.jetbrains.kotlin.backend.konan.ir.*
|
import org.jetbrains.kotlin.backend.konan.ir.*
|
||||||
import org.jetbrains.kotlin.backend.konan.library.impl.buildLibrary
|
import org.jetbrains.kotlin.backend.konan.library.impl.buildLibrary
|
||||||
|
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExport
|
||||||
import org.jetbrains.kotlin.backend.konan.optimizations.*
|
import org.jetbrains.kotlin.backend.konan.optimizations.*
|
||||||
import org.jetbrains.kotlin.backend.konan.util.getValueOrNull
|
import org.jetbrains.kotlin.backend.konan.util.getValueOrNull
|
||||||
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
@@ -291,7 +292,12 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
context.log{"visitModule : ${ir2string(declaration)}"}
|
context.log{"visitModule : ${ir2string(declaration)}"}
|
||||||
|
|
||||||
declaration.acceptChildrenVoid(this)
|
declaration.acceptChildrenVoid(this)
|
||||||
appendLlvmUsed(context.llvm.usedFunctions)
|
|
||||||
|
// Note: it is here because it also generates some bitcode.
|
||||||
|
ObjCExport(context).produceObjCFramework()
|
||||||
|
|
||||||
|
appendLlvmUsed("llvm.used", context.llvm.usedFunctions + context.llvm.usedGlobals)
|
||||||
|
appendLlvmUsed("llvm.compiler.used", context.llvm.compilerUsedGlobals)
|
||||||
appendStaticInitializers(context.llvm.staticInitializers)
|
appendStaticInitializers(context.llvm.staticInitializers)
|
||||||
appendEntryPointSelector(findMainEntryPoint(context))
|
appendEntryPointSelector(findMainEntryPoint(context))
|
||||||
}
|
}
|
||||||
@@ -2311,37 +2317,9 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
|
|
||||||
fun callVirtual(descriptor: FunctionDescriptor, args: List<LLVMValueRef>,
|
fun callVirtual(descriptor: FunctionDescriptor, args: List<LLVMValueRef>,
|
||||||
resultLifetime: Lifetime): LLVMValueRef {
|
resultLifetime: Lifetime): LLVMValueRef {
|
||||||
assert(LLVMTypeOf(args[0]) == codegen.kObjHeaderPtr)
|
|
||||||
|
|
||||||
val owner = descriptor.containingDeclaration as ClassDescriptor
|
val function = functionGenerationContext.lookupVirtualImpl(args.first(), descriptor)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
val vtablePlace = functionGenerationContext.gep(typeInfoPtr, Int32(1).llvm) // typeInfoPtr + 1
|
|
||||||
val vtable = functionGenerationContext.bitcast(kInt8PtrPtr, vtablePlace)
|
|
||||||
|
|
||||||
val slot = functionGenerationContext.gep(vtable, Int32(index).llvm)
|
|
||||||
functionGenerationContext.load(slot)
|
|
||||||
} else {
|
|
||||||
// Otherwise, call by hash.
|
|
||||||
// TODO: optimize by storing interface number in lower bits of 'this' pointer
|
|
||||||
// when passing object as an interface. This way we can use those bits as index
|
|
||||||
// for an additional per-interface vtable.
|
|
||||||
val methodHash = codegen.functionHash(descriptor) // Calculate hash of the method to be invoked
|
|
||||||
val lookupArgs = listOf(typeInfoPtr, methodHash) // Prepare args for lookup
|
|
||||||
call(context.llvm.lookupOpenMethodFunction, lookupArgs)
|
|
||||||
}
|
|
||||||
val functionPtrType = pointerType(codegen.getLlvmFunctionType(descriptor)) // Construct type of the method to be invoked
|
|
||||||
val function = functionGenerationContext.bitcast(functionPtrType, llvmMethod) // Cast method address to the type
|
|
||||||
return call(descriptor, function, args, resultLifetime) // Invoke the method
|
return call(descriptor, function, args, resultLifetime) // Invoke the method
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2396,13 +2374,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
|||||||
|
|
||||||
//-------------------------------------------------------------------------//
|
//-------------------------------------------------------------------------//
|
||||||
|
|
||||||
private fun appendLlvmUsed(args: List<LLVMValueRef>) {
|
private fun appendLlvmUsed(name: String, args: List<LLVMValueRef>) {
|
||||||
if (args.isEmpty()) return
|
if (args.isEmpty()) return
|
||||||
|
|
||||||
memScoped {
|
memScoped {
|
||||||
val argsCasted = args.map { it -> constPointer(it).bitcast(int8TypePtr) }
|
val argsCasted = args.map { it -> constPointer(it).bitcast(int8TypePtr) }
|
||||||
val llvmUsedGlobal =
|
val llvmUsedGlobal =
|
||||||
context.llvm.staticData.placeGlobalArray("llvm.used", int8TypePtr, argsCasted)
|
context.llvm.staticData.placeGlobalArray(name, int8TypePtr, argsCasted)
|
||||||
|
|
||||||
LLVMSetLinkage(llvmUsedGlobal.llvmGlobal, LLVMLinkage.LLVMAppendingLinkage);
|
LLVMSetLinkage(llvmUsedGlobal.llvmGlobal, LLVMLinkage.LLVMAppendingLinkage);
|
||||||
LLVMSetSection(llvmUsedGlobal.llvmGlobal, "llvm.metadata");
|
LLVMSetSection(llvmUsedGlobal.llvmGlobal, "llvm.metadata");
|
||||||
|
|||||||
+4
-2
@@ -117,13 +117,15 @@ internal class KotlinObjCClassInfoGenerator(override val context: Context) : Con
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val impType = pointerType(functionType(int8TypePtr, true, int8TypePtr, int8TypePtr))
|
||||||
|
|
||||||
private inner class ObjCMethodDesc(
|
private inner class ObjCMethodDesc(
|
||||||
val selector: String, val encoding: String, val impFunction: LLVMValueRef
|
val selector: String, val encoding: String, val impFunction: LLVMValueRef
|
||||||
) : Struct(
|
) : Struct(
|
||||||
runtime.objCMethodDescription,
|
runtime.objCMethodDescription,
|
||||||
|
constPointer(impFunction).bitcast(impType),
|
||||||
staticData.cStringLiteral(selector),
|
staticData.cStringLiteral(selector),
|
||||||
staticData.cStringLiteral(encoding),
|
staticData.cStringLiteral(encoding)
|
||||||
constPointer(impFunction).bitcast(int8TypePtr)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun generateMethodDesc(info: ObjCMethodInfo) = ObjCMethodDesc(
|
private fun generateMethodDesc(info: ObjCMethodInfo) = ObjCMethodDesc(
|
||||||
|
|||||||
+16
-1
@@ -75,6 +75,7 @@ internal class ClassLlvmDeclarations(
|
|||||||
val bodyType: LLVMTypeRef,
|
val bodyType: LLVMTypeRef,
|
||||||
val fields: List<PropertyDescriptor>, // TODO: it is not an LLVM declaration.
|
val fields: List<PropertyDescriptor>, // TODO: it is not an LLVM declaration.
|
||||||
val typeInfoGlobal: StaticData.Global,
|
val typeInfoGlobal: StaticData.Global,
|
||||||
|
val writableTypeInfoGlobal: StaticData.Global?,
|
||||||
val typeInfo: ConstPointer,
|
val typeInfo: ConstPointer,
|
||||||
val singletonDeclarations: SingletonLlvmDeclarations?,
|
val singletonDeclarations: SingletonLlvmDeclarations?,
|
||||||
val objCDeclarations: KotlinObjCClassLlvmDeclarations?)
|
val objCDeclarations: KotlinObjCClassLlvmDeclarations?)
|
||||||
@@ -289,7 +290,21 @@ private class DeclarationsGeneratorVisitor(override val context: Context) :
|
|||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
return ClassLlvmDeclarations(bodyType, fields, typeInfoGlobal, typeInfoPtr,
|
val writableTypeInfoType = runtime.writableTypeInfoType
|
||||||
|
val writableTypeInfoGlobal = if (writableTypeInfoType == null) {
|
||||||
|
null
|
||||||
|
} else if (descriptor.isExported()) {
|
||||||
|
val name = descriptor.writableTypeInfoSymbolName
|
||||||
|
staticData.createGlobal(writableTypeInfoType, name, isExported = true).also {
|
||||||
|
it.setLinkage(LLVMLinkage.LLVMCommonLinkage) // Allows to be replaced by other bitcode module.
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
staticData.createGlobal(writableTypeInfoType, "")
|
||||||
|
}.also {
|
||||||
|
it.setZeroInitializer()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ClassLlvmDeclarations(bodyType, fields, typeInfoGlobal, writableTypeInfoGlobal, typeInfoPtr,
|
||||||
singletonDeclarations, objCDeclarations)
|
singletonDeclarations, objCDeclarations)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+23
-3
@@ -59,13 +59,29 @@ internal class ConstArray(val elemType: LLVMTypeRef?, val elements: List<ConstVa
|
|||||||
override val llvm = LLVMConstArray(elemType, elements.map { it.llvm }.toCValues(), elements.size)!!
|
override val llvm = LLVMConstArray(elemType, elements.map { it.llvm }.toCValues(), elements.size)!!
|
||||||
}
|
}
|
||||||
|
|
||||||
internal open class Struct(val type: LLVMTypeRef?, val elements: List<ConstValue>) : ConstValue {
|
internal open class Struct(val type: LLVMTypeRef?, val elements: List<ConstValue?>) : ConstValue {
|
||||||
|
|
||||||
constructor(type: LLVMTypeRef?, vararg elements: ConstValue) : this(type, elements.toList())
|
constructor(type: LLVMTypeRef?, vararg elements: ConstValue?) : this(type, elements.toList())
|
||||||
|
|
||||||
constructor(vararg elements: ConstValue) : this(structType(elements.map { it.llvmType }), *elements)
|
constructor(vararg elements: ConstValue) : this(structType(elements.map { it.llvmType }), *elements)
|
||||||
|
|
||||||
override val llvm = LLVMConstNamedStruct(type, elements.map { it.llvm }.toCValues(), elements.size)!!
|
override val llvm = LLVMConstNamedStruct(type, elements.mapIndexed { index, element ->
|
||||||
|
val expectedType = LLVMStructGetTypeAtIndex(type, index)
|
||||||
|
if (element == null) {
|
||||||
|
LLVMConstNull(expectedType)!!
|
||||||
|
} else {
|
||||||
|
element.llvm.also {
|
||||||
|
assert(it.type == expectedType) {
|
||||||
|
"Unexpected type at $index: expected ${LLVMPrintTypeToString(expectedType)!!.toKString()} " +
|
||||||
|
"got ${LLVMPrintTypeToString(it.type)!!.toKString()}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.toCValues(), elements.size)!!
|
||||||
|
|
||||||
|
init {
|
||||||
|
assert(elements.size == LLVMCountStructElementTypes(type))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class Int1(val value: Byte) : ConstValue {
|
internal class Int1(val value: Byte) : ConstValue {
|
||||||
@@ -106,6 +122,7 @@ internal fun constValue(value: LLVMValueRef) = object : ConstValue {
|
|||||||
|
|
||||||
internal val int1Type = LLVMInt1Type()!!
|
internal val int1Type = LLVMInt1Type()!!
|
||||||
internal val int8Type = LLVMInt8Type()!!
|
internal val int8Type = LLVMInt8Type()!!
|
||||||
|
internal val int16Type = LLVMInt16Type()!!
|
||||||
internal val int32Type = LLVMInt32Type()!!
|
internal val int32Type = LLVMInt32Type()!!
|
||||||
internal val int8TypePtr = pointerType(int8Type)
|
internal val int8TypePtr = pointerType(int8Type)
|
||||||
|
|
||||||
@@ -219,6 +236,9 @@ internal fun functionType(returnType: LLVMTypeRef, isVarArg: Boolean = false, va
|
|||||||
if (isVarArg) 1 else 0
|
if (isVarArg) 1 else 0
|
||||||
)!!
|
)!!
|
||||||
|
|
||||||
|
internal fun functionType(returnType: LLVMTypeRef, isVarArg: Boolean = false, paramTypes: List<LLVMTypeRef>) =
|
||||||
|
functionType(returnType, isVarArg, *paramTypes.toTypedArray())
|
||||||
|
|
||||||
|
|
||||||
fun llvm2string(value: LLVMValueRef?): String {
|
fun llvm2string(value: LLVMValueRef?): String {
|
||||||
if (value == null) return "<null>"
|
if (value == null) return "<null>"
|
||||||
|
|||||||
+106
-26
@@ -35,7 +35,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
private inner class FieldTableRecord(val nameSignature: LocalHash, val fieldOffset: Int) :
|
private inner class FieldTableRecord(val nameSignature: LocalHash, val fieldOffset: Int) :
|
||||||
Struct(runtime.fieldTableRecordType, nameSignature, Int32(fieldOffset))
|
Struct(runtime.fieldTableRecordType, nameSignature, Int32(fieldOffset))
|
||||||
|
|
||||||
private inner class MethodTableRecord(val nameSignature: LocalHash, val methodEntryPoint: ConstValue) :
|
inner class MethodTableRecord(val nameSignature: LocalHash, val methodEntryPoint: ConstPointer?) :
|
||||||
Struct(runtime.methodTableRecordType, nameSignature, methodEntryPoint)
|
Struct(runtime.methodTableRecordType, nameSignature, methodEntryPoint)
|
||||||
|
|
||||||
private inner class TypeInfo(val name: ConstValue, val size: Int,
|
private inner class TypeInfo(val name: ConstValue, val size: Int,
|
||||||
@@ -49,7 +49,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
val fields: ConstValue,
|
val fields: ConstValue,
|
||||||
val fieldsCount: Int,
|
val fieldsCount: Int,
|
||||||
val packageName: String?,
|
val packageName: String?,
|
||||||
val relativeName: String?) :
|
val relativeName: String?,
|
||||||
|
val writableTypeInfo: ConstPointer?) :
|
||||||
Struct(
|
Struct(
|
||||||
runtime.typeInfoType,
|
runtime.typeInfoType,
|
||||||
|
|
||||||
@@ -71,7 +72,9 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
Int32(fieldsCount),
|
Int32(fieldsCount),
|
||||||
|
|
||||||
kotlinStringLiteral(packageName),
|
kotlinStringLiteral(packageName),
|
||||||
kotlinStringLiteral(relativeName)
|
kotlinStringLiteral(relativeName),
|
||||||
|
|
||||||
|
*listOfNotNull(writableTypeInfo).toTypedArray()
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun kotlinStringLiteral(string: String?): ConstPointer = if (string == null) {
|
private fun kotlinStringLiteral(string: String?): ConstPointer = if (string == null) {
|
||||||
@@ -155,18 +158,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
val methods = if (classDesc.isAbstract()) {
|
val methods = if (classDesc.isAbstract()) {
|
||||||
emptyList()
|
emptyList()
|
||||||
} else {
|
} else {
|
||||||
val functionNames = mutableMapOf<Long, OverriddenFunctionDescriptor>()
|
methodTableRecords(classDesc)
|
||||||
context.getVtableBuilder(classDesc).methodTableEntries.map {
|
|
||||||
val functionName = it.overriddenDescriptor.functionName
|
|
||||||
val nameSignature = functionName.localHash
|
|
||||||
val previous = functionNames.putIfAbsent(nameSignature.value, it)
|
|
||||||
if (previous != null)
|
|
||||||
throw AssertionError("Duplicate method table entry: functionName = '$functionName', hash = '${nameSignature.value}', entry1 = $previous, entry2 = $it")
|
|
||||||
|
|
||||||
// TODO: compile-time resolution limits binary compatibility
|
|
||||||
val methodEntryPoint = it.implementation.entryPointAddress
|
|
||||||
MethodTableRecord(nameSignature, methodEntryPoint)
|
|
||||||
}.sortedBy { it.nameSignature.value }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val methodsPtr = staticData.placeGlobalConstArray("kmethods:$className",
|
val methodsPtr = staticData.placeGlobalConstArray("kmethods:$className",
|
||||||
@@ -181,7 +173,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
methodsPtr, methods.size,
|
methodsPtr, methods.size,
|
||||||
fieldsPtr, if (classDesc.isInterface) -1 else fields.size,
|
fieldsPtr, if (classDesc.isInterface) -1 else fields.size,
|
||||||
reflectionInfo.packageName,
|
reflectionInfo.packageName,
|
||||||
reflectionInfo.relativeName
|
reflectionInfo.relativeName,
|
||||||
|
llvmDeclarations.writableTypeInfoGlobal?.pointer
|
||||||
)
|
)
|
||||||
|
|
||||||
val typeInfoGlobal = llvmDeclarations.typeInfoGlobal
|
val typeInfoGlobal = llvmDeclarations.typeInfoGlobal
|
||||||
@@ -189,16 +182,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
val typeInfoGlobalValue = if (!classDesc.typeInfoHasVtableAttached) {
|
val typeInfoGlobalValue = if (!classDesc.typeInfoHasVtableAttached) {
|
||||||
typeInfo
|
typeInfo
|
||||||
} else {
|
} else {
|
||||||
// TODO: compile-time resolution limits binary compatibility
|
val vtable = vtable(classDesc)
|
||||||
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)
|
Struct(typeInfo, vtable)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,6 +192,102 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils {
|
|||||||
exportTypeInfoIfRequired(classDesc, classDesc.llvmTypeInfoPtr)
|
exportTypeInfoIfRequired(classDesc, classDesc.llvmTypeInfoPtr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun vtable(classDesc: ClassDescriptor): ConstArray {
|
||||||
|
// TODO: compile-time resolution limits binary compatibility
|
||||||
|
val vtableEntries = context.getVtableBuilder(classDesc).vtableEntries.map {
|
||||||
|
val implementation = it.implementation
|
||||||
|
if (implementation.isExternalObjCClassMethod() || implementation.modality == Modality.ABSTRACT) {
|
||||||
|
NullPointer(int8Type)
|
||||||
|
} else {
|
||||||
|
implementation.entryPointAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ConstArray(int8TypePtr, vtableEntries)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun methodTableRecords(classDesc: ClassDescriptor): List<MethodTableRecord> {
|
||||||
|
val functionNames = mutableMapOf<Long, OverriddenFunctionDescriptor>()
|
||||||
|
return context.getVtableBuilder(classDesc).methodTableEntries.map {
|
||||||
|
val functionName = it.overriddenDescriptor.functionName
|
||||||
|
val nameSignature = functionName.localHash
|
||||||
|
val previous = functionNames.putIfAbsent(nameSignature.value, it)
|
||||||
|
if (previous != null)
|
||||||
|
throw AssertionError("Duplicate method table entry: functionName = '$functionName', hash = '${nameSignature.value}', entry1 = $previous, entry2 = $it")
|
||||||
|
|
||||||
|
// TODO: compile-time resolution limits binary compatibility
|
||||||
|
val implementation = it.implementation
|
||||||
|
val methodEntryPoint = if (implementation.modality == Modality.ABSTRACT) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
implementation.entryPointAddress
|
||||||
|
}
|
||||||
|
MethodTableRecord(nameSignature, methodEntryPoint)
|
||||||
|
}.sortedBy { it.nameSignature.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: extract more code common with generate()
|
||||||
|
fun generateSyntheticInterfaceImpl(
|
||||||
|
descriptor: ClassDescriptor,
|
||||||
|
methodImpls: Map<FunctionDescriptor, ConstPointer>
|
||||||
|
): ConstPointer {
|
||||||
|
assert(descriptor.isInterface)
|
||||||
|
|
||||||
|
val name = "".globalHash
|
||||||
|
|
||||||
|
val size = 0
|
||||||
|
|
||||||
|
val superClass = context.builtIns.any
|
||||||
|
|
||||||
|
assert(superClass.implementedInterfaces.isEmpty())
|
||||||
|
val interfaces = listOf(descriptor.typeInfoPtr)
|
||||||
|
val interfacesPtr = staticData.placeGlobalConstArray("",
|
||||||
|
pointerType(runtime.typeInfoType), interfaces)
|
||||||
|
|
||||||
|
assert(superClass.getMemberScope().getVariableNames().isEmpty())
|
||||||
|
val objOffsetsPtr = NullPointer(int32Type)
|
||||||
|
val objOffsetsCount = 0
|
||||||
|
val fieldsPtr = NullPointer(runtime.fieldTableRecordType)
|
||||||
|
val fieldsCount = 0
|
||||||
|
|
||||||
|
val methods = (methodTableRecords(superClass) + methodImpls.map { (method, impl) ->
|
||||||
|
assert(method.containingDeclaration == descriptor)
|
||||||
|
MethodTableRecord(method.functionName.localHash, impl.bitcast(int8TypePtr))
|
||||||
|
}).sortedBy { it.nameSignature.value }.also {
|
||||||
|
assert(it.distinctBy { it.nameSignature.value } == it)
|
||||||
|
}
|
||||||
|
|
||||||
|
val methodsPtr = staticData.placeGlobalConstArray("", runtime.methodTableRecordType, methods)
|
||||||
|
|
||||||
|
val reflectionInfo = ReflectionInfo(null, null)
|
||||||
|
|
||||||
|
val writableTypeInfoType = runtime.writableTypeInfoType
|
||||||
|
val writableTypeInfo = if (writableTypeInfoType == null) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
staticData.createGlobal(writableTypeInfoType, "")
|
||||||
|
.also { it.setZeroInitializer() }
|
||||||
|
.pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
val typeInfo = TypeInfo(
|
||||||
|
name = name,
|
||||||
|
size = size,
|
||||||
|
superType = superClass.typeInfoPtr,
|
||||||
|
objOffsets = objOffsetsPtr, objOffsetsCount = objOffsetsCount,
|
||||||
|
interfaces = interfacesPtr, interfacesCount = interfaces.size,
|
||||||
|
methods = methodsPtr, methodsCount = methods.size,
|
||||||
|
fields = fieldsPtr, fieldsCount = fieldsCount,
|
||||||
|
packageName = reflectionInfo.packageName,
|
||||||
|
relativeName = reflectionInfo.relativeName,
|
||||||
|
writableTypeInfo = writableTypeInfo
|
||||||
|
)
|
||||||
|
|
||||||
|
val vtable = vtable(superClass)
|
||||||
|
|
||||||
|
return staticData.placeGlobal("", Struct(typeInfo, vtable))
|
||||||
|
.pointer.getElementPtr(0)
|
||||||
|
}
|
||||||
|
|
||||||
private val OverriddenFunctionDescriptor.implementation get() = getImplementation(context)
|
private val OverriddenFunctionDescriptor.implementation get() = getImplementation(context)
|
||||||
|
|
||||||
data class ReflectionInfo(val packageName: String?, val relativeName: String?)
|
data class ReflectionInfo(val packageName: String?, val relativeName: String?)
|
||||||
|
|||||||
+7
-1
@@ -29,9 +29,11 @@ interface RuntimeAware {
|
|||||||
class Runtime(bitcodeFile: String) {
|
class Runtime(bitcodeFile: String) {
|
||||||
val llvmModule: LLVMModuleRef = parseBitcodeFile(bitcodeFile)
|
val llvmModule: LLVMModuleRef = parseBitcodeFile(bitcodeFile)
|
||||||
|
|
||||||
private fun getStructType(name: String) = LLVMGetTypeByName(llvmModule, "struct.$name")!!
|
internal fun getStructTypeOrNull(name: String) = LLVMGetTypeByName(llvmModule, "struct.$name")
|
||||||
|
internal fun getStructType(name: String) = getStructTypeOrNull(name)!!
|
||||||
|
|
||||||
val typeInfoType = getStructType("TypeInfo")
|
val typeInfoType = getStructType("TypeInfo")
|
||||||
|
val writableTypeInfoType = getStructTypeOrNull("WritableTypeInfo")
|
||||||
val fieldTableRecordType = getStructType("FieldTableRecord")
|
val fieldTableRecordType = getStructType("FieldTableRecord")
|
||||||
val methodTableRecordType = getStructType("MethodTableRecord")
|
val methodTableRecordType = getStructType("MethodTableRecord")
|
||||||
val globalHashType = getStructType("GlobalHash")
|
val globalHashType = getStructType("GlobalHash")
|
||||||
@@ -58,6 +60,10 @@ class Runtime(bitcodeFile: String) {
|
|||||||
|
|
||||||
val kotlinObjCClassInfo by lazy { getStructType("KotlinObjCClassInfo") }
|
val kotlinObjCClassInfo by lazy { getStructType("KotlinObjCClassInfo") }
|
||||||
val objCMethodDescription by lazy { getStructType("ObjCMethodDescription") }
|
val objCMethodDescription by lazy { getStructType("ObjCMethodDescription") }
|
||||||
|
val objCTypeAdapter by lazy { getStructType("ObjCTypeAdapter") }
|
||||||
|
val objCToKotlinMethodAdapter by lazy { getStructType("ObjCToKotlinMethodAdapter") }
|
||||||
|
val kotlinToObjCMethodAdapter by lazy { getStructType("KotlinToObjCMethodAdapter") }
|
||||||
|
val typeInfoObjCExportAddition by lazy { getStructType("TypeInfoObjCExportAddition") }
|
||||||
|
|
||||||
|
|
||||||
val pointerSize: Int by lazy {
|
val pointerSize: Int by lazy {
|
||||||
|
|||||||
+18
@@ -65,14 +65,32 @@ internal class StaticData(override val context: Context): ContextUtils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val type get() = getGlobalType(this.llvmGlobal)
|
||||||
|
|
||||||
fun setInitializer(value: ConstValue) {
|
fun setInitializer(value: ConstValue) {
|
||||||
LLVMSetInitializer(llvmGlobal, value.llvm)
|
LLVMSetInitializer(llvmGlobal, value.llvm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setZeroInitializer() {
|
||||||
|
LLVMSetInitializer(llvmGlobal, LLVMConstNull(this.type)!!)
|
||||||
|
}
|
||||||
|
|
||||||
fun setConstant(value: Boolean) {
|
fun setConstant(value: Boolean) {
|
||||||
LLVMSetGlobalConstant(llvmGlobal, if (value) 1 else 0)
|
LLVMSetGlobalConstant(llvmGlobal, if (value) 1 else 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setLinkage(value: LLVMLinkage) {
|
||||||
|
LLVMSetLinkage(llvmGlobal, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAlignment(value: Int) {
|
||||||
|
LLVMSetAlignment(llvmGlobal, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setSection(name: String) {
|
||||||
|
LLVMSetSection(llvmGlobal, name)
|
||||||
|
}
|
||||||
|
|
||||||
val pointer = Pointer.to(this)
|
val pointer = Pointer.to(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+51
@@ -0,0 +1,51 @@
|
|||||||
|
/*
|
||||||
|
* 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.objc
|
||||||
|
|
||||||
|
import llvm.LLVMTypeRef
|
||||||
|
import llvm.LLVMValueRef
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.stdlibModule
|
||||||
|
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||||
|
|
||||||
|
internal open class ObjCCodeGenerator(val codegen: CodeGenerator) {
|
||||||
|
val context = codegen.context
|
||||||
|
|
||||||
|
val dataGenerator = ObjCDataGenerator(codegen)
|
||||||
|
|
||||||
|
fun FunctionGenerationContext.genSelector(selector: String): LLVMValueRef {
|
||||||
|
val selectorRef = dataGenerator.genSelectorRef(selector)
|
||||||
|
// TODO: clang emits it with `invariant.load` metadata.
|
||||||
|
return load(selectorRef.llvm)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FunctionGenerationContext.genGetSystemClass(name: String): LLVMValueRef {
|
||||||
|
val classRef = dataGenerator.genClassRef(name)
|
||||||
|
return load(classRef.llvm)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val objcMsgSend = constPointer(
|
||||||
|
context.llvm.externalFunction(
|
||||||
|
"objc_msgSend",
|
||||||
|
functionType(int8TypePtr, true, int8TypePtr, int8TypePtr),
|
||||||
|
context.stdlibModule.llvmSymbolOrigin
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: this doesn't support stret.
|
||||||
|
fun msgSender(functionType: LLVMTypeRef): LLVMValueRef =
|
||||||
|
objcMsgSend.bitcast(pointerType(functionType)).llvm
|
||||||
|
}
|
||||||
+248
@@ -0,0 +1,248 @@
|
|||||||
|
/*
|
||||||
|
* 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.objc
|
||||||
|
|
||||||
|
import llvm.*
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.CurrentKonanModule
|
||||||
|
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class provides methods to generate Objective-C RTTI and other data.
|
||||||
|
* It is mostly based on `clang/lib/CodeGen/CGObjCMac.cpp`, and supports only subset of operations
|
||||||
|
* required for our purposes (thus simplified).
|
||||||
|
*
|
||||||
|
* [finishModule] must be called exactly once after all required data was generated.
|
||||||
|
*/
|
||||||
|
internal class ObjCDataGenerator(val codegen: CodeGenerator) {
|
||||||
|
|
||||||
|
val context = codegen.context
|
||||||
|
|
||||||
|
fun finishModule() {
|
||||||
|
addModuleClassList(
|
||||||
|
definedClasses,
|
||||||
|
"OBJC_LABEL_CLASS_$",
|
||||||
|
"__DATA,__objc_classlist,regular,no_dead_strip"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val selectorRefs = mutableMapOf<String, ConstPointer>()
|
||||||
|
private val classRefs = mutableMapOf<String, ConstPointer>()
|
||||||
|
|
||||||
|
fun genSelectorRef(selector: String): ConstPointer = selectorRefs.getOrPut(selector) {
|
||||||
|
val literal = selectors.get(selector)
|
||||||
|
val global = codegen.staticData.placeGlobal("OBJC_SELECTOR_REFERENCES_", literal)
|
||||||
|
global.setLinkage(LLVMLinkage.LLVMPrivateLinkage)
|
||||||
|
LLVMSetExternallyInitialized(global.llvmGlobal, 1)
|
||||||
|
global.setAlignment(codegen.runtime.pointerAlignment)
|
||||||
|
global.setSection("__DATA,__objc_selrefs,literal_pointers,no_dead_strip")
|
||||||
|
|
||||||
|
context.llvm.compilerUsedGlobals += global.llvmGlobal
|
||||||
|
|
||||||
|
global.pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun genClassRef(name: String): ConstPointer = classRefs.getOrPut(name) {
|
||||||
|
val classGlobal = getClassGlobal(name, isMetaclass = false)
|
||||||
|
val global = codegen.staticData.placeGlobal("OBJC_CLASSLIST_REFERENCES_\$_", classGlobal).also {
|
||||||
|
it.setLinkage(LLVMLinkage.LLVMPrivateLinkage)
|
||||||
|
it.setSection("__DATA,__objc_classrefs,regular,no_dead_strip")
|
||||||
|
it.setAlignment(codegen.runtime.pointerAlignment)
|
||||||
|
}
|
||||||
|
|
||||||
|
context.llvm.compilerUsedGlobals += global.pointer.llvm
|
||||||
|
|
||||||
|
global.pointer.bitcast(pointerType(int8TypePtr))
|
||||||
|
}
|
||||||
|
|
||||||
|
val classObjectType = codegen.runtime.getStructType("_class_t")
|
||||||
|
|
||||||
|
private fun getClassGlobal(name: String, isMetaclass: Boolean): ConstPointer {
|
||||||
|
val prefix = if (isMetaclass) {
|
||||||
|
"OBJC_METACLASS_\$_"
|
||||||
|
} else {
|
||||||
|
"OBJC_CLASS_\$_"
|
||||||
|
}
|
||||||
|
|
||||||
|
val globalName = prefix + name
|
||||||
|
|
||||||
|
// TODO: refactor usages and use [Global] class.
|
||||||
|
val llvmGlobal = LLVMGetNamedGlobal(context.llvmModule, globalName) ?:
|
||||||
|
codegen.importGlobal(globalName, classObjectType, CurrentKonanModule)
|
||||||
|
|
||||||
|
return constPointer(llvmGlobal)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val emptyCache = constPointer(
|
||||||
|
codegen.importGlobal(
|
||||||
|
"_objc_empty_cache",
|
||||||
|
codegen.runtime.getStructType("_objc_cache"),
|
||||||
|
CurrentKonanModule
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun emitEmptyClass(name: String, superName: String) {
|
||||||
|
val runtime = context.llvm.runtime
|
||||||
|
fun struct(name: String) = runtime.getStructType(name)
|
||||||
|
|
||||||
|
val classRoType = struct("_class_ro_t")
|
||||||
|
val methodListType = struct("__method_list_t")
|
||||||
|
val protocolListType = struct("_objc_protocol_list")
|
||||||
|
val ivarListType = struct("_ivar_list_t")
|
||||||
|
val propListType = struct("_prop_list_t")
|
||||||
|
|
||||||
|
val classNameLiteral = classNames.get(name)
|
||||||
|
|
||||||
|
fun buildClassRo(isMetaclass: Boolean): ConstPointer {
|
||||||
|
// TODO: add NonFragileABI_Class_CompiledByARC flag?
|
||||||
|
|
||||||
|
val flags: Int
|
||||||
|
val start: Int
|
||||||
|
val size: Int
|
||||||
|
// TODO: stop using hard-coded values.
|
||||||
|
if (isMetaclass) {
|
||||||
|
flags = 1
|
||||||
|
start = 40
|
||||||
|
size = 40
|
||||||
|
} else {
|
||||||
|
flags = 0
|
||||||
|
start = 8
|
||||||
|
size = 8
|
||||||
|
}
|
||||||
|
|
||||||
|
val fields = mutableListOf<ConstValue>()
|
||||||
|
|
||||||
|
fields += Int32(flags)
|
||||||
|
fields += Int32(start)
|
||||||
|
fields += Int32(size)
|
||||||
|
fields += NullPointer(int8Type) // ivar layout name
|
||||||
|
fields += classNameLiteral
|
||||||
|
fields += NullPointer(methodListType)
|
||||||
|
fields += NullPointer(protocolListType)
|
||||||
|
fields += NullPointer(ivarListType)
|
||||||
|
fields += NullPointer(int8Type) // ivar layout
|
||||||
|
fields += NullPointer(propListType)
|
||||||
|
|
||||||
|
val roValue = Struct(classRoType, fields)
|
||||||
|
|
||||||
|
val roLabel = if (isMetaclass) {
|
||||||
|
"\\01l_OBJC_METACLASS_RO_\$_"
|
||||||
|
} else {
|
||||||
|
"\\01l_OBJC_CLASS_RO_\$_"
|
||||||
|
} + name
|
||||||
|
|
||||||
|
val roGlobal = context.llvm.staticData.placeGlobal(roLabel, roValue).also {
|
||||||
|
it.setLinkage(LLVMLinkage.LLVMPrivateLinkage)
|
||||||
|
it.setAlignment(runtime.pointerAlignment)
|
||||||
|
it.setSection("__DATA, __objc_const")
|
||||||
|
}
|
||||||
|
|
||||||
|
return roGlobal.pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
fun buildClassObject(
|
||||||
|
isMetaclass: Boolean,
|
||||||
|
isa: ConstPointer,
|
||||||
|
superClass: ConstPointer,
|
||||||
|
classRo: ConstPointer
|
||||||
|
): ConstPointer {
|
||||||
|
val fields = mutableListOf<ConstValue>()
|
||||||
|
|
||||||
|
fields += isa
|
||||||
|
fields += superClass
|
||||||
|
fields += emptyCache
|
||||||
|
val vtableEntryType = pointerType(functionType(int8TypePtr, false, int8TypePtr, int8TypePtr))
|
||||||
|
fields += NullPointer(vtableEntryType) // empty vtable
|
||||||
|
fields += classRo
|
||||||
|
|
||||||
|
val classObjectValue = Struct(classObjectType, fields)
|
||||||
|
val classGlobal = getClassGlobal(name, isMetaclass = isMetaclass)
|
||||||
|
|
||||||
|
LLVMSetInitializer(classGlobal.llvm, classObjectValue.llvm)
|
||||||
|
LLVMSetSection(classGlobal.llvm, "__DATA, __objc_data")
|
||||||
|
LLVMSetAlignment(classGlobal.llvm, LLVMABIAlignmentOfType(runtime.targetData, classObjectType))
|
||||||
|
|
||||||
|
context.llvm.usedGlobals.add(classGlobal.llvm)
|
||||||
|
|
||||||
|
return classGlobal
|
||||||
|
}
|
||||||
|
|
||||||
|
val metaclassObject = buildClassObject(
|
||||||
|
isMetaclass = true,
|
||||||
|
isa = getClassGlobal("NSObject", isMetaclass = true),
|
||||||
|
superClass = getClassGlobal(superName, isMetaclass = true),
|
||||||
|
classRo = buildClassRo(isMetaclass = true)
|
||||||
|
)
|
||||||
|
|
||||||
|
val classObject = buildClassObject(
|
||||||
|
isMetaclass = false,
|
||||||
|
isa = metaclassObject,
|
||||||
|
superClass = getClassGlobal(superName, isMetaclass = false),
|
||||||
|
classRo = buildClassRo(isMetaclass = false)
|
||||||
|
)
|
||||||
|
|
||||||
|
definedClasses.add(classObject)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val definedClasses = mutableListOf<ConstPointer>()
|
||||||
|
|
||||||
|
private fun addModuleClassList(elements: List<ConstPointer>, name: String, section: String) {
|
||||||
|
if (elements.isEmpty()) return
|
||||||
|
|
||||||
|
val global = context.llvm.staticData.placeGlobalArray(
|
||||||
|
name,
|
||||||
|
int8TypePtr,
|
||||||
|
elements.map { it.bitcast(int8TypePtr) }
|
||||||
|
)
|
||||||
|
|
||||||
|
global.setAlignment(
|
||||||
|
LLVMABIAlignmentOfType(
|
||||||
|
context.llvm.runtime.targetData,
|
||||||
|
LLVMGetInitializer(global.llvmGlobal)!!.type
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
global.setSection(section)
|
||||||
|
|
||||||
|
context.llvm.compilerUsedGlobals += global.llvmGlobal
|
||||||
|
}
|
||||||
|
|
||||||
|
private val classNames =
|
||||||
|
CStringLiteralsTable("OBJC_CLASS_NAME_", "__TEXT,__objc_classname,cstring_literals")
|
||||||
|
|
||||||
|
private val selectors =
|
||||||
|
CStringLiteralsTable("OBJC_METH_VAR_NAME_", "__TEXT,__objc_methname,cstring_literals")
|
||||||
|
|
||||||
|
private inner class CStringLiteralsTable(val label: String, val section: String) {
|
||||||
|
|
||||||
|
private val literals = mutableMapOf<String, ConstPointer>()
|
||||||
|
|
||||||
|
fun get(value: String) = literals.getOrPut(value) {
|
||||||
|
val bytes = value.toByteArray(Charsets.UTF_8).map { Int8(it) } + Int8(0)
|
||||||
|
val global = context.llvm.staticData.placeGlobalArray(label, int8Type, bytes)
|
||||||
|
|
||||||
|
global.setConstant(true)
|
||||||
|
global.setLinkage(LLVMLinkage.LLVMPrivateLinkage)
|
||||||
|
global.setSection(section)
|
||||||
|
LLVMSetUnnamedAddr(global.llvmGlobal, 1)
|
||||||
|
global.setAlignment(1)
|
||||||
|
|
||||||
|
context.llvm.compilerUsedGlobals += global.llvmGlobal
|
||||||
|
|
||||||
|
global.pointer.getElementPtr(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+229
@@ -0,0 +1,229 @@
|
|||||||
|
/*
|
||||||
|
* 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.objcexport
|
||||||
|
|
||||||
|
import llvm.*
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.CurrentKonanModule
|
||||||
|
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||||
|
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
|
||||||
|
internal fun ObjCExportCodeGenerator.generateKotlinFunctionImpl(invokeMethod: FunctionDescriptor): ConstPointer {
|
||||||
|
// TODO: consider also overriding methods of `Any`.
|
||||||
|
|
||||||
|
val numberOfParameters = invokeMethod.valueParameters.size
|
||||||
|
|
||||||
|
val function = generateFunction(
|
||||||
|
codegen,
|
||||||
|
codegen.getLlvmFunctionType(invokeMethod),
|
||||||
|
"invokeFunction$numberOfParameters"
|
||||||
|
) {
|
||||||
|
val args = (0 until numberOfParameters).map { index -> kotlinReferenceToObjC(param(index + 1)) }
|
||||||
|
|
||||||
|
val rawBlockPtr = callFromBridge(context.llvm.Kotlin_ObjCExport_GetAssociatedObject, listOf(param(0)))
|
||||||
|
|
||||||
|
val blockLiteralType = codegen.runtime.getStructType("Block_literal_1")
|
||||||
|
val blockPtr = bitcast(pointerType(blockLiteralType), rawBlockPtr)
|
||||||
|
val invokePtr = structGep(blockPtr, 3)
|
||||||
|
|
||||||
|
val blockInvokeType = functionType(int8TypePtr, false, (0 .. numberOfParameters).map { int8TypePtr })
|
||||||
|
|
||||||
|
val invoke = bitcast(pointerType(blockInvokeType), load(invokePtr))
|
||||||
|
val result = callFromBridge(invoke, listOf(rawBlockPtr) + args)
|
||||||
|
|
||||||
|
// TODO: support void-as-Unit.
|
||||||
|
ret(objCReferenceToKotlin(result, Lifetime.RETURN_VALUE))
|
||||||
|
}.also {
|
||||||
|
LLVMSetLinkage(it, LLVMLinkage.LLVMInternalLinkage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return constPointer(function)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjCExportCodeGenerator) {
|
||||||
|
private val codegen get() = objCExportCodeGenerator.codegen
|
||||||
|
|
||||||
|
private val blockLiteralType = structType(
|
||||||
|
codegen.runtime.getStructType("Block_literal_1"),
|
||||||
|
codegen.kObjHeaderPtr
|
||||||
|
)
|
||||||
|
|
||||||
|
private val blockDescriptorType = codegen.runtime.getStructType("Block_descriptor_1")
|
||||||
|
|
||||||
|
val disposeHelper = generateFunction(
|
||||||
|
codegen,
|
||||||
|
functionType(voidType, false, int8TypePtr),
|
||||||
|
"blockDisposeHelper"
|
||||||
|
) {
|
||||||
|
val blockPtr = bitcast(pointerType(blockLiteralType), param(0))
|
||||||
|
val slot = structGep(blockPtr, 1)
|
||||||
|
storeAny(kNullObjHeaderPtr, slot) // TODO: can dispose_helper write to the block?
|
||||||
|
|
||||||
|
ret(null)
|
||||||
|
}.also {
|
||||||
|
LLVMSetLinkage(it, LLVMLinkage.LLVMInternalLinkage)
|
||||||
|
}
|
||||||
|
|
||||||
|
val copyHelper = generateFunction(
|
||||||
|
codegen,
|
||||||
|
functionType(voidType, false, int8TypePtr, int8TypePtr),
|
||||||
|
"blockCopyHelper"
|
||||||
|
) {
|
||||||
|
val dstBlockPtr = bitcast(pointerType(blockLiteralType), param(0))
|
||||||
|
val dstSlot = structGep(dstBlockPtr, 1)
|
||||||
|
|
||||||
|
val srcBlockPtr = bitcast(pointerType(blockLiteralType), param(1))
|
||||||
|
val srcSlot = structGep(srcBlockPtr, 1)
|
||||||
|
|
||||||
|
// Kotlin reference was `memcpy`ed from src to dst, "revert" this:
|
||||||
|
storeRefUnsafe(kNullObjHeaderPtr, dstSlot)
|
||||||
|
// and copy properly:
|
||||||
|
storeAny(loadSlot(srcSlot, isVar = false), dstSlot)
|
||||||
|
|
||||||
|
ret(null)
|
||||||
|
}.also {
|
||||||
|
LLVMSetLinkage(it, LLVMLinkage.LLVMInternalLinkage)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateDescriptorForBlockAdapterToFunction(numberOfParameters: Int): ConstValue {
|
||||||
|
val signature = buildString {
|
||||||
|
append('@')
|
||||||
|
val pointerSize = codegen.runtime.pointerSize
|
||||||
|
append(pointerSize * (numberOfParameters + 1))
|
||||||
|
|
||||||
|
var paramOffset = 0L
|
||||||
|
|
||||||
|
(0 .. numberOfParameters).forEach { index ->
|
||||||
|
append('@')
|
||||||
|
if (index == 0) append('?')
|
||||||
|
append(paramOffset)
|
||||||
|
paramOffset += pointerSize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(codegen.context.is64Bit())
|
||||||
|
|
||||||
|
return Struct(blockDescriptorType,
|
||||||
|
Int64(0),
|
||||||
|
Int64(LLVMStoreSizeOfType(codegen.runtime.targetData, blockLiteralType)),
|
||||||
|
constPointer(copyHelper),
|
||||||
|
constPointer(disposeHelper),
|
||||||
|
codegen.staticData.cStringLiteral(signature),
|
||||||
|
NullPointer(int8Type)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FunctionGenerationContext.storeRefUnsafe(value: LLVMValueRef, slot: LLVMValueRef) {
|
||||||
|
assert(value.type == kObjHeaderPtr)
|
||||||
|
assert(slot.type == kObjHeaderPtrPtr)
|
||||||
|
|
||||||
|
storeAny(
|
||||||
|
bitcast(int8TypePtr, value),
|
||||||
|
bitcast(pointerType(int8TypePtr), slot)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.generateInvoke(numberOfParameters: Int): ConstPointer {
|
||||||
|
val functionType = functionType(
|
||||||
|
int8TypePtr,
|
||||||
|
false,
|
||||||
|
(0 .. numberOfParameters).map { int8TypePtr }
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = generateFunction(codegen, functionType, "invokeBlock$numberOfParameters") {
|
||||||
|
val blockPtr = bitcast(pointerType(blockLiteralType), param(0))
|
||||||
|
val kotlinFunction = loadSlot(structGep(blockPtr, 1), isVar = false)
|
||||||
|
|
||||||
|
val args = (1 .. numberOfParameters).map { index ->
|
||||||
|
objCReferenceToKotlin(param(index), Lifetime.ARGUMENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
val callee = lookupVirtualImpl(kotlinFunction, context.builtIns.getInvokeDescriptor(numberOfParameters))
|
||||||
|
|
||||||
|
val result = callFromBridge(callee, listOf(kotlinFunction) + args, Lifetime.ARGUMENT)
|
||||||
|
|
||||||
|
ret(kotlinReferenceToObjC(result))
|
||||||
|
}.also {
|
||||||
|
LLVMSetLinkage(it, LLVMLinkage.LLVMInternalLinkage)
|
||||||
|
}
|
||||||
|
|
||||||
|
return constPointer(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun ObjCExportCodeGenerator.generateConvertFunctionToBlock(numberOfParameters: Int): LLVMValueRef {
|
||||||
|
val blockDescriptor = codegen.staticData.placeGlobal(
|
||||||
|
"",
|
||||||
|
generateDescriptorForBlockAdapterToFunction(numberOfParameters)
|
||||||
|
)
|
||||||
|
|
||||||
|
return generateFunction(
|
||||||
|
codegen,
|
||||||
|
functionType(int8TypePtr, false, codegen.kObjHeaderPtr),
|
||||||
|
"convertFunction$numberOfParameters"
|
||||||
|
) {
|
||||||
|
val isa = codegen.importGlobal(
|
||||||
|
"_NSConcreteStackBlock",
|
||||||
|
int8TypePtr,
|
||||||
|
CurrentKonanModule
|
||||||
|
)
|
||||||
|
|
||||||
|
val flags = Int32((1 shl 25) or (1 shl 30) or (1 shl 31)).llvm
|
||||||
|
val reserved = Int32(0).llvm
|
||||||
|
|
||||||
|
val invokeType = pointerType(functionType(voidType, true, int8TypePtr))
|
||||||
|
val invoke = generateInvoke(numberOfParameters).bitcast(invokeType).llvm
|
||||||
|
val descriptor = blockDescriptor.llvmGlobal
|
||||||
|
|
||||||
|
val blockOnStack = alloca(blockLiteralType)
|
||||||
|
val blockOnStackBase = structGep(blockOnStack, 0)
|
||||||
|
val slot = structGep(blockOnStack, 1)
|
||||||
|
|
||||||
|
listOf(bitcast(int8TypePtr, isa), flags, reserved, invoke, descriptor).forEachIndexed { index, it ->
|
||||||
|
storeAny(it, structGep(blockOnStackBase, index))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: it is the slot in the block located on stack, so no need to manage it properly:
|
||||||
|
storeRefUnsafe(param(0), slot)
|
||||||
|
|
||||||
|
val retainBlock = context.llvm.externalFunction(
|
||||||
|
"objc_retainBlock",
|
||||||
|
functionType(int8TypePtr, false, int8TypePtr),
|
||||||
|
CurrentKonanModule
|
||||||
|
)
|
||||||
|
|
||||||
|
val copiedBlock = callFromBridge(retainBlock, listOf(bitcast(int8TypePtr, blockOnStack)))
|
||||||
|
|
||||||
|
val autoreleaseReturnValue = context.llvm.externalFunction(
|
||||||
|
"objc_autoreleaseReturnValue",
|
||||||
|
functionType(int8TypePtr, false, int8TypePtr),
|
||||||
|
CurrentKonanModule
|
||||||
|
)
|
||||||
|
|
||||||
|
ret(callFromBridge(autoreleaseReturnValue, listOf(copiedBlock)))
|
||||||
|
}.also {
|
||||||
|
LLVMSetLinkage(it, LLVMLinkage.LLVMInternalLinkage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KotlinBuiltIns.getInvokeDescriptor(numberOfParameters: Int): FunctionDescriptor =
|
||||||
|
getFunction(numberOfParameters).unsubstitutedMemberScope.getContributedFunctions(
|
||||||
|
Name.identifier("invoke"),
|
||||||
|
NoLookupLocation.FROM_BACKEND
|
||||||
|
).single()
|
||||||
+791
@@ -0,0 +1,791 @@
|
|||||||
|
/*
|
||||||
|
* 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.objcexport
|
||||||
|
|
||||||
|
import llvm.*
|
||||||
|
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
|
||||||
|
import org.jetbrains.kotlin.backend.konan.*
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.backend.konan.llvm.*
|
||||||
|
import org.jetbrains.kotlin.backend.konan.llvm.objc.ObjCCodeGenerator
|
||||||
|
import org.jetbrains.kotlin.backend.konan.objcexport.*
|
||||||
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassOrAny
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
|
||||||
|
internal class ObjCExportCodeGenerator(
|
||||||
|
codegen: CodeGenerator,
|
||||||
|
val namer: ObjCExportNamer,
|
||||||
|
val mapper: ObjCExportMapper
|
||||||
|
) : ObjCCodeGenerator(codegen) {
|
||||||
|
|
||||||
|
val runtime get() = codegen.runtime
|
||||||
|
val staticData get() = codegen.staticData
|
||||||
|
|
||||||
|
val rttiGenerator = RTTIGenerator(context)
|
||||||
|
|
||||||
|
// TODO: currently bridges don't have any custom `landingpad`s,
|
||||||
|
// so it is correct to use [callAtFunctionScope] here.
|
||||||
|
// However, exception handling probably should be refactored
|
||||||
|
// (e.g. moved from `IrToBitcode.kt` to [FunctionGenerationContext]).
|
||||||
|
fun FunctionGenerationContext.callFromBridge(
|
||||||
|
function: LLVMValueRef,
|
||||||
|
args: List<LLVMValueRef>,
|
||||||
|
resultLifetime: Lifetime = Lifetime.IRRELEVANT
|
||||||
|
): LLVMValueRef = callAtFunctionScope(function, args, resultLifetime)
|
||||||
|
|
||||||
|
fun FunctionGenerationContext.genSendMessage(
|
||||||
|
returnType: LLVMTypeRef,
|
||||||
|
receiver: LLVMValueRef,
|
||||||
|
selector: String,
|
||||||
|
vararg args: LLVMValueRef
|
||||||
|
): LLVMValueRef {
|
||||||
|
|
||||||
|
val objcMsgSendType = functionType(
|
||||||
|
returnType,
|
||||||
|
false,
|
||||||
|
listOf(int8TypePtr, int8TypePtr) + args.map { it.type }
|
||||||
|
)
|
||||||
|
|
||||||
|
return callFromBridge(msgSender(objcMsgSendType), listOf(receiver, genSelector(selector)) + args)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FunctionGenerationContext.kotlinToObjC(
|
||||||
|
value: LLVMValueRef,
|
||||||
|
valueType: ObjCValueType
|
||||||
|
): LLVMValueRef = when (valueType) {
|
||||||
|
ObjCValueType.BOOL -> zext(value, int8Type) // TODO: zext behaviour may be strange on bit types.
|
||||||
|
|
||||||
|
ObjCValueType.CHAR, ObjCValueType.UNSIGNED_SHORT, ObjCValueType.SHORT,
|
||||||
|
ObjCValueType.INT, ObjCValueType.LONG_LONG, ObjCValueType.FLOAT, ObjCValueType.DOUBLE -> value
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun FunctionGenerationContext.objCToKotlin(
|
||||||
|
value: LLVMValueRef,
|
||||||
|
valueType: ObjCValueType
|
||||||
|
): LLVMValueRef = when (valueType) {
|
||||||
|
ObjCValueType.BOOL -> icmpNe(value, Int8(0).llvm)
|
||||||
|
|
||||||
|
ObjCValueType.CHAR, ObjCValueType.UNSIGNED_SHORT, ObjCValueType.SHORT,
|
||||||
|
ObjCValueType.INT, ObjCValueType.LONG_LONG, ObjCValueType.FLOAT, ObjCValueType.DOUBLE -> value
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FunctionGenerationContext.kotlinReferenceToObjC(value: LLVMValueRef) =
|
||||||
|
callFromBridge(context.llvm.Kotlin_ObjCExport_refToObjC, listOf(value))
|
||||||
|
|
||||||
|
fun FunctionGenerationContext.objCReferenceToKotlin(value: LLVMValueRef, resultLifetime: Lifetime) =
|
||||||
|
callFromBridge(context.llvm.Kotlin_ObjCExport_refFromObjC, listOf(value), resultLifetime)
|
||||||
|
|
||||||
|
fun FunctionGenerationContext.kotlinToObjC(
|
||||||
|
value: LLVMValueRef,
|
||||||
|
typeBridge: TypeBridge
|
||||||
|
): LLVMValueRef = when (typeBridge) {
|
||||||
|
is ReferenceBridge -> kotlinReferenceToObjC(value)
|
||||||
|
is ValueTypeBridge -> kotlinToObjC(value, typeBridge.objCValueType)
|
||||||
|
is HashCodeBridge -> {
|
||||||
|
assert(codegen.context.is64Bit())
|
||||||
|
zext(value, kInt64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun FunctionGenerationContext.objCToKotlin(
|
||||||
|
value: LLVMValueRef,
|
||||||
|
typeBridge: TypeBridge,
|
||||||
|
resultLifetime: Lifetime
|
||||||
|
): LLVMValueRef = when (typeBridge) {
|
||||||
|
is ReferenceBridge -> objCReferenceToKotlin(value, resultLifetime)
|
||||||
|
is ValueTypeBridge -> objCToKotlin(value, typeBridge.objCValueType)
|
||||||
|
is HashCodeBridge -> {
|
||||||
|
assert(codegen.context.is64Bit())
|
||||||
|
val low = trunc(value, int32Type)
|
||||||
|
val high = trunc(shr(value, 32, signed = false), int32Type)
|
||||||
|
xor(low, high)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline fun FunctionGenerationContext.convertKotlin(
|
||||||
|
genValue: (Lifetime) -> LLVMValueRef,
|
||||||
|
actualType: KotlinType,
|
||||||
|
expectedType: KotlinType,
|
||||||
|
resultLifetime: Lifetime
|
||||||
|
): LLVMValueRef {
|
||||||
|
|
||||||
|
val conversion = context.ir.symbols.getTypeConversion(actualType, expectedType)
|
||||||
|
?: return genValue(resultLifetime)
|
||||||
|
|
||||||
|
val value = genValue(Lifetime.ARGUMENT)
|
||||||
|
|
||||||
|
return callFromBridge(conversion.descriptor.llvmFunction, listOf(value), resultLifetime)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun emitRtti(
|
||||||
|
generatedClasses: Collection<ClassDescriptor>,
|
||||||
|
topLevel: Map<FqName, List<CallableMemberDescriptor>>
|
||||||
|
) {
|
||||||
|
val objCTypeAdapters = mutableListOf<ObjCTypeAdapter>()
|
||||||
|
|
||||||
|
generatedClasses.forEach {
|
||||||
|
objCTypeAdapters += createTypeAdapter(it)
|
||||||
|
val className = namer.getClassOrProtocolName(it)
|
||||||
|
val superClass = it.getSuperClassOrAny()
|
||||||
|
val superClassName = namer.getClassOrProtocolName(superClass)
|
||||||
|
|
||||||
|
dataGenerator.emitEmptyClass(className, superClassName)
|
||||||
|
// Note: it is generated only to be visible for linker.
|
||||||
|
// Methods will be added at runtime.
|
||||||
|
}
|
||||||
|
|
||||||
|
topLevel.forEach { fqName, declarations ->
|
||||||
|
objCTypeAdapters += createTypeAdapterForPackage(fqName, declarations)
|
||||||
|
dataGenerator.emitEmptyClass(namer.getPackageName(fqName), namer.kotlinAnyName)
|
||||||
|
}
|
||||||
|
|
||||||
|
emitSpecialClassesConvertions()
|
||||||
|
|
||||||
|
objCTypeAdapters += createTypeAdapter(context.builtIns.any)
|
||||||
|
|
||||||
|
val placedClassAdapters = mutableMapOf<String, ConstPointer>()
|
||||||
|
val placedInterfaceAdapters = mutableMapOf<String, ConstPointer>()
|
||||||
|
|
||||||
|
objCTypeAdapters.forEach { adapter ->
|
||||||
|
val typeAdapter = staticData.placeGlobal("", adapter).pointer
|
||||||
|
val descriptor = adapter.descriptor
|
||||||
|
|
||||||
|
val descriptorToAdapter = if (descriptor?.isInterface == true) {
|
||||||
|
placedInterfaceAdapters
|
||||||
|
} else {
|
||||||
|
// Objective-C class for Kotlin class or top-level declarations.
|
||||||
|
placedClassAdapters
|
||||||
|
}
|
||||||
|
descriptorToAdapter[adapter.objCName] = typeAdapter
|
||||||
|
|
||||||
|
if (descriptor != null) {
|
||||||
|
setObjCExportTypeInfo(descriptor, typeAdapter = typeAdapter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun emitSortedAdapters(nameToAdapter: Map<String, ConstPointer>, prefix: String) {
|
||||||
|
val sortedAdapters = nameToAdapter.toList().sortedBy { it.first }.map {
|
||||||
|
it.second
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sortedAdapters.isNotEmpty()) {
|
||||||
|
val type = sortedAdapters.first().llvmType
|
||||||
|
val sortedAdaptersPointer = staticData.placeGlobalConstArray("", type, sortedAdapters)
|
||||||
|
|
||||||
|
// Note: this globals replace runtime globals with weak linkage:
|
||||||
|
staticData.placeGlobal(prefix, sortedAdaptersPointer, isExported = true)
|
||||||
|
staticData.placeGlobal("${prefix}Num", Int32(sortedAdapters.size), isExported = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
emitSortedAdapters(placedClassAdapters, "Kotlin_ObjCExport_sortedClassAdapters")
|
||||||
|
emitSortedAdapters(placedInterfaceAdapters, "Kotlin_ObjCExport_sortedProtocolAdapters")
|
||||||
|
|
||||||
|
context.llvm.kObjectReservedTailSize!!.setInitializer(Int32(runtime.pointerSize))
|
||||||
|
|
||||||
|
dataGenerator.finishModule() // TODO: move to appropriate place.
|
||||||
|
}
|
||||||
|
|
||||||
|
private val impType = pointerType(functionType(int8TypePtr, true, int8TypePtr, int8TypePtr))
|
||||||
|
|
||||||
|
inner class ObjCToKotlinMethodAdapter(
|
||||||
|
selector: String,
|
||||||
|
encoding: String,
|
||||||
|
imp: ConstPointer
|
||||||
|
) : Struct(
|
||||||
|
runtime.objCToKotlinMethodAdapter,
|
||||||
|
staticData.cStringLiteral(selector),
|
||||||
|
staticData.cStringLiteral(encoding),
|
||||||
|
imp.bitcast(impType)
|
||||||
|
)
|
||||||
|
|
||||||
|
inner class KotlinToObjCMethodAdapter(
|
||||||
|
selector: String,
|
||||||
|
nameSignature: Long,
|
||||||
|
vtableIndex: Int,
|
||||||
|
kotlinImpl: ConstPointer
|
||||||
|
) : Struct(
|
||||||
|
runtime.kotlinToObjCMethodAdapter,
|
||||||
|
staticData.cStringLiteral(selector),
|
||||||
|
Int64(nameSignature),
|
||||||
|
Int32(vtableIndex),
|
||||||
|
kotlinImpl
|
||||||
|
)
|
||||||
|
|
||||||
|
inner class ObjCTypeAdapter(
|
||||||
|
val descriptor: ClassDescriptor?,
|
||||||
|
typeInfo: ConstPointer?,
|
||||||
|
vtable: ConstPointer?,
|
||||||
|
vtableSize: Int,
|
||||||
|
methodTable: List<RTTIGenerator.MethodTableRecord>,
|
||||||
|
val objCName: String,
|
||||||
|
directAdapters: List<ObjCToKotlinMethodAdapter>,
|
||||||
|
virtualAdapters: List<ObjCToKotlinMethodAdapter>,
|
||||||
|
reverseAdapters: List<KotlinToObjCMethodAdapter>
|
||||||
|
) : Struct(
|
||||||
|
runtime.objCTypeAdapter,
|
||||||
|
typeInfo,
|
||||||
|
|
||||||
|
vtable,
|
||||||
|
Int32(vtableSize),
|
||||||
|
|
||||||
|
staticData.placeGlobalConstArray("", runtime.methodTableRecordType, methodTable),
|
||||||
|
Int32(methodTable.size),
|
||||||
|
|
||||||
|
staticData.cStringLiteral(objCName),
|
||||||
|
|
||||||
|
staticData.placeGlobalConstArray(
|
||||||
|
"",
|
||||||
|
runtime.objCToKotlinMethodAdapter,
|
||||||
|
directAdapters
|
||||||
|
),
|
||||||
|
Int32(directAdapters.size),
|
||||||
|
|
||||||
|
staticData.placeGlobalConstArray(
|
||||||
|
"",
|
||||||
|
runtime.objCToKotlinMethodAdapter,
|
||||||
|
virtualAdapters
|
||||||
|
),
|
||||||
|
Int32(virtualAdapters.size),
|
||||||
|
|
||||||
|
staticData.placeGlobalConstArray(
|
||||||
|
"",
|
||||||
|
runtime.kotlinToObjCMethodAdapter,
|
||||||
|
reverseAdapters
|
||||||
|
),
|
||||||
|
Int32(reverseAdapters.size)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.setObjCExportTypeInfo(
|
||||||
|
descriptor: ClassDescriptor,
|
||||||
|
converter: ConstPointer? = null,
|
||||||
|
objCClass: ConstPointer? = null,
|
||||||
|
typeAdapter: ConstPointer? = null
|
||||||
|
) {
|
||||||
|
val objCExportAddition = Struct(runtime.typeInfoObjCExportAddition,
|
||||||
|
converter,
|
||||||
|
objCClass,
|
||||||
|
typeAdapter
|
||||||
|
)
|
||||||
|
|
||||||
|
val writableTypeInfoType = runtime.writableTypeInfoType!!
|
||||||
|
val writableTypeInfoValue = Struct(writableTypeInfoType, objCExportAddition)
|
||||||
|
|
||||||
|
val global = if (codegen.isExternal(descriptor)) {
|
||||||
|
// Note: this global replaces the external one with common linkage.
|
||||||
|
staticData.createGlobal(
|
||||||
|
writableTypeInfoType,
|
||||||
|
descriptor.writableTypeInfoSymbolName,
|
||||||
|
isExported = true
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
context.llvmDeclarations.forClass(descriptor).writableTypeInfoGlobal!!.also {
|
||||||
|
it.setLinkage(LLVMLinkage.LLVMExternalLinkage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
global.setInitializer(writableTypeInfoValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ObjCExportCodeGenerator.kotlinToObjCFunctionType: LLVMTypeRef
|
||||||
|
get() = functionType(int8TypePtr, false, codegen.kObjHeaderPtr)
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.emitBoxConverter(objCValueType: ObjCValueType) {
|
||||||
|
val valueType = objCValueType.kotlinValueType
|
||||||
|
|
||||||
|
val symbols = context.ir.symbols
|
||||||
|
|
||||||
|
val name = "${valueType.classFqName.shortName()}To${objCValueType.nsNumberName}"
|
||||||
|
|
||||||
|
val converter = generateFunction(codegen, kotlinToObjCFunctionType, name) {
|
||||||
|
val unboxFunction = symbols.getUnboxFunction(valueType).descriptor.llvmFunction
|
||||||
|
val kotlinValue = callFromBridge(
|
||||||
|
unboxFunction,
|
||||||
|
listOf(param(0)),
|
||||||
|
Lifetime.IRRELEVANT
|
||||||
|
)
|
||||||
|
|
||||||
|
val value = kotlinToObjC(kotlinValue, objCValueType)
|
||||||
|
|
||||||
|
val nsNumber = genGetSystemClass("NSNumber")
|
||||||
|
ret(genSendMessage(int8TypePtr, nsNumber, objCValueType.nsNumberFactorySelector, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
LLVMSetLinkage(converter, LLVMLinkage.LLVMPrivateLinkage)
|
||||||
|
|
||||||
|
val boxClass = symbols.boxClasses[valueType]!!
|
||||||
|
setObjCExportTypeInfo(boxClass.descriptor, constPointer(converter))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.emitFunctionConverters() {
|
||||||
|
val generator = BlockAdapterToFunctionGenerator(this)
|
||||||
|
|
||||||
|
(0 .. 22).forEach { numberOfParameters ->
|
||||||
|
val converter = generator.run { generateConvertFunctionToBlock(numberOfParameters) }
|
||||||
|
setObjCExportTypeInfo(context.builtIns.getFunction(numberOfParameters), constPointer(converter))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.generateKotlinFunctionAdapterToBlock(numberOfParameters: Int): ConstPointer {
|
||||||
|
val interfaceDescriptor = codegen.context.builtIns.getFunction(numberOfParameters)
|
||||||
|
val invokeMethod = interfaceDescriptor.unsubstitutedMemberScope.getContributedFunctions(
|
||||||
|
Name.identifier("invoke"), NoLookupLocation.FROM_BACKEND
|
||||||
|
).single()
|
||||||
|
|
||||||
|
val invokeImpl = generateKotlinFunctionImpl(invokeMethod)
|
||||||
|
|
||||||
|
return rttiGenerator.generateSyntheticInterfaceImpl(
|
||||||
|
interfaceDescriptor,
|
||||||
|
mapOf(invokeMethod to invokeImpl)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.emitKotlinFunctionAdaptersToBlock() {
|
||||||
|
val ptr = staticData.placeGlobalArray(
|
||||||
|
"",
|
||||||
|
pointerType(runtime.typeInfoType),
|
||||||
|
(0 .. 22).map {
|
||||||
|
generateKotlinFunctionAdapterToBlock(it)
|
||||||
|
}
|
||||||
|
).pointer.getElementPtr(0)
|
||||||
|
|
||||||
|
// Note: this global replaces the weak global defined in runtime.
|
||||||
|
staticData.placeGlobal("Kotlin_ObjCExport_functionAdaptersToBlock", ptr, isExported = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() {
|
||||||
|
setObjCExportTypeInfo(
|
||||||
|
context.builtIns.string,
|
||||||
|
constPointer(context.llvm.Kotlin_Interop_CreateNSStringFromKString)
|
||||||
|
)
|
||||||
|
|
||||||
|
setObjCExportTypeInfo(
|
||||||
|
context.builtIns.list,
|
||||||
|
constPointer(context.llvm.Kotlin_Interop_CreateNSArrayFromKList)
|
||||||
|
)
|
||||||
|
|
||||||
|
ObjCValueType.values().forEach {
|
||||||
|
emitBoxConverter(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
emitFunctionConverters()
|
||||||
|
|
||||||
|
emitKotlinFunctionAdaptersToBlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.generateObjCImp(
|
||||||
|
target: FunctionDescriptor?,
|
||||||
|
methodBridge: MethodBridge,
|
||||||
|
isVirtual: Boolean = false
|
||||||
|
): LLVMValueRef {
|
||||||
|
// TODO: adapt exceptions.
|
||||||
|
|
||||||
|
val returnType = methodBridge.returnBridge
|
||||||
|
|
||||||
|
val result = LLVMAddFunction(context.llvmModule, "", objCFunctionType(methodBridge))!!
|
||||||
|
|
||||||
|
generateFunction(codegen, result) {
|
||||||
|
// TODO: call [NSObject init] if it is a constructor?
|
||||||
|
// TODO: check for abstract class if it is a constructor.
|
||||||
|
|
||||||
|
if (methodBridge.isKotlinTopLevel) {
|
||||||
|
callFromBridge(context.llvm.initRuntimeIfNeeded, emptyList())
|
||||||
|
// For instance methods it gets called when allocating.
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target == null) {
|
||||||
|
// IMP for abstract method.
|
||||||
|
callFromBridge(
|
||||||
|
context.llvm.Kotlin_ObjCExport_AbstractMethodCalled,
|
||||||
|
listOf(param(0), param(1))
|
||||||
|
)
|
||||||
|
unreachable()
|
||||||
|
return@generateFunction
|
||||||
|
}
|
||||||
|
|
||||||
|
val args = methodBridge.paramBridges.mapIndexedNotNull { index, typeBridge ->
|
||||||
|
val isReceiver = index == 0
|
||||||
|
if (isReceiver && methodBridge.isKotlinTopLevel) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
val param = param(if (isReceiver) index else index + 1)
|
||||||
|
objCToKotlin(param, typeBridge, Lifetime.ARGUMENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val llvmTarget = if (!isVirtual) {
|
||||||
|
codegen.llvmFunction(target)
|
||||||
|
} else {
|
||||||
|
lookupVirtualImpl(args.first(), target)
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetResult = callFromBridge(llvmTarget, args, Lifetime.ARGUMENT)
|
||||||
|
|
||||||
|
if (target is ConstructorDescriptor) {
|
||||||
|
ret(param(0))
|
||||||
|
} else when (returnType) {
|
||||||
|
VoidBridge -> ret(null)
|
||||||
|
is TypeBridge -> ret(kotlinToObjC(targetResult, returnType))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LLVMSetLinkage(result, LLVMLinkage.LLVMPrivateLinkage)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: cache bridges.
|
||||||
|
private fun ObjCExportCodeGenerator.generateKotlinToObjCBridge(
|
||||||
|
descriptor: FunctionDescriptor,
|
||||||
|
baseMethod: FunctionDescriptor
|
||||||
|
): ConstPointer {
|
||||||
|
val methodBridge = mapper.bridgeMethod(baseMethod)
|
||||||
|
|
||||||
|
val allBaseMethodParams = baseMethod.allParameters
|
||||||
|
val paramBridges = methodBridge.paramBridges
|
||||||
|
val returnBridge = methodBridge.returnBridge
|
||||||
|
|
||||||
|
val objcMsgSend = msgSender(objCFunctionType(methodBridge))
|
||||||
|
|
||||||
|
val functionType = codegen.getLlvmFunctionType(descriptor)
|
||||||
|
|
||||||
|
val result = generateFunction(codegen, functionType, "") {
|
||||||
|
val args = mutableListOf<LLVMValueRef>()
|
||||||
|
|
||||||
|
descriptor.allParameters.forEachIndexed { index, parameter ->
|
||||||
|
|
||||||
|
val kotlinValue = convertKotlin(
|
||||||
|
{ param(index) },
|
||||||
|
actualType = parameter.type,
|
||||||
|
expectedType = allBaseMethodParams[index].type,
|
||||||
|
resultLifetime = Lifetime.ARGUMENT
|
||||||
|
)
|
||||||
|
|
||||||
|
args += kotlinToObjC(kotlinValue, paramBridges[index])
|
||||||
|
|
||||||
|
// TODO: if `convertKotlin` boxes Kotlin value, then it gets converted by `kotlinToObjC` to `NSNumber`,
|
||||||
|
// and boxing directly to `NSNumber` would be much efficient.
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
args += genSelector(namer.getSelector(baseMethod))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val targetResult = callFromBridge(objcMsgSend, args)
|
||||||
|
|
||||||
|
assert(baseMethod !is ConstructorDescriptor)
|
||||||
|
|
||||||
|
when (returnBridge) {
|
||||||
|
VoidBridge -> {
|
||||||
|
if (LLVMGetReturnType(functionType) == voidType) {
|
||||||
|
ret(null)
|
||||||
|
} else {
|
||||||
|
ret(staticData.theUnitInstanceRef.llvm)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is TypeBridge -> {
|
||||||
|
|
||||||
|
val genConvertedTargetResult = { lifetime: Lifetime ->
|
||||||
|
objCToKotlin(targetResult, returnBridge, lifetime)
|
||||||
|
}
|
||||||
|
|
||||||
|
ret(convertKotlin(
|
||||||
|
genConvertedTargetResult,
|
||||||
|
actualType = baseMethod.returnType!!,
|
||||||
|
expectedType = descriptor.returnType!!,
|
||||||
|
resultLifetime = Lifetime.RETURN_VALUE
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LLVMSetLinkage(result, LLVMLinkage.LLVMPrivateLinkage)
|
||||||
|
|
||||||
|
return constPointer(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.createReverseAdapter(
|
||||||
|
descriptor: FunctionDescriptor,
|
||||||
|
baseMethod: FunctionDescriptor,
|
||||||
|
functionName: String,
|
||||||
|
vtableIndex: Int?
|
||||||
|
): ObjCExportCodeGenerator.KotlinToObjCMethodAdapter {
|
||||||
|
|
||||||
|
val nameSignature = functionName.localHash.value
|
||||||
|
val selector = namer.getSelector(baseMethod)
|
||||||
|
|
||||||
|
val kotlinToObjC = generateKotlinToObjCBridge(
|
||||||
|
descriptor,
|
||||||
|
baseMethod
|
||||||
|
).bitcast(int8TypePtr)
|
||||||
|
|
||||||
|
return KotlinToObjCMethodAdapter(selector, nameSignature, vtableIndex ?: -1, kotlinToObjC)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.createMethodVirtualAdapter(
|
||||||
|
baseMethod: FunctionDescriptor
|
||||||
|
): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter {
|
||||||
|
assert(mapper.isBaseMethod(baseMethod))
|
||||||
|
|
||||||
|
val selector = namer.getSelector(baseMethod)
|
||||||
|
|
||||||
|
val methodBridge = mapper.bridgeMethod(baseMethod)
|
||||||
|
val objCToKotlin = constPointer(generateObjCImp(baseMethod, methodBridge, isVirtual = true))
|
||||||
|
return ObjCToKotlinMethodAdapter(selector, getEncoding(methodBridge), objCToKotlin)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.createMethodAdapter(
|
||||||
|
implementation: FunctionDescriptor?,
|
||||||
|
baseMethod: FunctionDescriptor
|
||||||
|
): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter {
|
||||||
|
val selectorName = namer.getSelector(baseMethod)
|
||||||
|
val methodBridge = mapper.bridgeMethod(baseMethod)
|
||||||
|
val objCEncoding = getEncoding(methodBridge)
|
||||||
|
val objCToKotlin = constPointer(generateObjCImp(implementation, methodBridge))
|
||||||
|
|
||||||
|
return ObjCToKotlinMethodAdapter(selectorName, objCEncoding, objCToKotlin)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.createConstructorAdapter(
|
||||||
|
descriptor: ConstructorDescriptor
|
||||||
|
): ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter = createMethodAdapter(descriptor, descriptor)
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.vtableIndex(descriptor: FunctionDescriptor): Int? {
|
||||||
|
assert(descriptor.isOverridable)
|
||||||
|
val classDescriptor = descriptor.containingDeclaration as ClassDescriptor
|
||||||
|
return if (classDescriptor.isInterface) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
context.getVtableBuilder(classDescriptor).vtableIndex(descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.createTypeAdapterForPackage(
|
||||||
|
fqName: FqName,
|
||||||
|
declarations: List<CallableMemberDescriptor>
|
||||||
|
): ObjCExportCodeGenerator.ObjCTypeAdapter {
|
||||||
|
val name = namer.getPackageName(fqName)
|
||||||
|
|
||||||
|
val adapters = declarations.toMethods().map { createMethodAdapter(it, it) }
|
||||||
|
|
||||||
|
return ObjCTypeAdapter(
|
||||||
|
descriptor = null,
|
||||||
|
typeInfo = null,
|
||||||
|
vtable = null,
|
||||||
|
vtableSize = -1,
|
||||||
|
methodTable = emptyList(),
|
||||||
|
objCName = name,
|
||||||
|
directAdapters = adapters,
|
||||||
|
virtualAdapters = emptyList(),
|
||||||
|
reverseAdapters = emptyList()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportCodeGenerator.createTypeAdapter(
|
||||||
|
descriptor: ClassDescriptor
|
||||||
|
): ObjCExportCodeGenerator.ObjCTypeAdapter {
|
||||||
|
val adapters = mutableListOf<ObjCExportCodeGenerator.ObjCToKotlinMethodAdapter>()
|
||||||
|
|
||||||
|
if (descriptor != context.builtIns.any) {
|
||||||
|
descriptor.constructors.forEach {
|
||||||
|
if (mapper.shouldBeExposed(it)) adapters += createConstructorAdapter(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val categoryMethods = mapper.getCategoryMembersFor(descriptor).toMethods()
|
||||||
|
|
||||||
|
val exposedMethods = descriptor.contributedMethods.filter { mapper.shouldBeExposed(it) } + categoryMethods
|
||||||
|
|
||||||
|
exposedMethods.filter { it.kind.isReal }.forEach { method ->
|
||||||
|
mapper.getBaseMethods(method).mapTo(adapters) { base ->
|
||||||
|
val implementation = if (method.modality == Modality.ABSTRACT) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
OverriddenFunctionDescriptor(method, base).getImplementation(context)
|
||||||
|
}
|
||||||
|
createMethodAdapter(implementation, base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val reverseAdapters = mutableListOf<ObjCExportCodeGenerator.KotlinToObjCMethodAdapter>()
|
||||||
|
|
||||||
|
exposedMethods.forEach { method ->
|
||||||
|
val baseMethods = mapper.getBaseMethods(method)
|
||||||
|
val hasSelectorClash = baseMethods.map { namer.getSelector(it) }.distinct().size > 1
|
||||||
|
|
||||||
|
if (method.isOverridable && !hasSelectorClash) {
|
||||||
|
val baseMethod = baseMethods.first()
|
||||||
|
|
||||||
|
val presentVtableBridges = mutableSetOf<Int?>(null)
|
||||||
|
val presentMethodTableBridges = mutableSetOf<String>()
|
||||||
|
|
||||||
|
val allOverriddenDescriptors = method.allOverriddenDescriptors.map { it.original }
|
||||||
|
|
||||||
|
val (inherited, uninherited) = allOverriddenDescriptors.partition {
|
||||||
|
it != method && mapper.shouldBeExposed(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
inherited.forEach {
|
||||||
|
presentVtableBridges += vtableIndex(it)
|
||||||
|
presentMethodTableBridges += it.functionName
|
||||||
|
}
|
||||||
|
|
||||||
|
uninherited.forEach {
|
||||||
|
val vtableIndex = vtableIndex(it)
|
||||||
|
val functionName = it.functionName
|
||||||
|
|
||||||
|
if (vtableIndex !in presentVtableBridges || functionName !in presentMethodTableBridges) {
|
||||||
|
presentVtableBridges += vtableIndex
|
||||||
|
presentMethodTableBridges += functionName
|
||||||
|
reverseAdapters += createReverseAdapter(it, baseMethod, functionName, vtableIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Mark it as non-overridable:
|
||||||
|
baseMethods.forEach { base ->
|
||||||
|
reverseAdapters += KotlinToObjCMethodAdapter(
|
||||||
|
namer.getSelector(base),
|
||||||
|
-1,
|
||||||
|
-1,
|
||||||
|
NullPointer(int8Type)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: some fake-overrides can be skipped.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val virtualAdapters = descriptor.contributedMethods.
|
||||||
|
filter { mapper.isBaseMethod(it) && it.isOverridable }
|
||||||
|
.map { createMethodVirtualAdapter(it) }
|
||||||
|
|
||||||
|
val typeInfo = constPointer(codegen.typeInfoValue(descriptor))
|
||||||
|
val objCName = namer.getClassOrProtocolName(descriptor)
|
||||||
|
|
||||||
|
val vtableSize = if (descriptor.kind == ClassKind.INTERFACE) {
|
||||||
|
-1
|
||||||
|
} else {
|
||||||
|
context.getVtableBuilder(descriptor).vtableEntries.size
|
||||||
|
}
|
||||||
|
|
||||||
|
val vtable = if (!descriptor.isInterface && !descriptor.typeInfoHasVtableAttached) {
|
||||||
|
staticData.placeGlobal("", rttiGenerator.vtable(descriptor)).also {
|
||||||
|
it.setConstant(true)
|
||||||
|
}.pointer.getElementPtr(0)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
val methodTable = if (!descriptor.isInterface && descriptor.isAbstract()) {
|
||||||
|
rttiGenerator.methodTableRecords(descriptor)
|
||||||
|
} else {
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
return ObjCTypeAdapter(
|
||||||
|
descriptor,
|
||||||
|
typeInfo,
|
||||||
|
vtable,
|
||||||
|
vtableSize,
|
||||||
|
methodTable,
|
||||||
|
objCName,
|
||||||
|
adapters,
|
||||||
|
virtualAdapters,
|
||||||
|
reverseAdapters
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun List<CallableMemberDescriptor>.toMethods(): List<FunctionDescriptor> = this.flatMap {
|
||||||
|
when (it) {
|
||||||
|
is PropertyDescriptor -> listOfNotNull(it.getter, it.setter)
|
||||||
|
is FunctionDescriptor -> listOf(it)
|
||||||
|
else -> error(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun objCFunctionType(methodBridge: MethodBridge): LLVMTypeRef {
|
||||||
|
val paramTypes = mutableListOf<LLVMTypeRef>()
|
||||||
|
|
||||||
|
methodBridge.paramBridges.forEachIndexed { index, typeBridge ->
|
||||||
|
paramTypes += typeBridge.objCType
|
||||||
|
if (index == 0) paramTypes += int8TypePtr // Selector.
|
||||||
|
}
|
||||||
|
|
||||||
|
val returnType = methodBridge.returnBridge.objCType
|
||||||
|
|
||||||
|
return functionType(returnType, false, *(paramTypes.toTypedArray()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ObjCValueType.llvmType: LLVMTypeRef get() = when (this) {
|
||||||
|
ObjCValueType.BOOL -> int8Type
|
||||||
|
ObjCValueType.CHAR -> int8Type
|
||||||
|
ObjCValueType.UNSIGNED_SHORT -> int16Type
|
||||||
|
ObjCValueType.SHORT -> int16Type
|
||||||
|
ObjCValueType.INT -> int32Type
|
||||||
|
ObjCValueType.LONG_LONG -> kInt64
|
||||||
|
ObjCValueType.FLOAT -> LLVMFloatType()!!
|
||||||
|
ObjCValueType.DOUBLE -> LLVMDoubleType()!!
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ReturnableTypeBridge.objCType: LLVMTypeRef get() = when (this) {
|
||||||
|
VoidBridge -> voidType
|
||||||
|
is ReferenceBridge -> int8TypePtr
|
||||||
|
is ValueTypeBridge -> this.objCValueType.llvmType
|
||||||
|
is HashCodeBridge -> kInt64 // TODO: only for 64-bit platforms
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ObjCExportCodeGenerator.getEncoding(methodBridge: MethodBridge): String {
|
||||||
|
var paramOffset = 0
|
||||||
|
val pointerSize = runtime.pointerSize
|
||||||
|
|
||||||
|
val params = buildString {
|
||||||
|
fun appendParam(encoding: String, size: Int) {
|
||||||
|
append(encoding)
|
||||||
|
append(paramOffset)
|
||||||
|
paramOffset += size
|
||||||
|
}
|
||||||
|
|
||||||
|
methodBridge.paramBridges.forEachIndexed { index, typeBridge ->
|
||||||
|
appendParam(
|
||||||
|
typeBridge.objCEncoding,
|
||||||
|
LLVMStoreSizeOfType(runtime.targetData, typeBridge.objCType).toInt()
|
||||||
|
)
|
||||||
|
if (index == 0) appendParam(":", pointerSize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val returnTypeEncoding = methodBridge.returnBridge.objCEncoding
|
||||||
|
|
||||||
|
val paramSize = paramOffset
|
||||||
|
return "$returnTypeEncoding$paramSize$params"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ReturnableTypeBridge.objCEncoding: String get() = when (this) {
|
||||||
|
VoidBridge -> "v"
|
||||||
|
ReferenceBridge -> "@"
|
||||||
|
is ValueTypeBridge -> this.objCValueType.encoding
|
||||||
|
HashCodeBridge -> "L" // NSUInteger = unsigned long; // TODO: `unsigned int` on watchOS
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun Context.is64Bit(): Boolean = this.config.targetManager.target.architecture.bitness == 64
|
||||||
+11
-42
@@ -18,6 +18,7 @@ package org.jetbrains.kotlin.backend.konan.lower
|
|||||||
|
|
||||||
import org.jetbrains.kotlin.backend.common.AbstractValueUsageTransformer
|
import org.jetbrains.kotlin.backend.common.AbstractValueUsageTransformer
|
||||||
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
import org.jetbrains.kotlin.backend.common.FileLoweringPass
|
||||||
|
import org.jetbrains.kotlin.backend.common.descriptors.explicitParameters
|
||||||
import org.jetbrains.kotlin.backend.common.descriptors.isSuspend
|
import org.jetbrains.kotlin.backend.common.descriptors.isSuspend
|
||||||
import org.jetbrains.kotlin.backend.konan.*
|
import org.jetbrains.kotlin.backend.konan.*
|
||||||
import org.jetbrains.kotlin.backend.konan.descriptors.target
|
import org.jetbrains.kotlin.backend.konan.descriptors.target
|
||||||
@@ -28,10 +29,7 @@ import org.jetbrains.kotlin.ir.declarations.IrFunction
|
|||||||
import org.jetbrains.kotlin.ir.expressions.*
|
import org.jetbrains.kotlin.ir.expressions.*
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrCallImpl
|
||||||
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
|
import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl
|
||||||
import org.jetbrains.kotlin.ir.util.getPropertyGetter
|
import org.jetbrains.kotlin.ir.util.*
|
||||||
import org.jetbrains.kotlin.ir.util.isNullConst
|
|
||||||
import org.jetbrains.kotlin.ir.util.render
|
|
||||||
import org.jetbrains.kotlin.ir.util.type
|
|
||||||
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid
|
||||||
import org.jetbrains.kotlin.types.KotlinType
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
import org.jetbrains.kotlin.types.TypeUtils
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
@@ -194,23 +192,16 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun IrExpression.adaptIfNecessary(actualType: KotlinType, expectedType: KotlinType): IrExpression {
|
private fun IrExpression.adaptIfNecessary(actualType: KotlinType, expectedType: KotlinType): IrExpression {
|
||||||
val actualValueType = actualType.correspondingValueType
|
val conversion = symbols.getTypeConversion(actualType, expectedType)
|
||||||
val expectedValueType = expectedType.correspondingValueType
|
return if (conversion == null) {
|
||||||
|
this
|
||||||
|
} else {
|
||||||
|
val parameter = conversion.descriptor.explicitParameters.single()
|
||||||
|
val argument = this.uncheckedCast(parameter.type)
|
||||||
|
|
||||||
return when {
|
IrCallImpl(startOffset, endOffset, conversion).apply {
|
||||||
actualValueType == expectedValueType -> this
|
addArguments(listOf(parameter to argument))
|
||||||
|
}.uncheckedCast(this.type) // Try not to bring new type incompatibilities.
|
||||||
actualValueType == null && expectedValueType != null -> {
|
|
||||||
// This may happen in the following cases:
|
|
||||||
// 1. `actualType` is `Nothing`;
|
|
||||||
// 2. `actualType` is incompatible.
|
|
||||||
|
|
||||||
this.unbox(expectedValueType)
|
|
||||||
}
|
|
||||||
|
|
||||||
actualValueType != null && expectedValueType == null -> this.box(actualValueType)
|
|
||||||
|
|
||||||
else -> throw IllegalArgumentException("actual type is $actualType, expected $expectedType")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,26 +220,4 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr
|
|||||||
private fun getBoxType(valueType: ValueType) =
|
private fun getBoxType(valueType: ValueType) =
|
||||||
context.getInternalClass("${valueType.shortName}Box").defaultType
|
context.getInternalClass("${valueType.shortName}Box").defaultType
|
||||||
|
|
||||||
private fun IrExpression.box(valueType: ValueType): IrExpression {
|
|
||||||
val boxFunction = symbols.boxFunctions[valueType]!!
|
|
||||||
|
|
||||||
return IrCallImpl(startOffset, endOffset, boxFunction).apply {
|
|
||||||
putValueArgument(0, this@box)
|
|
||||||
}.uncheckedCast(this.type) // Try not to bring new type incompatibilities.
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun IrExpression.unbox(valueType: ValueType): IrExpression {
|
|
||||||
symbols.unboxFunctions[valueType]?.let {
|
|
||||||
return IrCallImpl(startOffset, endOffset, it).apply {
|
|
||||||
putValueArgument(0, this@unbox.uncheckedCast(it.owner.valueParameters[0].type))
|
|
||||||
}.uncheckedCast(this.type)
|
|
||||||
}
|
|
||||||
|
|
||||||
val boxGetter = symbols.boxClasses[valueType]!!.getPropertyGetter("value")!!
|
|
||||||
|
|
||||||
return IrCallImpl(startOffset, endOffset, boxGetter).apply {
|
|
||||||
dispatchReceiver = this@unbox.uncheckedCast(boxGetter.descriptor.dispatchReceiverParameter!!.type)
|
|
||||||
}.uncheckedCast(this.type) // Try not to bring new type incompatibilities.
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
+61
@@ -0,0 +1,61 @@
|
|||||||
|
/*
|
||||||
|
* 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.objcexport
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
|
import org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator
|
||||||
|
import org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGenerator
|
||||||
|
import org.jetbrains.kotlin.konan.file.File
|
||||||
|
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
|
||||||
|
|
||||||
|
internal class ObjCExport(val context: Context) {
|
||||||
|
internal fun produceObjCFramework() {
|
||||||
|
if (context.config.produce != CompilerOutputKind.FRAMEWORK) return
|
||||||
|
|
||||||
|
val headerGenerator = ObjCExportHeaderGenerator(context)
|
||||||
|
headerGenerator.translateModule()
|
||||||
|
|
||||||
|
val namer = headerGenerator.namer
|
||||||
|
val mapper = headerGenerator.mapper
|
||||||
|
|
||||||
|
val framework = File(context.config.outputFile)
|
||||||
|
val headers = framework.child("Headers")
|
||||||
|
|
||||||
|
val frameworkName = framework.name.removeSuffix(".framework")
|
||||||
|
val headerName = frameworkName + ".h"
|
||||||
|
val header = headers.child(headerName)
|
||||||
|
headers.mkdirs()
|
||||||
|
header.writeLines(headerGenerator.build())
|
||||||
|
|
||||||
|
val modules = framework.child("Modules")
|
||||||
|
modules.mkdirs()
|
||||||
|
|
||||||
|
val moduleMap = """
|
||||||
|
|framework module $frameworkName {
|
||||||
|
| umbrella header "$headerName"
|
||||||
|
|
|
||||||
|
| export *
|
||||||
|
| module * { export * }
|
||||||
|
|}
|
||||||
|
""".trimMargin()
|
||||||
|
|
||||||
|
modules.child("module.modulemap").writeBytes(moduleMap.toByteArray())
|
||||||
|
|
||||||
|
val objCCodeGenerator = ObjCExportCodeGenerator(CodeGenerator(context), namer, mapper)
|
||||||
|
objCCodeGenerator.emitRtti(headerGenerator.generatedClasses, headerGenerator.topLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
+587
@@ -0,0 +1,587 @@
|
|||||||
|
/*
|
||||||
|
* 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.objcexport
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||||
|
import org.jetbrains.kotlin.builtins.getReceiverTypeFromFunctionType
|
||||||
|
import org.jetbrains.kotlin.builtins.getReturnTypeFromFunctionType
|
||||||
|
import org.jetbrains.kotlin.builtins.getValueParameterTypesFromFunctionType
|
||||||
|
import org.jetbrains.kotlin.builtins.isFunctionType
|
||||||
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperClassNotAny
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||||
|
import org.jetbrains.kotlin.resolve.scopes.MemberScope
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
|
import org.jetbrains.kotlin.types.typeUtil.supertypes
|
||||||
|
import org.jetbrains.kotlin.utils.addIfNotNull
|
||||||
|
|
||||||
|
internal class ObjCExportHeaderGenerator(val context: Context) {
|
||||||
|
val mapper: ObjCExportMapper = object : ObjCExportMapper {
|
||||||
|
override fun isRepresentedAsObjCInterface(descriptor: ClassDescriptor): Boolean {
|
||||||
|
val objCType = mapReferenceType(descriptor.defaultType)
|
||||||
|
return objCType is ObjCClassType && objCType.className == translateClassName(descriptor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getCategoryMembersFor(descriptor: ClassDescriptor) =
|
||||||
|
extensions[descriptor].orEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
val namer = ObjCExportNamer(context, mapper)
|
||||||
|
|
||||||
|
val generatedClasses = mutableSetOf<ClassDescriptor>()
|
||||||
|
val topLevel = mutableMapOf<FqName, MutableList<CallableMemberDescriptor>>()
|
||||||
|
|
||||||
|
private val kotlinAnyName = namer.kotlinAnyName
|
||||||
|
|
||||||
|
private val stubs = mutableListOf<Stub>()
|
||||||
|
private val classToName = mutableMapOf<ClassDescriptor, String>()
|
||||||
|
private val interfaceToName = mutableMapOf<ClassDescriptor, String>()
|
||||||
|
private val extensions = mutableMapOf<ClassDescriptor, MutableList<CallableMemberDescriptor>>()
|
||||||
|
val extraClassesToTranslate = mutableSetOf<ClassDescriptor>()
|
||||||
|
|
||||||
|
fun translateModule() {
|
||||||
|
// TODO: make the translation order stable
|
||||||
|
// to stabilize name mangling.
|
||||||
|
|
||||||
|
val packageFragments = context.moduleDescriptor.getPackageFragments()
|
||||||
|
|
||||||
|
packageFragments.forEach { packageFragment ->
|
||||||
|
packageFragment.getMemberScope().getContributedDescriptors()
|
||||||
|
.filterIsInstance<CallableMemberDescriptor>()
|
||||||
|
.filter { mapper.shouldBeExposed(it) }
|
||||||
|
.forEach {
|
||||||
|
val classDescriptor = mapper.getClassIfCategory(it)
|
||||||
|
if (classDescriptor != null) {
|
||||||
|
extensions.getOrPut(classDescriptor, { mutableListOf() }) += it
|
||||||
|
} else {
|
||||||
|
topLevel.getOrPut(packageFragment.fqName, { mutableListOf() }) += it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MemberScope.translateClasses(): Unit = this.getContributedDescriptors()
|
||||||
|
.filterIsInstance<ClassDescriptor>()
|
||||||
|
.filter { mapper.shouldBeExposed(it) }
|
||||||
|
.forEach {
|
||||||
|
if (it.isInterface) {
|
||||||
|
translateInterface(it)
|
||||||
|
} else {
|
||||||
|
translateClass(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
it.unsubstitutedMemberScope.translateClasses()
|
||||||
|
}
|
||||||
|
|
||||||
|
packageFragments.forEach { packageFragment ->
|
||||||
|
packageFragment.getMemberScope().translateClasses()
|
||||||
|
}
|
||||||
|
|
||||||
|
extensions.forEach { classDescriptor, declarations ->
|
||||||
|
translateExtensions(classDescriptor, declarations)
|
||||||
|
}
|
||||||
|
|
||||||
|
topLevel.forEach { packageFqName, declarations ->
|
||||||
|
translateTopLevel(packageFqName, declarations)
|
||||||
|
}
|
||||||
|
|
||||||
|
while (extraClassesToTranslate.isNotEmpty()) {
|
||||||
|
val descriptor = extraClassesToTranslate.first()
|
||||||
|
extraClassesToTranslate -= descriptor
|
||||||
|
if (descriptor.isInterface) {
|
||||||
|
translateInterface(descriptor)
|
||||||
|
} else {
|
||||||
|
translateClass(descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun translateClassName(descriptor: ClassDescriptor): String {
|
||||||
|
val descriptorToName = if (descriptor.isInterface) interfaceToName else classToName
|
||||||
|
|
||||||
|
return descriptorToName.getOrPut(descriptor) {
|
||||||
|
namer.getClassOrProtocolName(descriptor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateInterface(descriptor: ClassDescriptor) {
|
||||||
|
if (!generatedClasses.add(descriptor)) return
|
||||||
|
|
||||||
|
val name = translateClassName(descriptor)
|
||||||
|
|
||||||
|
stubs.addBuiltBy {
|
||||||
|
+"@protocol $name${descriptor.superProtocolsClause}"
|
||||||
|
+"@required"
|
||||||
|
|
||||||
|
translateClassOrInterfaceMembers(descriptor)
|
||||||
|
|
||||||
|
+"@end;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ClassDescriptor.superProtocolsClause: String get() {
|
||||||
|
val interfaces = this.getSuperInterfaces()
|
||||||
|
return if (interfaces.isEmpty()) {
|
||||||
|
""
|
||||||
|
} else buildString {
|
||||||
|
append(" <")
|
||||||
|
interfaces.forEach {
|
||||||
|
translateInterface(it)
|
||||||
|
append(translateClassName(it))
|
||||||
|
}
|
||||||
|
append(">")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateExtensions(classDescriptor: ClassDescriptor, declarations: List<CallableMemberDescriptor>) {
|
||||||
|
translateClass(classDescriptor)
|
||||||
|
|
||||||
|
stubs.addBuiltBy {
|
||||||
|
+"@interface ${translateClassName(classDescriptor)} (Extensions)"
|
||||||
|
|
||||||
|
translateMembers(declarations)
|
||||||
|
|
||||||
|
+"@end;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateTopLevel(packageFqName: FqName, declarations: List<CallableMemberDescriptor>) {
|
||||||
|
val name = namer.getPackageName(packageFqName)
|
||||||
|
stubs.addBuiltBy {
|
||||||
|
+"__attribute__((objc_subclassing_restricted))"
|
||||||
|
+"@interface $name : KotlinBase" // TODO: stop inheriting KotlinBase.
|
||||||
|
|
||||||
|
translateMembers(declarations)
|
||||||
|
|
||||||
|
+"@end;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun translateClass(descriptor: ClassDescriptor) {
|
||||||
|
if (!generatedClasses.add(descriptor)) return
|
||||||
|
|
||||||
|
val name = translateClassName(descriptor)
|
||||||
|
val superClass = descriptor.getSuperClassNotAny()
|
||||||
|
|
||||||
|
val superName = if (superClass == null) {
|
||||||
|
kotlinAnyName
|
||||||
|
} else {
|
||||||
|
translateClass(superClass)
|
||||||
|
translateClassName(superClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
stubs.addBuiltBy {
|
||||||
|
if (descriptor.isFinalOrEnum) {
|
||||||
|
+"__attribute__((objc_subclassing_restricted))"
|
||||||
|
}
|
||||||
|
|
||||||
|
+"@interface $name : $superName${descriptor.superProtocolsClause}"
|
||||||
|
|
||||||
|
val presentConstructors = mutableSetOf<String>()
|
||||||
|
|
||||||
|
descriptor.constructors.filter { mapper.shouldBeExposed(it) }.forEach {
|
||||||
|
presentConstructors += getSelector(it)
|
||||||
|
|
||||||
|
+"${getSignature(it, it)};"
|
||||||
|
+""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide "unimplemented" super constructors:
|
||||||
|
superClass?.constructors?.filter { mapper.shouldBeExposed(it) }?.forEach {
|
||||||
|
if (getSelector(it) !in presentConstructors) {
|
||||||
|
+"${getSignature(it, it)} __attribute__((unavailable));"
|
||||||
|
+""
|
||||||
|
// TODO: consider adding exception-throwing impls for these.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
translateClassOrInterfaceMembers(descriptor)
|
||||||
|
|
||||||
|
+"@end;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StubBuilder.translateClassOrInterfaceMembers(descriptor: ClassDescriptor) {
|
||||||
|
val members = descriptor.unsubstitutedMemberScope.getContributedDescriptors()
|
||||||
|
.filterIsInstance<CallableMemberDescriptor>()
|
||||||
|
.filter { mapper.shouldBeExposed(it) }
|
||||||
|
|
||||||
|
translateMembers(members)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StubBuilder.translateMembers(members: List<CallableMemberDescriptor>) {
|
||||||
|
// TODO: add some marks about modality.
|
||||||
|
|
||||||
|
val methods = mutableListOf<FunctionDescriptor>()
|
||||||
|
val properties = mutableListOf<PropertyDescriptor>()
|
||||||
|
|
||||||
|
members.forEach {
|
||||||
|
when (it) {
|
||||||
|
is FunctionDescriptor -> methods += it
|
||||||
|
is PropertyDescriptor -> if (mapper.isObjCProperty(it)) {
|
||||||
|
properties += it
|
||||||
|
} else {
|
||||||
|
methods.addIfNotNull(it.getter)
|
||||||
|
methods.addIfNotNull(it.setter)
|
||||||
|
}
|
||||||
|
else -> error(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
methods.forEach { method ->
|
||||||
|
val superSignatures = method.overriddenDescriptors
|
||||||
|
.filter { mapper.shouldBeExposed(it) }
|
||||||
|
.flatMap { getSignatures(it.original) }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
(getSignatures(method) - superSignatures).forEach {
|
||||||
|
+"$it;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
properties.forEach { property ->
|
||||||
|
val superSignatures = property.overriddenDescriptors
|
||||||
|
.filter { mapper.shouldBeExposed(it) }
|
||||||
|
.flatMap { getSignatures(it.original) }
|
||||||
|
.toSet()
|
||||||
|
|
||||||
|
getSignatures(property).filter { it !in superSignatures }.forEach {
|
||||||
|
+"$it;"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val methodToSignatures = mutableMapOf<FunctionDescriptor, Set<String>>()
|
||||||
|
|
||||||
|
private fun getSignatures(method: FunctionDescriptor) = methodToSignatures.getOrPut(method) {
|
||||||
|
mapper.getBaseMethods(method).distinctBy { namer.getSelector(it) }.map { base ->
|
||||||
|
getSignature(method, base)
|
||||||
|
}.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
private val propertyToSignatures = mutableMapOf<PropertyDescriptor, Set<String>>()
|
||||||
|
|
||||||
|
private fun getSignatures(property: PropertyDescriptor) = propertyToSignatures.getOrPut(property) {
|
||||||
|
mapper.getBaseProperties(property).distinctBy { namer.getName(it) }.map { base ->
|
||||||
|
getSignature(property, base)
|
||||||
|
}.toSet()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consider checking that signatures for bases with same selector/name are equal.
|
||||||
|
|
||||||
|
fun getSelector(method: FunctionDescriptor): String {
|
||||||
|
return namer.getSelector(method)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSignature(property: PropertyDescriptor, baseProperty: PropertyDescriptor) = buildString {
|
||||||
|
assert(mapper.isBaseProperty(baseProperty))
|
||||||
|
assert(mapper.isObjCProperty(baseProperty))
|
||||||
|
|
||||||
|
val type = mapType(property.type, mapper.bridgeMethod(baseProperty.getter!!).returnBridge)
|
||||||
|
val name = namer.getName(baseProperty)
|
||||||
|
|
||||||
|
append("@property ")
|
||||||
|
|
||||||
|
val attributes = mutableListOf<String>()
|
||||||
|
val getterSelector = getSelector(baseProperty.getter!!)
|
||||||
|
if (getterSelector != name) {
|
||||||
|
attributes += "getter=$getterSelector"
|
||||||
|
}
|
||||||
|
|
||||||
|
val propertySetter = property.setter
|
||||||
|
if (propertySetter != null && mapper.shouldBeExposed(propertySetter)) {
|
||||||
|
val baseSetter = mapper.getBaseMethods(propertySetter).single()
|
||||||
|
val setterSelector = getSelector(baseSetter)
|
||||||
|
if (setterSelector != "set" + name.capitalize() + ":") {
|
||||||
|
attributes += "setter=$setterSelector"
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attributes += "readonly"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attributes.isNotEmpty()) {
|
||||||
|
attributes.joinTo(this, prefix = "(", postfix = ") ")
|
||||||
|
}
|
||||||
|
|
||||||
|
append(type.render(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getSignature(method: FunctionDescriptor, baseMethod: FunctionDescriptor) = buildString {
|
||||||
|
assert(mapper.isBaseMethod(baseMethod))
|
||||||
|
val methodBridge = mapper.bridgeMethod(baseMethod)
|
||||||
|
|
||||||
|
val selectorParts = getSelector(baseMethod).split(':')
|
||||||
|
|
||||||
|
if (methodBridge.isKotlinTopLevel) {
|
||||||
|
append("+")
|
||||||
|
} else {
|
||||||
|
append("-")
|
||||||
|
}
|
||||||
|
|
||||||
|
append("(")
|
||||||
|
val returnType = if (method is ConstructorDescriptor) {
|
||||||
|
"instancetype"
|
||||||
|
} else {
|
||||||
|
mapType(method.returnType!!, methodBridge.returnBridge).render()
|
||||||
|
}
|
||||||
|
append(returnType)
|
||||||
|
append(")")
|
||||||
|
|
||||||
|
val valueParameters = mapper.objCValueParameters(method)
|
||||||
|
val valueParameterNames = mutableListOf<String>()
|
||||||
|
valueParameters.forEach { p ->
|
||||||
|
// TODO: mangle only the extension receiver parameter.
|
||||||
|
var candidate = when {
|
||||||
|
p is ReceiverParameterDescriptor -> "receiver"
|
||||||
|
method is PropertySetterDescriptor -> "value"
|
||||||
|
else -> p.name.asString()
|
||||||
|
}
|
||||||
|
while (candidate in valueParameterNames) {
|
||||||
|
candidate += "_"
|
||||||
|
}
|
||||||
|
valueParameterNames += candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
append(selectorParts[0])
|
||||||
|
|
||||||
|
valueParameters.forEachIndexed { index, p ->
|
||||||
|
val name = valueParameterNames[index]
|
||||||
|
if (index != 0) {
|
||||||
|
append(' ')
|
||||||
|
append(selectorParts[index])
|
||||||
|
}
|
||||||
|
|
||||||
|
append(":")
|
||||||
|
append("(")
|
||||||
|
append(mapType(p.type, methodBridge.paramBridges[index + 1]).render())
|
||||||
|
append(")")
|
||||||
|
append(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
val swiftName = namer.getSwiftName(baseMethod)
|
||||||
|
|
||||||
|
append(" NS_SWIFT_NAME($swiftName)")
|
||||||
|
|
||||||
|
if (method is ConstructorDescriptor) {
|
||||||
|
append(" NS_DESIGNATED_INITIALIZER")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build(): List<String> = mutableListOf<String>().apply {
|
||||||
|
add("#import <stdint.h>")
|
||||||
|
add("#import <objc/NSObject.h>")
|
||||||
|
add("#import <CoreFoundation/CFBase.h>")
|
||||||
|
add("#import <Foundation/NSObjCRuntime.h>")
|
||||||
|
add("")
|
||||||
|
|
||||||
|
if (classToName.isNotEmpty()) {
|
||||||
|
add("@class ${classToName.values.joinToString()};")
|
||||||
|
add("")
|
||||||
|
}
|
||||||
|
|
||||||
|
if (interfaceToName.isNotEmpty()) {
|
||||||
|
add("@protocol ${interfaceToName.values.joinToString()};")
|
||||||
|
add("")
|
||||||
|
}
|
||||||
|
|
||||||
|
add("NS_ASSUME_NONNULL_BEGIN")
|
||||||
|
add("")
|
||||||
|
|
||||||
|
add("@interface $kotlinAnyName : NSObject")
|
||||||
|
add("-(instancetype) init __attribute__((unavailable));")
|
||||||
|
add("+(void)initialize;")
|
||||||
|
add("@end;")
|
||||||
|
add("")
|
||||||
|
|
||||||
|
stubs.forEach {
|
||||||
|
addAll(it.lines)
|
||||||
|
add("")
|
||||||
|
}
|
||||||
|
|
||||||
|
add("NS_ASSUME_NONNULL_END")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ObjCType {
|
||||||
|
final override fun toString(): String = this.render()
|
||||||
|
|
||||||
|
open fun render(varName: String): String = "${this.render()} $varName"
|
||||||
|
abstract fun render(): String
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class ObjCReferenceType(kotlinType: KotlinType) : ObjCType() {
|
||||||
|
val attributes = if (TypeUtils.isNullableType(kotlinType)) " _Nullable" else ""
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ObjCClassType(kotlinType: KotlinType, val className: String) : ObjCReferenceType(kotlinType) {
|
||||||
|
override fun render() = "$className*$attributes"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ObjCProtocolType(kotlinType: KotlinType, val protocolName: String) : ObjCReferenceType(kotlinType) {
|
||||||
|
override fun render() = "id<$protocolName>$attributes" // TODO: check
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ObjCIdType(kotlinType: KotlinType) : ObjCReferenceType(kotlinType) {
|
||||||
|
override fun render() = "id$attributes"
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ObjCBlockPointerType(
|
||||||
|
kotlinType: KotlinType, val returnType: ObjCReferenceType, val parameterTypes: List<ObjCReferenceType>
|
||||||
|
) : ObjCReferenceType(kotlinType) {
|
||||||
|
|
||||||
|
override fun render() = render("")
|
||||||
|
|
||||||
|
override fun render(varName: String) = buildString {
|
||||||
|
append(returnType.render())
|
||||||
|
append("(^")
|
||||||
|
append(attributes)
|
||||||
|
append(varName)
|
||||||
|
append(")(")
|
||||||
|
if (parameterTypes.isEmpty()) append("void")
|
||||||
|
parameterTypes.joinTo(this) { it.render() }
|
||||||
|
append(')')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ObjCPrimitiveType(val cName: String) : ObjCType() {
|
||||||
|
override fun render() = cName
|
||||||
|
}
|
||||||
|
|
||||||
|
private object ObjCVoidType : ObjCType() {
|
||||||
|
override fun render() = "void"
|
||||||
|
override fun render(varName: String) = error("variables can't have `void` type")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportHeaderGenerator.mapReferenceType(kotlinType: KotlinType): ObjCReferenceType {
|
||||||
|
// TODO: translate `where T : BaseClass, T : SomeInterface` to `BaseClass* <SomeInterface>`
|
||||||
|
val classDescriptor = kotlinType.getErasedTypeClass()
|
||||||
|
|
||||||
|
if (classDescriptor.isSubclassOf(context.builtIns.list)) {
|
||||||
|
return ObjCClassType(kotlinType, "NSArray")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// TODO: Kotlin code doesn't have any checkcasts on unboxing,
|
||||||
|
// so it is possible that it expects boxed number of other type and unboxes it incorrectly.
|
||||||
|
|
||||||
|
if (classDescriptor.isSubclassOf(context.builtIns.number) ||
|
||||||
|
classDescriptor == context.builtIns.boolean ||
|
||||||
|
classDescriptor == context.builtIns.char) return ObjCClassType(kotlinType, "NSNumber")
|
||||||
|
|
||||||
|
when (classDescriptor) {
|
||||||
|
context.builtIns.any -> return ObjCIdType(kotlinType)
|
||||||
|
context.builtIns.string -> return ObjCClassType(kotlinType, "NSString")
|
||||||
|
}
|
||||||
|
|
||||||
|
val functionType = if (kotlinType.isFunctionType) {
|
||||||
|
kotlinType
|
||||||
|
} else {
|
||||||
|
kotlinType.supertypes().firstOrNull { it.isFunctionType }
|
||||||
|
// TODO: may be incorrect if type has more then one function supertype.
|
||||||
|
}
|
||||||
|
if (functionType != null) {
|
||||||
|
val returnType = functionType.getReturnTypeFromFunctionType()
|
||||||
|
val parameterTypes = listOfNotNull(functionType.getReceiverTypeFromFunctionType()) +
|
||||||
|
functionType.getValueParameterTypesFromFunctionType().map { it.type }
|
||||||
|
|
||||||
|
return ObjCBlockPointerType(
|
||||||
|
kotlinType,
|
||||||
|
mapReferenceType(returnType),
|
||||||
|
parameterTypes.map { mapReferenceType(it) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (classDescriptor !in generatedClasses) {
|
||||||
|
extraClassesToTranslate += classDescriptor
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (classDescriptor.isInterface) {
|
||||||
|
ObjCProtocolType(kotlinType, translateClassName(classDescriptor))
|
||||||
|
} else {
|
||||||
|
ObjCClassType(kotlinType, translateClassName(classDescriptor))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportHeaderGenerator.mapType(
|
||||||
|
kotlinType: KotlinType,
|
||||||
|
typeBridge: ReturnableTypeBridge
|
||||||
|
): ObjCType = when (typeBridge) {
|
||||||
|
VoidBridge -> ObjCVoidType
|
||||||
|
ReferenceBridge -> mapReferenceType(kotlinType)
|
||||||
|
is ValueTypeBridge -> {
|
||||||
|
val cName = when (typeBridge.objCValueType) {
|
||||||
|
ObjCValueType.BOOL -> "BOOL"
|
||||||
|
ObjCValueType.CHAR -> "int8_t"
|
||||||
|
ObjCValueType.UNSIGNED_SHORT -> "unichar"
|
||||||
|
ObjCValueType.SHORT -> "int16_t"
|
||||||
|
ObjCValueType.INT -> "int32_t"
|
||||||
|
ObjCValueType.LONG_LONG -> "int64_t"
|
||||||
|
ObjCValueType.FLOAT -> "float"
|
||||||
|
ObjCValueType.DOUBLE -> "double"
|
||||||
|
}
|
||||||
|
// TODO: consider other namings.
|
||||||
|
ObjCPrimitiveType(cName)
|
||||||
|
}
|
||||||
|
HashCodeBridge -> ObjCPrimitiveType("NSUInteger")
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class Stub(val lines: List<String>)
|
||||||
|
|
||||||
|
private class StubBuilder {
|
||||||
|
private val lines = mutableListOf<String>()
|
||||||
|
|
||||||
|
operator fun String.unaryPlus() {
|
||||||
|
lines.add(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
operator fun Stub.unaryPlus() {
|
||||||
|
this@StubBuilder.lines.addAll(this.lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun build() = Stub(lines)
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun buildStub(block: StubBuilder.() -> Unit) = StubBuilder().let {
|
||||||
|
it.block()
|
||||||
|
it.build()
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun MutableCollection<Stub>.addBuiltBy(block: StubBuilder.() -> Unit) {
|
||||||
|
this.add(buildStub(block))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getPackagesFqNames(module: ModuleDescriptor): Set<FqName> {
|
||||||
|
val result = mutableSetOf<FqName>()
|
||||||
|
|
||||||
|
fun getSubPackages(fqName: FqName) {
|
||||||
|
result.add(fqName)
|
||||||
|
module.getSubPackagesOf(fqName) { true }.forEach { getSubPackages(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
getSubPackages(FqName.ROOT)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ModuleDescriptor.getPackageFragments(): List<PackageFragmentDescriptor> =
|
||||||
|
getPackagesFqNames(this).flatMap {
|
||||||
|
getPackage(it).fragments.filter { it.module == this }
|
||||||
|
}
|
||||||
+190
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* 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.objcexport
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.common.descriptors.allParameters
|
||||||
|
import org.jetbrains.kotlin.backend.common.descriptors.isSuspend
|
||||||
|
import org.jetbrains.kotlin.backend.konan.ValueType
|
||||||
|
import org.jetbrains.kotlin.backend.konan.correspondingValueType
|
||||||
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isEffectivelyPublicApi
|
||||||
|
import org.jetbrains.kotlin.types.KotlinType
|
||||||
|
import org.jetbrains.kotlin.types.TypeUtils
|
||||||
|
import org.jetbrains.kotlin.types.typeUtil.isUnit
|
||||||
|
|
||||||
|
internal interface ObjCExportMapper {
|
||||||
|
fun isRepresentedAsObjCInterface(descriptor: ClassDescriptor): Boolean
|
||||||
|
fun getCategoryMembersFor(descriptor: ClassDescriptor): List<CallableMemberDescriptor>
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.getClassIfCategory(descriptor: CallableMemberDescriptor): ClassDescriptor? {
|
||||||
|
if (descriptor.dispatchReceiverParameter != null) return null
|
||||||
|
|
||||||
|
val extensionReceiverType = descriptor.extensionReceiverParameter?.type ?: return null
|
||||||
|
|
||||||
|
val erasedClass = extensionReceiverType.getErasedTypeClass()
|
||||||
|
return if (this.isRepresentedAsObjCInterface(erasedClass)) {
|
||||||
|
erasedClass
|
||||||
|
} else {
|
||||||
|
// E.g. receiver is protocol, or some type with custom mapping.
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.shouldBeExposed(descriptor: CallableMemberDescriptor): Boolean =
|
||||||
|
descriptor.isEffectivelyPublicApi && !descriptor.isSuspend
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.shouldBeExposed(descriptor: ClassDescriptor): Boolean =
|
||||||
|
descriptor.isEffectivelyPublicApi
|
||||||
|
|
||||||
|
private fun ObjCExportMapper.isBase(descriptor: CallableMemberDescriptor): Boolean =
|
||||||
|
descriptor.overriddenDescriptors.all { !shouldBeExposed(it) }
|
||||||
|
// e.g. it is not `override`, or overrides only unexposed methods.
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.isBaseMethod(descriptor: FunctionDescriptor) =
|
||||||
|
this.isBase(descriptor)
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.getBaseMethods(descriptor: FunctionDescriptor): List<FunctionDescriptor> =
|
||||||
|
if (isBaseMethod(descriptor)) {
|
||||||
|
listOf(descriptor)
|
||||||
|
} else {
|
||||||
|
descriptor.overriddenDescriptors.filter { shouldBeExposed(it) }.flatMap { getBaseMethods(it.original)}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.isBaseProperty(descriptor: PropertyDescriptor) =
|
||||||
|
isBase(descriptor)
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.getBaseProperties(descriptor: PropertyDescriptor): List<PropertyDescriptor> =
|
||||||
|
if (isBaseProperty(descriptor)) {
|
||||||
|
listOf(descriptor)
|
||||||
|
} else {
|
||||||
|
descriptor.overriddenDescriptors.flatMap { getBaseProperties(it.original) }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal tailrec fun KotlinType.getErasedTypeClass(): ClassDescriptor =
|
||||||
|
TypeUtils.getClassDescriptor(this) ?: this.constructor.supertypes.first().getErasedTypeClass()
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.isTopLevel(descriptor: CallableMemberDescriptor): Boolean =
|
||||||
|
descriptor.containingDeclaration !is ClassDescriptor && this.getClassIfCategory(descriptor) == null
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.objCValueParameters(method: FunctionDescriptor): List<ParameterDescriptor> =
|
||||||
|
when {
|
||||||
|
method is ConstructorDescriptor ->
|
||||||
|
listOfNotNull(method.dispatchReceiverParameter) + method.valueParameters
|
||||||
|
|
||||||
|
getClassIfCategory(method) == null ->
|
||||||
|
listOfNotNull(method.extensionReceiverParameter) + method.valueParameters
|
||||||
|
|
||||||
|
else -> method.valueParameters
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.isObjCProperty(property: PropertyDescriptor): Boolean =
|
||||||
|
this.objCValueParameters(property.getter!!).isEmpty() && // Which is false e.g. if it has two receivers.
|
||||||
|
!this.isTopLevel(property) // Because Objective-C has no class (e.g. static) properties.
|
||||||
|
|
||||||
|
// TODO: generalize type bridges to support such things as selectors, ignored class method receivers etc.
|
||||||
|
|
||||||
|
internal sealed class ReturnableTypeBridge
|
||||||
|
internal object VoidBridge : ReturnableTypeBridge()
|
||||||
|
internal sealed class TypeBridge : ReturnableTypeBridge()
|
||||||
|
internal object ReferenceBridge : TypeBridge()
|
||||||
|
internal data class ValueTypeBridge(val objCValueType: ObjCValueType) : TypeBridge()
|
||||||
|
internal object HashCodeBridge : TypeBridge()
|
||||||
|
|
||||||
|
internal data class MethodBridge(
|
||||||
|
val returnBridge: ReturnableTypeBridge,
|
||||||
|
val paramBridges: List<TypeBridge>,
|
||||||
|
val isKotlinTopLevel: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun ObjCExportMapper.bridgeType(kotlinType: KotlinType): TypeBridge {
|
||||||
|
val valueType = kotlinType.correspondingValueType
|
||||||
|
?: return ReferenceBridge
|
||||||
|
|
||||||
|
val objCValueType = ObjCValueType.values().singleOrNull { it.kotlinValueType == valueType }
|
||||||
|
?: error(valueType)
|
||||||
|
|
||||||
|
return ValueTypeBridge(objCValueType)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportMapper.bridgeReturnType(kotlinType: KotlinType): ReturnableTypeBridge = if (kotlinType.isUnit()) {
|
||||||
|
VoidBridge
|
||||||
|
} else {
|
||||||
|
bridgeType(kotlinType)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.bridgeReturnType(descriptor: FunctionDescriptor): ReturnableTypeBridge {
|
||||||
|
val returnType = descriptor.returnType!!
|
||||||
|
return when {
|
||||||
|
descriptor.containingDeclaration == descriptor.builtIns.any && descriptor.name.asString() == "hashCode" ->
|
||||||
|
HashCodeBridge
|
||||||
|
|
||||||
|
descriptor is PropertyGetterDescriptor -> bridgePropertyType(descriptor.correspondingProperty)
|
||||||
|
|
||||||
|
else -> bridgeReturnType(returnType)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.bridgeMethod(descriptor: FunctionDescriptor): MethodBridge {
|
||||||
|
assert(isBaseMethod(descriptor))
|
||||||
|
|
||||||
|
val returnBridge = bridgeReturnType(descriptor)
|
||||||
|
|
||||||
|
val paramBridges = mutableListOf<TypeBridge>()
|
||||||
|
|
||||||
|
val isTopLevel = isTopLevel(descriptor)
|
||||||
|
if (isTopLevel) {
|
||||||
|
paramBridges += ReferenceBridge
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptor.allParameters.mapTo(paramBridges) { bridgeType(it.type) }
|
||||||
|
|
||||||
|
return MethodBridge(returnBridge, paramBridges, isKotlinTopLevel = isTopLevel)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ObjCExportMapper.bridgePropertyType(descriptor: PropertyDescriptor): TypeBridge {
|
||||||
|
assert(isBaseProperty(descriptor))
|
||||||
|
|
||||||
|
return bridgeType(descriptor.type)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal enum class ObjCValueType(
|
||||||
|
val kotlinValueType: ValueType, // It is here for simplicity.
|
||||||
|
val encoding: String
|
||||||
|
) {
|
||||||
|
|
||||||
|
BOOL(ValueType.BOOLEAN, "c"),
|
||||||
|
CHAR(ValueType.BYTE, "c"),
|
||||||
|
UNSIGNED_SHORT(ValueType.CHAR, "S"),
|
||||||
|
SHORT(ValueType.SHORT, "s"),
|
||||||
|
INT(ValueType.INT, "i"),
|
||||||
|
LONG_LONG(ValueType.LONG, "q"),
|
||||||
|
FLOAT(ValueType.FLOAT, "f"),
|
||||||
|
DOUBLE(ValueType.DOUBLE, "d")
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
// UNSIGNED_SHORT -> unsignedShort
|
||||||
|
val nsNumberName = this.name.split('_').mapIndexed { index, s ->
|
||||||
|
val lower = s.toLowerCase()
|
||||||
|
if (index > 0) lower.capitalize() else lower
|
||||||
|
}.joinToString("")
|
||||||
|
|
||||||
|
val nsNumberValueSelector get() = "${nsNumberName}Value"
|
||||||
|
val nsNumberFactorySelector get() = "numberWith${nsNumberName.capitalize()}:"
|
||||||
|
}
|
||||||
+365
@@ -0,0 +1,365 @@
|
|||||||
|
/*
|
||||||
|
* 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.objcexport
|
||||||
|
|
||||||
|
import org.jetbrains.kotlin.backend.konan.Context
|
||||||
|
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
|
||||||
|
import org.jetbrains.kotlin.descriptors.*
|
||||||
|
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.name.Name
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.isSubclassOf
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.module
|
||||||
|
import org.jetbrains.kotlin.resolve.descriptorUtil.parentsWithSelf
|
||||||
|
|
||||||
|
internal class ObjCExportNamer(val context: Context, val mapper: ObjCExportMapper) {
|
||||||
|
val kotlinAnyName = "KotlinBase"
|
||||||
|
|
||||||
|
private val methodSelectors = object : Mapping<FunctionDescriptor, String>() {
|
||||||
|
override fun conflict(first: FunctionDescriptor, second: FunctionDescriptor): Boolean =
|
||||||
|
!mapper.canHaveSameSelector(first, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val methodSwiftNames = object : Mapping<FunctionDescriptor, String>() {
|
||||||
|
override fun conflict(first: FunctionDescriptor, second: FunctionDescriptor): Boolean =
|
||||||
|
!mapper.canHaveSameSelector(first, second)
|
||||||
|
// Note: this condition is correct but can be too strict.
|
||||||
|
}
|
||||||
|
|
||||||
|
private val propertyNames = object : Mapping<PropertyDescriptor, String>() {
|
||||||
|
override fun conflict(first: PropertyDescriptor, second: PropertyDescriptor): Boolean =
|
||||||
|
!mapper.canHaveSameName(first, second)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val classNames = object : Mapping<Any, String>() {
|
||||||
|
override fun conflict(first: Any, second: Any): Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
private val protocolNames = object : Mapping<Any, String>() {
|
||||||
|
override fun conflict(first: Any, second: Any): Boolean = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getPackageName(fqName: FqName): String = classNames.getOrPut(fqName) {
|
||||||
|
StringBuilder().apply {
|
||||||
|
append(context.moduleDescriptor.namePrefix)
|
||||||
|
if (fqName.isRoot) {
|
||||||
|
append("TopLevel")
|
||||||
|
} else {
|
||||||
|
append(fqName.pathSegments().joinToString("") { it.asString().capitalize() })
|
||||||
|
}
|
||||||
|
}.mangledSequence { append("_") }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getClassOrProtocolName(descriptor: ClassDescriptor): String {
|
||||||
|
val mapping = if (descriptor.isInterface) protocolNames else classNames
|
||||||
|
|
||||||
|
return mapping.getOrPut(descriptor) {
|
||||||
|
StringBuilder().apply {
|
||||||
|
append(context.moduleDescriptor.namePrefix)
|
||||||
|
|
||||||
|
if (descriptor.module != context.moduleDescriptor) {
|
||||||
|
append(descriptor.module.namePrefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
descriptor.parentsWithSelf.takeWhile { it is ClassDescriptor }
|
||||||
|
.toList().reversed()
|
||||||
|
.joinTo(this, "") { it.name.asString().capitalize() }
|
||||||
|
}.mangledSequence { append("_") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSelector(method: FunctionDescriptor): String = methodSelectors.getOrPut(method) {
|
||||||
|
assert(mapper.isBaseMethod(method))
|
||||||
|
|
||||||
|
val parameters = mapper.objCValueParameters(method)
|
||||||
|
|
||||||
|
StringBuilder().apply {
|
||||||
|
append(method.mangledName)
|
||||||
|
|
||||||
|
parameters.forEachIndexed { index, it ->
|
||||||
|
val name = when {
|
||||||
|
it is ReceiverParameterDescriptor -> ""
|
||||||
|
method is PropertySetterDescriptor -> when (parameters.size) {
|
||||||
|
1 -> ""
|
||||||
|
else -> "value"
|
||||||
|
}
|
||||||
|
else -> it.name.asString()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
if (method is ConstructorDescriptor) append("With")
|
||||||
|
append(name.capitalize())
|
||||||
|
} else {
|
||||||
|
append(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
append(':')
|
||||||
|
}
|
||||||
|
}.mangledSequence {
|
||||||
|
if (parameters.isNotEmpty()) {
|
||||||
|
// "foo:" -> "foo_:"
|
||||||
|
insert(lastIndex, '_')
|
||||||
|
} else {
|
||||||
|
// "foo" -> "foo_"
|
||||||
|
append("_")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getSwiftName(method: FunctionDescriptor): String = methodSwiftNames.getOrPut(method) {
|
||||||
|
assert(mapper.isBaseMethod(method))
|
||||||
|
|
||||||
|
val parameters = mapper.objCValueParameters(method)
|
||||||
|
|
||||||
|
StringBuilder().apply {
|
||||||
|
append(method.mangledName)
|
||||||
|
append("(")
|
||||||
|
|
||||||
|
parameters.forEach {
|
||||||
|
val label = when {
|
||||||
|
it is ReceiverParameterDescriptor -> "_"
|
||||||
|
method is PropertySetterDescriptor -> when (parameters.size) {
|
||||||
|
1 -> "_"
|
||||||
|
else -> "value"
|
||||||
|
}
|
||||||
|
else -> it.name.asString()
|
||||||
|
}
|
||||||
|
append(label)
|
||||||
|
append(":")
|
||||||
|
}
|
||||||
|
|
||||||
|
append(")")
|
||||||
|
}.mangledSequence {
|
||||||
|
if (parameters.isNotEmpty()) {
|
||||||
|
// "foo(label:)" -> "foo(label_:)"
|
||||||
|
insert(lastIndex - 1, '_')
|
||||||
|
} else {
|
||||||
|
// "foo()" -> "foo_()"
|
||||||
|
insert(lastIndex - 2, '_')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getName(property: PropertyDescriptor): String = propertyNames.getOrPut(property) {
|
||||||
|
assert(mapper.isBaseProperty(property))
|
||||||
|
assert(mapper.isObjCProperty(property))
|
||||||
|
|
||||||
|
StringBuilder().apply {
|
||||||
|
append(property.name.asString())
|
||||||
|
}.mangledSequence {
|
||||||
|
append('_')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
val any = context.builtIns.any
|
||||||
|
|
||||||
|
classNames.forceAssign(any, kotlinAnyName)
|
||||||
|
|
||||||
|
fun ClassDescriptor.method(name: String) =
|
||||||
|
this.unsubstitutedMemberScope.getContributedFunctions(
|
||||||
|
Name.identifier(name),
|
||||||
|
NoLookupLocation.FROM_BACKEND
|
||||||
|
).single()
|
||||||
|
|
||||||
|
val hashCode = any.method("hashCode")
|
||||||
|
val toString = any.method("toString")
|
||||||
|
val equals = any.method("equals")
|
||||||
|
|
||||||
|
methodSelectors.forceAssign(hashCode, "hash")
|
||||||
|
methodSwiftNames.forceAssign(hashCode, "hash")
|
||||||
|
|
||||||
|
methodSelectors.forceAssign(toString, "description")
|
||||||
|
methodSwiftNames.forceAssign(toString, "description")
|
||||||
|
|
||||||
|
methodSelectors.forceAssign(equals, "isEqual:")
|
||||||
|
methodSwiftNames.forceAssign(equals, "isEqual(:)")
|
||||||
|
}
|
||||||
|
|
||||||
|
private val FunctionDescriptor.mangledName: String get() {
|
||||||
|
if (this is ConstructorDescriptor) {
|
||||||
|
return "init"
|
||||||
|
}
|
||||||
|
|
||||||
|
val candidate = when (this) {
|
||||||
|
is PropertyGetterDescriptor -> this.correspondingProperty.name.asString()
|
||||||
|
is PropertySetterDescriptor -> "set${this.correspondingProperty.name.asString().capitalize()}"
|
||||||
|
else -> this.name.asString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val trimmedCandidate = candidate.dropWhile { it == '_' }
|
||||||
|
for (family in listOf("alloc", "copy", "mutableCopy", "new", "init")) {
|
||||||
|
if (trimmedCandidate.startsWithWords(family)) {
|
||||||
|
// Then method can be detected as having special family by Objective-C compiler.
|
||||||
|
// mangle the name:
|
||||||
|
return "do" + candidate.capitalize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: handle clashes with NSObject methods etc.
|
||||||
|
|
||||||
|
return candidate
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun String.startsWithWords(words: String) = this.startsWith(words) &&
|
||||||
|
(this.length == words.length || !this[words.length].isLowerCase())
|
||||||
|
|
||||||
|
private abstract inner class Mapping<T : Any, N>() {
|
||||||
|
private val elementToName = mutableMapOf<T, N>()
|
||||||
|
private val nameToElements = mutableMapOf<N, MutableList<T>>()
|
||||||
|
|
||||||
|
abstract fun conflict(first: T, second: T): Boolean
|
||||||
|
|
||||||
|
fun getOrPut(element: T, nameCandidates: () -> Sequence<N>): N {
|
||||||
|
getIfAssigned(element)?.let { return it }
|
||||||
|
|
||||||
|
nameCandidates().forEach {
|
||||||
|
if (tryAssign(element, it)) {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error("name candidates run out")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIfAssigned(element: T): N? = elementToName[element]
|
||||||
|
|
||||||
|
fun tryAssign(element: T, name: N): Boolean {
|
||||||
|
if (element in elementToName) error(element)
|
||||||
|
|
||||||
|
val elements = nameToElements.getOrPut(name) { mutableListOf() }
|
||||||
|
if (elements.any { conflict(element, it) }) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
elements += element
|
||||||
|
|
||||||
|
elementToName[element] = name
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun forceAssign(element: T, name: N) {
|
||||||
|
if (name in nameToElements || element in elementToName) error(element)
|
||||||
|
|
||||||
|
nameToElements[name] = mutableListOf(element)
|
||||||
|
elementToName[element] = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private inline fun StringBuilder.mangledSequence(crossinline mangle: StringBuilder.() -> Unit) =
|
||||||
|
generateSequence(this.toString()) {
|
||||||
|
this@mangledSequence.mangle()
|
||||||
|
this@mangledSequence.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportMapper.canHaveCommonSubtype(first: ClassDescriptor, second: ClassDescriptor): Boolean {
|
||||||
|
assert(shouldBeExposed(first))
|
||||||
|
assert(shouldBeExposed(second))
|
||||||
|
|
||||||
|
if (first.isSubclassOf(second) || second.isSubclassOf(first)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first.isFinalClass || second.isFinalClass) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return first.isInterface || second.isInterface
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportMapper.canBeInheritedBySameClass(
|
||||||
|
first: CallableMemberDescriptor,
|
||||||
|
second: CallableMemberDescriptor
|
||||||
|
): Boolean {
|
||||||
|
if (this.isTopLevel(first) || this.isTopLevel(second)) {
|
||||||
|
return (first.containingDeclaration.fqNameSafe == second.containingDeclaration.fqNameSafe)
|
||||||
|
}
|
||||||
|
|
||||||
|
val firstClass = this.getClassIfCategory(first) ?: first.containingDeclaration as ClassDescriptor
|
||||||
|
val secondClass = this.getClassIfCategory(second) ?: second.containingDeclaration as ClassDescriptor
|
||||||
|
|
||||||
|
if (first is ConstructorDescriptor) {
|
||||||
|
return firstClass == secondClass || second !is ConstructorDescriptor && firstClass.isSubclassOf(secondClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (second is ConstructorDescriptor) {
|
||||||
|
return secondClass == firstClass || first !is ConstructorDescriptor && secondClass.isSubclassOf(firstClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
return canHaveCommonSubtype(firstClass, secondClass)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportMapper.canHaveSameSelector(first: FunctionDescriptor, second: FunctionDescriptor): Boolean {
|
||||||
|
assert(isBaseMethod(first))
|
||||||
|
assert(isBaseMethod(second))
|
||||||
|
|
||||||
|
if (!canBeInheritedBySameClass(first, second)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first.dispatchReceiverParameter == null || second.dispatchReceiverParameter == null) {
|
||||||
|
// I.e. any is category method.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first.name != second.name) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (first.extensionReceiverParameter?.type != second.extensionReceiverParameter?.type) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (first.valueParameters.map { it.type } != second.valueParameters.map { it.type }) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise both are Kotlin member methods should merge in any common subclass.
|
||||||
|
|
||||||
|
// Taking into account the conditions above, check if methods have the same ABI:
|
||||||
|
return bridgeReturnType(first) == bridgeReturnType(second)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ObjCExportMapper.canHaveSameName(first: PropertyDescriptor, second: PropertyDescriptor): Boolean {
|
||||||
|
assert(isBaseProperty(first))
|
||||||
|
assert(isObjCProperty(first))
|
||||||
|
assert(isBaseProperty(second))
|
||||||
|
assert(isObjCProperty(second))
|
||||||
|
|
||||||
|
if (!canBeInheritedBySameClass(first, second)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first.dispatchReceiverParameter == null || second.dispatchReceiverParameter == null) {
|
||||||
|
// I.e. any is category property.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return bridgePropertyType(first) == bridgePropertyType(second)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val ModuleDescriptor.namePrefix: String get() {
|
||||||
|
// <fooBar> -> FooBar
|
||||||
|
val moduleName = this.name.asString().let { it.substring(1, it.lastIndex) }.capitalize()
|
||||||
|
|
||||||
|
val uppers = moduleName.filterIndexed { index, character -> index == 0 || character.isUpperCase() }
|
||||||
|
if (uppers.length >= 3) return uppers
|
||||||
|
|
||||||
|
return moduleName
|
||||||
|
}
|
||||||
@@ -39,7 +39,7 @@ llvmLtoFlags.osx =
|
|||||||
llvmLtoOptFlags.osx = -O3 -function-sections
|
llvmLtoOptFlags.osx = -O3 -function-sections
|
||||||
llvmLtoNooptFlags.osx = -O1
|
llvmLtoNooptFlags.osx = -O1
|
||||||
llvmLtoDynamicFlags.osx = -relocation-model=pic
|
llvmLtoDynamicFlags.osx = -relocation-model=pic
|
||||||
linkerKonanFlags.osx = -lc++ -lobjc
|
linkerKonanFlags.osx = -lc++ -lobjc -framework Foundation
|
||||||
linkerOptimizationFlags.osx = -dead_strip
|
linkerOptimizationFlags.osx = -dead_strip
|
||||||
linkerDebugFlags.osx = -S
|
linkerDebugFlags.osx = -S
|
||||||
linkerDynamicFlags.osx = -S -dylib
|
linkerDynamicFlags.osx = -S -dylib
|
||||||
@@ -66,7 +66,7 @@ llvmLtoFlags.ios =
|
|||||||
llvmLtoOptFlags.ios = -O3 -function-sections
|
llvmLtoOptFlags.ios = -O3 -function-sections
|
||||||
linkerDebugFlags.ios = -S
|
linkerDebugFlags.ios = -S
|
||||||
llvmLtoNooptFlags.ios = -O1
|
llvmLtoNooptFlags.ios = -O1
|
||||||
linkerKonanFlags.ios = -lc++ -lobjc -sdk_version 10.2
|
linkerKonanFlags.ios = -lc++ -lobjc -framework Foundation -sdk_version 10.2
|
||||||
linkerOptimizationFlags.ios = -dead_strip
|
linkerOptimizationFlags.ios = -dead_strip
|
||||||
osVersionMinFlagLd.ios = -iphoneos_version_min
|
osVersionMinFlagLd.ios = -iphoneos_version_min
|
||||||
osVersionMinFlagClang.ios = -miphoneos-version-min
|
osVersionMinFlagClang.ios = -miphoneos-version-min
|
||||||
@@ -86,7 +86,7 @@ libffiDir.ios_sim = libffi-3.2.1-2-darwin-ios-sim
|
|||||||
llvmLtoFlags.ios_sim =
|
llvmLtoFlags.ios_sim =
|
||||||
llvmLtoOptFlags.ios_sim = -O3 -function-sections
|
llvmLtoOptFlags.ios_sim = -O3 -function-sections
|
||||||
llvmLtoNooptFlags.ios_sim = -O1
|
llvmLtoNooptFlags.ios_sim = -O1
|
||||||
linkerKonanFlags.ios_sim = -lc++ -lobjc -sdk_version 10.2
|
linkerKonanFlags.ios_sim = -lc++ -lobjc -framework Foundation -sdk_version 10.2
|
||||||
linkerOptimizationFlags.ios_sim = -dead_strip
|
linkerOptimizationFlags.ios_sim = -dead_strip
|
||||||
linkerDebugFlags.ios_sim = -S
|
linkerDebugFlags.ios_sim = -S
|
||||||
osVersionMinFlagLd.ios_sim = -ios_simulator_version_min
|
osVersionMinFlagLd.ios_sim = -ios_simulator_version_min
|
||||||
|
|||||||
@@ -33,6 +33,11 @@ inline void konanFreeMemory(void* memory) {
|
|||||||
konan::free(memory);
|
konan::free(memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
inline T* konanAllocArray(size_t length) {
|
||||||
|
return reinterpret_cast<T*>(konanAllocMemory(length * sizeof(T)));
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T, typename ...A>
|
template <typename T, typename ...A>
|
||||||
inline T* konanConstructInstance(A&& ...args) {
|
inline T* konanConstructInstance(A&& ...args) {
|
||||||
return new (konanAllocMemory(sizeof(T))) T(::std::forward<A>(args)...);
|
return new (konanAllocMemory(sizeof(T))) T(::std::forward<A>(args)...);
|
||||||
|
|||||||
@@ -30,4 +30,9 @@
|
|||||||
|
|
||||||
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(a[0]))
|
||||||
|
|
||||||
|
#if KONAN_OBJC_INTEROP
|
||||||
|
#define KONAN_OBJECTS_CAN_HAVE_RESERVED_TAIL 1
|
||||||
|
#define KONAN_TYPE_INFO_HAS_WRITABLE_PART 1
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif // RUNTIME_COMMON_H
|
#endif // RUNTIME_COMMON_H
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include "Assert.h"
|
#include "Assert.h"
|
||||||
#include "Exceptions.h"
|
#include "Exceptions.h"
|
||||||
#include "Memory.h"
|
#include "Memory.h"
|
||||||
|
#include "MemoryPrivate.hpp"
|
||||||
#include "Natives.h"
|
#include "Natives.h"
|
||||||
|
|
||||||
// If garbage collection algorithm for cyclic garbage to be used.
|
// If garbage collection algorithm for cyclic garbage to be used.
|
||||||
@@ -349,10 +350,6 @@ inline bool isFreeable(const ContainerHeader* header) {
|
|||||||
return (header->refCount_ & CONTAINER_TAG_MASK) < CONTAINER_TAG_PERMANENT;
|
return (header->refCount_ & CONTAINER_TAG_MASK) < CONTAINER_TAG_PERMANENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
inline bool isPermanent(const ContainerHeader* header) {
|
|
||||||
return (header->refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_PERMANENT;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline bool isArena(const ContainerHeader* header) {
|
inline bool isArena(const ContainerHeader* header) {
|
||||||
return (header->refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_STACK;
|
return (header->refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_STACK;
|
||||||
}
|
}
|
||||||
@@ -361,14 +358,21 @@ inline container_size_t alignUp(container_size_t size, int alignment) {
|
|||||||
return (size + alignment - 1) & ~(alignment - 1);
|
return (size + alignment - 1) & ~(alignment - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if KONAN_OBJECTS_CAN_HAVE_RESERVED_TAIL
|
||||||
|
// Note: defined by a compiler-generated bitcode.
|
||||||
|
extern "C" const container_size_t kObjectReservedTailSize;
|
||||||
|
#else
|
||||||
|
constexpr container_size_t kObjectReservedTailSize = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
// TODO: shall we do padding for alignment?
|
// TODO: shall we do padding for alignment?
|
||||||
inline container_size_t objectSize(const ObjHeader* obj) {
|
inline container_size_t objectSize(const ObjHeader* obj) {
|
||||||
const TypeInfo* type_info = obj->type_info();
|
const TypeInfo* type_info = obj->type_info();
|
||||||
container_size_t size = type_info->instanceSize_ < 0 ?
|
container_size_t size = kObjectReservedTailSize + (type_info->instanceSize_ < 0 ?
|
||||||
// An array.
|
// An array.
|
||||||
ArrayDataSizeBytes(obj->array()) + sizeof(ArrayHeader)
|
ArrayDataSizeBytes(obj->array()) + sizeof(ArrayHeader)
|
||||||
:
|
:
|
||||||
type_info->instanceSize_ + sizeof(ObjHeader);
|
type_info->instanceSize_ + sizeof(ObjHeader));
|
||||||
return alignUp(size, kObjectAlignment);
|
return alignUp(size, kObjectAlignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -393,6 +397,7 @@ inline bool isRefCounted(KConstRef object) {
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
void objc_release(void* ptr);
|
void objc_release(void* ptr);
|
||||||
|
void Kotlin_ObjCExport_releaseReservedObjectTail(ObjHeader* obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline void runDeallocationHooks(ObjHeader* obj) {
|
inline void runDeallocationHooks(ObjHeader* obj) {
|
||||||
@@ -400,6 +405,10 @@ inline void runDeallocationHooks(ObjHeader* obj) {
|
|||||||
if (obj->type_info() == theObjCPointerHolderTypeInfo) {
|
if (obj->type_info() == theObjCPointerHolderTypeInfo) {
|
||||||
void* objcPtr = *reinterpret_cast<void**>(obj + 1); // TODO: use more reliable layout description
|
void* objcPtr = *reinterpret_cast<void**>(obj + 1); // TODO: use more reliable layout description
|
||||||
objc_release(objcPtr);
|
objc_release(objcPtr);
|
||||||
|
} else {
|
||||||
|
if (HasReservedObjectTail(obj)) {
|
||||||
|
Kotlin_ObjCExport_releaseReservedObjectTail(obj);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
@@ -756,7 +765,7 @@ ContainerHeader* AllocContainer(size_t size) {
|
|||||||
// TODO: try to reuse elements of finalizer queue for new allocations, question
|
// TODO: try to reuse elements of finalizer queue for new allocations, question
|
||||||
// is how to get actual size of container.
|
// is how to get actual size of container.
|
||||||
#endif
|
#endif
|
||||||
ContainerHeader* result = konanConstructSizedInstance<ContainerHeader>(size);
|
ContainerHeader* result = konanConstructSizedInstance<ContainerHeader>(alignUp(size, kObjectAlignment));
|
||||||
CONTAINER_ALLOC_EVENT(state, size, result);
|
CONTAINER_ALLOC_EVENT(state, size, result);
|
||||||
#if TRACE_MEMORY
|
#if TRACE_MEMORY
|
||||||
state->containers->insert(result);
|
state->containers->insert(result);
|
||||||
@@ -794,7 +803,7 @@ void FreeContainer(ContainerHeader* header) {
|
|||||||
void ObjectContainer::Init(const TypeInfo* type_info) {
|
void ObjectContainer::Init(const TypeInfo* type_info) {
|
||||||
RuntimeAssert(type_info->instanceSize_ >= 0, "Must be an object");
|
RuntimeAssert(type_info->instanceSize_ >= 0, "Must be an object");
|
||||||
uint32_t alloc_size =
|
uint32_t alloc_size =
|
||||||
sizeof(ContainerHeader) + sizeof(ObjHeader) + type_info->instanceSize_;
|
sizeof(ContainerHeader) + sizeof(ObjHeader) + type_info->instanceSize_ + kObjectReservedTailSize;
|
||||||
header_ = AllocContainer(alloc_size);
|
header_ = AllocContainer(alloc_size);
|
||||||
if (header_) {
|
if (header_) {
|
||||||
// One object in this container.
|
// One object in this container.
|
||||||
@@ -810,7 +819,7 @@ void ArrayContainer::Init(const TypeInfo* type_info, uint32_t elements) {
|
|||||||
RuntimeAssert(type_info->instanceSize_ < 0, "Must be an array");
|
RuntimeAssert(type_info->instanceSize_ < 0, "Must be an array");
|
||||||
uint32_t alloc_size =
|
uint32_t alloc_size =
|
||||||
sizeof(ContainerHeader) + sizeof(ArrayHeader) -
|
sizeof(ContainerHeader) + sizeof(ArrayHeader) -
|
||||||
type_info->instanceSize_ * elements;
|
type_info->instanceSize_ * elements + kObjectReservedTailSize;
|
||||||
header_ = AllocContainer(alloc_size);
|
header_ = AllocContainer(alloc_size);
|
||||||
RuntimeAssert(header_ != nullptr, "Cannot alloc memory");
|
RuntimeAssert(header_ != nullptr, "Cannot alloc memory");
|
||||||
if (header_) {
|
if (header_) {
|
||||||
@@ -893,7 +902,7 @@ ObjHeader** ArenaContainer::getSlot() {
|
|||||||
|
|
||||||
ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
|
ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
|
||||||
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
|
RuntimeAssert(type_info->instanceSize_ >= 0, "must be an object");
|
||||||
uint32_t size = type_info->instanceSize_ + sizeof(ObjHeader);
|
uint32_t size = type_info->instanceSize_ + sizeof(ObjHeader) + kObjectReservedTailSize;
|
||||||
ObjHeader* result = reinterpret_cast<ObjHeader*>(place(size));
|
ObjHeader* result = reinterpret_cast<ObjHeader*>(place(size));
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -906,7 +915,7 @@ ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) {
|
|||||||
|
|
||||||
ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, uint32_t count) {
|
ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, uint32_t count) {
|
||||||
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
|
RuntimeAssert(type_info->instanceSize_ < 0, "must be an array");
|
||||||
container_size_t size = sizeof(ArrayHeader) - type_info->instanceSize_ * count;
|
container_size_t size = sizeof(ArrayHeader) - type_info->instanceSize_ * count + kObjectReservedTailSize;
|
||||||
ArrayHeader* result = reinterpret_cast<ArrayHeader*>(place(size));
|
ArrayHeader* result = reinterpret_cast<ArrayHeader*>(place(size));
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
@@ -928,6 +937,14 @@ inline void ReleaseRef(const ObjHeader* object) {
|
|||||||
Release(object->container());
|
Release(object->container());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AddRefFromAssociatedObject(const ObjHeader* object) {
|
||||||
|
AddRef(object);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReleaseRefFromAssociatedObject(const ObjHeader* object) {
|
||||||
|
ReleaseRef(object);
|
||||||
|
}
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
MemoryState* InitMemory() {
|
MemoryState* InitMemory() {
|
||||||
@@ -1036,6 +1053,16 @@ OBJ_GETTER(InitInstance,
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool HasReservedObjectTail(ObjHeader* obj) {
|
||||||
|
return kObjectReservedTailSize != 0 && !isPermanent(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* GetReservedObjectTail(ObjHeader* obj) {
|
||||||
|
return reinterpret_cast<void*>(
|
||||||
|
reinterpret_cast<uintptr_t>(obj) + objectSize(obj) - kObjectReservedTailSize
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
void SetRef(ObjHeader** location, const ObjHeader* object) {
|
void SetRef(ObjHeader** location, const ObjHeader* object) {
|
||||||
MEMORY_LOG("SetRef *%p: %p\n", location, object)
|
MEMORY_LOG("SetRef *%p: %p\n", location, object)
|
||||||
*const_cast<const ObjHeader**>(location) = object;
|
*const_cast<const ObjHeader**>(location) = object;
|
||||||
|
|||||||
@@ -115,6 +115,10 @@ struct ContainerHeader {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline bool isPermanent(const ContainerHeader* header) {
|
||||||
|
return (header->refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_PERMANENT;
|
||||||
|
}
|
||||||
|
|
||||||
struct ArrayHeader;
|
struct ArrayHeader;
|
||||||
|
|
||||||
// Header of every object.
|
// Header of every object.
|
||||||
@@ -153,6 +157,10 @@ struct ObjHeader {
|
|||||||
const ArrayHeader* array() const { return reinterpret_cast<const ArrayHeader*>(this); }
|
const ArrayHeader* array() const { return reinterpret_cast<const ArrayHeader*>(this); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
inline bool isPermanent(const ObjHeader* obj) {
|
||||||
|
return isPermanent(obj->container());
|
||||||
|
}
|
||||||
|
|
||||||
// Header of value type array objects. Keep layout in sync with that of object header.
|
// Header of value type array objects. Keep layout in sync with that of object header.
|
||||||
struct ArrayHeader {
|
struct ArrayHeader {
|
||||||
const TypeInfo* type_info_;
|
const TypeInfo* type_info_;
|
||||||
@@ -331,6 +339,11 @@ void DeinitInstanceBody(const TypeInfo* typeInfo, void* body);
|
|||||||
OBJ_GETTER(InitInstance, ObjHeader** location, const TypeInfo* type_info,
|
OBJ_GETTER(InitInstance, ObjHeader** location, const TypeInfo* type_info,
|
||||||
void (*ctor)(ObjHeader*));
|
void (*ctor)(ObjHeader*));
|
||||||
|
|
||||||
|
// Returns true iff the object has space reserved in its tail for special purposes.
|
||||||
|
bool HasReservedObjectTail(ObjHeader* obj) RUNTIME_NOTHROW;
|
||||||
|
// Returns the pointer to the reserved space, `HasReservedObjectTail(obj)` must be true.
|
||||||
|
void* GetReservedObjectTail(ObjHeader* obj) RUNTIME_NOTHROW;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Object reference management.
|
// Object reference management.
|
||||||
//
|
//
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef RUNTIME_MEMORYPRIVATE_HPP
|
||||||
|
#define RUNTIME_MEMORYPRIVATE_HPP
|
||||||
|
|
||||||
|
#include "Memory.h"
|
||||||
|
|
||||||
|
void AddRefFromAssociatedObject(const ObjHeader* object) RUNTIME_NOTHROW;
|
||||||
|
void ReleaseRefFromAssociatedObject(const ObjHeader* object) RUNTIME_NOTHROW;
|
||||||
|
|
||||||
|
#endif // RUNTIME_MEMORYPRIVATE_HPP
|
||||||
@@ -0,0 +1,941 @@
|
|||||||
|
/*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#import "Types.h"
|
||||||
|
#import "Memory.h"
|
||||||
|
|
||||||
|
#if KONAN_OBJC_INTEROP
|
||||||
|
|
||||||
|
#import <Foundation/NSObject.h>
|
||||||
|
#import <Foundation/NSArray.h>
|
||||||
|
#import <Foundation/NSValue.h>
|
||||||
|
#import <Foundation/NSString.h>
|
||||||
|
#import <Foundation/NSNull.h>
|
||||||
|
#import <Foundation/NSMethodSignature.h>
|
||||||
|
#import <Foundation/NSException.h>
|
||||||
|
#import <objc/runtime.h>
|
||||||
|
#import "MemoryPrivate.hpp"
|
||||||
|
#import "Runtime.h"
|
||||||
|
#import "Utils.h"
|
||||||
|
|
||||||
|
struct ObjCToKotlinMethodAdapter {
|
||||||
|
const char* selector;
|
||||||
|
const char* encoding;
|
||||||
|
IMP imp;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KotlinToObjCMethodAdapter {
|
||||||
|
const char* selector;
|
||||||
|
MethodNameHash nameSignature;
|
||||||
|
int vtableIndex;
|
||||||
|
const void* kotlinImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ObjCTypeAdapter {
|
||||||
|
const TypeInfo* kotlinTypeInfo;
|
||||||
|
|
||||||
|
const void * const * kotlinVtable;
|
||||||
|
int kotlinVtableSize;
|
||||||
|
|
||||||
|
const MethodTableRecord* kotlinMethodTable;
|
||||||
|
int kotlinMethodTableSize;
|
||||||
|
|
||||||
|
const char* objCName;
|
||||||
|
|
||||||
|
const ObjCToKotlinMethodAdapter* directAdapters;
|
||||||
|
int directAdapterNum;
|
||||||
|
|
||||||
|
const ObjCToKotlinMethodAdapter* virtualAdapters;
|
||||||
|
int virtualAdapterNum;
|
||||||
|
|
||||||
|
const KotlinToObjCMethodAdapter* reverseAdapters;
|
||||||
|
int reverseAdapterNum;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef id (*convertReferenceToObjC)(ObjHeader* obj);
|
||||||
|
|
||||||
|
struct TypeInfoObjCExportAddition {
|
||||||
|
convertReferenceToObjC convert;
|
||||||
|
Class objCClass;
|
||||||
|
const ObjCTypeAdapter* typeAdapter;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct WritableTypeInfo {
|
||||||
|
TypeInfoObjCExportAddition objCExport;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
static char associatedTypeInfoKey;
|
||||||
|
|
||||||
|
static const TypeInfo* getAssociatedTypeInfo(Class clazz) {
|
||||||
|
return (const TypeInfo*)[objc_getAssociatedObject(clazz, &associatedTypeInfoKey) pointerValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setAssociatedTypeInfo(Class clazz, const TypeInfo* typeInfo) {
|
||||||
|
objc_setAssociatedObject(clazz, &associatedTypeInfoKey, [NSValue valueWithPointer:typeInfo], OBJC_ASSOCIATION_RETAIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static bool HasAssociatedObjectField(ObjHeader* obj) {
|
||||||
|
return HasReservedObjectTail(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static void SetAssociatedObject(ObjHeader* obj, id value) {
|
||||||
|
*reinterpret_cast<id*>(GetReservedObjectTail(obj)) = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" inline id Kotlin_ObjCExport_GetAssociatedObject(ObjHeader* obj) {
|
||||||
|
return *reinterpret_cast<id*>(GetReservedObjectTail(obj));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline static OBJ_GETTER(AllocInstanceWithAssociatedObject, const TypeInfo* typeInfo, id associatedObject) {
|
||||||
|
ObjHeader* result = AllocInstance(typeInfo, OBJ_RESULT);
|
||||||
|
RuntimeAssert(HasAssociatedObjectField(result), "");
|
||||||
|
SetAssociatedObject(result, associatedObject);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void initializeClass(Class clazz);
|
||||||
|
|
||||||
|
@protocol ConvertibleToKotlin
|
||||||
|
@required
|
||||||
|
-(KRef)toKotlin:(KRef*)OBJ_RESULT;
|
||||||
|
@end;
|
||||||
|
|
||||||
|
@interface KotlinBase : NSObject <ConvertibleToKotlin>
|
||||||
|
@end;
|
||||||
|
|
||||||
|
@implementation KotlinBase {
|
||||||
|
KRef kotlinObj;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(KRef)toKotlin:(KRef*)OBJ_RESULT {
|
||||||
|
RETURN_OBJ(kotlinObj);
|
||||||
|
}
|
||||||
|
|
||||||
|
+(void)initialize {
|
||||||
|
initializeClass(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
+(instancetype)allocWithZone:(NSZone*)zone {
|
||||||
|
Kotlin_initRuntimeIfNeeded();
|
||||||
|
|
||||||
|
KotlinBase* result = [super allocWithZone:zone];
|
||||||
|
|
||||||
|
const TypeInfo* typeInfo = getAssociatedTypeInfo(self);
|
||||||
|
if (typeInfo == nullptr) {
|
||||||
|
[NSException raise:NSGenericException
|
||||||
|
format:@"%s is not allocatable or +[KotlinBase initialize] method wasn't called on it",
|
||||||
|
class_getName(object_getClass(self))];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeInfo->instanceSize_ < 0) {
|
||||||
|
[NSException raise:NSGenericException
|
||||||
|
format:@"Allocating %s is not supported yet",
|
||||||
|
class_getName(object_getClass(self))];
|
||||||
|
}
|
||||||
|
|
||||||
|
AllocInstanceWithAssociatedObject(typeInfo, result, &result->kotlinObj);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
+(instancetype)createWrapper:(ObjHeader*)obj {
|
||||||
|
KotlinBase* result = [super allocWithZone:nil];
|
||||||
|
// TODO: should we call NSObject.init ?
|
||||||
|
UpdateRef(&result->kotlinObj, obj);
|
||||||
|
|
||||||
|
if (!isPermanent(obj)) {
|
||||||
|
RuntimeAssert(HasAssociatedObjectField(obj), "");
|
||||||
|
SetAssociatedObject(obj, result);
|
||||||
|
}
|
||||||
|
// TODO: permanent objects should probably be supported as custom types.
|
||||||
|
|
||||||
|
return [result autorelease];
|
||||||
|
}
|
||||||
|
|
||||||
|
-(instancetype)retain {
|
||||||
|
ObjHeader* obj = kotlinObj;
|
||||||
|
if (isPermanent(obj)) { // TODO: consider storing `isPermanent` to self field.
|
||||||
|
[super retain];
|
||||||
|
} else {
|
||||||
|
AddRefFromAssociatedObject(obj);
|
||||||
|
}
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(oneway void)release {
|
||||||
|
ObjHeader* obj = kotlinObj;
|
||||||
|
if (isPermanent(obj)) {
|
||||||
|
[super release];
|
||||||
|
} else {
|
||||||
|
ReleaseRefFromAssociatedObject(kotlinObj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)releaseAsAssociatedObject {
|
||||||
|
RuntimeAssert(!isPermanent(kotlinObj), "");
|
||||||
|
[super release];
|
||||||
|
}
|
||||||
|
|
||||||
|
@end;
|
||||||
|
|
||||||
|
extern "C" void Kotlin_ObjCExport_releaseReservedObjectTail(ObjHeader* obj) {
|
||||||
|
RuntimeAssert(HasAssociatedObjectField(obj), "");
|
||||||
|
id associatedObject = Kotlin_ObjCExport_GetAssociatedObject(obj);
|
||||||
|
if (associatedObject != nullptr) {
|
||||||
|
[associatedObject releaseAsAssociatedObject];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ObjCTypeAdapter* findAdapterByName(
|
||||||
|
const char* name,
|
||||||
|
const ObjCTypeAdapter** sortedAdapters,
|
||||||
|
int adapterNum
|
||||||
|
) {
|
||||||
|
|
||||||
|
int left = 0, right = adapterNum - 1;
|
||||||
|
|
||||||
|
while (right >= left) {
|
||||||
|
int mid = (left + right) / 2;
|
||||||
|
int cmp = strcmp(name, sortedAdapters[mid]->objCName);
|
||||||
|
if (cmp < 0) {
|
||||||
|
right = mid - 1;
|
||||||
|
} else if (cmp > 0) {
|
||||||
|
left = mid + 1;
|
||||||
|
} else {
|
||||||
|
return sortedAdapters[mid];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
__attribute__((weak)) const ObjCTypeAdapter** Kotlin_ObjCExport_sortedClassAdapters = nullptr;
|
||||||
|
__attribute__((weak)) int Kotlin_ObjCExport_sortedClassAdaptersNum = 0;
|
||||||
|
|
||||||
|
__attribute__((weak)) const ObjCTypeAdapter** Kotlin_ObjCExport_sortedProtocolAdapters = nullptr;
|
||||||
|
__attribute__((weak)) int Kotlin_ObjCExport_sortedProtocolAdaptersNum = 0;
|
||||||
|
|
||||||
|
static const ObjCTypeAdapter* findClassAdapter(Class clazz) {
|
||||||
|
return findAdapterByName(class_getName(clazz),
|
||||||
|
Kotlin_ObjCExport_sortedClassAdapters,
|
||||||
|
Kotlin_ObjCExport_sortedClassAdaptersNum
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ObjCTypeAdapter* findProtocolAdapter(Protocol* prot) {
|
||||||
|
return findAdapterByName(protocol_getName(prot),
|
||||||
|
Kotlin_ObjCExport_sortedProtocolAdapters,
|
||||||
|
Kotlin_ObjCExport_sortedProtocolAdaptersNum
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const ObjCTypeAdapter* getTypeAdapter(const TypeInfo* typeInfo) {
|
||||||
|
return typeInfo->writableInfo_->objCExport.typeAdapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Protocol* getProtocolForInterface(const TypeInfo* interfaceInfo) {
|
||||||
|
const ObjCTypeAdapter* protocolAdapter = getTypeAdapter(interfaceInfo);
|
||||||
|
if (protocolAdapter != nullptr) {
|
||||||
|
Protocol* protocol = objc_getProtocol(protocolAdapter->objCName);
|
||||||
|
if (protocol != nullptr) {
|
||||||
|
return protocol;
|
||||||
|
} else {
|
||||||
|
// TODO: construct the protocol in compiler instead, because this case can't be handled easily.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const TypeInfo* getOrCreateTypeInfo(Class clazz);
|
||||||
|
|
||||||
|
static void initializeClass(Class clazz) {
|
||||||
|
const ObjCTypeAdapter* typeAdapter = findClassAdapter(clazz);
|
||||||
|
if (typeAdapter == nullptr) {
|
||||||
|
getOrCreateTypeInfo(clazz);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TypeInfo* typeInfo = typeAdapter->kotlinTypeInfo;
|
||||||
|
bool isClassForPackage = typeInfo == nullptr;
|
||||||
|
if (!isClassForPackage) {
|
||||||
|
setAssociatedTypeInfo(clazz, typeInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < typeAdapter->directAdapterNum; ++i) {
|
||||||
|
const ObjCToKotlinMethodAdapter* adapter = typeAdapter->directAdapters + i;
|
||||||
|
SEL selector = sel_registerName(adapter->selector);
|
||||||
|
Class methodContainer = isClassForPackage ? object_getClass(clazz) : clazz;
|
||||||
|
BOOL added = class_addMethod(methodContainer, selector, adapter->imp, adapter->encoding);
|
||||||
|
RuntimeAssert(added, "Unexpected selector clash");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isClassForPackage) return;
|
||||||
|
|
||||||
|
for (int i = 0; i < typeInfo->implementedInterfacesCount_; ++i) {
|
||||||
|
Protocol* protocol = getProtocolForInterface(typeInfo->implementedInterfaces_[i]);
|
||||||
|
if (protocol != nullptr) {
|
||||||
|
class_addProtocol(clazz, protocol);
|
||||||
|
class_addProtocol(object_getClass(clazz), protocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface NSObject (NSObjectToKotlin) <ConvertibleToKotlin>
|
||||||
|
@end;
|
||||||
|
|
||||||
|
extern "C" id objc_retain(id self);
|
||||||
|
extern "C" id objc_retainBlock(id self);
|
||||||
|
extern "C" void objc_release(id self);
|
||||||
|
extern "C" id objc_retainAutoreleaseReturnValue(id self);
|
||||||
|
|
||||||
|
@implementation NSObject (NSObjectToKotlin)
|
||||||
|
-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT {
|
||||||
|
const TypeInfo* typeInfo = getOrCreateTypeInfo(object_getClass(self));
|
||||||
|
RETURN_RESULT_OF(AllocInstanceWithAssociatedObject, typeInfo, objc_retain(self));
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)releaseAsAssociatedObject {
|
||||||
|
objc_release(self);
|
||||||
|
}
|
||||||
|
@end;
|
||||||
|
|
||||||
|
@interface NSString (NSStringToKotlin) <ConvertibleToKotlin>
|
||||||
|
@end;
|
||||||
|
|
||||||
|
extern "C" OBJ_GETTER(Kotlin_Interop_CreateKStringFromNSString, NSString* str);
|
||||||
|
|
||||||
|
@implementation NSString (NSStringToKotlin)
|
||||||
|
-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT {
|
||||||
|
RETURN_RESULT_OF(Kotlin_Interop_CreateKStringFromNSString, self);
|
||||||
|
}
|
||||||
|
@end;
|
||||||
|
|
||||||
|
@interface NSArray (NSArrayToKotlin) <ConvertibleToKotlin>
|
||||||
|
@end;
|
||||||
|
|
||||||
|
extern "C" void Kotlin_NSArrayList_constructor(ObjHeader* obj);
|
||||||
|
|
||||||
|
extern const TypeInfo *theNSArrayListTypeInfo;
|
||||||
|
|
||||||
|
@implementation NSArray (NSArrayToKotlin)
|
||||||
|
-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT {
|
||||||
|
ObjHeader* result = AllocInstanceWithAssociatedObject(theNSArrayListTypeInfo, objc_retain(self), OBJ_RESULT);
|
||||||
|
|
||||||
|
Kotlin_NSArrayList_constructor(result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)releaseAsAssociatedObject {
|
||||||
|
objc_release(self);
|
||||||
|
}
|
||||||
|
@end;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
OBJ_GETTER(Kotlin_boxBoolean, KBoolean value);
|
||||||
|
OBJ_GETTER(Kotlin_boxChar, KChar value);
|
||||||
|
OBJ_GETTER(Kotlin_boxByte, KByte value);
|
||||||
|
OBJ_GETTER(Kotlin_boxShort, KShort value);
|
||||||
|
OBJ_GETTER(Kotlin_boxInt, KInt value);
|
||||||
|
OBJ_GETTER(Kotlin_boxLong, KLong value);
|
||||||
|
OBJ_GETTER(Kotlin_boxFloat, KFloat value);
|
||||||
|
OBJ_GETTER(Kotlin_boxDouble, KDouble value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@interface NSNumber (NSNumberToKotlin) <ConvertibleToKotlin>
|
||||||
|
@end;
|
||||||
|
|
||||||
|
static Class __NSCFBooleanClass = nullptr;
|
||||||
|
|
||||||
|
@implementation NSNumber (NSNumberToKotlin)
|
||||||
|
-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT {
|
||||||
|
const char* type = self.objCType;
|
||||||
|
|
||||||
|
// TODO: the code below makes some assumption on char, short, int and long sizes.
|
||||||
|
|
||||||
|
switch (type[0]) {
|
||||||
|
case 'S': RETURN_RESULT_OF(Kotlin_boxChar, self.unsignedShortValue);
|
||||||
|
case 'c': {
|
||||||
|
Class booleanClass = __NSCFBooleanClass;
|
||||||
|
if (booleanClass == nullptr) {
|
||||||
|
// Note: __NSCFBoolean is not visible to linker, so this case can't be handled with a category.
|
||||||
|
booleanClass = __NSCFBooleanClass = objc_getClass("__NSCFBoolean");
|
||||||
|
if (booleanClass == nullptr) {
|
||||||
|
[NSException raise:NSGenericException format:@"__NSCFBoolean class not found"];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (object_getClass(self) == booleanClass) {
|
||||||
|
RETURN_RESULT_OF(Kotlin_boxBoolean, self.boolValue);
|
||||||
|
} else {
|
||||||
|
RETURN_RESULT_OF(Kotlin_boxByte, self.charValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case 's': RETURN_RESULT_OF(Kotlin_boxShort, self.shortValue);
|
||||||
|
case 'i': RETURN_RESULT_OF(Kotlin_boxInt, self.intValue);
|
||||||
|
case 'q': RETURN_RESULT_OF(Kotlin_boxLong, self.longLongValue);
|
||||||
|
case 'f': RETURN_RESULT_OF(Kotlin_boxFloat, self.floatValue);
|
||||||
|
case 'd': RETURN_RESULT_OF(Kotlin_boxDouble, self.doubleValue);
|
||||||
|
|
||||||
|
default: return [super toKotlin:OBJ_RESULT];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@end;
|
||||||
|
|
||||||
|
@interface KListNSArray : NSArray <ConvertibleToKotlin>
|
||||||
|
@end;
|
||||||
|
|
||||||
|
extern "C" OBJ_GETTER(Kotlin_List_get, ObjHeader* list, KInt index);
|
||||||
|
extern "C" KInt Kotlin_List_getSize(ObjHeader* list);
|
||||||
|
|
||||||
|
extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj);
|
||||||
|
|
||||||
|
@implementation KListNSArray {
|
||||||
|
ObjHeader* list;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)dealloc {
|
||||||
|
UpdateRef(&list, nullptr);
|
||||||
|
[super dealloc];
|
||||||
|
}
|
||||||
|
|
||||||
|
+(id)createWithKList:(ObjHeader*)list {
|
||||||
|
KListNSArray* result = [[[KListNSArray alloc] init] autorelease];
|
||||||
|
UpdateRef(&result->list, list);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT {
|
||||||
|
RETURN_OBJ(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
-(id)objectAtIndex:(NSUInteger)index {
|
||||||
|
ObjHolder kotlinValueHolder;
|
||||||
|
ObjHeader* kotlinValue = Kotlin_List_get(list, index, kotlinValueHolder.slot());
|
||||||
|
if (kotlinValue == nullptr) return [NSNull null];
|
||||||
|
|
||||||
|
return Kotlin_ObjCExport_refToObjC(kotlinValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
-(NSUInteger)count {
|
||||||
|
return Kotlin_List_getSize(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end;
|
||||||
|
|
||||||
|
@interface NSBlock <NSObject>
|
||||||
|
@end;
|
||||||
|
|
||||||
|
@interface NSBlock (NSBlockToKotlin) <ConvertibleToKotlin>
|
||||||
|
@end;
|
||||||
|
|
||||||
|
struct Block_descriptor_1;
|
||||||
|
|
||||||
|
// Based on https://clang.llvm.org/docs/Block-ABI-Apple.html and libclosure source.
|
||||||
|
struct Block_literal_1 {
|
||||||
|
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
|
||||||
|
int flags;
|
||||||
|
int reserved;
|
||||||
|
void (*invoke)(void *, ...);
|
||||||
|
struct Block_descriptor_1 *descriptor; // IFF (1<<25)
|
||||||
|
// Or:
|
||||||
|
// struct Block_descriptor_1_without_helpers* descriptor // if hasn't (1<<25).
|
||||||
|
|
||||||
|
// imported variables
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Block_descriptor_1 {
|
||||||
|
unsigned long int reserved; // NULL
|
||||||
|
unsigned long int size; // sizeof(struct Block_literal_1)
|
||||||
|
|
||||||
|
// optional helper functions
|
||||||
|
void (*copy_helper)(void *dst, void *src);
|
||||||
|
void (*dispose_helper)(void *src);
|
||||||
|
// required ABI.2010.3.16
|
||||||
|
const char *signature; // IFF (1<<30)
|
||||||
|
const void* layout; // IFF (1<<31)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Block_descriptor_1_without_helpers {
|
||||||
|
unsigned long int reserved; // NULL
|
||||||
|
unsigned long int size; // sizeof(struct Block_literal_1)
|
||||||
|
|
||||||
|
// required ABI.2010.3.16
|
||||||
|
const char *signature; // IFF (1<<30)
|
||||||
|
const void* layout; // IFF (1<<31)
|
||||||
|
};
|
||||||
|
|
||||||
|
static const char* getBlockEncoding(id block) {
|
||||||
|
Block_literal_1* literal = reinterpret_cast<Block_literal_1*>(block);
|
||||||
|
|
||||||
|
int flags = literal->flags;
|
||||||
|
RuntimeAssert((flags & (1 << 30)) != 0, "block has no signature stored");
|
||||||
|
return (flags & (1 << 25)) != 0 ?
|
||||||
|
literal->descriptor->signature :
|
||||||
|
reinterpret_cast<struct Block_descriptor_1_without_helpers*>(literal->descriptor)->signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: replaced by compiler in appropriate compilation modes.
|
||||||
|
__attribute__((weak)) const TypeInfo * const * Kotlin_ObjCExport_functionAdaptersToBlock = nullptr;
|
||||||
|
|
||||||
|
static const TypeInfo* getFunctionTypeInfoForBlock(id block) {
|
||||||
|
const char* encoding = getBlockEncoding(block);
|
||||||
|
|
||||||
|
// TODO: optimize:
|
||||||
|
NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:encoding];
|
||||||
|
int parameterCount = signature.numberOfArguments - 1; // 1 for the block itself.
|
||||||
|
|
||||||
|
if (parameterCount > 22) {
|
||||||
|
[NSException raise:NSGenericException format:@"Blocks with %d (>22) parameters aren't supported", parameterCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 1; i <= parameterCount; ++i) {
|
||||||
|
const char* argEncoding = [signature getArgumentTypeAtIndex:i];
|
||||||
|
if (argEncoding[0] != '@') {
|
||||||
|
[NSException raise:NSGenericException
|
||||||
|
format:@"Blocks with non-reference-typed arguments aren't supported (%s)", argEncoding];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* returnTypeEncoding = signature.methodReturnType;
|
||||||
|
if (returnTypeEncoding[0] != '@') {
|
||||||
|
[NSException raise:NSGenericException
|
||||||
|
format:@"Blocks with non-reference-typed return value aren't supported (%s)", returnTypeEncoding];
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: support Unit-as-void.
|
||||||
|
|
||||||
|
return Kotlin_ObjCExport_functionAdaptersToBlock[parameterCount];
|
||||||
|
}
|
||||||
|
|
||||||
|
@implementation NSBlock (NSBlockToKotlin)
|
||||||
|
-(ObjHeader*)toKotlin:(ObjHeader**)OBJ_RESULT {
|
||||||
|
|
||||||
|
const TypeInfo* typeInfo = getFunctionTypeInfoForBlock(self);
|
||||||
|
RETURN_RESULT_OF(AllocInstanceWithAssociatedObject, typeInfo, objc_retainBlock(self));
|
||||||
|
// TODO: call (Any) constructor?
|
||||||
|
}
|
||||||
|
|
||||||
|
-(void)releaseAsAssociatedObject {
|
||||||
|
objc_release(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
@end;
|
||||||
|
|
||||||
|
extern "C" id Kotlin_Interop_CreateNSArrayFromKList(ObjHeader* obj) {
|
||||||
|
return [KListNSArray createWithKList:obj];
|
||||||
|
}
|
||||||
|
|
||||||
|
static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj);
|
||||||
|
|
||||||
|
extern "C" id Kotlin_ObjCExport_refToObjC(ObjHeader* obj) {
|
||||||
|
if (obj == nullptr) return nullptr;
|
||||||
|
|
||||||
|
if (HasAssociatedObjectField(obj)) {
|
||||||
|
id associatedObject = Kotlin_ObjCExport_GetAssociatedObject(obj);
|
||||||
|
if (associatedObject != nullptr) {
|
||||||
|
return objc_retainAutoreleaseReturnValue(associatedObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
convertReferenceToObjC converter = obj->type_info()->writableInfo_->objCExport.convert;
|
||||||
|
if (converter != nullptr) {
|
||||||
|
return converter(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Kotlin_ObjCExport_refToObjC_slowpath(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" OBJ_GETTER(Kotlin_ObjCExport_refFromObjC, id obj) {
|
||||||
|
if (obj == nullptr) RETURN_OBJ(nullptr);
|
||||||
|
id convertible = (id<ConvertibleToKotlin>)obj;
|
||||||
|
return [convertible toKotlin:OBJ_RESULT];
|
||||||
|
}
|
||||||
|
|
||||||
|
static Class getOrCreateClass(const TypeInfo* typeInfo);
|
||||||
|
|
||||||
|
static id convertKotlinObject(ObjHeader* obj) {
|
||||||
|
Class clazz = obj->type_info()->writableInfo_->objCExport.objCClass;
|
||||||
|
RuntimeAssert(clazz != nullptr, "");
|
||||||
|
return [clazz createWrapper:obj];
|
||||||
|
}
|
||||||
|
|
||||||
|
static id Kotlin_ObjCExport_refToObjC_slowpath(ObjHeader* obj) {
|
||||||
|
const TypeInfo* typeInfo = obj->type_info();
|
||||||
|
convertReferenceToObjC converter = nullptr;
|
||||||
|
|
||||||
|
for (int i = 0; i < typeInfo->implementedInterfacesCount_; ++i) {
|
||||||
|
converter = typeInfo->implementedInterfaces_[i]->writableInfo_->objCExport.convert;
|
||||||
|
if (converter != nullptr) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (converter == nullptr) {
|
||||||
|
getOrCreateClass(typeInfo);
|
||||||
|
converter = &convertKotlinObject;
|
||||||
|
}
|
||||||
|
|
||||||
|
typeInfo->writableInfo_->objCExport.convert = converter;
|
||||||
|
|
||||||
|
return converter(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" KInt Kotlin_NSArrayList_getSize(ObjHeader* obj) {
|
||||||
|
NSArray* array = (NSArray*) Kotlin_ObjCExport_GetAssociatedObject(obj);
|
||||||
|
return [array count];
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" OBJ_GETTER(Kotlin_NSArrayList_getElement, ObjHeader* obj, KInt index) {
|
||||||
|
NSArray* array = (NSArray*) Kotlin_ObjCExport_GetAssociatedObject(obj);
|
||||||
|
id element = [array objectAtIndex:index];
|
||||||
|
if (element == NSNull.null) RETURN_OBJ(nullptr);
|
||||||
|
RETURN_RESULT_OF(Kotlin_ObjCExport_refFromObjC, element);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const TypeInfo* createTypeInfo(
|
||||||
|
const TypeInfo* superType,
|
||||||
|
const KStdVector<const TypeInfo*>& superInterfaces,
|
||||||
|
const KStdVector<const void*>& vtable,
|
||||||
|
const KStdVector<MethodTableRecord>& methodTable
|
||||||
|
) {
|
||||||
|
TypeInfo* result = (TypeInfo*)konanAllocMemory(sizeof(TypeInfo) + vtable.size() * sizeof(void*));
|
||||||
|
|
||||||
|
MakeGlobalHash(nullptr, 0, &result->name_);
|
||||||
|
result->instanceSize_ = superType->instanceSize_;
|
||||||
|
result->superType_ = superType;
|
||||||
|
result->objOffsets_ = superType->objOffsets_;
|
||||||
|
result->objOffsetsCount_ = superType->objOffsetsCount_;
|
||||||
|
|
||||||
|
KStdVector<const TypeInfo*> implementedInterfaces(
|
||||||
|
superType->implementedInterfaces_, superType->implementedInterfaces_ + superType->implementedInterfacesCount_
|
||||||
|
);
|
||||||
|
KStdUnorderedSet<const TypeInfo*> usedInterfaces(implementedInterfaces.begin(), implementedInterfaces.end());
|
||||||
|
|
||||||
|
for (const TypeInfo* interface : superInterfaces) {
|
||||||
|
if (usedInterfaces.insert(interface).second) {
|
||||||
|
implementedInterfaces.push_back(interface);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const TypeInfo** implementedInterfaces_ = konanAllocArray<const TypeInfo*>(implementedInterfaces.size());
|
||||||
|
for (int i = 0; i < implementedInterfaces.size(); ++i) {
|
||||||
|
implementedInterfaces_[i] = implementedInterfaces[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
result->implementedInterfaces_ = implementedInterfaces_;
|
||||||
|
result->implementedInterfacesCount_ = implementedInterfaces.size();
|
||||||
|
|
||||||
|
MethodTableRecord* openMethods_ = konanAllocArray<MethodTableRecord>(methodTable.size());
|
||||||
|
for (int i = 0; i < methodTable.size(); ++i) openMethods_[i] = methodTable[i];
|
||||||
|
|
||||||
|
result->openMethods_ = openMethods_;
|
||||||
|
result->openMethodsCount_ = methodTable.size();
|
||||||
|
|
||||||
|
result->fields_ = nullptr;
|
||||||
|
result->fieldsCount_ = 0;
|
||||||
|
|
||||||
|
result->packageName_ = nullptr;
|
||||||
|
result->relativeName_ = nullptr; // TODO: add some info.
|
||||||
|
result->writableInfo_ = (WritableTypeInfo*)konanAllocMemory(sizeof(WritableTypeInfo));
|
||||||
|
|
||||||
|
for (int i = 0; i < vtable.size(); ++i) result->vtable()[i] = vtable[i];
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void addDefinedSelectors(Class clazz, KStdUnorderedSet<SEL>& result) {
|
||||||
|
unsigned int objcMethodCount;
|
||||||
|
Method* objcMethods = class_copyMethodList(clazz, &objcMethodCount);
|
||||||
|
|
||||||
|
for (int i = 0; i < objcMethodCount; ++i) {
|
||||||
|
result.insert(method_getName(objcMethods[i]));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (objcMethods != nullptr) free(objcMethods);
|
||||||
|
}
|
||||||
|
|
||||||
|
static KStdVector<const TypeInfo*> getProtocolsAsInterfaces(Class clazz) {
|
||||||
|
KStdVector<const TypeInfo*> result;
|
||||||
|
|
||||||
|
unsigned int protocolCount;
|
||||||
|
Protocol** protocols = class_copyProtocolList(clazz, &protocolCount);
|
||||||
|
if (protocols == nullptr) return result;
|
||||||
|
|
||||||
|
for (int i = 0; i < protocolCount; ++i) {
|
||||||
|
Protocol* protocol = protocols[i];
|
||||||
|
const ObjCTypeAdapter* typeAdapter = findProtocolAdapter(protocol);
|
||||||
|
if (typeAdapter != nullptr) result.push_back(typeAdapter->kotlinTypeInfo);
|
||||||
|
}
|
||||||
|
if (protocols != nullptr) free(protocols);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const TypeInfo* getMostSpecificKotlinClass(const TypeInfo* typeInfo) {
|
||||||
|
const TypeInfo* result = typeInfo;
|
||||||
|
while (getTypeAdapter(result) == nullptr) {
|
||||||
|
result = result->superType_;
|
||||||
|
RuntimeAssert(result != nullptr, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void insertOrReplace(KStdVector<MethodTableRecord>& methodTable, MethodNameHash nameSignature, void* entryPoint) {
|
||||||
|
MethodTableRecord record = {nameSignature, entryPoint};
|
||||||
|
|
||||||
|
for (int i = methodTable.size() - 1; i >= 0; --i) {
|
||||||
|
if (methodTable[i].nameSignature_ == nameSignature) {
|
||||||
|
methodTable[i].methodEntryPoint_ = entryPoint;
|
||||||
|
return;
|
||||||
|
} else if (methodTable[i].nameSignature_ < nameSignature) {
|
||||||
|
methodTable.insert(methodTable.begin() + (i + 1), record);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
methodTable.insert(methodTable.begin(), record);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const TypeInfo* createTypeInfo(Class clazz, const TypeInfo* superType) {
|
||||||
|
Class superClass = class_getSuperclass(clazz);
|
||||||
|
|
||||||
|
KStdUnorderedSet<SEL> definedSelectors;
|
||||||
|
addDefinedSelectors(clazz, definedSelectors);
|
||||||
|
|
||||||
|
const ObjCTypeAdapter* superTypeAdapter = getTypeAdapter(superType);
|
||||||
|
|
||||||
|
const void * const * superVtable = nullptr;
|
||||||
|
int superVtableSize = getTypeAdapter(getMostSpecificKotlinClass(superType))->kotlinVtableSize;
|
||||||
|
|
||||||
|
const MethodTableRecord* superMethodTable = nullptr;
|
||||||
|
int superMethodTableSize = 0;
|
||||||
|
|
||||||
|
if (superTypeAdapter != nullptr) {
|
||||||
|
// Then super class is Kotlin class.
|
||||||
|
|
||||||
|
// And if it is abstract, then vtable and method table are not available from TypeInfo,
|
||||||
|
// but present in type adapter instead:
|
||||||
|
superVtable = superTypeAdapter->kotlinVtable;
|
||||||
|
superMethodTable = superTypeAdapter->kotlinMethodTable;
|
||||||
|
superMethodTableSize = superTypeAdapter->kotlinMethodTableSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (superVtable == nullptr) superVtable = superType->vtable();
|
||||||
|
if (superMethodTable == nullptr) {
|
||||||
|
superMethodTable = superType->openMethods_;
|
||||||
|
superMethodTableSize = superType->openMethodsCount_;
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdVector<const void*> vtable(
|
||||||
|
superVtable,
|
||||||
|
superVtable + superVtableSize
|
||||||
|
);
|
||||||
|
|
||||||
|
KStdVector<MethodTableRecord> methodTable(
|
||||||
|
superMethodTable, superMethodTable + superMethodTableSize
|
||||||
|
);
|
||||||
|
|
||||||
|
KStdVector<const TypeInfo*> addedInterfaces = getProtocolsAsInterfaces(clazz);
|
||||||
|
|
||||||
|
KStdVector<const TypeInfo*> supers(
|
||||||
|
superType->implementedInterfaces_,
|
||||||
|
superType->implementedInterfaces_ + superType->implementedInterfacesCount_
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const TypeInfo* t = superType; t != nullptr; t = t->superType_) {
|
||||||
|
supers.push_back(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const TypeInfo* t : supers) {
|
||||||
|
const ObjCTypeAdapter* typeAdapter = getTypeAdapter(t);
|
||||||
|
if (typeAdapter == nullptr) continue;
|
||||||
|
|
||||||
|
for (int i = 0; i < typeAdapter->reverseAdapterNum; ++i) {
|
||||||
|
const KotlinToObjCMethodAdapter* adapter = &typeAdapter->reverseAdapters[i];
|
||||||
|
if (definedSelectors.find(sel_registerName(adapter->selector)) == definedSelectors.end()) continue;
|
||||||
|
|
||||||
|
if (adapter->kotlinImpl == nullptr) {
|
||||||
|
[NSException raise:NSGenericException
|
||||||
|
format:@"[%s %s] can't be implemented",
|
||||||
|
class_getName(clazz), adapter->selector];
|
||||||
|
// TODO: describe the reasons
|
||||||
|
}
|
||||||
|
|
||||||
|
insertOrReplace(methodTable, adapter->nameSignature, const_cast<void*>(adapter->kotlinImpl));
|
||||||
|
if (adapter->vtableIndex != -1) vtable[adapter->vtableIndex] = adapter->kotlinImpl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const TypeInfo* typeInfo : addedInterfaces) {
|
||||||
|
const ObjCTypeAdapter* typeAdapter = getTypeAdapter(typeInfo);
|
||||||
|
if (typeAdapter == nullptr) continue;
|
||||||
|
|
||||||
|
for (int i = 0; i < typeAdapter->reverseAdapterNum; ++i) {
|
||||||
|
const KotlinToObjCMethodAdapter* adapter = &typeAdapter->reverseAdapters[i];
|
||||||
|
if (adapter->kotlinImpl == nullptr) {
|
||||||
|
[NSException raise:NSGenericException
|
||||||
|
format:@"[%s %s] can't be implemented",
|
||||||
|
class_getName(clazz), adapter->selector];
|
||||||
|
}
|
||||||
|
|
||||||
|
insertOrReplace(methodTable, adapter->nameSignature, const_cast<void*>(adapter->kotlinImpl));
|
||||||
|
RuntimeAssert(adapter->vtableIndex == -1, "");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: consider forbidding the class being abstract.
|
||||||
|
|
||||||
|
const TypeInfo* result = createTypeInfo(superType, addedInterfaces, vtable, methodTable);
|
||||||
|
|
||||||
|
// TODO: it will probably never be requested, since such a class can't be instantiated in Kotlin.
|
||||||
|
result->writableInfo_->objCExport.objCClass = clazz;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SimpleMutex typeInfoCreationMutex;
|
||||||
|
|
||||||
|
static const TypeInfo* getOrCreateTypeInfo(Class clazz) {
|
||||||
|
const TypeInfo* result = getAssociatedTypeInfo(clazz);
|
||||||
|
if (result != nullptr) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
Class superClass = class_getSuperclass(clazz);
|
||||||
|
|
||||||
|
const TypeInfo* superType = superClass == nullptr ?
|
||||||
|
theAnyTypeInfo :
|
||||||
|
getOrCreateTypeInfo(superClass);
|
||||||
|
|
||||||
|
LockGuard<SimpleMutex> lockGuard(typeInfoCreationMutex);
|
||||||
|
|
||||||
|
result = getAssociatedTypeInfo(clazz); // double-checking.
|
||||||
|
if (result == nullptr) {
|
||||||
|
result = createTypeInfo(clazz, superType);
|
||||||
|
setAssociatedTypeInfo(clazz, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static SimpleMutex classCreationMutex;
|
||||||
|
static int anonymousClassNextId = 0;
|
||||||
|
|
||||||
|
static void addVirtualAdapters(Class clazz, const ObjCTypeAdapter* typeAdapter) {
|
||||||
|
for (int i = 0; i < typeAdapter->virtualAdapterNum; ++i) {
|
||||||
|
const ObjCToKotlinMethodAdapter* adapter = typeAdapter->virtualAdapters + i;
|
||||||
|
SEL selector = sel_registerName(adapter->selector);
|
||||||
|
|
||||||
|
class_addMethod(clazz, selector, adapter->imp, adapter->encoding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Class createClass(const TypeInfo* typeInfo, Class superClass) {
|
||||||
|
RuntimeAssert(typeInfo->superType_ != nullptr, "");
|
||||||
|
|
||||||
|
char classNameBuffer[64];
|
||||||
|
snprintf(classNameBuffer, sizeof(classNameBuffer), "kobjcc%d", anonymousClassNextId++);
|
||||||
|
const char* className = classNameBuffer;
|
||||||
|
|
||||||
|
Class result = objc_allocateClassPair(superClass, className, 0);
|
||||||
|
RuntimeAssert(result != nullptr, "");
|
||||||
|
|
||||||
|
// TODO: optimize by adding virtual adapters only for overridden methods.
|
||||||
|
|
||||||
|
if (getTypeAdapter(typeInfo->superType_) == nullptr) {
|
||||||
|
// class for super type is also synthesized, no need to add class adapters;
|
||||||
|
} else {
|
||||||
|
for (const TypeInfo* superType = typeInfo->superType_; superType != nullptr; superType = superType->superType_) {
|
||||||
|
const ObjCTypeAdapter* typeAdapter = getTypeAdapter(superType);
|
||||||
|
if (typeAdapter != nullptr) {
|
||||||
|
addVirtualAdapters(result, typeAdapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KStdUnorderedSet<const TypeInfo*> superImplementedInterfaces(
|
||||||
|
typeInfo->superType_->implementedInterfaces_,
|
||||||
|
typeInfo->superType_->implementedInterfaces_ + typeInfo->superType_->implementedInterfacesCount_
|
||||||
|
);
|
||||||
|
|
||||||
|
for (int i = 0; i < typeInfo->implementedInterfacesCount_; ++i) {
|
||||||
|
const TypeInfo* interface = typeInfo->implementedInterfaces_[i];
|
||||||
|
if (superImplementedInterfaces.find(interface) == superImplementedInterfaces.end()) {
|
||||||
|
const ObjCTypeAdapter* typeAdapter = getTypeAdapter(interface);
|
||||||
|
if (typeAdapter != nullptr) {
|
||||||
|
addVirtualAdapters(result, typeAdapter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
objc_registerClassPair(result);
|
||||||
|
|
||||||
|
// TODO: it will probably never be requested, since such a class can't be instantiated in Objective-C.
|
||||||
|
setAssociatedTypeInfo(result, typeInfo);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Class getOrCreateClass(const TypeInfo* typeInfo) {
|
||||||
|
Class result = typeInfo->writableInfo_->objCExport.objCClass;
|
||||||
|
if (result != nullptr) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ObjCTypeAdapter* typeAdapter = getTypeAdapter(typeInfo);
|
||||||
|
if (typeAdapter != nullptr) {
|
||||||
|
result = objc_getClass(typeAdapter->objCName);
|
||||||
|
RuntimeAssert(result != nullptr, "");
|
||||||
|
typeInfo->writableInfo_->objCExport.objCClass = result;
|
||||||
|
} else {
|
||||||
|
Class superClass = getOrCreateClass(typeInfo->superType_);
|
||||||
|
|
||||||
|
LockGuard<SimpleMutex> lockGuard(classCreationMutex); // Note: non-recursive
|
||||||
|
|
||||||
|
result = typeInfo->writableInfo_->objCExport.objCClass; // double-checking.
|
||||||
|
if (result == nullptr) {
|
||||||
|
result = createClass(typeInfo, superClass);
|
||||||
|
RuntimeAssert(result != nullptr, "");
|
||||||
|
typeInfo->writableInfo_->objCExport.objCClass = result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" void Kotlin_ObjCExport_AbstractMethodCalled(id self, SEL selector) {
|
||||||
|
[NSException raise:NSGenericException
|
||||||
|
format:@"[%s %s] is abstract",
|
||||||
|
class_getName(object_getClass(self)), sel_getName(selector)];
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // KONAN_OBJC_INTEROP
|
||||||
|
|
||||||
|
extern "C" KInt Kotlin_NSArrayList_getSize(ObjHeader* obj) {
|
||||||
|
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C" OBJ_GETTER(Kotlin_NSArrayList_getElement, ObjHeader* obj, KInt index) {
|
||||||
|
RuntimeAssert(false, "Objective-C interop is disabled");
|
||||||
|
RETURN_OBJ(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // KONAN_OBJC_INTEROP
|
||||||
@@ -43,6 +43,8 @@ static inline void SetKotlinTypeInfo(Class clazz, const TypeInfo* typeInfo) {
|
|||||||
GetKotlinClassData(clazz)->typeInfo = typeInfo;
|
GetKotlinClassData(clazz)->typeInfo = typeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) RUNTIME_NOTHROW;
|
||||||
|
|
||||||
const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) {
|
const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) {
|
||||||
void* objcPtr = *reinterpret_cast<void * const *>(obj + 1); // TODO: use more reliable layout description
|
void* objcPtr = *reinterpret_cast<void * const *>(obj + 1); // TODO: use more reliable layout description
|
||||||
Class clazz = object_getClass(reinterpret_cast<id>(objcPtr));
|
Class clazz = object_getClass(reinterpret_cast<id>(objcPtr));
|
||||||
@@ -85,9 +87,9 @@ static void AddDeallocMethod(Class clazz) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ObjCMethodDescription {
|
struct ObjCMethodDescription {
|
||||||
|
void* (*imp)(void*, void*, ...);
|
||||||
const char* selector;
|
const char* selector;
|
||||||
const char* encoding;
|
const char* encoding;
|
||||||
const void* imp;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
struct KotlinObjCClassInfo {
|
struct KotlinObjCClassInfo {
|
||||||
|
|||||||
@@ -44,15 +44,15 @@ namespace {
|
|||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
|
||||||
NSString* Kotlin_Interop_CreateNSStringFromKString(const ArrayHeader* str) {
|
id Kotlin_Interop_CreateNSStringFromKString(const ObjHeader* str) {
|
||||||
if (str == nullptr) {
|
if (str == nullptr) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
const KChar* utf16Chars = CharArrayAddressOfElementAt(str, 0);
|
const KChar* utf16Chars = CharArrayAddressOfElementAt(str->array(), 0);
|
||||||
|
|
||||||
NSString* result = [[[getNSStringClass() alloc] initWithBytes:utf16Chars
|
NSString* result = [[[getNSStringClass() alloc] initWithBytes:utf16Chars
|
||||||
length:str->count_*sizeof(KChar)
|
length:str->array()->count_*sizeof(KChar)
|
||||||
encoding:NSUTF16LittleEndianStringEncoding] autorelease];
|
encoding:NSUTF16LittleEndianStringEncoding] autorelease];
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -84,6 +84,15 @@ void Kotlin_initRuntimeIfNeeded() {
|
|||||||
runtimeState = InitRuntime();
|
runtimeState = InitRuntime();
|
||||||
// Register runtime deinit function at thread cleanup.
|
// Register runtime deinit function at thread cleanup.
|
||||||
konan::onThreadExit(Kotlin_deinitRuntimeIfNeeded);
|
konan::onThreadExit(Kotlin_deinitRuntimeIfNeeded);
|
||||||
|
#ifndef KONAN_WASM
|
||||||
|
// `onThreadExit` doesn't work on main thread, use `atexit`:
|
||||||
|
|
||||||
|
static bool deinitScheduledAtexit = false;
|
||||||
|
if (!deinitScheduledAtexit) {
|
||||||
|
deinitScheduledAtexit = true; // Having data race is OK here.
|
||||||
|
::atexit(Kotlin_deinitRuntimeIfNeeded);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -22,6 +22,10 @@
|
|||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Names.h"
|
#include "Names.h"
|
||||||
|
|
||||||
|
#if KONAN_TYPE_INFO_HAS_WRITABLE_PART
|
||||||
|
struct WritableTypeInfo;
|
||||||
|
#endif
|
||||||
|
|
||||||
struct ObjHeader;
|
struct ObjHeader;
|
||||||
|
|
||||||
// An element of sorted by hash in-place array representing methods.
|
// An element of sorted by hash in-place array representing methods.
|
||||||
@@ -67,8 +71,21 @@ struct TypeInfo {
|
|||||||
// or `null` if the class is anonymous.
|
// or `null` if the class is anonymous.
|
||||||
ObjHeader* relativeName_;
|
ObjHeader* relativeName_;
|
||||||
|
|
||||||
|
#if KONAN_TYPE_INFO_HAS_WRITABLE_PART
|
||||||
|
WritableTypeInfo* writableInfo_;
|
||||||
|
#endif
|
||||||
|
|
||||||
// vtable starts just after declared contents of the TypeInfo:
|
// vtable starts just after declared contents of the TypeInfo:
|
||||||
// void* const vtable_[];
|
// void* const vtable_[];
|
||||||
|
#ifdef __cplusplus
|
||||||
|
inline const void* const * vtable() const {
|
||||||
|
return reinterpret_cast<void * const *>(this + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline const void** vtable() {
|
||||||
|
return reinterpret_cast<const void**>(this + 1);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class BooleanBox(val value: Boolean) : Comparable<Boolean> {
|
|||||||
override fun compareTo(other: Boolean): Int = value.compareTo(other)
|
override fun compareTo(other: Boolean): Int = value.compareTo(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_boxBoolean")
|
||||||
fun boxBoolean(value: Boolean) = BooleanBox(value)
|
fun boxBoolean(value: Boolean) = BooleanBox(value)
|
||||||
|
|
||||||
class CharBox(val value: Char) : Comparable<Char> {
|
class CharBox(val value: Char) : Comparable<Char> {
|
||||||
@@ -52,6 +53,7 @@ class CharBox(val value: Char) : Comparable<Char> {
|
|||||||
override fun compareTo(other: Char): Int = value.compareTo(other)
|
override fun compareTo(other: Char): Int = value.compareTo(other)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_boxChar")
|
||||||
fun boxChar(value: Char) = CharBox(value)
|
fun boxChar(value: Char) = CharBox(value)
|
||||||
|
|
||||||
class ByteBox(val value: Byte) : Number(), Comparable<Byte> {
|
class ByteBox(val value: Byte) : Number(), Comparable<Byte> {
|
||||||
@@ -78,6 +80,7 @@ class ByteBox(val value: Byte) : Number(), Comparable<Byte> {
|
|||||||
override fun toDouble() = value.toDouble()
|
override fun toDouble() = value.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_boxByte")
|
||||||
fun boxByte(value: Byte) = ByteBox(value)
|
fun boxByte(value: Byte) = ByteBox(value)
|
||||||
|
|
||||||
class ShortBox(val value: Short) : Number(), Comparable<Short> {
|
class ShortBox(val value: Short) : Number(), Comparable<Short> {
|
||||||
@@ -104,6 +107,7 @@ class ShortBox(val value: Short) : Number(), Comparable<Short> {
|
|||||||
override fun toDouble() = value.toDouble()
|
override fun toDouble() = value.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_boxShort")
|
||||||
fun boxShort(value: Short) = ShortBox(value)
|
fun boxShort(value: Short) = ShortBox(value)
|
||||||
|
|
||||||
class IntBox(val value: Int) : Number(), Comparable<Int> {
|
class IntBox(val value: Int) : Number(), Comparable<Int> {
|
||||||
@@ -130,6 +134,7 @@ class IntBox(val value: Int) : Number(), Comparable<Int> {
|
|||||||
override fun toDouble() = value.toDouble()
|
override fun toDouble() = value.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_boxInt")
|
||||||
fun boxInt(value: Int) = IntBox(value)
|
fun boxInt(value: Int) = IntBox(value)
|
||||||
|
|
||||||
class LongBox(val value: Long) : Number(), Comparable<Long> {
|
class LongBox(val value: Long) : Number(), Comparable<Long> {
|
||||||
@@ -156,6 +161,7 @@ class LongBox(val value: Long) : Number(), Comparable<Long> {
|
|||||||
override fun toDouble() = value.toDouble()
|
override fun toDouble() = value.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_boxLong")
|
||||||
fun boxLong(value: Long) = LongBox(value)
|
fun boxLong(value: Long) = LongBox(value)
|
||||||
|
|
||||||
class FloatBox(val value: Float) : Number(), Comparable<Float> {
|
class FloatBox(val value: Float) : Number(), Comparable<Float> {
|
||||||
@@ -182,6 +188,7 @@ class FloatBox(val value: Float) : Number(), Comparable<Float> {
|
|||||||
override fun toDouble() = value.toDouble()
|
override fun toDouble() = value.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_boxFloat")
|
||||||
fun boxFloat(value: Float) = FloatBox(value)
|
fun boxFloat(value: Float) = FloatBox(value)
|
||||||
|
|
||||||
class DoubleBox(val value: Double) : Number(), Comparable<Double> {
|
class DoubleBox(val value: Double) : Number(), Comparable<Double> {
|
||||||
@@ -208,4 +215,5 @@ class DoubleBox(val value: Double) : Number(), Comparable<Double> {
|
|||||||
override fun toDouble() = value.toDouble()
|
override fun toDouble() = value.toDouble()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ExportForCppRuntime("Kotlin_boxDouble")
|
||||||
fun boxDouble(value: Double) = DoubleBox(value)
|
fun boxDouble(value: Double) = DoubleBox(value)
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ data class File constructor(internal val javaPath: Path) {
|
|||||||
fun appendBytes(bytes: ByteArray)
|
fun appendBytes(bytes: ByteArray)
|
||||||
= Files.write(javaPath, bytes, StandardOpenOption.APPEND)
|
= Files.write(javaPath, bytes, StandardOpenOption.APPEND)
|
||||||
|
|
||||||
|
fun writeLines(lines: Iterable<String>) {
|
||||||
|
Files.write(javaPath, lines)
|
||||||
|
}
|
||||||
|
|
||||||
fun forEachLine(action: (String) -> Unit) {
|
fun forEachLine(action: (String) -> Unit) {
|
||||||
Files.lines(javaPath).use { lines ->
|
Files.lines(javaPath).use { lines ->
|
||||||
lines.forEach { action(it) }
|
lines.forEach { action(it) }
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ enum class Architecture(val bitness: Int) {
|
|||||||
enum class KonanTarget(val family: Family, val architecture: Architecture, val detailedName: String, var enabled: Boolean = false) {
|
enum class KonanTarget(val family: Family, val architecture: Architecture, val detailedName: String, var enabled: Boolean = false) {
|
||||||
ANDROID_ARM32( Family.ANDROID, Architecture.ARM32, "android_arm32"),
|
ANDROID_ARM32( Family.ANDROID, Architecture.ARM32, "android_arm32"),
|
||||||
ANDROID_ARM64( Family.ANDROID, Architecture.ARM64, "android_arm64"),
|
ANDROID_ARM64( Family.ANDROID, Architecture.ARM64, "android_arm64"),
|
||||||
IPHONE( Family.IOS, Architecture.ARM32, "ios"),
|
IPHONE( Family.IOS, Architecture.ARM64, "ios"),
|
||||||
IPHONE_SIM( Family.IOS, Architecture.X64, "ios_sim"),
|
IPHONE_SIM( Family.IOS, Architecture.X64, "ios_sim"),
|
||||||
LINUX( Family.LINUX, Architecture.X64, "linux"),
|
LINUX( Family.LINUX, Architecture.X64, "linux"),
|
||||||
MINGW( Family.WINDOWS, Architecture.X64, "mingw"),
|
MINGW( Family.WINDOWS, Architecture.X64, "mingw"),
|
||||||
@@ -63,6 +63,9 @@ enum class CompilerOutputKind {
|
|||||||
DYNAMIC {
|
DYNAMIC {
|
||||||
override fun suffix(target: KonanTarget?) = ".${target!!.family.dynamicSuffix}"
|
override fun suffix(target: KonanTarget?) = ".${target!!.family.dynamicSuffix}"
|
||||||
},
|
},
|
||||||
|
FRAMEWORK {
|
||||||
|
override fun suffix(target: KonanTarget?): String = ".framework"
|
||||||
|
},
|
||||||
LIBRARY {
|
LIBRARY {
|
||||||
override fun suffix(target: KonanTarget?) = ".klib"
|
override fun suffix(target: KonanTarget?) = ".klib"
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user