diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt index e6d9213b001..33791b24287 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/CommonCompilerArguments.kt @@ -238,6 +238,18 @@ abstract class CommonCompilerArguments : CommonToolArguments() { ) var profilePhases: Boolean by FreezableVar(false) + @Argument( + value = "-Xcheck-conditions", + description = "Check pre- and postconditions on phases" + ) + var checkPhaseConditions: Boolean by FreezableVar(false) + + @Argument( + value = "-Xcheck-sticky-conditions", + description = "Run sticky condition checks on subsequent phases as well. Implies -Xcheck-conditions" + ) + var checkStickyConditions: Boolean by FreezableVar(false) + open fun configureAnalysisFlags(collector: MessageCollector): MutableMap, Any> { return HashMap, Any>().apply { put(AnalysisFlags.skipMetadataVersionCheck, skipMetadataVersionCheck) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/common/arguments.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/common/arguments.kt index d3804b581fd..a793750bed4 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/common/arguments.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/common/arguments.kt @@ -53,6 +53,8 @@ fun CompilerConfiguration.setupCommonArguments( } put(CommonConfigurationKeys.PROFILE_PHASES, arguments.profilePhases) + put(CommonConfigurationKeys.CHECK_PHASE_CONDITIONS, arguments.checkPhaseConditions or arguments.checkStickyConditions) + put(CommonConfigurationKeys.CHECK_STICKY_CONDITIONS, arguments.checkStickyConditions) } fun CompilerConfiguration.setupLanguageVersionSettings(arguments: A) { diff --git a/compiler/frontend/src/org/jetbrains/kotlin/config/CommonConfigurationKeys.kt b/compiler/frontend/src/org/jetbrains/kotlin/config/CommonConfigurationKeys.kt index 26037f867d4..2b9db8b581d 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/config/CommonConfigurationKeys.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/config/CommonConfigurationKeys.kt @@ -72,6 +72,12 @@ object CommonConfigurationKeys { @JvmField val PROFILE_PHASES = CompilerConfigurationKey.create("profile backend phase execution") + @JvmField + val CHECK_PHASE_CONDITIONS = CompilerConfigurationKey.create("run pre- and postcondition checkers for phases") + + @JvmField + val CHECK_STICKY_CONDITIONS = CompilerConfigurationKey.create("run sticky postcondition checkers on subsequent phases as well") + @JvmField val EXCLUDED_ELEMENTS_FROM_DUMPING = CompilerConfigurationKey.create>("lowering elements which shouldn't be dumped at all") } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/CompilerPhase.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/CompilerPhase.kt index c431ced6974..275ce8a2799 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/CompilerPhase.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/CompilerPhase.kt @@ -8,22 +8,30 @@ package org.jetbrains.kotlin.backend.common.phaser import org.jetbrains.kotlin.backend.common.CommonBackendContext import kotlin.system.measureTimeMillis -class PhaserState { - val alreadyDone = mutableSetOf() - var depth = 0 -} +class PhaserState( + val alreadyDone: MutableSet = mutableSetOf(), + var depth: Int = 0, + val stickyPostconditions: MutableSet> = mutableSetOf() +) -fun PhaserState.downlevel(nlevels: Int = 1, block: () -> R): R { +// Copy state, forgetting the sticky postconditions (which will not be applicable to the new type) +fun PhaserState.changeType() = PhaserState(alreadyDone, depth, mutableSetOf()) + + +fun PhaserState.downlevel(nlevels: Int = 1, block: () -> R): R { depth += nlevels val result = block() depth -= nlevels return result } -interface CompilerPhase { - fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input): Output +interface CompilerPhase { + fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input): Output - fun getNamedSubphases(startDepth: Int = 0): List>> = emptyList() + fun getNamedSubphases(startDepth: Int = 0): List> = emptyList() + + // In phase trees, `stickyPostconditions` is inherited along the right edge to be used in `then`. + val stickyPostconditions: Set> get() = emptySet() } fun CompilerPhase.invokeToplevel( @@ -34,14 +42,18 @@ fun CompilerPhase : CompilerPhase -interface NamedCompilerPhase : CompilerPhase { +// A failing checker should just throw an exception. +typealias Checker = (Data) -> Unit + +interface NamedCompilerPhase : CompilerPhase { val name: String val description: String val prerequisite: Set get() = emptySet() + val preconditions: Set> + val postconditions: Set> } typealias AnyNamedPhase = NamedCompilerPhase<*, *, *> - enum class BeforeOrAfter { BEFORE, AFTER } interface PhaseDumperVerifier { @@ -53,13 +65,16 @@ abstract class AbstractNamedPhaseWrapper, - private val nlevels: Int = 0, - private val lower: CompilerPhase + private val lower: CompilerPhase, + override val preconditions: Set> = emptySet(), + override val postconditions: Set> = emptySet(), + override val stickyPostconditions: Set> = emptySet(), + private val nlevels: Int = 0 ) : NamedCompilerPhase { abstract val inputDumperVerifier: PhaseDumperVerifier abstract val outputDumperVerifier: PhaseDumperVerifier - override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input): Output { + override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input): Output { if (this is SameTypeCompilerPhase<*, *> && this !in phaseConfig.enabled ) { @@ -71,7 +86,7 @@ abstract class AbstractNamedPhaseWrapper, context: Context, input: Input): Output { return if (phaseConfig.needProfiling) { runAndProfile(phaseConfig, phaserState, context, input) } else { @@ -93,12 +111,20 @@ abstract class AbstractNamedPhaseWrapper, context: Context, output: Output) { checkAndRun(phaseConfig.toDumpStateAfter) { outputDumperVerifier.dump(this, context, output, BeforeOrAfter.AFTER) } checkAndRun(phaseConfig.toValidateStateAfter) { outputDumperVerifier.verify(context, output) } + if (phaseConfig.checkConditions) { + for (post in postconditions) post(output) + for (post in stickyPostconditions) post(output) + if (phaseConfig.checkStickyConditions && this is SameTypeCompilerPhase<*, *>) { + val phaserStateO = phaserState as PhaserState + for (post in phaserStateO.stickyPostconditions) post(output) + } + } } - private fun runAndProfile(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, source: Input): Output { + private fun runAndProfile(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, source: Input): Output { var result: Output? = null val msec = measureTimeMillis { result = phaserState.downlevel(nlevels) { @@ -124,10 +150,15 @@ class SameTypeNamedPhaseWrapper( name: String, description: String, prerequisite: Set, - nlevels: Int = 0, lower: CompilerPhase, + preconditions: Set> = emptySet(), + postconditions: Set> = emptySet(), + stickyPostconditions: Set> = lower.stickyPostconditions, + nlevels: Int = 0, val dumperVerifier: PhaseDumperVerifier -) : AbstractNamedPhaseWrapper(name, description, prerequisite, nlevels, lower), SameTypeCompilerPhase { +) : AbstractNamedPhaseWrapper( + name, description, prerequisite, lower, preconditions, postconditions, stickyPostconditions, nlevels +), SameTypeCompilerPhase { override val inputDumperVerifier get() = dumperVerifier override val outputDumperVerifier get() = dumperVerifier } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseBuilders.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseBuilders.kt index 23b7f757bce..d3829f0907e 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseBuilders.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseBuilders.kt @@ -8,155 +8,218 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment // Phase composition. infix fun CompilerPhase.then( - other: CompilerPhase + other: CompilerPhase ) = object : CompilerPhase { - override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input): Output = - this@then.invoke(phaseConfig, phaserState, context, input).let { mid -> - other.invoke(phaseConfig, phaserState, context, mid) - } + override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input): Output = + this@then.invoke(phaseConfig, phaserState, context, input).let { mid -> + val newPhaserState = if (other is SameTypeCompilerPhase<*, *>) + // Keep `stickyPostconditions`. + phaserState as PhaserState + else + // Discard `stickyPostcoditions`, they are useless since data type is changing. + phaserState.changeType() + newPhaserState.stickyPostconditions.addAll(this@then.stickyPostconditions) + other.invoke(phaseConfig, newPhaserState, context, mid) + } override fun getNamedSubphases(startDepth: Int) = - this@then.getNamedSubphases(startDepth) + other.getNamedSubphases(startDepth) + this@then.getNamedSubphases(startDepth) + other.getNamedSubphases(startDepth) + + override val stickyPostconditions get() = other.stickyPostconditions } fun namedIrModulePhase( - name: String, - description: String, - prerequisite: Set = emptySet(), - verify: (Context, IrModuleFragment) -> Unit = { _, _ -> }, - nlevels: Int = 1, - lower: CompilerPhase -) = SameTypeNamedPhaseWrapper(name, description, prerequisite, nlevels, lower, IrModuleDumperVerifier(verify)) + name: String, + description: String, + prerequisite: Set = emptySet(), + lower: CompilerPhase, + preconditions: Set> = emptySet(), + postconditions: Set> = emptySet(), + stickyPostconditions: Set> = lower.stickyPostconditions, + verify: (Context, IrModuleFragment) -> Unit = { _, _ -> }, + nlevels: Int = 1 +) = SameTypeNamedPhaseWrapper( + name, + description, + prerequisite, + lower, + preconditions, + postconditions, + stickyPostconditions, + nlevels, + IrModuleDumperVerifier(verify) +) fun namedIrFilePhase( - name: String, - description: String, - prerequisite: Set = emptySet(), - verify: (Context, IrFile) -> Unit = { _, _ -> }, - nlevels: Int = 1, - lower: CompilerPhase -) = SameTypeNamedPhaseWrapper(name, description, prerequisite, nlevels, lower, IrFileDumperVerifier(verify)) + name: String, + description: String, + prerequisite: Set = emptySet(), + lower: CompilerPhase, + preconditions: Set> = emptySet(), + postconditions: Set> = emptySet(), + stickyPostconditions: Set> = lower.stickyPostconditions, + verify: (Context, IrFile) -> Unit = { _, _ -> }, + nlevels: Int = 1 +) = SameTypeNamedPhaseWrapper( + name, + description, + prerequisite, + lower, + preconditions, + postconditions, + stickyPostconditions, + nlevels, + IrFileDumperVerifier(verify) +) fun namedUnitPhase( - name: String, - description: String, - prerequisite: Set = emptySet(), - nlevels: Int = 1, - lower: CompilerPhase -) = SameTypeNamedPhaseWrapper(name, description, prerequisite, nlevels, lower, EmptyDumperVerifier()) + name: String, + description: String, + prerequisite: Set = emptySet(), + nlevels: Int = 1, + lower: CompilerPhase +) = SameTypeNamedPhaseWrapper( + name, description, prerequisite, + lower = lower, + nlevels = nlevels, + dumperVerifier = EmptyDumperVerifier() +) fun namedOpUnitPhase( - name: String, - description: String, - prerequisite: Set, - op: Context.() -> Unit + name: String, + description: String, + prerequisite: Set, + op: Context.() -> Unit ) = namedUnitPhase( - name, description, prerequisite, - nlevels = 0, - lower = object : SameTypeCompilerPhase { - override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Unit) { - context.op() - } + name, description, prerequisite, + nlevels = 0, + lower = object : SameTypeCompilerPhase { + override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Unit) { + context.op() } + } ) fun performByIrFile( - name: String = "PerformByIrFile", - description: String = "Perform phases by IrFile", - prerequisite: Set = emptySet(), - verify: (Context, IrModuleFragment) -> Unit = { _, _ -> }, - lower: CompilerPhase + name: String = "PerformByIrFile", + description: String = "Perform phases by IrFile", + prerequisite: Set = emptySet(), + preconditions: Set> = emptySet(), + postconditions: Set> = emptySet(), + stickyPostconditions: Set> = emptySet(), + verify: (Context, IrModuleFragment) -> Unit = { _, _ -> }, + lower: CompilerPhase ) = namedIrModulePhase( - name, description, prerequisite, verify, - nlevels = 1, - lower = object : SameTypeCompilerPhase { - override fun invoke( - phaseConfig: PhaseConfig, - phaserState: PhaserState, - context: Context, - input: IrModuleFragment - ): IrModuleFragment { - for (irFile in input.files) { - lower.invoke(phaseConfig, phaserState, context, irFile) - } - - // TODO: no guarantee that module identity is preserved by `lower` - return input + name, description, prerequisite, + preconditions = preconditions, + postconditions = postconditions, + stickyPostconditions = stickyPostconditions, + verify = verify, + nlevels = 1, + lower = object : SameTypeCompilerPhase { + override fun invoke( + phaseConfig: PhaseConfig, + phaserState: PhaserState, + context: Context, + input: IrModuleFragment + ): IrModuleFragment { + for (irFile in input.files) { + lower.invoke(phaseConfig, phaserState.changeType(), context, irFile) } - override fun getNamedSubphases(startDepth: Int) = lower.getNamedSubphases(startDepth) + // TODO: no guarantee that module identity is preserved by `lower` + return input } + + override fun getNamedSubphases(startDepth: Int) = lower.getNamedSubphases(startDepth) + } ) fun makeIrFilePhase( - lowering: (Context) -> FileLoweringPass, - name: String, - description: String, - prerequisite: Set = emptySet(), - verify: (Context, IrFile) -> Unit = { _, _ -> } + lowering: (Context) -> FileLoweringPass, + name: String, + description: String, + prerequisite: Set = emptySet(), + preconditions: Set> = emptySet(), + postconditions: Set> = emptySet(), + stickyPostconditions: Set> = emptySet(), + verify: (Context, IrFile) -> Unit = { _, _ -> } ) = namedIrFilePhase( - name, description, prerequisite, verify, - nlevels = 0, - lower = object : SameTypeCompilerPhase { - override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: IrFile): IrFile { - lowering(context).lower(input) - return input - } + name, description, prerequisite, + preconditions = preconditions, + postconditions = postconditions, + stickyPostconditions = stickyPostconditions, + verify = verify, + nlevels = 0, + lower = object : SameTypeCompilerPhase { + override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: IrFile): IrFile { + lowering(context).lower(input) + return input } + } ) fun makeIrModulePhase( - lowering: (Context) -> FileLoweringPass, - name: String, - description: String, - prerequisite: Set = emptySet(), - verify: (Context, IrModuleFragment) -> Unit = { _, _ -> } + lowering: (Context) -> FileLoweringPass, + name: String, + description: String, + prerequisite: Set = emptySet(), + preconditions: Set> = emptySet(), + postconditions: Set> = emptySet(), + stickyPostconditions: Set> = emptySet(), + verify: (Context, IrModuleFragment) -> Unit = { _, _ -> } ) = namedIrModulePhase( - name, description, prerequisite, verify, - nlevels = 0, - lower = object : SameTypeCompilerPhase { - override fun invoke( - phaseConfig: PhaseConfig, - phaserState: PhaserState, - context: Context, - input: IrModuleFragment - ): IrModuleFragment { - lowering(context).lower(input) - return input - } + name, description, prerequisite, + preconditions=preconditions, + postconditions = postconditions, + stickyPostconditions = stickyPostconditions, + verify = verify, + nlevels = 0, + lower = object : SameTypeCompilerPhase { + override fun invoke( + phaseConfig: PhaseConfig, + phaserState: PhaserState, + context: Context, + input: IrModuleFragment + ): IrModuleFragment { + lowering(context).lower(input) + return input } + } ) fun unitPhase( - name: String, - description: String, - prerequisite: Set, - op: Context.() -> Unit + name: String, + description: String, + prerequisite: Set, + preconditions: Set>, + op: Context.() -> Unit ) = - object : AbstractNamedPhaseWrapper( - name, description, prerequisite, - nlevels = 0, - lower = object : CompilerPhase { - override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input) { - context.op() - } - } - ) { - override val inputDumperVerifier = EmptyDumperVerifier() - override val outputDumperVerifier = EmptyDumperVerifier() + object : AbstractNamedPhaseWrapper( + name, description, prerequisite, + preconditions = preconditions, + nlevels = 0, + lower = object : CompilerPhase { + override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input) { + context.op() + } } + ) { + override val inputDumperVerifier = EmptyDumperVerifier() + override val outputDumperVerifier = EmptyDumperVerifier() + } fun unitSink() = object : CompilerPhase { - override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input) {} + override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input) {} } // Intermediate phases to change the object of transformations fun takeFromContext(op: (Context) -> NewData) = - object : CompilerPhase { - override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: OldData) = op(context) - } + object : CompilerPhase { + override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: OldData) = op(context) + } fun transform(op: (OldData) -> NewData) = - object : CompilerPhase { - override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: OldData) = op(input) - } + object : CompilerPhase { + override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: OldData) = op(input) + } diff --git a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseConfig.kt b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseConfig.kt index 7079cd7a29d..0121d8a728a 100644 --- a/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseConfig.kt +++ b/compiler/ir/backend.common/src/org/jetbrains/kotlin/backend/common/phaser/PhaseConfig.kt @@ -35,6 +35,8 @@ class PhaseConfig(private val compoundPhase: CompilerPhase<*, *, *>, config: Com } val needProfiling = config.getBoolean(CommonConfigurationKeys.PROFILE_PHASES) + val checkConditions = config.getBoolean(CommonConfigurationKeys.CHECK_PHASE_CONDITIONS) + val checkStickyConditions = config.getBoolean(CommonConfigurationKeys.CHECK_STICKY_CONDITIONS) fun known(name: String): String { if (phases[name] == null) { diff --git a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt index 2c73700a80f..e42601b6618 100644 --- a/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt +++ b/compiler/ir/backend.js/src/org/jetbrains/kotlin/ir/backend/js/JsLoweringPhases.kt @@ -57,7 +57,7 @@ private fun makeCustomJsModulePhase( lower = object : SameTypeCompilerPhase { override fun invoke( phaseConfig: PhaseConfig, - phaserState: PhaserState, + phaserState: PhaserState, context: JsIrBackendContext, input: IrModuleFragment ): IrModuleFragment { diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt index 07e7a72ed30..5fd498df549 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmLower.kt @@ -26,7 +26,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid private fun makePatchParentsPhase(number: Int) = namedIrFilePhase( lower = object : SameTypeCompilerPhase { - override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: CommonBackendContext, input: IrFile): IrFile { + override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: CommonBackendContext, input: IrFile): IrFile { input.acceptVoid(PatchDeclarationParentsVisitor()) return input }