From d745e801351f099a6bf817aa7a047a674c898190 Mon Sep 17 00:00:00 2001 From: Svyatoslav Scherbina Date: Wed, 22 Nov 2017 17:30:31 +0300 Subject: [PATCH] Implement `-produce framework` option It compiles Kotlin code to Objective-C framework (also importable to Swift) --- .../kotlinx/cinterop/ObjectiveCExportImpl.kt | 41 + .../jetbrains/kotlin/backend/konan/Boxing.kt | 52 + .../kotlin/backend/konan/CompilerOutput.kt | 6 + .../kotlin/backend/konan/KonanConfig.kt | 2 +- .../kotlin/backend/konan/KonanPhases.kt | 6 +- .../kotlin/backend/konan/LinkStage.kt | 13 +- .../konan/descriptors/ClassVtablesBuilder.kt | 2 - .../backend/konan/llvm/BinaryInterface.kt | 6 + .../backend/konan/llvm/CodeGenerator.kt | 53 + .../kotlin/backend/konan/llvm/ContextUtils.kt | 33 + .../kotlin/backend/konan/llvm/IrToBitcode.kt | 42 +- .../llvm/KotlinObjCClassInfoGenerator.kt | 6 +- .../backend/konan/llvm/LlvmDeclarations.kt | 17 +- .../kotlin/backend/konan/llvm/LlvmUtils.kt | 26 +- .../backend/konan/llvm/RTTIGenerator.kt | 132 ++- .../kotlin/backend/konan/llvm/Runtime.kt | 8 +- .../kotlin/backend/konan/llvm/StaticData.kt | 18 + .../konan/llvm/objc/ObjCCodeGenerator.kt | 51 + .../konan/llvm/objc/ObjCDataGenerator.kt | 248 +++++ .../llvm/objcexport/BlockPointerSupport.kt | 229 +++++ .../objcexport/ObjCExportCodeGenerator.kt | 791 +++++++++++++++ .../kotlin/backend/konan/lower/Autoboxing.kt | 53 +- .../backend/konan/objcexport/ObjCExport.kt | 61 ++ .../objcexport/ObjCExportHeaderGenerator.kt | 587 +++++++++++ .../konan/objcexport/ObjCExportMapper.kt | 190 ++++ .../konan/objcexport/ObjCExportNamer.kt | 365 +++++++ backend.native/konan.properties | 6 +- runtime/src/main/cpp/Alloc.h | 5 + runtime/src/main/cpp/Common.h | 5 + runtime/src/main/cpp/Memory.cpp | 49 +- runtime/src/main/cpp/Memory.h | 13 + runtime/src/main/cpp/MemoryPrivate.hpp | 25 + runtime/src/main/cpp/ObjCExport.mm | 941 ++++++++++++++++++ runtime/src/main/cpp/ObjCInterop.cpp | 4 +- runtime/src/main/cpp/ObjCInteropUtils.mm | 6 +- runtime/src/main/cpp/Runtime.cpp | 9 + runtime/src/main/cpp/TypeInfo.h | 17 + .../src/main/kotlin/konan/internal/Boxing.kt | 8 + .../jetbrains/kotlin/konan/file/NoJavaUtil.kt | 4 + .../kotlin/konan/target/KonanTarget.kt | 5 +- 40 files changed, 3999 insertions(+), 136 deletions(-) create mode 100644 Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCExportImpl.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt create mode 100644 backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt create mode 100644 runtime/src/main/cpp/MemoryPrivate.hpp create mode 100644 runtime/src/main/cpp/ObjCExport.mm diff --git a/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCExportImpl.kt b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCExportImpl.kt new file mode 100644 index 00000000000..76793d05708 --- /dev/null +++ b/Interop/Runtime/src/native/kotlin/kotlinx/cinterop/ObjectiveCExportImpl.kt @@ -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 { + + // 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 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt new file mode 100644 index 00000000000..bc69442c691 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/Boxing.kt @@ -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 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt index cb80748d097..557e8a866fc 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt @@ -22,6 +22,11 @@ import org.jetbrains.kotlin.backend.konan.llvm.parseBitcodeFile import org.jetbrains.kotlin.backend.konan.util.getValueOrNull 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) { val llvmModule = context.llvmModule!! @@ -29,6 +34,7 @@ internal fun produceOutput(context: Context) { when (config.get(KonanConfigKeys.PRODUCE)) { CompilerOutputKind.DYNAMIC, + CompilerOutputKind.FRAMEWORK, CompilerOutputKind.PROGRAM -> { val program = context.config.outputName val output = "$program.kt.bc" diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt index e38139e0cba..babefb2da96 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt @@ -65,7 +65,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration 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) val outputName = configuration.get(KonanConfigKeys.OUTPUT)?.removeSuffixIfPresent(suffix) ?: produce.name.toLowerCase() val outputFile = outputName.suffixIfNot(produce.suffix(targetManager.target)) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt index ea1f78722ef..d0a4b97197f 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanPhases.kt @@ -87,10 +87,8 @@ object KonanPhases { // Don't serialize anything to a final executable. KonanPhase.SERIALIZER.enabled = - (get(PRODUCE) == CompilerOutputKind.LIBRARY) - KonanPhase.LINK_STAGE.enabled = - (get(PRODUCE) == CompilerOutputKind.PROGRAM || - get(PRODUCE) == CompilerOutputKind.DYNAMIC) + (config.produce == CompilerOutputKind.LIBRARY) + KonanPhase.LINK_STAGE.enabled = config.produce.isNativeBinary KonanPhase.TEST_PROCESSOR.enabled = getBoolean(GENERATE_TEST_RUNNER) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt index f1bf77b42e0..8a4c21811cf 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/LinkStage.kt @@ -239,7 +239,8 @@ internal class LinkStage(val context: Context) { private val optimize = config.get(KonanConfigKeys.OPTIMIZATION) ?: 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 emitted = context.bitcodeFileName 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. // And just do the same on Linux. private val entryPointSelector: List - get() = if (nomain) emptyList() else platform.entrySelector + get() = if (nomain || dynamic) emptyList() else platform.entrySelector private fun link(objectFiles: List, includedBinaries: List, libraryProvidedLinkerFlags: List): 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) + platform.targetLibffi + diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt index 4ec722d7260..b780264bfa6 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassVtablesBuilder.kt @@ -130,8 +130,6 @@ internal class ClassVtablesBuilder(val classDescriptor: ClassDescriptor, val con } val methodTableEntries: List by lazy { - assert(!classDescriptor.isAbstract()) - classDescriptor.sortedContributedMethods .flatMap { method -> method.allOverriddenDescriptors.map { OverriddenFunctionDescriptor(method, it) } } .filter { it.canBeCalledVirtually } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt index be4a95a36e2..f9a8fa05255 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/BinaryInterface.kt @@ -242,6 +242,12 @@ internal val ClassDescriptor.typeInfoSymbolName: String 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 ClassDescriptor.objectInstanceFieldSymbolName: String diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt index d14092333e4..cf55a5fdd7d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt @@ -20,6 +20,8 @@ package org.jetbrains.kotlin.backend.konan.llvm import kotlinx.cinterop.* import llvm.* 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.konan.target.KonanTarget import org.jetbrains.kotlin.descriptors.ClassDescriptor @@ -76,6 +78,17 @@ internal inline fun generateFunction(codegen: CodeGenerator, function: LLVMVa 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 generateFunctionBody(functionGenerationContext: FunctionGenerationContext, code: FunctionGenerationContext.(FunctionGenerationContext) -> R) { functionGenerationContext.prologue() functionGenerationContext.code(functionGenerationContext) @@ -184,6 +197,8 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, return res } + fun param(index: Int): LLVMValueRef = LLVMGetParam(this.function, index)!! + fun load(value: LLVMValueRef, name: String = ""): LLVMValueRef { val result = LLVMBuildLoad(builder, value, name)!! // 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 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 = LLVMBuildZExt(builder, arg, type, "")!! @@ -401,6 +417,8 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, fun gep(base: LLVMValueRef, index: LLVMValueRef, name: String = ""): LLVMValueRef { 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 { val personalityFunction = LLVMConstBitCast(context.llvm.gxxPersonalityFunction, int8TypePtr) @@ -462,6 +480,41 @@ internal class FunctionGenerationContext(val function: LLVMValueRef, 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() { if (!context.shouldContainDebugInfo()) return if (!currentPositionHolder.isAfterTerminator) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt index 1ab45ae8f00..fb3e605ebb4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt @@ -24,6 +24,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.DeserializedKonanModule import org.jetbrains.kotlin.backend.konan.descriptors.LlvmSymbolOrigin import org.jetbrains.kotlin.konan.target.KonanTarget 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.impl.LibraryReaderImpl 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.types.KotlinType import org.jetbrains.kotlin.types.TypeUtils +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty internal sealed class SlotType { // 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 throwExceptionFunction = importRtFunction("ThrowException") val appendToInitalizersTail = importRtFunction("AppendToInitializersTail") + val initRuntimeIfNeeded = importRtFunction("Kotlin_initRuntimeIfNeeded") val createKotlinObjCClass by lazy { importRtFunction("CreateKotlinObjCClass") } val getObjCKotlinTypeInfo by lazy { importRtFunction("GetObjCKotlinTypeInfo") } 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) { KonanTarget.MINGW -> "__gxx_personality_seh0" else -> "__gxx_personality_v0" @@ -400,6 +420,19 @@ internal class Llvm(val context: Context, val llvmModule: LLVMModuleRef) { val memsetFunction = importMemset() val usedFunctions = mutableListOf() + val usedGlobals = mutableListOf() + val compilerUsedGlobals = mutableListOf() val staticInitializers = mutableListOf() val fileInitializers = mutableListOf() + + private object lazyRtFunction { + operator fun provideDelegate( + thisRef: Llvm, property: KProperty<*> + ) = object : ReadOnlyProperty { + + val value by lazy { thisRef.importRtFunction(property.name) } + + override fun getValue(thisRef: Llvm, property: KProperty<*>): LLVMValueRef = value + } + } } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt index 258c10973b9..d1adea8a496 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt @@ -25,6 +25,7 @@ import org.jetbrains.kotlin.backend.konan.* import org.jetbrains.kotlin.backend.konan.descriptors.* import org.jetbrains.kotlin.backend.konan.ir.* 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.util.getValueOrNull import org.jetbrains.kotlin.builtins.KotlinBuiltIns @@ -291,7 +292,12 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map, 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 } @@ -2396,13 +2374,13 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map) { + private fun appendLlvmUsed(name: String, args: List) { if (args.isEmpty()) return memScoped { val argsCasted = args.map { it -> constPointer(it).bitcast(int8TypePtr) } val llvmUsedGlobal = - context.llvm.staticData.placeGlobalArray("llvm.used", int8TypePtr, argsCasted) + context.llvm.staticData.placeGlobalArray(name, int8TypePtr, argsCasted) LLVMSetLinkage(llvmUsedGlobal.llvmGlobal, LLVMLinkage.LLVMAppendingLinkage); LLVMSetSection(llvmUsedGlobal.llvmGlobal, "llvm.metadata"); diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt index a147c220ed3..862cf14e600 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinObjCClassInfoGenerator.kt @@ -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( val selector: String, val encoding: String, val impFunction: LLVMValueRef ) : Struct( runtime.objCMethodDescription, + constPointer(impFunction).bitcast(impType), staticData.cStringLiteral(selector), - staticData.cStringLiteral(encoding), - constPointer(impFunction).bitcast(int8TypePtr) + staticData.cStringLiteral(encoding) ) private fun generateMethodDesc(info: ObjCMethodInfo) = ObjCMethodDesc( diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt index 9f07d872d5b..d46f1d63ad4 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmDeclarations.kt @@ -75,6 +75,7 @@ internal class ClassLlvmDeclarations( val bodyType: LLVMTypeRef, val fields: List, // TODO: it is not an LLVM declaration. val typeInfoGlobal: StaticData.Global, + val writableTypeInfoGlobal: StaticData.Global?, val typeInfo: ConstPointer, val singletonDeclarations: SingletonLlvmDeclarations?, val objCDeclarations: KotlinObjCClassLlvmDeclarations?) @@ -289,7 +290,21 @@ private class DeclarationsGeneratorVisitor(override val context: Context) : 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) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt index 429b4d24476..6eba59b409d 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt @@ -59,13 +59,29 @@ internal class ConstArray(val elemType: LLVMTypeRef?, val elements: List) : ConstValue { +internal open class Struct(val type: LLVMTypeRef?, val elements: List) : 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) - 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 { @@ -106,6 +122,7 @@ internal fun constValue(value: LLVMValueRef) = object : ConstValue { internal val int1Type = LLVMInt1Type()!! internal val int8Type = LLVMInt8Type()!! +internal val int16Type = LLVMInt16Type()!! internal val int32Type = LLVMInt32Type()!! internal val int8TypePtr = pointerType(int8Type) @@ -219,6 +236,9 @@ internal fun functionType(returnType: LLVMTypeRef, isVarArg: Boolean = false, va if (isVarArg) 1 else 0 )!! +internal fun functionType(returnType: LLVMTypeRef, isVarArg: Boolean = false, paramTypes: List) = + functionType(returnType, isVarArg, *paramTypes.toTypedArray()) + fun llvm2string(value: LLVMValueRef?): String { if (value == null) return "" diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt index 6c55773ba61..e5190c61399 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/RTTIGenerator.kt @@ -35,7 +35,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { private inner class FieldTableRecord(val nameSignature: LocalHash, val fieldOffset: Int) : 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) 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 fieldsCount: Int, val packageName: String?, - val relativeName: String?) : + val relativeName: String?, + val writableTypeInfo: ConstPointer?) : Struct( runtime.typeInfoType, @@ -71,7 +72,9 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { Int32(fieldsCount), kotlinStringLiteral(packageName), - kotlinStringLiteral(relativeName) + kotlinStringLiteral(relativeName), + + *listOfNotNull(writableTypeInfo).toTypedArray() ) 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()) { emptyList() } else { - val functionNames = mutableMapOf() - 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 } + methodTableRecords(classDesc) } val methodsPtr = staticData.placeGlobalConstArray("kmethods:$className", @@ -181,7 +173,8 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { methodsPtr, methods.size, fieldsPtr, if (classDesc.isInterface) -1 else fields.size, reflectionInfo.packageName, - reflectionInfo.relativeName + reflectionInfo.relativeName, + llvmDeclarations.writableTypeInfoGlobal?.pointer ) val typeInfoGlobal = llvmDeclarations.typeInfoGlobal @@ -189,16 +182,7 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { val typeInfoGlobalValue = if (!classDesc.typeInfoHasVtableAttached) { typeInfo } else { - // TODO: compile-time resolution limits binary compatibility - val vtableEntries = context.getVtableBuilder(classDesc).vtableEntries.map { - val implementation = it.implementation - if (implementation.isExternalObjCClassMethod()) { - NullPointer(int8Type) - } else { - implementation.entryPointAddress - } - } - val vtable = ConstArray(int8TypePtr, vtableEntries) + val vtable = vtable(classDesc) Struct(typeInfo, vtable) } @@ -208,6 +192,102 @@ internal class RTTIGenerator(override val context: Context) : ContextUtils { 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 { + val functionNames = mutableMapOf() + 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 + ): 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) data class ReflectionInfo(val packageName: String?, val relativeName: String?) diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt index 25a81641e7e..4c25dd27c1c 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/Runtime.kt @@ -29,9 +29,11 @@ interface RuntimeAware { class Runtime(bitcodeFile: String) { 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 writableTypeInfoType = getStructTypeOrNull("WritableTypeInfo") val fieldTableRecordType = getStructType("FieldTableRecord") val methodTableRecordType = getStructType("MethodTableRecord") val globalHashType = getStructType("GlobalHash") @@ -58,6 +60,10 @@ class Runtime(bitcodeFile: String) { val kotlinObjCClassInfo by lazy { getStructType("KotlinObjCClassInfo") } 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 { diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt index 806da789ffe..1bd8a329b91 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt @@ -65,14 +65,32 @@ internal class StaticData(override val context: Context): ContextUtils { } } + val type get() = getGlobalType(this.llvmGlobal) + fun setInitializer(value: ConstValue) { LLVMSetInitializer(llvmGlobal, value.llvm) } + fun setZeroInitializer() { + LLVMSetInitializer(llvmGlobal, LLVMConstNull(this.type)!!) + } + fun setConstant(value: Boolean) { 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) } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt new file mode 100644 index 00000000000..835ec3699bb --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCCodeGenerator.kt @@ -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 +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt new file mode 100644 index 00000000000..2e719620356 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt @@ -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() + private val classRefs = mutableMapOf() + + 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() + + 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() + + 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() + + private fun addModuleClassList(elements: List, 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() + + 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) + } + } +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt new file mode 100644 index 00000000000..f6aef0a8e61 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt @@ -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() diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt new file mode 100644 index 00000000000..fdf652eef69 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt @@ -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, + 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, + topLevel: Map> + ) { + val objCTypeAdapters = mutableListOf() + + 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() + val placedInterfaceAdapters = mutableMapOf() + + 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, 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, + val objCName: String, + directAdapters: List, + virtualAdapters: List, + reverseAdapters: List + ) : 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() + + 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 +): 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() + + 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() + + 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(null) + val presentMethodTableBridges = mutableSetOf() + + 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.toMethods(): List = 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() + + 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 diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt index 3219d1ce01f..1aea9151e44 100644 --- a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/lower/Autoboxing.kt @@ -18,6 +18,7 @@ package org.jetbrains.kotlin.backend.konan.lower import org.jetbrains.kotlin.backend.common.AbstractValueUsageTransformer 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.konan.* 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.impl.IrCallImpl import org.jetbrains.kotlin.ir.expressions.impl.IrTypeOperatorCallImpl -import org.jetbrains.kotlin.ir.util.getPropertyGetter -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.util.* import org.jetbrains.kotlin.ir.visitors.transformChildrenVoid import org.jetbrains.kotlin.types.KotlinType 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 { - val actualValueType = actualType.correspondingValueType - val expectedValueType = expectedType.correspondingValueType + val conversion = symbols.getTypeConversion(actualType, expectedType) + return if (conversion == null) { + this + } else { + val parameter = conversion.descriptor.explicitParameters.single() + val argument = this.uncheckedCast(parameter.type) - return when { - actualValueType == expectedValueType -> this - - 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") + IrCallImpl(startOffset, endOffset, conversion).apply { + addArguments(listOf(parameter to argument)) + }.uncheckedCast(this.type) // Try not to bring new type incompatibilities. } } @@ -229,26 +220,4 @@ private class AutoboxingTransformer(val context: Context) : AbstractValueUsageTr private fun getBoxType(valueType: ValueType) = 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. - } - } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt new file mode 100644 index 00000000000..573606cf9e2 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExport.kt @@ -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) + } +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt new file mode 100644 index 00000000000..7d2b1015dc8 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportHeaderGenerator.kt @@ -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() + val topLevel = mutableMapOf>() + + private val kotlinAnyName = namer.kotlinAnyName + + private val stubs = mutableListOf() + private val classToName = mutableMapOf() + private val interfaceToName = mutableMapOf() + private val extensions = mutableMapOf>() + val extraClassesToTranslate = mutableSetOf() + + fun translateModule() { + // TODO: make the translation order stable + // to stabilize name mangling. + + val packageFragments = context.moduleDescriptor.getPackageFragments() + + packageFragments.forEach { packageFragment -> + packageFragment.getMemberScope().getContributedDescriptors() + .filterIsInstance() + .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() + .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) { + translateClass(classDescriptor) + + stubs.addBuiltBy { + +"@interface ${translateClassName(classDescriptor)} (Extensions)" + + translateMembers(declarations) + + +"@end;" + } + } + + private fun translateTopLevel(packageFqName: FqName, declarations: List) { + 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() + + 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() + .filter { mapper.shouldBeExposed(it) } + + translateMembers(members) + } + + private fun StubBuilder.translateMembers(members: List) { + // TODO: add some marks about modality. + + val methods = mutableListOf() + val properties = mutableListOf() + + 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>() + + 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>() + + 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() + 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() + 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 = mutableListOf().apply { + add("#import ") + add("#import ") + add("#import ") + add("#import ") + 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(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* ` + 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) + +private class StubBuilder { + private val lines = mutableListOf() + + 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.addBuiltBy(block: StubBuilder.() -> Unit) { + this.add(buildStub(block)) +} + +private fun getPackagesFqNames(module: ModuleDescriptor): Set { + val result = mutableSetOf() + + fun getSubPackages(fqName: FqName) { + result.add(fqName) + module.getSubPackagesOf(fqName) { true }.forEach { getSubPackages(it) } + } + + getSubPackages(FqName.ROOT) + return result +} + +private fun ModuleDescriptor.getPackageFragments(): List = + getPackagesFqNames(this).flatMap { + getPackage(it).fragments.filter { it.module == this } + } diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt new file mode 100644 index 00000000000..580166e0553 --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportMapper.kt @@ -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 +} + +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 = + 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 = + 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 = + 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, + 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() + + 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()}:" +} diff --git a/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt new file mode 100644 index 00000000000..2c4d96360ef --- /dev/null +++ b/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/objcexport/ObjCExportNamer.kt @@ -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() { + override fun conflict(first: FunctionDescriptor, second: FunctionDescriptor): Boolean = + !mapper.canHaveSameSelector(first, second) + } + + private val methodSwiftNames = object : Mapping() { + 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() { + override fun conflict(first: PropertyDescriptor, second: PropertyDescriptor): Boolean = + !mapper.canHaveSameName(first, second) + } + + private val classNames = object : Mapping() { + override fun conflict(first: Any, second: Any): Boolean = true + } + + private val protocolNames = object : Mapping() { + 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() { + private val elementToName = mutableMapOf() + private val nameToElements = mutableMapOf>() + + abstract fun conflict(first: T, second: T): Boolean + + fun getOrPut(element: T, nameCandidates: () -> Sequence): 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 + 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 +} diff --git a/backend.native/konan.properties b/backend.native/konan.properties index 0f7420039cc..9a1edc8169f 100644 --- a/backend.native/konan.properties +++ b/backend.native/konan.properties @@ -39,7 +39,7 @@ llvmLtoFlags.osx = llvmLtoOptFlags.osx = -O3 -function-sections llvmLtoNooptFlags.osx = -O1 llvmLtoDynamicFlags.osx = -relocation-model=pic -linkerKonanFlags.osx = -lc++ -lobjc +linkerKonanFlags.osx = -lc++ -lobjc -framework Foundation linkerOptimizationFlags.osx = -dead_strip linkerDebugFlags.osx = -S linkerDynamicFlags.osx = -S -dylib @@ -66,7 +66,7 @@ llvmLtoFlags.ios = llvmLtoOptFlags.ios = -O3 -function-sections linkerDebugFlags.ios = -S 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 osVersionMinFlagLd.ios = -iphoneos_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 = llvmLtoOptFlags.ios_sim = -O3 -function-sections 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 linkerDebugFlags.ios_sim = -S osVersionMinFlagLd.ios_sim = -ios_simulator_version_min diff --git a/runtime/src/main/cpp/Alloc.h b/runtime/src/main/cpp/Alloc.h index 1b73cbd94eb..fca43491c32 100644 --- a/runtime/src/main/cpp/Alloc.h +++ b/runtime/src/main/cpp/Alloc.h @@ -33,6 +33,11 @@ inline void konanFreeMemory(void* memory) { konan::free(memory); } +template +inline T* konanAllocArray(size_t length) { + return reinterpret_cast(konanAllocMemory(length * sizeof(T))); +} + template inline T* konanConstructInstance(A&& ...args) { return new (konanAllocMemory(sizeof(T))) T(::std::forward(args)...); diff --git a/runtime/src/main/cpp/Common.h b/runtime/src/main/cpp/Common.h index 9b40141461d..ed023c84941 100644 --- a/runtime/src/main/cpp/Common.h +++ b/runtime/src/main/cpp/Common.h @@ -30,4 +30,9 @@ #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 diff --git a/runtime/src/main/cpp/Memory.cpp b/runtime/src/main/cpp/Memory.cpp index 25923d944b8..3d302d45a39 100644 --- a/runtime/src/main/cpp/Memory.cpp +++ b/runtime/src/main/cpp/Memory.cpp @@ -27,6 +27,7 @@ #include "Assert.h" #include "Exceptions.h" #include "Memory.h" +#include "MemoryPrivate.hpp" #include "Natives.h" // 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; } -inline bool isPermanent(const ContainerHeader* header) { - return (header->refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_PERMANENT; -} - inline bool isArena(const ContainerHeader* header) { 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); } +#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? inline container_size_t objectSize(const ObjHeader* obj) { 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. ArrayDataSizeBytes(obj->array()) + sizeof(ArrayHeader) : - type_info->instanceSize_ + sizeof(ObjHeader); + type_info->instanceSize_ + sizeof(ObjHeader)); return alignUp(size, kObjectAlignment); } @@ -393,6 +397,7 @@ inline bool isRefCounted(KConstRef object) { extern "C" { void objc_release(void* ptr); +void Kotlin_ObjCExport_releaseReservedObjectTail(ObjHeader* obj); } inline void runDeallocationHooks(ObjHeader* obj) { @@ -400,6 +405,10 @@ inline void runDeallocationHooks(ObjHeader* obj) { if (obj->type_info() == theObjCPointerHolderTypeInfo) { void* objcPtr = *reinterpret_cast(obj + 1); // TODO: use more reliable layout description objc_release(objcPtr); + } else { + if (HasReservedObjectTail(obj)) { + Kotlin_ObjCExport_releaseReservedObjectTail(obj); + } } #endif } @@ -756,7 +765,7 @@ ContainerHeader* AllocContainer(size_t size) { // TODO: try to reuse elements of finalizer queue for new allocations, question // is how to get actual size of container. #endif - ContainerHeader* result = konanConstructSizedInstance(size); + ContainerHeader* result = konanConstructSizedInstance(alignUp(size, kObjectAlignment)); CONTAINER_ALLOC_EVENT(state, size, result); #if TRACE_MEMORY state->containers->insert(result); @@ -794,7 +803,7 @@ void FreeContainer(ContainerHeader* header) { void ObjectContainer::Init(const TypeInfo* type_info) { RuntimeAssert(type_info->instanceSize_ >= 0, "Must be an object"); uint32_t alloc_size = - sizeof(ContainerHeader) + sizeof(ObjHeader) + type_info->instanceSize_; + sizeof(ContainerHeader) + sizeof(ObjHeader) + type_info->instanceSize_ + kObjectReservedTailSize; header_ = AllocContainer(alloc_size); if (header_) { // 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"); uint32_t alloc_size = sizeof(ContainerHeader) + sizeof(ArrayHeader) - - type_info->instanceSize_ * elements; + type_info->instanceSize_ * elements + kObjectReservedTailSize; header_ = AllocContainer(alloc_size); RuntimeAssert(header_ != nullptr, "Cannot alloc memory"); if (header_) { @@ -893,7 +902,7 @@ ObjHeader** ArenaContainer::getSlot() { ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) { 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(place(size)); if (!result) { return nullptr; @@ -906,7 +915,7 @@ ObjHeader* ArenaContainer::PlaceObject(const TypeInfo* type_info) { ArrayHeader* ArenaContainer::PlaceArray(const TypeInfo* type_info, uint32_t count) { 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(place(size)); if (!result) { return nullptr; @@ -928,6 +937,14 @@ inline void ReleaseRef(const ObjHeader* object) { Release(object->container()); } +void AddRefFromAssociatedObject(const ObjHeader* object) { + AddRef(object); +} + +void ReleaseRefFromAssociatedObject(const ObjHeader* object) { + ReleaseRef(object); +} + extern "C" { MemoryState* InitMemory() { @@ -1036,6 +1053,16 @@ OBJ_GETTER(InitInstance, #endif } +bool HasReservedObjectTail(ObjHeader* obj) { + return kObjectReservedTailSize != 0 && !isPermanent(obj); +} + +void* GetReservedObjectTail(ObjHeader* obj) { + return reinterpret_cast( + reinterpret_cast(obj) + objectSize(obj) - kObjectReservedTailSize + ); +} + void SetRef(ObjHeader** location, const ObjHeader* object) { MEMORY_LOG("SetRef *%p: %p\n", location, object) *const_cast(location) = object; diff --git a/runtime/src/main/cpp/Memory.h b/runtime/src/main/cpp/Memory.h index 60a4e255398..2539323ca78 100644 --- a/runtime/src/main/cpp/Memory.h +++ b/runtime/src/main/cpp/Memory.h @@ -115,6 +115,10 @@ struct ContainerHeader { } }; +inline bool isPermanent(const ContainerHeader* header) { + return (header->refCount_ & CONTAINER_TAG_MASK) == CONTAINER_TAG_PERMANENT; +} + struct ArrayHeader; // Header of every object. @@ -153,6 +157,10 @@ struct ObjHeader { const ArrayHeader* array() const { return reinterpret_cast(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. struct ArrayHeader { const TypeInfo* type_info_; @@ -331,6 +339,11 @@ void DeinitInstanceBody(const TypeInfo* typeInfo, void* body); OBJ_GETTER(InitInstance, ObjHeader** location, const TypeInfo* type_info, 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. // diff --git a/runtime/src/main/cpp/MemoryPrivate.hpp b/runtime/src/main/cpp/MemoryPrivate.hpp new file mode 100644 index 00000000000..315a76612be --- /dev/null +++ b/runtime/src/main/cpp/MemoryPrivate.hpp @@ -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 diff --git a/runtime/src/main/cpp/ObjCExport.mm b/runtime/src/main/cpp/ObjCExport.mm new file mode 100644 index 00000000000..95e5792ac49 --- /dev/null +++ b/runtime/src/main/cpp/ObjCExport.mm @@ -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 +#import +#import +#import +#import +#import +#import +#import +#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(GetReservedObjectTail(obj)) = value; +} + +extern "C" inline id Kotlin_ObjCExport_GetAssociatedObject(ObjHeader* obj) { + return *reinterpret_cast(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 +@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) +@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) +@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) +@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) +@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 +@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 +@end; + +@interface NSBlock (NSBlockToKotlin) +@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); + + int flags = literal->flags; + RuntimeAssert((flags & (1 << 30)) != 0, "block has no signature stored"); + return (flags & (1 << 25)) != 0 ? + literal->descriptor->signature : + reinterpret_cast(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)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& superInterfaces, + const KStdVector& vtable, + const KStdVector& 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 implementedInterfaces( + superType->implementedInterfaces_, superType->implementedInterfaces_ + superType->implementedInterfacesCount_ + ); + KStdUnorderedSet usedInterfaces(implementedInterfaces.begin(), implementedInterfaces.end()); + + for (const TypeInfo* interface : superInterfaces) { + if (usedInterfaces.insert(interface).second) { + implementedInterfaces.push_back(interface); + } + } + + const TypeInfo** implementedInterfaces_ = konanAllocArray(implementedInterfaces.size()); + for (int i = 0; i < implementedInterfaces.size(); ++i) { + implementedInterfaces_[i] = implementedInterfaces[i]; + } + + result->implementedInterfaces_ = implementedInterfaces_; + result->implementedInterfacesCount_ = implementedInterfaces.size(); + + MethodTableRecord* openMethods_ = konanAllocArray(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& 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 getProtocolsAsInterfaces(Class clazz) { + KStdVector 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& 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 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 vtable( + superVtable, + superVtable + superVtableSize + ); + + KStdVector methodTable( + superMethodTable, superMethodTable + superMethodTableSize + ); + + KStdVector addedInterfaces = getProtocolsAsInterfaces(clazz); + + KStdVector 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(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(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 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 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 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 diff --git a/runtime/src/main/cpp/ObjCInterop.cpp b/runtime/src/main/cpp/ObjCInterop.cpp index ca85fd5def5..54cb40d1333 100644 --- a/runtime/src/main/cpp/ObjCInterop.cpp +++ b/runtime/src/main/cpp/ObjCInterop.cpp @@ -43,6 +43,8 @@ static inline void SetKotlinTypeInfo(Class clazz, const TypeInfo* typeInfo) { GetKotlinClassData(clazz)->typeInfo = typeInfo; } +const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) RUNTIME_NOTHROW; + const TypeInfo* GetObjCKotlinTypeInfo(const ObjHeader* obj) { void* objcPtr = *reinterpret_cast(obj + 1); // TODO: use more reliable layout description Class clazz = object_getClass(reinterpret_cast(objcPtr)); @@ -85,9 +87,9 @@ static void AddDeallocMethod(Class clazz) { } struct ObjCMethodDescription { + void* (*imp)(void*, void*, ...); const char* selector; const char* encoding; - const void* imp; }; struct KotlinObjCClassInfo { diff --git a/runtime/src/main/cpp/ObjCInteropUtils.mm b/runtime/src/main/cpp/ObjCInteropUtils.mm index 13b5652a1d8..cf6cd3a238b 100644 --- a/runtime/src/main/cpp/ObjCInteropUtils.mm +++ b/runtime/src/main/cpp/ObjCInteropUtils.mm @@ -44,15 +44,15 @@ namespace { extern "C" { -NSString* Kotlin_Interop_CreateNSStringFromKString(const ArrayHeader* str) { +id Kotlin_Interop_CreateNSStringFromKString(const ObjHeader* str) { if (str == nullptr) { return nullptr; } - const KChar* utf16Chars = CharArrayAddressOfElementAt(str, 0); + const KChar* utf16Chars = CharArrayAddressOfElementAt(str->array(), 0); NSString* result = [[[getNSStringClass() alloc] initWithBytes:utf16Chars - length:str->count_*sizeof(KChar) + length:str->array()->count_*sizeof(KChar) encoding:NSUTF16LittleEndianStringEncoding] autorelease]; return result; diff --git a/runtime/src/main/cpp/Runtime.cpp b/runtime/src/main/cpp/Runtime.cpp index 351a918f4ed..5f7541314eb 100644 --- a/runtime/src/main/cpp/Runtime.cpp +++ b/runtime/src/main/cpp/Runtime.cpp @@ -84,6 +84,15 @@ void Kotlin_initRuntimeIfNeeded() { runtimeState = InitRuntime(); // Register runtime deinit function at thread cleanup. 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 } } diff --git a/runtime/src/main/cpp/TypeInfo.h b/runtime/src/main/cpp/TypeInfo.h index 40d755f52d6..8684eca3c6b 100644 --- a/runtime/src/main/cpp/TypeInfo.h +++ b/runtime/src/main/cpp/TypeInfo.h @@ -22,6 +22,10 @@ #include "Common.h" #include "Names.h" +#if KONAN_TYPE_INFO_HAS_WRITABLE_PART +struct WritableTypeInfo; +#endif + struct ObjHeader; // An element of sorted by hash in-place array representing methods. @@ -67,8 +71,21 @@ struct TypeInfo { // or `null` if the class is anonymous. ObjHeader* relativeName_; +#if KONAN_TYPE_INFO_HAS_WRITABLE_PART + WritableTypeInfo* writableInfo_; +#endif + // vtable starts just after declared contents of the TypeInfo: // void* const vtable_[]; +#ifdef __cplusplus + inline const void* const * vtable() const { + return reinterpret_cast(this + 1); + } + + inline const void** vtable() { + return reinterpret_cast(this + 1); + } +#endif }; #ifdef __cplusplus diff --git a/runtime/src/main/kotlin/konan/internal/Boxing.kt b/runtime/src/main/kotlin/konan/internal/Boxing.kt index 41f055ef942..79b70f1a86f 100644 --- a/runtime/src/main/kotlin/konan/internal/Boxing.kt +++ b/runtime/src/main/kotlin/konan/internal/Boxing.kt @@ -34,6 +34,7 @@ class BooleanBox(val value: Boolean) : Comparable { override fun compareTo(other: Boolean): Int = value.compareTo(other) } +@ExportForCppRuntime("Kotlin_boxBoolean") fun boxBoolean(value: Boolean) = BooleanBox(value) class CharBox(val value: Char) : Comparable { @@ -52,6 +53,7 @@ class CharBox(val value: Char) : Comparable { override fun compareTo(other: Char): Int = value.compareTo(other) } +@ExportForCppRuntime("Kotlin_boxChar") fun boxChar(value: Char) = CharBox(value) class ByteBox(val value: Byte) : Number(), Comparable { @@ -78,6 +80,7 @@ class ByteBox(val value: Byte) : Number(), Comparable { override fun toDouble() = value.toDouble() } +@ExportForCppRuntime("Kotlin_boxByte") fun boxByte(value: Byte) = ByteBox(value) class ShortBox(val value: Short) : Number(), Comparable { @@ -104,6 +107,7 @@ class ShortBox(val value: Short) : Number(), Comparable { override fun toDouble() = value.toDouble() } +@ExportForCppRuntime("Kotlin_boxShort") fun boxShort(value: Short) = ShortBox(value) class IntBox(val value: Int) : Number(), Comparable { @@ -130,6 +134,7 @@ class IntBox(val value: Int) : Number(), Comparable { override fun toDouble() = value.toDouble() } +@ExportForCppRuntime("Kotlin_boxInt") fun boxInt(value: Int) = IntBox(value) class LongBox(val value: Long) : Number(), Comparable { @@ -156,6 +161,7 @@ class LongBox(val value: Long) : Number(), Comparable { override fun toDouble() = value.toDouble() } +@ExportForCppRuntime("Kotlin_boxLong") fun boxLong(value: Long) = LongBox(value) class FloatBox(val value: Float) : Number(), Comparable { @@ -182,6 +188,7 @@ class FloatBox(val value: Float) : Number(), Comparable { override fun toDouble() = value.toDouble() } +@ExportForCppRuntime("Kotlin_boxFloat") fun boxFloat(value: Float) = FloatBox(value) class DoubleBox(val value: Double) : Number(), Comparable { @@ -208,4 +215,5 @@ class DoubleBox(val value: Double) : Number(), Comparable { override fun toDouble() = value.toDouble() } +@ExportForCppRuntime("Kotlin_boxDouble") fun boxDouble(value: Double) = DoubleBox(value) diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/NoJavaUtil.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/NoJavaUtil.kt index 7f96601849c..19100f594b3 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/NoJavaUtil.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/file/NoJavaUtil.kt @@ -111,6 +111,10 @@ data class File constructor(internal val javaPath: Path) { fun appendBytes(bytes: ByteArray) = Files.write(javaPath, bytes, StandardOpenOption.APPEND) + fun writeLines(lines: Iterable) { + Files.write(javaPath, lines) + } + fun forEachLine(action: (String) -> Unit) { Files.lines(javaPath).use { lines -> lines.forEach { action(it) } diff --git a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTarget.kt b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTarget.kt index 04e69dfe08e..3ffc72925b0 100644 --- a/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTarget.kt +++ b/shared/src/main/kotlin/org/jetbrains/kotlin/konan/target/KonanTarget.kt @@ -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) { ANDROID_ARM32( Family.ANDROID, Architecture.ARM32, "android_arm32"), 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"), LINUX( Family.LINUX, Architecture.X64, "linux"), MINGW( Family.WINDOWS, Architecture.X64, "mingw"), @@ -63,6 +63,9 @@ enum class CompilerOutputKind { DYNAMIC { override fun suffix(target: KonanTarget?) = ".${target!!.family.dynamicSuffix}" }, + FRAMEWORK { + override fun suffix(target: KonanTarget?): String = ".framework" + }, LIBRARY { override fun suffix(target: KonanTarget?) = ".klib" },