Introduce flag for bitcode to native compilation

This enables splitting the compilation pipeline into multiple
invocations of the compiler.
diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt
index b4181e3..f574068 100644
--- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt
+++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2NativeCompilerArguments.kt
@@ -412,6 +412,19 @@
     @Argument(value = "-Xomit-framework-binary", description = "Omit binary when compiling framework")
     var omitFrameworkBinary: Boolean = false
 
+    @Argument(value = "-Xcompile-from-bitcode", description = "Continue compilation from bitcode file", valueDescription = "<path>")
+    var compileFromBitcode: String? = null
+
+    @Argument(
+        value = "-Xread-dependencies-from",
+        description = "Serialized dependencies to use for linking",
+        valueDescription = "<path>"
+    )
+    var serializedDependencies: String? = null
+
+    @Argument(value = "-Xwrite-dependencies-to", description = "Path for writing backend dependencies")
+    var saveDependenciesPath: String? = null
+
     @Argument(value = "-Xsave-llvm-ir-directory", description = "Directory that should contain results of -Xsave-llvm-ir-after=<phase>")
     var saveLlvmIrDirectory: String? = null
 
diff --git a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt
index 5ba62ff..7d37831 100644
--- a/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt
+++ b/kotlin-native/backend.native/cli.bc/src/org/jetbrains/kotlin/cli/bc/K2Native.kt
@@ -133,7 +133,7 @@
 
     private val K2NativeCompilerArguments.isUsefulWithoutFreeArgs: Boolean
         get() = listTargets || listPhases || checkDependencies || !includes.isNullOrEmpty() ||
-                libraryToAddToCache != null || !exportedLibraries.isNullOrEmpty()
+                libraryToAddToCache != null || !exportedLibraries.isNullOrEmpty() || !compileFromBitcode.isNullOrEmpty()
 
     // It is executed before doExecute().
     override fun setupPlatformSpecificArgumentsAndServices(
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt
index 441544f..8feb250 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/CompilerOutput.kt
@@ -167,7 +167,7 @@
     }
 }
 
-private fun embedAppleLinkerOptionsToBitcode(llvm: Llvm, config: KonanConfig) {
+private fun embedAppleLinkerOptionsToBitcode(llvm: CodegenLlvmHelpers, config: KonanConfig) {
     fun findEmbeddableOptions(options: List<String>): List<List<String>> {
         val result = mutableListOf<List<String>>()
         val iterator = options.iterator()
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DependenciesTracker.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DependenciesTracker.kt
index e60688a..be37c72 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DependenciesTracker.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/DependenciesTracker.kt
@@ -371,5 +371,49 @@
 data class DependenciesTrackingResult(
         val nativeDependenciesToLink: List<KonanLibrary>,
         val allNativeDependencies: List<KonanLibrary>,
-        val allCachedBitcodeDependencies: List<DependenciesTracker.ResolvedDependency>
-)
\ No newline at end of file
+        val allCachedBitcodeDependencies: List<DependenciesTracker.ResolvedDependency>) {
+
+    companion object {
+        private const val NATIVE_DEPENDENCIES_TO_LINK = "NATIVE_DEPENDENCIES_TO_LINK"
+        private const val ALL_NATIVE_DEPENDENCIES = "ALL_NATIVE_DEPENDENCIES"
+        private const val ALL_CACHED_BITCODE_DEPENDENCIES = "ALL_CACHED_BITCODE_DEPENDENCIES"
+
+        fun serialize(res: DependenciesTrackingResult): List<String> {
+            val nativeDepsToLink = DependenciesSerializer.serialize(res.nativeDependenciesToLink.map { DependenciesTracker.ResolvedDependency.wholeModule(it) })
+            val allNativeDeps = DependenciesSerializer.serialize(res.allNativeDependencies.map { DependenciesTracker.ResolvedDependency.wholeModule(it) })
+            val allCachedBitcodeDeps = DependenciesSerializer.serialize(res.allCachedBitcodeDependencies)
+            return listOf(NATIVE_DEPENDENCIES_TO_LINK) + nativeDepsToLink +
+                    listOf(ALL_NATIVE_DEPENDENCIES) + allNativeDeps +
+                    listOf(ALL_CACHED_BITCODE_DEPENDENCIES) + allCachedBitcodeDeps
+        }
+
+        fun deserialize(path: String, dependencies: List<String>, config: KonanConfig): DependenciesTrackingResult {
+
+            val nativeDepsToLinkIndex = dependencies.indexOf(NATIVE_DEPENDENCIES_TO_LINK)
+            require(nativeDepsToLinkIndex >= 0) { "Invalid dependency file at $path" }
+            val allNativeDepsIndex = dependencies.indexOf(ALL_NATIVE_DEPENDENCIES)
+            require(allNativeDepsIndex >= 0) { "Invalid dependency file at $path" }
+            val allCachedBitcodeDepsIndex = dependencies.indexOf(ALL_CACHED_BITCODE_DEPENDENCIES)
+            require(allCachedBitcodeDepsIndex >= 0) { "Invalid dependency file at $path" }
+
+            val nativeLibsToLink = DependenciesSerializer.deserialize(path, dependencies.subList(nativeDepsToLinkIndex + 1, allNativeDepsIndex)).map { it.libName }
+            val allNativeLibs = DependenciesSerializer.deserialize(path, dependencies.subList(allNativeDepsIndex + 1, allCachedBitcodeDepsIndex)).map { it.libName }
+            val allCachedBitcodeDeps = DependenciesSerializer.deserialize(path, dependencies.subList(allCachedBitcodeDepsIndex + 1, dependencies.size))
+
+            val topSortedLibraries = config.resolvedLibraries.getFullList(TopologicalLibraryOrder)
+            val nativeDependenciesToLink = topSortedLibraries.mapNotNull { if (it.uniqueName in nativeLibsToLink && it is KonanLibrary) it else null }
+            val allNativeDependencies = topSortedLibraries.mapNotNull { if (it.uniqueName in allNativeLibs && it is KonanLibrary) it else null }
+            val allCachedBitcodeDependencies = allCachedBitcodeDeps.map { unresolvedDep ->
+                val lib = topSortedLibraries.find { it.uniqueName == unresolvedDep.libName }
+                require(lib != null && lib is KonanLibrary) { "Invalid dependency ${unresolvedDep.libName} at $path" }
+                when (unresolvedDep.kind) {
+                    is DependenciesTracker.DependencyKind.CertainFiles ->
+                        DependenciesTracker.ResolvedDependency.certainFiles(lib, unresolvedDep.kind.files)
+                    else -> DependenciesTracker.ResolvedDependency.wholeModule(lib)
+                }
+            }
+
+            return DependenciesTrackingResult(nativeDependenciesToLink, allNativeDependencies, allCachedBitcodeDependencies)
+        }
+    }
+}
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt
index 1252c38..bbe8129 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfig.kt
@@ -476,11 +476,6 @@
         }
                 ?: File(outputPath).name
 
-    val infoArgsOnly = (configuration.kotlinSourceRoots.isEmpty()
-            && configuration[KonanConfigKeys.INCLUDED_LIBRARIES].isNullOrEmpty()
-            && configuration[KonanConfigKeys.EXPORTED_LIBRARIES].isNullOrEmpty()
-            && libraryToCache == null)
-
     /**
      * Do not compile binary when compiling framework.
      * This is useful when user care only about framework's interface.
@@ -495,6 +490,40 @@
     }
 
     /**
+     * Continue from bitcode. Skips the frontend and codegen phase of the compiler
+     * and instead reads the provided bitcode file.
+     * This option can be used for continuing the compilation from a previous invocation.
+     */
+    internal val compileFromBitcode: String? by lazy {
+        configuration.get(KonanConfigKeys.COMPILE_FROM_BITCODE)
+    }
+
+    /**
+     * Path to serialized dependencies to use for bitcode compilation.
+     */
+    internal val readSerializedDependencies: String? by lazy {
+        configuration.get(KonanConfigKeys.SERIALIZED_DEPENDENCIES).also {
+            if (compileFromBitcode.isNullOrEmpty()) {
+                configuration.report(CompilerMessageSeverity.STRONG_WARNING,
+                        "Providing serialized dependencies only works in conjunction with a bitcode file to compile.")
+            }
+        }
+    }
+
+    /**
+     * Path to store backend dependency information.
+     */
+    internal val writeSerializedDependencies: String? by lazy {
+        configuration.get(KonanConfigKeys.SAVE_DEPENDENCIES_PATH)
+    }
+
+    val infoArgsOnly = (configuration.kotlinSourceRoots.isEmpty()
+            && configuration[KonanConfigKeys.INCLUDED_LIBRARIES].isNullOrEmpty()
+            && configuration[KonanConfigKeys.EXPORTED_LIBRARIES].isNullOrEmpty()
+            && libraryToCache == null && compileFromBitcode.isNullOrEmpty())
+
+
+    /**
      * Directory to store LLVM IR from -Xsave-llvm-ir-after.
      */
     internal val saveLlvmIrDirectory: java.io.File by lazy {
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt
index c9cd246..b2c0590 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/KonanConfigurationKeys.kt
@@ -161,6 +161,9 @@
         val PARTIAL_LINKAGE: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey.create("allows some symbols in klibs be missed")
         val TEST_DUMP_OUTPUT_PATH: CompilerConfigurationKey<String?> = CompilerConfigurationKey.create("path to a file to dump the list of all available tests")
         val OMIT_FRAMEWORK_BINARY: CompilerConfigurationKey<Boolean> = CompilerConfigurationKey.create("do not generate binary in framework")
+        val COMPILE_FROM_BITCODE: CompilerConfigurationKey<String?> = CompilerConfigurationKey.create("path to bitcode file to compile")
+        val SERIALIZED_DEPENDENCIES: CompilerConfigurationKey<String?> = CompilerConfigurationKey.create("path to serialized dependencies for native linking")
+        val SAVE_DEPENDENCIES_PATH: CompilerConfigurationKey<String?> = CompilerConfigurationKey.create("path to save serialized dependencies to")
         val SAVE_LLVM_IR_DIRECTORY: CompilerConfigurationKey<String?> = CompilerConfigurationKey.create("directory to store LLVM IR from phases")
     }
 }
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/NativeGenerationState.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/NativeGenerationState.kt
index b473355..a3d60ad 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/NativeGenerationState.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/NativeGenerationState.kt
@@ -7,6 +7,7 @@
 
 import llvm.*
 import org.jetbrains.kotlin.backend.konan.driver.BasicPhaseContext
+import org.jetbrains.kotlin.backend.konan.driver.PhaseContext
 import org.jetbrains.kotlin.backend.konan.driver.utilities.BackendContextHolder
 import org.jetbrains.kotlin.backend.konan.driver.utilities.LlvmIrHolder
 import org.jetbrains.kotlin.backend.konan.llvm.*
@@ -16,7 +17,6 @@
 import org.jetbrains.kotlin.backend.konan.serialization.SerializedEagerInitializedFile
 import org.jetbrains.kotlin.backend.konan.serialization.SerializedInlineFunctionReference
 import org.jetbrains.kotlin.ir.declarations.*
-import org.jetbrains.kotlin.konan.TempFiles
 
 internal class InlineFunctionOriginInfo(val irFunction: IrFunction, val irFile: IrFile, val startOffset: Int, val endOffset: Int)
 
@@ -38,6 +38,19 @@
             "$prefix${cStubCount++}"
 }
 
+internal interface BitcodePostProcessingContext : PhaseContext, LlvmIrHolder {
+    val llvm: BasicLlvmHelpers
+    val llvmContext: LLVMContextRef
+}
+
+internal class BitcodePostProcessingContextImpl(
+        config: KonanConfig,
+        override val llvmModule: LLVMModuleRef,
+        override val llvmContext: LLVMContextRef
+) : BitcodePostProcessingContext, BasicPhaseContext(config) {
+    override val llvm: BasicLlvmHelpers = BasicLlvmHelpers(this, llvmModule)
+}
+
 internal class NativeGenerationState(
         config: KonanConfig,
         // TODO: Get rid of this property completely once transition to the dynamic driver is complete.
@@ -48,7 +61,7 @@
         val llvmModuleSpecification: LlvmModuleSpecification,
         val outputFiles: OutputFiles,
         val llvmModuleName: String,
-) : BasicPhaseContext(config), BackendContextHolder<Context>, LlvmIrHolder {
+) : BasicPhaseContext(config), BackendContextHolder<Context>, LlvmIrHolder, BitcodePostProcessingContext {
     val outputFile = outputFiles.mainFileName
 
     val inlineFunctionBodies = mutableListOf<SerializedInlineFunctionReference>()
@@ -72,12 +85,12 @@
     val producedLlvmModuleContainsStdlib get() = llvmModuleSpecification.containsModule(context.stdlibModule)
 
     private val runtimeDelegate = lazy { Runtime(llvmContext, config.distribution.compilerInterface(config.target)) }
-    private val llvmDelegate = lazy { Llvm(this, LLVMModuleCreateWithNameInContext(llvmModuleName, llvmContext)!!) }
+    private val llvmDelegate = lazy { CodegenLlvmHelpers(this, LLVMModuleCreateWithNameInContext(llvmModuleName, llvmContext)!!) }
     private val debugInfoDelegate = lazy { DebugInfo(this) }
 
-    val llvmContext = LLVMContextCreate()!!
+    override val llvmContext = LLVMContextCreate()!!
     val runtime by runtimeDelegate
-    val llvm by llvmDelegate
+    override val llvm by llvmDelegate
     val debugInfo by debugInfoDelegate
     val cStubsManager = CStubsManager(config.target, this)
     lateinit var llvmDeclarations: LlvmDeclarations
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt
index 42bd119..f6c6a95 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/SetupConfiguration.kt
@@ -259,6 +259,9 @@
     arguments.testDumpOutputPath?.let { put(TEST_DUMP_OUTPUT_PATH, it) }
     put(PARTIAL_LINKAGE, arguments.partialLinkage)
     put(OMIT_FRAMEWORK_BINARY, arguments.omitFrameworkBinary)
+    putIfNotNull(COMPILE_FROM_BITCODE, arguments.compileFromBitcode)
+    putIfNotNull(SERIALIZED_DEPENDENCIES, arguments.serializedDependencies)
+    putIfNotNull(SAVE_DEPENDENCIES_PATH, arguments.saveDependenciesPath)
     putIfNotNull(SAVE_LLVM_IR_DIRECTORY, arguments.saveLlvmIrDirectory)
 }
 
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassLayoutBuilder.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassLayoutBuilder.kt
index 81dc38f..0703696 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassLayoutBuilder.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/descriptors/ClassLayoutBuilder.kt
@@ -12,7 +12,7 @@
 import org.jetbrains.kotlin.backend.common.lower.coroutines.getOrCreateFunctionWithContinuationStub
 import org.jetbrains.kotlin.backend.konan.*
 import org.jetbrains.kotlin.backend.konan.ir.*
-import org.jetbrains.kotlin.backend.konan.llvm.Llvm
+import org.jetbrains.kotlin.backend.konan.llvm.CodegenLlvmHelpers
 import org.jetbrains.kotlin.backend.konan.llvm.computeFunctionName
 import org.jetbrains.kotlin.backend.konan.llvm.toLLVMType
 import org.jetbrains.kotlin.backend.konan.llvm.localHash
@@ -263,7 +263,7 @@
     }
 }
 
-internal fun IrField.requiredAlignment(llvm: Llvm): Int {
+internal fun IrField.requiredAlignment(llvm: CodegenLlvmHelpers): Int {
     val llvmType = type.toLLVMType(llvm)
     val abiAlignment = if (llvmType == llvm.vector128Type) {
         8 // over-aligned objects are not supported now, and this worked somehow, so let's keep it as it for now
@@ -283,7 +283,7 @@
 
 
 internal class ClassLayoutBuilder(val irClass: IrClass, val context: Context) {
-    private fun IrField.toFieldInfo(llvm: Llvm): FieldInfo {
+    private fun IrField.toFieldInfo(llvm: CodegenLlvmHelpers): FieldInfo {
         val isConst = correspondingPropertySymbol?.owner?.isConst ?: false
         require(!isConst || initializer?.expression is IrConst<*>) { "A const val field ${render()} must have constant initializer" }
         return FieldInfo(name.asString(), type, isConst, symbol, requiredAlignment(llvm))
@@ -435,7 +435,7 @@
      * All fields of the class instance.
      * The order respects the class hierarchy, i.e. a class [fields] contains superclass [fields] as a prefix.
      */
-    fun getFields(llvm: Llvm): List<FieldInfo> = getFieldsInternal(llvm).map { fieldInfo ->
+    fun getFields(llvm: CodegenLlvmHelpers): List<FieldInfo> = getFieldsInternal(llvm).map { fieldInfo ->
         val mappedField = fieldInfo.irField?.let { context.mapping.lateInitFieldToNullableField[it] ?: it }
         if (mappedField == fieldInfo.irField)
             fieldInfo
@@ -445,7 +445,7 @@
 
     private var fields: List<FieldInfo>? = null
 
-    private fun getFieldsInternal(llvm: Llvm): List<FieldInfo> {
+    private fun getFieldsInternal(llvm: CodegenLlvmHelpers): List<FieldInfo> {
         fields?.let { return it }
 
         val superClass = irClass.getSuperClassNotAny()
@@ -503,7 +503,7 @@
     /**
      * Fields declared in the class.
      */
-    fun getDeclaredFields(llvm: Llvm): List<FieldInfo> {
+    fun getDeclaredFields(llvm: CodegenLlvmHelpers): List<FieldInfo> {
         val outerThisField = if (irClass.isInner)
             context.innerClassesSupport.getOuterThisField(irClass)
         else null
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/DynamicCompilerDriver.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/DynamicCompilerDriver.kt
index 962aa15..763c8a8 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/DynamicCompilerDriver.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/DynamicCompilerDriver.kt
@@ -6,14 +6,20 @@
 package org.jetbrains.kotlin.backend.konan.driver
 
 import kotlinx.cinterop.usingJvmCInteropCallbacks
+import llvm.LLVMContextCreate
+import llvm.LLVMContextDispose
+import llvm.LLVMDisposeModule
+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.KonanConfig
 import org.jetbrains.kotlin.backend.konan.driver.phases.*
 import org.jetbrains.kotlin.backend.konan.getIncludedLibraryDescriptors
-import org.jetbrains.kotlin.backend.konan.isCache
+import org.jetbrains.kotlin.backend.konan.llvm.parseBitcodeFile
 import org.jetbrains.kotlin.builtins.konan.KonanBuiltIns
+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.konan.util.usingNativeMemoryAllocator
 
@@ -26,7 +32,8 @@
         usingNativeMemoryAllocator {
             usingJvmCInteropCallbacks {
                 PhaseEngine.startTopLevel(config) { engine ->
-                    when (config.produce) {
+                    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)
@@ -124,6 +131,22 @@
         engine.runBackend(backendContext, psiToIrOutput.irModule)
     }
 
+    private fun produceBinaryFromBitcode(engine: PhaseEngine<PhaseContext>, config: KonanConfig, bitcodeFilePath: String) {
+        val llvmContext = LLVMContextCreate()!!
+        val llvmModule = parseBitcodeFile(llvmContext, bitcodeFilePath)
+        try {
+            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 {
+            LLVMDisposeModule(llvmModule)
+            LLVMContextDispose(llvmContext)
+        }
+    }
+
     private fun createBackendContext(
             config: KonanConfig,
             frontendOutput: FrontendPhaseOutput.Full,
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/Machinery.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/Machinery.kt
index 90612ec..5671d8a 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/Machinery.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/Machinery.kt
@@ -118,6 +118,15 @@
         }
     }
 
+    /**
+     * Create a new PhaseEngine instance for an existing context that should not be disposed after the action.
+     * This is useful for creating engines for a sub/super context type.
+     */
+    inline fun <T : PhaseContext, R> newEngine(newContext: T, action: (PhaseEngine<T>) -> R): R {
+        val newEngine = PhaseEngine(phaseConfig, phaserState, newContext)
+        return action(newEngine)
+    }
+
     fun <Input, Output, P : AbstractNamedCompilerPhase<C, Input, Output>> runPhase(
             phase: P,
             input: Input,
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Bitcode.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Bitcode.kt
index 82187a7..cc97add 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Bitcode.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/Bitcode.kt
@@ -108,7 +108,7 @@
         op = { context, _ -> runCoveragePass(context) }
 )
 
-internal val RemoveRedundantSafepointsPhase = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
+internal val RemoveRedundantSafepointsPhase = createSimpleNamedCompilerPhase<BitcodePostProcessingContext, Unit>(
         name = "RemoveRedundantSafepoints",
         description = "Remove function prologue safepoints inlined to another function",
         postactions = getDefaultLlvmModuleActions(),
@@ -120,7 +120,7 @@
         }
 )
 
-internal val OptimizeTLSDataLoadsPhase = createSimpleNamedCompilerPhase<NativeGenerationState, Unit>(
+internal val OptimizeTLSDataLoadsPhase = createSimpleNamedCompilerPhase<BitcodePostProcessingContext, Unit>(
         name = "OptimizeTLSDataLoads",
         description = "Optimize multiple loads of thread data",
         postactions = getDefaultLlvmModuleActions(),
@@ -153,15 +153,11 @@
         op = { _, llvmModule -> LLVMDumpModule(llvmModule) }
 )
 
-internal fun PhaseEngine<NativeGenerationState>.runBitcodePostProcessing() {
-    val checkExternalCalls = context.config.configuration.getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)
-    if (checkExternalCalls) {
-        runPhase(CheckExternalCallsPhase)
-    }
+internal fun <T : BitcodePostProcessingContext> PhaseEngine<T>.runBitcodePostProcessing() {
     val optimizationConfig = createLTOFinalPipelineConfig(
             context,
             context.llvm.targetTriple,
-            closedWorld = context.llvmModuleSpecification.isFinal,
+            closedWorld = context.config.isFinalBinary,
             timePasses = context.config.flexiblePhaseConfig.needProfiling,
     )
     useContext(OptimizationState(context.config, optimizationConfig)) {
@@ -175,14 +171,14 @@
             null -> {}
         }
     }
-    runPhase(CoveragePhase)
+    val checkExternalCalls = context.config.configuration.getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)
+    if (checkExternalCalls && context is NativeGenerationState) {
+        newEngine(context) { it.runPhase(CoveragePhase) }
+    }
     if (context.config.memoryModel == MemoryModel.EXPERIMENTAL) {
         runPhase(RemoveRedundantSafepointsPhase)
     }
     if (context.config.optimizationsEnabled) {
         runPhase(OptimizeTLSDataLoadsPhase)
     }
-    if (checkExternalCalls) {
-        runPhase(RewriteExternalCallsCheckerGlobals)
-    }
-}
\ No newline at end of file
+}
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/TopLevelPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/TopLevelPhases.kt
index 2375a00..ed5d04c 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/TopLevelPhases.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/phases/TopLevelPhases.kt
@@ -23,7 +23,7 @@
 import org.jetbrains.kotlin.konan.target.CompilerOutputKind
 import org.jetbrains.kotlin.konan.target.Family
 import org.jetbrains.kotlin.library.impl.javaFile
-import java.io.File
+import org.jetbrains.kotlin.konan.file.File
 
 internal fun PhaseEngine<PhaseContext>.runFrontend(config: KonanConfig, environment: KotlinCoreEnvironment): FrontendPhaseOutput.Full? {
     val frontendOutput = useContext(FrontendContextImpl(config)) { it.runPhase(FrontendPhase, environment) }
@@ -80,7 +80,12 @@
                     // TODO: Make this work if we first compile all the fragments and only after that run the link phases.
                     generationStateEngine.compileModule(fragment.irModule, bitcodeFile, cExportFiles)
                     // Split here
-                    val moduleCompilationOutput = ModuleCompilationOutput(bitcodeFile, generationState.dependenciesTracker.collectResult())
+                    val dependenciesTrackingResult = generationState.dependenciesTracker.collectResult()
+                    val depsFilePath = config.writeSerializedDependencies
+                    if (!depsFilePath.isNullOrEmpty()) {
+                        depsFilePath.File().writeLines(DependenciesTrackingResult.serialize(dependenciesTrackingResult))
+                    }
+                    val moduleCompilationOutput = ModuleCompilationOutput(bitcodeFile, dependenciesTrackingResult)
                     compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles, isCoverageEnabled = false)
                 }
             } finally {
@@ -90,6 +95,19 @@
     }
 }
 
+internal fun <C : PhaseContext> PhaseEngine<C>.runBitcodeBackend(context: BitcodePostProcessingContext, dependencies: DependenciesTrackingResult) {
+    useContext(context) { bitcodeEngine ->
+        val tempFiles = createTempFiles(context.config, null)
+        val bitcodeFile = tempFiles.create(context.config.shortModuleName ?: "out", ".bc").javaFile()
+        val outputPath = context.config.outputPath
+        val outputFiles = OutputFiles(outputPath, context.config.target, context.config.produce)
+        bitcodeEngine.runBitcodePostProcessing()
+        runPhase(WriteBitcodeFilePhase, WriteBitcodeFileInput(context.llvm.module, bitcodeFile))
+        val moduleCompilationOutput = ModuleCompilationOutput(bitcodeFile, dependencies)
+        compileAndLink(moduleCompilationOutput, outputFiles.mainFileName, outputFiles, tempFiles, isCoverageEnabled = false)
+    }
+}
+
 private fun isReferencedByNativeRuntime(declarations: List<IrDeclaration>): Boolean =
         declarations.any {
             it.hasAnnotation(RuntimeNames.exportTypeInfoAnnotation)
@@ -159,7 +177,7 @@
 }
 
 internal data class ModuleCompilationOutput(
-        val bitcodeFile: File,
+        val bitcodeFile: java.io.File,
         val dependenciesTrackingResult: DependenciesTrackingResult,
 )
 
@@ -170,7 +188,7 @@
  * 4. Optimizes it.
  * 5. Serializes it to a bitcode file.
  */
-internal fun PhaseEngine<NativeGenerationState>.compileModule(module: IrModuleFragment, bitcodeFile: File, cExportFiles: CExportFiles?) {
+internal fun PhaseEngine<NativeGenerationState>.compileModule(module: IrModuleFragment, bitcodeFile: java.io.File, cExportFiles: CExportFiles?) {
     if (context.config.produce.isCache) {
         runPhase(BuildAdditionalCacheInfoPhase, module)
     }
@@ -178,13 +196,21 @@
         runPhase(EntryPointPhase, module)
     }
     runBackendCodegen(module, cExportFiles)
-    runBitcodePostProcessing()
+    val checkExternalCalls = context.config.configuration.getBoolean(KonanConfigKeys.CHECK_EXTERNAL_CALLS)
+    if (checkExternalCalls) {
+        runPhase(CheckExternalCallsPhase)
+    }
+    newEngine(context as BitcodePostProcessingContext) { it.runBitcodePostProcessing() }
+    if (checkExternalCalls) {
+        runPhase(RewriteExternalCallsCheckerGlobals)
+    }
     if (context.config.produce.isCache) {
         runPhase(SaveAdditionalCacheInfoPhase)
     }
     runPhase(WriteBitcodeFilePhase, WriteBitcodeFileInput(context.llvm.module, bitcodeFile))
 }
 
+
 internal fun <C : PhaseContext> PhaseEngine<C>.compileAndLink(
         moduleCompilationOutput: ModuleCompilationOutput,
         linkerOutputFile: String,
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt
index 09b895a..d0b33a1 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/CodeGenerator.kt
@@ -1298,7 +1298,7 @@
         assert(!irClass.isInterface)
 
         return if (irClass.isExternalObjCClass()) {
-            llvm.dependenciesTracker.add(irClass)
+            generationState.dependenciesTracker.add(irClass)
             if (irClass.isObjCMetaClass()) {
                 val name = irClass.descriptor.getExternalObjCMetaClassBinaryName()
                 val objCClass = getObjCClass(name)
@@ -1337,7 +1337,7 @@
     private fun getObjCClass(binaryName: String) = load(codegen.objCDataGenerator!!.genClassRef(binaryName).llvm)
 
     fun getObjCClassFromNativeRuntime(binaryName: String): LLVMValueRef {
-        llvm.dependenciesTracker.addNativeRuntime()
+        generationState.dependenciesTracker.addNativeRuntime()
         return getObjCClass(binaryName)
     }
 
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt
index d3b39c4..b9d2c3a 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/ContextUtils.kt
@@ -148,7 +148,7 @@
     val llvmTargetData: LLVMTargetDataRef
         get() = runtime.targetData
 
-    val llvm: Llvm
+    val llvm: CodegenLlvmHelpers
         get() = generationState.llvm
 
     val staticData: KotlinStaticData
@@ -256,42 +256,61 @@
     }
 }
 
-internal class ConstInt1(llvm: Llvm, val value: Boolean) : ConstValue {
+internal class ConstInt1(llvm: CodegenLlvmHelpers, val value: Boolean) : ConstValue {
     override val llvm = LLVMConstInt(llvm.int1Type, if (value) 1 else 0, 1)!!
 }
 
-internal class ConstInt8(llvm: Llvm, val value: Byte) : ConstValue {
+internal class ConstInt8(llvm: CodegenLlvmHelpers, val value: Byte) : ConstValue {
     override val llvm = LLVMConstInt(llvm.int8Type, value.toLong(), 1)!!
 }
 
-internal class ConstInt16(llvm: Llvm, val value: Short) : ConstValue {
+internal class ConstInt16(llvm: CodegenLlvmHelpers, val value: Short) : ConstValue {
     override val llvm = LLVMConstInt(llvm.int16Type, value.toLong(), 1)!!
 }
 
-internal class ConstChar16(llvm: Llvm, val value: Char) : ConstValue {
+internal class ConstChar16(llvm: CodegenLlvmHelpers, val value: Char) : ConstValue {
     override val llvm = LLVMConstInt(llvm.int16Type, value.code.toLong(), 1)!!
 }
 
-internal class ConstInt32(llvm: Llvm, val value: Int) : ConstValue {
+internal class ConstInt32(llvm: CodegenLlvmHelpers, val value: Int) : ConstValue {
     override val llvm = LLVMConstInt(llvm.int32Type, value.toLong(), 1)!!
 }
 
-internal class ConstInt64(llvm: Llvm, val value: Long) : ConstValue {
+internal class ConstInt64(llvm: CodegenLlvmHelpers, val value: Long) : ConstValue {
     override val llvm = LLVMConstInt(llvm.int64Type, value, 1)!!
 }
 
-internal class ConstFloat32(llvm: Llvm, val value: Float) : ConstValue {
+internal class ConstFloat32(llvm: CodegenLlvmHelpers, val value: Float) : ConstValue {
     override val llvm = LLVMConstReal(llvm.floatType, value.toDouble())!!
 }
 
-internal class ConstFloat64(llvm: Llvm, val value: Double) : ConstValue {
+internal class ConstFloat64(llvm: CodegenLlvmHelpers, val value: Double) : ConstValue {
     override val llvm = LLVMConstReal(llvm.doubleType, value)!!
 }
 
+internal open class BasicLlvmHelpers(bitcodeContext: BitcodePostProcessingContext, val module: LLVMModuleRef) {
+
+    val llvmContext = bitcodeContext.llvmContext
+    val targetTriple by lazy {
+        LLVMGetTarget(module)!!.toKString()
+    }
+
+    val runtimeAnnotationMap by lazy {
+        StaticData.getGlobal(module, "llvm.global.annotations")
+                ?.getInitializer()
+                ?.let { getOperands(it) }
+                ?.groupBy(
+                        { LLVMGetInitializer(LLVMGetOperand(LLVMGetOperand(it, 1), 0))?.getAsCString() ?: "" },
+                        { LLVMGetOperand(LLVMGetOperand(it, 0), 0)!! }
+                )
+                ?.filterKeys { it != "" }
+                ?: emptyMap()
+    }
+}
+
 @Suppress("FunctionName", "PropertyName", "PrivatePropertyName")
-internal class Llvm(private val generationState: NativeGenerationState, val module: LLVMModuleRef) : RuntimeAware {
+internal class CodegenLlvmHelpers(private val generationState: NativeGenerationState, module: LLVMModuleRef) : BasicLlvmHelpers(generationState, module), RuntimeAware {
     private val context = generationState.context
-    val llvmContext = generationState.llvmContext
 
     private fun importFunction(name: String, otherModule: LLVMModuleRef): LlvmCallable {
         if (LLVMGetNamedFunction(module, name) != null) {
@@ -378,11 +397,9 @@
 
     override val runtime get() = generationState.runtime
 
-    val targetTriple = runtime.target
-
     init {
         LLVMSetDataLayout(module, runtime.dataLayout)
-        LLVMSetTarget(module, targetTriple)
+        LLVMSetTarget(module, runtime.target)
     }
 
     private fun importRtFunction(name: String) = importFunction(name, runtime.llvmModule)
@@ -480,27 +497,14 @@
     val initializersGenerationState = InitializersGenerationState()
     val boxCacheGlobals = mutableMapOf<BoxCache, StaticData.Global>()
 
-    val runtimeAnnotationMap by lazy {
-        staticData.getGlobal("llvm.global.annotations")
-                ?.getInitializer()
-                ?.let { getOperands(it) }
-                ?.groupBy(
-                        { LLVMGetInitializer(LLVMGetOperand(LLVMGetOperand(it, 1), 0))?.getAsCString() ?: "" },
-                        { LLVMGetOperand(LLVMGetOperand(it, 0), 0)!! }
-                )
-                ?.filterKeys { it != "" }
-                ?: emptyMap()
-    }
-
-
     private object lazyRtFunction {
         operator fun provideDelegate(
-                thisRef: Llvm, property: KProperty<*>
-        ) = object : ReadOnlyProperty<Llvm, LlvmCallable> {
+                thisRef: CodegenLlvmHelpers, property: KProperty<*>
+        ) = object : ReadOnlyProperty<CodegenLlvmHelpers, LlvmCallable> {
 
             val value: LlvmCallable by lazy { thisRef.importRtFunction(property.name) }
 
-            override fun getValue(thisRef: Llvm, property: KProperty<*>): LlvmCallable = value
+            override fun getValue(thisRef: CodegenLlvmHelpers, property: KProperty<*>): LlvmCallable = value
         }
     }
 
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt
index a9e8ccd..4bc2afb 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/DataLayout.kt
@@ -11,7 +11,7 @@
 import org.jetbrains.kotlin.ir.types.isNothing
 import org.jetbrains.kotlin.ir.types.isUnit
 
-private fun PrimitiveBinaryType?.toLlvmType(llvm: Llvm) = when (this) {
+private fun PrimitiveBinaryType?.toLlvmType(llvm: CodegenLlvmHelpers) = when (this) {
     null -> llvm.kObjHeaderPtr
 
     PrimitiveBinaryType.BOOLEAN -> llvm.int1Type
@@ -26,12 +26,12 @@
     PrimitiveBinaryType.POINTER -> llvm.int8PtrType
 }
 
-internal fun IrType.toLLVMType(llvm: Llvm): LLVMTypeRef =
+internal fun IrType.toLLVMType(llvm: CodegenLlvmHelpers): LLVMTypeRef =
         llvm.runtime.calculatedLLVMTypes.getOrPut(this) { computePrimitiveBinaryTypeOrNull().toLlvmType(llvm) }
 
 internal fun IrType.isVoidAsReturnType() = isUnit() || isNothing()
 
-internal fun IrType.getLLVMReturnType(llvm: Llvm) = when {
+internal fun IrType.getLLVMReturnType(llvm: CodegenLlvmHelpers) = when {
     isVoidAsReturnType() -> llvm.voidType
     else -> toLLVMType(llvm)
 }
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt
index a67b6ca..3a9cb48 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/IrToBitcode.kt
@@ -40,7 +40,6 @@
 import org.jetbrains.kotlin.konan.target.Family
 import org.jetbrains.kotlin.library.KotlinLibrary
 import org.jetbrains.kotlin.library.uniqueName
-import org.jetbrains.kotlin.name.FqName
 import org.jetbrains.kotlin.name.Name
 import org.jetbrains.kotlin.resolve.descriptorUtil.classId
 
@@ -472,7 +471,7 @@
 
     private fun createInitBody(state: ScopeInitializersGenerationState): LLVMValueRef {
         val initFunction = addLlvmFunctionWithDefaultAttributes(
-                context,
+                generationState.context,
                 llvm.module,
                 "",
                 kInitFuncType
@@ -551,7 +550,7 @@
         // Create static object of class InitNode.
         val initNode = LLVMConstNamedStruct(kNodeInitType, argList, 2)!!
         // Create global variable with init record data.
-        return llvm.staticData.placeGlobal("init_node", constPointer(initNode), isExported = false).llvmGlobal
+        return codegen.staticData.placeGlobal("init_node", constPointer(initNode), isExported = false).llvmGlobal
     }
 
     //-------------------------------------------------------------------------//
@@ -842,7 +841,7 @@
                             recordCoverage(body)
                             if (declaration.isReifiedInline) {
                                 callDirect(context.ir.symbols.throwIllegalStateExceptionWithMessage.owner,
-                                        listOf(llvm.staticData.kotlinStringLiteral(
+                                        listOf(codegen.staticData.kotlinStringLiteral(
                                                 "unsupported call of reified inlined function `${declaration.fqNameForIrSerialization}`").llvm),
                                         Lifetime.IRRELEVANT, null)
                                 return@usingVariableScope
@@ -1288,7 +1287,7 @@
             functionGenerationContext.positionAtEnd(whenEmittingContext.bbExit.value)
 
         return when {
-            expression.type.isUnit() -> functionGenerationContext.theUnitInstanceRef.llvm
+            expression.type.isUnit() -> codegen.theUnitInstanceRef.llvm
             expression.type.isNothing() -> functionGenerationContext.kNothingFakeValue
             whenEmittingContext.resultPhi.isInitialized() -> whenEmittingContext.resultPhi.value
             else -> LLVMGetUndef(whenEmittingContext.llvmType)!!
@@ -1346,7 +1345,7 @@
         }
 
         assert(loop.type.isUnit())
-        return functionGenerationContext.theUnitInstanceRef.llvm
+        return codegen.theUnitInstanceRef.llvm
     }
 
     //-------------------------------------------------------------------------//
@@ -1371,7 +1370,7 @@
         }
 
         assert(loop.type.isUnit())
-        return functionGenerationContext.theUnitInstanceRef.llvm
+        return codegen.theUnitInstanceRef.llvm
     }
 
     //-------------------------------------------------------------------------//
@@ -1395,7 +1394,7 @@
         val variable = currentCodeContext.getDeclaredValue(value.symbol.owner)
         functionGenerationContext.vars.store(result, variable)
         assert(value.type.isUnit())
-        return functionGenerationContext.theUnitInstanceRef.llvm
+        return codegen.theUnitInstanceRef.llvm
     }
 
     //-------------------------------------------------------------------------//
@@ -1465,7 +1464,7 @@
             IrTypeOperator.IMPLICIT_NOTNULL          -> TODO(ir2string(value))
             IrTypeOperator.IMPLICIT_COERCION_TO_UNIT -> {
                 evaluateExpression(value.argument)
-                functionGenerationContext.theUnitInstanceRef.llvm
+                codegen.theUnitInstanceRef.llvm
             }
             IrTypeOperator.SAFE_CAST                 -> throw IllegalStateException("safe cast wasn't lowered")
             IrTypeOperator.INSTANCEOF                -> evaluateInstanceOf(value)
@@ -1533,7 +1532,7 @@
                     val dstFullClassName = dstClass.fqNameWhenAvailable?.toString() ?: dstClass.name.toString()
                     callDirect(
                             context.ir.symbols.throwTypeCastException.owner,
-                            listOf(srcArg, llvm.staticData.kotlinStringLiteral(dstFullClassName).llvm),
+                            listOf(srcArg, codegen.staticData.kotlinStringLiteral(dstFullClassName).llvm),
                             Lifetime.GLOBAL,
                             null
                     )
@@ -1810,7 +1809,7 @@
 
     //-------------------------------------------------------------------------//
     private fun evaluateStringConst(value: IrConst<String>) =
-            llvm.staticData.kotlinStringLiteral(value.value)
+            codegen.staticData.kotlinStringLiteral(value.value)
 
     private fun evaluateConst(value: IrConst<*>): ConstValue {
         context.log{"evaluateConst                  : ${ir2string(value)}"}
@@ -1862,7 +1861,7 @@
                         require(value.type.toLLVMType(llvm) == codegen.kObjHeaderPtr) {
                             "Can't wrap ${value.value.kind.asString} constant to type ${value.type.render()}"
                         }
-                        value.toBoxCacheValue(generationState) ?: llvm.staticData.createConstKotlinObject(
+                        value.toBoxCacheValue(generationState) ?: codegen.staticData.createConstKotlinObject(
                                 constructedType.getClass()!!,
                                 evaluateConst(value.value)
                         )
@@ -1876,7 +1875,7 @@
                 require(clazz.symbol == symbols.array || clazz.symbol in symbols.primitiveTypesToPrimitiveArrays.values) {
                     "Statically initialized array should have array type"
                 }
-                llvm.staticData.createConstKotlinArray(
+                codegen.staticData.createConstKotlinArray(
                         value.type.getClass()!!,
                         value.elements.map { evaluateConstantValue(it) }
                 )
@@ -1937,7 +1936,7 @@
                 }
 
                 require(value.type.toLLVMType(llvm) == codegen.kObjHeaderPtr) { "Constant object is not an object, but ${value.type.render()}" }
-                llvm.staticData.createConstKotlinObject(
+                codegen.staticData.createConstKotlinObject(
                         constructedClass,
                         *fields.toTypedArray()
                 )
@@ -2717,7 +2716,7 @@
         if (args.isEmpty()) return
 
         val argsCasted = args.map { constPointer(it).bitcast(llvm.int8PtrType) }
-        val llvmUsedGlobal = llvm.staticData.placeGlobalArray(name, llvm.int8PtrType, argsCasted)
+        val llvmUsedGlobal = codegen.staticData.placeGlobalArray(name, llvm.int8PtrType, argsCasted)
 
         LLVMSetLinkage(llvmUsedGlobal.llvmGlobal, LLVMLinkage.LLVMAppendingLinkage)
         LLVMSetSection(llvmUsedGlobal.llvmGlobal, "llvm.metadata")
@@ -2802,7 +2801,7 @@
 
         fun addCtorFunction(ctorName: String) =
                 addLlvmFunctionWithDefaultAttributes(
-                        context,
+                        generationState.context,
                         llvm.module,
                         ctorName,
                         kVoidFuncType
@@ -2898,7 +2897,7 @@
             LLVMSetLinkage(globalCtorFunction, LLVMLinkage.LLVMPrivateLinkage)
 
             // Append initializers of global variables in "llvm.global_ctors" array.
-            val globalCtors = llvm.staticData.placeGlobalArray("llvm.global_ctors", kCtorType,
+            val globalCtors = codegen.staticData.placeGlobalArray("llvm.global_ctors", kCtorType,
                     listOf(createGlobalCtor(globalCtorFunction)))
             LLVMSetLinkage(globalCtors.llvmGlobal, LLVMLinkage.LLVMAppendingLinkage)
             if (context.config.produce == CompilerOutputKind.PROGRAM) {
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinStaticData.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinStaticData.kt
index 18d1f0d..f3d0297 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinStaticData.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/KotlinStaticData.kt
@@ -7,9 +7,7 @@
 
 import kotlinx.cinterop.cValuesOf
 import llvm.*
-import org.jetbrains.kotlin.backend.konan.Context
 import org.jetbrains.kotlin.backend.konan.NativeGenerationState
-import org.jetbrains.kotlin.backend.konan.ir.llvmSymbolOrigin
 import org.jetbrains.kotlin.ir.declarations.IrClass
 import org.jetbrains.kotlin.ir.expressions.IrConst
 
@@ -17,7 +15,7 @@
     return constPointer(LLVMConstGEP(llvm, cValuesOf(index), 1)!!)
 }
 
-internal class KotlinStaticData(override val generationState: NativeGenerationState, override val llvm: Llvm, module: LLVMModuleRef) : ContextUtils, StaticData(module, llvm) {
+internal class KotlinStaticData(override val generationState: NativeGenerationState, override val llvm: CodegenLlvmHelpers, module: LLVMModuleRef) : ContextUtils, StaticData(module, llvm) {
     private val stringLiterals = mutableMapOf<String, ConstPointer>()
 
     // Must match OBJECT_TAG_PERMANENT_CONTAINER in C++.
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt
index 4775b6c..06b1766 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/LlvmUtils.kt
@@ -23,7 +23,7 @@
     get() = this.llvm.type
 
 internal interface ConstPointer : ConstValue {
-    fun getElementPtr(llvm: Llvm, index: Int): ConstPointer = ConstGetElementPtr(llvm, this, index)
+    fun getElementPtr(llvm: CodegenLlvmHelpers, index: Int): ConstPointer = ConstGetElementPtr(llvm, this, index)
 }
 
 internal fun constPointer(value: LLVMValueRef) = object : ConstPointer {
@@ -34,7 +34,7 @@
     override val llvm = value
 }
 
-private class ConstGetElementPtr(llvm: Llvm, pointer: ConstPointer, index: Int) : ConstPointer {
+private class ConstGetElementPtr(llvm: CodegenLlvmHelpers, pointer: ConstPointer, index: Int) : ConstPointer {
     override val llvm = LLVMConstInBoundsGEP(pointer.llvm, cValuesOf(llvm.int32(0), llvm.int32(index)), 2)!!
     // TODO: squash multiple GEPs
 }
@@ -182,12 +182,12 @@
 }
 
 internal fun ContextUtils.importGlobal(name: String, type: LLVMTypeRef, declaration: IrDeclaration) =
-        importGlobal(name, type).also { llvm.dependenciesTracker.add(declaration) }
+        importGlobal(name, type).also { generationState.dependenciesTracker.add(declaration) }
 
 internal fun ContextUtils.importObjCGlobal(name: String, type: LLVMTypeRef) = importGlobal(name, type)
 
 internal fun ContextUtils.importNativeRuntimeGlobal(name: String, type: LLVMTypeRef) =
-        importGlobal(name, type).also { llvm.dependenciesTracker.addNativeRuntime() }
+        importGlobal(name, type).also { generationState.dependenciesTracker.addNativeRuntime() }
 
 private fun CodeGenerator.replaceExternalWeakOrCommonGlobal(name: String, value: ConstValue) {
     if (generationState.llvmModuleSpecification.importsKotlinDeclarationsFromOtherSharedLibraries()) {
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt
index 6a86709..2ce7ace 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/StaticData.kt
@@ -10,7 +10,7 @@
 /**
  * Provides utilities to create static data.
  */
-internal open class StaticData(val module: LLVMModuleRef, private val llvm: Llvm) {
+internal open class StaticData(val module: LLVMModuleRef, private val llvm: CodegenLlvmHelpers) {
 
     /**
      * Represents the LLVM global variable.
@@ -52,6 +52,11 @@
                 val llvmGlobal = LLVMGetNamedGlobal(staticData.module, name) ?: return null
                 return Global(llvmGlobal)
             }
+
+            fun get(module: LLVMModuleRef, name: String): Global? {
+                val llvmGlobal = LLVMGetNamedGlobal(module, name) ?: return null
+                return Global(llvmGlobal)
+            }
         }
 
         val type get() = getGlobalType(this.llvmGlobal)
@@ -150,4 +155,8 @@
     }
 
     internal fun cStringLiteral(value: String) = cStringLiterals.getOrPut(value) { placeCStringLiteral(value) }
+
+    companion object {
+        fun getGlobal(module: LLVMModuleRef, name: String) = Global.get(module, name)
+    }
 }
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt
index 96fd623..2f21ae6 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/ObjCDataGenerator.kt
@@ -116,7 +116,7 @@
             )
 
             val globalName = "\u0001l_OBJC_\$_INSTANCE_METHODS_$name"
-            val global = llvm.staticData.placeGlobal(globalName, methodList).also {
+            val global = codegen.staticData.placeGlobal(globalName, methodList).also {
                 it.setLinkage(LLVMLinkage.LLVMPrivateLinkage)
                 it.setAlignment(runtime.pointerAlignment)
                 it.setSection("__DATA, __objc_const")
@@ -165,7 +165,7 @@
                 "\u0001l_OBJC_CLASS_RO_\$_"
             } + name
 
-            val roGlobal = llvm.staticData.placeGlobal(roLabel, roValue).also {
+            val roGlobal = codegen.staticData.placeGlobal(roLabel, roValue).also {
                 it.setLinkage(LLVMLinkage.LLVMPrivateLinkage)
                 it.setAlignment(runtime.pointerAlignment)
                 it.setSection("__DATA, __objc_const")
@@ -223,7 +223,7 @@
     private fun addModuleClassList(elements: List<ConstPointer>, name: String, section: String) {
         if (elements.isEmpty()) return
 
-        val global = llvm.staticData.placeGlobalArray(
+        val global = codegen.staticData.placeGlobalArray(
                 name,
                 llvm.int8PtrType,
                 elements.map { it.bitcast(llvm.int8PtrType) }
@@ -270,7 +270,7 @@
     }
 
     class CStringLiteralsGenerator(val label: String, val section: String) {
-        fun generate(module: LLVMModuleRef, llvm: Llvm, value: String): ConstPointer {
+        fun generate(module: LLVMModuleRef, llvm: CodegenLlvmHelpers, value: String): ConstPointer {
             val bytes = value.toByteArray(Charsets.UTF_8).map { llvm.constInt8(it) } + llvm.constInt8(0)
             val initializer = ConstArray(llvm.int8Type, bytes)
             val llvmGlobal = LLVMAddGlobal(module, initializer.llvmType, label)!!
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt
index 8274f17..43d9980 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objc/linkObjC.kt
@@ -7,7 +7,6 @@
 
 import kotlinx.cinterop.*
 import llvm.*
-import org.jetbrains.kotlin.backend.konan.Context
 import org.jetbrains.kotlin.backend.konan.NativeGenerationState
 import org.jetbrains.kotlin.backend.konan.isFinalBinary
 import org.jetbrains.kotlin.backend.konan.llvm.*
@@ -127,7 +126,7 @@
     }
 }
 
-private fun PatchBuilder.buildAndApply(llvmModule: LLVMModuleRef, llvm: Llvm) {
+private fun PatchBuilder.buildAndApply(llvmModule: LLVMModuleRef, llvm: CodegenLlvmHelpers) {
     val nameToGlobalPatch = globalPatches.associateNonRepeatingBy { it.globalName }
 
     val sectionToValueToLiteralPatch = literalPatches.groupBy { it.generator.section }
@@ -197,10 +196,10 @@
                 }
 
 private fun patchLiteral(
-        global: LLVMValueRef,
-        llvm: Llvm,
-        generator: ObjCDataGenerator.CStringLiteralsGenerator,
-        newValue: String
+    global: LLVMValueRef,
+    llvm: CodegenLlvmHelpers,
+    generator: ObjCDataGenerator.CStringLiteralsGenerator,
+    newValue: String
 ) {
     val module = LLVMGetGlobalParent(global)!!
 
@@ -216,7 +215,7 @@
     }
 }
 
-private fun LLVMValueRef.isFirstCharPtr(llvm: Llvm, global: LLVMValueRef): Boolean =
+private fun LLVMValueRef.isFirstCharPtr(llvm: CodegenLlvmHelpers, global: LLVMValueRef): Boolean =
         this.type == llvm.int8PtrType &&
                 LLVMIsConstant(this) != 0 && LLVMGetConstOpcode(this) == LLVMOpcode.LLVMGetElementPtr
                 && LLVMGetNumOperands(this) == 3
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt
index 7604a7d..afaf917 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/BlockPointerSupport.kt
@@ -148,7 +148,7 @@
  */
 internal data class BlockType(val numberOfParameters: Int, val returnsVoid: Boolean)
 
-private fun BlockType.toBlockInvokeLlvmType(llvm: Llvm): LlvmFunctionSignature =
+private fun BlockType.toBlockInvokeLlvmType(llvm: CodegenLlvmHelpers): LlvmFunctionSignature =
         LlvmFunctionSignature(
                 LlvmRetType(if (returnsVoid) llvm.voidType else llvm.int8PtrType),
                 (0..numberOfParameters).map { LlvmParamType(llvm.int8PtrType) }
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt
index a34c295..8045948 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/llvm/objcexport/ObjCExportCodeGenerator.kt
@@ -43,7 +43,7 @@
 import org.jetbrains.kotlin.psi2ir.descriptors.IrBuiltInsOverDescriptors
 import org.jetbrains.kotlin.utils.DFS
 
-internal fun TypeBridge.makeNothing(llvm: Llvm) = when (this) {
+internal fun TypeBridge.makeNothing(llvm: CodegenLlvmHelpers) = when (this) {
     is ReferenceBridge, is BlockPointerBridge -> llvm.kNullInt8Ptr
     is ValueTypeBridge -> LLVMConstNull(this.objCValueType.toLlvmType(llvm))!!
 }
@@ -1929,7 +1929,7 @@
     return LlvmFunctionSignature(returnType, paramTypes, isVararg = false)
 }
 
-private fun ObjCValueType.toLlvmType(llvm: Llvm): LLVMTypeRef = when (this) {
+private fun ObjCValueType.toLlvmType(llvm: CodegenLlvmHelpers): LLVMTypeRef = when (this) {
     ObjCValueType.BOOL -> llvm.int8Type
     ObjCValueType.UNICHAR -> llvm.int16Type
     ObjCValueType.CHAR -> llvm.int8Type
@@ -1945,7 +1945,7 @@
     ObjCValueType.POINTER -> llvm.int8PtrType
 }
 
-private fun MethodBridgeParameter.toLlvmParamType(llvm: Llvm): LlvmParamType = when (this) {
+private fun MethodBridgeParameter.toLlvmParamType(llvm: CodegenLlvmHelpers): LlvmParamType = when (this) {
     is MethodBridgeValueParameter.Mapped -> this.bridge.toLlvmParamType(llvm)
     is MethodBridgeReceiver -> ReferenceBridge.toLlvmParamType(llvm)
     MethodBridgeSelector -> LlvmParamType(llvm.int8PtrType)
@@ -1972,7 +1972,7 @@
     }
 }
 
-private fun TypeBridge.toLlvmParamType(llvm: Llvm): LlvmParamType = when (this) {
+private fun TypeBridge.toLlvmParamType(llvm: CodegenLlvmHelpers): LlvmParamType = when (this) {
     is ReferenceBridge, is BlockPointerBridge -> LlvmParamType(llvm.int8PtrType)
     is ValueTypeBridge -> LlvmParamType(this.objCValueType.toLlvmType(llvm), this.objCValueType.defaultParameterAttributes)
 }
diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/RemoveMultipleThreadDataLoads.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/RemoveMultipleThreadDataLoads.kt
index e19287e..b6e575d 100644
--- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/RemoveMultipleThreadDataLoads.kt
+++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/optimizations/RemoveMultipleThreadDataLoads.kt
@@ -6,8 +6,7 @@
 package org.jetbrains.kotlin.backend.konan.optimizations
 
 import llvm.*
-import org.jetbrains.kotlin.backend.konan.Context
-import org.jetbrains.kotlin.backend.konan.NativeGenerationState
+import org.jetbrains.kotlin.backend.konan.BitcodePostProcessingContext
 import org.jetbrains.kotlin.backend.konan.llvm.getBasicBlocks
 import org.jetbrains.kotlin.backend.konan.llvm.getFunctions
 import org.jetbrains.kotlin.backend.konan.llvm.getInstructions
@@ -32,10 +31,10 @@
             }
 }
 
-internal fun removeMultipleThreadDataLoads(generationState: NativeGenerationState) {
-    val currentThreadTLV = generationState.llvm.runtimeAnnotationMap["current_thread_tlv"]?.singleOrNull() ?: return
+internal fun removeMultipleThreadDataLoads(context: BitcodePostProcessingContext) {
+    val currentThreadTLV = context.llvm.runtimeAnnotationMap["current_thread_tlv"]?.singleOrNull() ?: return
 
-    getFunctions(generationState.llvm.module)
+    getFunctions(context.llvm.module)
             .filter { it.name?.startsWith("kfun:") == true }
             .filterNot { LLVMIsDeclaration(it) == 1 }
             .forEach { process(it, currentThreadTLV) }
diff --git a/kotlin-native/backend.native/tests/build.gradle b/kotlin-native/backend.native/tests/build.gradle
index 892c896..6f29399 100644
--- a/kotlin-native/backend.native/tests/build.gradle
+++ b/kotlin-native/backend.native/tests/build.gradle
@@ -5600,6 +5600,19 @@
     useGoldenData = true
 }
 
+standaloneTest("split_compilation_pipeline") {
+    def dir = buildDir.absolutePath
+    source = "link/private_fake_overrides/override_main.kt"
+    doBeforeBuild {
+        konanc("$projectDir/link/private_fake_overrides/override_lib.kt -p library -target ${target.name} -o $dir/lib")
+        konanc("$projectDir/$source -target ${target.name} -o $dir/out -r $dir -l lib " +
+                "-Xtemporary-files-dir=$dir/tmp/split " +
+                "-Xwrite-dependencies-to=${dir}/split_compilation_pipeline.deps")
+    }
+    flags = ["-Xread-dependencies-from=${dir}/split_compilation_pipeline.deps", "-Xcompile-from-bitcode=${dir}/tmp/split/out.bc"]
+    useGoldenData = true
+}
+
 linkTest("private_fake_overrides_0") {
     source = "link/private_fake_overrides/inherit_main.kt"
     lib = "link/private_fake_overrides/inherit_lib.kt"