From 4bae6d794c6bda5cab4436ea961d25b7bc67de1f Mon Sep 17 00:00:00 2001 From: Sergey Bogolepov Date: Fri, 14 Oct 2022 16:52:31 +0300 Subject: [PATCH] [K/N] Introduce PhaseEngine and PhaseContext These two classes represents a core of a new dynamic K/N driver. --- .../kotlin/backend/konan/ToplevelPhases.kt | 6 +- .../kotlin/backend/konan/driver/Machinery.kt | 130 ++++++++++++++++++ 2 files changed, 133 insertions(+), 3 deletions(-) create mode 100644 kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/Machinery.kt diff --git a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt index 38034fa6024..227f7a5de34 100644 --- a/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/ToplevelPhases.kt @@ -513,15 +513,15 @@ internal val toplevelPhase: CompilerPhase = namedUnitPhase( val toplevelPhaseErased: CompilerPhase<*, Unit, Unit> get() = toplevelPhase -internal fun PhaseConfig.disableIf(phase: AnyNamedPhase, condition: Boolean) { +internal fun PhaseConfigurationService.disableIf(phase: AnyNamedPhase, condition: Boolean) { if (condition) disable(phase) } -internal fun PhaseConfig.disableUnless(phase: AnyNamedPhase, condition: Boolean) { +internal fun PhaseConfigurationService.disableUnless(phase: AnyNamedPhase, condition: Boolean) { if (!condition) disable(phase) } -internal fun PhaseConfig.konanPhasesConfig(config: KonanConfig) { +internal fun PhaseConfigurationService.konanPhasesConfig(config: KonanConfig) { with(config.configuration) { // The original comment around [checkSamSuperTypesPhase] still holds, but in order to be on par with JVM_IR // (which doesn't report error for these corner cases), we turn off the checker for now (the problem with variances 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 new file mode 100644 index 00000000000..2efd4452156 --- /dev/null +++ b/kotlin-native/backend.native/compiler/ir/backend.native/src/org/jetbrains/kotlin/backend/konan/driver/Machinery.kt @@ -0,0 +1,130 @@ +/* + * 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 org.jetbrains.kotlin.backend.common.ErrorReportingContext +import org.jetbrains.kotlin.backend.common.LoggingContext +import org.jetbrains.kotlin.backend.common.phaser.* +import org.jetbrains.kotlin.backend.konan.ConfigChecks +import org.jetbrains.kotlin.backend.konan.KonanConfig +import org.jetbrains.kotlin.backend.konan.getCompilerMessageLocation +import org.jetbrains.kotlin.backend.konan.konanPhasesConfig +import org.jetbrains.kotlin.cli.common.CLIConfigurationKeys +import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity +import org.jetbrains.kotlin.cli.common.messages.MessageCollector +import org.jetbrains.kotlin.ir.IrElement +import org.jetbrains.kotlin.ir.declarations.IrFile + +/** + * Context is a set of resources that is shared between different phases. PhaseContext is a "minimal context", + * effectively just a wrapper around [KonanConfig]. Still, it is more than enough in many cases. + * + * There is a fuzzy line between phase Input/Output and Context. We can consider them as a spectre: + * * On the one end there is a [org.jetbrains.kotlin.backend.konan.Context] (circa 1.8.0). It has a lot of properties, + * some even lateinit, which makes this object hard to construct and phases that depend on it are tightly coupled. + * But we don't need to pass data between phases explicitly, which makes code easier to write. + * * One the other end we can pass everything explicitly via I/O types. It will decouple code at the cost of boilerplate. + * + * So we have to find a point on this spectre for each phase. + * We still don't have a rule of thumb for deciding whether object should be a part of context or not. + * Some notes: + * * Lifetime of context should be as small as possible: it reduces memory usage and forces a clean architecture. + * * Frontend and backend are not really tied to IR and its friends, so we can pass more bytes via I/O. + * * On the other hand, middle- and bitcode phases are hard to decouple due to the way the code was written many years ago. + * It will take some time to rewrite it properly. + */ +internal interface PhaseContext : LoggingContext, ConfigChecks, ErrorReportingContext { + val messageCollector: MessageCollector + + /** + * Called by [PhaseEngine.useContext] after action completion to cleanup resources. + */ + fun dispose() +} + +internal open class BasicPhaseContext( + override val config: KonanConfig, +) : PhaseContext { + override var inVerbosePhase = false + override fun log(message: () -> String) { + if (inVerbosePhase) { + println(message()) + } + } + + override val messageCollector: MessageCollector + get() = config.configuration.getNotNull(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY) + + override fun report(element: IrElement?, irFile: IrFile?, message: String, isError: Boolean) { + val location = element?.getCompilerMessageLocation(irFile ?: error("irFile should be not null for $element")) + this.messageCollector.report( + if (isError) CompilerMessageSeverity.ERROR else CompilerMessageSeverity.WARNING, + message, location + ) + } + + override fun dispose() { + + } +} + +/** + * PhaseEngine is a heart of dynamic compiler driver. Unlike old static compiler driver that relies on predefined list of phases, + * dynamic one requires user to write a sequence of phases by hand (thus "dynamic"). PhaseEngine provides a framework for that by tracking + * phase configuration and state under the hood and exposing two methods: + * * [runPhase], well, executes a given phase. + * * [useContext] creates a child engine with a more specific [PhaseContext] that will be cleanup at the end of the call. + * This way, PhaseEngine forces user to create more specialized contexts that have a limited lifetime. + */ +internal class PhaseEngine( + private val phaseConfig: PhaseConfigurationService, + private val phaserState: PhaserState, + val context: C +) { + companion object { + fun startTopLevel(config: KonanConfig, body: (PhaseEngine) -> Unit) { + val phaserState = PhaserState() + val phaseConfig = config.flexiblePhaseConfig + val context = BasicPhaseContext(config) + // TODO: Get rid of when transition to the dynamic driver complete. + phaseConfig.konanPhasesConfig(config) + val topLevelPhase = object : SimpleNamedCompilerPhase( + "Compiler", + "The whole compilation process", + ) { + override fun phaseBody(context: PhaseContext, input: Any) { + val engine = PhaseEngine(phaseConfig, phaserState, context) + body(engine) + } + + override fun outputIfNotEnabled(phaseConfig: PhaseConfigurationService, phaserState: PhaserState, context: PhaseContext, input: Any) { + error("Compiler was disabled") + } + } + topLevelPhase.invoke(phaseConfig, phaserState, context, Unit) + } + } + + /** + * Switch to a more specific phase engine. + */ + inline fun useContext(newContext: T, action: (PhaseEngine) -> R): R { + val newEngine = PhaseEngine(phaseConfig, phaserState, newContext) + try { + return action(newEngine) + } finally { + newContext.dispose() + } + } + + fun runPhase( + phase: AbstractNamedCompilerPhase, + input: Input + ): Output { + // We lose sticky postconditions here, but it should be ok, since type is changed. + return phase.invoke(phaseConfig, phaserState.changePhaserStateType(), context, input) + } +} \ No newline at end of file