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
17 changes: 17 additions & 0 deletions .claude/skills/testing-hashql/references/mir-builder-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ The `<id>` can be a numeric literal (`0`, `1`, `42`) or a variable identifier (`
| `fn` | `Source::Closure` | Regular closures/functions |
| `thunk` | `Source::Thunk` | Thunk bodies (zero-arg delayed computations) |
| `[ctor sym::path]` | `Source::Ctor(sym)` | Constructor bodies (always inlined) |
| `[graph::read::filter]` | `Source::GraphReadFilter` | Graph read filter bodies (never inlined) |
| `intrinsic` | `Source::Intrinsic` | Intrinsic bodies (never inlined) |

### Types
Expand Down Expand Up @@ -224,6 +225,22 @@ let body = body!(interner, env; fn@0/0 -> Null {
});
```

### Graph Read Filter

Filter bodies for graph traversal. The first two declared locals become the function arguments (`_0` = env tuple, `_1` = vertex):

```rust
let body = body!(interner, env; [graph::read::filter]@0/2 -> Bool {
decl env: (Int,), vertex: (Int, Int), result: Bool;
@proj vertex_field = vertex.0: Int;

bb0() {
result = bin.== vertex_field 42;
return result;
}
});
```

### Direct Function Calls

Use a `DefId` variable directly:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ pub(crate) fn mir_pass_transform_post_inline<'heap>(
diagnostics: DiagnosticIssues::new(),
};

let mut pass = PostInline::new_in(&mut scratch);
let mut pass = PostInline::new_in(heap, &mut scratch);
let _: Changed = pass.run(
&mut context,
&mut GlobalTransformState::new_in(&bodies, heap),
Expand Down
5 changes: 5 additions & 0 deletions libs/@local/hashql/core/src/id/vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -404,6 +404,11 @@ where
self.raw.extend_from_slice(other.as_raw());
}

#[inline]
pub fn append(&mut self, other: &mut Self) {
self.raw.append(&mut other.raw);
}

pub fn into_iter_enumerated(
self,
) -> impl DoubleEndedIterator<Item = (I, T)> + ExactSizeIterator {
Expand Down
2 changes: 1 addition & 1 deletion libs/@local/hashql/mir/benches/interpret.rs
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ fn create_fibonacci_body<'heap>(
let _: Changed = inline.run(&mut context, &mut state.as_mut(), bodies_mut);
scratch.reset();

let mut post = PostInline::new_in(&mut scratch);
let mut post = PostInline::new_in(context.heap, &mut scratch);
let _: Changed = post.run(&mut context, &mut state.as_mut(), bodies_mut);
scratch.reset();

Expand Down
12 changes: 8 additions & 4 deletions libs/@local/hashql/mir/benches/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -512,7 +512,8 @@ fn pipeline(criterion: &mut Criterion) {
changed |= Inline::new_in(InlineConfig::default(), &mut *scratch)
.run(context, &mut state, bodies);
scratch.reset();
changed |= PostInline::new_in(&mut *scratch).run(context, &mut state, bodies);
changed |=
PostInline::new_in(context.heap, &mut *scratch).run(context, &mut state, bodies);
scratch.reset();
changed
});
Expand All @@ -527,7 +528,8 @@ fn pipeline(criterion: &mut Criterion) {
changed |= Inline::new_in(InlineConfig::default(), &mut *scratch)
.run(context, &mut state, bodies);
scratch.reset();
changed |= PostInline::new_in(&mut *scratch).run(context, &mut state, bodies);
changed |=
PostInline::new_in(context.heap, &mut *scratch).run(context, &mut state, bodies);
scratch.reset();
changed
});
Expand All @@ -542,7 +544,8 @@ fn pipeline(criterion: &mut Criterion) {
changed |= Inline::new_in(InlineConfig::default(), &mut *scratch)
.run(context, &mut state, bodies);
scratch.reset();
changed |= PostInline::new_in(&mut *scratch).run(context, &mut state, bodies);
changed |=
PostInline::new_in(context.heap, &mut *scratch).run(context, &mut state, bodies);
scratch.reset();
changed
});
Expand All @@ -557,7 +560,8 @@ fn pipeline(criterion: &mut Criterion) {
changed |= Inline::new_in(InlineConfig::default(), &mut *scratch)
.run(context, &mut state, bodies);
scratch.reset();
changed |= PostInline::new_in(&mut *scratch).run(context, &mut state, bodies);
changed |=
PostInline::new_in(context.heap, &mut *scratch).run(context, &mut state, bodies);
scratch.reset();
changed
});
Expand Down
20 changes: 20 additions & 0 deletions libs/@local/hashql/mir/src/body/place.rs
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,13 @@ impl<'heap> Place<'heap> {
.map_or_else(|| decl[self.local].r#type, |projection| projection.r#type)
}

#[must_use]
pub fn type_id_unchecked(&self, decl: &LocalDecl<'heap>) -> TypeId {
self.projections
.last()
.map_or_else(|| decl.r#type, |projection| projection.r#type)
}

/// Returns a borrowed reference to this place.
#[must_use]
pub const fn as_ref(&self) -> PlaceRef<'heap, 'heap> {
Expand All @@ -412,6 +419,19 @@ impl<'heap> Place<'heap> {
}
}

impl fmt::Display for Place<'_> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { local, projections } = self;
fmt::Display::fmt(local, fmt)?;

for projection in projections {
fmt::Display::fmt(&projection.kind, fmt)?;
}

Ok(())
}
}

/// A single projection step that navigates into structured data, carrying its result type.
///
/// A [`Projection`] represents one step in navigating through structured data, combining
Expand Down
3 changes: 3 additions & 0 deletions libs/@local/hashql/mir/src/builder/body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,9 @@ macro_rules! body {
(@source fn) => {
$crate::body::Source::Closure(hashql_hir::node::HirId::PLACEHOLDER, None)
};
(@source [graph::read::filter]) => {
$crate::body::Source::GraphReadFilter(hashql_hir::node::HirId::PLACEHOLDER)
};
(@source [ctor $name:expr]) => {
$crate::body::Source::Ctor($name)
};
Expand Down
11 changes: 11 additions & 0 deletions libs/@local/hashql/mir/src/pass/transform/canonicalization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ impl<A: BumpAllocator> Canonicalization<A> {
Self { alloc, config }
}

/// Returns a reference to the allocator used for temporary data structures.
pub const fn allocator(&self) -> &A {
&self.alloc
}

/// Returns a mutable reference to the allocator, allowing callers to run additional
/// passes within the same allocator scope.
pub const fn allocator_mut(&mut self) -> &mut A {
&mut self.alloc
}

/// Runs a local transform pass on all unstable bodies.
///
/// Only bodies in the `unstable` set are processed. The `state` slice is updated to track
Expand Down
4 changes: 3 additions & 1 deletion libs/@local/hashql/mir/src/pass/transform/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ mod inst_simplify;
mod post_inline;
mod pre_inline;
mod ssa_repair;
mod traversal_extraction;

pub use self::{
administrative_reduction::AdministrativeReduction,
Expand All @@ -24,7 +25,8 @@ pub use self::{
forward_substitution::ForwardSubstitution,
inline::{Inline, InlineConfig, InlineCostEstimationConfig, InlineHeuristicsConfig},
inst_simplify::InstSimplify,
post_inline::PostInline,
post_inline::{PostInline, PostInlineResidual},
pre_inline::PreInline,
ssa_repair::SsaRepair,
traversal_extraction::{TraversalExtraction, Traversals},
};
59 changes: 48 additions & 11 deletions libs/@local/hashql/mir/src/pass/transform/post_inline.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,26 @@
//! Post-inlining optimization pass.
//!
//! This module contains the [`PostInline`] pass, a thin wrapper around [`Canonicalization`] that
//! runs with settings tuned for post-inlining optimization.
//! Runs [`Canonicalization`] to clean up redundancy from inlining, then [`TraversalExtraction`]
//! to materialize vertex projections in graph read filter bodies.
//!
//! After running, call [`PostInline::finish`] to retrieve the [`Traversals`] maps.

use core::alloc::Allocator;

use hashql_core::heap::BumpAllocator;
use hashql_core::heap::{BumpAllocator, Heap};

use super::{Canonicalization, CanonicalizationConfig};
use super::{Canonicalization, CanonicalizationConfig, TraversalExtraction, Traversals};
use crate::{
body::Body,
context::MirContext,
def::DefIdSlice,
pass::{Changed, GlobalTransformPass, GlobalTransformState},
def::{DefIdSlice, DefIdVec},
pass::{Changed, GlobalTransformPass, GlobalTransformState, TransformPass as _},
};

pub struct PostInlineResidual<'heap> {
pub traversals: DefIdVec<Option<Traversals<'heap>>, &'heap Heap>,
}

/// Post-inlining optimization driver.
///
/// A thin wrapper around [`Canonicalization`] configured for post-inlining optimization. By running
Expand All @@ -28,32 +34,63 @@ use crate::{
/// more optimization opportunities that may require additional passes to fully resolve.
///
/// See [`Canonicalization`] for details on the pass ordering and implementation.
pub struct PostInline<A: Allocator> {
pub struct PostInline<'heap, A: Allocator> {
canonicalization: Canonicalization<A>,

traversals: DefIdVec<Option<Traversals<'heap>>, &'heap Heap>,
}

impl<A: BumpAllocator> PostInline<A> {
impl<'heap, A: BumpAllocator> PostInline<'heap, A> {
/// Creates a new post-inlining pass with the given allocator.
///
/// The allocator is used for temporary data structures within sub-passes and is reset
/// between pass invocations.
pub const fn new_in(alloc: A) -> Self {
pub const fn new_in(heap: &'heap Heap, alloc: A) -> Self {
Self {
canonicalization: Canonicalization::new_in(
CanonicalizationConfig { max_iterations: 16 },
alloc,
),
traversals: DefIdVec::new_in(heap),
}
}

/// Consumes the pass and returns accumulated results.
///
/// The returned [`PostInlineResidual`] contains traversal maps for each graph read filter
/// body processed during the pass run.
pub fn finish(self) -> PostInlineResidual<'heap> {
PostInlineResidual {
traversals: self.traversals,
}
}
}

impl<'env, 'heap, A: BumpAllocator> GlobalTransformPass<'env, 'heap> for PostInline<A> {
impl<'env, 'heap, A: BumpAllocator> GlobalTransformPass<'env, 'heap> for PostInline<'heap, A> {
fn run(
&mut self,
context: &mut MirContext<'env, 'heap>,
state: &mut GlobalTransformState<'_>,
bodies: &mut DefIdSlice<Body<'heap>>,
) -> Changed {
self.canonicalization.run(context, state, bodies)
let mut changed = Changed::No;
changed |= self.canonicalization.run(context, state, bodies);

self.canonicalization.allocator_mut().scoped(|alloc| {
let mut extraction = TraversalExtraction::new_in(alloc);

for (id, body) in bodies.iter_enumerated_mut() {
let changed_body = extraction.run(context, body);

if let Some(traversal) = extraction.take_traversals() {
self.traversals.insert(id, traversal);
}

state.mark(id, changed_body);
changed |= changed_body;
}
});

changed
}
}
Loading
Loading