1use crate::lcnf::*;
6use std::collections::{HashMap, HashSet};
7
8use super::types::{
9 NumericSpecializer, SizeBudget, SpecAnalysisCache, SpecCallSite, SpecClosureArg, SpecConstArg,
10 SpecConstantFoldingHelper, SpecDepGraph, SpecDominatorTree, SpecExtCache, SpecExtConstFolder,
11 SpecExtDepGraph, SpecExtDomTree, SpecExtLiveness, SpecExtPassConfig, SpecExtPassPhase,
12 SpecExtPassRegistry, SpecExtPassStats, SpecExtWorklist, SpecLivenessInfo, SpecPassConfig,
13 SpecPassPhase, SpecPassRegistry, SpecPassStats, SpecTypeArg, SpecWorklist, SpecializationCache,
14 SpecializationConfig, SpecializationKey, SpecializationPass, SpecializationStats,
15};
16
17pub(super) fn type_suffix(ty: &LcnfType) -> String {
19 match ty {
20 LcnfType::Nat => "nat".to_string(),
21 LcnfType::Int => "int".to_string(),
22 LcnfType::Object => "obj".to_string(),
23 LcnfType::Unit => "unit".to_string(),
24 LcnfType::Erased => "e".to_string(),
25 LcnfType::LcnfString => "str".to_string(),
26 LcnfType::Var(name) => name.clone(),
27 LcnfType::Ctor(name, _) => name.clone(),
28 LcnfType::Fun(_, _) => "fn".to_string(),
29 LcnfType::Irrelevant => "irr".to_string(),
30 }
31}
32pub(super) fn find_specialization_sites(
34 expr: &LcnfExpr,
35 known_constants: &HashMap<LcnfVarId, LcnfLit>,
36 known_functions: &HashMap<LcnfVarId, String>,
37 decl_names: &HashSet<String>,
38) -> Vec<SpecCallSite> {
39 let mut sites = Vec::new();
40 let mut call_idx = 0;
41 find_spec_sites_inner(
42 expr,
43 known_constants,
44 known_functions,
45 decl_names,
46 &mut sites,
47 &mut call_idx,
48 );
49 sites
50}
51pub(super) fn find_spec_sites_inner(
52 expr: &LcnfExpr,
53 known_constants: &HashMap<LcnfVarId, LcnfLit>,
54 known_functions: &HashMap<LcnfVarId, String>,
55 decl_names: &HashSet<String>,
56 sites: &mut Vec<SpecCallSite>,
57 call_idx: &mut usize,
58) {
59 match expr {
60 LcnfExpr::Let {
61 id, value, body, ..
62 } => {
63 let mut extended_consts = known_constants.clone();
64 if let LcnfLetValue::Lit(lit) = value {
65 extended_consts.insert(*id, lit.clone());
66 }
67 let mut extended_fns = known_functions.clone();
68 if let LcnfLetValue::FVar(fvar) = value {
69 if let Some(fname) = known_functions.get(fvar) {
70 extended_fns.insert(*id, fname.clone());
71 }
72 }
73 if let LcnfLetValue::App(func, args) = value {
74 let callee_name = match func {
75 LcnfArg::Var(v) => known_functions.get(v).cloned(),
76 _ => None,
77 };
78 if let Some(ref callee) = callee_name {
79 if decl_names.contains(callee.as_str()) {
80 let const_args: Vec<SpecConstArg> = args
81 .iter()
82 .map(|arg| match arg {
83 LcnfArg::Lit(LcnfLit::Nat(n)) => SpecConstArg::Nat(*n),
84 LcnfArg::Lit(LcnfLit::Str(s)) => SpecConstArg::Str(s.clone()),
85 LcnfArg::Var(v) => {
86 if let Some(lit) = extended_consts.get(v) {
87 match lit {
88 LcnfLit::Nat(n) => SpecConstArg::Nat(*n),
89 LcnfLit::Int(_) => SpecConstArg::Unknown,
90 LcnfLit::Str(s) => SpecConstArg::Str(s.clone()),
91 }
92 } else {
93 SpecConstArg::Unknown
94 }
95 }
96 _ => SpecConstArg::Unknown,
97 })
98 .collect();
99 let closure_args: Vec<SpecClosureArg> = args
100 .iter()
101 .enumerate()
102 .map(|(i, arg)| {
103 let known_fn = match arg {
104 LcnfArg::Var(v) => extended_fns.get(v).cloned(),
105 _ => None,
106 };
107 SpecClosureArg {
108 known_fn,
109 param_idx: i,
110 }
111 })
112 .collect();
113 let callee_var = match func {
114 LcnfArg::Var(v) => Some(*v),
115 _ => None,
116 };
117 sites.push(SpecCallSite {
118 callee: callee.clone(),
119 call_idx: *call_idx,
120 type_args: vec![],
121 const_args,
122 closure_args,
123 callee_var,
124 });
125 *call_idx += 1;
126 }
127 }
128 }
129 find_spec_sites_inner(
130 body,
131 &extended_consts,
132 &extended_fns,
133 decl_names,
134 sites,
135 call_idx,
136 );
137 }
138 LcnfExpr::Case { alts, default, .. } => {
139 for alt in alts {
140 find_spec_sites_inner(
141 &alt.body,
142 known_constants,
143 known_functions,
144 decl_names,
145 sites,
146 call_idx,
147 );
148 }
149 if let Some(def) = default {
150 find_spec_sites_inner(
151 def,
152 known_constants,
153 known_functions,
154 decl_names,
155 sites,
156 call_idx,
157 );
158 }
159 }
160 LcnfExpr::TailCall(func, args) => {
161 let callee_name = match func {
162 LcnfArg::Var(v) => known_functions.get(v).cloned(),
163 _ => None,
164 };
165 if let Some(callee) = callee_name {
166 if decl_names.contains(callee.as_str()) {
167 let const_args: Vec<SpecConstArg> = args
168 .iter()
169 .map(|arg| match arg {
170 LcnfArg::Lit(LcnfLit::Nat(n)) => SpecConstArg::Nat(*n),
171 LcnfArg::Lit(LcnfLit::Str(s)) => SpecConstArg::Str(s.clone()),
172 LcnfArg::Var(v) => {
173 if let Some(lit) = known_constants.get(v) {
174 match lit {
175 LcnfLit::Nat(n) => SpecConstArg::Nat(*n),
176 LcnfLit::Int(_) => SpecConstArg::Unknown,
177 LcnfLit::Str(s) => SpecConstArg::Str(s.clone()),
178 }
179 } else {
180 SpecConstArg::Unknown
181 }
182 }
183 _ => SpecConstArg::Unknown,
184 })
185 .collect();
186 let closure_args: Vec<SpecClosureArg> = args
187 .iter()
188 .enumerate()
189 .map(|(i, arg)| {
190 let known_fn = match arg {
191 LcnfArg::Var(v) => known_functions.get(v).cloned(),
192 _ => None,
193 };
194 SpecClosureArg {
195 known_fn,
196 param_idx: i,
197 }
198 })
199 .collect();
200 let callee_var = match func {
201 LcnfArg::Var(v) => Some(*v),
202 _ => None,
203 };
204 sites.push(SpecCallSite {
205 callee,
206 call_idx: *call_idx,
207 type_args: vec![],
208 const_args,
209 closure_args,
210 callee_var,
211 });
212 *call_idx += 1;
213 }
214 }
215 }
216 LcnfExpr::Return(_) | LcnfExpr::Unreachable => {}
217 }
218}
219pub(super) fn count_instructions(expr: &LcnfExpr) -> usize {
221 match expr {
222 LcnfExpr::Let { body, .. } => 1 + count_instructions(body),
223 LcnfExpr::Case { alts, default, .. } => {
224 let alts_size: usize = alts.iter().map(|a| count_instructions(&a.body)).sum();
225 let def_size = default.as_ref().map(|d| count_instructions(d)).unwrap_or(0);
226 1 + alts_size + def_size
227 }
228 LcnfExpr::Return(_) | LcnfExpr::TailCall(_, _) | LcnfExpr::Unreachable => 1,
229 }
230}
231pub(super) fn analyze_closure_uniformity(
233 decl: &LcnfFunDecl,
234 param_idx: usize,
235 sites: &[SpecCallSite],
236) -> Option<String> {
237 let mut known_fn: Option<String> = None;
238 for site in sites {
239 if site.callee != decl.name {
240 continue;
241 }
242 if param_idx >= site.closure_args.len() {
243 return None;
244 }
245 match &site.closure_args[param_idx].known_fn {
246 Some(fn_name) => {
247 if let Some(ref existing) = known_fn {
248 if existing != fn_name {
249 return None;
250 }
251 } else {
252 known_fn = Some(fn_name.clone());
253 }
254 }
255 None => return None,
256 }
257 }
258 known_fn
259}
260pub(super) fn is_called_as_function(expr: &LcnfExpr, param_id: LcnfVarId) -> bool {
262 match expr {
263 LcnfExpr::Let { value, body, .. } => {
264 let called_here = matches!(
265 value, LcnfLetValue::App(LcnfArg::Var(v), _) if * v == param_id
266 );
267 called_here || is_called_as_function(body, param_id)
268 }
269 LcnfExpr::Case { alts, default, .. } => {
270 alts.iter()
271 .any(|a| is_called_as_function(&a.body, param_id))
272 || default
273 .as_ref()
274 .is_some_and(|d| is_called_as_function(d, param_id))
275 }
276 LcnfExpr::TailCall(LcnfArg::Var(v), _) => *v == param_id,
277 _ => false,
278 }
279}
280pub fn specialize_module(module: &mut LcnfModule, config: &SpecializationConfig) {
282 let mut pass = SpecializationPass::new(config.clone());
283 pass.run(module);
284}
285pub fn specialize_numeric(decl: &LcnfFunDecl) -> Option<LcnfFunDecl> {
287 let specializer = NumericSpecializer::new();
288 if !specializer.is_numeric_op(&decl.name) {
289 return None;
290 }
291 let mut spec = decl.clone();
292 spec.name = format!("{}_u64", decl.name);
293 for param in &mut spec.params {
294 param.ty = specializer.specialize_nat_to_u64(¶m.ty);
295 }
296 spec.ret_type = specializer.specialize_nat_to_u64(&spec.ret_type);
297 Some(spec)
298}
299pub fn is_worth_specializing(decl: &LcnfFunDecl, config: &SpecializationConfig) -> bool {
301 let size = count_instructions(&decl.body);
302 if size > config.size_threshold {
303 return false;
304 }
305 let has_poly = decl
306 .params
307 .iter()
308 .any(|p| matches!(p.ty, LcnfType::Var(_) | LcnfType::Object | LcnfType::Erased));
309 let has_fn_param = decl
310 .params
311 .iter()
312 .any(|p| matches!(p.ty, LcnfType::Fun(_, _)));
313 has_poly || (has_fn_param && config.specialize_closures)
314}
315#[cfg(test)]
316mod tests {
317 use super::*;
318 pub(super) fn make_var(n: u64) -> LcnfVarId {
319 LcnfVarId(n)
320 }
321 pub(super) fn make_param(n: u64, name: &str, ty: LcnfType) -> LcnfParam {
322 LcnfParam {
323 id: LcnfVarId(n),
324 name: name.to_string(),
325 ty,
326 erased: false,
327 borrowed: false,
328 }
329 }
330 pub(super) fn make_simple_let(id: u64, value: LcnfLetValue, body: LcnfExpr) -> LcnfExpr {
331 LcnfExpr::Let {
332 id: LcnfVarId(id),
333 name: format!("x{}", id),
334 ty: LcnfType::Nat,
335 value,
336 body: Box::new(body),
337 }
338 }
339 pub(super) fn make_decl(name: &str, params: Vec<LcnfParam>, body: LcnfExpr) -> LcnfFunDecl {
340 LcnfFunDecl {
341 name: name.to_string(),
342 original_name: None,
343 params,
344 ret_type: LcnfType::Nat,
345 body,
346 is_recursive: false,
347 is_lifted: false,
348 inline_cost: 1,
349 }
350 }
351 #[test]
352 pub(super) fn test_config_default() {
353 let config = SpecializationConfig::default();
354 assert_eq!(config.max_specializations, 8);
355 assert!(config.specialize_closures);
356 assert!(config.specialize_numerics);
357 assert_eq!(config.size_threshold, 200);
358 }
359 #[test]
360 pub(super) fn test_spec_key_trivial() {
361 let key = SpecializationKey {
362 original: "foo".to_string(),
363 type_args: vec![SpecTypeArg::Poly],
364 const_args: vec![SpecConstArg::Unknown],
365 closure_args: vec![SpecClosureArg {
366 known_fn: None,
367 param_idx: 0,
368 }],
369 };
370 assert!(key.is_trivial());
371 }
372 #[test]
373 pub(super) fn test_spec_key_non_trivial_type() {
374 let key = SpecializationKey {
375 original: "foo".to_string(),
376 type_args: vec![SpecTypeArg::Concrete(LcnfType::Nat)],
377 const_args: vec![],
378 closure_args: vec![],
379 };
380 assert!(!key.is_trivial());
381 }
382 #[test]
383 pub(super) fn test_spec_key_non_trivial_const() {
384 let key = SpecializationKey {
385 original: "foo".to_string(),
386 type_args: vec![],
387 const_args: vec![SpecConstArg::Nat(42)],
388 closure_args: vec![],
389 };
390 assert!(!key.is_trivial());
391 }
392 #[test]
393 pub(super) fn test_spec_key_mangled_name() {
394 let key = SpecializationKey {
395 original: "List.map".to_string(),
396 type_args: vec![SpecTypeArg::Concrete(LcnfType::Nat)],
397 const_args: vec![SpecConstArg::Unknown],
398 closure_args: vec![],
399 };
400 let name = key.mangled_name();
401 assert!(name.starts_with("List.map"));
402 assert!(name.contains("_T0_nat"));
403 }
404 #[test]
405 pub(super) fn test_spec_key_mangled_name_with_const() {
406 let key = SpecializationKey {
407 original: "repeat".to_string(),
408 type_args: vec![],
409 const_args: vec![SpecConstArg::Nat(3)],
410 closure_args: vec![],
411 };
412 let name = key.mangled_name();
413 assert!(name.contains("_C0_N3"));
414 }
415 #[test]
416 pub(super) fn test_spec_key_mangled_name_with_closure() {
417 let key = SpecializationKey {
418 original: "List.map".to_string(),
419 type_args: vec![],
420 const_args: vec![],
421 closure_args: vec![SpecClosureArg {
422 known_fn: Some("double".to_string()),
423 param_idx: 0,
424 }],
425 };
426 let name = key.mangled_name();
427 assert!(name.contains("_Fdouble"));
428 }
429 #[test]
430 pub(super) fn test_type_suffix() {
431 assert_eq!(type_suffix(&LcnfType::Nat), "nat");
432 assert_eq!(type_suffix(&LcnfType::Object), "obj");
433 assert_eq!(type_suffix(&LcnfType::Unit), "unit");
434 assert_eq!(type_suffix(&LcnfType::LcnfString), "str");
435 }
436 #[test]
437 pub(super) fn test_cache_operations() {
438 let mut cache = SpecializationCache::new();
439 let key = SpecializationKey {
440 original: "foo".to_string(),
441 type_args: vec![SpecTypeArg::Concrete(LcnfType::Nat)],
442 const_args: vec![],
443 closure_args: vec![],
444 };
445 assert!(cache.lookup(&key).is_none());
446 cache.insert(key.clone(), "foo_nat".to_string(), 10);
447 assert_eq!(cache.lookup(&key), Some("foo_nat"));
448 assert_eq!(cache.specialization_count("foo"), 1);
449 assert_eq!(cache.total_growth, 10);
450 }
451 #[test]
452 pub(super) fn test_size_budget() {
453 let mut budget = SizeBudget::new(100, 2.0);
454 assert!(budget.can_afford(50));
455 assert!(budget.can_afford(100));
456 assert!(!budget.can_afford(101));
457 budget.spend(50);
458 assert!(budget.can_afford(50));
459 assert!(!budget.can_afford(51));
460 assert_eq!(budget.remaining(), 50);
461 }
462 #[test]
463 pub(super) fn test_numeric_specializer() {
464 let specializer = NumericSpecializer::new();
465 assert!(specializer.is_numeric_op("Nat.add"));
466 assert!(specializer.is_numeric_op("Nat.mul"));
467 assert!(!specializer.is_numeric_op("List.map"));
468 }
469 #[test]
470 pub(super) fn test_numeric_type_specialization() {
471 let specializer = NumericSpecializer::new();
472 let ty = LcnfType::Fun(vec![LcnfType::Nat, LcnfType::Nat], Box::new(LcnfType::Nat));
473 let spec = specializer.specialize_nat_to_u64(&ty);
474 assert_eq!(
475 spec,
476 LcnfType::Fun(vec![LcnfType::Nat, LcnfType::Nat], Box::new(LcnfType::Nat))
477 );
478 }
479 #[test]
480 pub(super) fn test_specialize_numeric() {
481 let body = LcnfExpr::Return(LcnfArg::Var(make_var(0)));
482 let decl = make_decl(
483 "Nat.add",
484 vec![
485 make_param(0, "a", LcnfType::Nat),
486 make_param(1, "b", LcnfType::Nat),
487 ],
488 body,
489 );
490 let result = specialize_numeric(&decl);
491 assert!(result.is_some());
492 let spec = result.expect("spec should be Some/Ok");
493 assert_eq!(spec.name, "Nat.add_u64");
494 }
495 #[test]
496 pub(super) fn test_specialize_numeric_non_numeric() {
497 let body = LcnfExpr::Return(LcnfArg::Var(make_var(0)));
498 let decl = make_decl("List.map", vec![make_param(0, "f", LcnfType::Object)], body);
499 let result = specialize_numeric(&decl);
500 assert!(result.is_none());
501 }
502 #[test]
503 pub(super) fn test_is_worth_specializing_polymorphic() {
504 let body = LcnfExpr::Return(LcnfArg::Var(make_var(0)));
505 let decl = make_decl(
506 "id",
507 vec![make_param(0, "x", LcnfType::Var("a".to_string()))],
508 body,
509 );
510 let config = SpecializationConfig::default();
511 assert!(is_worth_specializing(&decl, &config));
512 }
513 #[test]
514 pub(super) fn test_is_worth_specializing_concrete() {
515 let body = LcnfExpr::Return(LcnfArg::Var(make_var(0)));
516 let decl = make_decl(
517 "add",
518 vec![
519 make_param(0, "a", LcnfType::Nat),
520 make_param(1, "b", LcnfType::Nat),
521 ],
522 body,
523 );
524 let config = SpecializationConfig::default();
525 assert!(!is_worth_specializing(&decl, &config));
526 }
527 #[test]
528 pub(super) fn test_is_worth_specializing_higher_order() {
529 let body = LcnfExpr::Return(LcnfArg::Var(make_var(0)));
530 let fn_ty = LcnfType::Fun(vec![LcnfType::Nat], Box::new(LcnfType::Nat));
531 let decl = make_decl("apply", vec![make_param(0, "f", fn_ty)], body);
532 let config = SpecializationConfig::default();
533 assert!(is_worth_specializing(&decl, &config));
534 }
535 #[test]
536 pub(super) fn test_is_called_as_function() {
537 let body = make_simple_let(
538 5,
539 LcnfLetValue::App(
540 LcnfArg::Var(make_var(0)),
541 vec![LcnfArg::Lit(LcnfLit::Nat(1))],
542 ),
543 LcnfExpr::Return(LcnfArg::Var(make_var(5))),
544 );
545 assert!(is_called_as_function(&body, make_var(0)));
546 assert!(!is_called_as_function(&body, make_var(1)));
547 }
548 #[test]
549 pub(super) fn test_count_instructions() {
550 let expr = make_simple_let(
551 1,
552 LcnfLetValue::Lit(LcnfLit::Nat(42)),
553 make_simple_let(
554 2,
555 LcnfLetValue::Lit(LcnfLit::Nat(10)),
556 LcnfExpr::Return(LcnfArg::Var(make_var(2))),
557 ),
558 );
559 assert_eq!(count_instructions(&expr), 3);
560 }
561 #[test]
562 pub(super) fn test_substitute_constant() {
563 let mut expr = make_simple_let(
564 1,
565 LcnfLetValue::FVar(make_var(0)),
566 LcnfExpr::Return(LcnfArg::Var(make_var(1))),
567 );
568 let pass = SpecializationPass::new(SpecializationConfig::default());
569 pass.substitute_constant(&mut expr, make_var(0), &LcnfLit::Nat(42));
570 if let LcnfExpr::Let { value, .. } = &expr {
571 assert_eq!(*value, LcnfLetValue::Lit(LcnfLit::Nat(42)));
572 } else {
573 panic!("Expected Let");
574 }
575 }
576 #[test]
577 pub(super) fn test_substitute_constant_in_return() {
578 let mut expr = LcnfExpr::Return(LcnfArg::Var(make_var(0)));
579 let pass = SpecializationPass::new(SpecializationConfig::default());
580 pass.substitute_constant(&mut expr, make_var(0), &LcnfLit::Nat(99));
581 assert_eq!(expr, LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(99))));
582 }
583 #[test]
584 pub(super) fn test_specialize_module_empty() {
585 let mut module = LcnfModule::default();
586 let config = SpecializationConfig::default();
587 specialize_module(&mut module, &config);
588 assert!(module.fun_decls.is_empty());
589 }
590 #[test]
591 pub(super) fn test_specialize_module_simple() {
592 let body = LcnfExpr::Return(LcnfArg::Var(make_var(0)));
593 let decl = make_decl(
594 "id",
595 vec![make_param(0, "x", LcnfType::Var("a".to_string()))],
596 body,
597 );
598 let mut module = LcnfModule {
599 fun_decls: vec![decl],
600 extern_decls: vec![],
601 name: "test".to_string(),
602 metadata: LcnfModuleMetadata::default(),
603 };
604 let config = SpecializationConfig::default();
605 specialize_module(&mut module, &config);
606 assert!(!module.fun_decls.is_empty());
607 }
608 #[test]
609 pub(super) fn test_closure_uniformity_analysis() {
610 let body = LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(0)));
611 let decl = make_decl("apply", vec![make_param(0, "f", LcnfType::Object)], body);
612 let sites = vec![
613 SpecCallSite {
614 callee: "apply".to_string(),
615 call_idx: 0,
616 type_args: vec![],
617 const_args: vec![],
618 closure_args: vec![SpecClosureArg {
619 known_fn: Some("double".to_string()),
620 param_idx: 0,
621 }],
622 callee_var: None,
623 },
624 SpecCallSite {
625 callee: "apply".to_string(),
626 call_idx: 1,
627 type_args: vec![],
628 const_args: vec![],
629 closure_args: vec![SpecClosureArg {
630 known_fn: Some("double".to_string()),
631 param_idx: 0,
632 }],
633 callee_var: None,
634 },
635 ];
636 let result = analyze_closure_uniformity(&decl, 0, &sites);
637 assert_eq!(result, Some("double".to_string()));
638 }
639 #[test]
640 pub(super) fn test_closure_uniformity_non_uniform() {
641 let body = LcnfExpr::Return(LcnfArg::Lit(LcnfLit::Nat(0)));
642 let decl = make_decl("apply", vec![make_param(0, "f", LcnfType::Object)], body);
643 let sites = vec![
644 SpecCallSite {
645 callee: "apply".to_string(),
646 call_idx: 0,
647 type_args: vec![],
648 const_args: vec![],
649 closure_args: vec![SpecClosureArg {
650 known_fn: Some("double".to_string()),
651 param_idx: 0,
652 }],
653 callee_var: None,
654 },
655 SpecCallSite {
656 callee: "apply".to_string(),
657 call_idx: 1,
658 type_args: vec![],
659 const_args: vec![],
660 closure_args: vec![SpecClosureArg {
661 known_fn: Some("triple".to_string()),
662 param_idx: 0,
663 }],
664 callee_var: None,
665 },
666 ];
667 let result = analyze_closure_uniformity(&decl, 0, &sites);
668 assert!(result.is_none());
669 }
670 #[test]
671 pub(super) fn test_find_specialization_sites() {
672 let body = make_simple_let(
673 1,
674 LcnfLetValue::App(
675 LcnfArg::Var(make_var(10)),
676 vec![LcnfArg::Lit(LcnfLit::Nat(42))],
677 ),
678 LcnfExpr::Return(LcnfArg::Var(make_var(1))),
679 );
680 let known_consts: HashMap<LcnfVarId, LcnfLit> = HashMap::new();
681 let mut known_fns: HashMap<LcnfVarId, String> = HashMap::new();
682 known_fns.insert(make_var(10), "target_fn".to_string());
683 let mut decl_names = HashSet::new();
684 decl_names.insert("target_fn".to_string());
685 let sites = find_specialization_sites(&body, &known_consts, &known_fns, &decl_names);
686 assert_eq!(sites.len(), 1);
687 assert_eq!(sites[0].callee, "target_fn");
688 assert!(matches!(sites[0].const_args[0], SpecConstArg::Nat(42)));
689 }
690 #[test]
691 pub(super) fn test_create_specialization() {
692 let body = LcnfExpr::Return(LcnfArg::Var(make_var(0)));
693 let decl = make_decl(
694 "my_fn",
695 vec![
696 make_param(0, "x", LcnfType::Nat),
697 make_param(1, "y", LcnfType::Nat),
698 ],
699 body,
700 );
701 let key = SpecializationKey {
702 original: "my_fn".to_string(),
703 type_args: vec![],
704 const_args: vec![SpecConstArg::Nat(10), SpecConstArg::Unknown],
705 closure_args: vec![],
706 };
707 let mut pass = SpecializationPass::new(SpecializationConfig::default());
708 let result = pass.create_specialization(&decl, &key);
709 assert!(result.is_some());
710 let spec = result.expect("spec should be Some/Ok");
711 assert!(spec.decl.name.contains("my_fn"));
712 assert!(spec.decl.name.contains("_C0_N10"));
713 assert_eq!(spec.decl.params.len(), 1);
714 }
715 #[test]
716 pub(super) fn test_stats_default() {
717 let stats = SpecializationStats::default();
718 assert_eq!(stats.type_specializations, 0);
719 assert_eq!(stats.const_specializations, 0);
720 assert_eq!(stats.closure_specializations, 0);
721 }
722 #[test]
723 pub(super) fn test_pass_fresh_id() {
724 let mut pass = SpecializationPass::new(SpecializationConfig::default());
725 let id1 = pass.fresh_id();
726 let id2 = pass.fresh_id();
727 assert_ne!(id1, id2);
728 }
729 #[test]
730 pub(super) fn test_substitute_in_tailcall() {
731 let mut expr = LcnfExpr::TailCall(
732 LcnfArg::Var(make_var(10)),
733 vec![LcnfArg::Var(make_var(0)), LcnfArg::Var(make_var(1))],
734 );
735 let pass = SpecializationPass::new(SpecializationConfig::default());
736 pass.substitute_constant(&mut expr, make_var(0), &LcnfLit::Nat(7));
737 if let LcnfExpr::TailCall(_, args) = &expr {
738 assert_eq!(args[0], LcnfArg::Lit(LcnfLit::Nat(7)));
739 assert_eq!(args[1], LcnfArg::Var(make_var(1)));
740 } else {
741 panic!("Expected TailCall");
742 }
743 }
744 #[test]
745 pub(super) fn test_is_called_in_case() {
746 let body = LcnfExpr::Case {
747 scrutinee: make_var(1),
748 scrutinee_ty: LcnfType::Nat,
749 alts: vec![LcnfAlt {
750 ctor_name: "True".to_string(),
751 ctor_tag: 0,
752 params: vec![],
753 body: make_simple_let(
754 5,
755 LcnfLetValue::App(
756 LcnfArg::Var(make_var(0)),
757 vec![LcnfArg::Lit(LcnfLit::Nat(1))],
758 ),
759 LcnfExpr::Return(LcnfArg::Var(make_var(5))),
760 ),
761 }],
762 default: None,
763 };
764 assert!(is_called_as_function(&body, make_var(0)));
765 assert!(!is_called_as_function(&body, make_var(2)));
766 }
767 #[test]
768 pub(super) fn test_tailcall_specialization_site() {
769 let expr = LcnfExpr::TailCall(
770 LcnfArg::Var(make_var(10)),
771 vec![LcnfArg::Lit(LcnfLit::Nat(5))],
772 );
773 let mut known_fns: HashMap<LcnfVarId, String> = HashMap::new();
774 known_fns.insert(make_var(10), "recurse".to_string());
775 let mut decl_names = HashSet::new();
776 decl_names.insert("recurse".to_string());
777 let known_consts: HashMap<LcnfVarId, LcnfLit> = HashMap::new();
778 let sites = find_specialization_sites(&expr, &known_consts, &known_fns, &decl_names);
779 assert_eq!(sites.len(), 1);
780 assert_eq!(sites[0].callee, "recurse");
781 }
782 #[test]
783 pub(super) fn test_recursive_specialization_disabled() {
784 let body = LcnfExpr::Return(LcnfArg::Var(make_var(0)));
785 let mut decl = make_decl("rec_fn", vec![make_param(0, "n", LcnfType::Nat)], body);
786 decl.is_recursive = true;
787 let key = SpecializationKey {
788 original: "rec_fn".to_string(),
789 type_args: vec![],
790 const_args: vec![SpecConstArg::Nat(5)],
791 closure_args: vec![],
792 };
793 let mut pass = SpecializationPass::new(SpecializationConfig {
794 allow_recursive: false,
795 ..SpecializationConfig::default()
796 });
797 let result = pass.create_specialization(&decl, &key);
798 assert!(result.is_none());
799 }
800}
801#[cfg(test)]
802mod Spec_infra_tests {
803 use super::*;
804 #[test]
805 pub(super) fn test_pass_config() {
806 let config = SpecPassConfig::new("test_pass", SpecPassPhase::Transformation);
807 assert!(config.enabled);
808 assert!(config.phase.is_modifying());
809 assert_eq!(config.phase.name(), "transformation");
810 }
811 #[test]
812 pub(super) fn test_pass_stats() {
813 let mut stats = SpecPassStats::new();
814 stats.record_run(10, 100, 3);
815 stats.record_run(20, 200, 5);
816 assert_eq!(stats.total_runs, 2);
817 assert!((stats.average_changes_per_run() - 15.0).abs() < 0.01);
818 assert!((stats.success_rate() - 1.0).abs() < 0.01);
819 let s = stats.format_summary();
820 assert!(s.contains("Runs: 2/2"));
821 }
822 #[test]
823 pub(super) fn test_pass_registry() {
824 let mut reg = SpecPassRegistry::new();
825 reg.register(SpecPassConfig::new("pass_a", SpecPassPhase::Analysis));
826 reg.register(SpecPassConfig::new("pass_b", SpecPassPhase::Transformation).disabled());
827 assert_eq!(reg.total_passes(), 2);
828 assert_eq!(reg.enabled_count(), 1);
829 reg.update_stats("pass_a", 5, 50, 2);
830 let stats = reg.get_stats("pass_a").expect("stats should exist");
831 assert_eq!(stats.total_changes, 5);
832 }
833 #[test]
834 pub(super) fn test_analysis_cache() {
835 let mut cache = SpecAnalysisCache::new(10);
836 cache.insert("key1".to_string(), vec![1, 2, 3]);
837 assert!(cache.get("key1").is_some());
838 assert!(cache.get("key2").is_none());
839 assert!((cache.hit_rate() - 0.5).abs() < 0.01);
840 cache.invalidate("key1");
841 assert!(!cache.entries["key1"].valid);
842 assert_eq!(cache.size(), 1);
843 }
844 #[test]
845 pub(super) fn test_worklist() {
846 let mut wl = SpecWorklist::new();
847 assert!(wl.push(1));
848 assert!(wl.push(2));
849 assert!(!wl.push(1));
850 assert_eq!(wl.len(), 2);
851 assert_eq!(wl.pop(), Some(1));
852 assert!(!wl.contains(1));
853 assert!(wl.contains(2));
854 }
855 #[test]
856 pub(super) fn test_dominator_tree() {
857 let mut dt = SpecDominatorTree::new(5);
858 dt.set_idom(1, 0);
859 dt.set_idom(2, 0);
860 dt.set_idom(3, 1);
861 assert!(dt.dominates(0, 3));
862 assert!(dt.dominates(1, 3));
863 assert!(!dt.dominates(2, 3));
864 assert!(dt.dominates(3, 3));
865 }
866 #[test]
867 pub(super) fn test_liveness() {
868 let mut liveness = SpecLivenessInfo::new(3);
869 liveness.add_def(0, 1);
870 liveness.add_use(1, 1);
871 assert!(liveness.defs[0].contains(&1));
872 assert!(liveness.uses[1].contains(&1));
873 }
874 #[test]
875 pub(super) fn test_constant_folding() {
876 assert_eq!(SpecConstantFoldingHelper::fold_add_i64(3, 4), Some(7));
877 assert_eq!(SpecConstantFoldingHelper::fold_div_i64(10, 0), None);
878 assert_eq!(SpecConstantFoldingHelper::fold_div_i64(10, 2), Some(5));
879 assert_eq!(
880 SpecConstantFoldingHelper::fold_bitand_i64(0b1100, 0b1010),
881 0b1000
882 );
883 assert_eq!(SpecConstantFoldingHelper::fold_bitnot_i64(0), -1);
884 }
885 #[test]
886 pub(super) fn test_dep_graph() {
887 let mut g = SpecDepGraph::new();
888 g.add_dep(1, 2);
889 g.add_dep(2, 3);
890 g.add_dep(1, 3);
891 assert_eq!(g.dependencies_of(2), vec![1]);
892 let topo = g.topological_sort();
893 assert_eq!(topo.len(), 3);
894 assert!(!g.has_cycle());
895 let pos: std::collections::HashMap<u32, usize> =
896 topo.iter().enumerate().map(|(i, &n)| (n, i)).collect();
897 assert!(pos[&1] < pos[&2]);
898 assert!(pos[&1] < pos[&3]);
899 assert!(pos[&2] < pos[&3]);
900 }
901}
902#[cfg(test)]
903mod specext_pass_tests {
904 use super::*;
905 #[test]
906 pub(super) fn test_specext_phase_order() {
907 assert_eq!(SpecExtPassPhase::Early.order(), 0);
908 assert_eq!(SpecExtPassPhase::Middle.order(), 1);
909 assert_eq!(SpecExtPassPhase::Late.order(), 2);
910 assert_eq!(SpecExtPassPhase::Finalize.order(), 3);
911 assert!(SpecExtPassPhase::Early.is_early());
912 assert!(!SpecExtPassPhase::Early.is_late());
913 }
914 #[test]
915 pub(super) fn test_specext_config_builder() {
916 let c = SpecExtPassConfig::new("p")
917 .with_phase(SpecExtPassPhase::Late)
918 .with_max_iter(50)
919 .with_debug(1);
920 assert_eq!(c.name, "p");
921 assert_eq!(c.max_iterations, 50);
922 assert!(c.is_debug_enabled());
923 assert!(c.enabled);
924 let c2 = c.disabled();
925 assert!(!c2.enabled);
926 }
927 #[test]
928 pub(super) fn test_specext_stats() {
929 let mut s = SpecExtPassStats::new();
930 s.visit();
931 s.visit();
932 s.modify();
933 s.iterate();
934 assert_eq!(s.nodes_visited, 2);
935 assert_eq!(s.nodes_modified, 1);
936 assert!(s.changed);
937 assert_eq!(s.iterations, 1);
938 let e = s.efficiency();
939 assert!((e - 0.5).abs() < 1e-9);
940 }
941 #[test]
942 pub(super) fn test_specext_registry() {
943 let mut r = SpecExtPassRegistry::new();
944 r.register(SpecExtPassConfig::new("a").with_phase(SpecExtPassPhase::Early));
945 r.register(SpecExtPassConfig::new("b").disabled());
946 assert_eq!(r.len(), 2);
947 assert_eq!(r.enabled_passes().len(), 1);
948 assert_eq!(r.passes_in_phase(&SpecExtPassPhase::Early).len(), 1);
949 }
950 #[test]
951 pub(super) fn test_specext_cache() {
952 let mut c = SpecExtCache::new(4);
953 assert!(c.get(99).is_none());
954 c.put(99, vec![1, 2, 3]);
955 let v = c.get(99).expect("v should be present in map");
956 assert_eq!(v, &[1u8, 2, 3]);
957 assert!(c.hit_rate() > 0.0);
958 assert_eq!(c.live_count(), 1);
959 }
960 #[test]
961 pub(super) fn test_specext_worklist() {
962 let mut w = SpecExtWorklist::new(10);
963 w.push(5);
964 w.push(3);
965 w.push(5);
966 assert_eq!(w.len(), 2);
967 assert!(w.contains(5));
968 let first = w.pop().expect("first should be available to pop");
969 assert!(!w.contains(first));
970 }
971 #[test]
972 pub(super) fn test_specext_dom_tree() {
973 let mut dt = SpecExtDomTree::new(5);
974 dt.set_idom(1, 0);
975 dt.set_idom(2, 0);
976 dt.set_idom(3, 1);
977 dt.set_idom(4, 1);
978 assert!(dt.dominates(0, 3));
979 assert!(dt.dominates(1, 4));
980 assert!(!dt.dominates(2, 3));
981 assert_eq!(dt.depth_of(3), 2);
982 }
983 #[test]
984 pub(super) fn test_specext_liveness() {
985 let mut lv = SpecExtLiveness::new(3);
986 lv.add_def(0, 1);
987 lv.add_use(1, 1);
988 assert!(lv.var_is_def_in_block(0, 1));
989 assert!(lv.var_is_used_in_block(1, 1));
990 assert!(!lv.var_is_def_in_block(1, 1));
991 }
992 #[test]
993 pub(super) fn test_specext_const_folder() {
994 let mut cf = SpecExtConstFolder::new();
995 assert_eq!(cf.add_i64(3, 4), Some(7));
996 assert_eq!(cf.div_i64(10, 0), None);
997 assert_eq!(cf.mul_i64(6, 7), Some(42));
998 assert_eq!(cf.and_i64(0b1100, 0b1010), 0b1000);
999 assert_eq!(cf.fold_count(), 3);
1000 assert_eq!(cf.failure_count(), 1);
1001 }
1002 #[test]
1003 pub(super) fn test_specext_dep_graph() {
1004 let mut g = SpecExtDepGraph::new(4);
1005 g.add_edge(0, 1);
1006 g.add_edge(1, 2);
1007 g.add_edge(2, 3);
1008 assert!(!g.has_cycle());
1009 assert_eq!(g.topo_sort(), Some(vec![0, 1, 2, 3]));
1010 assert_eq!(g.reachable(0).len(), 4);
1011 let sccs = g.scc();
1012 assert_eq!(sccs.len(), 4);
1013 }
1014}