Skip to content

Commit 23c0283

Browse files
authored
BE-239: HashQL: Implement DSE (Dead Store Elimination) pass for MIR (#8184)
1 parent 24aaf5c commit 23c0283

32 files changed

+2609
-30
lines changed
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
use std::io::Write as _;
2+
3+
use hashql_ast::node::expr::Expr;
4+
use hashql_core::{heap::Heap, r#type::environment::Environment};
5+
use hashql_diagnostics::DiagnosticIssues;
6+
use hashql_mir::{
7+
body::Body,
8+
context::MirContext,
9+
def::{DefId, DefIdSlice, DefIdVec},
10+
intern::Interner,
11+
pass::{TransformPass as _, transform::DeadStoreElimination},
12+
};
13+
14+
use super::{
15+
RunContext, Suite, SuiteDiagnostic, common::process_issues,
16+
mir_pass_transform_sroa::mir_pass_transform_sroa,
17+
};
18+
use crate::suite::{
19+
common::Header,
20+
mir_pass_transform_cfg_simplify::mir_pass_transform_cfg_simplify_default_renderer,
21+
mir_reify::{d2_output_enabled, mir_format_d2, mir_format_text, mir_spawn_d2},
22+
};
23+
24+
pub(crate) fn mir_pass_transform_dse<'heap>(
25+
heap: &'heap Heap,
26+
expr: Expr<'heap>,
27+
interner: &Interner<'heap>,
28+
render: impl FnOnce(&'heap Heap, &Environment<'heap>, DefId, &DefIdSlice<Body<'heap>>),
29+
environment: &mut Environment<'heap>,
30+
diagnostics: &mut Vec<SuiteDiagnostic>,
31+
) -> Result<(DefId, DefIdVec<Body<'heap>>), SuiteDiagnostic> {
32+
let (root, mut bodies) =
33+
mir_pass_transform_sroa(heap, expr, interner, render, environment, diagnostics)?;
34+
35+
let mut context = MirContext {
36+
heap,
37+
env: environment,
38+
interner,
39+
diagnostics: DiagnosticIssues::new(),
40+
};
41+
42+
let mut pass = DeadStoreElimination::new();
43+
for body in bodies.as_mut_slice() {
44+
pass.run(&mut context, body);
45+
}
46+
47+
process_issues(diagnostics, context.diagnostics)?;
48+
Ok((root, bodies))
49+
}
50+
51+
pub(crate) struct MirPassTransformDse;
52+
53+
impl Suite for MirPassTransformDse {
54+
fn priority(&self) -> usize {
55+
1
56+
}
57+
58+
fn name(&self) -> &'static str {
59+
"mir/pass/transform/dse"
60+
}
61+
62+
fn description(&self) -> &'static str {
63+
"Dead Store Elimination in the MIR"
64+
}
65+
66+
fn secondary_file_extensions(&self) -> &[&str] {
67+
&["svg"]
68+
}
69+
70+
fn run<'heap>(
71+
&self,
72+
RunContext {
73+
heap,
74+
diagnostics,
75+
suite_directives,
76+
reports,
77+
secondary_outputs,
78+
..
79+
}: RunContext<'_, 'heap>,
80+
expr: Expr<'heap>,
81+
) -> Result<String, SuiteDiagnostic> {
82+
let mut environment = Environment::new(heap);
83+
let interner = Interner::new(heap);
84+
85+
let mut buffer = Vec::new();
86+
let mut d2 = d2_output_enabled(self, suite_directives, reports).then(mir_spawn_d2);
87+
88+
let (root, bodies) = mir_pass_transform_dse(
89+
heap,
90+
expr,
91+
&interner,
92+
mir_pass_transform_cfg_simplify_default_renderer(
93+
&mut buffer,
94+
d2.as_mut().map(|(writer, _)| writer),
95+
),
96+
&mut environment,
97+
diagnostics,
98+
)?;
99+
100+
let _ = writeln!(buffer, "\n{}\n", Header::new("MIR after DSE"));
101+
mir_format_text(heap, &environment, &mut buffer, root, &bodies);
102+
103+
if let Some((mut writer, handle)) = d2 {
104+
writeln!(writer, "final: 'MIR after DSE' {{")
105+
.expect("should be able to write to buffer");
106+
mir_format_d2(heap, &environment, &mut writer, root, &bodies);
107+
writeln!(writer, "}}").expect("should be able to write to buffer");
108+
109+
writer.flush().expect("should be able to write to buffer");
110+
drop(writer);
111+
112+
let diagram = handle.join().expect("should be able to join handle");
113+
let diagram = String::from_utf8_lossy_owned(diagram);
114+
115+
secondary_outputs.insert("svg", diagram);
116+
}
117+
118+
Ok(String::from_utf8_lossy_owned(buffer))
119+
}
120+
}

libs/@local/hashql/compiletest/src/suite/mod.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ mod hir_lower_thunking;
2222
mod hir_reify;
2323
mod mir_pass_analysis_data_dependency;
2424
mod mir_pass_transform_cfg_simplify;
25+
mod mir_pass_transform_dse;
2526
mod mir_pass_transform_sroa;
2627
mod mir_reify;
2728
mod parse_syntax_dump;
@@ -54,8 +55,8 @@ use self::{
5455
hir_lower_thunking::HirLowerThunkingSuite, hir_reify::HirReifySuite,
5556
mir_pass_analysis_data_dependency::MirPassAnalysisDataDependency,
5657
mir_pass_transform_cfg_simplify::MirPassTransformCfgSimplify,
57-
mir_pass_transform_sroa::MirPassTransformSroa, mir_reify::MirReifySuite,
58-
parse_syntax_dump::ParseSyntaxDumpSuite,
58+
mir_pass_transform_dse::MirPassTransformDse, mir_pass_transform_sroa::MirPassTransformSroa,
59+
mir_reify::MirReifySuite, parse_syntax_dump::ParseSyntaxDumpSuite,
5960
};
6061
use crate::executor::TrialError;
6162

@@ -151,6 +152,7 @@ const SUITES: &[&dyn Suite] = &[
151152
&HirReifySuite,
152153
&MirPassAnalysisDataDependency,
153154
&MirPassTransformCfgSimplify,
155+
&MirPassTransformDse,
154156
&MirPassTransformSroa,
155157
&MirReifySuite,
156158
&ParseSyntaxDumpSuite,

libs/@local/hashql/core/src/id/bit_vec/mod.rs

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,17 @@
2525
//! - Removed used of `static_assert_size` macro in favor of `assert_eq!`.
2626
//! - Removed mentions of `IntervalSet`.
2727
//! - Implement `MixedBitSet::intersect`.
28+
//! - Implement `DenseBitSet::negate`
2829
#![expect(
2930
clippy::integer_division,
3031
clippy::integer_division_remainder_used,
3132
clippy::cast_possible_truncation,
3233
clippy::too_long_first_doc_paragraph
3334
)]
3435

35-
use alloc::rc::Rc;
36+
use alloc::{alloc::Global, rc::Rc};
3637
use core::{
38+
alloc::Allocator,
3739
fmt, iter,
3840
marker::PhantomData,
3941
ops::{BitAnd, BitAndAssign, BitOrAssign, Bound, Not, Range, RangeBounds, Shl},
@@ -359,6 +361,11 @@ impl<T: Id> DenseBitSet<T> {
359361
// out-of-domain bits, so we need to clear them.
360362
self.clear_excess_bits();
361363
}
364+
365+
pub fn negate(&mut self) {
366+
self.words.iter_mut().for_each(|word| *word = !*word);
367+
self.clear_excess_bits();
368+
}
362369
}
363370

364371
// dense REL dense
@@ -1751,9 +1758,9 @@ impl<R: Id, C: Id> fmt::Debug for BitMatrix<R, C> {
17511758
/// `R` and `C` are index types used to identify rows and columns respectively;
17521759
/// typically newtyped `usize` wrappers, but they can also just be `usize`.
17531760
#[derive(Clone)]
1754-
pub struct SparseBitMatrix<R, C> {
1761+
pub struct SparseBitMatrix<R, C, A: Allocator = Global> {
17551762
num_columns: usize,
1756-
rows: IdVec<R, Option<DenseBitSet<C>>>,
1763+
rows: IdVec<R, Option<DenseBitSet<C>>, A>,
17571764
}
17581765

17591766
impl<R: Id, C: Id> SparseBitMatrix<R, C> {
@@ -1765,6 +1772,17 @@ impl<R: Id, C: Id> SparseBitMatrix<R, C> {
17651772
rows: IdVec::new(),
17661773
}
17671774
}
1775+
}
1776+
1777+
impl<R: Id, C: Id, A: Allocator> SparseBitMatrix<R, C, A> {
1778+
/// Creates a new empty sparse bit matrix with no rows or columns.
1779+
#[must_use]
1780+
pub const fn new_in(num_columns: usize, alloc: A) -> Self {
1781+
Self {
1782+
num_columns,
1783+
rows: IdVec::new_in(alloc),
1784+
}
1785+
}
17681786

17691787
fn ensure_row(&mut self, row: R) -> &mut DenseBitSet<C> {
17701788
// Instantiate any missing rows up to and including row `row` with an empty `DenseBitSet`.
@@ -1848,6 +1866,20 @@ impl<R: Id, C: Id> SparseBitMatrix<R, C> {
18481866
self.rows.get(row)?.as_ref()
18491867
}
18501868

1869+
pub fn superset_row(&self, row: R, other: &DenseBitSet<C>) -> Option<bool> {
1870+
match self.rows.get(row) {
1871+
Some(Some(row)) => Some(row.superset(other)),
1872+
_ => None,
1873+
}
1874+
}
1875+
1876+
pub fn subset_row(&self, row: R, other: &DenseBitSet<C>) -> Option<bool> {
1877+
match self.rows.get(row) {
1878+
Some(Some(row)) => Some(other.superset(row)),
1879+
_ => None,
1880+
}
1881+
}
1882+
18511883
/// Intersects `row` with `set`. `set` can be either `DenseBitSet` or
18521884
/// `ChunkedBitSet`. Has no effect if `row` does not exist.
18531885
///

libs/@local/hashql/core/src/id/vec.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -370,7 +370,7 @@ where
370370
}
371371

372372
// Map-like APIs for IdVec<I, Option<T>>
373-
impl<I, T> IdVec<I, Option<T>>
373+
impl<I, T, A: Allocator> IdVec<I, Option<T>, A>
374374
where
375375
I: Id,
376376
{

libs/@local/hashql/mir/src/body/local.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use hashql_core::{id, span::SpanId, symbol::Symbol, r#type::TypeId};
88

99
id::newtype!(
10+
#[steppable]
1011
/// A unique identifier for a local variable in the HashQL MIR.
1112
///
1213
/// Local variables represent storage locations within a function's execution context.

libs/@local/hashql/mir/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
iter_intersperse,
2222
string_from_utf8_lossy_owned,
2323
try_trait_v2,
24+
step_trait,
25+
exact_length_collection
2426
)]
2527
#![expect(clippy::indexing_slicing)]
2628
extern crate alloc;

libs/@local/hashql/mir/src/pass/analysis/data_dependency/graph.rs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use alloc::alloc::Global;
22
use core::{alloc::Allocator, fmt};
33

44
use hashql_core::{
5-
graph::{LinkedGraph, NodeId, linked::IncidentEdges},
5+
graph::{LinkedGraph, NodeId, Predecessors as _, Successors as _, linked::IncidentEdges},
66
id::Id as _,
77
intern::Interned,
88
symbol::Symbol,
@@ -216,6 +216,14 @@ impl<'heap, A: Allocator> TransientDataDependencyGraph<'heap, A> {
216216
{
217217
self.graph.resolve(interner, place)
218218
}
219+
220+
pub fn depends_on(&self, local: Local) -> impl Iterator<Item = Local> {
221+
self.graph.depends_on(local)
222+
}
223+
224+
pub fn dependent_on(&self, local: Local) -> impl Iterator<Item = Local> {
225+
self.graph.dependent_on(local)
226+
}
219227
}
220228

221229
impl<A: Allocator> fmt::Display for TransientDataDependencyGraph<'_, A> {
@@ -236,6 +244,18 @@ pub struct DataDependencyGraph<'heap, A: Allocator = Global> {
236244
}
237245

238246
impl<'heap, A: Allocator> DataDependencyGraph<'heap, A> {
247+
pub fn depends_on(&self, local: Local) -> impl Iterator<Item = Local> {
248+
self.graph
249+
.successors(NodeId::new(local.as_usize()))
250+
.map(|node| Local::new(node.as_usize()))
251+
}
252+
253+
pub fn dependent_on(&self, local: Local) -> impl Iterator<Item = Local> {
254+
self.graph
255+
.predecessors(NodeId::new(local.as_usize()))
256+
.map(|node| Local::new(node.as_usize()))
257+
}
258+
239259
/// Creates a transient graph with all edges resolved to their ultimate sources.
240260
///
241261
/// Each edge in the original graph is traced through [`resolve`](Self::resolve), producing

0 commit comments

Comments
 (0)