org.jetbrains.kotlin.backend.konan.driver.DynamicCompilerDriver.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of kotlin-native-compiler-embeddable Show documentation
Show all versions of kotlin-native-compiler-embeddable Show documentation
Embeddable JAR of Kotlin/Native compiler
/*
* 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
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.usingJvmCInteropCallbacks
import llvm.LLVMContextCreate
import llvm.LLVMContextDispose
import llvm.LLVMDisposeModule
import llvm.LLVMOpaqueModule
import org.jetbrains.kotlin.backend.konan.*
import org.jetbrains.kotlin.backend.konan.BitcodePostProcessingContextImpl
import org.jetbrains.kotlin.backend.konan.Context
import org.jetbrains.kotlin.backend.konan.driver.phases.*
import org.jetbrains.kotlin.backend.konan.getIncludedLibraryDescriptors
import org.jetbrains.kotlin.backend.konan.llvm.parseBitcodeFile
import org.jetbrains.kotlin.builtins.konan.KonanBuiltIns
import org.jetbrains.kotlin.cli.common.CommonCompilerPerformanceManager
import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity
import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment
import org.jetbrains.kotlin.config.CommonConfigurationKeys
import org.jetbrains.kotlin.konan.file.File
import org.jetbrains.kotlin.konan.target.CompilerOutputKind
import org.jetbrains.kotlin.utils.usingNativeMemoryAllocator
/**
* Dynamic driver does not "know" upfront which phases will be executed.
*/
internal class DynamicCompilerDriver(private val performanceManager: CommonCompilerPerformanceManager?) : CompilerDriver() {
override fun run(config: KonanConfig, environment: KotlinCoreEnvironment) {
usingNativeMemoryAllocator {
usingJvmCInteropCallbacks {
PhaseEngine.startTopLevel(config) { engine ->
if (!config.compileFromBitcode.isNullOrEmpty()) produceBinaryFromBitcode(engine, config, config.compileFromBitcode!!)
else when (config.produce) {
CompilerOutputKind.PROGRAM -> produceBinary(engine, config, environment)
CompilerOutputKind.DYNAMIC -> produceCLibrary(engine, config, environment)
CompilerOutputKind.STATIC -> produceCLibrary(engine, config, environment)
CompilerOutputKind.FRAMEWORK -> produceObjCFramework(engine, config, environment)
CompilerOutputKind.LIBRARY -> produceKlib(engine, config, environment)
CompilerOutputKind.BITCODE -> error("Bitcode output kind is obsolete.")
CompilerOutputKind.DYNAMIC_CACHE -> produceBinary(engine, config, environment)
CompilerOutputKind.STATIC_CACHE -> produceBinary(engine, config, environment)
CompilerOutputKind.HEADER_CACHE -> produceBinary(engine, config, environment)
CompilerOutputKind.TEST_BUNDLE -> produceBundle(engine, config, environment)
}
}
}
}
}
/**
* 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, config: KonanConfig, environment: KotlinCoreEnvironment) {
val frontendOutput = performanceManager.trackAnalysis { engine.runFrontend(config, environment) } ?: return
val (objCExportedInterface, psiToIrOutput, objCCodeSpec) = performanceManager.trackIRTranslation {
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)
}
require(psiToIrOutput is PsiToIrOutput.ForBackend)
Triple(objCExportedInterface, psiToIrOutput, objCCodeSpec)
}
performanceManager.trackGeneration {
val backendContext = createBackendContext(config, frontendOutput, psiToIrOutput) {
it.objCExportedInterface = objCExportedInterface
it.objCExportCodeSpec = objCCodeSpec
}
engine.runBackend(backendContext, psiToIrOutput.irModule)
}
}
private fun produceCLibrary(engine: PhaseEngine, config: KonanConfig, environment: KotlinCoreEnvironment) {
val frontendOutput = performanceManager.trackAnalysis { engine.runFrontend(config, environment) } ?: return
val (psiToIrOutput, cAdapterElements) = performanceManager.trackIRTranslation {
engine.runPsiToIr(frontendOutput, isProducingLibrary = false) {
if (config.cInterfaceGenerationMode == CInterfaceGenerationMode.V1) {
it.runPhase(BuildCExports, frontendOutput)
} else {
null
}
}
}
require(psiToIrOutput is PsiToIrOutput.ForBackend)
performanceManager.trackGeneration {
val backendContext = createBackendContext(config, frontendOutput, psiToIrOutput) {
it.cAdapterExportedElements = cAdapterElements
}
engine.runBackend(backendContext, psiToIrOutput.irModule)
}
}
private fun produceKlib(engine: PhaseEngine, config: KonanConfig, environment: KotlinCoreEnvironment) {
val serializerOutput = if (environment.configuration.getBoolean(CommonConfigurationKeys.USE_FIR))
serializeKLibK2(engine, config, environment)
else
serializeKlibK1(engine, config, environment)
serializerOutput?.let { engine.writeKlib(it) }
}
private fun serializeKLibK2(
engine: PhaseEngine,
config: KonanConfig,
environment: KotlinCoreEnvironment
): SerializerOutput? {
val frontendOutput = performanceManager.trackAnalysis { engine.runFirFrontend(environment) }
if (frontendOutput is FirOutput.ShouldNotGenerateCode) return null
require(frontendOutput is FirOutput.Full)
return if (config.metadataKlib) {
engine.runFirSerializer(frontendOutput)
} else {
performanceManager.trackIRTranslation {
val fir2IrOutput = engine.runFir2Ir(frontendOutput)
val headerKlibPath = config.headerKlibPath
if (!headerKlibPath.isNullOrEmpty()) {
val headerKlib = engine.runFir2IrSerializer(FirSerializerInput(fir2IrOutput, produceHeaderKlib = true))
engine.writeKlib(headerKlib, headerKlibPath, produceHeaderKlib = true)
// Don't overwrite the header klib with the full klib and stop compilation here.
// By providing the same path for both regular output and header klib we can skip emitting the full klib.
if (File(config.outputPath).canonicalPath == File(headerKlibPath).canonicalPath) return null
}
engine.runK2SpecialBackendChecks(fir2IrOutput)
engine.runFir2IrSerializer(FirSerializerInput(fir2IrOutput))
}
}
}
private fun serializeKlibK1(
engine: PhaseEngine,
config: KonanConfig,
environment: KotlinCoreEnvironment
): SerializerOutput? {
val frontendOutput = performanceManager.trackAnalysis { engine.runFrontend(config, environment) } ?: return null
return performanceManager.trackIRTranslation {
val psiToIrOutput = if (config.metadataKlib) {
null
} else {
engine.runPsiToIr(frontendOutput, isProducingLibrary = true) as PsiToIrOutput.ForKlib
}
val headerKlibPath = config.headerKlibPath
if (!headerKlibPath.isNullOrEmpty()) {
val headerKlib = engine.runSerializer(frontendOutput.moduleDescriptor, psiToIrOutput, produceHeaderKlib = true)
engine.writeKlib(headerKlib, headerKlibPath, produceHeaderKlib = true)
// Don't overwrite the header klib with the full klib and stop compilation here.
// By providing the same path for both regular output and header klib we can skip emitting the full klib.
if (File(config.outputPath).canonicalPath == File(headerKlibPath).canonicalPath) return null
}
engine.runSerializer(frontendOutput.moduleDescriptor, psiToIrOutput)
}
}
/**
* Produce a single binary artifact.
*/
private fun produceBinary(engine: PhaseEngine, config: KonanConfig, environment: KotlinCoreEnvironment) {
val frontendOutput = performanceManager.trackAnalysis { engine.runFrontend(config, environment) } ?: return
val psiToIrOutput = performanceManager.trackIRTranslation { engine.runPsiToIr(frontendOutput, isProducingLibrary = false) }
require(psiToIrOutput is PsiToIrOutput.ForBackend)
performanceManager.trackGeneration {
val backendContext = createBackendContext(config, frontendOutput, psiToIrOutput)
engine.runBackend(backendContext, psiToIrOutput.irModule)
}
}
private fun produceBinaryFromBitcode(engine: PhaseEngine, config: KonanConfig, bitcodeFilePath: String) {
val llvmContext = LLVMContextCreate()!!
var llvmModule: CPointer? = null
try {
llvmModule = parseBitcodeFile(engine.context, engine.context.messageCollector, llvmContext, bitcodeFilePath)
val context = BitcodePostProcessingContextImpl(config, llvmModule, llvmContext)
val depsPath = config.readSerializedDependencies
val dependencies = if (depsPath.isNullOrEmpty()) DependenciesTrackingResult(emptyList(), emptyList(), emptyList()).also {
config.configuration.report(CompilerMessageSeverity.WARNING, "No backend dependencies provided.")
} else DependenciesTrackingResult.deserialize(depsPath, File(depsPath).readStrings(), config)
engine.runBitcodeBackend(context, dependencies)
} finally {
llvmModule?.let { LLVMDisposeModule(it) }
LLVMContextDispose(llvmContext)
}
}
/**
* Produce a bundle that is a directory with code and resources.
* It consists of
* - Info.plist
* - Binary without an entry point.
*
* See https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/AboutBundles/AboutBundles.html
*/
private fun produceBundle(engine: PhaseEngine, config: KonanConfig, environment: KotlinCoreEnvironment) {
require(config.target.family.isAppleFamily)
require(config.produce == CompilerOutputKind.TEST_BUNDLE)
val frontendOutput = performanceManager.trackAnalysis { engine.runFrontend(config, environment) } ?: return
val psiToIrOutput = performanceManager.trackIRTranslation {
engine.runPhase(CreateTestBundlePhase, frontendOutput)
engine.runPsiToIr(frontendOutput, isProducingLibrary = false)
}
require(psiToIrOutput is PsiToIrOutput.ForBackend)
performanceManager.trackGeneration {
val backendContext = createBackendContext(config, frontendOutput, psiToIrOutput)
engine.runBackend(backendContext, psiToIrOutput.irModule)
}
}
private fun createBackendContext(
config: KonanConfig,
frontendOutput: FrontendPhaseOutput.Full,
psiToIrOutput: PsiToIrOutput.ForBackend,
additionalDataSetter: (Context) -> Unit = {}
) = Context(
config,
frontendOutput.moduleDescriptor.getIncludedLibraryDescriptors(config).toSet() + frontendOutput.moduleDescriptor,
frontendOutput.moduleDescriptor.builtIns as KonanBuiltIns,
psiToIrOutput.irModule.irBuiltins,
psiToIrOutput.irModules,
psiToIrOutput.irLinker,
psiToIrOutput.symbols
).also {
additionalDataSetter(it)
}
}