Skip to content

Commit 24aaf5c

Browse files
authored
BE-229, BE-230: HashQL: Implement SROA (Scalar Replacement of Aggregates) pass for MIR (#8183)
1 parent 862de3b commit 24aaf5c

21 files changed

+1673
-2
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::Sroa},
12+
};
13+
14+
use super::{
15+
RunContext, Suite, SuiteDiagnostic, common::process_issues,
16+
mir_pass_transform_cfg_simplify::mir_pass_transform_cfg_simplify,
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_sroa<'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_cfg_simplify(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 = Sroa::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 MirPassTransformSroa;
52+
53+
impl Suite for MirPassTransformSroa {
54+
fn priority(&self) -> usize {
55+
1
56+
}
57+
58+
fn name(&self) -> &'static str {
59+
"mir/pass/transform/sroa"
60+
}
61+
62+
fn description(&self) -> &'static str {
63+
"Scalar Replacement of Aggregates 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_sroa(
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 SROA"));
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 SROA' {{")
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 & 1 deletion
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_sroa;
2526
mod mir_reify;
2627
mod parse_syntax_dump;
2728

@@ -52,7 +53,8 @@ use self::{
5253
hir_lower_specialization::HirLowerSpecializationSuite,
5354
hir_lower_thunking::HirLowerThunkingSuite, hir_reify::HirReifySuite,
5455
mir_pass_analysis_data_dependency::MirPassAnalysisDataDependency,
55-
mir_pass_transform_cfg_simplify::MirPassTransformCfgSimplify, mir_reify::MirReifySuite,
56+
mir_pass_transform_cfg_simplify::MirPassTransformCfgSimplify,
57+
mir_pass_transform_sroa::MirPassTransformSroa, mir_reify::MirReifySuite,
5658
parse_syntax_dump::ParseSyntaxDumpSuite,
5759
};
5860
use crate::executor::TrialError;
@@ -149,6 +151,7 @@ const SUITES: &[&dyn Suite] = &[
149151
&HirReifySuite,
150152
&MirPassAnalysisDataDependency,
151153
&MirPassTransformCfgSimplify,
154+
&MirPassTransformSroa,
152155
&MirReifySuite,
153156
&ParseSyntaxDumpSuite,
154157
];
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,15 @@
3737
//! - `heap::VecDeque<'heap, T>`: A double-ended queue allocated on the heap
3838
//! - `heap::HashMap<'heap, K, V, S>`: A hash map allocated on the heap
3939
40+
mod scratch;
41+
4042
use core::{alloc::Allocator, ptr};
4143
use std::sync::Mutex;
4244

4345
use bumpalo::Bump;
4446
use hashbrown::HashSet;
4547

48+
pub use self::scratch::Scratch;
4649
use crate::{
4750
collections::{FastHashSet, fast_hash_set_with_capacity},
4851
symbol::{Symbol, sym::TABLES},
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
//! Scratch allocator for temporary allocations.
2+
//!
3+
//! This module provides [`Scratch`], a resettable bump allocator designed for
4+
//! temporary allocations that can be bulk-freed by calling [`Scratch::reset`].
5+
6+
use core::alloc::Allocator;
7+
8+
use bumpalo::Bump;
9+
10+
/// A resettable scratch allocator for temporary allocations.
11+
///
12+
/// `Scratch` wraps a [`Bump`] allocator, providing a simple interface for
13+
/// allocating memory that can be efficiently freed in bulk. This is useful
14+
/// for temporary allocations during query processing where individual
15+
/// deallocations are unnecessary.
16+
///
17+
/// # Usage
18+
///
19+
/// The allocator can be used directly with collections via the [`Allocator`] trait:
20+
///
21+
/// ```
22+
/// # #![feature(allocator_api)]
23+
/// # use hashql_core::heap::Scratch;
24+
/// let mut scratch = Scratch::new();
25+
/// let mut vec: Vec<u32, &Scratch> = Vec::new_in(&scratch);
26+
/// vec.push(42);
27+
/// # drop(vec);
28+
/// // When done, reset to free all allocations at once
29+
/// scratch.reset();
30+
/// ```
31+
#[derive(Debug)]
32+
pub struct Scratch {
33+
bump: Bump,
34+
}
35+
36+
impl Scratch {
37+
/// Creates a new scratch allocator with an empty arena.
38+
#[must_use]
39+
pub fn new() -> Self {
40+
Self { bump: Bump::new() }
41+
}
42+
43+
/// Resets the allocator, freeing all allocations at once.
44+
pub fn reset(&mut self) {
45+
self.bump.reset();
46+
}
47+
}
48+
49+
impl Default for Scratch {
50+
fn default() -> Self {
51+
Self::new()
52+
}
53+
}
54+
55+
#[expect(unsafe_code, reason = "proxy to bump")]
56+
// SAFETY: this simply delegates to the bump allocator
57+
unsafe impl Allocator for &Scratch {
58+
fn allocate_zeroed(
59+
&self,
60+
layout: core::alloc::Layout,
61+
) -> Result<core::ptr::NonNull<[u8]>, core::alloc::AllocError> {
62+
(&self.bump).allocate_zeroed(layout)
63+
}
64+
65+
unsafe fn grow(
66+
&self,
67+
ptr: core::ptr::NonNull<u8>,
68+
old_layout: core::alloc::Layout,
69+
new_layout: core::alloc::Layout,
70+
) -> Result<core::ptr::NonNull<[u8]>, core::alloc::AllocError> {
71+
// SAFETY: this simply delegates to the bump allocator
72+
unsafe { (&self.bump).grow(ptr, old_layout, new_layout) }
73+
}
74+
75+
unsafe fn grow_zeroed(
76+
&self,
77+
ptr: core::ptr::NonNull<u8>,
78+
old_layout: core::alloc::Layout,
79+
new_layout: core::alloc::Layout,
80+
) -> Result<core::ptr::NonNull<[u8]>, core::alloc::AllocError> {
81+
// SAFETY: this simply delegates to the bump allocator
82+
unsafe { (&self.bump).grow_zeroed(ptr, old_layout, new_layout) }
83+
}
84+
85+
unsafe fn shrink(
86+
&self,
87+
ptr: core::ptr::NonNull<u8>,
88+
old_layout: core::alloc::Layout,
89+
new_layout: core::alloc::Layout,
90+
) -> Result<core::ptr::NonNull<[u8]>, core::alloc::AllocError> {
91+
// SAFETY: this simply delegates to the bump allocator
92+
unsafe { (&self.bump).shrink(ptr, old_layout, new_layout) }
93+
}
94+
95+
fn allocate(
96+
&self,
97+
layout: core::alloc::Layout,
98+
) -> Result<core::ptr::NonNull<[u8]>, core::alloc::AllocError> {
99+
(&self.bump).allocate(layout)
100+
}
101+
102+
unsafe fn deallocate(&self, ptr: core::ptr::NonNull<u8>, layout: core::alloc::Layout) {
103+
// SAFETY: this simply delegates to the bump allocator
104+
unsafe { (&self.bump).deallocate(ptr, layout) }
105+
}
106+
}

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,10 @@ pub struct Location {
2424
/// complete (terminator).
2525
pub statement_index: usize,
2626
}
27+
28+
impl Location {
29+
pub const PLACEHOLDER: Self = Self {
30+
block: BasicBlockId::PLACEHOLDER,
31+
statement_index: usize::MAX,
32+
};
33+
}
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
mod cfg_simplify;
22
mod dbe;
33
pub mod error;
4+
mod sroa;
45
mod ssa_repair;
56

6-
pub use self::{cfg_simplify::CfgSimplify, dbe::DeadBlockElimination, ssa_repair::SsaRepair};
7+
pub use self::{
8+
cfg_simplify::CfgSimplify, dbe::DeadBlockElimination, sroa::Sroa, ssa_repair::SsaRepair,
9+
};

0 commit comments

Comments
 (0)