Check pre- and postconditions on phases

This commit is contained in:
Georgy Bronnikov
2019-02-15 06:43:46 +03:00
parent 48433110a4
commit da13d3288e
8 changed files with 245 additions and 129 deletions
@@ -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<AnalysisFlag<*>, Any> {
return HashMap<AnalysisFlag<*>, Any>().apply {
put(AnalysisFlags.skipMetadataVersionCheck, skipMetadataVersionCheck)
@@ -53,6 +53,8 @@ fun <A : CommonCompilerArguments> 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 <A : CommonCompilerArguments> CompilerConfiguration.setupLanguageVersionSettings(arguments: A) {
@@ -72,6 +72,12 @@ object CommonConfigurationKeys {
@JvmField
val PROFILE_PHASES = CompilerConfigurationKey.create<Boolean>("profile backend phase execution")
@JvmField
val CHECK_PHASE_CONDITIONS = CompilerConfigurationKey.create<Boolean>("run pre- and postcondition checkers for phases")
@JvmField
val CHECK_STICKY_CONDITIONS = CompilerConfigurationKey.create<Boolean>("run sticky postcondition checkers on subsequent phases as well")
@JvmField
val EXCLUDED_ELEMENTS_FROM_DUMPING = CompilerConfigurationKey.create<Set<String>>("lowering elements which shouldn't be dumped at all")
}
@@ -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<AnyNamedPhase>()
var depth = 0
}
class PhaserState<Data>(
val alreadyDone: MutableSet<AnyNamedPhase> = mutableSetOf(),
var depth: Int = 0,
val stickyPostconditions: MutableSet<Checker<Data>> = mutableSetOf()
)
fun <R> PhaserState.downlevel(nlevels: Int = 1, block: () -> R): R {
// Copy state, forgetting the sticky postconditions (which will not be applicable to the new type)
fun <Input, Output> PhaserState<Input>.changeType() = PhaserState<Output>(alreadyDone, depth, mutableSetOf())
fun <R, D> PhaserState<D>.downlevel(nlevels: Int = 1, block: () -> R): R {
depth += nlevels
val result = block()
depth -= nlevels
return result
}
interface CompilerPhase<in Context : CommonBackendContext, in Input, out Output> {
fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input): Output
interface CompilerPhase<in Context : CommonBackendContext, Input, Output> {
fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<Input>, context: Context, input: Input): Output
fun getNamedSubphases(startDepth: Int = 0): List<Pair<Int, NamedCompilerPhase<*, *, *>>> = emptyList()
fun getNamedSubphases(startDepth: Int = 0): List<Pair<Int, AnyNamedPhase>> = emptyList()
// In phase trees, `stickyPostconditions` is inherited along the right edge to be used in `then`.
val stickyPostconditions: Set<Checker<Output>> get() = emptySet()
}
fun <Context: CommonBackendContext, Input, Output> CompilerPhase<Context, Input, Output>.invokeToplevel(
@@ -34,14 +42,18 @@ fun <Context: CommonBackendContext, Input, Output> CompilerPhase<Context, Input
interface SameTypeCompilerPhase<in Context: CommonBackendContext, Data> : CompilerPhase<Context, Data, Data>
interface NamedCompilerPhase<in Context : CommonBackendContext, in Input, out Output> : CompilerPhase<Context, Input, Output> {
// A failing checker should just throw an exception.
typealias Checker<Data> = (Data) -> Unit
interface NamedCompilerPhase<in Context : CommonBackendContext, Input, Output> : CompilerPhase<Context, Input, Output> {
val name: String
val description: String
val prerequisite: Set<AnyNamedPhase> get() = emptySet()
val preconditions: Set<Checker<Input>>
val postconditions: Set<Checker<Output>>
}
typealias AnyNamedPhase = NamedCompilerPhase<*, *, *>
enum class BeforeOrAfter { BEFORE, AFTER }
interface PhaseDumperVerifier<in Context : CommonBackendContext, Data> {
@@ -53,13 +65,16 @@ abstract class AbstractNamedPhaseWrapper<in Context : CommonBackendContext, Inpu
override val name: String,
override val description: String,
override val prerequisite: Set<AnyNamedPhase>,
private val nlevels: Int = 0,
private val lower: CompilerPhase<Context, Input, Output>
private val lower: CompilerPhase<Context, Input, Output>,
override val preconditions: Set<Checker<Input>> = emptySet(),
override val postconditions: Set<Checker<Output>> = emptySet(),
override val stickyPostconditions: Set<Checker<Output>> = emptySet(),
private val nlevels: Int = 0
) : NamedCompilerPhase<Context, Input, Output> {
abstract val inputDumperVerifier: PhaseDumperVerifier<Context, Input>
abstract val outputDumperVerifier: PhaseDumperVerifier<Context, Output>
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input): Output {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<Input>, context: Context, input: Input): Output {
if (this is SameTypeCompilerPhase<*, *> &&
this !in phaseConfig.enabled
) {
@@ -71,7 +86,7 @@ abstract class AbstractNamedPhaseWrapper<in Context : CommonBackendContext, Inpu
runBefore(phaseConfig, context, input)
val output = runBody(phaseConfig, phaserState, context, input)
runAfter(phaseConfig, context, output)
runAfter(phaseConfig, phaserState, context, output)
phaserState.alreadyDone.add(this)
@@ -81,9 +96,12 @@ abstract class AbstractNamedPhaseWrapper<in Context : CommonBackendContext, Inpu
private fun runBefore(phaseConfig: PhaseConfig, context: Context, input: Input) {
checkAndRun(phaseConfig.toDumpStateBefore) { inputDumperVerifier.dump(this, context, input, BeforeOrAfter.BEFORE) }
checkAndRun(phaseConfig.toValidateStateBefore) { inputDumperVerifier.verify(context, input) }
if (phaseConfig.checkConditions) {
for (pre in preconditions) pre(input)
}
}
private fun runBody(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input): Output {
private fun runBody(phaseConfig: PhaseConfig, phaserState: PhaserState<Input>, context: Context, input: Input): Output {
return if (phaseConfig.needProfiling) {
runAndProfile(phaseConfig, phaserState, context, input)
} else {
@@ -93,12 +111,20 @@ abstract class AbstractNamedPhaseWrapper<in Context : CommonBackendContext, Inpu
}
}
private fun runAfter(phaseConfig: PhaseConfig, context: Context, output: Output) {
private fun runAfter(phaseConfig: PhaseConfig, phaserState: PhaserState<Input>, 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<Output>
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<Input>, context: Context, source: Input): Output {
var result: Output? = null
val msec = measureTimeMillis {
result = phaserState.downlevel(nlevels) {
@@ -124,10 +150,15 @@ class SameTypeNamedPhaseWrapper<in Context : CommonBackendContext, Data>(
name: String,
description: String,
prerequisite: Set<AnyNamedPhase>,
nlevels: Int = 0,
lower: CompilerPhase<Context, Data, Data>,
preconditions: Set<Checker<Data>> = emptySet(),
postconditions: Set<Checker<Data>> = emptySet(),
stickyPostconditions: Set<Checker<Data>> = lower.stickyPostconditions,
nlevels: Int = 0,
val dumperVerifier: PhaseDumperVerifier<Context, Data>
) : AbstractNamedPhaseWrapper<Context, Data, Data>(name, description, prerequisite, nlevels, lower), SameTypeCompilerPhase<Context, Data> {
) : AbstractNamedPhaseWrapper<Context, Data, Data>(
name, description, prerequisite, lower, preconditions, postconditions, stickyPostconditions, nlevels
), SameTypeCompilerPhase<Context, Data> {
override val inputDumperVerifier get() = dumperVerifier
override val outputDumperVerifier get() = dumperVerifier
}
@@ -8,155 +8,218 @@ import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
// Phase composition.
infix fun <Context : CommonBackendContext, Input, Mid, Output> CompilerPhase<Context, Input, Mid>.then(
other: CompilerPhase<Context, Mid, Output>
other: CompilerPhase<Context, Mid, Output>
) = object : CompilerPhase<Context, Input, Output> {
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<Input>, 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<Mid>
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 <Context : CommonBackendContext> namedIrModulePhase(
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
verify: (Context, IrModuleFragment) -> Unit = { _, _ -> },
nlevels: Int = 1,
lower: CompilerPhase<Context, IrModuleFragment, IrModuleFragment>
) = SameTypeNamedPhaseWrapper(name, description, prerequisite, nlevels, lower, IrModuleDumperVerifier(verify))
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
lower: CompilerPhase<Context, IrModuleFragment, IrModuleFragment>,
preconditions: Set<Checker<IrModuleFragment>> = emptySet(),
postconditions: Set<Checker<IrModuleFragment>> = emptySet(),
stickyPostconditions: Set<Checker<IrModuleFragment>> = lower.stickyPostconditions,
verify: (Context, IrModuleFragment) -> Unit = { _, _ -> },
nlevels: Int = 1
) = SameTypeNamedPhaseWrapper(
name,
description,
prerequisite,
lower,
preconditions,
postconditions,
stickyPostconditions,
nlevels,
IrModuleDumperVerifier(verify)
)
fun <Context : CommonBackendContext> namedIrFilePhase(
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
verify: (Context, IrFile) -> Unit = { _, _ -> },
nlevels: Int = 1,
lower: CompilerPhase<Context, IrFile, IrFile>
) = SameTypeNamedPhaseWrapper(name, description, prerequisite, nlevels, lower, IrFileDumperVerifier(verify))
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
lower: CompilerPhase<Context, IrFile, IrFile>,
preconditions: Set<Checker<IrFile>> = emptySet(),
postconditions: Set<Checker<IrFile>> = emptySet(),
stickyPostconditions: Set<Checker<IrFile>> = lower.stickyPostconditions,
verify: (Context, IrFile) -> Unit = { _, _ -> },
nlevels: Int = 1
) = SameTypeNamedPhaseWrapper(
name,
description,
prerequisite,
lower,
preconditions,
postconditions,
stickyPostconditions,
nlevels,
IrFileDumperVerifier(verify)
)
fun <Context : CommonBackendContext> namedUnitPhase(
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
nlevels: Int = 1,
lower: CompilerPhase<Context, Unit, Unit>
) = SameTypeNamedPhaseWrapper(name, description, prerequisite, nlevels, lower, EmptyDumperVerifier())
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
nlevels: Int = 1,
lower: CompilerPhase<Context, Unit, Unit>
) = SameTypeNamedPhaseWrapper(
name, description, prerequisite,
lower = lower,
nlevels = nlevels,
dumperVerifier = EmptyDumperVerifier()
)
fun <Context : CommonBackendContext> namedOpUnitPhase(
name: String,
description: String,
prerequisite: Set<AnyNamedPhase>,
op: Context.() -> Unit
name: String,
description: String,
prerequisite: Set<AnyNamedPhase>,
op: Context.() -> Unit
) = namedUnitPhase(
name, description, prerequisite,
nlevels = 0,
lower = object : SameTypeCompilerPhase<Context, Unit> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Unit) {
context.op()
}
name, description, prerequisite,
nlevels = 0,
lower = object : SameTypeCompilerPhase<Context, Unit> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<Unit>, context: Context, input: Unit) {
context.op()
}
}
)
fun <Context : CommonBackendContext> performByIrFile(
name: String = "PerformByIrFile",
description: String = "Perform phases by IrFile",
prerequisite: Set<AnyNamedPhase> = emptySet(),
verify: (Context, IrModuleFragment) -> Unit = { _, _ -> },
lower: CompilerPhase<Context, IrFile, IrFile>
name: String = "PerformByIrFile",
description: String = "Perform phases by IrFile",
prerequisite: Set<AnyNamedPhase> = emptySet(),
preconditions: Set<Checker<IrModuleFragment>> = emptySet(),
postconditions: Set<Checker<IrModuleFragment>> = emptySet(),
stickyPostconditions: Set<Checker<IrModuleFragment>> = emptySet(),
verify: (Context, IrModuleFragment) -> Unit = { _, _ -> },
lower: CompilerPhase<Context, IrFile, IrFile>
) = namedIrModulePhase(
name, description, prerequisite, verify,
nlevels = 1,
lower = object : SameTypeCompilerPhase<Context, IrModuleFragment> {
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<Context, IrModuleFragment> {
override fun invoke(
phaseConfig: PhaseConfig,
phaserState: PhaserState<IrModuleFragment>,
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 <Context : CommonBackendContext> makeIrFilePhase(
lowering: (Context) -> FileLoweringPass,
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
verify: (Context, IrFile) -> Unit = { _, _ -> }
lowering: (Context) -> FileLoweringPass,
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
preconditions: Set<Checker<IrFile>> = emptySet(),
postconditions: Set<Checker<IrFile>> = emptySet(),
stickyPostconditions: Set<Checker<IrFile>> = emptySet(),
verify: (Context, IrFile) -> Unit = { _, _ -> }
) = namedIrFilePhase(
name, description, prerequisite, verify,
nlevels = 0,
lower = object : SameTypeCompilerPhase<Context, IrFile> {
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<Context, IrFile> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<IrFile>, context: Context, input: IrFile): IrFile {
lowering(context).lower(input)
return input
}
}
)
fun <Context : CommonBackendContext> makeIrModulePhase(
lowering: (Context) -> FileLoweringPass,
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
verify: (Context, IrModuleFragment) -> Unit = { _, _ -> }
lowering: (Context) -> FileLoweringPass,
name: String,
description: String,
prerequisite: Set<AnyNamedPhase> = emptySet(),
preconditions: Set<Checker<IrModuleFragment>> = emptySet(),
postconditions: Set<Checker<IrModuleFragment>> = emptySet(),
stickyPostconditions: Set<Checker<IrModuleFragment>> = emptySet(),
verify: (Context, IrModuleFragment) -> Unit = { _, _ -> }
) = namedIrModulePhase(
name, description, prerequisite, verify,
nlevels = 0,
lower = object : SameTypeCompilerPhase<Context, IrModuleFragment> {
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<Context, IrModuleFragment> {
override fun invoke(
phaseConfig: PhaseConfig,
phaserState: PhaserState<IrModuleFragment>,
context: Context,
input: IrModuleFragment
): IrModuleFragment {
lowering(context).lower(input)
return input
}
}
)
fun <Context : CommonBackendContext, Input> unitPhase(
name: String,
description: String,
prerequisite: Set<AnyNamedPhase>,
op: Context.() -> Unit
name: String,
description: String,
prerequisite: Set<AnyNamedPhase>,
preconditions: Set<Checker<Input>>,
op: Context.() -> Unit
) =
object : AbstractNamedPhaseWrapper<Context, Input, Unit>(
name, description, prerequisite,
nlevels = 0,
lower = object : CompilerPhase<Context, Input, Unit> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input) {
context.op()
}
}
) {
override val inputDumperVerifier = EmptyDumperVerifier<Context, Input>()
override val outputDumperVerifier = EmptyDumperVerifier<Context, Unit>()
object : AbstractNamedPhaseWrapper<Context, Input, Unit>(
name, description, prerequisite,
preconditions = preconditions,
nlevels = 0,
lower = object : CompilerPhase<Context, Input, Unit> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<Input>, context: Context, input: Input) {
context.op()
}
}
) {
override val inputDumperVerifier = EmptyDumperVerifier<Context, Input>()
override val outputDumperVerifier = EmptyDumperVerifier<Context, Unit>()
}
fun <Context : CommonBackendContext, Input> unitSink() = object : CompilerPhase<Context, Input, Unit> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: Input) {}
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<Input>, context: Context, input: Input) {}
}
// Intermediate phases to change the object of transformations
fun <Context : CommonBackendContext, OldData, NewData> takeFromContext(op: (Context) -> NewData) =
object : CompilerPhase<Context, OldData, NewData> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: OldData) = op(context)
}
object : CompilerPhase<Context, OldData, NewData> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<OldData>, context: Context, input: OldData) = op(context)
}
fun <Context : CommonBackendContext, OldData, NewData> transform(op: (OldData) -> NewData) =
object : CompilerPhase<Context, OldData, NewData> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: Context, input: OldData) = op(input)
}
object : CompilerPhase<Context, OldData, NewData> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<OldData>, context: Context, input: OldData) = op(input)
}
@@ -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) {
@@ -57,7 +57,7 @@ private fun makeCustomJsModulePhase(
lower = object : SameTypeCompilerPhase<JsIrBackendContext, IrModuleFragment> {
override fun invoke(
phaseConfig: PhaseConfig,
phaserState: PhaserState,
phaserState: PhaserState<IrModuleFragment>,
context: JsIrBackendContext,
input: IrModuleFragment
): IrModuleFragment {
@@ -26,7 +26,7 @@ import org.jetbrains.kotlin.ir.visitors.acceptVoid
private fun makePatchParentsPhase(number: Int) = namedIrFilePhase(
lower = object : SameTypeCompilerPhase<CommonBackendContext, IrFile> {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState, context: CommonBackendContext, input: IrFile): IrFile {
override fun invoke(phaseConfig: PhaseConfig, phaserState: PhaserState<IrFile>, context: CommonBackendContext, input: IrFile): IrFile {
input.acceptVoid(PatchDeclarationParentsVisitor())
return input
}