[K/N] Introduce PhaseEngine and PhaseContext

These two classes represents a core of a new dynamic K/N driver.
This commit is contained in:
Sergey Bogolepov
2022-10-14 16:52:31 +03:00
committed by Space Team
parent 0b2bc06870
commit 4bae6d794c
2 changed files with 133 additions and 3 deletions
@@ -513,15 +513,15 @@ internal val toplevelPhase: CompilerPhase<Context, Unit, Unit> = 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
@@ -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<C : PhaseContext>(
private val phaseConfig: PhaseConfigurationService,
private val phaserState: PhaserState<Any>,
val context: C
) {
companion object {
fun startTopLevel(config: KonanConfig, body: (PhaseEngine<PhaseContext>) -> Unit) {
val phaserState = PhaserState<Any>()
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<PhaseContext, Any, Unit>(
"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<Any>, context: PhaseContext, input: Any) {
error("Compiler was disabled")
}
}
topLevelPhase.invoke(phaseConfig, phaserState, context, Unit)
}
}
/**
* Switch to a more specific phase engine.
*/
inline fun <T : PhaseContext, R> useContext(newContext: T, action: (PhaseEngine<T>) -> R): R {
val newEngine = PhaseEngine(phaseConfig, phaserState, newContext)
try {
return action(newEngine)
} finally {
newContext.dispose()
}
}
fun <Input, Output> runPhase(
phase: AbstractNamedCompilerPhase<C, Input, Output>,
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)
}
}