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
14 changes: 10 additions & 4 deletions .claude/skills/testing-hashql/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ For testing MIR transformation and analysis passes directly with programmaticall
- Edge cases requiring specific MIR structures hard to produce from source
- Benchmarking pass performance

**Key features:**

- Transform passes return `Changed` enum (`Yes`, `No`, `Unknown`) to indicate modifications
- Test harness captures and includes `Changed` value in snapshots for verification
- Snapshot format: before MIR β†’ `Changed: Yes/No/Unknown` separator β†’ after MIR

**Quick Example:**

```rust
Expand All @@ -201,10 +207,10 @@ builder
let body = builder.finish(0, TypeBuilder::synthetic(&env).integer());
```

πŸ“– **Full Guide:** [resources/mir-builder-guide.md](resources/mir-builder-guide.md)
πŸ“– **Full Guide:** [references/mir-builder-guide.md](references/mir-builder-guide.md)

## References

- [compiletest Guide](resources/compiletest-guide.md) - Detailed UI test documentation
- [Testing Strategies](resources/testing-strategies.md) - Choosing the right approach
- [MIR Builder Guide](resources/mir-builder-guide.md) - Programmatic MIR construction for tests
- [compiletest Guide](references/compiletest-guide.md) - Detailed UI test documentation
- [Testing Strategies](references/testing-strategies.md) - Choosing the right approach
- [MIR Builder Guide](references/mir-builder-guide.md) - Programmatic MIR construction for tests
21 changes: 13 additions & 8 deletions .claude/skills/testing-hashql/references/mir-builder-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,11 @@ builder.build_block(bb_merge).ret(x); // x receives value from param

## Test Harness Pattern

Standard pattern used across transform pass tests:
Standard pattern used across transform pass tests. The harness captures and displays
the `Changed` return value to verify pass behavior:

```rust
use std::path::PathBuf;
use std::{io::Write as _, path::PathBuf};
use bstr::ByteVec as _;
use hashql_core::{
pretty::Formatter,
Expand Down Expand Up @@ -274,12 +275,16 @@ fn assert_pass<'heap>(
.format(DefIdSlice::from_raw(&bodies), &[])
.expect("should be able to write bodies");

text_format
.writer
.extend(b"\n\n------------------------------------\n\n");

// Run the pass
YourPass::new().run(context, &mut bodies[0]);
// Run the pass and capture change status
let changed = YourPass::new().run(context, &mut bodies[0]);

// Include Changed value in snapshot for verification
write!(
text_format.writer,
"\n\n{:=^50}\n\n",
format!(" Changed: {changed:?} ")
)
.expect("infallible");

// Format after
text_format
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use hashql_mir::{
context::MirContext,
def::{DefId, DefIdSlice, DefIdVec},
intern::Interner,
pass::{TransformPass as _, transform::CfgSimplify},
pass::{Changed, TransformPass as _, transform::CfgSimplify},
};

use super::{RunContext, Suite, SuiteDiagnostic, common::process_issues, mir_reify::mir_reify};
Expand Down Expand Up @@ -63,7 +63,7 @@ pub(crate) fn mir_pass_transform_cfg_simplify<'heap>(

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

process_issues(diagnostics, context.diagnostics)?;
Expand Down
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::{TransformPass as _, transform::DeadStoreElimination},
pass::{Changed, TransformPass as _, transform::DeadStoreElimination},
};

use super::{
Expand Down Expand Up @@ -45,7 +45,7 @@ pub(crate) fn mir_pass_transform_dse<'heap>(
// CFG -> SROA -> Inst -> DSE
let mut pass = DeadStoreElimination::new_in(&mut scratch);
for body in bodies.as_mut_slice() {
pass.run(&mut context, body);
let _: Changed = pass.run(&mut context, body);
}

process_issues(diagnostics, context.diagnostics)?;
Expand Down
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::{TransformPass as _, transform::InstSimplify},
pass::{Changed, TransformPass as _, transform::InstSimplify},
};

use super::{
Expand Down Expand Up @@ -44,7 +44,7 @@ pub(crate) fn mir_pass_transform_inst_simplify<'heap>(

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

process_issues(diagnostics, context.diagnostics)?;
Expand Down
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::{TransformPass as _, transform::Sroa},
pass::{Changed, TransformPass as _, transform::Sroa},
};

use super::{
Expand Down Expand Up @@ -44,7 +44,7 @@ pub(crate) fn mir_pass_transform_sroa<'heap>(

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

process_issues(diagnostics, context.diagnostics)?;
Expand Down
49 changes: 32 additions & 17 deletions libs/@local/hashql/mir/benches/transform.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
clippy::similar_names
)]

use core::hint::black_box;
use core::{cmp, hint::black_box};

use codspeed_criterion_compat::{BatchSize, Bencher, Criterion, criterion_group, criterion_main};
use hashql_core::{
Expand Down Expand Up @@ -301,10 +301,10 @@ fn create_complex_cfg<'heap>(env: &Environment<'heap>, interner: &Interner<'heap

#[expect(unsafe_code)]
#[inline]
fn run_bencher(
fn run_bencher<T>(
bencher: &mut Bencher,
body: for<'heap> fn(&Environment<'heap>, &Interner<'heap>) -> Body<'heap>,
mut func: impl for<'env, 'heap> FnMut(&mut MirContext<'env, 'heap>, &mut Body<'heap>),
mut func: impl for<'env, 'heap> FnMut(&mut MirContext<'env, 'heap>, &mut Body<'heap>) -> T,
) {
// NOTE: `heap` must not be moved or reassigned; `heap_ptr` assumes its address is stable
// for the entire duration of this function.
Expand Down Expand Up @@ -348,8 +348,8 @@ fn run_bencher(
diagnostics: DiagnosticIssues::new(),
};

func(black_box(&mut context), black_box(body));
context.diagnostics
let value = func(black_box(&mut context), black_box(body));
(context.diagnostics, value)
},
BatchSize::PerIteration,
);
Expand Down Expand Up @@ -440,30 +440,45 @@ fn pipeline(criterion: &mut Criterion) {
let mut scratch = Scratch::new();

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);
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, InstSimplify::new().run(context, body));
changed = cmp::max(
changed,
DeadStoreElimination::new_in(&mut scratch).run(context, body),
);

changed
});
});
group.bench_function("diamond", |bencher| {
let mut scratch = Scratch::new();

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);
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, InstSimplify::new().run(context, body));
changed = cmp::max(
changed,
DeadStoreElimination::new_in(&mut scratch).run(context, body),
);

changed
});
});
group.bench_function("complex", |bencher| {
let mut scratch = Scratch::new();

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);
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, InstSimplify::new().run(context, body));
changed = cmp::max(
changed,
DeadStoreElimination::new_in(&mut scratch).run(context, body),
);

changed
});
});
}
Expand Down
13 changes: 13 additions & 0 deletions libs/@local/hashql/mir/src/body/location.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use core::{fmt, fmt::Display};

use super::basic_block::BasicBlockId;

/// A precise location identifying a specific statement within the MIR control flow graph.
Expand Down Expand Up @@ -31,3 +33,14 @@ impl Location {
statement_index: usize::MAX,
};
}

impl Display for Location {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
block,
statement_index,
} = self;

write!(fmt, "bb{block}:{statement_index}")
}
}
7 changes: 7 additions & 0 deletions libs/@local/hashql/mir/src/body/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,13 @@ pub enum Source<'heap> {
/// usage and improve interning efficiency while maintaining sufficient debugging information.
#[derive(Debug, Clone)]
pub struct Body<'heap> {
/// The unique identifier for this body.
///
/// This [`DefId`] serves as a stable reference to this body within a collection of bodies,
/// enabling cross-body analyses like call graphs. The `DefId` is assigned during lowering
/// and corresponds to the body's index in the global definition table.
pub id: DefId,

/// The source location span for this entire body.
///
/// This [`SpanId`] tracks the source location of the function, closure,
Expand Down
13 changes: 13 additions & 0 deletions libs/@local/hashql/mir/src/body/terminator/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
//! the HashQL graph store. They provide structured access to graph data with
//! control flow implications based on query results.

use core::{fmt, fmt::Display};

use hashql_core::heap;

use crate::{
Expand Down Expand Up @@ -33,6 +35,17 @@ pub struct GraphReadLocation {
pub graph_read_index: usize,
}

impl Display for GraphReadLocation {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
base,
graph_read_index,
} = self;

write!(fmt, "{base}:{graph_read_index}")
}
}

/// The starting point for a graph read operation.
///
/// Determines where the query begins in the bi-temporal graph. The head
Expand Down
1 change: 1 addition & 0 deletions libs/@local/hashql/mir/src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ impl<'env, 'heap> BodyBuilder<'env, 'heap> {
);

Body {
id: DefId::MAX,
span: SpanId::SYNTHETIC,
return_type: return_ty,
source: Source::Intrinsic(DefId::MAX),
Expand Down
Loading
Loading