mod boolean_shells;
mod branch_value_folding;
mod carried_locals;
mod close_scopes;
mod closure_self_capture;
mod dead_labels;
mod dead_temps;
pub(super) mod decision;
mod expr_facts;
mod locals;
mod logical_simplify;
mod residuals;
mod table_constructors;
mod temp_inline;
mod temp_touch;
mod traverse;
mod visit;
mod walk;
use crate::debug::DebugFilters;
use crate::generate::GenerateMode;
use crate::hir::common::HirModule;
use crate::hir::promotion::ProtoPromotionFacts;
use crate::readability::ReadabilityOptions;
use crate::scheduler::{run_invalidation_loop, InvalidationTag, PassDescriptor, PassPhase};
use crate::timing::TimingCollector;
#[derive(Clone, Default)]
pub(crate) struct PassDumpConfig {
pub pass_names: Vec<String>,
pub filters: DebugFilters,
}
const MAX_SIMPLIFY_ITERATIONS: usize = 128;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
enum HirInvalidation {
DecisionShape,
BooleanPattern,
LogicalExpr,
TablePattern,
TempChain,
LocalBinding,
BlockStructure,
LabelGoto,
ClosureCapture,
}
impl InvalidationTag for HirInvalidation {
fn all() -> &'static [Self] {
&[
Self::DecisionShape,
Self::BooleanPattern,
Self::LogicalExpr,
Self::TablePattern,
Self::TempChain,
Self::LocalBinding,
Self::BlockStructure,
Self::LabelGoto,
Self::ClosureCapture,
]
}
}
use HirInvalidation::*;
const PASS_DESCRIPTORS: &[PassDescriptor<HirInvalidation>] = &[
PassDescriptor {
name: "decision",
phase: PassPhase::Normal,
depends_on: &[DecisionShape],
invalidates: &[DecisionShape, LogicalExpr, BooleanPattern],
},
PassDescriptor {
name: "boolean-shells",
phase: PassPhase::Normal,
depends_on: &[BooleanPattern, DecisionShape],
invalidates: &[BooleanPattern, TempChain],
},
PassDescriptor {
name: "logical-simplify",
phase: PassPhase::Normal,
depends_on: &[LogicalExpr, DecisionShape],
invalidates: &[LogicalExpr, DecisionShape],
},
PassDescriptor {
name: "table-constructors",
phase: PassPhase::Normal,
depends_on: &[TablePattern, LocalBinding],
invalidates: &[TablePattern],
},
PassDescriptor {
name: "closure-self-capture",
phase: PassPhase::Normal,
depends_on: &[ClosureCapture],
invalidates: &[ClosureCapture],
},
PassDescriptor {
name: "temp-inline",
phase: PassPhase::Normal,
depends_on: &[TempChain, DecisionShape, BooleanPattern, LogicalExpr],
invalidates: &[TempChain, LocalBinding],
},
PassDescriptor {
name: "locals",
phase: PassPhase::Normal,
depends_on: &[TempChain, LocalBinding, BlockStructure],
invalidates: &[LocalBinding, TempChain],
},
PassDescriptor {
name: "eliminate-decisions",
phase: PassPhase::Deferred,
depends_on: &[DecisionShape],
invalidates: &[DecisionShape],
},
PassDescriptor {
name: "close-scopes",
phase: PassPhase::Deferred,
depends_on: &[BlockStructure],
invalidates: &[BlockStructure, LocalBinding, TempChain],
},
PassDescriptor {
name: "carried-locals",
phase: PassPhase::Deferred,
depends_on: &[LocalBinding],
invalidates: &[LocalBinding],
},
PassDescriptor {
name: "dead-unresolved-temps",
phase: PassPhase::Deferred,
depends_on: &[TempChain],
invalidates: &[TempChain],
},
PassDescriptor {
name: "dead-labels",
phase: PassPhase::Deferred,
depends_on: &[LabelGoto],
invalidates: &[LabelGoto],
},
];
pub(super) fn simplify_hir(
module: &mut HirModule,
readability: ReadabilityOptions,
timings: &TimingCollector,
promotion_facts: &[ProtoPromotionFacts],
generate_mode: GenerateMode,
dialect: crate::ast::AstDialectVersion,
dump_config: &PassDumpConfig,
) {
let empty_facts = ProtoPromotionFacts::default();
let dump_active = !dump_config.pass_names.is_empty();
run_invalidation_loop(
PASS_DESCRIPTORS,
|index, name| {
let before_snapshots = if dump_active && dump_config.pass_names.iter().any(|p| p == name) {
Some(capture_hir_snapshots(module, &dump_config.filters))
} else {
None
};
let changed = timings.record(name, || {
apply_proto_pass(module, |proto| {
let facts = promotion_facts
.get(proto.id.index())
.unwrap_or(&empty_facts);
match index {
0 => decision::simplify_decision_exprs_in_proto(proto),
1 => boolean_shells::remove_boolean_materialization_shells_in_proto(proto),
2 => logical_simplify::simplify_logical_exprs_in_proto(proto),
3 => table_constructors::stabilize_table_constructors_in_proto(proto, dialect),
4 => closure_self_capture::resolve_recursive_closure_self_captures_in_proto(proto),
5 => temp_inline::inline_temps_in_proto_with_facts(proto, readability, facts),
6 => locals::promote_temps_to_locals_in_proto_with_facts(proto, facts),
7 => decision::eliminate_remaining_decisions_in_proto(proto),
8 => close_scopes::materialize_tbc_close_scopes_in_proto(proto),
9 => carried_locals::collapse_carried_local_handoffs_in_proto(proto),
10 => dead_temps::remove_dead_temp_materializations_in_proto(proto),
11 => dead_labels::remove_unused_labels_in_proto(proto),
_ => unreachable!("invalid HIR pass index: {index}"),
}
})
});
if let Some(before) = before_snapshots.filter(|_| changed) {
emit_hir_pass_diff(name, &before, module, &dump_config.filters);
}
changed
},
MAX_SIMPLIFY_ITERATIONS,
);
let residuals = residuals::collect_hir_exit_residuals(module);
if residuals.has_soft_residuals() && generate_mode != GenerateMode::Permissive {
residuals::emit_hir_warning(format!(
"HIR exit still contains residual nodes: decision={}, unresolved={}, \
fallback_unstructured={}, other_unstructured={}.",
residuals.decisions,
residuals.unresolved,
residuals.fallback_unstructured,
residuals.other_unstructured
));
}
}
fn apply_proto_pass(
module: &mut HirModule,
mut pass: impl FnMut(&mut crate::hir::common::HirProto) -> bool,
) -> bool {
let mut changed = false;
for proto in &mut module.protos {
changed |= pass(proto);
}
changed
}
fn capture_hir_snapshots(
module: &HirModule,
filters: &DebugFilters,
) -> Vec<(usize, String, bool)> {
let entries = super::debug::collect_hir_entries(module);
let plan = super::debug::plan_focus(&entries, filters);
if plan.focus.is_none() {
return Vec::new();
}
entries
.iter()
.filter_map(|entry| {
if plan.is_visible(entry.id) {
Some((
entry.proto.id.index(),
super::debug::dump_proto_snapshot(entry.proto),
true,
))
} else if plan.is_elided(entry.id) {
Some((
entry.proto.id.index(),
super::debug::dump_proto_snapshot(entry.proto),
false,
))
} else {
None
}
})
.collect()
}
fn emit_hir_pass_diff(
pass_name: &str,
before: &[(usize, String, bool)],
module: &HirModule,
filters: &DebugFilters,
) {
let after = capture_hir_snapshots(module, filters);
for ((idx, before_text, before_visible), (_, after_text, _)) in
before.iter().zip(after.iter())
{
if before_text == after_text {
continue;
}
if *before_visible {
eprintln!("=== [hir] pass={pass_name} proto#{idx} CHANGED ===");
eprintln!("--- before ---");
eprint!("{before_text}");
eprintln!("--- after ---");
eprint!("{after_text}");
eprintln!("=== end ===");
} else {
eprintln!("=== [hir] pass={pass_name} proto#{idx} CHANGED (elided) ===");
}
}
}
pub(crate) use decision::synthesize_readable_pure_logical_expr;