Working program output
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 c4f9189..3efffad 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
@@ -170,11 +170,10 @@
     }
 }
 
-internal fun insertAliasToEntryPoint(context: Context) {
+internal fun insertAliasToEntryPoint(context: PhaseContext, module: LLVMModuleRef) {
     val nomain = context.config.configuration.get(KonanConfigKeys.NOMAIN) ?: false
     if (context.config.produce != CompilerOutputKind.PROGRAM || nomain)
         return
-    val module = context.generationState.llvm.module
     val entryPointName = context.config.entryPointName
     val entryPoint = LLVMGetNamedFunction(module, entryPointName)
             ?: error("Module doesn't contain `$entryPointName`")
@@ -224,7 +223,7 @@
             context.bitcodeFileName = output
             // Insert `_main` after pipeline so we won't worry about optimizations
             // corrupting entry point.
-            insertAliasToEntryPoint(context)
+            insertAliasToEntryPoint(context, context.generationState.llvm.module)
             LLVMWriteBitcodeToFile(context.generationState.llvm.module, output)
         }
         CompilerOutputKind.LIBRARY -> {
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 e057dc4..d989c24 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
@@ -35,7 +35,7 @@
             usingNativeMemoryAllocator {
                 usingJvmCInteropCallbacks {
                     when (config.produce) {
-                        CompilerOutputKind.PROGRAM -> TODO()
+                        CompilerOutputKind.PROGRAM -> produceProgram(engine, config, environment)
                         CompilerOutputKind.DYNAMIC -> TODO()
                         CompilerOutputKind.STATIC -> TODO()
                         CompilerOutputKind.FRAMEWORK -> produceFramework(engine, config, environment)
@@ -120,4 +120,43 @@
             }
         }
     }
+
+    private fun produceProgram(engine: PhaseEngine<PhaseContext>, config: KonanConfig, environment: KotlinCoreEnvironment) {
+        val frontendResult = engine.useContext(FrontendContextImpl(config)) { frontendEngine ->
+            frontendEngine.runFrontend(environment)
+        }
+        if (frontendResult is FrontendPhaseResult.ShouldNotGenerateCode) {
+            return
+        }
+        require(frontendResult is FrontendPhaseResult.Full)
+        val psiToIrResult = run {
+            val symbolTable = SymbolTable(KonanIdSignaturer(KonanManglerDesc), IrFactoryImpl)
+            val psiToIrContext = PsiToContextImpl(config, frontendResult.moduleDescriptor, symbolTable)
+            engine.useContext(psiToIrContext) { psiToIrEngine ->
+                psiToIrEngine.runPsiToIr(frontendResult, isProducingLibrary = false)
+            }
+        }
+        // Let's "eat" Context step-by-step. We don't use it for frontend and psi2ir,
+        // but use it for lowerings and bitcode generation for now.
+        val context = Context(config).also {
+            it.populateAfterFrontend(frontendResult)
+            it.populateAfterPsiToIr(psiToIrResult)
+            it.objCExport = ObjCExport(it, null, null)
+        }
+        engine.useContext(context) { middleEndEngine ->
+            middleEndEngine.runPhase(context, functionsWithoutBoundCheck, Unit)
+            middleEndEngine.useContext(NativeGenerationState(context)) { nativeGenerationEngine ->
+                // TODO: Drop this property, use generation state separately.
+                context.generationState = nativeGenerationEngine.context
+                middleEndEngine.runBackendCodegen(context.irModule!!)
+                val module = context.generationState.llvm.module
+                insertAliasToEntryPoint(context, module)
+                val bitcodeFile = nativeGenerationEngine.writeBitcodeFile(module)
+                // TODO: These two phases should not use NativeGenerationEngine. Instead, they should use their own.
+                //  Probably separate, because in the future we want linker to accumulate results.
+                val objectFiles = nativeGenerationEngine.produceObjectFiles(bitcodeFile)
+                nativeGenerationEngine.linkObjectFiles(objectFiles, context.llvmModuleSpecification, context.coverage.enabled)
+            }
+        }
+    }
 }
\ No newline at end of file