[K/N] Support ObjCExport in dynamic driver

This commit is contained in:
Sergey Bogolepov
2022-12-09 13:28:09 +02:00
committed by Space Team
parent b271b409c7
commit 1137299497
4 changed files with 153 additions and 64 deletions
@@ -15,6 +15,7 @@ import org.jetbrains.kotlin.backend.common.serialization.KlibIrVersion
import org.jetbrains.kotlin.backend.konan.cexport.produceCAdapterBitcode
import org.jetbrains.kotlin.backend.konan.llvm.*
import org.jetbrains.kotlin.backend.konan.llvm.objc.patchObjCRuntimeModule
import org.jetbrains.kotlin.backend.konan.objcexport.createObjCFramework
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.konan.CURRENT
import org.jetbrains.kotlin.konan.CompilerVersion
@@ -203,6 +204,7 @@ internal fun linkBitcodeDependencies(generationState: NativeGenerationState) {
}
// TODO: Remove this function after dynamic driver is complete.
internal fun produceOutput(generationState: NativeGenerationState) {
val context = generationState.context
val config = context.config
@@ -25,6 +25,7 @@ internal class DynamicCompilerDriver : CompilerDriver() {
CompilerOutputKind.LIBRARY,
CompilerOutputKind.DYNAMIC_CACHE,
CompilerOutputKind.STATIC_CACHE,
CompilerOutputKind.FRAMEWORK,
)
}
@@ -36,7 +37,7 @@ internal class DynamicCompilerDriver : CompilerDriver() {
CompilerOutputKind.PROGRAM -> produceBinary(engine, config, environment)
CompilerOutputKind.DYNAMIC -> error("Dynamic compiler driver does not support `dynamic` output yet.")
CompilerOutputKind.STATIC -> error("Dynamic compiler driver does not support `static` output yet.")
CompilerOutputKind.FRAMEWORK -> error("Dynamic compiler driver does not support `framework` output yet.")
CompilerOutputKind.FRAMEWORK -> produceObjCFramework(engine, config, environment)
CompilerOutputKind.LIBRARY -> produceKlib(engine, config, environment)
CompilerOutputKind.BITCODE -> error("Dynamic compiler driver does not support `bitcode` output yet.")
CompilerOutputKind.DYNAMIC_CACHE -> produceBinary(engine, config, environment)
@@ -48,6 +49,29 @@ internal class DynamicCompilerDriver : CompilerDriver() {
}
}
/**
* Create an Objective-C framework which is a directory consisting of
* - Objective-C header
* - Info.plist
* - Binary (if -Xomit-framework-binary is not passed).
*/
private fun produceObjCFramework(engine: PhaseEngine<PhaseContext>, config: KonanConfig, environment: KotlinCoreEnvironment) {
val frontendOutput = engine.runFrontend(config, environment) ?: return
val objCExportedInterface = engine.runPhase(ProduceObjCExportInterfacePhase, frontendOutput)
engine.runPhase(CreateObjCFrameworkPhase, CreateObjCFrameworkInput(frontendOutput.moduleDescriptor, objCExportedInterface))
if (config.omitFrameworkBinary) {
return
}
val (psiToIrOutput, objCCodeSpec) = engine.runPsiToIr(frontendOutput, isProducingLibrary = false) {
it.runPhase(CreateObjCExportCodeSpecPhase, objCExportedInterface)
}
val backendContext = createBackendContext(config, frontendOutput, psiToIrOutput) {
it.objCExportedInterface = objCExportedInterface
it.objCExportCodeSpec = objCCodeSpec
}
engine.runBackend(backendContext)
}
private fun produceKlib(engine: PhaseEngine<PhaseContext>, config: KonanConfig, environment: KotlinCoreEnvironment) {
val frontendOutput = engine.runFrontend(config, environment) ?: return
val psiToIrOutput = if (config.metadataKlib) {
@@ -0,0 +1,55 @@
/*
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.konan.driver.phases
import org.jetbrains.kotlin.backend.konan.OutputFiles
import org.jetbrains.kotlin.backend.konan.driver.PhaseContext
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportCodeSpec
import org.jetbrains.kotlin.backend.konan.objcexport.ObjCExportedInterface
import org.jetbrains.kotlin.backend.konan.objcexport.createCodeSpec
import org.jetbrains.kotlin.backend.konan.objcexport.createObjCFramework
import org.jetbrains.kotlin.backend.konan.objcexport.produceObjCExportInterface
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
/**
* Create internal representation of Objective-C wrapper.
*/
internal val ProduceObjCExportInterfacePhase = createSimpleNamedCompilerPhase<PhaseContext, FrontendPhaseOutput.Full, ObjCExportedInterface>(
"ObjCExportInterface",
"Objective-C header generation",
outputIfNotEnabled = { _, _, _, _ -> error("Cannot disable `ObjCExportInterface` phase when producing ObjC framework") }
) { context, input ->
produceObjCExportInterface(context, input.moduleDescriptor, input.frontendServices)
}
internal data class CreateObjCFrameworkInput(
val moduleDescriptor: ModuleDescriptor,
val exportedInterface: ObjCExportedInterface,
)
/**
* Create Objective-C framework in the given directory without binary.
*/
internal val CreateObjCFrameworkPhase = createSimpleNamedCompilerPhase<PhaseContext, CreateObjCFrameworkInput>(
"CreateObjCFramework",
"Create Objective-C framework"
) { context, input ->
val config = context.config
// TODO: Share this instance between multiple contexts (including NativeGenerationState)?
val outputFiles = OutputFiles(config.outputPath, config.target, config.produce)
createObjCFramework(config, input.moduleDescriptor, input.exportedInterface, outputFiles.mainFile)
}
/**
* Create specification for bridges between exported Objective-C interfaces and their Kotlin origins.
*/
internal val CreateObjCExportCodeSpecPhase = createSimpleNamedCompilerPhase<PsiToIrContext, ObjCExportedInterface, ObjCExportCodeSpec>(
"ObjCExportCodeCodeSpec",
"Objective-C IR symbols",
outputIfNotEnabled = { _, _, _, _, -> ObjCExportCodeSpec(emptyList(), emptyList()) }
) { context, input ->
input.createCodeSpec(context.symbolTable!!)
}
@@ -61,6 +61,33 @@ internal fun produceObjCExportInterface(
return headerGenerator.buildInterface()
}
/**
* Populate framework directory with headers, module and info.plist.
*/
internal fun createObjCFramework(
config: KonanConfig,
moduleDescriptor: ModuleDescriptor,
exportedInterface: ObjCExportedInterface,
frameworkDirectory: File
) {
val frameworkName = frameworkDirectory.name.removeSuffix(".framework")
val frameworkBuilder = FrameworkBuilder(
config,
infoPListBuilder = InfoPListBuilder(config),
moduleMapBuilder = ModuleMapBuilder(),
objCHeaderWriter = ObjCHeaderWriter(),
mainPackageGuesser = MainPackageGuesser(),
)
frameworkBuilder.build(
moduleDescriptor,
frameworkDirectory,
frameworkName,
exportedInterface.headerLines,
moduleDependencies = emptySet()
)
}
// TODO: No need for such class in dynamic driver.
internal class ObjCExport(
private val generationState: NativeGenerationState,
private val moduleDescriptor: ModuleDescriptor,
@@ -93,7 +120,7 @@ internal class ObjCExport(
val objCCodeGenerator = ObjCExportCodeGenerator(codegen, namer, mapper)
exportedInterface?.generateWorkaroundForSwiftSR10177()
exportedInterface?.generateWorkaroundForSwiftSR10177(generationState)
objCCodeGenerator.generate(codeSpec)
objCCodeGenerator.dispose()
@@ -104,68 +131,49 @@ internal class ObjCExport(
*/
fun produceFrameworkInterface() {
if (exportedInterface != null) {
produceFrameworkSpecific(exportedInterface.headerLines)
}
}
private fun produceFrameworkSpecific(headerLines: List<String>) {
val frameworkDirectory = File(generationState.outputFile)
val frameworkName = frameworkDirectory.name.removeSuffix(".framework")
val frameworkBuilder = FrameworkBuilder(
config,
infoPListBuilder = InfoPListBuilder(config),
moduleMapBuilder = ModuleMapBuilder(),
objCHeaderWriter = ObjCHeaderWriter(),
mainPackageGuesser = MainPackageGuesser(),
)
frameworkBuilder.build(
moduleDescriptor,
frameworkDirectory,
frameworkName,
headerLines,
moduleDependencies = emptySet()
)
}
// See https://bugs.swift.org/browse/SR-10177
private fun ObjCExportedInterface.generateWorkaroundForSwiftSR10177() {
// Code for all protocols from the header should get into the binary.
// Objective-C protocols ABI is complicated (consider e.g. undocumented extended type encoding),
// so the easiest way to achieve this (quickly) is to compile a stub by clang.
val protocolsStub = listOf(
"__attribute__((used)) static void __workaroundSwiftSR10177() {",
buildString {
append(" ")
generatedClasses.forEach {
if (it.isInterface) {
val protocolName = namer.getClassOrProtocolName(it).objCName
append("@protocol($protocolName); ")
}
}
},
"}"
)
val source = createTempFile("protocols", ".m").deleteOnExit()
source.writeLines(headerLines + protocolsStub)
val bitcode = createTempFile("protocols", ".bc").deleteOnExit()
val clangCommand = config.clang.clangC(
source.absolutePath,
"-O2",
"-emit-llvm",
"-c", "-o", bitcode.absolutePath
)
val result = Command(clangCommand).getResult(withErrors = true)
if (result.exitCode == 0) {
generationState.llvm.additionalProducedBitcodeFiles += bitcode.absolutePath
} else {
// Note: ignoring compile errors intentionally.
// In this case resulting framework will likely be unusable due to compile errors when importing it.
createObjCFramework(generationState.config, moduleDescriptor, exportedInterface, File(generationState.outputFile))
}
}
}
// See https://bugs.swift.org/browse/SR-10177
private fun ObjCExportedInterface.generateWorkaroundForSwiftSR10177(generationState: NativeGenerationState) {
// Code for all protocols from the header should get into the binary.
// Objective-C protocols ABI is complicated (consider e.g. undocumented extended type encoding),
// so the easiest way to achieve this (quickly) is to compile a stub by clang.
val protocolsStub = listOf(
"__attribute__((used)) static void __workaroundSwiftSR10177() {",
buildString {
append(" ")
generatedClasses.forEach {
if (it.isInterface) {
val protocolName = namer.getClassOrProtocolName(it).objCName
append("@protocol($protocolName); ")
}
}
},
"}"
)
val source = createTempFile("protocols", ".m").deleteOnExit()
source.writeLines(headerLines + protocolsStub)
val bitcode = createTempFile("protocols", ".bc").deleteOnExit()
val clangCommand = generationState.config.clang.clangC(
source.absolutePath,
"-O2",
"-emit-llvm",
"-c", "-o", bitcode.absolutePath
)
val result = Command(clangCommand).getResult(withErrors = true)
if (result.exitCode == 0) {
generationState.llvm.additionalProducedBitcodeFiles += bitcode.absolutePath
} else {
// Note: ignoring compile errors intentionally.
// In this case resulting framework will likely be unusable due to compile errors when importing it.
}
}