@@ -16,7 +16,7 @@ use hashql_core::{
1616use hashql_diagnostics:: DiagnosticIssues ;
1717use hashql_mir:: {
1818 body:: Body ,
19- builder:: BodyBuilder ,
19+ builder:: { BodyBuilder , body } ,
2020 context:: MirContext ,
2121 def:: DefId ,
2222 intern:: Interner ,
@@ -38,7 +38,10 @@ use hashql_mir::{
3838/// bb1: y = x == 2; goto bb2
3939/// bb2: z = y == 3; return z
4040/// ```
41- fn create_linear_cfg < ' heap > ( env : & Environment < ' heap > , interner : & Interner < ' heap > ) -> Body < ' heap > {
41+ fn create_linear_cfg < ' heap > (
42+ env : & Environment < ' heap > ,
43+ interner : & Interner < ' heap > ,
44+ ) -> [ Body < ' heap > ; 1 ] {
4245 let mut builder = BodyBuilder :: new ( interner) ;
4346 let int_ty = TypeBuilder :: synthetic ( env) . integer ( ) ;
4447
@@ -71,7 +74,8 @@ fn create_linear_cfg<'heap>(env: &Environment<'heap>, interner: &Interner<'heap>
7174
7275 let mut body = builder. finish ( 0 , TypeBuilder :: synthetic ( env) . integer ( ) ) ;
7376 body. id = DefId :: new ( 0 ) ;
74- body
77+
78+ [ body]
7579}
7680
7781/// Creates a branching CFG body with a diamond pattern for benchmarking.
@@ -83,7 +87,10 @@ fn create_linear_cfg<'heap>(env: &Environment<'heap>, interner: &Interner<'heap>
8387/// bb2: b = 20; goto bb3
8488/// bb3(p): result = p; return result
8589/// ```
86- fn create_diamond_cfg < ' heap > ( env : & Environment < ' heap > , interner : & Interner < ' heap > ) -> Body < ' heap > {
90+ fn create_diamond_cfg < ' heap > (
91+ env : & Environment < ' heap > ,
92+ interner : & Interner < ' heap > ,
93+ ) -> [ Body < ' heap > ; 1 ] {
8794 let mut builder = BodyBuilder :: new ( interner) ;
8895 let int_ty = TypeBuilder :: synthetic ( env) . integer ( ) ;
8996
@@ -122,7 +129,7 @@ fn create_diamond_cfg<'heap>(env: &Environment<'heap>, interner: &Interner<'heap
122129
123130 let mut body = builder. finish ( 1 , TypeBuilder :: synthetic ( env) . integer ( ) ) ;
124131 body. id = DefId :: new ( 0 ) ;
125- body
132+ [ body]
126133}
127134
128135/// Creates a body with dead code for dead store elimination benchmarking.
@@ -134,7 +141,7 @@ fn create_diamond_cfg<'heap>(env: &Environment<'heap>, interner: &Interner<'heap
134141fn create_dead_store_cfg < ' heap > (
135142 env : & Environment < ' heap > ,
136143 interner : & Interner < ' heap > ,
137- ) -> Body < ' heap > {
144+ ) -> [ Body < ' heap > ; 1 ] {
138145 let mut builder = BodyBuilder :: new ( interner) ;
139146 let int_ty = TypeBuilder :: synthetic ( env) . integer ( ) ;
140147
@@ -160,7 +167,7 @@ fn create_dead_store_cfg<'heap>(
160167
161168 let mut body = builder. finish ( 0 , TypeBuilder :: synthetic ( env) . integer ( ) ) ;
162169 body. id = DefId :: new ( 0 ) ;
163- body
170+ [ body]
164171}
165172
166173/// Creates a body with patterns that `InstSimplify` can optimize.
@@ -181,7 +188,7 @@ fn create_dead_store_cfg<'heap>(
181188fn create_inst_simplify_cfg < ' heap > (
182189 env : & Environment < ' heap > ,
183190 interner : & Interner < ' heap > ,
184- ) -> Body < ' heap > {
191+ ) -> [ Body < ' heap > ; 1 ] {
185192 let mut builder = BodyBuilder :: new ( interner) ;
186193 let int_ty = TypeBuilder :: synthetic ( env) . integer ( ) ;
187194 let bool_ty = TypeBuilder :: synthetic ( env) . boolean ( ) ;
@@ -218,10 +225,10 @@ fn create_inst_simplify_cfg<'heap>(
218225
219226 let mut body = builder. finish ( 0 , TypeBuilder :: synthetic ( env) . boolean ( ) ) ;
220227 body. id = DefId :: new ( 0 ) ;
221- body
228+ [ body]
222229}
223230
224- /// Creates a larger CFG with multiple branches and join points for more realistic benchmarking .
231+ /// Creates a complex CFG with nested loops, back edges, unreachable blocks, and deep nesting .
225232///
226233/// Structure:
227234/// ```text
@@ -234,7 +241,10 @@ fn create_inst_simplify_cfg<'heap>(
234241/// bb6(p2): f = p2 == 20; goto bb7
235242/// bb7(p3): result = p3; return result
236243/// ```
237- fn create_complex_cfg < ' heap > ( env : & Environment < ' heap > , interner : & Interner < ' heap > ) -> Body < ' heap > {
244+ fn create_complex_cfg < ' heap > (
245+ env : & Environment < ' heap > ,
246+ interner : & Interner < ' heap > ,
247+ ) -> [ Body < ' heap > ; 1 ] {
238248 let mut builder = BodyBuilder :: new ( interner) ;
239249 let int_ty = TypeBuilder :: synthetic ( env) . integer ( ) ;
240250
@@ -309,17 +319,90 @@ fn create_complex_cfg<'heap>(env: &Environment<'heap>, interner: &Interner<'heap
309319 . assign_place ( result, |rv| rv. load ( p3) )
310320 . ret ( result) ;
311321
312- builder. finish ( 1 , TypeBuilder :: synthetic ( env) . integer ( ) )
322+ let mut body = builder. finish ( 1 , TypeBuilder :: synthetic ( env) . integer ( ) ) ;
323+ body. id = DefId :: new ( 0 ) ;
324+ [ body]
325+ }
326+
327+ /// Creates multiple bodies for inlining benchmarks: a caller with call sites and a callee.
328+ ///
329+ /// The caller has multiple call sites in different control flow paths.
330+ ///
331+ /// Structure:
332+ /// ```text
333+ /// callee (DefId 0):
334+ /// bb0(arg): cond = arg < 5; if cond then bb1() else bb2()
335+ /// bb1: r1 = arg == 0; return r1
336+ /// bb2: r2 = arg > 10; return r2
337+ ///
338+ /// caller (DefId 1):
339+ /// bb0: switch mode [0 => bb1, 1 => bb2, _ => bb3]
340+ /// bb1: x1 = apply callee, 3; goto bb4(x1)
341+ /// bb2: x2 = apply callee, 7; goto bb4(x2)
342+ /// bb3: x3 = apply callee, 0; goto bb4(x3)
343+ /// bb4(p): return p
344+ /// ```
345+ fn create_inlinable_cfg < ' heap > (
346+ env : & Environment < ' heap > ,
347+ interner : & Interner < ' heap > ,
348+ ) -> [ Body < ' heap > ; 2 ] {
349+ let callee_id = DefId :: new ( 0 ) ;
350+ let caller_id = DefId :: new ( 1 ) ;
351+
352+ // Callee: non-trivial function with control flow
353+ // Takes one Int arg, returns Bool based on comparisons
354+ let callee = body ! ( interner, env; fn @callee_id/1 -> Bool {
355+ decl arg0: Int , cond0: Bool , r1: Bool , r2: Bool ;
356+
357+ bb0( ) {
358+ cond0 = bin. < arg0 5 ;
359+ if cond0 then bb1( ) else bb2( ) ;
360+ } ,
361+ bb1( ) {
362+ r1 = bin. == arg0 0 ;
363+ return r1;
364+ } ,
365+ bb2( ) {
366+ r2 = bin. > arg0 10 ;
367+ return r2;
368+ }
369+ } ) ;
370+
371+ // Caller: calls callee from multiple paths
372+ let caller = body ! ( interner, env; fn @caller_id/1 -> Bool {
373+ decl mode0: Int , x1: Bool , x2: Bool , x3: Bool , p4: Bool ;
374+
375+ bb0( ) {
376+ switch mode0 [ 0 => bb1( ) , 1 => bb2( ) , _ => bb3( ) ] ;
377+ } ,
378+ bb1( ) {
379+ x1 = apply callee_id, 3 ;
380+ goto bb4( x1) ;
381+ } ,
382+ bb2( ) {
383+ x2 = apply callee_id, 7 ;
384+ goto bb4( x2) ;
385+ } ,
386+ bb3( ) {
387+ x3 = apply callee_id, 0 ;
388+ goto bb4( x3) ;
389+ } ,
390+ bb4( p4) {
391+ return p4;
392+ }
393+ } ) ;
394+
395+ [ callee, caller]
313396}
314397
315398#[ expect( unsafe_code) ]
316399#[ inline]
317- fn run_bencher < T > (
400+ fn run_bencher < T , const N : usize > (
318401 bencher : & mut Bencher ,
319- body : for <' heap > fn ( & Environment < ' heap > , & Interner < ' heap > ) -> Body < ' heap > ,
402+ body : for <' heap > fn ( & Environment < ' heap > , & Interner < ' heap > ) -> [ Body < ' heap > ; N ] ,
320403 mut func : impl for <' env , ' heap > FnMut (
321404 & mut MirContext < ' env , ' heap > ,
322- & mut Body < ' heap > ,
405+ & mut [ Body < ' heap > ; N ] ,
323406 & mut Scratch ,
324407 ) -> T ,
325408) {
@@ -402,19 +485,19 @@ fn cfg_simplify(criterion: &mut Criterion) {
402485 let mut group = criterion. benchmark_group ( "cfg_simplify" ) ;
403486
404487 group. bench_function ( "linear" , |bencher| {
405- run_bencher ( bencher, create_linear_cfg, |context, body, scratch| {
488+ run_bencher ( bencher, create_linear_cfg, |context, [ body] , scratch| {
406489 CfgSimplify :: new_in ( scratch) . run ( context, body)
407490 } ) ;
408491 } ) ;
409492
410493 group. bench_function ( "diamond" , |bencher| {
411- run_bencher ( bencher, create_diamond_cfg, |context, body, scratch| {
494+ run_bencher ( bencher, create_diamond_cfg, |context, [ body] , scratch| {
412495 CfgSimplify :: new_in ( scratch) . run ( context, body)
413496 } ) ;
414497 } ) ;
415498
416499 group. bench_function ( "complex" , |bencher| {
417- run_bencher ( bencher, create_complex_cfg, |context, body, scratch| {
500+ run_bencher ( bencher, create_complex_cfg, |context, [ body] , scratch| {
418501 CfgSimplify :: new_in ( scratch) . run ( context, body)
419502 } ) ;
420503 } ) ;
@@ -424,19 +507,19 @@ fn forward_substitution(criterion: &mut Criterion) {
424507 let mut group = criterion. benchmark_group ( "forward_substitution" ) ;
425508
426509 group. bench_function ( "linear" , |bencher| {
427- run_bencher ( bencher, create_linear_cfg, |context, body, scratch| {
510+ run_bencher ( bencher, create_linear_cfg, |context, [ body] , scratch| {
428511 ForwardSubstitution :: new_in ( scratch) . run ( context, body)
429512 } ) ;
430513 } ) ;
431514
432515 group. bench_function ( "diamond" , |bencher| {
433- run_bencher ( bencher, create_diamond_cfg, |context, body, scratch| {
516+ run_bencher ( bencher, create_diamond_cfg, |context, [ body] , scratch| {
434517 ForwardSubstitution :: new_in ( scratch) . run ( context, body)
435518 } ) ;
436519 } ) ;
437520
438521 group. bench_function ( "complex" , |bencher| {
439- run_bencher ( bencher, create_complex_cfg, |context, body, scratch| {
522+ run_bencher ( bencher, create_complex_cfg, |context, [ body] , scratch| {
440523 ForwardSubstitution :: new_in ( scratch) . run ( context, body)
441524 } ) ;
442525 } ) ;
@@ -446,25 +529,27 @@ fn dse(criterion: &mut Criterion) {
446529 let mut group = criterion. benchmark_group ( "dse" ) ;
447530
448531 group. bench_function ( "dead stores" , |bencher| {
449- run_bencher ( bencher, create_dead_store_cfg, |context, body, scratch| {
450- DeadStoreElimination :: new_in ( scratch) . run ( context, body)
451- } ) ;
532+ run_bencher (
533+ bencher,
534+ create_dead_store_cfg,
535+ |context, [ body] , scratch| DeadStoreElimination :: new_in ( scratch) . run ( context, body) ,
536+ ) ;
452537 } ) ;
453538
454539 group. bench_function ( "linear" , |bencher| {
455- run_bencher ( bencher, create_linear_cfg, |context, body, scratch| {
540+ run_bencher ( bencher, create_linear_cfg, |context, [ body] , scratch| {
456541 DeadStoreElimination :: new_in ( scratch) . run ( context, body)
457542 } ) ;
458543 } ) ;
459544
460545 group. bench_function ( "diamond" , |bencher| {
461- run_bencher ( bencher, create_diamond_cfg, |context, body, scratch| {
546+ run_bencher ( bencher, create_diamond_cfg, |context, [ body] , scratch| {
462547 DeadStoreElimination :: new_in ( scratch) . run ( context, body)
463548 } ) ;
464549 } ) ;
465550
466551 group. bench_function ( "complex" , |bencher| {
467- run_bencher ( bencher, create_complex_cfg, |context, body, scratch| {
552+ run_bencher ( bencher, create_complex_cfg, |context, [ body] , scratch| {
468553 DeadStoreElimination :: new_in ( scratch) . run ( context, body)
469554 } ) ;
470555 } ) ;
@@ -477,23 +562,23 @@ fn inst_simplify(criterion: &mut Criterion) {
477562 run_bencher (
478563 bencher,
479564 create_inst_simplify_cfg,
480- |context, body, scratch| InstSimplify :: new_in ( scratch) . run ( context, body) ,
565+ |context, [ body] , scratch| InstSimplify :: new_in ( scratch) . run ( context, body) ,
481566 ) ;
482567 } ) ;
483568 group. bench_function ( "linear" , |bencher| {
484- run_bencher ( bencher, create_linear_cfg, |context, body, scratch| {
569+ run_bencher ( bencher, create_linear_cfg, |context, [ body] , scratch| {
485570 InstSimplify :: new_in ( scratch) . run ( context, body)
486571 } ) ;
487572 } ) ;
488573
489574 group. bench_function ( "diamond" , |bencher| {
490- run_bencher ( bencher, create_diamond_cfg, |context, body, scratch| {
575+ run_bencher ( bencher, create_diamond_cfg, |context, [ body] , scratch| {
491576 InstSimplify :: new_in ( scratch) . run ( context, body)
492577 } ) ;
493578 } ) ;
494579
495580 group. bench_function ( "complex" , |bencher| {
496- run_bencher ( bencher, create_complex_cfg, |context, body, scratch| {
581+ run_bencher ( bencher, create_complex_cfg, |context, [ body] , scratch| {
497582 InstSimplify :: new_in ( scratch) . run ( context, body)
498583 } ) ;
499584 } ) ;
@@ -503,8 +588,8 @@ fn pipeline(criterion: &mut Criterion) {
503588 let mut group = criterion. benchmark_group ( "pipeline" ) ;
504589
505590 group. bench_function ( "linear" , |bencher| {
506- run_bencher ( bencher, create_linear_cfg, |context, body , scratch| {
507- let bodies = IdSlice :: from_raw_mut ( core :: slice :: from_mut ( body ) ) ;
591+ run_bencher ( bencher, create_linear_cfg, |context, bodies , scratch| {
592+ let bodies = IdSlice :: from_raw_mut ( bodies ) ;
508593 let mut state = GlobalTransformState :: new_in ( bodies, context. heap ) ;
509594
510595 let mut changed = PreInline :: new_in ( & mut * scratch) . run ( context, & mut state, bodies) ;
@@ -518,8 +603,8 @@ fn pipeline(criterion: &mut Criterion) {
518603 } ) ;
519604 } ) ;
520605 group. bench_function ( "diamond" , |bencher| {
521- run_bencher ( bencher, create_diamond_cfg, |context, body , scratch| {
522- let bodies = IdSlice :: from_raw_mut ( core :: slice :: from_mut ( body ) ) ;
606+ run_bencher ( bencher, create_diamond_cfg, |context, bodies , scratch| {
607+ let bodies = IdSlice :: from_raw_mut ( bodies ) ;
523608 let mut state = GlobalTransformState :: new_in ( bodies, context. heap ) ;
524609
525610 let mut changed = PreInline :: new_in ( & mut * scratch) . run ( context, & mut state, bodies) ;
@@ -533,8 +618,23 @@ fn pipeline(criterion: &mut Criterion) {
533618 } ) ;
534619 } ) ;
535620 group. bench_function ( "complex" , |bencher| {
536- run_bencher ( bencher, create_complex_cfg, |context, body, scratch| {
537- let bodies = IdSlice :: from_raw_mut ( core:: slice:: from_mut ( body) ) ;
621+ run_bencher ( bencher, create_complex_cfg, |context, bodies, scratch| {
622+ let bodies = IdSlice :: from_raw_mut ( bodies) ;
623+ let mut state = GlobalTransformState :: new_in ( bodies, context. heap ) ;
624+
625+ let mut changed = PreInline :: new_in ( & mut * scratch) . run ( context, & mut state, bodies) ;
626+ scratch. reset ( ) ;
627+ changed |= Inline :: new_in ( InlineConfig :: default ( ) , & mut * scratch)
628+ . run ( context, & mut state, bodies) ;
629+ scratch. reset ( ) ;
630+ changed |= PostInline :: new_in ( & mut * scratch) . run ( context, & mut state, bodies) ;
631+ scratch. reset ( ) ;
632+ changed
633+ } ) ;
634+ } ) ;
635+ group. bench_function ( "inline" , |bencher| {
636+ run_bencher ( bencher, create_inlinable_cfg, |context, bodies, scratch| {
637+ let bodies = IdSlice :: from_raw_mut ( bodies) ;
538638 let mut state = GlobalTransformState :: new_in ( bodies, context. heap ) ;
539639
540640 let mut changed = PreInline :: new_in ( & mut * scratch) . run ( context, & mut state, bodies) ;
0 commit comments