Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
5312e64
feat: expose scc membership
indietyp Dec 24, 2025
f4c4e83
feat: cost configuration
indietyp Dec 25, 2025
b7e6599
feat: first inliner (WIP)
indietyp Dec 25, 2025
95683ae
feat: choose call sites to inline
indietyp Dec 25, 2025
24c16db
chore: renames
indietyp Dec 25, 2025
193ec8c
feat: inlining
indietyp Dec 25, 2025
675041d
feat: make members 2x smaller
indietyp Dec 25, 2025
f660778
feat: inline add all blocks
indietyp Dec 26, 2025
4dcb73b
feat: inliner WIP
indietyp Dec 26, 2025
413c868
feat: inline extract out data
indietyp Dec 26, 2025
5a81ada
feat: create inliner
indietyp Dec 26, 2025
aadd5dc
chore: fix lints
indietyp Dec 26, 2025
56a8087
chore: fix lints
indietyp Dec 26, 2025
e58d380
chore: experiment
indietyp Dec 26, 2025
fd73251
feat: checkpointing API
indietyp Dec 26, 2025
c56d301
feat: account for aggressive inlining in normal phased
indietyp Dec 27, 2025
9ee6526
feat: finish (untested) inline
indietyp Dec 27, 2025
d1db3d3
chore: docs
indietyp Dec 27, 2025
a94d676
chore: cleanup
indietyp Dec 27, 2025
e2c4cc3
chore: checkpoint
indietyp Dec 27, 2025
6e6d749
chore: checkpoint
indietyp Dec 27, 2025
cbf8fd5
feat: diagnostic
indietyp Dec 27, 2025
9a043bc
chore: Default trait impl for config
indietyp Dec 27, 2025
5f6acee
chore: checkpoint
indietyp Dec 27, 2025
c1b6880
feat: double inlining
indietyp Dec 27, 2025
3021642
fix: merge conflicts
indietyp Jan 2, 2026
1f60499
fix: imports
indietyp Jan 2, 2026
6994e8e
chore: checkpoint
indietyp Jan 2, 2026
65653f7
chore: tests
indietyp Jan 2, 2026
0d62325
feat: tests
indietyp Jan 2, 2026
e5eb8a9
fix: inline config params
indietyp Jan 2, 2026
ffbd594
feat: docs
indietyp Jan 2, 2026
10fc039
feat: more test coverage
indietyp Jan 2, 2026
494e60c
chore: correctly gate the debug_assert via cfg and not conditional
indietyp Jan 2, 2026
9d19db1
feat: rework tests to be less... large
indietyp Jan 2, 2026
9b56319
feat: verify that heap is indeed max
indietyp Jan 2, 2026
7e32c71
feat: use `GlobalTransformPass` for Inline
indietyp Jan 3, 2026
14a0b0a
chore: checkpoint
indietyp Jan 1, 2026
5b42b5f
feat: use heap for changed state tracking
indietyp Jan 1, 2026
ae3798d
chore: fix comment
indietyp Jan 1, 2026
731bcea
fix: import
indietyp Jan 1, 2026
4eea690
chore: remove dead reference
indietyp Jan 2, 2026
c1ba012
fix: appease the AI overlords
indietyp Jan 2, 2026
da59fc7
feat: inline cost esimation
indietyp Dec 24, 2025
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
11 changes: 10 additions & 1 deletion .claude/skills/testing-hashql/references/mir-builder-guide.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,22 @@ body!(interner, env; <source> @ <id> / <arity> -> <return_type> {

| Component | Description | Example |
| --------- | ----------- | ------- |
| `<source>` | Body source type | `fn` (closure) or `thunk` |
| `<source>` | Body source type | `fn`, `thunk`, `[ctor expr]`, `intrinsic` |
| `<id>` | DefId (literal or variable) | `0`, `42`, `my_def_id` |
| `<arity>` | Number of function arguments | `0`, `1`, `2` |
| `<return_type>` | Return type | `Int`, `Bool`, `(Int, Bool)` |

The `<id>` can be a numeric literal (`0`, `1`, `42`) or a variable identifier (`callee_id`, `my_def_id`). When using a variable, it must be a `DefId` in scope.

**Source types:**

| Syntax | Maps to | Use case |
| ------ | ------- | -------- |
| `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) |
| `intrinsic` | `Source::Intrinsic` | Intrinsic bodies (never inlined) |

### Types

| Syntax | Description | Example |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::io::Write as _;

use hashql_ast::node::expr::Expr;
use hashql_core::{
heap::{Heap, Scratch},
heap::{Heap, ResetAllocator as _, Scratch},
id::IdVec,
r#type::environment::Environment,
};
Expand Down Expand Up @@ -52,6 +52,7 @@ pub(crate) fn mir_pass_transform_administrative_reduction<'heap>(
&mut GlobalTransformState::new(&mut changed),
&mut bodies,
);
scratch.reset();

process_issues(diagnostics, context.diagnostics)?;
Ok((root, bodies, scratch))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::{

use hashql_ast::node::expr::Expr;
use hashql_core::{
heap::{Heap, Scratch},
heap::{Heap, ResetAllocator as _, Scratch},
r#type::environment::Environment,
};
use hashql_diagnostics::DiagnosticIssues;
Expand Down Expand Up @@ -65,6 +65,7 @@ pub(crate) fn mir_pass_transform_cfg_simplify<'heap>(
for body in bodies.as_mut_slice() {
let _: Changed = pass.run(&mut context, body);
}
scratch.reset();

process_issues(diagnostics, context.diagnostics)?;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::io::Write as _;

use hashql_ast::node::expr::Expr;
use hashql_core::{
heap::{Heap, Scratch},
heap::{Heap, ResetAllocator as _, Scratch},
r#type::environment::Environment,
};
use hashql_diagnostics::DiagnosticIssues;
Expand Down Expand Up @@ -46,6 +46,7 @@ pub(crate) fn mir_pass_transform_forward_substitution<'heap>(
for body in bodies.as_mut_slice() {
let _: Changed = pass.run(&mut context, body);
}
scratch.reset();

process_issues(diagnostics, context.diagnostics)?;
Ok((root, bodies, scratch))
Expand Down
180 changes: 180 additions & 0 deletions libs/@local/hashql/compiletest/src/suite/mir_pass_transform_inline.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
use std::io::Write as _;

use hashql_ast::node::expr::Expr;
use hashql_core::{
heap::{Heap, ResetAllocator as _, Scratch},
r#type::environment::Environment,
};
use hashql_diagnostics::DiagnosticIssues;
use hashql_mir::{
body::Body,
context::MirContext,
def::{DefId, DefIdVec},
intern::Interner,
pass::{
Changed, GlobalTransformPass as _, GlobalTransformState,
transform::{Inline, InlineConfig},
},
};

use super::{
RunContext, Suite, SuiteDiagnostic,
common::process_issues,
mir_pass_transform_pre_inlining::{
MirRenderer, RenderContext, Stage, mir_pass_transform_pre_inlining,
},
};
use crate::suite::{
mir_pass_transform_pre_inlining::{D2Renderer, TextRenderer},
mir_reify::{d2_output_enabled, mir_spawn_d2},
};

pub(crate) fn mir_pass_transform_inline<'heap>(
heap: &'heap Heap,
expr: Expr<'heap>,
config: InlineConfig,
interner: &Interner<'heap>,
mut render: impl MirRenderer,
environment: &mut Environment<'heap>,
diagnostics: &mut Vec<SuiteDiagnostic>,
) -> Result<(DefId, DefIdVec<Body<'heap>>, Scratch), SuiteDiagnostic> {
let (root, mut bodies, mut scratch) = mir_pass_transform_pre_inlining(
heap,
expr,
interner,
&mut render,
environment,
diagnostics,
)?;

let mut context = MirContext {
heap,
env: environment,
interner,
diagnostics: DiagnosticIssues::new(),
};

let mut pass = Inline::new_in(config, &mut scratch);
let _: Changed = pass.run(
&mut context,
&mut GlobalTransformState::new_in(&bodies, heap),
&mut bodies,
);
scratch.reset();

process_issues(diagnostics, context.diagnostics)?;

render.render(
&mut RenderContext {
heap,
env: environment,
stage: Stage {
id: "inline",
title: "Inlined MIR",
},
root,
},
&bodies,
);

Ok((root, bodies, scratch))
}

pub(crate) struct MirPassTransformInline;

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

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

fn description(&self) -> &'static str {
"Inlining 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 config = InlineConfig::default();

#[expect(clippy::cast_sign_loss, clippy::cast_possible_truncation)]
if let Some(aggressive_inline_cutoff) = suite_directives
.get("aggressive-inline-cutoff")
.and_then(toml::Value::as_integer)
{
config.aggressive_inline_cutoff = aggressive_inline_cutoff as usize;
}

#[expect(clippy::cast_possible_truncation)]
if let Some(rvalue_input_cost) = suite_directives
.get("rvalue-input-cost")
.and_then(toml::Value::as_float)
{
config.cost.rvalue_input = rvalue_input_cost as f32;
}

#[expect(clippy::cast_possible_truncation)]
if let Some(max_cost) = suite_directives
.get("max-cost")
.and_then(toml::Value::as_float)
{
config.heuristics.max = max_cost as f32;
}

let skip_output = suite_directives
.get("skip-output")
.and_then(toml::Value::as_bool)
.unwrap_or(false);

let mut buffer = Vec::new();
let mut d2 = d2_output_enabled(self, suite_directives, reports).then(mir_spawn_d2);

mir_pass_transform_inline(
heap,
expr,
config,
&interner,
(
TextRenderer::new(&mut buffer),
d2.as_mut().map(|(writer, _)| D2Renderer::new(writer)),
),
&mut environment,
diagnostics,
)?;

if let Some((mut writer, handle)) = d2 {
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);
}

if skip_output {
return Ok("[output intentionally skipped]".to_owned());
}

Ok(String::from_utf8_lossy_owned(buffer))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::io::Write as _;

use hashql_ast::node::expr::Expr;
use hashql_core::{
heap::{Heap, Scratch},
heap::{Heap, ResetAllocator as _, Scratch},
r#type::environment::Environment,
};
use hashql_diagnostics::DiagnosticIssues;
Expand Down Expand Up @@ -52,6 +52,7 @@ pub(crate) fn mir_pass_transform_inst_simplify<'heap>(
for body in bodies.as_mut_slice() {
let _: Changed = pass.run(&mut context, body);
}
scratch.reset();

process_issues(diagnostics, context.diagnostics)?;
Ok((root, bodies, scratch))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ use crate::suite::{

#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub(crate) struct Stage {
id: &'static str,
title: &'static str,
pub id: &'static str,
pub title: &'static str,
}

pub(crate) struct RenderContext<'env, 'heap> {
Expand Down
3 changes: 3 additions & 0 deletions libs/@local/hashql/compiletest/src/suite/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ mod mir_pass_transform_administrative_reduction;
mod mir_pass_transform_cfg_simplify;
mod mir_pass_transform_dse;
mod mir_pass_transform_forward_substitution;
mod mir_pass_transform_inline;
mod mir_pass_transform_inst_simplify;
mod mir_pass_transform_pre_inlining;
mod mir_reify;
Expand Down Expand Up @@ -61,6 +62,7 @@ use self::{
mir_pass_transform_cfg_simplify::MirPassTransformCfgSimplify,
mir_pass_transform_dse::MirPassTransformDse,
mir_pass_transform_forward_substitution::MirPassTransformForwardSubstitution,
mir_pass_transform_inline::MirPassTransformInline,
mir_pass_transform_inst_simplify::MirPassTransformInstSimplify,
mir_pass_transform_pre_inlining::MirPassTransformPreInlining, mir_reify::MirReifySuite,
parse_syntax_dump::ParseSyntaxDumpSuite,
Expand Down Expand Up @@ -162,6 +164,7 @@ const SUITES: &[&dyn Suite] = &[
&MirPassTransformCfgSimplify,
&MirPassTransformDse,
&MirPassTransformForwardSubstitution,
&MirPassTransformInline,
&MirPassTransformInstSimplify,
&MirPassTransformPreInlining,
&MirReifySuite,
Expand Down
2 changes: 1 addition & 1 deletion libs/@local/hashql/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"fix:clippy": "just clippy --fix",
"lint:clippy": "just clippy",
"test:codspeed": "cargo codspeed run -p hashql-core",
"test:miri": "cargo miri nextest run -- co_sort try_scan heap::transfer stable_empty_slice",
"test:miri": "cargo miri nextest run -- co_sort try_scan heap::transfer stable_empty_slice id::slice tarjan::tests::members",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we just generally run Miri on all tests

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i can give it a try next time, I didnt because of the time it takes to run, but let me check if that is still true

"test:unit": "mise run test:unit @rust/hashql-core"
},
"dependencies": {
Expand Down
2 changes: 1 addition & 1 deletion libs/@local/hashql/core/src/collections/work_queue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ where
#[must_use]
pub fn new_in(domain_size: usize, alloc: A) -> Self {
Self {
queue: VecDeque::new_in(alloc),
queue: VecDeque::with_capacity_in(domain_size, alloc),
set: DenseBitSet::new_empty(domain_size),
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -465,7 +465,7 @@ fn compute_access_time<N: Id>(
}

let mut max = EdgeIndex::from_u32(0);
for edge in edges.iter_mut() {
for edge in &mut edges {
max.increment_by(edge.end.as_usize());
edge.start = max;
edge.end = max;
Expand Down
Loading
Loading