diff --git a/libs/@local/hashql/compiletest/src/pipeline.rs b/libs/@local/hashql/compiletest/src/pipeline.rs index 8b18e299072..e347acb4e28 100644 --- a/libs/@local/hashql/compiletest/src/pipeline.rs +++ b/libs/@local/hashql/compiletest/src/pipeline.rs @@ -31,12 +31,7 @@ use hashql_mir::{ body::Body, context::MirContext, def::{DefId, DefIdSlice, DefIdVec}, - pass::{ - Changed, GlobalAnalysisPass as _, GlobalTransformPass as _, GlobalTransformState, - analysis::SizeEstimationAnalysis, - execution::{ExecutionAnalysis, ExecutionAnalysisResidual}, - transform::{Inline, InlineConfig, PostInline, PreInline}, - }, + pass::{self, LowerConfig, execution::ExecutionAnalysisResidual}, reify::ReifyContext, }; use hashql_syntax_jexpr::span::Span; @@ -189,9 +184,11 @@ impl<'heap> Pipeline<'heap> { bodies: &mut bodies, mir: &mut mir_context, hir: &hir_context, + scratch: &self.scratch, }; let entry = tri!(hashql_mir::reify::from_hir(node, &mut reify_context)); + self.scratch.reset(); // drain the context, because we're going to re-create it self.diagnostics.extend( @@ -218,24 +215,14 @@ impl<'heap> Pipeline<'heap> { bodies: &mut DefIdSlice>, ) -> Result<(), BoxedDiagnostic<'static, SpanId>> { let mut context = MirContext::new(&self.env, interner); - let mut state = GlobalTransformState::new_in(&*bodies, self.heap); - - self.scratch.reset(); - - let mut pass = PreInline::new_in(&mut self.scratch); - let _: Changed = pass.run(&mut context, &mut state, bodies); - self.scratch.reset(); - - let mut pass = Inline::new_in(InlineConfig::default(), &mut self.scratch); - let _: Changed = pass.run(&mut context, &mut state, bodies); - self.scratch.reset(); - let mut pass = PostInline::new_in(&mut self.scratch); - let _: Changed = pass.run(&mut context, &mut state, bodies); - self.scratch.reset(); - - let status = context.diagnostics.generalize().boxed().into_status(()); - process_status(&mut self.diagnostics, status)?; + let result = pass::lower( + &mut context, + &mut self.scratch, + bodies, + &LowerConfig::default(), + ); + process_status(&mut self.diagnostics, result)?; Ok(()) } @@ -262,20 +249,8 @@ impl<'heap> Pipeline<'heap> { > { let mut context = MirContext::new(&self.env, interner); - let mut pass = SizeEstimationAnalysis::new_in(&self.scratch); - pass.run(&mut context, bodies); - let footprints = pass.finish(); - self.scratch.reset(); - - let pass = ExecutionAnalysis { - footprints: &footprints, - scratch: &mut self.scratch, - }; - let analysis = pass.run_all_in(&mut context, bodies, self.heap); - self.scratch.reset(); - - let status = context.diagnostics.generalize().boxed().into_status(()); - process_status(&mut self.diagnostics, status)?; + let status = pass::place(&mut context, &mut self.scratch, bodies); + let analysis = process_status(&mut self.diagnostics, status)?; Ok(analysis) } diff --git a/libs/@local/hashql/compiletest/src/suite/eval_postgres.rs b/libs/@local/hashql/compiletest/src/suite/eval_postgres.rs index 721796be1e9..7cd0bd37246 100644 --- a/libs/@local/hashql/compiletest/src/suite/eval_postgres.rs +++ b/libs/@local/hashql/compiletest/src/suite/eval_postgres.rs @@ -122,7 +122,7 @@ impl Suite for EvalPostgres { &interner, &bodies, &analysis, - context.heap, + heap, &mut scratch, ); scratch.reset(); diff --git a/libs/@local/hashql/compiletest/src/suite/mir_pass_analysis_data_dependency.rs b/libs/@local/hashql/compiletest/src/suite/mir_pass_analysis_data_dependency.rs index d249c5cbc15..8d10d49f139 100644 --- a/libs/@local/hashql/compiletest/src/suite/mir_pass_analysis_data_dependency.rs +++ b/libs/@local/hashql/compiletest/src/suite/mir_pass_analysis_data_dependency.rs @@ -38,7 +38,8 @@ impl Suite for MirPassAnalysisDataDependency { let mut buffer = Vec::new(); - let (root, mut bodies) = mir_reify(heap, expr, &interner, &mut environment, diagnostics)?; + let (root, mut bodies, _) = + mir_reify(heap, expr, &interner, &mut environment, diagnostics)?; writeln!(buffer, "{}\n", Header::new("MIR")).expect("should be able to write to buffer"); mir_format_text(heap, &environment, &mut buffer, root, &bodies); diff --git a/libs/@local/hashql/compiletest/src/suite/mir_pass_transform_cfg_simplify.rs b/libs/@local/hashql/compiletest/src/suite/mir_pass_transform_cfg_simplify.rs index fa54cc8f4f7..a4e19a5d7cf 100644 --- a/libs/@local/hashql/compiletest/src/suite/mir_pass_transform_cfg_simplify.rs +++ b/libs/@local/hashql/compiletest/src/suite/mir_pass_transform_cfg_simplify.rs @@ -49,7 +49,8 @@ pub(crate) fn mir_pass_transform_cfg_simplify<'heap>( environment: &mut Environment<'heap>, diagnostics: &mut Vec, ) -> Result<(DefId, DefIdVec>, Scratch), SuiteDiagnostic> { - let (root, mut bodies) = mir_reify(heap, expr, interner, environment, diagnostics)?; + let (root, mut bodies, mut scratch) = + mir_reify(heap, expr, interner, environment, diagnostics)?; render(heap, environment, root, &bodies); @@ -59,7 +60,6 @@ pub(crate) fn mir_pass_transform_cfg_simplify<'heap>( interner, diagnostics: DiagnosticIssues::new(), }; - let mut scratch = Scratch::new(); let mut pass = CfgSimplify::new_in(&mut scratch); for body in bodies.as_mut_slice() { diff --git a/libs/@local/hashql/compiletest/src/suite/mir_pass_transform_pre_inline.rs b/libs/@local/hashql/compiletest/src/suite/mir_pass_transform_pre_inline.rs index 25c2c906b5e..1386621c949 100644 --- a/libs/@local/hashql/compiletest/src/suite/mir_pass_transform_pre_inline.rs +++ b/libs/@local/hashql/compiletest/src/suite/mir_pass_transform_pre_inline.rs @@ -168,7 +168,7 @@ pub(crate) fn mir_pass_transform_pre_inline<'heap>( environment: &mut Environment<'heap>, diagnostics: &mut Vec, ) -> Result<(DefId, DefIdVec>, Scratch), SuiteDiagnostic> { - let (root, mut bodies) = mir_reify(heap, expr, interner, environment, diagnostics)?; + let (root, mut bodies, _) = mir_reify(heap, expr, interner, environment, diagnostics)?; render.render( &mut RenderContext { diff --git a/libs/@local/hashql/compiletest/src/suite/mir_reify.rs b/libs/@local/hashql/compiletest/src/suite/mir_reify.rs index ce16f3b4aa6..6e813cae28a 100644 --- a/libs/@local/hashql/compiletest/src/suite/mir_reify.rs +++ b/libs/@local/hashql/compiletest/src/suite/mir_reify.rs @@ -8,7 +8,7 @@ use std::{ use error_stack::ReportSink; use hashql_ast::node::expr::Expr; use hashql_core::{ - heap::Heap, + heap::{Heap, ResetAllocator as _, Scratch}, id::IdVec, module::ModuleRegistry, pretty::Formatter, @@ -32,7 +32,8 @@ pub(crate) fn mir_reify<'heap>( interner: &Interner<'heap>, environment: &mut Environment<'heap>, diagnostics: &mut Vec, -) -> Result<(DefId, DefIdVec>), SuiteDiagnostic> { +) -> Result<(DefId, DefIdVec>, Scratch), SuiteDiagnostic> { + let mut scratch = Scratch::new(); let registry = ModuleRegistry::new(environment); let hir_interner = hashql_hir::intern::Interner::new(heap); let mut hir_context = HirContext::new(&hir_interner, ®istry); @@ -66,11 +67,13 @@ pub(crate) fn mir_reify<'heap>( bodies: &mut bodies, mir: &mut mir_context, hir: &hir_context, + scratch: &scratch, }, ), )?; + scratch.reset(); - Ok((root, bodies)) + Ok((root, bodies, scratch)) } pub(crate) fn mir_format_text<'heap>( @@ -208,7 +211,7 @@ impl Suite for MirReifySuite { let mut environment = Environment::new(heap); let interner = Interner::new(heap); - let (root, bodies) = mir_reify(heap, expr, &interner, &mut environment, diagnostics)?; + let (root, bodies, _) = mir_reify(heap, expr, &interner, &mut environment, diagnostics)?; let mut buffer = Vec::new(); mir_format_text(heap, &environment, &mut buffer, root, &bodies); diff --git a/libs/@local/hashql/core/src/graph/linked.rs b/libs/@local/hashql/core/src/graph/linked.rs index 14341a654ec..6494bd62ad1 100644 --- a/libs/@local/hashql/core/src/graph/linked.rs +++ b/libs/@local/hashql/core/src/graph/linked.rs @@ -50,6 +50,7 @@ use alloc::alloc::Global; use core::{ alloc::Allocator, + fmt, ops::{Index, IndexMut}, }; @@ -213,7 +214,7 @@ impl Edge { /// } /// # else { unreachable!() } /// ``` -#[derive(Debug, Clone)] +#[derive(Clone)] pub struct LinkedGraph { /// All nodes in the graph, indexed by [`NodeId`]. nodes: IdVec, A>, @@ -541,6 +542,17 @@ impl Default for LinkedGraph { } } +impl core::fmt::Debug + for LinkedGraph +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("LinkedGraph") + .field("nodes", &self.nodes) + .field("edges", &self.edges) + .finish() + } +} + impl IndexMut for LinkedGraph { fn index_mut(&mut self, index: NodeId) -> &mut Self::Output { &mut self.nodes[index] diff --git a/libs/@local/hashql/mir/src/body/operand.rs b/libs/@local/hashql/mir/src/body/operand.rs index 4190f286b7d..4dc7f6dc794 100644 --- a/libs/@local/hashql/mir/src/body/operand.rs +++ b/libs/@local/hashql/mir/src/body/operand.rs @@ -60,12 +60,14 @@ impl<'heap> Operand<'heap> { } impl From for Operand<'_> { + #[inline] fn from(value: !) -> Self { value } } impl From for Operand<'_> { + #[inline] fn from(local: Local) -> Self { Operand::Place(Place::local(local)) } diff --git a/libs/@local/hashql/mir/src/pass/analysis/data_dependency/mod.rs b/libs/@local/hashql/mir/src/pass/analysis/data_dependency/mod.rs index 99c9d311537..eb98209a6d4 100644 --- a/libs/@local/hashql/mir/src/pass/analysis/data_dependency/mod.rs +++ b/libs/@local/hashql/mir/src/pass/analysis/data_dependency/mod.rs @@ -104,6 +104,7 @@ impl DataDependencyAnalysis<'_> { } impl Default for DataDependencyAnalysis<'_> { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/pass/execution/cost/mod.rs b/libs/@local/hashql/mir/src/pass/execution/cost/mod.rs index 470f53f4259..100bf4f2bc2 100644 --- a/libs/@local/hashql/mir/src/pass/execution/cost/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/cost/mod.rs @@ -252,6 +252,7 @@ impl From for ApproxCost { } impl From for ApproxCost { + #[inline] fn from(value: InformationUnit) -> Self { #[expect(clippy::cast_precision_loss)] Self(value.as_u32() as f32) diff --git a/libs/@local/hashql/mir/src/pass/execution/island/graph/mod.rs b/libs/@local/hashql/mir/src/pass/execution/island/graph/mod.rs index c3c4f6a54dc..97170c90c46 100644 --- a/libs/@local/hashql/mir/src/pass/execution/island/graph/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/island/graph/mod.rs @@ -23,6 +23,7 @@ pub(crate) mod tests; use alloc::alloc::Global; use core::{ alloc::Allocator, + fmt, ops::{Index, IndexMut}, }; @@ -287,6 +288,16 @@ impl IslandGraph { } } +impl fmt::Debug for IslandGraph { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("IslandGraph") + .field("vertex", &self.vertex) + .field("inner", &self.inner) + .field("lookup", &self.lookup) + .finish() + } +} + impl DirectedGraph for IslandGraph { type Edge<'this> = &'this Edge diff --git a/libs/@local/hashql/mir/src/pass/execution/island/mod.rs b/libs/@local/hashql/mir/src/pass/execution/island/mod.rs index fb26e47b8c2..9d9f0446afc 100644 --- a/libs/@local/hashql/mir/src/pass/execution/island/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/island/mod.rs @@ -84,6 +84,7 @@ impl Island { self.members.is_empty() } + #[inline] #[must_use] pub const fn traversals(&self) -> TraversalPathBitSet { self.traversals @@ -114,6 +115,7 @@ impl IslandPlacement { } impl Default for IslandPlacement { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/pass/execution/mod.rs b/libs/@local/hashql/mir/src/pass/execution/mod.rs index ce0bddb23ef..81408178561 100644 --- a/libs/@local/hashql/mir/src/pass/execution/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/mod.rs @@ -19,7 +19,7 @@ mod terminator_placement; pub mod traversal; mod vertex; -use core::{alloc::Allocator, assert_matches}; +use core::{alloc::Allocator, assert_matches, fmt}; use hashql_core::heap::{BumpAllocator, Heap}; @@ -57,6 +57,15 @@ pub struct ExecutionAnalysisResidual { pub islands: IslandGraph, } +impl core::fmt::Debug for ExecutionAnalysisResidual { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("ExecutionAnalysisResidual") + .field("assignment", &self.assignment) + .field("islands", &self.islands) + .finish() + } +} + pub struct ExecutionAnalysis<'ctx, 'heap, S: Allocator> { pub footprints: &'ctx DefIdSlice>, pub scratch: S, diff --git a/libs/@local/hashql/mir/src/pass/execution/splitting/mod.rs b/libs/@local/hashql/mir/src/pass/execution/splitting/mod.rs index c3fa52702a9..0cc69483b44 100644 --- a/libs/@local/hashql/mir/src/pass/execution/splitting/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/splitting/mod.rs @@ -401,6 +401,7 @@ impl BasicBlockSplitting { } impl Default for BasicBlockSplitting { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/pass/execution/terminator_placement/mod.rs b/libs/@local/hashql/mir/src/pass/execution/terminator_placement/mod.rs index 36b83bd728c..bb2c3e6ce98 100644 --- a/libs/@local/hashql/mir/src/pass/execution/terminator_placement/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/terminator_placement/mod.rs @@ -201,6 +201,7 @@ impl TransMatrix { } impl Default for TransMatrix { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/pass/execution/traversal/mod.rs b/libs/@local/hashql/mir/src/pass/execution/traversal/mod.rs index 37b5f21404f..76cd0be7d1d 100644 --- a/libs/@local/hashql/mir/src/pass/execution/traversal/mod.rs +++ b/libs/@local/hashql/mir/src/pass/execution/traversal/mod.rs @@ -309,6 +309,7 @@ impl TraversalPathBitMap { } impl From for TraversalPathBitMap { + #[inline] fn from(value: TraversalPathBitSet) -> Self { let mut this = TraversalMapLattice.bottom(); this[value.vertex()] = value; diff --git a/libs/@local/hashql/mir/src/pass/mod.rs b/libs/@local/hashql/mir/src/pass/mod.rs index a71f27bd006..2fd6b3292dc 100644 --- a/libs/@local/hashql/mir/src/pass/mod.rs +++ b/libs/@local/hashql/mir/src/pass/mod.rs @@ -19,15 +19,26 @@ use core::{ alloc::Allocator, + mem, ops::{BitOr, BitOrAssign}, }; -use hashql_core::heap::BumpAllocator; +use hashql_core::{ + heap::{BumpAllocator, Heap, ResetAllocator as _, Scratch}, + span::SpanId, +}; +use hashql_diagnostics::Status; +use self::{ + analysis::SizeEstimationAnalysis, + execution::{ExecutionAnalysis, ExecutionAnalysisResidual}, + transform::{Inline, InlineConfig, PostInline, PreInline}, +}; use crate::{ body::Body, context::MirContext, def::{DefId, DefIdSlice, DefIdVec}, + error::MirDiagnosticCategory, }; pub mod analysis; @@ -159,6 +170,7 @@ impl BitOrAssign for Changed { } impl From for Changed { + #[inline] fn from(value: bool) -> Self { if value { Self::Yes } else { Self::No } } @@ -467,6 +479,104 @@ pub trait GlobalAnalysisPass<'env, 'heap> { } } +/// Configuration for the MIR lowering pipeline. +#[derive(Debug, Clone, Default)] +pub struct LowerConfig { + pub inline: InlineConfig, +} + +/// Runs the MIR lowering pipeline over all bodies. +/// +/// Produces optimized, fully inlined MIR ready for execution placement. The +/// pipeline runs three phases: +/// +/// 1. **Pre-inline canonicalization**: simplifies each body in isolation (constant folding, dead +/// code elimination, CFG cleanup) so that the inliner sees clean callees and makes better +/// decisions. +/// 2. **Inlining**: inter-procedural pass that substitutes callees into call sites based on cost +/// heuristics, with aggressive inlining for filter bodies. +/// 3. **Post-inline canonicalization**: cleans up redundancy introduced by inlining (duplicate +/// code, dead stores, unreachable blocks). +/// +/// # Errors +/// +/// Returns `Err` if any pass emits a critical diagnostic (`Error` or `Bug` +/// severity). This indicates a compiler invariant violation, since transform +/// passes operate on well-typed, well-formed MIR. +pub fn lower<'heap>( + context: &mut MirContext<'_, 'heap>, + scratch: &mut Scratch, + bodies: &mut DefIdSlice>, + config: &LowerConfig, +) -> Status<(), MirDiagnosticCategory, SpanId> { + scratch.reset(); + + let mut state = GlobalTransformState::new_in(&*bodies, context.heap); + + let mut pass = PreInline::new_in(&mut *scratch); + let _: Changed = pass.run(context, &mut state, bodies); + scratch.reset(); + + let mut pass = Inline::new_in(config.inline, &mut *scratch); + let _: Changed = pass.run(context, &mut state, bodies); + scratch.reset(); + + let mut pass = PostInline::new_in(&mut *scratch); + let _: Changed = pass.run(context, &mut state, bodies); + scratch.reset(); + + let issues = mem::take(&mut context.diagnostics); + issues.into_status(()) +} + +/// Determines which execution backend each basic block runs on. +/// +/// Only [`GraphReadFilter`] bodies are analyzed; all other bodies receive +/// `None` in the result. The pipeline runs two phases: +/// +/// 1. **Size estimation**: computes per-body footprints used to estimate data transfer costs at +/// island boundaries. +/// 2. **Execution analysis**: for each filter body, computes per-target statement and terminator +/// costs, solves the placement problem, fuses adjacent same-backend blocks, and builds the +/// island graph. +/// +/// # Errors +/// +/// Returns `Err` if the placement solver emits a critical diagnostic. The +/// interpreter is a universal execution target, so a valid assignment +/// should always exist; a solver failure indicates a bug in domain pruning +/// or target construction. +/// +/// [`GraphReadFilter`]: crate::body::Source::GraphReadFilter +pub fn place<'heap>( + context: &mut MirContext<'_, 'heap>, + scratch: &mut Scratch, + bodies: &mut DefIdSlice>, +) -> Status< + DefIdVec>, &'heap Heap>, + MirDiagnosticCategory, + SpanId, +> { + scratch.reset(); + + let heap = context.heap; + + let mut pass = SizeEstimationAnalysis::new_in(&*scratch); + pass.run(context, bodies); + let footprints = pass.finish(); + scratch.reset(); + + let pass = ExecutionAnalysis { + footprints: &footprints, + scratch: &mut *scratch, + }; + let residual = pass.run_all_in(context, bodies, heap); + scratch.reset(); + + let issues = mem::take(&mut context.diagnostics); + issues.into_status(residual) +} + #[cfg(test)] mod tests { use super::Changed; diff --git a/libs/@local/hashql/mir/src/pass/transform/canonicalization.rs b/libs/@local/hashql/mir/src/pass/transform/canonicalization.rs index e218ee2544a..221bb4ab101 100644 --- a/libs/@local/hashql/mir/src/pass/transform/canonicalization.rs +++ b/libs/@local/hashql/mir/src/pass/transform/canonicalization.rs @@ -26,6 +26,7 @@ pub struct CanonicalizationConfig { } impl Default for CanonicalizationConfig { + #[inline] fn default() -> Self { Self { max_iterations: 16 } } diff --git a/libs/@local/hashql/mir/src/pass/transform/cfg_simplify/mod.rs b/libs/@local/hashql/mir/src/pass/transform/cfg_simplify/mod.rs index a8d33328cbb..aff1a68addb 100644 --- a/libs/@local/hashql/mir/src/pass/transform/cfg_simplify/mod.rs +++ b/libs/@local/hashql/mir/src/pass/transform/cfg_simplify/mod.rs @@ -483,6 +483,7 @@ impl CfgSimplify { } impl Default for CfgSimplify { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/pass/transform/copy_propagation/mod.rs b/libs/@local/hashql/mir/src/pass/transform/copy_propagation/mod.rs index 55e866ff43f..72c793a2adf 100644 --- a/libs/@local/hashql/mir/src/pass/transform/copy_propagation/mod.rs +++ b/libs/@local/hashql/mir/src/pass/transform/copy_propagation/mod.rs @@ -213,6 +213,7 @@ impl CopyPropagation { } impl Default for CopyPropagation { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/pass/transform/dse/mod.rs b/libs/@local/hashql/mir/src/pass/transform/dse/mod.rs index 8fdbeca6ee7..787d56e6dae 100644 --- a/libs/@local/hashql/mir/src/pass/transform/dse/mod.rs +++ b/libs/@local/hashql/mir/src/pass/transform/dse/mod.rs @@ -132,6 +132,7 @@ impl DeadStoreElimination { } impl Default for DeadStoreElimination { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/pass/transform/forward_substitution.rs b/libs/@local/hashql/mir/src/pass/transform/forward_substitution.rs index 145b0c6c70e..ef4a2c3c68b 100644 --- a/libs/@local/hashql/mir/src/pass/transform/forward_substitution.rs +++ b/libs/@local/hashql/mir/src/pass/transform/forward_substitution.rs @@ -157,6 +157,7 @@ impl<'env, 'heap, A: Allocator> TransformPass<'env, 'heap> for ForwardSubstituti } impl Default for ForwardSubstitution { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs b/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs index 34ee56a1333..18e1f148ed6 100644 --- a/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs +++ b/libs/@local/hashql/mir/src/pass/transform/inst_simplify/mod.rs @@ -158,6 +158,7 @@ impl InstSimplify { } impl Default for InstSimplify { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/pass/transform/ssa_repair/mod.rs b/libs/@local/hashql/mir/src/pass/transform/ssa_repair/mod.rs index 46eabd3cbee..49ed23dd800 100644 --- a/libs/@local/hashql/mir/src/pass/transform/ssa_repair/mod.rs +++ b/libs/@local/hashql/mir/src/pass/transform/ssa_repair/mod.rs @@ -156,6 +156,7 @@ impl SsaRepair { } impl Default for SsaRepair { + #[inline] fn default() -> Self { Self::new() } diff --git a/libs/@local/hashql/mir/src/reify/atom.rs b/libs/@local/hashql/mir/src/reify/atom.rs index 394b9b793b0..ca26410b975 100644 --- a/libs/@local/hashql/mir/src/reify/atom.rs +++ b/libs/@local/hashql/mir/src/reify/atom.rs @@ -1,3 +1,5 @@ +use core::alloc::Allocator; + use hashql_core::{id::Id as _, r#type::kind::TypeKind}; use hashql_hir::node::{ Node, @@ -25,7 +27,7 @@ use crate::{ }, }; -impl<'heap> Reifier<'_, '_, '_, '_, 'heap> { +impl<'heap, A: Allocator, S: Allocator> Reifier<'_, '_, '_, '_, 'heap, A, S> { fn local(&mut self, node: Node<'heap>) -> Local { let NodeKind::Variable(Variable::Local(local)) = node.kind else { self.state diff --git a/libs/@local/hashql/mir/src/reify/current.rs b/libs/@local/hashql/mir/src/reify/current.rs index f033cc15721..4ea1fdd1870 100644 --- a/libs/@local/hashql/mir/src/reify/current.rs +++ b/libs/@local/hashql/mir/src/reify/current.rs @@ -48,6 +48,7 @@ impl ForwardRef { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct EntryBlock(pub BasicBlockId); impl From for BasicBlockId { + #[inline] fn from(entry: EntryBlock) -> Self { entry.0 } @@ -56,6 +57,7 @@ impl From for BasicBlockId { #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub(crate) struct ExitBlock(pub BasicBlockId); impl From for BasicBlockId { + #[inline] fn from(exit: ExitBlock) -> Self { exit.0 } diff --git a/libs/@local/hashql/mir/src/reify/mod.rs b/libs/@local/hashql/mir/src/reify/mod.rs index 53de57a402f..19180f265fb 100644 --- a/libs/@local/hashql/mir/src/reify/mod.rs +++ b/libs/@local/hashql/mir/src/reify/mod.rs @@ -6,7 +6,8 @@ mod terminator; mod transform; mod types; -use core::debug_assert_matches; +use alloc::alloc::Global; +use core::{alloc::Allocator, debug_assert_matches}; use hashql_core::{ collections::{ @@ -65,13 +66,15 @@ use crate::{ /// /// This structure contains the essential components needed to transform HIR(ANF) into MIR, /// including symbol tables, type information, and memory management. -pub struct ReifyContext<'mir, 'hir, 'env, 'heap> { +pub struct ReifyContext<'mir, 'hir, 'env, 'heap, A: Allocator = Global, S: Allocator = Global> { /// Mutable reference to the collection of MIR bodies being generated. - pub bodies: &'mir mut DefIdVec>, + pub bodies: &'mir mut DefIdVec, A>, /// MIR context. pub mir: &'mir mut MirContext<'env, 'heap>, /// HIR context containing the source nodes and variable mappings. pub hir: &'hir HirContext<'hir, 'heap>, + /// Scratch allocator for temporary memory usage during reification. + pub scratch: S, } /// Tracks the mapping between variable IDs and their corresponding thunk definition IDs. @@ -83,12 +86,12 @@ pub struct ReifyContext<'mir, 'hir, 'env, 'heap> { /// /// Thunks are sparse and limited to the first few IDs since nested thunks are not allowed. /// Using a vector here is memory-efficient given this constraint. -pub struct Thunks { - defs: VarIdVec>, +pub struct Thunks { + defs: VarIdVec, S>, set: MixedBitSet, } -impl Thunks { +impl Thunks { fn insert(&mut self, var: VarId, def: DefId) { self.defs.insert(var, def); self.set.insert(var); @@ -99,9 +102,9 @@ impl Thunks { /// /// This structure maintains global state needed throughout reification, including /// thunk mappings, constructor definitions, and memory pools for efficient allocation. -struct CrossCompileState<'heap> { +struct CrossCompileState<'heap, S: Allocator> { /// Mapping of variable IDs to their thunk definitions. - thunks: Thunks, + thunks: Thunks, /// Collection of diagnostics encountered during reification. diagnostics: ReifyDiagnosticIssues, @@ -117,12 +120,12 @@ struct CrossCompileState<'heap> { /// Each `Reifier` instance is responsible for converting a single function/thunk/closure /// from HIR to MIR. It maintains its own local state for basic blocks, variable mappings, /// and local allocation while sharing global state through the context and cross-compile state. -struct Reifier<'ctx, 'mir, 'hir, 'env, 'heap> { +struct Reifier<'ctx, 'mir, 'hir, 'env, 'heap, A: Allocator, S: Allocator> { /// Reference to the global reification context. - context: &'ctx mut ReifyContext<'mir, 'hir, 'env, 'heap>, + context: &'ctx mut ReifyContext<'mir, 'hir, 'env, 'heap, A, S>, /// Reference to the shared cross-compilation state. - state: &'ctx mut CrossCompileState<'heap>, + state: &'ctx mut CrossCompileState<'heap, S>, /// Basic blocks being constructed for the current function body. blocks: BasicBlockVec, &'heap Heap>, @@ -133,10 +136,12 @@ struct Reifier<'ctx, 'mir, 'hir, 'env, 'heap> { local_decls: LocalVec, &'heap Heap>, } -impl<'ctx, 'mir, 'hir, 'env, 'heap> Reifier<'ctx, 'mir, 'hir, 'env, 'heap> { +impl<'ctx, 'mir, 'hir, 'env, 'heap, A: Allocator, S: Allocator> + Reifier<'ctx, 'mir, 'hir, 'env, 'heap, A, S> +{ const fn new( - context: &'ctx mut ReifyContext<'mir, 'hir, 'env, 'heap>, - state: &'ctx mut CrossCompileState<'heap>, + context: &'ctx mut ReifyContext<'mir, 'hir, 'env, 'heap, A, S>, + state: &'ctx mut CrossCompileState<'heap, S>, ) -> Self { let blocks = BasicBlockVec::new_in(context.mir.heap); let local_decls = LocalVec::new_in(context.mir.heap); @@ -473,9 +478,9 @@ impl<'ctx, 'mir, 'hir, 'env, 'heap> Reifier<'ctx, 'mir, 'hir, 'env, 'heap> { /// /// See [BE-67](https://linear.app/hash/issue/BE-67/hashql-implement-modules) for /// planned multi-module architecture. -pub fn from_hir<'heap>( +pub fn from_hir<'heap, A: Allocator, S: Allocator + Clone>( node: Node<'heap>, - context: &mut ReifyContext<'_, '_, '_, 'heap>, + context: &mut ReifyContext<'_, '_, '_, 'heap, A, S>, ) -> Status { // The node is already in HIR(ANF) - each node will be a thunk. let NodeKind::Let(Let { bindings, body }) = node.kind else { @@ -496,7 +501,7 @@ pub fn from_hir<'heap>( }; let thunks = Thunks { - defs: VarIdVec::new(), + defs: VarIdVec::new_in(context.scratch.clone()), set: MixedBitSet::new_empty(context.hir.counter.var.size()), }; let mut state = CrossCompileState { diff --git a/libs/@local/hashql/mir/src/reify/rvalue.rs b/libs/@local/hashql/mir/src/reify/rvalue.rs index d604a0ba16c..4cb798feab5 100644 --- a/libs/@local/hashql/mir/src/reify/rvalue.rs +++ b/libs/@local/hashql/mir/src/reify/rvalue.rs @@ -1,3 +1,5 @@ +use core::alloc::Allocator; + use hashql_core::{ id::{Id as _, IdVec}, symbol::sym, @@ -32,7 +34,7 @@ use crate::{ interpret::value::{Int, TryFromPrimitiveError}, }; -impl<'mir, 'heap> Reifier<'_, 'mir, '_, '_, 'heap> { +impl<'mir, 'heap, A: Allocator, S: Allocator> Reifier<'_, 'mir, '_, '_, 'heap, A, S> { fn rvalue_data(&mut self, data: Data<'heap>) -> RValue<'heap> { match data { Data::Primitive(primitive) => { @@ -112,15 +114,16 @@ impl<'mir, 'heap> Reifier<'_, 'mir, '_, '_, 'heap> { RValue::Load(Operand::Constant(Constant::Unit)) } TypeOperation::Constructor(ctor @ TypeConstructor { name }) => { - if let Some(&ptr) = self.state.ctor.get(&name) { - return RValue::Load(Operand::Constant(Constant::FnPtr(ptr))); - } - - let compiler = Reifier::new(self.context, self.state); - let ptr = compiler.lower_ctor(hir, ctor); - self.state.ctor.insert(name, ptr); + let def = if let Some(&ptr) = self.state.ctor.get(&name) { + ptr + } else { + let compiler = Reifier::new(self.context, self.state); + let ptr = compiler.lower_ctor(hir, ctor); + self.state.ctor.insert(name, ptr); + ptr + }; - let ptr = Operand::Constant(Constant::FnPtr(ptr)); + let ptr = Operand::Constant(Constant::FnPtr(def)); let env = Operand::Constant(Constant::Unit); let mut operands = IdVec::with_capacity_in(2, self.context.mir.heap); operands.push(ptr); diff --git a/libs/@local/hashql/mir/src/reify/terminator.rs b/libs/@local/hashql/mir/src/reify/terminator.rs index cfba67fb9d3..437bdbe68ed 100644 --- a/libs/@local/hashql/mir/src/reify/terminator.rs +++ b/libs/@local/hashql/mir/src/reify/terminator.rs @@ -1,3 +1,5 @@ +use core::alloc::Allocator; + use hashql_core::{heap, id::Id as _, span::SpanId}; use hashql_hir::node::{ branch, @@ -23,7 +25,7 @@ use crate::{ def::DefId, }; -impl<'mir, 'heap> Reifier<'_, 'mir, '_, '_, 'heap> { +impl<'mir, 'heap, A: Allocator, S: Allocator> Reifier<'_, 'mir, '_, '_, 'heap, A, S> { fn terminator_graph_read_head( &mut self, head: graph::GraphReadHead<'heap>, diff --git a/libs/@local/hashql/mir/src/reify/transform.rs b/libs/@local/hashql/mir/src/reify/transform.rs index a3249dca65a..5bbc4bfca06 100644 --- a/libs/@local/hashql/mir/src/reify/transform.rs +++ b/libs/@local/hashql/mir/src/reify/transform.rs @@ -1,3 +1,5 @@ +use core::alloc::Allocator; + use hashql_core::{ collections::TinyVec, id::{IdVec, bit_vec::BitRelations as _}, @@ -28,7 +30,7 @@ use crate::{ def::DefId, }; -impl<'mir, 'heap> Reifier<'_, 'mir, '_, '_, 'heap> { +impl<'mir, 'heap, A: Allocator, S: Allocator> Reifier<'_, 'mir, '_, '_, 'heap, A, S> { pub(super) fn transform_closure( &mut self, block: &mut CurrentBlock<'mir, 'heap>, diff --git a/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.aux.mir b/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.aux.mir index 0898aabc27b..3b899d54d7f 100644 --- a/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.aux.mir +++ b/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.aux.mir @@ -36,7 +36,7 @@ thunk {thunk#4}() -> () -> ::main::A:0 { let %0: () -> ::main::A:0 bb0(): { - %0 = ({ctor#::main::A:0} as FnPtr) + %0 = closure(({ctor#::main::A:0} as FnPtr), ()) return %0 } @@ -135,34 +135,34 @@ thunk {thunk#3}() -> ::main::A:0 { } thunk {thunk#4}() -> () -> ::main::A:0 { + let %0: () -> ::main::A:0 + bb0(): { - return ({ctor#::main::A:0} as FnPtr) + %0 = closure(({ctor#::main::A:0} as FnPtr), ()) + + return %0 } } thunk {thunk#5}() -> ::main::A:0 { - let %0: () -> ::main::A:0 - let %1: ::main::A:0 + let %0: ::main::A:0 bb0(): { - %0 = ({ctor#::main::A:0} as FnPtr) - %1 = apply %0.0 %0.1 + %0 = opaque(::main::A:0, ()) - return %1 + return %0 } } thunk {thunk#6}() -> (x: ::main::A:0, y: ::main::A:0) { let %0: (x: ::main::A:0, y: ::main::A:0) let %1: ::main::A:0 - let %2: () -> ::main::A:0 - let %3: ::main::A:0 + let %2: ::main::A:0 bb0(): { %1 = opaque(::main::A:0, ()) - %2 = ({ctor#::main::A:0} as FnPtr) - %3 = apply %2.0 %2.1 - %0 = (x: %1, y: %3) + %2 = opaque(::main::A:0, ()) + %0 = (x: %1, y: %2) return %0 } @@ -190,13 +190,17 @@ thunk {thunk#7}() -> ((x: ::main::A:0, y: ::main::A:0)) -> ::main::Outer:0 { *thunk {thunk#8}() -> ::main::Outer:0 { let %0: (x: ::main::A:0, y: ::main::A:0) - let %1: ::main::Outer:0 + let %1: ::main::A:0 + let %2: ::main::A:0 + let %3: ::main::Outer:0 bb0(): { - %0 = apply ({thunk#6} as FnPtr) - %1 = opaque(::main::Outer:0, %0) + %1 = opaque(::main::A:0, ()) + %2 = opaque(::main::A:0, ()) + %0 = (x: %1, y: %2) + %3 = opaque(::main::Outer:0, %0) - return %1 + return %3 } } @@ -233,34 +237,34 @@ thunk {thunk#3}() -> ::main::A:0 { } thunk {thunk#4}() -> () -> ::main::A:0 { + let %0: () -> ::main::A:0 + bb0(): { - return ({ctor#::main::A:0} as FnPtr) + %0 = closure(({ctor#::main::A:0} as FnPtr), ()) + + return %0 } } thunk {thunk#5}() -> ::main::A:0 { - let %0: () -> ::main::A:0 - let %1: ::main::A:0 + let %0: ::main::A:0 bb0(): { - %0 = ({ctor#::main::A:0} as FnPtr) - %1 = apply %0.0 %0.1 + %0 = opaque(::main::A:0, ()) - return %1 + return %0 } } thunk {thunk#6}() -> (x: ::main::A:0, y: ::main::A:0) { let %0: (x: ::main::A:0, y: ::main::A:0) let %1: ::main::A:0 - let %2: () -> ::main::A:0 - let %3: ::main::A:0 + let %2: ::main::A:0 bb0(): { %1 = opaque(::main::A:0, ()) - %2 = ({ctor#::main::A:0} as FnPtr) - %3 = apply %2.0 %2.1 - %0 = (x: %1, y: %3) + %2 = opaque(::main::A:0, ()) + %0 = (x: %1, y: %2) return %0 } @@ -288,29 +292,17 @@ thunk {thunk#7}() -> ((x: ::main::A:0, y: ::main::A:0)) -> ::main::Outer:0 { *thunk {thunk#8}() -> ::main::Outer:0 { let %0: (x: ::main::A:0, y: ::main::A:0) - let %1: ::main::Outer:0 - let %2: (x: ::main::A:0, y: ::main::A:0) - let %3: ::main::A:0 - let %4: () -> ::main::A:0 - let %5: ::main::A:0 + let %1: ::main::A:0 + let %2: ::main::A:0 + let %3: ::main::Outer:0 bb0(): { - goto -> bb2() - } - - bb1(%0): { - %1 = opaque(::main::Outer:0, %0) - - return %1 - } - - bb2(): { - %3 = opaque(::main::A:0, ()) - %4 = ({ctor#::main::A:0} as FnPtr) - %5 = apply %4.0 %4.1 - %2 = (x: %3, y: %5) + %1 = opaque(::main::A:0, ()) + %2 = opaque(::main::A:0, ()) + %0 = (x: %1, y: %2) + %3 = opaque(::main::Outer:0, %0) - goto -> bb1(%2) + return %3 } } @@ -347,34 +339,34 @@ thunk {thunk#3}() -> ::main::A:0 { } thunk {thunk#4}() -> () -> ::main::A:0 { + let %0: () -> ::main::A:0 + bb0(): { - return ({ctor#::main::A:0} as FnPtr) + %0 = closure(({ctor#::main::A:0} as FnPtr), ()) + + return %0 } } thunk {thunk#5}() -> ::main::A:0 { - let %0: () -> ::main::A:0 - let %1: ::main::A:0 + let %0: ::main::A:0 bb0(): { - %0 = ({ctor#::main::A:0} as FnPtr) - %1 = apply %0.0 %0.1 + %0 = opaque(::main::A:0, ()) - return %1 + return %0 } } thunk {thunk#6}() -> (x: ::main::A:0, y: ::main::A:0) { let %0: (x: ::main::A:0, y: ::main::A:0) let %1: ::main::A:0 - let %2: () -> ::main::A:0 - let %3: ::main::A:0 + let %2: ::main::A:0 bb0(): { %1 = opaque(::main::A:0, ()) - %2 = ({ctor#::main::A:0} as FnPtr) - %3 = apply %2.0 %2.1 - %0 = (x: %1, y: %3) + %2 = opaque(::main::A:0, ()) + %0 = (x: %1, y: %2) return %0 } @@ -401,19 +393,17 @@ thunk {thunk#7}() -> ((x: ::main::A:0, y: ::main::A:0)) -> ::main::Outer:0 { } *thunk {thunk#8}() -> ::main::Outer:0 { - let %0: ::main::Outer:0 - let %1: (x: ::main::A:0, y: ::main::A:0) + let %0: (x: ::main::A:0, y: ::main::A:0) + let %1: ::main::A:0 let %2: ::main::A:0 - let %3: () -> ::main::A:0 - let %4: ::main::A:0 + let %3: ::main::Outer:0 bb0(): { + %1 = opaque(::main::A:0, ()) %2 = opaque(::main::A:0, ()) - %3 = ({ctor#::main::A:0} as FnPtr) - %4 = apply %3.0 %3.1 - %1 = (x: %2, y: %4) - %0 = opaque(::main::Outer:0, %1) + %0 = (x: %1, y: %2) + %3 = opaque(::main::Outer:0, %0) - return %0 + return %3 } } \ No newline at end of file diff --git a/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.jsonc b/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.jsonc index 9f1993f89bf..730d16d09c2 100644 --- a/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.jsonc +++ b/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.jsonc @@ -1,4 +1,4 @@ -//@ run: fail +//@ run: pass //@ description: The interpreter should be able to simply delegate to the underlying struct. [ "newtype", @@ -9,6 +9,5 @@ "Outer", { "#struct": { "x": "A", "y": "A" } }, ["Outer", { "#struct": { "x": ["A"], "y": ["A"] } }] - //~^ INTERNAL COMPILER ERROR cannot project field ] ] diff --git a/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.stderr b/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.stderr deleted file mode 100644 index 5c5735d78d5..00000000000 --- a/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.stderr +++ /dev/null @@ -1,13 +0,0 @@ -error[interpret::type-invariant]: Type Invariant - ╭▸ -11 │ ["Outer", { "#struct": { "x": ["A"], "y": ["A"] } }] - │ ━━━━━ cannot project field from `*0` - │ - ├ help: type checking should have ensured only projectable types are projected - ├ help: This is a bug in the compiler, not an issue with your code. - ├ help: Please report this issue along with a minimal code example that reproduces the error. - ╰ note: Internal compiler errors indicate a bug in the compiler itself that needs to be fixed. - - We would appreciate if you could file a GitHub or Linear issue and reference this error. - - When reporting this issue, please include your query, any relevant type definitions, and the complete error message shown above. \ No newline at end of file diff --git a/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.stdout b/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.stdout new file mode 100644 index 00000000000..fa19ab2c819 --- /dev/null +++ b/libs/@local/hashql/mir/tests/ui/interpret/access-struct-through-opaque.stdout @@ -0,0 +1,37 @@ +Opaque( + Opaque { + name: Symbol( + "::main::Outer:0", + ), + value: Struct( + Struct { + fields: [ + Symbol( + "x", + ), + Symbol( + "y", + ), + ], + values: [ + Opaque( + Opaque { + name: Symbol( + "::main::A:0", + ), + value: Unit, + }, + ), + Opaque( + Opaque { + name: Symbol( + "::main::A:0", + ), + value: Unit, + }, + ), + ], + }, + ), + }, +) \ No newline at end of file diff --git a/libs/@local/hashql/mir/tests/ui/reify/ctor-cached-closure.jsonc b/libs/@local/hashql/mir/tests/ui/reify/ctor-cached-closure.jsonc new file mode 100644 index 00000000000..68a06ccee6d --- /dev/null +++ b/libs/@local/hashql/mir/tests/ui/reify/ctor-cached-closure.jsonc @@ -0,0 +1,15 @@ +//@ run: pass +//@ description: Cached constructor references must produce closure aggregates, not bare FnPtrs. +//@ description: Regression test: the second use of a constructor was previously emitted as a bare +//@ description: FnPtr (cache hit path), while the calling convention expects a fat pointer (closure). +[ + "newtype", + "A", + "Null", + [ + "newtype", + "Outer", + { "#struct": { "x": "A", "y": "A" } }, + ["Outer", { "#struct": { "x": ["A"], "y": ["A"] } }] + ] +] diff --git a/libs/@local/hashql/mir/tests/ui/reify/ctor-cached-closure.stdout b/libs/@local/hashql/mir/tests/ui/reify/ctor-cached-closure.stdout new file mode 100644 index 00000000000..17f3aed3dc1 --- /dev/null +++ b/libs/@local/hashql/mir/tests/ui/reify/ctor-cached-closure.stdout @@ -0,0 +1,101 @@ +fn {ctor#::main::A:0}(%0: ()) -> ::main::A:0 { + let %1: ::main::A:0 + + bb0(): { + %1 = opaque(::main::A:0, ()) + + return %1 + } +} + +thunk {thunk#2}() -> () -> ::main::A:0 { + let %0: () -> ::main::A:0 + + bb0(): { + %0 = closure(({ctor#::main::A:0} as FnPtr), ()) + + return %0 + } +} + +thunk {thunk#3}() -> ::main::A:0 { + let %0: () -> ::main::A:0 + let %1: ::main::A:0 + + bb0(): { + %0 = apply ({thunk#2} as FnPtr) + %1 = apply %0.0 %0.1 + + return %1 + } +} + +thunk {thunk#4}() -> () -> ::main::A:0 { + let %0: () -> ::main::A:0 + + bb0(): { + %0 = closure(({ctor#::main::A:0} as FnPtr), ()) + + return %0 + } +} + +thunk {thunk#5}() -> ::main::A:0 { + let %0: () -> ::main::A:0 + let %1: ::main::A:0 + + bb0(): { + %0 = apply ({thunk#4} as FnPtr) + %1 = apply %0.0 %0.1 + + return %1 + } +} + +thunk {thunk#6}() -> (x: ::main::A:0, y: ::main::A:0) { + let %0: ::main::A:0 + let %1: ::main::A:0 + let %2: (x: ::main::A:0, y: ::main::A:0) + + bb0(): { + %0 = apply ({thunk#3} as FnPtr) + %1 = apply ({thunk#5} as FnPtr) + %2 = (x: %0, y: %1) + + return %2 + } +} + +fn {ctor#::main::Outer:0}(%0: (), %1: (x: ::main::A:0, y: ::main::A:0)) -> ::main::Outer:0 { + let %2: ::main::Outer:0 + + bb0(): { + %2 = opaque(::main::Outer:0, %1) + + return %2 + } +} + +thunk {thunk#7}() -> ((x: ::main::A:0, y: ::main::A:0)) -> ::main::Outer:0 { + let %0: ((x: ::main::A:0, y: ::main::A:0)) -> ::main::Outer:0 + + bb0(): { + %0 = closure(({ctor#::main::Outer:0} as FnPtr), ()) + + return %0 + } +} + +*thunk {thunk#8}() -> ::main::Outer:0 { + let %0: (x: ::main::A:0, y: ::main::A:0) + let %1: ((x: ::main::A:0, y: ::main::A:0)) -> ::main::Outer:0 + let %2: ::main::Outer:0 + + bb0(): { + %0 = apply ({thunk#6} as FnPtr) + %1 = apply ({thunk#7} as FnPtr) + %2 = apply %1.0 %1.1 %0 + + return %2 + } +} \ No newline at end of file