Skip to content
Open
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 @@ -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_inst_simplify::mir_pass_transform_inst_simplify,
};
use crate::suite::{
common::Header,
Expand All @@ -33,7 +33,7 @@ pub(crate) fn mir_pass_transform_dse<'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)?;
mir_pass_transform_inst_simplify(heap, expr, interner, render, environment, diagnostics)?;

let mut context = MirContext {
heap,
Expand All @@ -42,6 +42,7 @@ pub(crate) fn mir_pass_transform_dse<'heap>(
diagnostics: DiagnosticIssues::new(),
};

// CFG -> SROA -> Inst -> DSE
let mut pass = DeadStoreElimination::new_in(&mut scratch);
for body in bodies.as_mut_slice() {
pass.run(&mut context, body);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use std::io::Write as _;

use hashql_ast::node::expr::Expr;
use hashql_core::{
heap::{Heap, Scratch},
r#type::environment::Environment,
};
use hashql_diagnostics::DiagnosticIssues;
use hashql_mir::{
body::Body,
context::MirContext,
def::{DefId, DefIdSlice, DefIdVec},
intern::Interner,
pass::{TransformPass as _, transform::InstSimplify},
};

use super::{
RunContext, Suite, SuiteDiagnostic, common::process_issues,
mir_pass_transform_sroa::mir_pass_transform_sroa,
};
use crate::suite::{
common::Header,
mir_pass_transform_cfg_simplify::mir_pass_transform_cfg_simplify_default_renderer,
mir_reify::{d2_output_enabled, mir_format_d2, mir_format_text, mir_spawn_d2},
};

pub(crate) fn mir_pass_transform_inst_simplify<'heap>(
heap: &'heap Heap,
expr: Expr<'heap>,
interner: &Interner<'heap>,
render: impl FnOnce(&'heap Heap, &Environment<'heap>, DefId, &DefIdSlice<Body<'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 mut context = MirContext {
heap,
env: environment,
interner,
diagnostics: DiagnosticIssues::new(),
};

let mut pass = InstSimplify::new_in(&mut scratch);
for body in bodies.as_mut_slice() {
pass.run(&mut context, body);
}

process_issues(diagnostics, context.diagnostics)?;
Ok((root, bodies, scratch))
}

pub(crate) struct MirPassTransformInstSimplify;

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

fn name(&self) -> &'static str {
"mir/pass/transform/inst-simplify"
}

fn description(&self) -> &'static str {
"Instruction Simplification in the MIR"
}

fn secondary_file_extensions(&self) -> &[&str] {
&["svg"]
}

fn run<'heap>(
&self,
RunContext {
heap,
diagnostics,
suite_directives,
reports,
secondary_outputs,
..
}: RunContext<'_, 'heap>,
expr: Expr<'heap>,
) -> Result<String, SuiteDiagnostic> {
let mut environment = Environment::new(heap);
let interner = Interner::new(heap);

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_inst_simplify(
heap,
expr,
&interner,
mir_pass_transform_cfg_simplify_default_renderer(
&mut buffer,
d2.as_mut().map(|(writer, _)| writer),
),
&mut environment,
diagnostics,
)?;

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

if let Some((mut writer, handle)) = d2 {
writeln!(writer, "final: 'MIR after InstSimplify' {{")
.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");

writer.flush().expect("should be able to write to buffer");
drop(writer);

let diagram = handle.join().expect("should be able to join handle");
let diagram = String::from_utf8_lossy_owned(diagram);

secondary_outputs.insert("svg", diagram);
}

Ok(String::from_utf8_lossy_owned(buffer))
}
}
8 changes: 6 additions & 2 deletions libs/@local/hashql/compiletest/src/suite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ mod hir_reify;
mod mir_pass_analysis_data_dependency;
mod mir_pass_transform_cfg_simplify;
mod mir_pass_transform_dse;
mod mir_pass_transform_inst_simplify;
mod mir_pass_transform_sroa;
mod mir_reify;
mod parse_syntax_dump;
Expand Down Expand Up @@ -55,8 +56,10 @@ use self::{
hir_lower_thunking::HirLowerThunkingSuite, hir_reify::HirReifySuite,
mir_pass_analysis_data_dependency::MirPassAnalysisDataDependency,
mir_pass_transform_cfg_simplify::MirPassTransformCfgSimplify,
mir_pass_transform_dse::MirPassTransformDse, mir_pass_transform_sroa::MirPassTransformSroa,
mir_reify::MirReifySuite, parse_syntax_dump::ParseSyntaxDumpSuite,
mir_pass_transform_dse::MirPassTransformDse,
mir_pass_transform_inst_simplify::MirPassTransformInstSimplify,
mir_pass_transform_sroa::MirPassTransformSroa, mir_reify::MirReifySuite,
parse_syntax_dump::ParseSyntaxDumpSuite,
};
use crate::executor::TrialError;

Expand Down Expand Up @@ -153,6 +156,7 @@ const SUITES: &[&dyn Suite] = &[
&MirPassAnalysisDataDependency,
&MirPassTransformCfgSimplify,
&MirPassTransformDse,
&MirPassTransformInstSimplify,
&MirPassTransformSroa,
&MirReifySuite,
&ParseSyntaxDumpSuite,
Expand Down
22 changes: 21 additions & 1 deletion libs/@local/hashql/core/src/id/vec.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use alloc::{alloc::Global, vec};
use core::{
alloc::Allocator,
alloc::{AllocError, Allocator},
borrow::{Borrow, BorrowMut},
cmp::Ordering,
fmt::{self, Debug},
Expand All @@ -10,6 +10,7 @@ use core::{
};

use super::{Id, slice::IdSlice};
use crate::heap::TryCloneIn;

/// A growable vector that uses typed IDs for indexing instead of raw `usize` values.
///
Expand Down Expand Up @@ -558,3 +559,22 @@ where
Self::new()
}
}

impl<I, T, A, B> TryCloneIn<B> for IdVec<I, T, A>
where
I: Id,
T: Clone,
A: Allocator,
B: Allocator,
{
type Cloned = IdVec<I, T, B>;

#[inline]
fn try_clone_in(&self, allocator: B) -> Result<Self::Cloned, AllocError> {
self.raw.try_clone_in(allocator).map(IdVec::from_raw)
}

fn try_clone_into(&self, into: &mut Self::Cloned, allocator: B) -> Result<(), AllocError> {
self.raw.try_clone_into(&mut into.raw, allocator)
}
}
83 changes: 80 additions & 3 deletions libs/@local/hashql/mir/benches/transform.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
#![expect(
clippy::min_ident_chars,
clippy::many_single_char_names,
clippy::significant_drop_tightening
clippy::significant_drop_tightening,
clippy::similar_names
)]

use core::hint::black_box;
Expand All @@ -20,7 +21,7 @@ use hashql_mir::{
op,
pass::{
TransformPass,
transform::{CfgSimplify, DeadStoreElimination, Sroa},
transform::{CfgSimplify, DeadStoreElimination, InstSimplify, Sroa},
},
};

Expand Down Expand Up @@ -151,6 +152,62 @@ fn create_dead_store_cfg<'heap>(
builder.finish(0, TypeBuilder::synthetic(env).integer())
}

/// Creates a body with patterns that `InstSimplify` can optimize.
///
/// Structure:
/// ```text
/// bb0:
/// a = 1 == 2 // const fold -> false
/// b = 3 < 5 // const fold -> true
/// c = b && true // identity -> b
/// x = 42
/// y = x & x // idempotent -> x
/// d = x == x // identical operand -> true
/// e = a || c // const fold (a=false, c=true) -> true
/// f = e && d // const fold -> true
/// return f
/// ```
fn create_inst_simplify_cfg<'heap>(
env: &Environment<'heap>,
interner: &Interner<'heap>,
) -> Body<'heap> {
let mut builder = BodyBuilder::new(interner);
let int_ty = TypeBuilder::synthetic(env).integer();
let bool_ty = TypeBuilder::synthetic(env).boolean();

let a = builder.local("a", bool_ty);
let b = builder.local("b", bool_ty);
let c = builder.local("c", bool_ty);
let x = builder.local("x", int_ty);
let y = builder.local("y", int_ty);
let d = builder.local("d", bool_ty);
let e = builder.local("e", bool_ty);
let f = builder.local("f", bool_ty);

let const_1 = builder.const_int(1);
let const_2 = builder.const_int(2);
let const_3 = builder.const_int(3);
let const_5 = builder.const_int(5);
let const_42 = builder.const_int(42);
let const_true = builder.const_bool(true);

let bb0 = builder.reserve_block([]);

builder
.build_block(bb0)
.assign_place(a, |rv| rv.binary(const_1, op![==], const_2))
.assign_place(b, |rv| rv.binary(const_3, op![<], const_5))
.assign_place(c, |rv| rv.binary(b, op![&], const_true))
.assign_place(x, |rv| rv.load(const_42))
.assign_place(y, |rv| rv.binary(x, op![&], x))
.assign_place(d, |rv| rv.binary(x, op![==], x))
.assign_place(e, |rv| rv.binary(a, op![|], c))
.assign_place(f, |rv| rv.binary(e, op![&], d))
.ret(f);

builder.finish(0, TypeBuilder::synthetic(env).boolean())
}

/// Creates a larger CFG with multiple branches and join points for more realistic benchmarking.
///
/// Structure:
Expand Down Expand Up @@ -359,6 +416,23 @@ fn dse(criterion: &mut Criterion) {
});
}

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

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

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

Expand All @@ -368,6 +442,7 @@ fn pipeline(criterion: &mut Criterion) {
run_bencher(bencher, create_linear_cfg, |context, body| {
CfgSimplify::new_in(&mut scratch).run(context, body);
Sroa::new_in(&mut scratch).run(context, body);
InstSimplify::new().run(context, body);
DeadStoreElimination::new_in(&mut scratch).run(context, body);
});
});
Expand All @@ -377,6 +452,7 @@ fn pipeline(criterion: &mut Criterion) {
run_bencher(bencher, create_diamond_cfg, |context, body| {
CfgSimplify::new_in(&mut scratch).run(context, body);
Sroa::new_in(&mut scratch).run(context, body);
InstSimplify::new().run(context, body);
DeadStoreElimination::new_in(&mut scratch).run(context, body);
});
});
Expand All @@ -386,10 +462,11 @@ fn pipeline(criterion: &mut Criterion) {
run_bencher(bencher, create_complex_cfg, |context, body| {
CfgSimplify::new_in(&mut scratch).run(context, body);
Sroa::new_in(&mut scratch).run(context, body);
InstSimplify::new().run(context, body);
DeadStoreElimination::new_in(&mut scratch).run(context, body);
});
});
}

criterion_group!(benches, cfg_simplify, sroa, dse, pipeline);
criterion_group!(benches, cfg_simplify, sroa, dse, inst_simplify, pipeline);
criterion_main!(benches);
2 changes: 1 addition & 1 deletion libs/@local/hashql/mir/src/body/basic_blocks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ impl graph::Successors for BasicBlocks<'_> {

impl graph::Predecessors for BasicBlocks<'_> {
type PredIter<'this>
= impl ExactSizeIterator<Item = BasicBlockId> + DoubleEndedIterator
= impl ExactSizeIterator<Item = BasicBlockId> + DoubleEndedIterator + Clone
where
Self: 'this;

Expand Down
5 changes: 3 additions & 2 deletions libs/@local/hashql/mir/src/body/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use core::{

use hashql_core::{id, intern::Interned, symbol::Symbol, r#type::TypeId};

use super::local::{Local, LocalDecl, LocalVec};
use super::local::{Local, LocalDecl, LocalSlice};
use crate::intern::Interner;

id::newtype!(
Expand Down Expand Up @@ -378,7 +378,8 @@ impl<'heap> Place<'heap> {
}

/// Return the type of the place after applying all projections.
pub fn type_id<A: Allocator>(&self, decl: &LocalVec<LocalDecl<'heap>, A>) -> TypeId {
#[must_use]
pub fn type_id(&self, decl: &LocalSlice<LocalDecl<'heap>>) -> TypeId {
self.projections
.last()
.map_or_else(|| decl[self.local].r#type, |projection| projection.r#type)
Expand Down
5 changes: 5 additions & 0 deletions libs/@local/hashql/mir/src/body/terminator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,11 @@ pub enum TerminatorKind<'heap> {
}

impl<'heap> TerminatorKind<'heap> {
#[must_use]
pub const fn is_effectful(&self) -> bool {
matches!(self, Self::GraphRead(_))
}

/// Returns an iterator over the basic block IDs of all possible successors.
///
/// The returned iterator yields the [`BasicBlockId`] of each block that control
Expand Down
Loading
Loading