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 @@ -Initial MIRPre-inlining MIRInlined MIRPost Inline MIR

fn {closure#13}(%0: (), %1: Integer) -> Boolean

-

thunk is_large:0() -> (Integer) -> Boolean

-

fn {closure#14}(%0: (), %1: Integer) -> Boolean

-

thunk is_small:0() -> (Integer) -> Boolean

-

fn {closure#17}(%0: (), %1: Integer) -> String

-

thunk classify:0() -> (Integer) -> String

-

thunk {thunk#12}() -> String

-

fn {closure#13}(%0: (), %1: Integer) -> Boolean

-

thunk is_large:0() -> (Integer) -> Boolean

-

fn {closure#14}(%0: (), %1: Integer) -> Boolean

-

thunk is_small:0() -> (Integer) -> Boolean

-

fn {closure#17}(%0: (), %1: Integer) -> String

-

thunk classify:0() -> (Integer) -> String

-

thunk {thunk#12}() -> String

-

fn {closure#13}(%0: (), %1: Integer) -> Boolean

-

thunk is_large:0() -> (Integer) -> Boolean

-

fn {closure#14}(%0: (), %1: Integer) -> Boolean

-

thunk is_small:0() -> (Integer) -> Boolean

-

fn {closure#17}(%0: (), %1: Integer) -> String

-

thunk classify:0() -> (Integer) -> String

-

thunk {thunk#12}() -> String

-

fn {closure#13}(%0: (), %1: Integer) -> Boolean

-

thunk is_large:0() -> (Integer) -> Boolean

-

fn {closure#14}(%0: (), %1: Integer) -> Boolean

-

thunk is_small:0() -> (Integer) -> Boolean

-

fn {closure#17}(%0: (), %1: Integer) -> String

-

thunk classify:0() -> (Integer) -> String

-

thunk {thunk#12}() -> String

-
bb0()
MIR
0%2 = %1 > 50
Treturn %2
bb0()
MIR
0%1 = ()
1%0 = closure(({closure#13} as FnPtr), %1)
Treturn %0
bb0()
MIR
0%2 = %1 < 10
Treturn %2
bb0()
MIR
0%1 = ()
1%0 = closure(({closure#14} as FnPtr), %1)
Treturn %0
bb0()
MIR
0%2 = apply (is_large:0 as FnPtr)
1%3 = apply %2.0 %2.1 %1
TswitchInt(%3)
bb1()
MIR
0%5 = apply (is_small:0 as FnPtr)
1%6 = apply %5.0 %5.1 %1
TswitchInt(%6)
bb4()
MIR
Tgoto
bb3()
MIR
Tgoto
bb2()
MIR
Tgoto
bb5(%7)
MIR
Tgoto
bb6(%4)
MIR
Treturn %4
bb0()
MIR
0%1 = ()
1%0 = closure(({closure#17} as FnPtr), %1)
Treturn %0
bb0()
MIR
0%0 = apply (classify:0 as FnPtr)
1%1 = apply %0.0 %0.1 75
Treturn %1
bb0()
MIR
0%2 = %1 > 50
Treturn %2
bb0()
MIR
0%0 = closure(({closure#13} as FnPtr), ())
Treturn %0
bb0()
MIR
0%2 = %1 < 10
Treturn %2
bb0()
MIR
0%0 = closure(({closure#14} as FnPtr), ())
Treturn %0
bb0()
MIR
0%2 = apply ({closure#13} as FnPtr) () %1
TswitchInt(%2)
bb1()
MIR
0%3 = apply ({closure#14} as FnPtr) () %1
TswitchInt(%3)
bb2()
MIR
Treturn "large"
bb3(%4)
MIR
Treturn %4
bb0()
MIR
0%0 = closure(({closure#17} as FnPtr), ())
Treturn %0
bb0()
MIR
0%0 = apply ({closure#17} as FnPtr) () 75
Treturn %0
bb0()
MIR
0%2 = %1 > 50
Treturn %2
bb0()
MIR
0%0 = closure(({closure#13} as FnPtr), ())
Treturn %0
bb0()
MIR
0%2 = %1 < 10
Treturn %2
bb0()
MIR
0%0 = closure(({closure#14} as FnPtr), ())
Treturn %0
bb0()
MIR
0%8 = ()
1%9 = %1
Tgoto
bb7()
MIR
0%10 = %9 > 50
Tgoto
bb1()
MIR
0%5 = ()
1%6 = %1
Tgoto
bb5()
MIR
0%7 = %6 < 10
Tgoto
bb2()
MIR
Treturn "large"
bb3(%4)
MIR
Treturn %4
bb4(%3)
MIR
TswitchInt(%3)
bb6(%2)
MIR
TswitchInt(%2)
bb0()
MIR
0%0 = closure(({closure#17} as FnPtr), ())
Treturn %0
bb0()
MIR
0%1 = ()
1%2 = 75
Tgoto
bb2()
MIR
0%9 = ()
1%10 = %2
Tgoto
bb1(%0)
MIR
Treturn %0
bb9()
MIR
0%11 = %10 > 50
Tgoto
bb3()
MIR
0%6 = ()
1%7 = %2
Tgoto
bb7()
MIR
0%8 = %7 < 10
Tgoto
bb4()
MIR
Tgoto
bb5(%5)
MIR
Tgoto
bb6(%4)
MIR
TswitchInt(%4)
bb8(%3)
MIR
TswitchInt(%3)
bb0()
MIR
0%2 = %1 > 50
Treturn %2
bb0()
MIR
0%0 = closure(({closure#13} as FnPtr), ())
Treturn %0
bb0()
MIR
0%2 = %1 < 10
Treturn %2
bb0()
MIR
0%0 = closure(({closure#14} as FnPtr), ())
Treturn %0
bb0()
MIR
0%4 = %1 > 50
TswitchInt(%4)
bb1()
MIR
0%3 = %1 < 10
TswitchInt(%3)
bb2()
MIR
Treturn "large"
bb3(%2)
MIR
Treturn %2
bb0()
MIR
0%0 = closure(({closure#17} as FnPtr), ())
Treturn %0
bb0()
MIR
Treturn "large"
()0()1()0()1("small")("medium")("large")(%7)()0()1("medium")0("small")1()()("medium")0("small")1(%7)()0()1(%10)()()()("large")(%5)("medium")0("small")1(%8)()0()1(%11)()0()1("medium")0("small")1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +Initial MIRPre-inlining MIRInlined MIRPost Inline MIR

fn {closure#13}(%0: (), %1: Integer) -> Boolean

+

thunk is_large:0() -> (Integer) -> Boolean

+

fn {closure#14}(%0: (), %1: Integer) -> Boolean

+

thunk is_small:0() -> (Integer) -> Boolean

+

fn {closure#17}(%0: (), %1: Integer) -> String

+

thunk classify:0() -> (Integer) -> String

+

thunk {thunk#12}() -> String

+

fn {closure#13}(%0: (), %1: Integer) -> Boolean

+

thunk is_large:0() -> (Integer) -> Boolean

+

fn {closure#14}(%0: (), %1: Integer) -> Boolean

+

thunk is_small:0() -> (Integer) -> Boolean

+

fn {closure#17}(%0: (), %1: Integer) -> String

+

thunk classify:0() -> (Integer) -> String

+

thunk {thunk#12}() -> String

+

fn {closure#13}(%0: (), %1: Integer) -> Boolean

+

thunk is_large:0() -> (Integer) -> Boolean

+

fn {closure#14}(%0: (), %1: Integer) -> Boolean

+

thunk is_small:0() -> (Integer) -> Boolean

+

fn {closure#17}(%0: (), %1: Integer) -> String

+

thunk classify:0() -> (Integer) -> String

+

thunk {thunk#12}() -> String

+

fn {closure#13}(%0: (), %1: Integer) -> Boolean

+

thunk is_large:0() -> (Integer) -> Boolean

+

fn {closure#14}(%0: (), %1: Integer) -> Boolean

+

thunk is_small:0() -> (Integer) -> Boolean

+

fn {closure#17}(%0: (), %1: Integer) -> String

+

thunk classify:0() -> (Integer) -> String

+

thunk {thunk#12}() -> String

+
bb0()
MIR
0%2 = %1 > 50
Treturn %2
bb0()
MIR
0%1 = ()
1%0 = closure(({closure#13} as FnPtr), %1)
Treturn %0
bb0()
MIR
0%2 = %1 < 10
Treturn %2
bb0()
MIR
0%1 = ()
1%0 = closure(({closure#14} as FnPtr), %1)
Treturn %0
bb0()
MIR
0%2 = apply (is_large:0 as FnPtr)
1%3 = apply %2.0 %2.1 %1
TswitchInt(%3)
bb1()
MIR
0%5 = apply (is_small:0 as FnPtr)
1%6 = apply %5.0 %5.1 %1
TswitchInt(%6)
bb4()
MIR
Tgoto
bb3()
MIR
Tgoto
bb2()
MIR
Tgoto
bb5(%7)
MIR
Tgoto
bb6(%4)
MIR
Treturn %4
bb0()
MIR
0%1 = ()
1%0 = closure(({closure#17} as FnPtr), %1)
Treturn %0
bb0()
MIR
0%0 = apply (classify:0 as FnPtr)
1%1 = apply %0.0 %0.1 75
Treturn %1
bb0()
MIR
0%2 = %1 > 50
Treturn %2
bb0()
MIR
0%0 = closure(({closure#13} as FnPtr), ())
Treturn %0
bb0()
MIR
0%2 = %1 < 10
Treturn %2
bb0()
MIR
0%0 = closure(({closure#14} as FnPtr), ())
Treturn %0
bb0()
MIR
0%3 = %1 > 50
TswitchInt(%3)
bb1()
MIR
0%4 = %1 < 10
TswitchInt(%4)
bb2()
MIR
Treturn "large"
bb3(%2)
MIR
Treturn %2
bb0()
MIR
0%0 = closure(({closure#17} as FnPtr), ())
Treturn %0
bb0()
MIR
0%0 = apply ({closure#17} as FnPtr) () 75
Treturn %0
bb0()
MIR
0%2 = %1 > 50
Treturn %2
bb0()
MIR
0%0 = closure(({closure#13} as FnPtr), ())
Treturn %0
bb0()
MIR
0%2 = %1 < 10
Treturn %2
bb0()
MIR
0%0 = closure(({closure#14} as FnPtr), ())
Treturn %0
bb0()
MIR
0%3 = %1 > 50
TswitchInt(%3)
bb1()
MIR
0%4 = %1 < 10
TswitchInt(%4)
bb2()
MIR
Treturn "large"
bb3(%2)
MIR
Treturn %2
bb0()
MIR
0%0 = closure(({closure#17} as FnPtr), ())
Treturn %0
bb0()
MIR
0%1 = ()
1%2 = 75
Tgoto
bb2()
MIR
0%4 = %2 > 50
TswitchInt(%4)
bb1(%0)
MIR
Treturn %0
bb3()
MIR
0%5 = %2 < 10
TswitchInt(%5)
bb4()
MIR
Tgoto
bb5(%3)
MIR
Tgoto
bb0()
MIR
0%2 = %1 > 50
Treturn %2
bb0()
MIR
0%0 = closure(({closure#13} as FnPtr), ())
Treturn %0
bb0()
MIR
0%2 = %1 < 10
Treturn %2
bb0()
MIR
0%0 = closure(({closure#14} as FnPtr), ())
Treturn %0
bb0()
MIR
0%3 = %1 > 50
TswitchInt(%3)
bb1()
MIR
0%4 = %1 < 10
TswitchInt(%4)
bb2()
MIR
Treturn "large"
bb3(%2)
MIR
Treturn %2
bb0()
MIR
0%0 = closure(({closure#17} as FnPtr), ())
Treturn %0
bb0()
MIR
Treturn "large"
()0()1()0()1("small")("medium")("large")(%7)()0()1("medium")0("small")1()0()1("medium")0("small")1()()0()1("medium")0("small")1("large")(%3)()0()1("medium")0("small")1 + + + + + + + + + + + + + + + + + + + + + + + + + + + +
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(): {