Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ use hashql_mir::{
context::MirContext,
def::{DefId, DefIdSlice, DefIdVec},
intern::Interner,
pass::{Changed, TransformPass as _, transform::Sroa},
pass::{Changed, TransformPass as _, transform::ForwardSubstitution},
};

use super::{
Expand All @@ -24,7 +24,7 @@ use crate::suite::{
mir_reify::{d2_output_enabled, mir_format_d2, mir_format_text, mir_spawn_d2},
};

pub(crate) fn mir_pass_transform_sroa<'heap>(
pub(crate) fn mir_pass_transform_forward_substitution<'heap>(
heap: &'heap Heap,
expr: Expr<'heap>,
interner: &Interner<'heap>,
Expand All @@ -42,7 +42,7 @@ pub(crate) fn mir_pass_transform_sroa<'heap>(
diagnostics: DiagnosticIssues::new(),
};

let mut pass = Sroa::new_in(&mut scratch);
let mut pass = ForwardSubstitution::new_in(&mut scratch);
for body in bodies.as_mut_slice() {
let _: Changed = pass.run(&mut context, body);
}
Expand All @@ -51,19 +51,19 @@ pub(crate) fn mir_pass_transform_sroa<'heap>(
Ok((root, bodies, scratch))
}

pub(crate) struct MirPassTransformSroa;
pub(crate) struct MirPassTransformForwardSubstitution;

impl Suite for MirPassTransformSroa {
impl Suite for MirPassTransformForwardSubstitution {
fn priority(&self) -> usize {
1
}

fn name(&self) -> &'static str {
"mir/pass/transform/sroa"
"mir/pass/transform/forward-substitution"
}

fn description(&self) -> &'static str {
"Scalar Replacement of Aggregates in the MIR"
"Forward Substitution in the MIR"
}

fn secondary_file_extensions(&self) -> &[&str] {
Expand All @@ -88,7 +88,7 @@ impl Suite for MirPassTransformSroa {
let mut buffer = Vec::new();
let mut d2 = d2_output_enabled(self, suite_directives, reports).then(mir_spawn_d2);

let (root, bodies, _) = mir_pass_transform_sroa(
let (root, bodies, _) = mir_pass_transform_forward_substitution(
heap,
expr,
&interner,
Expand All @@ -100,11 +100,15 @@ impl Suite for MirPassTransformSroa {
diagnostics,
)?;

let _ = writeln!(buffer, "\n{}\n", Header::new("MIR after SROA"));
let _ = writeln!(
buffer,
"\n{}\n",
Header::new("MIR after Forward Substitution")
);
mir_format_text(heap, &environment, &mut buffer, root, &bodies);

if let Some((mut writer, handle)) = d2 {
writeln!(writer, "final: 'MIR after SROA' {{")
writeln!(writer, "final: 'MIR after Forward Substitution' {{")
.expect("should be able to write to buffer");
mir_format_d2(heap, &environment, &mut writer, root, &bodies);
writeln!(writer, "}}").expect("should be able to write to buffer");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use hashql_mir::{

use super::{
RunContext, Suite, SuiteDiagnostic, common::process_issues,
mir_pass_transform_sroa::mir_pass_transform_sroa,
mir_pass_transform_forward_substitution::mir_pass_transform_forward_substitution,
};
use crate::suite::{
common::Header,
Expand All @@ -32,8 +32,14 @@ pub(crate) fn mir_pass_transform_inst_simplify<'heap>(
environment: &mut Environment<'heap>,
diagnostics: &mut Vec<SuiteDiagnostic>,
) -> Result<(DefId, DefIdVec<Body<'heap>>, Scratch), SuiteDiagnostic> {
let (root, mut bodies, mut scratch) =
mir_pass_transform_sroa(heap, expr, interner, render, environment, diagnostics)?;
let (root, mut bodies, mut scratch) = mir_pass_transform_forward_substitution(
heap,
expr,
interner,
render,
environment,
diagnostics,
)?;

let mut context = MirContext {
heap,
Expand Down
8 changes: 4 additions & 4 deletions libs/@local/hashql/compiletest/src/suite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ mod mir_pass_analysis_data_dependency;
mod mir_pass_transform_administrative_reduction;
mod mir_pass_transform_cfg_simplify;
mod mir_pass_transform_dse;
mod mir_pass_transform_forward_substitution;
mod mir_pass_transform_inst_simplify;
mod mir_pass_transform_sroa;
mod mir_reify;
mod parse_syntax_dump;

Expand Down Expand Up @@ -59,8 +59,8 @@ use self::{
mir_pass_transform_administrative_reduction::MirPassTransformAdministrativeReduction,
mir_pass_transform_cfg_simplify::MirPassTransformCfgSimplify,
mir_pass_transform_dse::MirPassTransformDse,
mir_pass_transform_inst_simplify::MirPassTransformInstSimplify,
mir_pass_transform_sroa::MirPassTransformSroa, mir_reify::MirReifySuite,
mir_pass_transform_forward_substitution::MirPassTransformForwardSubstitution,
mir_pass_transform_inst_simplify::MirPassTransformInstSimplify, mir_reify::MirReifySuite,
parse_syntax_dump::ParseSyntaxDumpSuite,
};
use crate::executor::TrialError;
Expand Down Expand Up @@ -159,8 +159,8 @@ const SUITES: &[&dyn Suite] = &[
&MirPassTransformAdministrativeReduction,
&MirPassTransformCfgSimplify,
&MirPassTransformDse,
&MirPassTransformForwardSubstitution,
&MirPassTransformInstSimplify,
&MirPassTransformSroa,
&MirReifySuite,
&ParseSyntaxDumpSuite,
];
Expand Down
36 changes: 26 additions & 10 deletions libs/@local/hashql/mir/benches/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use hashql_mir::{
op,
pass::{
TransformPass,
transform::{CfgSimplify, DeadStoreElimination, InstSimplify, Sroa},
transform::{CfgSimplify, DeadStoreElimination, ForwardSubstitution, InstSimplify},
},
};

Expand Down Expand Up @@ -385,17 +385,17 @@ fn cfg_simplify(criterion: &mut Criterion) {
});
}

fn sroa(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("sroa");
fn forward_substitution(criterion: &mut Criterion) {
let mut group = criterion.benchmark_group("forward_substitution");

group.bench_function("linear", |bencher| {
run(bencher, create_linear_cfg, Sroa::new());
run(bencher, create_linear_cfg, ForwardSubstitution::new());
});
group.bench_function("diamond", |bencher| {
run(bencher, create_diamond_cfg, Sroa::new());
run(bencher, create_diamond_cfg, ForwardSubstitution::new());
});
group.bench_function("complex", |bencher| {
run(bencher, create_complex_cfg, Sroa::new());
run(bencher, create_complex_cfg, ForwardSubstitution::new());
});
}

Expand Down Expand Up @@ -441,7 +441,10 @@ fn pipeline(criterion: &mut Criterion) {

run_bencher(bencher, create_linear_cfg, |context, body| {
let mut changed = CfgSimplify::new_in(&mut scratch).run(context, body);
changed = cmp::max(changed, Sroa::new_in(&mut scratch).run(context, body));
changed = cmp::max(
changed,
ForwardSubstitution::new_in(&mut scratch).run(context, body),
);
changed = cmp::max(changed, InstSimplify::new().run(context, body));
changed = cmp::max(
changed,
Expand All @@ -456,7 +459,10 @@ fn pipeline(criterion: &mut Criterion) {

run_bencher(bencher, create_diamond_cfg, |context, body| {
let mut changed = CfgSimplify::new_in(&mut scratch).run(context, body);
changed = cmp::max(changed, Sroa::new_in(&mut scratch).run(context, body));
changed = cmp::max(
changed,
ForwardSubstitution::new_in(&mut scratch).run(context, body),
);
changed = cmp::max(changed, InstSimplify::new().run(context, body));
changed = cmp::max(
changed,
Expand All @@ -471,7 +477,10 @@ fn pipeline(criterion: &mut Criterion) {

run_bencher(bencher, create_complex_cfg, |context, body| {
let mut changed = CfgSimplify::new_in(&mut scratch).run(context, body);
changed = cmp::max(changed, Sroa::new_in(&mut scratch).run(context, body));
changed = cmp::max(
changed,
ForwardSubstitution::new_in(&mut scratch).run(context, body),
);
changed = cmp::max(changed, InstSimplify::new().run(context, body));
changed = cmp::max(
changed,
Expand All @@ -483,5 +492,12 @@ fn pipeline(criterion: &mut Criterion) {
});
}

criterion_group!(benches, cfg_simplify, sroa, dse, inst_simplify, pipeline);
criterion_group!(
benches,
cfg_simplify,
forward_substitution,
dse,
inst_simplify,
pipeline
);
criterion_main!(benches);
4 changes: 2 additions & 2 deletions libs/@local/hashql/mir/src/pass/analysis/callgraph/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
//! `_1 = @fn; _2 = _1(...)`) produce an [`Opaque`] edge at the assignment site, not an
//! [`Apply`] edge at the call site.
//!
//! This is intentional: the analysis is designed to run after SROA, which propagates function
//! references through locals, eliminating most indirect call patterns.
//! This is intentional: the analysis is designed to run after forward substitution, which
//! propagates function references through locals, eliminating most indirect call patterns.
//!
//! [`Opaque`]: CallKind::Opaque
//! [`DataDependencyAnalysis`]: super::DataDependencyAnalysis
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ use crate::{
def::{DefId, DefIdSlice, DefIdVec},
pass::{
Changed, GlobalTransformPass, TransformPass, analysis::CallGraph,
transform::cp::propagate_block_params,
transform::copy_propagation::propagate_block_params,
},
visit::VisitorMut as _,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,32 @@
//! Copy and constant propagation transformation pass.
//!
//! This pass propagates constant values through the MIR by tracking which locals hold known
//! constants and substituting uses of those locals with the constant values directly.
//!
//! Unlike [`ForwardSubstitution`], this pass does not perform full data dependency analysis and
//! cannot resolve values through projections or chained access paths. It is faster but less
//! comprehensive, making it suitable for quick constant folding in simpler cases.
//!
//! # Algorithm
//!
//! The pass operates in a single forward traversal (reverse postorder):
//!
//! 1. For each block, propagates constants through block parameters when all predecessors pass the
//! same constant value
//! 2. For each assignment `_x = <operand>`, if the operand is a constant or a local known to hold a
//! constant, records that `_x` holds that constant
//! 3. For each use of a local known to hold a constant, substitutes the use with the constant
//!
//! # Limitations
//!
//! - Does not handle projections: `_2 = (_1,); use(_2.0)` is not simplified
//! - Does not perform fix-point iteration for loops
//! - Only tracks constants, not arbitrary value equivalences
//!
//! For more comprehensive value propagation including projections, see [`ForwardSubstitution`].
//!
//! [`ForwardSubstitution`]: super::ForwardSubstitution

#[cfg(test)]
mod tests;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ fn assert_cp_pass<'heap>(

let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
let mut settings = Settings::clone_current();
settings.set_snapshot_path(dir.join("tests/ui/pass/cp"));
settings.set_snapshot_path(dir.join("tests/ui/pass/copy_propagation"));
settings.set_prepend_module_to_snapshot(false);

let _drop = settings.bind_to_scope();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,24 @@
//! Scalar Replacement of Aggregates (SROA) transformation pass.
//! Forward substitution transformation pass.
//!
//! This pass resolves place operands to their ultimate sources by leveraging data dependency
//! analysis. It effectively "looks through" assignments, projections, and block parameters to
//! replace places with either:
//! substitute places with either:
//!
//! - **Constants**: When the place can be traced back to a constant value
//! - **Simplified places**: When the place can be traced to a simpler location
//!
//! This is a more comprehensive form of value propagation than [`CopyPropagation`], as it handles
//! projections, chained access paths, and closure environments through full data dependency
//! analysis.
//!
//! # Algorithm
//!
//! The pass operates by:
//!
//! 1. Running [`DataDependencyAnalysis`] to build a graph of data flow relationships
//! 2. Walking all operands in the MIR body
//! 3. For each place operand, resolving it through the dependency graph to find its source
//! 4. Replacing the operand with the resolved value (constant or simplified place)
//! 4. Substituting the operand with the resolved value (constant or simplified place)
//!
//! The resolution handles several cases:
//!
Expand Down Expand Up @@ -46,14 +50,19 @@
//!
//! # Interaction with Other Passes
//!
//! SROA runs after [`CfgSimplify`] in the optimization pipeline, which ensures that unreachable
//! code paths have been eliminated before resolution. This allows SROA to make more precise
//! determinations about constant values at join points.
//! Forward substitution runs after [`CfgSimplify`] in the optimization pipeline, which ensures
//! that unreachable code paths have been eliminated before resolution. This allows the pass to
//! make more precise determinations about constant values at join points.
//!
//! When combined with dead store elimination (DSE), forward substitution enables SROA-like
//! decomposition of aggregates: uses are substituted with their original values, and DSE
//! then removes the now-dead aggregate constructions.
//!
//! When block parameters receive the same constant from all predecessors, SROA resolves uses
//! of that parameter to the constant. When predecessors diverge (provide different constants),
//! the place is preserved unchanged.
//! When block parameters receive the same constant from all predecessors, forward substitution
//! resolves uses of that parameter to the constant. When predecessors diverge (provide different
//! constants), the place is preserved unchanged.
//!
//! [`CopyPropagation`]: super::CopyPropagation
//! [`DataDependencyAnalysis`]: crate::pass::analysis::DataDependencyAnalysis
//! [`CfgSimplify`]: super::CfgSimplify

Expand Down Expand Up @@ -105,16 +114,17 @@ impl<'heap, A: Allocator + Clone> VisitorMut<'heap> for PlaceVisitor<'_, 'heap,
}
}

/// Scalar Replacement of Aggregates transformation pass.
/// Forward substitution transformation pass.
///
/// Resolves place operands to their ultimate sources by tracing data dependencies. This enables
/// downstream passes to work with simplified operands and can expose constant propagation
/// opportunities.
pub struct Sroa<A: BumpAllocator = Scratch> {
/// Resolves place operands to their ultimate sources by tracing data dependencies through
/// projections, assignments, and block parameters. This enables downstream passes to work with
/// simplified operands and, when combined with dead store elimination, achieves SROA-like
/// decomposition of aggregates.
pub struct ForwardSubstitution<A: BumpAllocator = Scratch> {
alloc: A,
}

impl Sroa {
impl ForwardSubstitution {
#[must_use]
pub fn new() -> Self {
Self {
Expand All @@ -123,14 +133,14 @@ impl Sroa {
}
}

impl<A: BumpAllocator> Sroa<A> {
impl<A: BumpAllocator> ForwardSubstitution<A> {
#[must_use]
pub const fn new_in(alloc: A) -> Self {
Self { alloc }
}
}

impl<'env, 'heap, A: ResetAllocator> TransformPass<'env, 'heap> for Sroa<A> {
impl<'env, 'heap, A: ResetAllocator> TransformPass<'env, 'heap> for ForwardSubstitution<A> {
fn run(&mut self, context: &mut MirContext<'env, 'heap>, body: &mut Body<'heap>) -> Changed {
self.alloc.reset();

Expand All @@ -151,7 +161,7 @@ impl<'env, 'heap, A: ResetAllocator> TransformPass<'env, 'heap> for Sroa<A> {
}
}

impl Default for Sroa {
impl Default for ForwardSubstitution {
fn default() -> Self {
Self::new()
}
Expand Down
Loading
Loading