1use crate::lcnf::{LcnfArg, LcnfExpr, LcnfFunDecl, LcnfLetValue, LcnfLit, LcnfType, LcnfVarId};
6use std::collections::{HashMap, HashSet};
7
8use super::types::{
9 CallFrequencyAnalyzer, CallGraph, CallSite, CalleeSizeTable, CloneSpecializer,
10 ExtendedInlinePass, ExtendedInlineStats, FreshVarGen, HotPath, InlineAnnotation,
11 InlineAnnotationRegistry, InlineBudget, InlineConfig, InlineContextStack, InlineCost,
12 InlineDecision, InlineFusionManager, InlineHeuristics, InlineHistory, InlineOrderScheduler,
13 InlinePass, InlineProfile, InlineProfitabilityEstimator, InlineReport, InlineTrace,
14 InlineTraceEntry, InliningContext, InterproceduralInlinePass, NestingDepthTracker,
15 PartialInlineDecision, PartialInlineRegion, RecursiveInlineLimiter, SpeculativeInlineRecord,
16 SpeculativeInliner, TarjanScc,
17};
18
19pub fn estimate_size(decl: &LcnfFunDecl) -> u64 {
24 1 + count_expr_size(&decl.body)
25}
26pub(super) fn count_expr_size(expr: &LcnfExpr) -> u64 {
27 match expr {
28 LcnfExpr::Let { value, body, .. } => {
29 let value_cost = match value {
30 LcnfLetValue::App(_, args) => 1 + args.len() as u64,
31 LcnfLetValue::Ctor(_, _, args) | LcnfLetValue::Reuse(_, _, _, args) => {
32 1 + args.len() as u64
33 }
34 LcnfLetValue::Proj(..) => 1,
35 LcnfLetValue::Reset(..) => 1,
36 LcnfLetValue::Lit(_) | LcnfLetValue::Erased | LcnfLetValue::FVar(_) => 1,
37 };
38 value_cost + count_expr_size(body)
39 }
40 LcnfExpr::Case { alts, default, .. } => {
41 let alt_cost: u64 = alts.iter().map(|a| 2 + count_expr_size(&a.body)).sum();
42 let default_cost = default.as_ref().map_or(0, |d| 2 + count_expr_size(d));
43 1 + alt_cost + default_cost
44 }
45 LcnfExpr::Return(_) | LcnfExpr::TailCall(_, _) | LcnfExpr::Unreachable => 1,
46 }
47}
48pub(super) fn substitute_params(
55 body: &LcnfExpr,
56 params: &[String],
57 args: &[LcnfArg],
58 gen: &mut FreshVarGen,
59) -> LcnfExpr {
60 let mut param_map: HashMap<String, LcnfArg> = HashMap::new();
61 for (param, arg) in params.iter().zip(args.iter()) {
62 param_map.insert(param.clone(), arg.clone());
63 }
64 subst_expr(body, ¶m_map, gen)
65}
66#[allow(clippy::only_used_in_recursion)]
67pub(super) fn subst_expr(
68 expr: &LcnfExpr,
69 map: &HashMap<String, LcnfArg>,
70 gen: &mut FreshVarGen,
71) -> LcnfExpr {
72 match expr {
73 LcnfExpr::Let {
74 id,
75 name,
76 ty,
77 value,
78 body,
79 } => {
80 let new_value = subst_let_value(value, map);
81 let new_body = subst_expr(body, map, gen);
82 LcnfExpr::Let {
83 id: *id,
84 name: name.clone(),
85 ty: ty.clone(),
86 value: new_value,
87 body: Box::new(new_body),
88 }
89 }
90 LcnfExpr::Case {
91 scrutinee,
92 scrutinee_ty,
93 alts,
94 default,
95 } => {
96 let new_scrutinee = *scrutinee;
97 let new_alts = alts
98 .iter()
99 .map(|alt| crate::lcnf::LcnfAlt {
100 ctor_name: alt.ctor_name.clone(),
101 ctor_tag: alt.ctor_tag,
102 params: alt.params.clone(),
103 body: subst_expr(&alt.body, map, gen),
104 })
105 .collect();
106 let new_default = default.as_ref().map(|d| Box::new(subst_expr(d, map, gen)));
107 LcnfExpr::Case {
108 scrutinee: new_scrutinee,
109 scrutinee_ty: scrutinee_ty.clone(),
110 alts: new_alts,
111 default: new_default,
112 }
113 }
114 LcnfExpr::Return(arg) => LcnfExpr::Return(subst_arg(arg, map)),
115 LcnfExpr::TailCall(func, args) => LcnfExpr::TailCall(
116 subst_arg(func, map),
117 args.iter().map(|a| subst_arg(a, map)).collect(),
118 ),
119 LcnfExpr::Unreachable => LcnfExpr::Unreachable,
120 }
121}
122pub(super) fn subst_let_value(
123 value: &LcnfLetValue,
124 map: &HashMap<String, LcnfArg>,
125) -> LcnfLetValue {
126 match value {
127 LcnfLetValue::App(func, args) => LcnfLetValue::App(
128 subst_arg(func, map),
129 args.iter().map(|a| subst_arg(a, map)).collect(),
130 ),
131 LcnfLetValue::Ctor(name, tag, args) => LcnfLetValue::Ctor(
132 name.clone(),
133 *tag,
134 args.iter().map(|a| subst_arg(a, map)).collect(),
135 ),
136 LcnfLetValue::Reuse(slot, name, tag, args) => LcnfLetValue::Reuse(
137 *slot,
138 name.clone(),
139 *tag,
140 args.iter().map(|a| subst_arg(a, map)).collect(),
141 ),
142 LcnfLetValue::Proj(name, idx, var) => LcnfLetValue::Proj(name.clone(), *idx, *var),
143 LcnfLetValue::Reset(var) => LcnfLetValue::Reset(*var),
144 LcnfLetValue::Lit(lit) => LcnfLetValue::Lit(lit.clone()),
145 LcnfLetValue::Erased => LcnfLetValue::Erased,
146 LcnfLetValue::FVar(id) => LcnfLetValue::FVar(*id),
147 }
148}
149pub(super) fn subst_arg(arg: &LcnfArg, map: &HashMap<String, LcnfArg>) -> LcnfArg {
150 match arg {
151 LcnfArg::Lit(lit) => LcnfArg::Lit(lit.clone()),
152 LcnfArg::Var(id) => {
153 let name_str = format!("_x{}", id.0);
154 if let Some(replacement) = map.get(&name_str) {
155 replacement.clone()
156 } else {
157 LcnfArg::Var(*id)
158 }
159 }
160 LcnfArg::Erased => LcnfArg::Erased,
161 LcnfArg::Type(ty) => LcnfArg::Type(ty.clone()),
162 }
163}
164pub(super) fn splice_inlined(inlined: LcnfExpr, continuation: LcnfExpr) -> LcnfExpr {
168 match inlined {
169 LcnfExpr::Return(_) => continuation,
170 LcnfExpr::Unreachable => LcnfExpr::Unreachable,
171 other => sequence_exprs(other, continuation),
172 }
173}
174pub(super) fn sequence_exprs(first: LcnfExpr, next: LcnfExpr) -> LcnfExpr {
176 match first {
177 LcnfExpr::Let {
178 id,
179 name,
180 ty,
181 value,
182 body,
183 } => LcnfExpr::Let {
184 id,
185 name,
186 ty,
187 value,
188 body: Box::new(sequence_exprs(*body, next)),
189 },
190 LcnfExpr::Case {
191 scrutinee,
192 scrutinee_ty,
193 alts,
194 default,
195 } => LcnfExpr::Case {
196 scrutinee,
197 scrutinee_ty,
198 alts: alts
199 .into_iter()
200 .map(|alt| crate::lcnf::LcnfAlt {
201 ctor_name: alt.ctor_name,
202 ctor_tag: alt.ctor_tag,
203 params: alt.params,
204 body: sequence_exprs(alt.body, next.clone()),
205 })
206 .collect(),
207 default: default.map(|d| Box::new(sequence_exprs(*d, next.clone()))),
208 },
209 LcnfExpr::Return(_) => next,
210 LcnfExpr::TailCall(_, _) | LcnfExpr::Unreachable => first,
211 }
212}
213pub(super) fn arg_to_let_value(arg: &LcnfArg) -> LcnfLetValue {
215 match arg {
216 LcnfArg::Lit(lit) => LcnfLetValue::Lit(lit.clone()),
217 LcnfArg::Var(id) => LcnfLetValue::FVar(*id),
218 LcnfArg::Erased => LcnfLetValue::Erased,
219 LcnfArg::Type(_) => LcnfLetValue::Erased,
220 }
221}
222pub fn run_inline_pass(decls: &mut [LcnfFunDecl]) -> InlineReport {
224 let mut pass = InlinePass::default();
225 pass.run(decls);
226 pass.report().clone()
227}
228pub fn run_inline_pass_with_config(
230 decls: &mut [LcnfFunDecl],
231 config: InlineConfig,
232) -> InlineReport {
233 let mut pass = InlinePass::new(config);
234 pass.run(decls);
235 pass.report().clone()
236}
237#[allow(dead_code)]
239pub fn collect_callees(expr: &LcnfExpr) -> Vec<String> {
240 let mut out = Vec::new();
241 collect_callees_rec(expr, &mut out);
242 out
243}
244#[allow(dead_code)]
245pub(super) fn collect_callees_rec(expr: &LcnfExpr, out: &mut Vec<String>) {
246 match expr {
247 LcnfExpr::Let { value, body, .. } => {
248 if let LcnfLetValue::App(LcnfArg::Lit(LcnfLit::Str(name)), _) = value {
249 if !out.contains(name) {
250 out.push(name.clone());
251 }
252 }
253 collect_callees_rec(body, out);
254 }
255 LcnfExpr::Case { alts, default, .. } => {
256 for alt in alts {
257 collect_callees_rec(&alt.body, out);
258 }
259 if let Some(def) = default {
260 collect_callees_rec(def, out);
261 }
262 }
263 LcnfExpr::TailCall(LcnfArg::Lit(LcnfLit::Str(name)), _) => {
264 if !out.contains(name) {
265 out.push(name.clone());
266 }
267 }
268 _ => {}
269 }
270}
271#[allow(dead_code)]
272pub(super) fn inline_subst(expr: LcnfExpr, from: LcnfVarId, to: LcnfArg) -> LcnfExpr {
273 match expr {
274 LcnfExpr::Let {
275 id,
276 name,
277 ty,
278 value,
279 body,
280 } => {
281 let value2 = inline_subst_value(value, from, &to);
282 let body2 = inline_subst(*body, from, to);
283 LcnfExpr::Let {
284 id,
285 name,
286 ty,
287 value: value2,
288 body: Box::new(body2),
289 }
290 }
291 LcnfExpr::Case {
292 scrutinee,
293 scrutinee_ty,
294 alts,
295 default,
296 } => {
297 let s2 = if scrutinee == from {
298 match &to {
299 LcnfArg::Var(v) => *v,
300 _ => scrutinee,
301 }
302 } else {
303 scrutinee
304 };
305 let alts2 = alts
306 .into_iter()
307 .map(|alt| crate::lcnf::LcnfAlt {
308 ctor_name: alt.ctor_name,
309 ctor_tag: alt.ctor_tag,
310 params: alt.params,
311 body: inline_subst(alt.body, from, to.clone()),
312 })
313 .collect();
314 let default2 = default.map(|d| Box::new(inline_subst(*d, from, to)));
315 LcnfExpr::Case {
316 scrutinee: s2,
317 scrutinee_ty,
318 alts: alts2,
319 default: default2,
320 }
321 }
322 LcnfExpr::Return(arg) => LcnfExpr::Return(inline_subst_arg(arg, from, &to)),
323 LcnfExpr::TailCall(func, args) => LcnfExpr::TailCall(
324 inline_subst_arg(func, from, &to),
325 args.into_iter()
326 .map(|a| inline_subst_arg(a, from, &to))
327 .collect(),
328 ),
329 LcnfExpr::Unreachable => LcnfExpr::Unreachable,
330 }
331}
332#[allow(dead_code)]
333pub(super) fn inline_subst_arg(arg: LcnfArg, from: LcnfVarId, to: &LcnfArg) -> LcnfArg {
334 match &arg {
335 LcnfArg::Var(id) if *id == from => to.clone(),
336 _ => arg,
337 }
338}
339#[allow(dead_code)]
340pub(super) fn inline_subst_value(
341 value: LcnfLetValue,
342 from: LcnfVarId,
343 to: &LcnfArg,
344) -> LcnfLetValue {
345 match value {
346 LcnfLetValue::App(func, args) => LcnfLetValue::App(
347 inline_subst_arg(func, from, to),
348 args.into_iter()
349 .map(|a| inline_subst_arg(a, from, to))
350 .collect(),
351 ),
352 LcnfLetValue::Ctor(name, tag, args) => LcnfLetValue::Ctor(
353 name,
354 tag,
355 args.into_iter()
356 .map(|a| inline_subst_arg(a, from, to))
357 .collect(),
358 ),
359 LcnfLetValue::Proj(name, idx, var) => {
360 let v2 = if var == from {
361 match to {
362 LcnfArg::Var(v) => *v,
363 _ => var,
364 }
365 } else {
366 var
367 };
368 LcnfLetValue::Proj(name, idx, v2)
369 }
370 LcnfLetValue::FVar(var) => {
371 if var == from {
372 match to {
373 LcnfArg::Var(v) => LcnfLetValue::FVar(*v),
374 _ => LcnfLetValue::FVar(var),
375 }
376 } else {
377 LcnfLetValue::FVar(var)
378 }
379 }
380 LcnfLetValue::Reset(var) => {
381 let v2 = if var == from {
382 match to {
383 LcnfArg::Var(v) => *v,
384 _ => var,
385 }
386 } else {
387 var
388 };
389 LcnfLetValue::Reset(v2)
390 }
391 LcnfLetValue::Reuse(slot, name, tag, args) => {
392 let s2 = if slot == from {
393 match to {
394 LcnfArg::Var(v) => *v,
395 _ => slot,
396 }
397 } else {
398 slot
399 };
400 LcnfLetValue::Reuse(
401 s2,
402 name,
403 tag,
404 args.into_iter()
405 .map(|a| inline_subst_arg(a, from, to))
406 .collect(),
407 )
408 }
409 other => other,
410 }
411}
412#[cfg(test)]
413mod inline_tests {
414 use super::*;
415 use crate::lcnf::LcnfFunDecl;
416 pub(super) fn make_trivial_decl(name: &str) -> LcnfFunDecl {
417 LcnfFunDecl {
418 name: name.to_owned(),
419 original_name: None,
420 params: vec![],
421 ret_type: LcnfType::Object,
422 body: LcnfExpr::Return(LcnfArg::Erased),
423 is_recursive: false,
424 is_lifted: false,
425 inline_cost: 1,
426 }
427 }
428 pub(super) fn make_calling_decl(from: &str, to: &str) -> LcnfFunDecl {
429 LcnfFunDecl {
430 name: from.to_owned(),
431 original_name: None,
432 params: vec![],
433 ret_type: LcnfType::Object,
434 body: LcnfExpr::TailCall(LcnfArg::Lit(LcnfLit::Str(to.to_owned())), vec![]),
435 is_recursive: false,
436 is_lifted: false,
437 inline_cost: 2,
438 }
439 }
440 #[test]
441 pub(super) fn inline_decision_basics() {
442 assert!(InlineDecision::Always.should_inline(0));
443 assert!(!InlineDecision::Never.should_inline(0));
444 assert!(InlineDecision::Heuristic(0.8).should_inline(0));
445 assert!(!InlineDecision::Heuristic(0.3).should_inline(0));
446 assert!(InlineDecision::OnceOnly.should_inline(0));
447 assert!(!InlineDecision::OnceOnly.should_inline(1));
448 }
449 #[test]
450 pub(super) fn inline_cost_net_gain() {
451 let cost = InlineCost {
452 body_size: 5,
453 call_overhead: 4,
454 estimated_savings: 3,
455 };
456 assert_eq!(cost.net_gain(), 2);
457 assert!(cost.is_profitable());
458 }
459 #[test]
460 pub(super) fn call_site_benefit() {
461 let site = CallSite::new("f", "g", 1, true, false);
462 assert_eq!(site.inline_benefit(), 13);
463 let rec = CallSite::new("f", "f", 2, false, true);
464 assert_eq!(rec.inline_benefit(), 2);
465 }
466 #[test]
467 pub(super) fn inline_profile_hot() {
468 let mut p = InlineProfile::new();
469 for _ in 0..5 {
470 p.record_call("foo");
471 }
472 assert!(p.is_hot("foo", 5));
473 assert!(!p.is_hot("foo", 6));
474 assert_eq!(p.top_callees(1)[0].0, "foo");
475 }
476 #[test]
477 pub(super) fn heuristics_decide_tiny_fn() {
478 let h = InlineHeuristics::default();
479 let decl = make_trivial_decl("f");
480 let dec = h.decide(&decl, &InlineProfile::new());
481 assert_eq!(dec, InlineDecision::Always);
482 }
483 #[test]
484 pub(super) fn inlining_context_cycle() {
485 let mut ctx = InliningContext::new();
486 assert!(ctx.push_call("foo"));
487 assert!(!ctx.push_call("foo"));
488 ctx.pop_call();
489 assert!(ctx.push_call("foo"));
490 }
491 #[test]
492 pub(super) fn estimate_size_trivial() {
493 let decl = make_trivial_decl("f");
494 assert_eq!(estimate_size(&decl), 2);
495 }
496 #[test]
497 pub(super) fn inline_pass_smoke() {
498 let mut decls = vec![make_trivial_decl("main"), make_calling_decl("f", "main")];
499 let report = run_inline_pass(&mut decls);
500 let _ = report.summary();
501 }
502 #[test]
503 pub(super) fn tarjan_no_recursion() {
504 let decls = vec![make_trivial_decl("a"), make_trivial_decl("b")];
505 let mut scc = TarjanScc::new(&decls);
506 scc.compute();
507 assert!(!scc.is_recursive("a"));
508 }
509 #[test]
510 pub(super) fn budget_spend() {
511 let mut b = InlineBudget::new(100);
512 assert!(b.try_spend("f", 40));
513 assert!(b.try_spend("f", 40));
514 assert!(!b.try_spend("f", 40));
515 assert_eq!(b.remaining(), 20);
516 }
517 #[test]
518 pub(super) fn hot_path_trivial() {
519 let decl = make_trivial_decl("f");
520 assert!(!HotPath::extract(&decl).has_prefix());
521 }
522 #[test]
523 pub(super) fn speculative_inliner_committed() {
524 let mut si = SpeculativeInliner::new(0.6);
525 si.add(SpeculativeInlineRecord::new("f", "g", 0.8, "Nat"));
526 si.add(SpeculativeInlineRecord::new("f", "h", 0.3, "Bool"));
527 assert_eq!(si.committed().len(), 1);
528 }
529 #[test]
530 pub(super) fn annotation_registry() {
531 let mut reg = InlineAnnotationRegistry::new();
532 reg.register("f", InlineAnnotation::AlwaysInline);
533 reg.register("g", InlineAnnotation::NeverInline);
534 assert_eq!(
535 reg.apply("f", InlineDecision::Heuristic(0.2)),
536 InlineDecision::Always
537 );
538 assert_eq!(
539 reg.apply("g", InlineDecision::Always),
540 InlineDecision::Never
541 );
542 }
543 #[test]
544 pub(super) fn callee_size_table() {
545 let decls = vec![make_trivial_decl("a"), make_trivial_decl("b")];
546 let t = CalleeSizeTable::build(&decls);
547 assert_eq!(t.len(), 2);
548 assert!(t.size_of("a").is_some());
549 }
550 #[test]
551 pub(super) fn nesting_tracker() {
552 let mut t = NestingDepthTracker::new(2);
553 assert!(t.push());
554 assert!(t.push());
555 assert!(!t.push());
556 assert_eq!(t.limit_hit_count, 1);
557 assert_eq!(t.peak_depth, 2);
558 t.pop();
559 assert_eq!(t.remaining(), 1);
560 }
561 #[test]
562 pub(super) fn extended_stats_summary() {
563 let mut s = ExtendedInlineStats::new();
564 s.record_decision(&InlineDecision::Always, true);
565 s.record_size_change(100, 50);
566 assert_eq!(s.net_size_change(), 50);
567 assert!(s.summary().contains("InlineStats"));
568 }
569 #[test]
570 pub(super) fn call_freq_analyzer() {
571 let decls = vec![make_calling_decl("f", "g"), make_calling_decl("h", "g")];
572 let mut p = CallFrequencyAnalyzer::analyze(&decls);
573 assert_eq!(p.call_counts.get("g").copied(), Some(2));
574 CallFrequencyAnalyzer::mark_hot(&mut p, 2);
575 assert!(p.hot_functions.contains("g"));
576 }
577 #[test]
578 pub(super) fn recursive_limiter() {
579 let mut lim = RecursiveInlineLimiter::new(2);
580 assert!(lim.try_unroll("f"));
581 assert!(lim.try_unroll("f"));
582 assert!(!lim.try_unroll("f"));
583 lim.pop_unroll("f");
584 assert!(lim.try_unroll("f"));
585 }
586 #[test]
587 pub(super) fn extended_pass_init() {
588 let decls = vec![make_trivial_decl("main")];
589 let mut pass = ExtendedInlinePass::new(InlineConfig::default(), 10000);
590 pass.init_scc(&decls);
591 assert!(pass.scc.is_some());
592 assert_eq!(pass.size_table.len(), 1);
593 }
594 #[test]
595 pub(super) fn call_graph_build_and_query() {
596 let decls = vec![make_calling_decl("f", "g"), make_trivial_decl("g")];
597 let g = CallGraph::build(&decls);
598 assert_eq!(g.num_nodes(), 2);
599 assert_eq!(g.in_degree("g"), 1);
600 assert!(g.leaf_functions().contains(&"g"));
601 }
602 #[test]
603 pub(super) fn inline_order_bottom_up() {
604 let decls = vec![make_calling_decl("f", "g"), make_trivial_decl("g")];
605 let g = CallGraph::build(&decls);
606 let sched = InlineOrderScheduler::compute(&g);
607 let gp = sched.order.iter().position(|n| n == "g");
608 let fp = sched.order.iter().position(|n| n == "f");
609 if let (Some(gi), Some(fi)) = (gp, fp) {
610 assert!(gi < fi);
611 }
612 }
613 #[test]
614 pub(super) fn inline_trace_disabled() {
615 let mut t = InlineTrace::disabled();
616 t.record(InlineTraceEntry::new(0, "a", "b", "always", 1, true));
617 assert!(t.is_empty());
618 }
619 #[test]
620 pub(super) fn inline_trace_csv() {
621 let mut t = InlineTrace::new();
622 t.record(InlineTraceEntry::new(1, "f", "g", "always", 5, true));
623 let csv = t.to_csv();
624 assert!(csv.contains("f,g,always,5,true"));
625 }
626 #[test]
627 pub(super) fn inline_report_rate() {
628 let r = InlineReport {
629 total_calls_considered: 10,
630 inlined_count: 7,
631 skipped_recursive: 1,
632 skipped_too_large: 2,
633 };
634 assert!((r.inline_rate() - 0.7).abs() < 1e-9);
635 assert!(r.summary().contains("7/10"));
636 }
637 #[test]
638 pub(super) fn collect_callees_fn() {
639 let expr = LcnfExpr::TailCall(LcnfArg::Lit(LcnfLit::Str("foo".to_owned())), vec![]);
640 assert_eq!(collect_callees(&expr), vec!["foo".to_owned()]);
641 }
642}
643#[cfg(test)]
644mod inline_extended_tests {
645 use super::*;
646 #[test]
647 pub(super) fn inline_context_stack_basic() {
648 let mut stack = InlineContextStack::new();
649 assert_eq!(stack.depth(), 0);
650 stack.push("foo", 0);
651 stack.push("bar", 1);
652 assert_eq!(stack.depth(), 2);
653 assert!(stack.contains("foo"));
654 assert!(stack.contains("bar"));
655 assert!(!stack.contains("baz"));
656 let fp = stack.fingerprint();
657 assert!(fp.contains("foo"));
658 assert!(fp.contains("bar"));
659 let frame = stack.pop().expect("frame should be available to pop");
660 assert_eq!(frame.callee, "bar");
661 assert_eq!(stack.depth(), 1);
662 }
663 #[test]
664 pub(super) fn partial_inline_decision_will_inline() {
665 let d = PartialInlineDecision::full("f", 10);
666 assert!(d.will_inline());
667 let d2 = PartialInlineDecision::no_inline("g", "too_large");
668 assert!(!d2.will_inline());
669 let d3 = PartialInlineDecision::prefix("h", 3, 5);
670 assert!(d3.will_inline());
671 match d3.region {
672 PartialInlineRegion::Prefix(n) => assert_eq!(n, 3),
673 _ => panic!("expected Prefix"),
674 }
675 }
676 #[test]
677 pub(super) fn profitability_estimator_basic() {
678 let est = InlineProfitabilityEstimator::new();
679 assert!(est.is_profitable(2, 5.0, true));
680 assert!(!est.is_profitable(1000, 1.0, false));
681 }
682 #[test]
683 pub(super) fn clone_specializer_records() {
684 let mut cs = CloneSpecializer::new();
685 let name = cs.record("add", 0, "42");
686 assert!(name.starts_with("add_spec_0_42_c"));
687 assert_eq!(cs.count(), 1);
688 }
689 #[test]
690 pub(super) fn inline_history_tracking() {
691 let mut h = InlineHistory::new();
692 assert!(!h.has_seen("f", "g"));
693 h.mark_seen("f", "g");
694 assert!(h.has_seen("f", "g"));
695 assert_eq!(h.count(), 1);
696 h.reset();
697 assert!(!h.has_seen("f", "g"));
698 assert_eq!(h.count(), 0);
699 }
700 #[test]
701 pub(super) fn interprocedural_inline_pass_smoke() {
702 let config = InlineConfig::default();
703 let mut pass = InterproceduralInlinePass::new(config);
704 let mut decls: Vec<LcnfFunDecl> = vec![];
705 pass.run(&mut decls);
706 let report = pass.report();
707 assert!(report.contains("0 functions processed"));
708 }
709 #[test]
710 pub(super) fn inline_fusion_manager_basic() {
711 let mut mgr = InlineFusionManager::new();
712 let name = mgr.fuse("caller", "f", "g", 15);
713 assert!(name.starts_with("caller_fused_f_g_"));
714 assert_eq!(mgr.all_records().len(), 1);
715 assert_eq!(mgr.total_savings(), 15);
716 mgr.fuse("caller", "g", "h", 5);
717 assert_eq!(mgr.total_savings(), 20);
718 }
719 #[test]
720 pub(super) fn extended_inline_stats_default() {
721 let stats = ExtendedInlineStats::default();
722 assert_eq!(stats.total_functions_processed, 0);
723 assert!(stats.inlining_order.is_empty());
724 }
725}