Improve ObjC block dynamic conversion generation when using cache (#3507)

Generate conversions along with stdlib to guarantee all synthesized
function classes get covered.
This commit is contained in:
SvyatoslavScherbina
2019-10-30 13:42:06 +03:00
committed by GitHub
parent 41f8d5fc07
commit 091de1b8bc
4 changed files with 106 additions and 79 deletions
@@ -13,7 +13,7 @@ import org.jetbrains.kotlin.descriptors.konan.CurrentKlibModuleOrigin
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.util.OperatorNameConventions
internal fun ObjCExportCodeGenerator.generateBlockToKotlinFunctionConverter(
internal fun ObjCExportCodeGeneratorBase.generateBlockToKotlinFunctionConverter(
bridge: BlockPointerBridge
): LLVMValueRef {
val irInterface = symbols.functionN(bridge.numberOfParameters).owner
@@ -133,9 +133,7 @@ private val BlockPointerBridge.blockInvokeLlvmType: LLVMTypeRef
private val BlockPointerBridge.nameSuffix: String
get() = numberOfParameters.toString() + if (returnsVoid) "V" else ""
internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjCExportCodeGenerator) {
private val codegen get() = objCExportCodeGenerator.codegen
internal class BlockAdapterToFunctionGenerator(private val codegen: CodeGenerator) {
private val kRefSharedHolderType = LLVMGetTypeByName(codegen.runtime.llvmModule, "class.KRefSharedHolder")!!
private val blockLiteralType = structType(
@@ -220,7 +218,7 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC
private fun ObjCExportCodeGenerator.generateInvoke(bridge: BlockPointerBridge): ConstPointer {
private fun ObjCExportCodeGeneratorBase.generateInvoke(bridge: BlockPointerBridge): ConstPointer {
val numberOfParameters = bridge.numberOfParameters
val result = generateFunction(codegen, bridge.blockInvokeLlvmType, "invokeBlock${bridge.nameSuffix}") {
@@ -255,7 +253,7 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC
return constPointer(result)
}
fun ObjCExportCodeGenerator.generateConvertFunctionToBlock(bridge: BlockPointerBridge): LLVMValueRef {
fun ObjCExportCodeGeneratorBase.generateConvertFunctionToBlock(bridge: BlockPointerBridge): LLVMValueRef {
val blockDescriptor = codegen.staticData.placeGlobal(
"",
generateDescriptorForBlockAdapterToFunction(bridge)
@@ -311,7 +309,7 @@ internal class BlockAdapterToFunctionGenerator(val objCExportCodeGenerator: ObjC
}
}
private val ObjCExportCodeGenerator.retainBlock get() = context.llvm.externalFunction(
private val ObjCExportCodeGeneratorBase.retainBlock get() = context.llvm.externalFunction(
"objc_retainBlock",
functionType(int8TypePtr, false, int8TypePtr),
CurrentKlibModuleOrigin
@@ -34,14 +34,8 @@ internal fun TypeBridge.makeNothing() = when (this) {
is ValueTypeBridge -> LLVMConstNull(this.objCValueType.llvmType)!!
}
internal class ObjCExportCodeGenerator(
codegen: CodeGenerator,
val namer: ObjCExportNamer,
val mapper: ObjCExportMapper
) : ObjCCodeGenerator(codegen) {
internal open class ObjCExportCodeGeneratorBase(codegen: CodeGenerator) : ObjCCodeGenerator(codegen) {
val symbols get() = context.ir.symbols
val runtime get() = codegen.runtime
val staticData get() = codegen.staticData
@@ -57,10 +51,6 @@ internal class ObjCExportCodeGenerator(
}
}
val referencedSelectors = mutableMapOf<String, MethodBridge>()
val externalGlobalInitializers = mutableMapOf<LLVMValueRef, ConstValue>()
// 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
@@ -77,6 +67,55 @@ internal class ObjCExportCodeGenerator(
return call(function, args, resultLifetime, ExceptionHandler.Caller)
}
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)
private val blockToKotlinFunctionConverterCache = mutableMapOf<BlockPointerBridge, LLVMValueRef>()
internal fun blockToKotlinFunctionConverter(bridge: BlockPointerBridge): LLVMValueRef =
blockToKotlinFunctionConverterCache.getOrPut(bridge) {
generateBlockToKotlinFunctionConverter(bridge)
}
private val blockAdapterToFunctionGenerator = BlockAdapterToFunctionGenerator(this.codegen)
private val functionToBlockConverterCache = mutableMapOf<BlockPointerBridge, LLVMValueRef>()
internal fun kotlinFunctionToBlockConverter(bridge: BlockPointerBridge): LLVMValueRef =
functionToBlockConverterCache.getOrPut(bridge) {
blockAdapterToFunctionGenerator.run {
generateConvertFunctionToBlock(bridge)
}
}
}
internal class ObjCExportBlockCodeGenerator(codegen: CodeGenerator) : ObjCExportCodeGeneratorBase(codegen) {
init {
// Must be generated along with stdlib:
// 1. Enumerates [BuiltInFictitiousFunctionIrClassFactory] built classes, which may be incomplete otherwise.
// 2. Modifies stdlib global initializers.
// 3. Defines runtime-declared globals.
require(context.producedLlvmModuleContainsStdlib)
}
fun generate() {
emitFunctionConverters()
emitBlockToKotlinFunctionConverters()
}
}
internal class ObjCExportCodeGenerator(
codegen: CodeGenerator,
val namer: ObjCExportNamer,
val mapper: ObjCExportMapper
) : ObjCExportCodeGeneratorBase(codegen) {
val referencedSelectors = mutableMapOf<String, MethodBridge>()
val externalGlobalInitializers = mutableMapOf<LLVMValueRef, ConstValue>()
fun FunctionGenerationContext.genSendMessage(
returnType: LLVMTypeRef,
receiver: LLVMValueRef,
@@ -119,12 +158,6 @@ internal class ObjCExportCodeGenerator(
ObjCValueType.FLOAT, ObjCValueType.DOUBLE, ObjCValueType.POINTER -> 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)
private fun FunctionGenerationContext.objCBlockPointerToKotlin(
value: LLVMValueRef,
typeBridge: BlockPointerBridge,
@@ -135,29 +168,11 @@ internal class ObjCExportCodeGenerator(
resultLifetime
)
private val blockToKotlinFunctionConverterCache = mutableMapOf<BlockPointerBridge, LLVMValueRef>()
internal fun blockToKotlinFunctionConverter(bridge: BlockPointerBridge): LLVMValueRef =
blockToKotlinFunctionConverterCache.getOrPut(bridge) {
generateBlockToKotlinFunctionConverter(bridge)
}
private fun FunctionGenerationContext.kotlinFunctionToObjCBlockPointer(
typeBridge: BlockPointerBridge,
value: LLVMValueRef
) = callFromBridge(kotlinFunctionToBlockConverter(typeBridge), listOf(value))
private val blockAdapterToFunctionGenerator = BlockAdapterToFunctionGenerator(this)
private val functionToBlockConverterCache = mutableMapOf<BlockPointerBridge, LLVMValueRef>()
internal fun kotlinFunctionToBlockConverter(bridge: BlockPointerBridge): LLVMValueRef =
functionToBlockConverterCache.getOrPut(bridge) {
blockAdapterToFunctionGenerator.run {
generateConvertFunctionToBlock(bridge)
}
}
fun FunctionGenerationContext.kotlinToObjC(
value: LLVMValueRef,
typeBridge: TypeBridge
@@ -459,6 +474,36 @@ private fun ObjCExportCodeGenerator.setObjCExportTypeInfo(
objCClass: ConstPointer? = null,
typeAdapter: ConstPointer? = null
) {
val writableTypeInfoValue = buildWritableTypeInfoValue(
converter = converter,
objCClass = objCClass,
typeAdapter = typeAdapter
)
if (codegen.isExternal(irClass)) {
// Note: this global replaces the external one with common linkage.
replaceExternalWeakOrCommonGlobal(
irClass.writableTypeInfoSymbolName,
writableTypeInfoValue,
irClass.llvmSymbolOrigin
)
} else {
setOwnWritableTypeInfo(irClass, writableTypeInfoValue)
}
}
private fun ObjCExportCodeGeneratorBase.setOwnWritableTypeInfo(irClass: IrClass, writableTypeInfoValue: Struct) {
require(!codegen.isExternal(irClass))
val writeableTypeInfoGlobal = context.llvmDeclarations.forClass(irClass).writableTypeInfoGlobal!!
writeableTypeInfoGlobal.setLinkage(LLVMLinkage.LLVMExternalLinkage)
writeableTypeInfoGlobal.setInitializer(writableTypeInfoValue)
}
private fun ObjCExportCodeGeneratorBase.buildWritableTypeInfoValue(
converter: ConstPointer? = null,
objCClass: ConstPointer? = null,
typeAdapter: ConstPointer? = null
): Struct {
if (converter != null) {
assert(converter.llvmType == pointerType(functionType(int8TypePtr, false, codegen.kObjHeaderPtr)))
}
@@ -470,26 +515,13 @@ private fun ObjCExportCodeGenerator.setObjCExportTypeInfo(
)
val writableTypeInfoType = runtime.writableTypeInfoType!!
val writableTypeInfoValue = Struct(writableTypeInfoType, objCExportAddition)
if (codegen.isExternal(irClass)) {
// Note: this global replaces the external one with common linkage.
replaceExternalWeakOrCommonGlobal(
irClass.writableTypeInfoSymbolName,
writableTypeInfoValue,
irClass.llvmSymbolOrigin
)
} else {
context.llvmDeclarations.forClass(irClass).writableTypeInfoGlobal!!.also {
it.setLinkage(LLVMLinkage.LLVMExternalLinkage)
}.setInitializer(writableTypeInfoValue)
}
return Struct(writableTypeInfoType, objCExportAddition)
}
private val ObjCExportCodeGenerator.kotlinToObjCFunctionType: LLVMTypeRef
get() = functionType(int8TypePtr, false, codegen.kObjHeaderPtr)
private val ObjCExportCodeGenerator.objCToKotlinFunctionType: LLVMTypeRef
private val ObjCExportCodeGeneratorBase.objCToKotlinFunctionType: LLVMTypeRef
get() = functionType(codegen.kObjHeaderPtr, false, int8TypePtr, codegen.kObjHeaderPtrPtr)
private fun ObjCExportCodeGenerator.emitBoxConverters() {
@@ -534,14 +566,18 @@ private fun ObjCExportCodeGenerator.emitBoxConverter(
setObjCExportTypeInfo(boxClass, constPointer(converter))
}
private fun ObjCExportCodeGenerator.emitFunctionConverters() {
private fun ObjCExportBlockCodeGenerator.emitFunctionConverters() {
require(context.producedLlvmModuleContainsStdlib)
context.ir.symbols.functionIrClassFactory.builtFunctionNClasses.forEach { functionClass ->
val converter = kotlinFunctionToBlockConverter(BlockPointerBridge(functionClass.arity, returnsVoid = false))
setObjCExportTypeInfo(functionClass.irClass, constPointer(converter))
val writableTypeInfoValue = buildWritableTypeInfoValue(converter = constPointer(converter))
setOwnWritableTypeInfo(functionClass.irClass, writableTypeInfoValue)
}
}
private fun ObjCExportCodeGenerator.emitBlockToKotlinFunctionConverters() {
private fun ObjCExportBlockCodeGenerator.emitBlockToKotlinFunctionConverters() {
require(context.producedLlvmModuleContainsStdlib)
val functionClassesByArity =
context.ir.symbols.functionIrClassFactory.builtFunctionNClasses.associateBy { it.arity }
@@ -563,17 +599,9 @@ private fun ObjCExportCodeGenerator.emitBlockToKotlinFunctionConverters() {
converters
).pointer.getElementPtr(0)
// Note: replacing weak globals defined in runtime.
replaceExternalWeakOrCommonGlobal(
"Kotlin_ObjCExport_blockToFunctionConverters",
ptr,
context.standardLlvmSymbolsOrigin
)
replaceExternalWeakOrCommonGlobal(
"Kotlin_ObjCExport_blockToFunctionConverters_size",
Int32(count),
context.standardLlvmSymbolsOrigin
)
// Note: defining globals declared in runtime.
staticData.placeGlobal("Kotlin_ObjCExport_blockToFunctionConverters", ptr, isExported = true)
staticData.placeGlobal("Kotlin_ObjCExport_blockToFunctionConverters_size", Int32(count), isExported = true)
}
private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() {
@@ -585,10 +613,6 @@ private fun ObjCExportCodeGenerator.emitSpecialClassesConvertions() {
emitCollectionConverters()
emitBoxConverters()
emitFunctionConverters()
emitBlockToKotlinFunctionConverters()
}
private fun ObjCExportCodeGenerator.emitCollectionConverters() {
@@ -1470,4 +1494,4 @@ internal fun Context.is64BitLong(): Boolean = when (val target = this.config.tar
is KonanTarget.ZEPHYR,
KonanTarget.IOS_ARM32 -> false
KonanTarget.WATCHOS_X64 -> error("Target $target is not supported.")
}
}
@@ -11,6 +11,7 @@ import org.jetbrains.kotlin.backend.konan.descriptors.getPackageFragments
import org.jetbrains.kotlin.backend.konan.descriptors.isInterface
import org.jetbrains.kotlin.backend.konan.getExportedDependencies
import org.jetbrains.kotlin.backend.konan.llvm.CodeGenerator
import org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportBlockCodeGenerator
import org.jetbrains.kotlin.backend.konan.llvm.objcexport.ObjCExportCodeGenerator
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -75,6 +76,10 @@ internal class ObjCExport(val context: Context, symbolTable: SymbolTable) {
internal fun generate(codegen: CodeGenerator) {
if (!target.family.isAppleFamily) return
if (context.producedLlvmModuleContainsStdlib) {
ObjCExportBlockCodeGenerator(codegen).generate()
}
if (!context.config.produce.isFinalBinary) return // TODO: emit RTTI to the same modules as classes belong to.
val mapper = exportedInterface?.mapper ?: ObjCExportMapper()
+3 -3
View File
@@ -439,9 +439,9 @@ static const char* getBlockEncoding(id block) {
reinterpret_cast<struct Block_descriptor_1_without_helpers*>(literal->descriptor)->signature;
}
// Note: replaced by compiler in appropriate compilation modes.
__attribute__((weak)) convertReferenceFromObjC* Kotlin_ObjCExport_blockToFunctionConverters = nullptr;
__attribute__((weak)) int Kotlin_ObjCExport_blockToFunctionConverters_size = 0;
// Note: defined by compiler.
extern "C" convertReferenceFromObjC* Kotlin_ObjCExport_blockToFunctionConverters;
extern "C" int Kotlin_ObjCExport_blockToFunctionConverters_size;
extern "C" id objc_retainBlock(id self);