[K/N] Support ObjCExport in dynamic driver
This commit is contained in:
committed by
Space Team
parent
b271b409c7
commit
1137299497
+2
@@ -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
-1
@@ -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) {
|
||||
|
||||
+55
@@ -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!!)
|
||||
}
|
||||
+71
-63
@@ -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.
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user