@@ -17,6 +17,7 @@ use hashql_core::{
1717use hashql_hir:: node:: operation:: InputOp ;
1818
1919use super :: {
20+ Cardinal , InformationRange ,
2021 footprint:: { BodyFootprint , BodyFootprintSemilattice , Footprint } ,
2122 r#static:: StaticSizeEstimationCache ,
2223} ;
@@ -28,15 +29,19 @@ use crate::{
2829 local:: { Local , LocalDecl , LocalSlice } ,
2930 location:: Location ,
3031 operand:: Operand ,
31- place:: { Place , Projection , ProjectionKind } ,
32- rvalue:: { Aggregate , Apply , ArgSlice , BinOp , Binary , Input , RValue , UnOp , Unary } ,
32+ place:: { FieldIndex , Place , Projection , ProjectionKind } ,
33+ rvalue:: {
34+ Aggregate , AggregateKind , Apply , ArgSlice , BinOp , Binary , Input , RValue , UnOp , Unary ,
35+ } ,
3336 statement:: { Assign , Statement , StatementKind } ,
3437 } ,
3538 def:: { DefId , DefIdSlice } ,
3639 pass:: analysis:: {
3740 dataflow:: {
3841 framework:: { DataflowAnalysis , Direction } ,
39- lattice:: { AdditiveMonoid as _, SaturatingSemiring } ,
42+ lattice:: {
43+ AdditiveMonoid as _, HasBottom as _, JoinSemiLattice as _, SaturatingSemiring ,
44+ } ,
4045 } ,
4146 size_estimation:: {
4247 AffineEquation , estimate:: Estimate , range:: Cardinality , r#static:: StaticSizeEstimation ,
@@ -82,6 +87,13 @@ impl Eval {
8287 & Self :: Copy ( local) => & domain. locals [ local] ,
8388 }
8489 }
90+
91+ pub ( crate ) fn into_footprint < A : Allocator > ( self , domain : & BodyFootprint < A > ) -> Footprint {
92+ match self {
93+ Self :: Footprint ( footprint) => footprint,
94+ Self :: Copy ( local) => domain. locals [ local] . clone ( ) ,
95+ }
96+ }
8597}
8698
8799/// Helper for looking up operand footprints during dataflow analysis.
@@ -204,6 +216,120 @@ impl<'ctx, 'footprints, 'env, 'heap, A: Allocator, C: Allocator>
204216 self . lookup
205217 }
206218
219+ #[ expect(
220+ clippy:: integer_division,
221+ clippy:: cast_possible_truncation,
222+ clippy:: integer_division_remainder_used
223+ ) ]
224+ fn eval_rvalue_aggregate < B : Allocator > (
225+ & self ,
226+ domain : & BodyFootprint < B > ,
227+ aggregate : & Aggregate < ' heap > ,
228+ ) -> Eval {
229+ match aggregate {
230+ Aggregate {
231+ kind : AggregateKind :: Struct { fields : _ } | AggregateKind :: Tuple ,
232+ operands,
233+ } => {
234+ let mut units: Estimate < InformationRange > = SaturatingSemiring . zero ( ) ;
235+
236+ for operand in operands {
237+ let eval = self . lookup . operand ( domain, operand) ;
238+ let materialized = eval. into_footprint ( domain) . materialize ( ) ;
239+
240+ SaturatingSemiring . plus ( & mut units, & materialized) ;
241+ }
242+
243+ Eval :: Footprint ( Footprint :: one ( units) )
244+ }
245+ Aggregate {
246+ kind : AggregateKind :: List ,
247+ operands,
248+ } => {
249+ let mut average: Estimate < InformationRange > = SaturatingSemiring . bottom ( ) ;
250+
251+ for operand in operands {
252+ let eval = self . lookup . operand ( domain, operand) ;
253+ let units = eval. into_footprint ( domain) . materialize ( ) ;
254+
255+ SaturatingSemiring . join ( & mut average, & units) ;
256+ }
257+
258+ Eval :: Footprint ( Footprint {
259+ units : average,
260+ cardinality : Estimate :: Constant ( Cardinality :: value ( Cardinal :: new (
261+ operands. len ( ) as u32 ,
262+ ) ) ) ,
263+ } )
264+ }
265+ Aggregate {
266+ kind : AggregateKind :: Dict ,
267+ operands,
268+ } => {
269+ let mut average: Estimate < InformationRange > = SaturatingSemiring . bottom ( ) ;
270+ debug_assert ! ( operands. len( ) % 2 == 0 ) ;
271+
272+ for [ key, value] in operands. iter ( ) . array_chunks :: < 2 > ( ) {
273+ let mut key_units = self
274+ . lookup
275+ . operand ( domain, key)
276+ . into_footprint ( domain)
277+ . materialize ( ) ;
278+ let value_units = self
279+ . lookup
280+ . operand ( domain, value)
281+ . into_footprint ( domain)
282+ . materialize ( ) ;
283+
284+ key_units. saturating_mul_add ( & value_units, 1 ) ;
285+
286+ SaturatingSemiring . join ( & mut average, & key_units) ;
287+ }
288+
289+ Eval :: Footprint ( Footprint {
290+ units : average,
291+ cardinality : Estimate :: Constant ( Cardinality :: value ( Cardinal :: new (
292+ ( operands. len ( ) / 2 ) as u32 ,
293+ ) ) ) ,
294+ } )
295+ }
296+ Aggregate {
297+ kind : AggregateKind :: Closure ,
298+ operands,
299+ } => {
300+ debug_assert_eq ! ( operands. len( ) , 2 ) ;
301+
302+ let mut total = self
303+ . lookup
304+ . operand ( domain, & operands[ FieldIndex :: FN_PTR ] )
305+ . into_footprint ( domain)
306+ . materialize ( ) ;
307+ let env = self
308+ . lookup
309+ . operand ( domain, & operands[ FieldIndex :: ENV ] )
310+ . into_footprint ( domain)
311+ . materialize ( ) ;
312+ total. saturating_mul_add ( & env, 1 ) ;
313+
314+ Eval :: Footprint ( Footprint :: one ( total) )
315+ }
316+ Aggregate {
317+ kind : AggregateKind :: Opaque ( _) ,
318+ operands,
319+ } => {
320+ let mut total: Footprint = SaturatingSemiring . zero ( ) ;
321+
322+ for operand in operands {
323+ let eval = self . lookup . operand ( domain, operand) ;
324+
325+ SaturatingSemiring . plus ( & mut total, eval. as_ref ( domain) ) ;
326+ }
327+
328+ Eval :: Footprint ( total)
329+ }
330+ }
331+ }
332+
207333 /// Evaluates an rvalue to determine its footprint.
208334 fn eval_rvalue < B : Allocator > ( & self , domain : & BodyFootprint < B > , rvalue : & RValue < ' heap > ) -> Eval {
209335 #[ expect( clippy:: match_same_arms, reason = "explicit case handling for clarity" ) ]
@@ -228,17 +354,7 @@ impl<'ctx, 'footprints, 'env, 'heap, A: Allocator, C: Allocator>
228354 op : UnOp :: BitNot | UnOp :: Neg ,
229355 operand : _,
230356 } ) => Eval :: Footprint ( Footprint :: scalar ( ) ) ,
231- RValue :: Aggregate ( Aggregate { kind : _, operands } ) => {
232- let mut total: Footprint = SaturatingSemiring . zero ( ) ;
233-
234- for operand in operands {
235- let eval = self . lookup . operand ( domain, operand) ;
236-
237- SaturatingSemiring . plus ( & mut total, eval. as_ref ( domain) ) ;
238- }
239-
240- Eval :: Footprint ( total)
241- }
357+ RValue :: Aggregate ( aggregate) => self . eval_rvalue_aggregate ( domain, aggregate) ,
242358 RValue :: Input ( Input {
243359 op : InputOp :: Exists ,
244360 name : _,
0 commit comments