From 76489b9695a2d7c854afbaf90d9a13adeebd157e Mon Sep 17 00:00:00 2001
From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com>
Date: Fri, 19 Jun 2026 10:53:51 +0200
Subject: [PATCH 1/8] feat: intrinsic id
---
libs/@local/hashql/mir/src/body/mod.rs | 4 +-
libs/@local/hashql/mir/src/builder/body.rs | 3 +-
libs/@local/hashql/mir/src/intrinsic.rs | 7 +++
libs/@local/hashql/mir/src/lib.rs | 1 +
.../mir/src/pass/transform/inline/tests.rs | 43 ++++++++++---------
5 files changed, 34 insertions(+), 24 deletions(-)
create mode 100644 libs/@local/hashql/mir/src/intrinsic.rs
diff --git a/libs/@local/hashql/mir/src/body/mod.rs b/libs/@local/hashql/mir/src/body/mod.rs
index 65001060ff9..cb0dd57adba 100644
--- a/libs/@local/hashql/mir/src/body/mod.rs
+++ b/libs/@local/hashql/mir/src/body/mod.rs
@@ -17,7 +17,7 @@ use self::{
basic_blocks::BasicBlocks,
local::{LocalDecl, LocalVec},
};
-use crate::def::DefId;
+use crate::{def::DefId, intrinsic::IntrinsicId};
pub mod basic_block;
pub mod basic_blocks;
@@ -76,7 +76,7 @@ pub enum Source<'heap> {
///
/// The body of an intrinsic function is typically empty, as the intrinsic
/// operation is handled directly by the compiler or runtime.
- Intrinsic(DefId),
+ Intrinsic(IntrinsicId),
/// A filter closure for graph read operations.
///
diff --git a/libs/@local/hashql/mir/src/builder/body.rs b/libs/@local/hashql/mir/src/builder/body.rs
index 1f2bd5ad682..1176fa36e84 100644
--- a/libs/@local/hashql/mir/src/builder/body.rs
+++ b/libs/@local/hashql/mir/src/builder/body.rs
@@ -6,6 +6,7 @@ use hashql_core::{
span::SpanId,
r#type::{TypeId, builder::IntoSymbol},
};
+use hashql_hir::node::HirId;
use super::{base::BaseBuilder, basic_block::BasicBlockBuilder};
use crate::{
@@ -126,7 +127,7 @@ impl<'env, 'heap> BodyBuilder<'env, 'heap> {
id: DefId::MAX,
span: SpanId::SYNTHETIC,
return_type: return_ty,
- source: Source::Intrinsic(DefId::MAX),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
local_decls: self.local_decls,
basic_blocks: BasicBlocks::new(self.blocks),
args,
diff --git a/libs/@local/hashql/mir/src/intrinsic.rs b/libs/@local/hashql/mir/src/intrinsic.rs
new file mode 100644
index 00000000000..e8ef2187d16
--- /dev/null
+++ b/libs/@local/hashql/mir/src/intrinsic.rs
@@ -0,0 +1,7 @@
+use hashql_core::id::Id;
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Id)]
+#[repr(u8)]
+pub enum IntrinsicId {
+ EntityPropertyAccess,
+}
diff --git a/libs/@local/hashql/mir/src/lib.rs b/libs/@local/hashql/mir/src/lib.rs
index aecce9b1e49..2eee30d7f6d 100644
--- a/libs/@local/hashql/mir/src/lib.rs
+++ b/libs/@local/hashql/mir/src/lib.rs
@@ -45,6 +45,7 @@ pub mod def;
pub mod error;
pub mod intern;
pub mod interpret;
+mod intrinsic;
mod macros;
pub mod pass;
pub mod pretty;
diff --git a/libs/@local/hashql/mir/src/pass/transform/inline/tests.rs b/libs/@local/hashql/mir/src/pass/transform/inline/tests.rs
index 632256db6af..33e959c39dd 100644
--- a/libs/@local/hashql/mir/src/pass/transform/inline/tests.rs
+++ b/libs/@local/hashql/mir/src/pass/transform/inline/tests.rs
@@ -14,6 +14,7 @@ use hashql_core::{
r#type::{TypeFormatter, TypeFormatterOptions, environment::Environment},
};
use hashql_diagnostics::DiagnosticIssues;
+use hashql_hir::node::HirId;
use insta::{Settings, assert_snapshot};
use super::{
@@ -541,7 +542,7 @@ fn analysis_directives_by_source() {
let mut intrinsic_body = closure_body.clone();
intrinsic_body.id = DefId::new(2);
- intrinsic_body.source = Source::Intrinsic(DefId::PLACEHOLDER);
+ intrinsic_body.source = Source::Closure(HirId::PLACEHOLDER, None);
// Fix closure_body id to be 0
closure_body.id = DefId::new(0);
@@ -690,13 +691,13 @@ fn heuristics_directive_scores() {
// Test Always -> +∞
let properties = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Always,
cost: 100.0,
is_leaf: true,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 10.0,
is_leaf: false,
@@ -714,13 +715,13 @@ fn heuristics_directive_scores() {
// Test Never -> -∞
let properties = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Never,
cost: 5.0,
is_leaf: true,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 10.0,
is_leaf: false,
@@ -753,13 +754,13 @@ fn heuristics_cost_thresholds() {
// Below always_inline -> +∞
let properties = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: config.always_inline - 1.0,
is_leaf: true,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 10.0,
is_leaf: false,
@@ -778,13 +779,13 @@ fn heuristics_cost_thresholds() {
// Above max -> -∞
let properties = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: config.max + 1.0,
is_leaf: true,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 10.0,
is_leaf: false,
@@ -818,13 +819,13 @@ fn heuristics_leaf_bonus() {
let props_leaf = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost,
is_leaf: true,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 10.0,
is_leaf: false,
@@ -832,13 +833,13 @@ fn heuristics_leaf_bonus() {
]);
let props_non_leaf = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost,
is_leaf: false,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 10.0,
is_leaf: false,
@@ -892,13 +893,13 @@ fn heuristics_loop_bonus() {
let properties = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost,
is_leaf: true,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 50.0,
is_leaf: false,
@@ -966,13 +967,13 @@ fn heuristics_max_loop_multiplier() {
let properties = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost,
is_leaf: true,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 50.0,
is_leaf: false,
@@ -1035,13 +1036,13 @@ fn heuristics_caller_bonuses() {
let properties = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost,
is_leaf: true,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 50.0,
is_leaf: false,
@@ -1092,13 +1093,13 @@ fn heuristics_no_unique_callsite_bonus_multiple_calls() {
let properties = DefIdVec::from_raw(vec![
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost,
is_leaf: true,
},
BodyProperties {
- source: Source::Intrinsic(DefId::PLACEHOLDER),
+ source: Source::Closure(HirId::PLACEHOLDER, None),
directive: InlineDirective::Heuristic,
cost: 50.0,
is_leaf: false,
From d22fe46dc2d8edb1c7f6190d410df68691881f0e Mon Sep 17 00:00:00 2001
From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com>
Date: Fri, 19 Jun 2026 11:19:10 +0200
Subject: [PATCH 2/8] feat: add intrinsic
---
libs/@local/hashql/core/src/symbol/sym.rs | 1 +
libs/@local/hashql/mir/src/body/mod.rs | 4 +-
libs/@local/hashql/mir/src/def.rs | 47 ------
libs/@local/hashql/mir/src/intrinsic.rs | 75 ++++++++-
libs/@local/hashql/mir/src/pass/mod.rs | 25 +--
libs/@local/hashql/mir/src/pass/tests.rs | 146 ++++++++++++++++++
libs/@local/hashql/mir/src/pretty/text.rs | 5 +-
.../ui/pass/lower/goto_into_unreachable.snap | 10 ++
.../tests/ui/pass/lower/unreachable_only.snap | 10 ++
9 files changed, 248 insertions(+), 75 deletions(-)
create mode 100644 libs/@local/hashql/mir/src/pass/tests.rs
create mode 100644 libs/@local/hashql/mir/tests/ui/pass/lower/goto_into_unreachable.snap
create mode 100644 libs/@local/hashql/mir/tests/ui/pass/lower/unreachable_only.snap
diff --git a/libs/@local/hashql/core/src/symbol/sym.rs b/libs/@local/hashql/core/src/symbol/sym.rs
index 8fd7272a427..fcb3a6a25eb 100644
--- a/libs/@local/hashql/core/src/symbol/sym.rs
+++ b/libs/@local/hashql/core/src/symbol/sym.rs
@@ -49,6 +49,7 @@ hashql_macros::define_symbols! {
encodings,
end,
entity,
+ pointer,
Entity,
EntityType,
EntityTypeMetadata,
diff --git a/libs/@local/hashql/mir/src/body/mod.rs b/libs/@local/hashql/mir/src/body/mod.rs
index cb0dd57adba..c8f11f527ca 100644
--- a/libs/@local/hashql/mir/src/body/mod.rs
+++ b/libs/@local/hashql/mir/src/body/mod.rs
@@ -17,7 +17,7 @@ use self::{
basic_blocks::BasicBlocks,
local::{LocalDecl, LocalVec},
};
-use crate::{def::DefId, intrinsic::IntrinsicId};
+use crate::{def::DefId, intrinsic::Intrinsic};
pub mod basic_block;
pub mod basic_blocks;
@@ -76,7 +76,7 @@ pub enum Source<'heap> {
///
/// The body of an intrinsic function is typically empty, as the intrinsic
/// operation is handled directly by the compiler or runtime.
- Intrinsic(IntrinsicId),
+ Intrinsic(Intrinsic),
/// A filter closure for graph read operations.
///
diff --git a/libs/@local/hashql/mir/src/def.rs b/libs/@local/hashql/mir/src/def.rs
index 401c6d3c96a..c959e00df02 100644
--- a/libs/@local/hashql/mir/src/def.rs
+++ b/libs/@local/hashql/mir/src/def.rs
@@ -25,52 +25,5 @@ id::newtype!(
id::newtype_collections!(pub type DefId* from DefId);
impl DefId {
- /// Built-in dictionary insert operation (immutable).
- ///
- /// This operation inserts a key-value pair into a dictionary,
- /// returning a new dictionary with the added pair. The original
- /// dictionary remains unchanged.
- pub const DICT_INSERT: Self = Self::new(0xFFFF_FE00);
- /// Built-in dictionary insert operation (mutable).
- ///
- /// This operation inserts a key-value pair into a dictionary in-place,
- /// modifying the original dictionary. Used for efficient dictionary
- /// construction and updates.
- pub const DICT_INSERT_MUT: Self = Self::new(0xFFFF_FE01);
- /// Built-in dictionary remove operation (immutable).
- ///
- /// This operation removes a key-value pair from a dictionary,
- /// returning a new dictionary without the specified key. The
- /// original dictionary remains unchanged.
- pub const DICT_REMOVE: Self = Self::new(0xFFFF_FE02);
- /// Built-in dictionary remove operation (mutable).
- ///
- /// This operation removes a key-value pair from a dictionary in-place,
- /// modifying the original dictionary and returning the removed value
- /// if the key existed.
- pub const DICT_REMOVE_MUT: Self = Self::new(0xFFFF_FE03);
- /// Built-in list pop operation (immutable).
- ///
- /// This operation removes the last element from a list, returning
- /// both the element and a new list without the element. The original
- /// list remains unchanged.
- pub const LIST_POP: Self = Self::new(0xFFFF_FE04);
- /// Built-in list pop operation (mutable).
- ///
- /// This operation removes the last element from a list in-place,
- /// returning the removed element while modifying the original list.
- pub const LIST_POP_MUT: Self = Self::new(0xFFFF_FE05);
- /// Built-in list push operation (immutable).
- ///
- /// This operation appends an element to a list, returning a new list
- /// without modifying the original. Used for functional-style list
- /// manipulation where immutability is preferred.
- pub const LIST_PUSH: Self = Self::new(0xFFFF_FE06);
- /// Built-in list push operation (mutable).
- ///
- /// This operation appends an element to a list in-place, modifying
- /// the original list. Used for imperative-style list manipulation
- /// where performance is critical.
- pub const LIST_PUSH_MUT: Self = Self::new(0xFFFF_FE07);
pub const PLACEHOLDER: Self = Self::MAX;
}
diff --git a/libs/@local/hashql/mir/src/intrinsic.rs b/libs/@local/hashql/mir/src/intrinsic.rs
index e8ef2187d16..8aa0769962d 100644
--- a/libs/@local/hashql/mir/src/intrinsic.rs
+++ b/libs/@local/hashql/mir/src/intrinsic.rs
@@ -1,7 +1,80 @@
-use hashql_core::id::Id;
+use hashql_core::{
+ id::{Id, IdVec},
+ intern::Interned,
+ module::std_lib::graph::types::knowledge::entity::types::entity,
+ span::SpanId,
+ symbol::sym,
+ r#type::{TypeBuilder, environment::Environment},
+};
+
+use crate::{
+ body::{
+ Body, Source,
+ basic_block::{BasicBlock, BasicBlockVec},
+ basic_blocks::BasicBlocks,
+ local::LocalDecl,
+ terminator::{Terminator, TerminatorKind},
+ },
+ def::DefId,
+};
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Id)]
#[repr(u8)]
pub enum IntrinsicId {
EntityPropertyAccess,
}
+
+#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
+pub struct Intrinsic {
+ pub id: IntrinsicId,
+ // Hint to any optimization passes that this intrinsic should not be optimized in any way.
+ pub optimize: bool,
+}
+
+pub(crate) fn entity_property_access_body<'heap>(
+ env: &Environment<'heap>,
+ id: DefId,
+ span: SpanId,
+) -> Body<'heap> {
+ // Intrinsic body for property access, we **cannot** mock this specific intrinsic, because it's
+ // semantics are - while expressible - compile-time. We cannot index into a struct at run-time
+ // from a runtime-defined value. We just can't. Meaning that indeed we must mock this specific
+ // intrinsic on all backends that support it.
+ let builder = TypeBuilder::spanned(span, env);
+
+ let mut local_decls = IdVec::with_capacity_in(1, env.heap);
+ local_decls.push(LocalDecl {
+ span,
+ r#type: entity(&builder, builder.unknown(), None),
+ name: Some(sym::entity),
+ });
+ local_decls.push(LocalDecl {
+ span,
+ r#type: builder.list(builder.union([builder.string(), builder.integer()])),
+ name: Some(sym::pointer),
+ });
+
+ let mut blocks = BasicBlockVec::with_capacity_in(1, env.heap);
+ blocks.push(BasicBlock {
+ params: Interned::empty(),
+ statements: Vec::new_in(env.heap),
+ terminator: Terminator {
+ span,
+ kind: TerminatorKind::Unreachable,
+ },
+ });
+ let basic_blocks = BasicBlocks::new(blocks);
+
+ Body {
+ id,
+ span,
+ return_type: TypeBuilder::spanned(span, env).unknown(),
+ source: Source::Intrinsic(Intrinsic {
+ id: IntrinsicId::EntityPropertyAccess,
+ optimize: false,
+ }),
+ local_decls,
+ basic_blocks,
+ args: 2,
+ }
+}
diff --git a/libs/@local/hashql/mir/src/pass/mod.rs b/libs/@local/hashql/mir/src/pass/mod.rs
index 2fd6b3292dc..32ac60a1aeb 100644
--- a/libs/@local/hashql/mir/src/pass/mod.rs
+++ b/libs/@local/hashql/mir/src/pass/mod.rs
@@ -43,6 +43,8 @@ use crate::{
pub mod analysis;
pub mod execution;
+#[cfg(test)]
+mod tests;
pub mod transform;
/// Extracts the simple type name from a fully qualified type path.
@@ -576,26 +578,3 @@ pub fn place<'heap>(
let issues = mem::take(&mut context.diagnostics);
issues.into_status(residual)
}
-
-#[cfg(test)]
-mod tests {
- use super::Changed;
-
- #[test]
- fn changed_bitor() {
- for (lhs, rhs, expected) in [
- (Changed::No, Changed::No, Changed::No),
- (Changed::No, Changed::Yes, Changed::Yes),
- (Changed::No, Changed::Unknown, Changed::Unknown),
- (Changed::Yes, Changed::No, Changed::Yes),
- (Changed::Yes, Changed::Yes, Changed::Yes),
- (Changed::Yes, Changed::Unknown, Changed::Yes),
- (Changed::Unknown, Changed::No, Changed::Unknown),
- (Changed::Unknown, Changed::Yes, Changed::Yes),
- (Changed::Unknown, Changed::Unknown, Changed::Unknown),
- ] {
- let result = lhs | rhs;
- assert_eq!(result, expected);
- }
- }
-}
diff --git a/libs/@local/hashql/mir/src/pass/tests.rs b/libs/@local/hashql/mir/src/pass/tests.rs
new file mode 100644
index 00000000000..e8e4ca48920
--- /dev/null
+++ b/libs/@local/hashql/mir/src/pass/tests.rs
@@ -0,0 +1,146 @@
+use std::path::PathBuf;
+
+use hashql_core::{
+ heap::{Heap, Scratch},
+ pretty::Formatter,
+ r#type::{TypeFormatter, TypeFormatterOptions, environment::Environment},
+};
+use hashql_diagnostics::DiagnosticIssues;
+use insta::{Settings, assert_snapshot};
+
+use super::{Changed, LowerConfig, lower};
+use crate::{
+ body::Body,
+ builder::body,
+ context::MirContext,
+ def::DefIdVec,
+ intern::Interner,
+ pretty::{TextFormatAnnotations, TextFormatOptions},
+};
+
+struct NoAnnotations;
+impl TextFormatAnnotations for NoAnnotations {}
+
+/// Runs the full lowering pipeline on `bodies` and snapshots the resulting MIR.
+#[track_caller]
+fn assert_lower<'heap>(
+ name: &'static str,
+ context: &mut MirContext<'_, 'heap>,
+ bodies: &mut DefIdVec
>,
+) {
+ let mut scratch = Scratch::new();
+ let config = LowerConfig::default();
+
+ let status = lower(context, &mut scratch, bodies, &config);
+ let _success = status.expect("lowering should not produce critical diagnostics");
+
+ let formatter = Formatter::new(context.heap);
+ let type_formatter = TypeFormatter::new(&formatter, context.env, TypeFormatterOptions::terse());
+
+ let mut text_format = TextFormatOptions {
+ writer: Vec::::new(),
+ indent: 4,
+ sources: (),
+ types: type_formatter,
+ annotations: NoAnnotations,
+ }
+ .build();
+
+ for (_id, body) in bodies.iter_enumerated() {
+ text_format.format_body(body).expect("formatting failed");
+ }
+
+ let output = String::from_utf8_lossy(&text_format.writer).into_owned();
+
+ let dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ let mut settings = Settings::clone_current();
+ settings.set_snapshot_path(dir.join("tests/ui/pass/lower"));
+ settings.set_prepend_module_to_snapshot(false);
+
+ let _guard = settings.bind_to_scope();
+ assert_snapshot!(name, output);
+}
+
+#[test]
+fn changed_bitor() {
+ for (lhs, rhs, expected) in [
+ (Changed::No, Changed::No, Changed::No),
+ (Changed::No, Changed::Yes, Changed::Yes),
+ (Changed::No, Changed::Unknown, Changed::Unknown),
+ (Changed::Yes, Changed::No, Changed::Yes),
+ (Changed::Yes, Changed::Yes, Changed::Yes),
+ (Changed::Yes, Changed::Unknown, Changed::Yes),
+ (Changed::Unknown, Changed::No, Changed::Unknown),
+ (Changed::Unknown, Changed::Yes, Changed::Yes),
+ (Changed::Unknown, Changed::Unknown, Changed::Unknown),
+ ] {
+ let result = lhs | rhs;
+ assert_eq!(result, expected);
+ }
+}
+
+/// A body with only an unreachable terminator and no statements.
+///
+/// Verifies that the optimization pipeline (constant folding, DCE, CFG cleanup,
+/// inlining) handles a completely degenerate body without panicking or emitting
+/// diagnostics.
+#[test]
+fn unreachable_only() {
+ let heap = Heap::new();
+ let interner = Interner::new(&heap);
+ let env = Environment::new(&heap);
+
+ let body = body!(interner, env; fn@0/0 -> ? {
+ decl;
+
+ bb0() {
+ unreachable;
+ }
+ });
+
+ let mut bodies = DefIdVec::new();
+ bodies.push(body);
+
+ let mut context = MirContext {
+ heap: &heap,
+ env: &env,
+ interner: &interner,
+ diagnostics: DiagnosticIssues::new(),
+ };
+
+ assert_lower("unreachable_only", &mut context, &mut bodies);
+}
+
+/// A goto into a block whose only terminator is unreachable.
+///
+/// Tests whether the optimization pipeline propagates the unreachability
+/// backward through the goto edge and simplifies the entry block.
+#[test]
+fn goto_into_unreachable() {
+ let heap = Heap::new();
+ let interner = Interner::new(&heap);
+ let env = Environment::new(&heap);
+
+ let body = body!(interner, env; fn@0/0 -> ? {
+ decl;
+
+ bb0() {
+ goto bb1();
+ },
+ bb1() {
+ unreachable;
+ }
+ });
+
+ let mut bodies = DefIdVec::new();
+ bodies.push(body);
+
+ let mut context = MirContext {
+ heap: &heap,
+ env: &env,
+ interner: &interner,
+ diagnostics: DiagnosticIssues::new(),
+ };
+
+ assert_lower("goto_into_unreachable", &mut context, &mut bodies);
+}
diff --git a/libs/@local/hashql/mir/src/pretty/text.rs b/libs/@local/hashql/mir/src/pretty/text.rs
index 3b90a785ef4..b87ab4cdd4f 100644
--- a/libs/@local/hashql/mir/src/pretty/text.rs
+++ b/libs/@local/hashql/mir/src/pretty/text.rs
@@ -30,6 +30,7 @@ use crate::{
},
},
def::{DefId, DefIdSlice},
+ intrinsic::Intrinsic,
};
const fn source_keyword(source: Source<'_>) -> &'static str {
@@ -485,8 +486,8 @@ where
Source::Closure(id, binder) => named_symbol("closure", id, binder),
Source::GraphReadFilter(id) => named_symbol("graph::read::filter", id, None),
Source::Thunk(id, binder) => named_symbol("thunk", id, binder),
- Source::Intrinsic(def_id) => {
- write!(self.line_buffer, "{{intrinsic#{def_id}}}")
+ Source::Intrinsic(Intrinsic { id, .. }) => {
+ write!(self.line_buffer, "{{intrinsic#{id}}}")
}
}
}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/lower/goto_into_unreachable.snap b/libs/@local/hashql/mir/tests/ui/pass/lower/goto_into_unreachable.snap
new file mode 100644
index 00000000000..2342c00bda4
--- /dev/null
+++ b/libs/@local/hashql/mir/tests/ui/pass/lower/goto_into_unreachable.snap
@@ -0,0 +1,10 @@
+---
+source: libs/@local/hashql/mir/src/pass/tests.rs
+assertion_line: 61
+expression: output
+---
+fn {closure@4294967040}() -> ? {
+ bb0(): {
+ unreachable
+ }
+}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/lower/unreachable_only.snap b/libs/@local/hashql/mir/tests/ui/pass/lower/unreachable_only.snap
new file mode 100644
index 00000000000..953a4d4405a
--- /dev/null
+++ b/libs/@local/hashql/mir/tests/ui/pass/lower/unreachable_only.snap
@@ -0,0 +1,10 @@
+---
+source: libs/@local/hashql/mir/src/pass/tests.rs
+assertion_line: 57
+expression: output
+---
+fn {closure@4294967040}() -> ? {
+ bb0(): {
+ unreachable
+ }
+}
From 2298150d223c1a6cb1743305ff130f495e4105aa Mon Sep 17 00:00:00 2001
From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com>
Date: Mon, 22 Jun 2026 10:05:04 +0200
Subject: [PATCH 3/8] chore: checkpoint
---
libs/@local/hashql/mir/src/intrinsic.rs | 1 +
1 file changed, 1 insertion(+)
diff --git a/libs/@local/hashql/mir/src/intrinsic.rs b/libs/@local/hashql/mir/src/intrinsic.rs
index 8aa0769962d..a3ef532c350 100644
--- a/libs/@local/hashql/mir/src/intrinsic.rs
+++ b/libs/@local/hashql/mir/src/intrinsic.rs
@@ -48,6 +48,7 @@ pub(crate) fn entity_property_access_body<'heap>(
r#type: entity(&builder, builder.unknown(), None),
name: Some(sym::entity),
});
+ // TODO: we have a json path for exactly this
local_decls.push(LocalDecl {
span,
r#type: builder.list(builder.union([builder.string(), builder.integer()])),
From 0bc4ce1e30af503a35867adc061852d9e7d61cf9 Mon Sep 17 00:00:00 2001
From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com>
Date: Mon, 22 Jun 2026 12:52:42 +0200
Subject: [PATCH 4/8] feat: checkpoint
---
.../hashql/core/src/collections/pool.rs | 28 +++++++++++---
.../core/src/module/std_lib/core/json.rs | 38 +++++++++++++++++--
.../core/src/module/std_lib/core/mod.rs | 2 +-
.../core/src/module/std_lib/graph/entity.rs | 4 +-
libs/@local/hashql/mir/src/intrinsic.rs | 14 +++----
libs/@local/hashql/mir/src/reify/atom.rs | 7 +++-
libs/@local/hashql/mir/src/reify/mod.rs | 5 ++-
7 files changed, 76 insertions(+), 22 deletions(-)
diff --git a/libs/@local/hashql/core/src/collections/pool.rs b/libs/@local/hashql/core/src/collections/pool.rs
index 2b9e730a1f4..e1ca6575cfb 100644
--- a/libs/@local/hashql/core/src/collections/pool.rs
+++ b/libs/@local/hashql/core/src/collections/pool.rs
@@ -43,6 +43,8 @@
//! assert_eq!(vec2.len(), 0);
//! ```
+use std::alloc::{Allocator, Global};
+
use crate::id::{Id, bit_vec::MixedBitSet};
/// Trait for defining how objects are recycled, created, and prepared in a pool.
@@ -234,13 +236,13 @@ pub trait Recycler {
/// // Pool now contains 2 recycled vectors ready for reuse
/// ```
#[derive(Debug)]
-pub struct Pool {
- free: Vec,
+pub struct Pool {
+ free: Vec,
recycler: R,
capacity: usize,
}
-impl Pool {
+impl Pool {
/// Creates a new pool with the specified capacity and a default recycler.
///
/// The `capacity` parameter sets the maximum number of objects that will be
@@ -278,16 +280,30 @@ impl Pool {
/// let mut pool = MixedBitSetPool::::with_recycler(5, recycler);
/// ```
pub fn with_recycler(capacity: usize, recycler: R) -> Self {
+ Self::with_recycler_in(capacity, recycler, Global)
+ }
+}
+
+impl Pool {
+ pub fn new_in(capacity: usize, alloc: A) -> Self
+ where
+ R: Default,
+ {
+ Self::with_recycler_in(capacity, R::default(), alloc)
+ }
+
+ pub fn with_recycler_in(capacity: usize, recycler: R, alloc: A) -> Self {
Self {
- free: Vec::with_capacity(capacity),
+ free: Vec::with_capacity_in(capacity, alloc),
recycler,
capacity,
}
}
}
-impl Pool
+impl Pool
where
+ A: Allocator,
R: Recycler,
{
/// Changes the pool's capacity, adjusting internal storage as needed.
@@ -673,4 +689,4 @@ where
/// // Return to pool for reuse
/// pool.release(bitset);
/// ```
-pub type MixedBitSetPool = Pool, MixedBitSetRecycler>;
+pub type MixedBitSetPool = Pool, MixedBitSetRecycler, A>;
diff --git a/libs/@local/hashql/core/src/module/std_lib/core/json.rs b/libs/@local/hashql/core/src/module/std_lib/core/json.rs
index 741ff60b20d..8c2e20109e8 100644
--- a/libs/@local/hashql/core/src/module/std_lib/core/json.rs
+++ b/libs/@local/hashql/core/src/module/std_lib/core/json.rs
@@ -1,5 +1,6 @@
use core::alloc::Allocator;
+use self::types::JsonPathDependencies;
use crate::{
module::std_lib::{
CacheId, ItemDef, ModuleCache, ModuleDef, StandardLibraryContext, StandardLibraryModule,
@@ -7,6 +8,32 @@ use crate::{
symbol::{Symbol, sym},
};
+pub mod types {
+ use crate::r#type::{TypeBuilder, TypeId};
+
+ // type JsonPathSegment = String | Integer;
+ #[must_use]
+ pub fn json_path_segment(ty: &TypeBuilder<'_, '_>) -> TypeId {
+ ty.union([ty.string(), ty.integer()])
+ }
+
+ pub struct JsonPathDependencies {
+ pub json_path_segment: TypeId,
+ }
+
+ // type JsonPath = JsonPathSegment[];
+ #[must_use]
+ pub fn json_path(
+ ty: &TypeBuilder<'_, '_>,
+ dependencies: Option,
+ ) -> TypeId {
+ ty.list(dependencies.map_or_else(
+ || json_path_segment(ty),
+ |dependencies| dependencies.json_path_segment,
+ ))
+ }
+}
+
pub(in crate::module::std_lib) struct Json {
_dependencies: (),
}
@@ -28,16 +55,19 @@ impl<'heap> StandardLibraryModule<'heap> for Json {
// type JsonPathSegment = String | Integer;
// Note: The type should be Natural instead, but this requires refinement types
- let json_path_segment_ty = context
- .ty
- .union([context.ty.string(), context.ty.integer()]);
+ let json_path_segment_ty = self::types::json_path_segment(&context.ty);
def.push(
sym::JsonPathSegment,
ItemDef::r#type(context.ty.env, json_path_segment_ty, &[]),
);
// type JsonPath = JsonPathSegment[];
- let json_path_ty = context.ty.list(json_path_segment_ty);
+ let json_path_ty = self::types::json_path(
+ &context.ty,
+ Some(JsonPathDependencies {
+ json_path_segment: json_path_segment_ty,
+ }),
+ );
def.push(
sym::JsonPath,
ItemDef::r#type(context.ty.env, json_path_ty, &[]),
diff --git a/libs/@local/hashql/core/src/module/std_lib/core/mod.rs b/libs/@local/hashql/core/src/module/std_lib/core/mod.rs
index 1c89a5fce56..f5af80654e5 100644
--- a/libs/@local/hashql/core/src/module/std_lib/core/mod.rs
+++ b/libs/@local/hashql/core/src/module/std_lib/core/mod.rs
@@ -11,7 +11,7 @@ use crate::{
pub(in crate::module::std_lib) mod bits;
pub(in crate::module::std_lib) mod bool;
pub(in crate::module::std_lib) mod cmp;
-pub(in crate::module::std_lib) mod json;
+pub mod json;
pub(in crate::module::std_lib) mod math;
pub mod option;
pub(in crate::module::std_lib) mod result;
diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs b/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs
index 0109d5c70b7..0da64582371 100644
--- a/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs
+++ b/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs
@@ -64,7 +64,9 @@ impl<'heap> StandardLibraryModule<'heap> for Entity {
decl,
);
- // `property(entity: Entity, path: JsonPath) -> Option>`
+ // `property(entity: Entity, path: JsonPath) -> ?`
+ // TODO(BE-62): return `Option>` once pattern matching allows for option destructuring, to
+ // allow for proper comparison
let decl = decl!(context;
(entity: context.ty.apply([(entity_ty.arguments[0].id, T)], entity_ty.id),
path: json_path_ty.id
diff --git a/libs/@local/hashql/mir/src/intrinsic.rs b/libs/@local/hashql/mir/src/intrinsic.rs
index a3ef532c350..de02559460c 100644
--- a/libs/@local/hashql/mir/src/intrinsic.rs
+++ b/libs/@local/hashql/mir/src/intrinsic.rs
@@ -1,7 +1,7 @@
use hashql_core::{
id::{Id, IdVec},
intern::Interned,
- module::std_lib::graph::types::knowledge::entity::types::entity,
+ module::std_lib::{core::json, graph::types::knowledge::entity::types::entity},
span::SpanId,
symbol::sym,
r#type::{TypeBuilder, environment::Environment},
@@ -37,21 +37,21 @@ pub(crate) fn entity_property_access_body<'heap>(
span: SpanId,
) -> Body<'heap> {
// Intrinsic body for property access, we **cannot** mock this specific intrinsic, because it's
- // semantics are - while expressible - compile-time. We cannot index into a struct at run-time
- // from a runtime-defined value. We just can't. Meaning that indeed we must mock this specific
- // intrinsic on all backends that support it.
+ // semantics are - while expressible - compile-time only. We cannot index into a struct at
+ // run-time from a runtime-defined value, without sacrifing correctness guarantees the MIR
+ // relies on. Meaning that indeed we must mock this specific intrinsic on all backends that
+ // support it.
let builder = TypeBuilder::spanned(span, env);
- let mut local_decls = IdVec::with_capacity_in(1, env.heap);
+ let mut local_decls = IdVec::with_capacity_in(2, env.heap);
local_decls.push(LocalDecl {
span,
r#type: entity(&builder, builder.unknown(), None),
name: Some(sym::entity),
});
- // TODO: we have a json path for exactly this
local_decls.push(LocalDecl {
span,
- r#type: builder.list(builder.union([builder.string(), builder.integer()])),
+ r#type: json::types::json_path(&builder, None),
name: Some(sym::pointer),
});
diff --git a/libs/@local/hashql/mir/src/reify/atom.rs b/libs/@local/hashql/mir/src/reify/atom.rs
index 5779e1d4dcb..73cb7d63774 100644
--- a/libs/@local/hashql/mir/src/reify/atom.rs
+++ b/libs/@local/hashql/mir/src/reify/atom.rs
@@ -6,7 +6,7 @@ use hashql_hir::node::{
access::{Access, FieldAccess, IndexAccess},
data::Data,
kind::NodeKind,
- variable::Variable,
+ variable::{QualifiedVariable, Variable},
};
use super::{
@@ -154,6 +154,11 @@ impl<'heap, A: Allocator, S: Allocator> Reifier<'_, '_, '_, '_, 'heap, A, S> {
}
}
+ fn qualified_path(&mut self, QualifiedVariable { path, arguments }: QualifiedVariable<'heap>) {
+ // For the path, we simply match against the tree of values that are supported
+ todo!()
+ }
+
pub(super) fn operand(&mut self, node: Node<'heap>) -> Operand<'heap> {
match node.kind {
NodeKind::Variable(Variable::Qualified(_)) => {
diff --git a/libs/@local/hashql/mir/src/reify/mod.rs b/libs/@local/hashql/mir/src/reify/mod.rs
index 19180f265fb..caeee8fd6d2 100644
--- a/libs/@local/hashql/mir/src/reify/mod.rs
+++ b/libs/@local/hashql/mir/src/reify/mod.rs
@@ -112,7 +112,7 @@ struct CrossCompileState<'heap, S: Allocator> {
/// Cache of already-created type constructors to avoid duplication.
ctor: FastHashMap, DefId>,
- var_pool: MixedBitSetPool,
+ var_pool: MixedBitSetPool,
}
/// The core reification engine that converts individual HIR nodes to MIR bodies.
@@ -508,11 +508,12 @@ pub fn from_hir<'heap, A: Allocator, S: Allocator + Clone>(
thunks,
ctor: FastHashMap::default(),
diagnostics: ReifyDiagnosticIssues::new(),
- var_pool: MixedBitSetPool::with_recycler(
+ var_pool: MixedBitSetPool::with_recycler_in(
8,
MixedBitSetRecycler {
domain_size: context.hir.counter.var.size(),
},
+ context.scratch.clone(),
),
};
From d2e7a4a908a46e1d5e7dc36f428fbf003fa69e7a Mon Sep 17 00:00:00 2001
From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com>
Date: Mon, 22 Jun 2026 14:05:45 +0200
Subject: [PATCH 5/8] feat: checkpoint
---
libs/@local/hashql/mir/src/intrinsic.rs | 49 -------------------------
1 file changed, 49 deletions(-)
diff --git a/libs/@local/hashql/mir/src/intrinsic.rs b/libs/@local/hashql/mir/src/intrinsic.rs
index de02559460c..8c5525785b7 100644
--- a/libs/@local/hashql/mir/src/intrinsic.rs
+++ b/libs/@local/hashql/mir/src/intrinsic.rs
@@ -30,52 +30,3 @@ pub struct Intrinsic {
// Hint to any optimization passes that this intrinsic should not be optimized in any way.
pub optimize: bool,
}
-
-pub(crate) fn entity_property_access_body<'heap>(
- env: &Environment<'heap>,
- id: DefId,
- span: SpanId,
-) -> Body<'heap> {
- // Intrinsic body for property access, we **cannot** mock this specific intrinsic, because it's
- // semantics are - while expressible - compile-time only. We cannot index into a struct at
- // run-time from a runtime-defined value, without sacrifing correctness guarantees the MIR
- // relies on. Meaning that indeed we must mock this specific intrinsic on all backends that
- // support it.
- let builder = TypeBuilder::spanned(span, env);
-
- let mut local_decls = IdVec::with_capacity_in(2, env.heap);
- local_decls.push(LocalDecl {
- span,
- r#type: entity(&builder, builder.unknown(), None),
- name: Some(sym::entity),
- });
- local_decls.push(LocalDecl {
- span,
- r#type: json::types::json_path(&builder, None),
- name: Some(sym::pointer),
- });
-
- let mut blocks = BasicBlockVec::with_capacity_in(1, env.heap);
- blocks.push(BasicBlock {
- params: Interned::empty(),
- statements: Vec::new_in(env.heap),
- terminator: Terminator {
- span,
- kind: TerminatorKind::Unreachable,
- },
- });
- let basic_blocks = BasicBlocks::new(blocks);
-
- Body {
- id,
- span,
- return_type: TypeBuilder::spanned(span, env).unknown(),
- source: Source::Intrinsic(Intrinsic {
- id: IntrinsicId::EntityPropertyAccess,
- optimize: false,
- }),
- local_decls,
- basic_blocks,
- args: 2,
- }
-}
From 8eb5dfd0cd4b42e53c8b926cd8aaa2cb3bc7d11c Mon Sep 17 00:00:00 2001
From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com>
Date: Mon, 22 Jun 2026 14:13:51 +0200
Subject: [PATCH 6/8] chore: remove imports
---
.../hashql/core/src/collections/pool.rs | 3 ++-
.../core/src/module/std_lib/graph/entity.rs | 5 ++---
libs/@local/hashql/mir/src/intrinsic.rs | 20 +------------------
3 files changed, 5 insertions(+), 23 deletions(-)
diff --git a/libs/@local/hashql/core/src/collections/pool.rs b/libs/@local/hashql/core/src/collections/pool.rs
index e1ca6575cfb..b1d3abe49c6 100644
--- a/libs/@local/hashql/core/src/collections/pool.rs
+++ b/libs/@local/hashql/core/src/collections/pool.rs
@@ -43,7 +43,8 @@
//! assert_eq!(vec2.len(), 0);
//! ```
-use std::alloc::{Allocator, Global};
+use alloc::alloc::Global;
+use core::alloc::Allocator;
use crate::id::{Id, bit_vec::MixedBitSet};
diff --git a/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs b/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs
index 0da64582371..3289a960f43 100644
--- a/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs
+++ b/libs/@local/hashql/core/src/module/std_lib/graph/entity.rs
@@ -5,8 +5,7 @@ use crate::{
locals::TypeDef,
std_lib::{
self, CacheId, ModuleCache, ModuleDef, StandardLibraryContext, StandardLibraryModule,
- core::{func, option::types::option},
- decl,
+ core::func, decl,
},
},
symbol::{Symbol, sym},
@@ -70,7 +69,7 @@ impl<'heap> StandardLibraryModule<'heap> for Entity {
let decl = decl!(context;
(entity: context.ty.apply([(entity_ty.arguments[0].id, T)], entity_ty.id),
path: json_path_ty.id
- ) -> option(&context.ty, context.ty.unknown())
+ ) -> context.ty.unknown()
);
func(
diff --git a/libs/@local/hashql/mir/src/intrinsic.rs b/libs/@local/hashql/mir/src/intrinsic.rs
index 8c5525785b7..8b1c40bbf4a 100644
--- a/libs/@local/hashql/mir/src/intrinsic.rs
+++ b/libs/@local/hashql/mir/src/intrinsic.rs
@@ -1,22 +1,4 @@
-use hashql_core::{
- id::{Id, IdVec},
- intern::Interned,
- module::std_lib::{core::json, graph::types::knowledge::entity::types::entity},
- span::SpanId,
- symbol::sym,
- r#type::{TypeBuilder, environment::Environment},
-};
-
-use crate::{
- body::{
- Body, Source,
- basic_block::{BasicBlock, BasicBlockVec},
- basic_blocks::BasicBlocks,
- local::LocalDecl,
- terminator::{Terminator, TerminatorKind},
- },
- def::DefId,
-};
+use hashql_core::id::Id;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Id)]
#[repr(u8)]
From 829018e60ef912fec74099030d700881521b99c0 Mon Sep 17 00:00:00 2001
From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com>
Date: Mon, 22 Jun 2026 15:45:54 +0200
Subject: [PATCH 7/8] feat: add to different passes
---
libs/@local/hashql/eval/src/context.rs | 3 +-
.../ui/postgres/comparison-no-cast.aux.mir | 12 +-
.../ui/postgres/dict-construction.aux.mir | 20 +-
.../ui/postgres/entity-archived-check.stdout | 5 +-
.../ui/postgres/list-construction.aux.mir | 8 +-
.../ui/postgres/mixed-sources-filter.stdout | 5 +-
.../ui/postgres/opaque-passthrough.aux.mir | 30 +-
.../ui/postgres/struct-construction.aux.mir | 20 +-
.../ui/postgres/tuple-construction.aux.mir | 20 +-
libs/@local/hashql/mir/src/body/mod.rs | 10 +
libs/@local/hashql/mir/src/builder/body.rs | 10 +-
.../hashql/mir/src/pass/execution/mod.rs | 3 +-
.../statement_placement/embedding/mod.rs | 9 +-
.../statement_placement/interpret/mod.rs | 6 +-
.../statement_placement/postgres/mod.rs | 6 +-
.../administrative_reduction/kind.rs | 41 +-
.../transform/administrative_reduction/mod.rs | 6 +-
.../administrative_reduction/tests.rs | 151 +++-
.../mir/src/pass/transform/inline/analysis.rs | 2 +-
libs/@local/hashql/mir/src/pretty/text.rs | 6 +-
libs/@local/hashql/mir/src/reify/atom.rs | 7 +-
.../inline_trivial_closure_binary.snap | 60 ++
.../inline_trivial_closure_transitive.snap | 92 +++
.../non-reducible-computation.jsonc | 2 +-
.../non-reducible-computation.stdout | 10 +-
.../ui/pass/inline/closure-inline.stdout | 16 +-
.../ui/pass/inline/filter-aggressive.stdout | 46 +-
.../ui/pass/inline/filter-with-ctor.stdout | 46 +-
.../ui/pass/inline/heuristic-inline.stdout | 68 +-
.../ui/pass/inline/too-large-to-inline.stdout | 8 +-
.../cascading-simplification.stdout | 118 +--
.../post_inline/closure-env-cleanup.stdout | 26 +-
.../constant-propagation-after-inline.stdout | 26 +-
.../nested-branch-elimination.stdout | 98 +--
.../ui/pass/post_inline/showcase.aux.svg | 688 +++++++++---------
.../tests/ui/pass/post_inline/showcase.stdout | 112 +--
36 files changed, 890 insertions(+), 906 deletions(-)
create mode 100644 libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/inline_trivial_closure_binary.snap
create mode 100644 libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/inline_trivial_closure_transitive.snap
diff --git a/libs/@local/hashql/eval/src/context.rs b/libs/@local/hashql/eval/src/context.rs
index 5d59812c71c..33e2f686ab1 100644
--- a/libs/@local/hashql/eval/src/context.rs
+++ b/libs/@local/hashql/eval/src/context.rs
@@ -80,7 +80,8 @@ impl<'ctx, 'heap, A: Allocator> CodeGenerationContext<'ctx, 'heap, A> {
Source::Ctor(_)
| Source::Closure(_, _)
| Source::Thunk(_, _)
- | Source::Intrinsic(_) => continue,
+ | Source::Intrinsic(_)
+ | Source::Synthetic(_) => continue,
Source::GraphReadFilter(_) => {}
}
diff --git a/libs/@local/hashql/eval/tests/ui/postgres/comparison-no-cast.aux.mir b/libs/@local/hashql/eval/tests/ui/postgres/comparison-no-cast.aux.mir
index 8ab01df57eb..a6798c820bb 100644
--- a/libs/@local/hashql/eval/tests/ui/postgres/comparison-no-cast.aux.mir
+++ b/libs/@local/hashql/eval/tests/ui/postgres/comparison-no-cast.aux.mir
@@ -34,9 +34,9 @@ thunk {thunk#4}() -> Boolean {
let %2: Integer
bb0(): {
- %2 = input LOAD x
- %1 = input LOAD y
- %0 = %2 > %1
+ %1 = input LOAD x
+ %2 = input LOAD y
+ %0 = %1 > %2
return %0
}
@@ -48,9 +48,9 @@ fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity
let %4: Integer
bb0(): { // postgres
- %4 = input LOAD x
- %3 = input LOAD y
- %2 = %4 > %3
+ %3 = input LOAD x
+ %4 = input LOAD y
+ %2 = %3 > %4
return %2
}
diff --git a/libs/@local/hashql/eval/tests/ui/postgres/dict-construction.aux.mir b/libs/@local/hashql/eval/tests/ui/postgres/dict-construction.aux.mir
index 121e5b297c3..3aed8c63a62 100644
--- a/libs/@local/hashql/eval/tests/ui/postgres/dict-construction.aux.mir
+++ b/libs/@local/hashql/eval/tests/ui/postgres/dict-construction.aux.mir
@@ -30,13 +30,13 @@ thunk {thunk#3}() -> ::graph::types::principal::actor_group::web::WebId {
thunk {thunk#5}() -> Dict<::graph::types::knowledge::entity::EntityUuid, ::graph::types::principal::actor_group::web::WebId> {
let %0: Dict<::graph::types::knowledge::entity::EntityUuid, ::graph::types::principal::actor_group::web::WebId>
- let %1: ::graph::types::principal::actor_group::web::WebId
- let %2: ::graph::types::knowledge::entity::EntityUuid
+ let %1: ::graph::types::knowledge::entity::EntityUuid
+ let %2: ::graph::types::principal::actor_group::web::WebId
bb0(): {
- %2 = input LOAD k
- %1 = input LOAD v
- %0 = dict(%2: %1)
+ %1 = input LOAD k
+ %2 = input LOAD v
+ %0 = dict(%1: %2)
return %0
}
@@ -46,14 +46,14 @@ fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity
let %2: Dict<::graph::types::knowledge::entity::EntityUuid, ::graph::types::principal::actor_group::web::WebId>
let %3: Boolean
let %4: Dict<::graph::types::knowledge::entity::EntityUuid, ::graph::types::principal::actor_group::web::WebId>
- let %5: ::graph::types::principal::actor_group::web::WebId
- let %6: ::graph::types::knowledge::entity::EntityUuid
+ let %5: ::graph::types::knowledge::entity::EntityUuid
+ let %6: ::graph::types::principal::actor_group::web::WebId
bb0(): { // postgres
%2 = dict(%1.metadata.record_id.entity_id.entity_uuid: %1.metadata.record_id.entity_id.web_id)
- %6 = input LOAD k
- %5 = input LOAD v
- %4 = dict(%6: %5)
+ %5 = input LOAD k
+ %6 = input LOAD v
+ %4 = dict(%5: %6)
%3 = %2 == %4
return %3
diff --git a/libs/@local/hashql/eval/tests/ui/postgres/entity-archived-check.stdout b/libs/@local/hashql/eval/tests/ui/postgres/entity-archived-check.stdout
index f3f9ebc2bff..58fb7a3febf 100644
--- a/libs/@local/hashql/eval/tests/ui/postgres/entity-archived-check.stdout
+++ b/libs/@local/hashql/eval/tests/ui/postgres/entity-archived-check.stdout
@@ -2,8 +2,9 @@
SELECT ("continuation_1_0"."row")."block" AS "continuation_1_0_block", ("continuation_1_0"."row")."locals" AS "continuation_1_0_locals", ("continuation_1_0"."row")."values" AS "continuation_1_0_values"
FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0"
-INNER JOIN "entity_editions" AS "entity_editions_0_0_1"
- ON "entity_editions_0_0_1"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id"
+CROSS JOIN LATERAL (SELECT "ee"."entity_edition_id" AS "entity_edition_id", "ee"."properties" AS "properties", "ee"."archived" AS "archived", "ee"."confidence" AS "confidence", "ee"."provenance" AS "provenance", "ee"."property_metadata" AS "property_metadata"
+FROM "entity_editions" AS "ee"
+WHERE "ee"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "entity_editions_0_0_1"
CROSS JOIN LATERAL (SELECT (ROW(COALESCE(((NOT("entity_editions_0_0_1"."archived"))::boolean), FALSE), NULL, NULL, NULL)::continuation) AS "row") AS "continuation_1_0"
WHERE "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) AND ("continuation_1_0"."row")."filter" IS NOT FALSE
diff --git a/libs/@local/hashql/eval/tests/ui/postgres/list-construction.aux.mir b/libs/@local/hashql/eval/tests/ui/postgres/list-construction.aux.mir
index 129a0800977..6bfeead03f9 100644
--- a/libs/@local/hashql/eval/tests/ui/postgres/list-construction.aux.mir
+++ b/libs/@local/hashql/eval/tests/ui/postgres/list-construction.aux.mir
@@ -36,10 +36,10 @@ fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity
let %6: ::graph::types::knowledge::entity::EntityUuid
bb0(): { // postgres
- %6 = input LOAD u
- %2 = list(%1.metadata.record_id.entity_id.entity_uuid, %6)
- %5 = input LOAD v
- %3 = list(%5, %1.metadata.record_id.entity_id.entity_uuid)
+ %5 = input LOAD u
+ %2 = list(%1.metadata.record_id.entity_id.entity_uuid, %5)
+ %6 = input LOAD v
+ %3 = list(%6, %1.metadata.record_id.entity_id.entity_uuid)
%4 = %2 == %3
return %4
diff --git a/libs/@local/hashql/eval/tests/ui/postgres/mixed-sources-filter.stdout b/libs/@local/hashql/eval/tests/ui/postgres/mixed-sources-filter.stdout
index 726b00ebc07..5f00d3e7f50 100644
--- a/libs/@local/hashql/eval/tests/ui/postgres/mixed-sources-filter.stdout
+++ b/libs/@local/hashql/eval/tests/ui/postgres/mixed-sources-filter.stdout
@@ -2,8 +2,9 @@
SELECT ("continuation_0_0"."row")."block" AS "continuation_0_0_block", ("continuation_0_0"."row")."locals" AS "continuation_0_0_locals", ("continuation_0_0"."row")."values" AS "continuation_0_0_values", ("continuation_1_0"."row")."block" AS "continuation_1_0_block", ("continuation_1_0"."row")."locals" AS "continuation_1_0_locals", ("continuation_1_0"."row")."values" AS "continuation_1_0_values"
FROM "entity_temporal_metadata" AS "entity_temporal_metadata_0_0_0"
-INNER JOIN "entity_editions" AS "entity_editions_0_0_1"
- ON "entity_editions_0_0_1"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id"
+CROSS JOIN LATERAL (SELECT "ee"."entity_edition_id" AS "entity_edition_id", "ee"."properties" AS "properties", "ee"."archived" AS "archived", "ee"."confidence" AS "confidence", "ee"."provenance" AS "provenance", "ee"."property_metadata" AS "property_metadata"
+FROM "entity_editions" AS "ee"
+WHERE "ee"."entity_edition_id" = "entity_temporal_metadata_0_0_0"."entity_edition_id") AS "entity_editions_0_0_1"
CROSS JOIN LATERAL (SELECT (ROW(COALESCE(((NOT("entity_editions_0_0_1"."archived"))::boolean), FALSE), NULL, NULL, NULL)::continuation) AS "row") AS "continuation_0_0"
CROSS JOIN LATERAL (SELECT (ROW(COALESCE(((to_jsonb("entity_temporal_metadata_0_0_0"."entity_uuid") = to_jsonb(($3::jsonb)))::boolean), FALSE), NULL, NULL, NULL)::continuation) AS "row") AS "continuation_1_0"
WHERE "entity_temporal_metadata_0_0_0"."transaction_time" && ($1::tstzrange) AND "entity_temporal_metadata_0_0_0"."decision_time" && ($2::tstzrange) AND ("continuation_0_0"."row")."filter" IS NOT FALSE AND ("continuation_1_0"."row")."filter" IS NOT FALSE
diff --git a/libs/@local/hashql/eval/tests/ui/postgres/opaque-passthrough.aux.mir b/libs/@local/hashql/eval/tests/ui/postgres/opaque-passthrough.aux.mir
index 660d39aa265..51a46c6d860 100644
--- a/libs/@local/hashql/eval/tests/ui/postgres/opaque-passthrough.aux.mir
+++ b/libs/@local/hashql/eval/tests/ui/postgres/opaque-passthrough.aux.mir
@@ -39,14 +39,14 @@ thunk {thunk#3}() -> (String) -> ::core::uuid::Uuid {
}
thunk {thunk#4}() -> ::core::uuid::Uuid {
- let %0: ::core::uuid::Uuid
- let %1: String
+ let %0: String
+ let %1: ::core::uuid::Uuid
bb0(): {
- %1 = input LOAD id
- %0 = opaque(::core::uuid::Uuid, %1)
+ %0 = input LOAD id
+ %1 = opaque(::core::uuid::Uuid, %0)
- return %0
+ return %1
}
}
@@ -72,13 +72,13 @@ thunk {thunk#5}() -> (::core::uuid::Uuid) -> ::graph::types::knowledge::entity::
thunk {thunk#6}() -> ::graph::types::knowledge::entity::EntityUuid {
let %0: ::graph::types::knowledge::entity::EntityUuid
- let %1: ::core::uuid::Uuid
- let %2: String
+ let %1: String
+ let %2: ::core::uuid::Uuid
bb0(): {
- %2 = input LOAD id
- %1 = opaque(::core::uuid::Uuid, %2)
- %0 = opaque(::graph::types::knowledge::entity::EntityUuid, %1)
+ %1 = input LOAD id
+ %2 = opaque(::core::uuid::Uuid, %1)
+ %0 = opaque(::graph::types::knowledge::entity::EntityUuid, %2)
return %0
}
@@ -87,13 +87,13 @@ thunk {thunk#6}() -> ::graph::types::knowledge::entity::EntityUuid {
fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity) -> Boolean {
let %2: Boolean
let %3: ::graph::types::knowledge::entity::EntityUuid
- let %4: ::core::uuid::Uuid
- let %5: String
+ let %4: String
+ let %5: ::core::uuid::Uuid
bb0(): { // postgres
- %5 = input LOAD id
- %4 = opaque(::core::uuid::Uuid, %5)
- %3 = opaque(::graph::types::knowledge::entity::EntityUuid, %4)
+ %4 = input LOAD id
+ %5 = opaque(::core::uuid::Uuid, %4)
+ %3 = opaque(::graph::types::knowledge::entity::EntityUuid, %5)
%2 = %1.metadata.record_id.entity_id.entity_uuid == %3
return %2
diff --git a/libs/@local/hashql/eval/tests/ui/postgres/struct-construction.aux.mir b/libs/@local/hashql/eval/tests/ui/postgres/struct-construction.aux.mir
index f4287ad3fc2..87df7e6398f 100644
--- a/libs/@local/hashql/eval/tests/ui/postgres/struct-construction.aux.mir
+++ b/libs/@local/hashql/eval/tests/ui/postgres/struct-construction.aux.mir
@@ -30,13 +30,13 @@ thunk {thunk#3}() -> ::graph::types::principal::actor_group::web::WebId {
thunk {thunk#5}() -> (uuid: ::graph::types::knowledge::entity::EntityUuid, web: ::graph::types::principal::actor_group::web::WebId) {
let %0: (uuid: ::graph::types::knowledge::entity::EntityUuid, web: ::graph::types::principal::actor_group::web::WebId)
- let %1: ::graph::types::principal::actor_group::web::WebId
- let %2: ::graph::types::knowledge::entity::EntityUuid
+ let %1: ::graph::types::knowledge::entity::EntityUuid
+ let %2: ::graph::types::principal::actor_group::web::WebId
bb0(): {
- %2 = input LOAD u
- %1 = input LOAD w
- %0 = (uuid: %2, web: %1)
+ %1 = input LOAD u
+ %2 = input LOAD w
+ %0 = (uuid: %1, web: %2)
return %0
}
@@ -46,14 +46,14 @@ fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity
let %2: (uuid: ::graph::types::knowledge::entity::EntityUuid, web: ::graph::types::principal::actor_group::web::WebId)
let %3: Boolean
let %4: (uuid: ::graph::types::knowledge::entity::EntityUuid, web: ::graph::types::principal::actor_group::web::WebId)
- let %5: ::graph::types::principal::actor_group::web::WebId
- let %6: ::graph::types::knowledge::entity::EntityUuid
+ let %5: ::graph::types::knowledge::entity::EntityUuid
+ let %6: ::graph::types::principal::actor_group::web::WebId
bb0(): { // postgres
%2 = (uuid: %1.metadata.record_id.entity_id.entity_uuid, web: %1.metadata.record_id.entity_id.web_id)
- %6 = input LOAD u
- %5 = input LOAD w
- %4 = (uuid: %6, web: %5)
+ %5 = input LOAD u
+ %6 = input LOAD w
+ %4 = (uuid: %5, web: %6)
%3 = %2 == %4
return %3
diff --git a/libs/@local/hashql/eval/tests/ui/postgres/tuple-construction.aux.mir b/libs/@local/hashql/eval/tests/ui/postgres/tuple-construction.aux.mir
index aa383add640..4df382a5df0 100644
--- a/libs/@local/hashql/eval/tests/ui/postgres/tuple-construction.aux.mir
+++ b/libs/@local/hashql/eval/tests/ui/postgres/tuple-construction.aux.mir
@@ -30,13 +30,13 @@ thunk {thunk#3}() -> ::graph::types::principal::actor_group::web::WebId {
thunk {thunk#5}() -> (::graph::types::knowledge::entity::EntityUuid, ::graph::types::principal::actor_group::web::WebId) {
let %0: (::graph::types::knowledge::entity::EntityUuid, ::graph::types::principal::actor_group::web::WebId)
- let %1: ::graph::types::principal::actor_group::web::WebId
- let %2: ::graph::types::knowledge::entity::EntityUuid
+ let %1: ::graph::types::knowledge::entity::EntityUuid
+ let %2: ::graph::types::principal::actor_group::web::WebId
bb0(): {
- %2 = input LOAD u
- %1 = input LOAD w
- %0 = (%2, %1)
+ %1 = input LOAD u
+ %2 = input LOAD w
+ %0 = (%1, %2)
return %0
}
@@ -46,14 +46,14 @@ fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity
let %2: (::graph::types::knowledge::entity::EntityUuid, ::graph::types::principal::actor_group::web::WebId)
let %3: Boolean
let %4: (::graph::types::knowledge::entity::EntityUuid, ::graph::types::principal::actor_group::web::WebId)
- let %5: ::graph::types::principal::actor_group::web::WebId
- let %6: ::graph::types::knowledge::entity::EntityUuid
+ let %5: ::graph::types::knowledge::entity::EntityUuid
+ let %6: ::graph::types::principal::actor_group::web::WebId
bb0(): { // postgres
%2 = (%1.metadata.record_id.entity_id.entity_uuid, %1.metadata.record_id.entity_id.web_id)
- %6 = input LOAD u
- %5 = input LOAD w
- %4 = (%6, %5)
+ %5 = input LOAD u
+ %6 = input LOAD w
+ %4 = (%5, %6)
%3 = %2 == %4
return %3
diff --git a/libs/@local/hashql/mir/src/body/mod.rs b/libs/@local/hashql/mir/src/body/mod.rs
index c8f11f527ca..74e6e2fdb62 100644
--- a/libs/@local/hashql/mir/src/body/mod.rs
+++ b/libs/@local/hashql/mir/src/body/mod.rs
@@ -78,6 +78,16 @@ pub enum Source<'heap> {
/// operation is handled directly by the compiler or runtime.
Intrinsic(Intrinsic),
+ /// A compiler-synthesized wrapper that makes a non-first-class operation callable as a
+ /// closure value.
+ ///
+ /// The [`Symbol`] identifies the canonical path of the wrapped operation
+ /// (e.g. `::core::math::add`). The body takes the operation's parameters (with a unit
+ /// environment as the first argument per the fat closure ABI) and performs the operation.
+ ///
+ /// Synthetic bodies are always inlined and participate in administrative reduction.
+ Synthetic(Symbol<'heap>),
+
/// A filter closure for graph read operations.
///
/// This variant represents MIR generated from a closure expression used to
diff --git a/libs/@local/hashql/mir/src/builder/body.rs b/libs/@local/hashql/mir/src/builder/body.rs
index 1176fa36e84..29724042e8f 100644
--- a/libs/@local/hashql/mir/src/builder/body.rs
+++ b/libs/@local/hashql/mir/src/builder/body.rs
@@ -163,7 +163,8 @@ impl<'env, 'heap> Deref for BodyBuilder<'env, 'heap> {
///
/// ## Header
///
-/// - ``: `fn` (closure), `thunk`, `[ctor sym::path]`, or `intrinsic`
+/// - ``: `fn` (closure), `thunk`, `[ctor sym::path]`, `[synthetic sym::path]`, or
+/// `intrinsic`
/// - ``: Numeric literal for `DefId`
/// - ``: Number of function arguments
/// - ``: Return type (`Int`, `Bool`, tuple `(Int, Bool)`, or custom `|t| t.foo()`)
@@ -199,7 +200,7 @@ impl<'env, 'heap> Deref for BodyBuilder<'env, 'heap> {
/// | `x = struct a: , b: ;` | Create struct aggregate |
/// | `x = closure ;` | Create closure aggregate |
/// | `x = bin. ;` | Binary operation (e.g., `bin.== x y`) |
-/// | `x = un. ;` | Unary operation (e.g., `un.! cond`) |
+/// | `x = un. ;` | Unary operation (e.g., `un.neg x`) |
/// | `x = input.load! "name";` | Load required input |
/// | `x = input.load "name";` | Load optional input |
/// | `x = input.exists "name";` | Check if input exists |
@@ -256,7 +257,7 @@ impl<'env, 'heap> Deref for BodyBuilder<'env, 'heap> {
///
/// Binary (`bin.`): `==`, `!=`, `<`, `<=`, `>`, `>=`, `&`, `|`, `+`, `-`, `*`, `/`.
///
-/// Unary (`un.`): `!`, `neg`, `~`.
+/// Unary (`un.`): `neg`, `~`.
#[macro_export]
macro_rules! body {
(
@@ -383,6 +384,9 @@ macro_rules! body {
(@source intrinsic) => {
$crate::body::Source::Intrinsic($crate::def::DefId::PLACEHOLDER)
};
+ (@source [synthetic $name:expr]) => {
+ $crate::body::Source::Synthetic($name)
+ };
}
pub use body;
diff --git a/libs/@local/hashql/mir/src/pass/execution/mod.rs b/libs/@local/hashql/mir/src/pass/execution/mod.rs
index 81408178561..b6086bb13f2 100644
--- a/libs/@local/hashql/mir/src/pass/execution/mod.rs
+++ b/libs/@local/hashql/mir/src/pass/execution/mod.rs
@@ -174,7 +174,8 @@ impl<'heap, S: BumpAllocator> ExecutionAnalysis<'_, 'heap, S> {
Source::Ctor(_)
| Source::Closure(_, _)
| Source::Thunk(_, _)
- | Source::Intrinsic(_) => continue,
+ | Source::Intrinsic(_)
+ | Source::Synthetic(_) => continue,
Source::GraphReadFilter(_) => {}
}
diff --git a/libs/@local/hashql/mir/src/pass/execution/statement_placement/embedding/mod.rs b/libs/@local/hashql/mir/src/pass/execution/statement_placement/embedding/mod.rs
index cc0e6ed0543..8f9fda1661e 100644
--- a/libs/@local/hashql/mir/src/pass/execution/statement_placement/embedding/mod.rs
+++ b/libs/@local/hashql/mir/src/pass/execution/statement_placement/embedding/mod.rs
@@ -153,7 +153,11 @@ impl<'heap, A: Allocator + Clone, S: Allocator> StatementPlacement<'heap, A>
match body.source {
Source::GraphReadFilter(_) => {}
- Source::Ctor(_) | Source::Closure(..) | Source::Thunk(..) | Source::Intrinsic(_) => {
+ Source::Ctor(_)
+ | Source::Closure(..)
+ | Source::Thunk(..)
+ | Source::Intrinsic(_)
+ | Source::Synthetic(_) => {
return (statement_costs, terminator_costs);
}
}
@@ -169,7 +173,8 @@ impl<'heap, A: Allocator + Clone, S: Allocator> StatementPlacement<'heap, A>
Source::Ctor(_)
| Source::Closure(..)
| Source::Thunk(..)
- | Source::Intrinsic(_) => return,
+ | Source::Intrinsic(_)
+ | Source::Synthetic(_) => return,
}
debug_assert_eq!(body.args, 2);
diff --git a/libs/@local/hashql/mir/src/pass/execution/statement_placement/interpret/mod.rs b/libs/@local/hashql/mir/src/pass/execution/statement_placement/interpret/mod.rs
index eebbb5dbefb..76f3c9971a9 100644
--- a/libs/@local/hashql/mir/src/pass/execution/statement_placement/interpret/mod.rs
+++ b/libs/@local/hashql/mir/src/pass/execution/statement_placement/interpret/mod.rs
@@ -87,7 +87,11 @@ impl<'heap, A: Allocator + Clone> StatementPlacement<'heap, A> for InterpreterSt
match body.source {
Source::GraphReadFilter(_) => {}
- Source::Ctor(_) | Source::Closure(..) | Source::Thunk(..) | Source::Intrinsic(_) => {
+ Source::Ctor(_)
+ | Source::Closure(..)
+ | Source::Thunk(..)
+ | Source::Intrinsic(_)
+ | Source::Synthetic(_) => {
return (statement_costs, terminator_costs);
}
}
diff --git a/libs/@local/hashql/mir/src/pass/execution/statement_placement/postgres/mod.rs b/libs/@local/hashql/mir/src/pass/execution/statement_placement/postgres/mod.rs
index 637d6cb3aa9..d7668c96403 100644
--- a/libs/@local/hashql/mir/src/pass/execution/statement_placement/postgres/mod.rs
+++ b/libs/@local/hashql/mir/src/pass/execution/statement_placement/postgres/mod.rs
@@ -737,7 +737,11 @@ impl<'heap, A: Allocator + Clone, S: Allocator> StatementPlacement<'heap, A>
match body.source {
Source::GraphReadFilter(_) => {}
- Source::Ctor(_) | Source::Closure(..) | Source::Thunk(..) | Source::Intrinsic(_) => {
+ Source::Ctor(_)
+ | Source::Closure(..)
+ | Source::Thunk(..)
+ | Source::Intrinsic(_)
+ | Source::Synthetic(_) => {
return (statement_costs, terminator_costs);
}
}
diff --git a/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/kind.rs b/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/kind.rs
index 709dac04342..5dd78f6dcd1 100644
--- a/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/kind.rs
+++ b/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/kind.rs
@@ -64,6 +64,16 @@ pub(crate) enum ReductionKind {
/// Shape: single basic block, trivial prelude statements, final statement is an `Apply`,
/// terminator returns the result of that `Apply`.
ForwardingClosure,
+
+ /// A single-operation function whose result is returned directly.
+ ///
+ /// Shape: single basic block, trivial prelude (only `Nop`/`Load`/`Aggregate`), final
+ /// statement is any non-call assignment (`Binary`, `Unary`, `Aggregate`, `Input`, `Load`),
+ /// terminator returns the final statement's result.
+ ///
+ /// This covers compiler-synthesized wrappers (e.g. intrinsic-as-value bodies) as well as
+ /// user-written closures that happen to be a single operation.
+ TrivialClosure,
}
impl ReductionKind {
@@ -127,22 +137,39 @@ impl ReductionKind {
return None;
}
- // Final statement must be a call assignment.
- let StatementKind::Assign(Assign {
- lhs,
- rhs: RValue::Apply(_),
- }) = final_stmt.kind
- else {
+ // In this case it may either be a forwarding closure **or** a synthetic closure, this
+ // depends on the final statement, in either case the last statement must be an assignment.
+ let StatementKind::Assign(Assign { lhs, ref rhs }) = final_stmt.kind else {
return None;
};
+ // The kind depends on the final statement. Both cases are beta-reduction
+ // (substituting arguments into a function body at a call site):
+ //
+ // Apply: the body forwards to another call. The wrapper (lambda x. f(x)) is an
+ // eta-expansion of the inner function; inlining at the call site is beta-reduction
+ // that exposes the inner call directly.
+ //
+ // Binary, Unary, and other primitive operations: the body computes a single result
+ // from its arguments. Inlining substitutes the arguments and exposes the primitive
+ // operation. A subsequent constant folding pass may then perform delta-reduction
+ // (evaluating the primitive when operands are known).
+ let kind = match rhs {
+ RValue::Apply(_) => Self::ForwardingClosure,
+ RValue::Aggregate(_)
+ | RValue::Binary(_)
+ | RValue::Unary(_)
+ | RValue::Input(_)
+ | RValue::Load(_) => Self::TrivialClosure,
+ };
+
// Terminator must return the call's result.
if bb.terminator.kind
== TerminatorKind::Return(Return {
value: Operand::Place(lhs),
})
{
- return Some(Self::ForwardingClosure);
+ return Some(kind);
}
None
diff --git a/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/mod.rs b/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/mod.rs
index f794a012b19..958d0b96bd8 100644
--- a/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/mod.rs
+++ b/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/mod.rs
@@ -7,7 +7,7 @@
//!
//! # Reduction Targets
//!
-//! The pass identifies and reduces two kinds of functions:
+//! The pass identifies and reduces three kinds of functions:
//!
//! - **Trivial thunks**: Single-basic-block functions with only trivial statements (`Load`,
//! `Aggregate`, `Nop`) that immediately return a value. These are fully inlined.
@@ -15,6 +15,10 @@
//! - **Forwarding closures**: Single-basic-block functions where a trivial prelude leads to a
//! single call whose result is returned. The wrapper is eliminated, exposing the inner call.
//!
+//! - **Trivial closures**: Single-basic-block functions where a trivial prelude leads to a single
+//! non-call operation (`Binary`, `Unary`, `Aggregate`, `Input`, `Load`) whose result is returned.
+//! Covers synthesized intrinsic wrappers and user closures that are a single expression.
+//!
//! # Algorithm
//!
//! The pass operates in a single traversal using call-graph postorder (callees before callers):
diff --git a/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/tests.rs b/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/tests.rs
index 41897c0d07a..c6cf9702709 100644
--- a/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/tests.rs
+++ b/libs/@local/hashql/mir/src/pass/transform/administrative_reduction/tests.rs
@@ -112,9 +112,11 @@ fn classify_non_reducible_multi_bb() {
assert_eq!(ReductionKind::of(&body), None);
}
-/// Tests that a body with non-trivial operations (Binary, Unary, etc.) is not reducible.
+/// A single binary operation with trivial prelude is a [`TrivialClosure`].
+///
+/// [`TrivialClosure`]: ReductionKind::TrivialClosure
#[test]
-fn classify_non_reducible_non_trivial_op() {
+fn classify_trivial_closure_binary() {
let heap = Heap::new();
let interner = Interner::new(&heap);
let env = Environment::new(&heap);
@@ -129,6 +131,56 @@ fn classify_non_reducible_non_trivial_op() {
}
});
+ assert_eq!(
+ ReductionKind::of(&body),
+ Some(ReductionKind::TrivialClosure)
+ );
+}
+
+/// A single unary operation with trivial prelude is a [`TrivialClosure`].
+///
+/// [`TrivialClosure`]: ReductionKind::TrivialClosure
+#[test]
+fn classify_trivial_closure_unary() {
+ let heap = Heap::new();
+ let interner = Interner::new(&heap);
+ let env = Environment::new(&heap);
+
+ let body = body!(interner, env; fn@0/0 -> Int {
+ decl x: Int, result: Int;
+
+ bb0() {
+ x = load 1;
+ result = un.neg x;
+ return result;
+ }
+ });
+
+ assert_eq!(
+ ReductionKind::of(&body),
+ Some(ReductionKind::TrivialClosure)
+ );
+}
+
+/// A binary operation with a non-trivial prelude (contains a call) is not reducible.
+#[test]
+fn classify_non_reducible_non_trivial_prelude() {
+ let heap = Heap::new();
+ let interner = Interner::new(&heap);
+ let env = Environment::new(&heap);
+
+ let def_id = DefId::new(5);
+
+ let body = body!(interner, env; fn@0/0 -> Bool {
+ decl x: Int, y: Bool;
+
+ bb0() {
+ x = apply def_id;
+ y = bin.== x 2;
+ return y;
+ }
+ });
+
assert_eq!(ReductionKind::of(&body), None);
}
@@ -644,3 +696,98 @@ fn inline_indirect_closure() {
},
);
}
+
+/// Tests inlining a trivial closure containing a single binary operation.
+///
+/// body0 is a two-argument function that performs a single comparison and returns the result.
+/// body1 calls body0. After reduction, the binary operation is spliced directly into body1.
+#[test]
+fn inline_trivial_closure_binary() {
+ let heap = Heap::new();
+ let interner = Interner::new(&heap);
+ let env = Environment::new(&heap);
+
+ let body0 = body!(interner, env; fn@0/2 -> Bool {
+ decl lhs: Int, rhs: Int, result: Bool;
+
+ bb0() {
+ result = bin.== lhs rhs;
+ return result;
+ }
+ });
+
+ let body1 = body!(interner, env; fn@1/0 -> Bool {
+ decl a: Int, b: Int, result: Bool;
+
+ bb0() {
+ a = load 1;
+ b = load 2;
+ result = apply (body0.id), a, b;
+ return result;
+ }
+ });
+
+ let mut bodies = [body0, body1];
+ assert_admin_reduction_pass(
+ "inline_trivial_closure_binary",
+ &mut bodies,
+ &mut MirContext {
+ heap: &heap,
+ env: &env,
+ interner: &interner,
+ diagnostics: DiagnosticIssues::new(),
+ },
+ );
+}
+
+/// Tests transitive reduction: a forwarding closure calling a trivial closure.
+///
+/// body0 is a trivial closure (single binary op). body1 forwards to body0. body2 calls body1.
+/// After reduction in postorder, both layers collapse and body2 contains the binary op directly.
+#[test]
+fn inline_trivial_closure_transitive() {
+ let heap = Heap::new();
+ let interner = Interner::new(&heap);
+ let env = Environment::new(&heap);
+
+ let body0 = body!(interner, env; fn@0/2 -> Bool {
+ decl lhs: Int, rhs: Int, result: Bool;
+
+ bb0() {
+ result = bin.> lhs rhs;
+ return result;
+ }
+ });
+
+ let body1 = body!(interner, env; fn@1/2 -> Bool {
+ decl x: Int, y: Int, result: Bool;
+
+ bb0() {
+ result = apply (body0.id), x, y;
+ return result;
+ }
+ });
+
+ let body2 = body!(interner, env; fn@2/0 -> Bool {
+ decl a: Int, b: Int, result: Bool;
+
+ bb0() {
+ a = load 10;
+ b = load 20;
+ result = apply (body1.id), a, b;
+ return result;
+ }
+ });
+
+ let mut bodies = [body0, body1, body2];
+ assert_admin_reduction_pass(
+ "inline_trivial_closure_transitive",
+ &mut bodies,
+ &mut MirContext {
+ heap: &heap,
+ env: &env,
+ interner: &interner,
+ diagnostics: DiagnosticIssues::new(),
+ },
+ );
+}
diff --git a/libs/@local/hashql/mir/src/pass/transform/inline/analysis.rs b/libs/@local/hashql/mir/src/pass/transform/inline/analysis.rs
index 3d1ad00def9..263f9dde472 100644
--- a/libs/@local/hashql/mir/src/pass/transform/inline/analysis.rs
+++ b/libs/@local/hashql/mir/src/pass/transform/inline/analysis.rs
@@ -272,7 +272,7 @@ impl<'ctx, 'heap, A: Allocator> BodyAnalysis<'ctx, 'heap, A> {
/// 3. Cost by visiting all rvalues and terminators.
pub(crate) fn run(&mut self, body: &Body<'heap>) {
let inline = match body.source {
- Source::Ctor(_) => InlineDirective::Always,
+ Source::Ctor(_) | Source::Synthetic(_) => InlineDirective::Always,
Source::Closure(_, _) | Source::Thunk(_, _) => InlineDirective::Heuristic,
Source::Intrinsic(_) | Source::GraphReadFilter(_) => InlineDirective::Never,
};
diff --git a/libs/@local/hashql/mir/src/pretty/text.rs b/libs/@local/hashql/mir/src/pretty/text.rs
index b87ab4cdd4f..b2003675018 100644
--- a/libs/@local/hashql/mir/src/pretty/text.rs
+++ b/libs/@local/hashql/mir/src/pretty/text.rs
@@ -39,7 +39,8 @@ const fn source_keyword(source: Source<'_>) -> &'static str {
Source::Ctor(_)
| Source::Closure(..)
| Source::GraphReadFilter(..)
- | Source::Intrinsic(_) => "fn",
+ | Source::Intrinsic(_)
+ | Source::Synthetic(_) => "fn",
}
}
@@ -489,6 +490,9 @@ where
Source::Intrinsic(Intrinsic { id, .. }) => {
write!(self.line_buffer, "{{intrinsic#{id}}}")
}
+ Source::Synthetic(symbol) => {
+ write!(self.line_buffer, "{{synthetic#{symbol}}}")
+ }
}
}
}
diff --git a/libs/@local/hashql/mir/src/reify/atom.rs b/libs/@local/hashql/mir/src/reify/atom.rs
index 73cb7d63774..5779e1d4dcb 100644
--- a/libs/@local/hashql/mir/src/reify/atom.rs
+++ b/libs/@local/hashql/mir/src/reify/atom.rs
@@ -6,7 +6,7 @@ use hashql_hir::node::{
access::{Access, FieldAccess, IndexAccess},
data::Data,
kind::NodeKind,
- variable::{QualifiedVariable, Variable},
+ variable::Variable,
};
use super::{
@@ -154,11 +154,6 @@ impl<'heap, A: Allocator, S: Allocator> Reifier<'_, '_, '_, '_, 'heap, A, S> {
}
}
- fn qualified_path(&mut self, QualifiedVariable { path, arguments }: QualifiedVariable<'heap>) {
- // For the path, we simply match against the tree of values that are supported
- todo!()
- }
-
pub(super) fn operand(&mut self, node: Node<'heap>) -> Operand<'heap> {
match node.kind {
NodeKind::Variable(Variable::Qualified(_)) => {
diff --git a/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/inline_trivial_closure_binary.snap b/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/inline_trivial_closure_binary.snap
new file mode 100644
index 00000000000..86f3045f8fc
--- /dev/null
+++ b/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/inline_trivial_closure_binary.snap
@@ -0,0 +1,60 @@
+---
+source: libs/@local/hashql/mir/src/pass/transform/administrative_reduction/tests.rs
+assertion_line: 347
+expression: value
+---
+fn {closure@4294967040}(%0: Integer, %1: Integer) -> Boolean {
+ let %2: Boolean
+
+ bb0(): {
+ %2 = %0 == %1
+
+ return %2
+ }
+}
+
+fn {closure@4294967040}() -> Boolean {
+ let %0: Integer
+ let %1: Integer
+ let %2: Boolean
+
+ bb0(): {
+ %0 = 1
+ %1 = 2
+ %2 = apply ({def@0} as FnPtr) %0 %1
+
+ return %2
+ }
+}
+
+================== Changed: Yes ==================
+
+fn {closure@4294967040}(%0: Integer, %1: Integer) -> Boolean {
+ let %2: Boolean
+
+ bb0(): {
+ %2 = %0 == %1
+
+ return %2
+ }
+}
+
+fn {closure@4294967040}() -> Boolean {
+ let %0: Integer
+ let %1: Integer
+ let %2: Boolean
+ let %3: Integer
+ let %4: Integer
+ let %5: Boolean
+
+ bb0(): {
+ %0 = 1
+ %1 = 2
+ %3 = %0
+ %4 = %1
+ %5 = %3 == %4
+ %2 = %5
+
+ return %2
+ }
+}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/inline_trivial_closure_transitive.snap b/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/inline_trivial_closure_transitive.snap
new file mode 100644
index 00000000000..ce57e9a04d9
--- /dev/null
+++ b/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/inline_trivial_closure_transitive.snap
@@ -0,0 +1,92 @@
+---
+source: libs/@local/hashql/mir/src/pass/transform/administrative_reduction/tests.rs
+assertion_line: 347
+expression: value
+---
+fn {closure@4294967040}(%0: Integer, %1: Integer) -> Boolean {
+ let %2: Boolean
+
+ bb0(): {
+ %2 = %0 > %1
+
+ return %2
+ }
+}
+
+fn {closure@4294967040}(%0: Integer, %1: Integer) -> Boolean {
+ let %2: Boolean
+
+ bb0(): {
+ %2 = apply ({def@0} as FnPtr) %0 %1
+
+ return %2
+ }
+}
+
+fn {closure@4294967040}() -> Boolean {
+ let %0: Integer
+ let %1: Integer
+ let %2: Boolean
+
+ bb0(): {
+ %0 = 10
+ %1 = 20
+ %2 = apply ({def@1} as FnPtr) %0 %1
+
+ return %2
+ }
+}
+
+================== Changed: Yes ==================
+
+fn {closure@4294967040}(%0: Integer, %1: Integer) -> Boolean {
+ let %2: Boolean
+
+ bb0(): {
+ %2 = %0 > %1
+
+ return %2
+ }
+}
+
+fn {closure@4294967040}(%0: Integer, %1: Integer) -> Boolean {
+ let %2: Boolean
+ let %3: Integer
+ let %4: Integer
+ let %5: Boolean
+
+ bb0(): {
+ %3 = %0
+ %4 = %1
+ %5 = %3 > %4
+ %2 = %5
+
+ return %2
+ }
+}
+
+fn {closure@4294967040}() -> Boolean {
+ let %0: Integer
+ let %1: Integer
+ let %2: Boolean
+ let %3: Integer
+ let %4: Integer
+ let %5: Boolean
+ let %6: Integer
+ let %7: Integer
+ let %8: Boolean
+
+ bb0(): {
+ %0 = 10
+ %1 = 20
+ %3 = %0
+ %4 = %1
+ %6 = %3
+ %7 = %4
+ %8 = %6 > %7
+ %5 = %8
+ %2 = %5
+
+ return %2
+ }
+}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/non-reducible-computation.jsonc b/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/non-reducible-computation.jsonc
index a373ce8c933..b79609a690f 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/non-reducible-computation.jsonc
+++ b/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/non-reducible-computation.jsonc
@@ -1,5 +1,5 @@
//@ run: pass
-//@ description: Closure with computation (binary op) is NOT a trivial thunk
+//@ description: Closure with computation (binary op) is NOT a trivial thunk, but a trivial closure
[
"let",
"compute",
diff --git a/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/non-reducible-computation.stdout b/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/non-reducible-computation.stdout
index 83ce2714d33..1f4175a8f0c 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/non-reducible-computation.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/administrative_reduction/non-reducible-computation.stdout
@@ -62,12 +62,20 @@ thunk compute:0() -> (Integer, Integer) -> Boolean {
let %1: Boolean
let %2: (Integer, Integer) -> Boolean
let %3: ()
+ let %4: ()
+ let %5: Integer
+ let %6: Integer
+ let %7: Boolean
bb0(): {
%3 = ()
%2 = closure(({closure#5} as FnPtr), %3)
%0 = %2
- %1 = apply %0.0 %0.1 1 2
+ %4 = %0.1
+ %5 = 1
+ %6 = 2
+ %7 = %5 == %6
+ %1 = %7
return %1
}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/inline/closure-inline.stdout b/libs/@local/hashql/mir/tests/ui/pass/inline/closure-inline.stdout
index a346854964d..171f3088b05 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/inline/closure-inline.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/inline/closure-inline.stdout
@@ -60,7 +60,7 @@ thunk get_axis:0() -> () -> (::graph::temporal::PinnedTransactionTimeTemporalAxe
let %0: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
bb0(): {
- %0 = apply ({closure#3} as FnPtr) ()
+ %0 = input LOAD axis
return %0
}
@@ -90,22 +90,10 @@ thunk get_axis:0() -> () -> (::graph::temporal::PinnedTransactionTimeTemporalAxe
*thunk {thunk#2}() -> ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes {
let %0: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
- let %1: ()
- let %2: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
bb0(): {
- %1 = ()
-
- goto -> bb2()
- }
+ %0 = input LOAD axis
- bb1(%0): {
return %0
}
-
- bb2(): {
- %2 = input LOAD axis
-
- goto -> bb1(%2)
- }
}
\ No newline at end of file
diff --git a/libs/@local/hashql/mir/tests/ui/pass/inline/filter-aggressive.stdout b/libs/@local/hashql/mir/tests/ui/pass/inline/filter-aggressive.stdout
index 322037912fe..4e3c758e9dd 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/inline/filter-aggressive.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/inline/filter-aggressive.stdout
@@ -62,21 +62,21 @@ fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity
}
*thunk {thunk#3}() -> List<::graph::types::knowledge::entity::Entity> {
- let %0: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
- let %1: List<::graph::types::knowledge::entity::Entity>
- let %2: ()
+ let %0: List<::graph::types::knowledge::entity::Entity>
+ let %1: ()
+ let %2: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
bb0(): {
- %0 = apply ({thunk#1} as FnPtr)
- %2 = ()
+ %2 = input LOAD axis
+ %1 = ()
- graph read entities(%0)
- |> filter({graph::read::filter@7}, %2)
+ graph read entities(%2)
+ |> filter({graph::read::filter@7}, %1)
|> collect -> bb1(_)
}
- bb1(%1): {
- return %1
+ bb1(%0): {
+ return %0
}
}
@@ -103,30 +103,20 @@ fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity
}
*thunk {thunk#3}() -> List<::graph::types::knowledge::entity::Entity> {
- let %0: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
- let %1: List<::graph::types::knowledge::entity::Entity>
- let %2: ()
- let %3: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
+ let %0: List<::graph::types::knowledge::entity::Entity>
+ let %1: ()
+ let %2: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
bb0(): {
- goto -> bb3()
- }
-
- bb1(%1): {
- return %1
- }
+ %2 = input LOAD axis
+ %1 = ()
- bb2(%0): {
- %2 = ()
-
- graph read entities(%0)
- |> filter({graph::read::filter@7}, %2)
+ graph read entities(%2)
+ |> filter({graph::read::filter@7}, %1)
|> collect -> bb1(_)
}
- bb3(): {
- %3 = input LOAD axis
-
- goto -> bb2(%3)
+ bb1(%0): {
+ return %0
}
}
\ No newline at end of file
diff --git a/libs/@local/hashql/mir/tests/ui/pass/inline/filter-with-ctor.stdout b/libs/@local/hashql/mir/tests/ui/pass/inline/filter-with-ctor.stdout
index e58715e7cbe..6c39de78614 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/inline/filter-with-ctor.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/inline/filter-with-ctor.stdout
@@ -196,21 +196,21 @@ fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity
}
*thunk {thunk#7}() -> List<::graph::types::knowledge::entity::Entity> {
- let %0: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
- let %1: List<::graph::types::knowledge::entity::Entity>
- let %2: ()
+ let %0: List<::graph::types::knowledge::entity::Entity>
+ let %1: ()
+ let %2: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
bb0(): {
- %0 = apply ({thunk#1} as FnPtr)
- %2 = ()
+ %2 = input LOAD axis
+ %1 = ()
- graph read entities(%0)
- |> filter({graph::read::filter@7}, %2)
+ graph read entities(%2)
+ |> filter({graph::read::filter@7}, %1)
|> collect -> bb1(_)
}
- bb1(%1): {
- return %1
+ bb1(%0): {
+ return %0
}
}
@@ -303,30 +303,20 @@ fn {graph::read::filter@7}(%0: (), %1: ::graph::types::knowledge::entity::Entity
}
*thunk {thunk#7}() -> List<::graph::types::knowledge::entity::Entity> {
- let %0: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
- let %1: List<::graph::types::knowledge::entity::Entity>
- let %2: ()
- let %3: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
+ let %0: List<::graph::types::knowledge::entity::Entity>
+ let %1: ()
+ let %2: ::graph::temporal::PinnedTransactionTimeTemporalAxes | ::graph::temporal::PinnedDecisionTimeTemporalAxes
bb0(): {
- goto -> bb3()
- }
-
- bb1(%1): {
- return %1
- }
+ %2 = input LOAD axis
+ %1 = ()
- bb2(%0): {
- %2 = ()
-
- graph read entities(%0)
- |> filter({graph::read::filter@7}, %2)
+ graph read entities(%2)
+ |> filter({graph::read::filter@7}, %1)
|> collect -> bb1(_)
}
- bb3(): {
- %3 = input LOAD axis
-
- goto -> bb2(%3)
+ bb1(%0): {
+ return %0
}
}
\ No newline at end of file
diff --git a/libs/@local/hashql/mir/tests/ui/pass/inline/heuristic-inline.stdout b/libs/@local/hashql/mir/tests/ui/pass/inline/heuristic-inline.stdout
index 1baf94ea0b9..ff2f5f54756 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/inline/heuristic-inline.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/inline/heuristic-inline.stdout
@@ -103,13 +103,13 @@ fn {closure#10}(%0: ()) -> Boolean {
let %2: Boolean
bb0(): {
- %1 = apply ({closure#7} as FnPtr) ()
+ %1 = input LOAD flag
switchInt(%1) -> [0: bb2(), 1: bb1()]
}
bb1(): {
- %2 = apply ({closure#7} as FnPtr) ()
+ %2 = input LOAD flag
return %2
}
@@ -164,46 +164,22 @@ thunk load_flag:0() -> () -> Boolean {
fn {closure#10}(%0: ()) -> Boolean {
let %1: Boolean
let %2: Boolean
- let %3: ()
- let %4: Boolean
- let %5: ()
- let %6: Boolean
bb0(): {
- %5 = ()
+ %1 = input LOAD flag
- goto -> bb6()
+ switchInt(%1) -> [0: bb2(), 1: bb1()]
}
bb1(): {
- %3 = ()
+ %2 = input LOAD flag
- goto -> bb4()
+ return %2
}
bb2(): {
return false
}
-
- bb3(%2): {
- return %2
- }
-
- bb4(): {
- %4 = input LOAD flag
-
- goto -> bb3(%4)
- }
-
- bb5(%1): {
- switchInt(%1) -> [0: bb2(), 1: bb1()]
- }
-
- bb6(): {
- %6 = input LOAD flag
-
- goto -> bb5(%6)
- }
}
thunk use_flag:0() -> () -> Boolean {
@@ -221,10 +197,6 @@ thunk use_flag:0() -> () -> Boolean {
let %1: ()
let %2: Boolean
let %3: Boolean
- let %4: ()
- let %5: Boolean
- let %6: ()
- let %7: Boolean
bb0(): {
%1 = ()
@@ -237,38 +209,18 @@ thunk use_flag:0() -> () -> Boolean {
}
bb2(): {
- %6 = ()
+ %2 = input LOAD flag
- goto -> bb8()
+ switchInt(%2) -> [0: bb4(), 1: bb3()]
}
bb3(): {
- %4 = ()
+ %3 = input LOAD flag
- goto -> bb6()
+ goto -> bb1(%3)
}
bb4(): {
goto -> bb1(false)
}
-
- bb5(%3): {
- goto -> bb1(%3)
- }
-
- bb6(): {
- %5 = input LOAD flag
-
- goto -> bb5(%5)
- }
-
- bb7(%2): {
- switchInt(%2) -> [0: bb4(), 1: bb3()]
- }
-
- bb8(): {
- %7 = input LOAD flag
-
- goto -> bb7(%7)
- }
}
\ No newline at end of file
diff --git a/libs/@local/hashql/mir/tests/ui/pass/inline/too-large-to-inline.stdout b/libs/@local/hashql/mir/tests/ui/pass/inline/too-large-to-inline.stdout
index 6de99535e24..bbfefa60a3c 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/inline/too-large-to-inline.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/inline/too-large-to-inline.stdout
@@ -84,7 +84,7 @@ fn {closure#7}(%0: ()) -> Boolean {
let %1: Boolean
bb0(): {
- %1 = apply ({closure#5} as FnPtr) ()
+ %1 = input LOAD v
return %1
}
@@ -104,7 +104,7 @@ thunk caller:0() -> () -> Boolean {
let %0: Boolean
bb0(): {
- %0 = apply ({closure#5} as FnPtr) ()
+ %0 = input LOAD v
return %0
}
@@ -136,7 +136,7 @@ fn {closure#7}(%0: ()) -> Boolean {
let %1: Boolean
bb0(): {
- %1 = apply ({closure#5} as FnPtr) ()
+ %1 = input LOAD v
return %1
}
@@ -156,7 +156,7 @@ thunk caller:0() -> () -> Boolean {
let %0: Boolean
bb0(): {
- %0 = apply ({closure#5} as FnPtr) ()
+ %0 = input LOAD v
return %0
}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/post_inline/cascading-simplification.stdout b/libs/@local/hashql/mir/tests/ui/pass/post_inline/cascading-simplification.stdout
index e2bf218f858..cf695922def 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/post_inline/cascading-simplification.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/post_inline/cascading-simplification.stdout
@@ -127,36 +127,20 @@ thunk check_both:0() -> (Boolean, Boolean) -> Boolean {
}
thunk {thunk#7}() -> Boolean {
- let %0: Boolean
-
bb0(): {
- %0 = apply ({closure#10} as FnPtr) () 100
-
- return %0
+ return true
}
}
thunk {thunk#8}() -> Boolean {
- let %0: Boolean
-
bb0(): {
- %0 = apply ({closure#10} as FnPtr) () 75
-
- return %0
+ return true
}
}
*thunk {thunk#9}() -> Boolean {
- let %0: Boolean
- let %1: Boolean
- let %2: Boolean
-
bb0(): {
- %1 = apply ({closure#10} as FnPtr) () 100
- %2 = apply ({closure#10} as FnPtr) () 75
- %0 = apply ({closure#11} as FnPtr) () %1 %2
-
- return %0
+ return true
}
}
@@ -203,110 +187,20 @@ thunk check_both:0() -> (Boolean, Boolean) -> Boolean {
}
thunk {thunk#7}() -> Boolean {
- let %0: Boolean
- let %1: ()
- let %2: Integer
- let %3: Boolean
-
bb0(): {
- %1 = ()
- %2 = 100
-
- goto -> bb2()
- }
-
- bb1(%0): {
- return %0
- }
-
- bb2(): {
- %3 = %2 > 50
-
- goto -> bb1(%3)
+ return true
}
}
thunk {thunk#8}() -> Boolean {
- let %0: Boolean
- let %1: ()
- let %2: Integer
- let %3: Boolean
-
bb0(): {
- %1 = ()
- %2 = 75
-
- goto -> bb2()
- }
-
- bb1(%0): {
- return %0
- }
-
- bb2(): {
- %3 = %2 > 50
-
- goto -> bb1(%3)
+ return true
}
}
*thunk {thunk#9}() -> Boolean {
- let %0: Boolean
- let %1: Boolean
- let %2: Boolean
- let %3: ()
- let %4: Boolean
- let %5: Boolean
- let %6: Boolean
- let %7: ()
- let %8: Integer
- let %9: Boolean
- let %10: ()
- let %11: Integer
- let %12: Boolean
-
bb0(): {
- %10 = ()
- %11 = 100
-
- goto -> bb6()
- }
-
- bb1(%0): {
- return %0
- }
-
- bb2(): {
- %6 = %4 & %5
-
- goto -> bb1(%6)
- }
-
- bb3(%2): {
- %3 = ()
- %4 = %1
- %5 = %2
-
- goto -> bb2()
- }
-
- bb4(): {
- %9 = %8 > 50
-
- goto -> bb3(%9)
- }
-
- bb5(%1): {
- %7 = ()
- %8 = 75
-
- goto -> bb4()
- }
-
- bb6(): {
- %12 = %11 > 50
-
- goto -> bb5(%12)
+ return true
}
}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/post_inline/closure-env-cleanup.stdout b/libs/@local/hashql/mir/tests/ui/pass/post_inline/closure-env-cleanup.stdout
index 73938de717e..f480e3edb85 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/post_inline/closure-env-cleanup.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/post_inline/closure-env-cleanup.stdout
@@ -115,12 +115,8 @@ thunk checker:0() -> (Integer) -> Boolean {
}
*thunk {thunk#6}() -> Boolean {
- let %0: Boolean
-
bb0(): {
- %0 = apply ({closure#5} as FnPtr) () 150
-
- return %0
+ return true
}
}
@@ -173,26 +169,8 @@ thunk checker:0() -> (Integer) -> Boolean {
}
*thunk {thunk#6}() -> Boolean {
- let %0: Boolean
- let %1: ()
- let %2: Integer
- let %3: Boolean
-
bb0(): {
- %1 = ()
- %2 = 150
-
- goto -> bb2()
- }
-
- bb1(%0): {
- return %0
- }
-
- bb2(): {
- %3 = %2 > 100
-
- goto -> bb1(%3)
+ return true
}
}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/post_inline/constant-propagation-after-inline.stdout b/libs/@local/hashql/mir/tests/ui/pass/post_inline/constant-propagation-after-inline.stdout
index b81e60f02af..2a329e3b624 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/post_inline/constant-propagation-after-inline.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/post_inline/constant-propagation-after-inline.stdout
@@ -57,12 +57,8 @@ thunk is_positive:0() -> (Integer) -> Boolean {
}
*thunk {thunk#3}() -> Boolean {
- let %0: Boolean
-
bb0(): {
- %0 = apply ({closure#4} as FnPtr) () 42
-
- return %0
+ return true
}
}
@@ -89,26 +85,8 @@ thunk is_positive:0() -> (Integer) -> Boolean {
}
*thunk {thunk#3}() -> Boolean {
- let %0: Boolean
- let %1: ()
- let %2: Integer
- let %3: Boolean
-
bb0(): {
- %1 = ()
- %2 = 42
-
- goto -> bb2()
- }
-
- bb1(%0): {
- return %0
- }
-
- bb2(): {
- %3 = %2 > 0
-
- goto -> bb1(%3)
+ return true
}
}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/post_inline/nested-branch-elimination.stdout b/libs/@local/hashql/mir/tests/ui/pass/post_inline/nested-branch-elimination.stdout
index 122a85c7110..563ff6c06b7 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/post_inline/nested-branch-elimination.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/post_inline/nested-branch-elimination.stdout
@@ -140,38 +140,14 @@ thunk check_low:0() -> (Integer) -> Boolean {
}
thunk {thunk#8}() -> Boolean {
- let %0: Boolean
-
bb0(): {
- %0 = apply ({closure#10} as FnPtr) () 50
-
- return %0
+ return false
}
}
*thunk {thunk#9}() -> String {
- let %0: Boolean
- let %1: String
- let %2: Boolean
-
bb0(): {
- %2 = apply ({closure#10} as FnPtr) () 50
-
- switchInt(%2) -> [0: bb1(), 1: bb2()]
- }
-
- bb1(): {
- %0 = apply ({closure#11} as FnPtr) () 50
-
- switchInt(%0) -> [0: bb3("in range"), 1: bb3("too low")]
- }
-
- bb2(): {
- return "too high"
- }
-
- bb3(%1): {
- return %1
+ return "in range"
}
}
@@ -218,80 +194,14 @@ thunk check_low:0() -> (Integer) -> Boolean {
}
thunk {thunk#8}() -> Boolean {
- let %0: Boolean
- let %1: ()
- let %2: Integer
- let %3: Boolean
-
bb0(): {
- %1 = ()
- %2 = 50
-
- goto -> bb2()
- }
-
- bb1(%0): {
- return %0
- }
-
- bb2(): {
- %3 = %2 > 100
-
- goto -> bb1(%3)
+ return false
}
}
*thunk {thunk#9}() -> String {
- let %0: Boolean
- let %1: String
- let %2: Boolean
- let %3: ()
- let %4: Integer
- let %5: Boolean
- let %6: ()
- let %7: Integer
- let %8: Boolean
-
bb0(): {
- %6 = ()
- %7 = 50
-
- goto -> bb7()
- }
-
- bb1(): {
- %3 = ()
- %4 = 50
-
- goto -> bb5()
- }
-
- bb2(): {
- return "too high"
- }
-
- bb3(%1): {
- return %1
- }
-
- bb4(%0): {
- switchInt(%0) -> [0: bb3("in range"), 1: bb3("too low")]
- }
-
- bb5(): {
- %5 = %4 < 0
-
- goto -> bb4(%5)
- }
-
- bb6(%2): {
- switchInt(%2) -> [0: bb1(), 1: bb2()]
- }
-
- bb7(): {
- %8 = %7 > 100
-
- goto -> bb6(%8)
+ return "in range"
}
}
diff --git a/libs/@local/hashql/mir/tests/ui/pass/post_inline/showcase.aux.svg b/libs/@local/hashql/mir/tests/ui/pass/post_inline/showcase.aux.svg
index 19b8a8792f8..90b273d53f9 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/post_inline/showcase.aux.svg
+++ b/libs/@local/hashql/mir/tests/ui/pass/post_inline/showcase.aux.svg
@@ -1,21 +1,21 @@
-
diff --git a/libs/@local/hashql/mir/tests/ui/pass/post_inline/showcase.stdout b/libs/@local/hashql/mir/tests/ui/pass/post_inline/showcase.stdout
index 5c700b02dc3..9e5c0da98f9 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/post_inline/showcase.stdout
+++ b/libs/@local/hashql/mir/tests/ui/pass/post_inline/showcase.stdout
@@ -154,28 +154,28 @@ thunk is_small:0() -> (Integer) -> Boolean {
}
fn {closure#17}(%0: (), %1: Integer) -> String {
- let %2: Boolean
+ let %2: String
let %3: Boolean
- let %4: String
+ let %4: Boolean
bb0(): {
- %2 = apply ({closure#13} as FnPtr) () %1
+ %3 = %1 > 50
- switchInt(%2) -> [0: bb1(), 1: bb2()]
+ switchInt(%3) -> [0: bb1(), 1: bb2()]
}
bb1(): {
- %3 = apply ({closure#14} as FnPtr) () %1
+ %4 = %1 < 10
- switchInt(%3) -> [0: bb3("medium"), 1: bb3("small")]
+ switchInt(%4) -> [0: bb3("medium"), 1: bb3("small")]
}
bb2(): {
return "large"
}
- bb3(%4): {
- return %4
+ bb3(%2): {
+ return %2
}
}
@@ -242,56 +242,28 @@ thunk is_small:0() -> (Integer) -> Boolean {
}
fn {closure#17}(%0: (), %1: Integer) -> String {
- let %2: Boolean
+ let %2: String
let %3: Boolean
- let %4: String
- let %5: ()
- let %6: Integer
- let %7: Boolean
- let %8: ()
- let %9: Integer
- let %10: Boolean
+ let %4: Boolean
bb0(): {
- %8 = ()
- %9 = %1
+ %3 = %1 > 50
- goto -> bb7()
+ switchInt(%3) -> [0: bb1(), 1: bb2()]
}
bb1(): {
- %5 = ()
- %6 = %1
+ %4 = %1 < 10
- goto -> bb5()
+ switchInt(%4) -> [0: bb3("medium"), 1: bb3("small")]
}
bb2(): {
return "large"
}
- bb3(%4): {
- return %4
- }
-
- bb4(%3): {
- switchInt(%3) -> [0: bb3("medium"), 1: bb3("small")]
- }
-
- bb5(): {
- %7 = %6 < 10
-
- goto -> bb4(%7)
- }
-
- bb6(%2): {
- switchInt(%2) -> [0: bb1(), 1: bb2()]
- }
-
- bb7(): {
- %10 = %9 > 50
-
- goto -> bb6(%10)
+ bb3(%2): {
+ return %2
}
}
@@ -309,15 +281,9 @@ thunk classify:0() -> (Integer) -> String {
let %0: String
let %1: ()
let %2: Integer
- let %3: Boolean
+ let %3: String
let %4: Boolean
- let %5: String
- let %6: ()
- let %7: Integer
- let %8: Boolean
- let %9: ()
- let %10: Integer
- let %11: Boolean
+ let %5: Boolean
bb0(): {
%1 = ()
@@ -331,45 +297,23 @@ thunk classify:0() -> (Integer) -> String {
}
bb2(): {
- %9 = ()
- %10 = %2
+ %4 = %2 > 50
- goto -> bb9()
+ switchInt(%4) -> [0: bb3(), 1: bb4()]
}
bb3(): {
- %6 = ()
- %7 = %2
+ %5 = %2 < 10
- goto -> bb7()
+ switchInt(%5) -> [0: bb5("medium"), 1: bb5("small")]
}
bb4(): {
goto -> bb1("large")
}
- bb5(%5): {
- goto -> bb1(%5)
- }
-
- bb6(%4): {
- switchInt(%4) -> [0: bb5("medium"), 1: bb5("small")]
- }
-
- bb7(): {
- %8 = %7 < 10
-
- goto -> bb6(%8)
- }
-
- bb8(%3): {
- switchInt(%3) -> [0: bb3(), 1: bb4()]
- }
-
- bb9(): {
- %11 = %10 > 50
-
- goto -> bb8(%11)
+ bb5(%3): {
+ goto -> bb1(%3)
}
}
@@ -421,15 +365,15 @@ fn {closure#17}(%0: (), %1: Integer) -> String {
let %4: Boolean
bb0(): {
- %4 = %1 > 50
+ %3 = %1 > 50
- switchInt(%4) -> [0: bb1(), 1: bb2()]
+ switchInt(%3) -> [0: bb1(), 1: bb2()]
}
bb1(): {
- %3 = %1 < 10
+ %4 = %1 < 10
- switchInt(%3) -> [0: bb3("medium"), 1: bb3("small")]
+ switchInt(%4) -> [0: bb3("medium"), 1: bb3("small")]
}
bb2(): {
From d051ddb4c601d64b9a82b7bca6da9c8ae82eb9fd Mon Sep 17 00:00:00 2001
From: Bilal Mahmoud <7252775+indietyp@users.noreply.github.com>
Date: Tue, 23 Jun 2026 11:33:08 +0200
Subject: [PATCH 8/8] fix: use intrinsic where required instead of closure
---
libs/@local/hashql/mir/src/body/mod.rs | 8 ++++----
libs/@local/hashql/mir/src/builder/body.rs | 2 +-
libs/@local/hashql/mir/src/intrinsic.rs | 10 +++++++++-
libs/@local/hashql/mir/src/lib.rs | 2 +-
.../hashql/mir/src/pass/transform/inline/tests.rs | 3 ++-
.../pass/copy_propagation/block_param_effectful.snap | 6 +++---
.../tests/ui/pass/dle/projection_locals_preserved.snap | 4 ++--
.../tests/ui/pass/dse/graph_read_token_preserved.snap | 4 ++--
8 files changed, 24 insertions(+), 15 deletions(-)
diff --git a/libs/@local/hashql/mir/src/body/mod.rs b/libs/@local/hashql/mir/src/body/mod.rs
index 74e6e2fdb62..cd663e508dd 100644
--- a/libs/@local/hashql/mir/src/body/mod.rs
+++ b/libs/@local/hashql/mir/src/body/mod.rs
@@ -71,11 +71,11 @@ pub enum Source<'heap> {
/// A compiler intrinsic function.
///
/// This variant represents MIR generated for built-in operations that have
- /// special compiler support or runtime behavior. The [`DefId`] identifies the intrinsic
- /// definition.
+ /// special compiler support or runtime behavior. Intrinsics may define a fallback
+ /// implementation inside of their body.
///
- /// The body of an intrinsic function is typically empty, as the intrinsic
- /// operation is handled directly by the compiler or runtime.
+ /// Intrinsics are never inlined, but are still optimized, except in the case of explicit
+ /// opt-out.
Intrinsic(Intrinsic),
/// A compiler-synthesized wrapper that makes a non-first-class operation callable as a
diff --git a/libs/@local/hashql/mir/src/builder/body.rs b/libs/@local/hashql/mir/src/builder/body.rs
index 29724042e8f..1c198887096 100644
--- a/libs/@local/hashql/mir/src/builder/body.rs
+++ b/libs/@local/hashql/mir/src/builder/body.rs
@@ -382,7 +382,7 @@ macro_rules! body {
$crate::body::Source::Ctor($name)
};
(@source intrinsic) => {
- $crate::body::Source::Intrinsic($crate::def::DefId::PLACEHOLDER)
+ $crate::body::Source::Intrinsic($crate::intrinsic::Intrinsic { id: $crate::intrinsic::IntrinsicId::EntityPropertyAccess, optimize: true })
};
(@source [synthetic $name:expr]) => {
$crate::body::Source::Synthetic($name)
diff --git a/libs/@local/hashql/mir/src/intrinsic.rs b/libs/@local/hashql/mir/src/intrinsic.rs
index 8b1c40bbf4a..472a174ba43 100644
--- a/libs/@local/hashql/mir/src/intrinsic.rs
+++ b/libs/@local/hashql/mir/src/intrinsic.rs
@@ -9,6 +9,14 @@ pub enum IntrinsicId {
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Intrinsic {
pub id: IntrinsicId,
- // Hint to any optimization passes that this intrinsic should not be optimized in any way.
+ // Hint to any optimization passes whether the intrinsic should participate in optimization, or
+ // should be skipped.
pub optimize: bool,
}
+
+impl Intrinsic {
+ #[must_use]
+ pub const fn new(id: IntrinsicId) -> Self {
+ Self { id, optimize: true }
+ }
+}
diff --git a/libs/@local/hashql/mir/src/lib.rs b/libs/@local/hashql/mir/src/lib.rs
index 2eee30d7f6d..4df02d28c94 100644
--- a/libs/@local/hashql/mir/src/lib.rs
+++ b/libs/@local/hashql/mir/src/lib.rs
@@ -45,7 +45,7 @@ pub mod def;
pub mod error;
pub mod intern;
pub mod interpret;
-mod intrinsic;
+pub mod intrinsic;
mod macros;
pub mod pass;
pub mod pretty;
diff --git a/libs/@local/hashql/mir/src/pass/transform/inline/tests.rs b/libs/@local/hashql/mir/src/pass/transform/inline/tests.rs
index 33e959c39dd..4df021b3e12 100644
--- a/libs/@local/hashql/mir/src/pass/transform/inline/tests.rs
+++ b/libs/@local/hashql/mir/src/pass/transform/inline/tests.rs
@@ -27,6 +27,7 @@ use crate::{
context::MirContext,
def::{DefId, DefIdSlice, DefIdVec},
intern::Interner,
+ intrinsic::{Intrinsic, IntrinsicId},
pass::{
Changed, GlobalTransformPass as _, OwnedGlobalTransformState,
analysis::{CallGraph, CallSite},
@@ -542,7 +543,7 @@ fn analysis_directives_by_source() {
let mut intrinsic_body = closure_body.clone();
intrinsic_body.id = DefId::new(2);
- intrinsic_body.source = Source::Closure(HirId::PLACEHOLDER, None);
+ intrinsic_body.source = Source::Intrinsic(Intrinsic::new(IntrinsicId::EntityPropertyAccess));
// Fix closure_body id to be 0
closure_body.id = DefId::new(0);
diff --git a/libs/@local/hashql/mir/tests/ui/pass/copy_propagation/block_param_effectful.snap b/libs/@local/hashql/mir/tests/ui/pass/copy_propagation/block_param_effectful.snap
index ebf5bcba0fa..50c1a8892f3 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/copy_propagation/block_param_effectful.snap
+++ b/libs/@local/hashql/mir/tests/ui/pass/copy_propagation/block_param_effectful.snap
@@ -1,8 +1,8 @@
---
-source: libs/@local/hashql/mir/src/pass/transform/cp/tests.rs
+source: libs/@local/hashql/mir/src/pass/transform/copy_propagation/tests.rs
expression: value
---
-fn {intrinsic#4294967040}(%0: ?) -> Boolean {
+fn {closure@4294967040}(%0: ?) -> Boolean {
let %1: Integer
let %2: Boolean
@@ -24,7 +24,7 @@ fn {intrinsic#4294967040}(%0: ?) -> Boolean {
================== Changed: No ===================
-fn {intrinsic#4294967040}(%0: ?) -> Boolean {
+fn {closure@4294967040}(%0: ?) -> Boolean {
let %1: Integer
let %2: Boolean
diff --git a/libs/@local/hashql/mir/tests/ui/pass/dle/projection_locals_preserved.snap b/libs/@local/hashql/mir/tests/ui/pass/dle/projection_locals_preserved.snap
index 504ff9fbe1a..f601668cdbf 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/dle/projection_locals_preserved.snap
+++ b/libs/@local/hashql/mir/tests/ui/pass/dle/projection_locals_preserved.snap
@@ -2,7 +2,7 @@
source: libs/@local/hashql/mir/src/pass/transform/dle/tests.rs
expression: value
---
-fn {intrinsic#4294967040}(%0: List) -> Integer {
+fn {closure@4294967040}(%0: List) -> Integer {
let %1: Integer
let %2: Integer
@@ -16,7 +16,7 @@ fn {intrinsic#4294967040}(%0: List) -> Integer {
================== Changed: No ===================
-fn {intrinsic#4294967040}(%0: List) -> Integer {
+fn {closure@4294967040}(%0: List) -> Integer {
let %1: Integer
let %2: Integer
diff --git a/libs/@local/hashql/mir/tests/ui/pass/dse/graph_read_token_preserved.snap b/libs/@local/hashql/mir/tests/ui/pass/dse/graph_read_token_preserved.snap
index 860badd3f0a..f28ce0e4108 100644
--- a/libs/@local/hashql/mir/tests/ui/pass/dse/graph_read_token_preserved.snap
+++ b/libs/@local/hashql/mir/tests/ui/pass/dse/graph_read_token_preserved.snap
@@ -2,7 +2,7 @@
source: libs/@local/hashql/mir/src/pass/transform/dse/tests.rs
expression: value
---
-fn {intrinsic#4294967040}(%0: ?) -> Integer {
+fn {closure@4294967040}(%0: ?) -> Integer {
let %1: GraphToken
let %2: Integer
@@ -20,7 +20,7 @@ fn {intrinsic#4294967040}(%0: ?) -> Integer {
================== Changed: Yes ==================
-fn {intrinsic#4294967040}(%0: ?) -> Integer {
+fn {closure@4294967040}(%0: ?) -> Integer {
let %1: GraphToken
bb0(): {