1use super::types::{
6 AllocationProfile, AllocationSiteProfile, AutoFdoConfig, BoltInstrumentationConfig,
7 BranchPredictability, CallGraph, ContextSensitiveProfile, CoverageReport, DevirtualizationPass,
8 FunctionProfile, GlobalInstrumentationRegistry, HotColdSplit, InlineHeuristic, InlineHint,
9 InstrumentationCounters, InstrumentationPass, LoopIterationProfile, MemoryAccessPattern,
10 MergeWeightMode, OptAction, PgoAnnotatedFunction, PgoConfig, PgoDataFormat, PgoDecision,
11 PgoOptimizationLog, PgoPass, PgoStatisticsReport, PgoWorkflow, ProfileData, ProfileMerger,
12 ProfileSummary, PropellerEdge, PropellerFunctionInfo, PropellerProfile, RawProfileData,
13 SampleBasedProfileGenerator, SampleRecord, ThinLtoPgoData, VirtualCallRecord,
14 WholeProgramDevirt,
15};
16
17#[cfg(test)]
18mod tests {
19 use super::*;
20 #[test]
21 pub(super) fn test_record_call_increments_count() {
22 let mut data = ProfileData::new();
23 data.record_call("foo");
24 data.record_call("foo");
25 data.record_call("bar");
26 assert_eq!(data.call_counts["foo"], 2);
27 assert_eq!(data.call_counts["bar"], 1);
28 }
29 #[test]
30 pub(super) fn test_record_edge_increments_weight() {
31 let mut data = ProfileData::new();
32 data.record_edge("main", "foo");
33 data.record_edge("main", "foo");
34 data.record_edge("foo", "bar");
35 assert_eq!(data.edge_counts[&("main".to_owned(), "foo".to_owned())], 2);
36 assert_eq!(data.edge_counts[&("foo".to_owned(), "bar".to_owned())], 1);
37 }
38 #[test]
39 pub(super) fn test_mark_hot_and_is_hot() {
40 let mut data = ProfileData::new();
41 for _ in 0..200 {
42 data.record_call("hot_fn");
43 }
44 for _ in 0..50 {
45 data.record_call("cold_fn");
46 }
47 data.mark_hot(100);
48 assert!(data.is_hot("hot_fn"));
49 assert!(!data.is_hot("cold_fn"));
50 }
51 #[test]
52 pub(super) fn test_top_k_functions_ordering() {
53 let mut data = ProfileData::new();
54 data.record_call("a");
55 for _ in 0..5 {
56 data.record_call("b");
57 }
58 for _ in 0..3 {
59 data.record_call("c");
60 }
61 let top2 = data.top_k_functions(2);
62 assert_eq!(top2.len(), 2);
63 assert_eq!(top2[0].0, "b");
64 assert_eq!(top2[1].0, "c");
65 }
66 #[test]
67 pub(super) fn test_should_inline_hot_small() {
68 let config = PgoConfig {
69 hot_threshold: 10,
70 inline_hot: true,
71 max_inline_size: 40,
72 ..PgoConfig::default()
73 };
74 let mut pass = PgoPass::new(config);
75 let mut data = ProfileData::new();
76 for _ in 0..20 {
77 data.record_call("fast");
78 }
79 data.mark_hot(10);
80 pass.load_profile(data);
81 assert!(pass.should_inline("fast", 30));
82 assert!(!pass.should_inline("fast", 50));
83 assert!(!pass.should_inline("unknown", 10));
84 }
85 #[test]
86 pub(super) fn test_should_specialize() {
87 let config = PgoConfig {
88 hot_threshold: 5,
89 specialize_hot: true,
90 ..PgoConfig::default()
91 };
92 let mut pass = PgoPass::new(config);
93 let mut data = ProfileData::new();
94 for _ in 0..10 {
95 data.record_call("poly_fn");
96 }
97 data.mark_hot(5);
98 pass.load_profile(data);
99 assert!(pass.should_specialize("poly_fn"));
100 assert!(!pass.should_specialize("rare_fn"));
101 }
102 #[test]
103 pub(super) fn test_optimize_call_sites_produces_correct_actions() {
104 let config = PgoConfig {
105 hot_threshold: 10,
106 inline_hot: true,
107 specialize_hot: true,
108 max_inline_size: 30,
109 };
110 let mut pass = PgoPass::new(config);
111 let mut data = ProfileData::new();
112 for _ in 0..50 {
113 data.record_call("inline_me");
114 }
115 for _ in 0..50 {
116 data.record_call("specialize_me");
117 }
118 data.mark_hot(10);
119 pass.load_profile(data);
120 let sites = vec![
121 ("inline_me".to_owned(), 20_usize),
122 ("specialize_me".to_owned(), 60_usize),
123 ("cold_fn".to_owned(), 10_usize),
124 ];
125 let actions = pass.optimize_call_sites(&sites);
126 assert_eq!(actions.len(), 3);
127 assert!(
128 matches!(& actions[0], OptAction::Inline { callee, .. } if callee ==
129 "inline_me")
130 );
131 assert!(
132 matches!(& actions[1], OptAction::Specialize { func, call_site : 1 } if func
133 == "specialize_me")
134 );
135 assert_eq!(actions[2], OptAction::Noop);
136 }
137 #[test]
138 pub(super) fn test_instrument_function_contains_name() {
139 let pass = InstrumentationPass::new();
140 let stub = pass.instrument_function("my_func");
141 assert!(stub.contains("my_func"));
142 assert!(stub.contains("__pgo_counter_increment"));
143 }
144 #[test]
145 pub(super) fn test_generate_profile_report_content() {
146 let pass = InstrumentationPass::new();
147 let mut data = ProfileData::new();
148 for _ in 0..200 {
149 data.record_call("render");
150 }
151 data.record_edge("main", "render");
152 data.mark_hot(100);
153 let report = pass.generate_profile_report(&data);
154 assert!(report.contains("render"));
155 assert!(report.contains("200 calls"));
156 assert!(report.contains("Total call-graph edges: 1"));
157 }
158}
159#[cfg(test)]
160mod pgo_extended_tests {
161 use super::*;
162 #[test]
163 pub(super) fn test_function_profile() {
164 let mut p = FunctionProfile::new("main");
165 p.total_calls = 1000;
166 p.add_block(0, 1000, 500);
167 p.add_block(1, 100, 500);
168 assert_eq!(p.hot_blocks().len(), 1);
169 assert_eq!(p.total_block_executions(), 1100);
170 assert!(p.is_hot_function(999));
171 assert!(!p.is_hot_function(1001));
172 }
173 #[test]
174 pub(super) fn test_inline_heuristic() {
175 let h = InlineHeuristic::aggressive();
176 assert!(h.should_inline(100, 100, 5));
177 assert!(!h.should_inline(5, 100, 5));
178 assert!(!h.should_inline(100, 600, 5));
179 assert!(!h.should_inline(100, 100, 11));
180 }
181 #[test]
182 pub(super) fn test_branch_predictability() {
183 let bp = BranchPredictability::from_frequency(98.0, 100.0);
184 assert!(matches!(bp, BranchPredictability::AlwaysTaken));
185 let hint = bp.emit_hint();
186 assert_eq!(hint, Some("[[likely]]"));
187 let bp2 = BranchPredictability::from_frequency(1.0, 100.0);
188 assert!(matches!(bp2, BranchPredictability::AlwaysNotTaken));
189 let bp3 = BranchPredictability::from_frequency(50.0, 100.0);
190 assert!(matches!(bp3, BranchPredictability::Unpredictable));
191 assert!(!bp3.is_biased());
192 }
193 #[test]
194 pub(super) fn test_call_graph() {
195 let mut cg = CallGraph::new();
196 cg.add_edge("main", "foo", 100);
197 cg.add_edge("main", "bar", 50);
198 cg.add_edge("foo", "bar", 200);
199 assert_eq!(cg.callers_of("bar").len(), 2);
200 assert_eq!(cg.callees_of("main").len(), 2);
201 assert_eq!(cg.total_call_count(), 350);
202 assert_eq!(cg.hot_call_sites(100).len(), 2);
203 }
204 #[test]
205 pub(super) fn test_hot_cold_split() {
206 let mut split = HotColdSplit::new(80.0);
207 let profiles = vec![
208 FunctionProfile {
209 name: "hot".to_string(),
210 total_calls: 900,
211 blocks: vec![],
212 edges: vec![],
213 average_call_depth: 0.0,
214 },
215 FunctionProfile {
216 name: "cold".to_string(),
217 total_calls: 100,
218 blocks: vec![],
219 edges: vec![],
220 average_call_depth: 0.0,
221 },
222 ];
223 split.classify(&profiles);
224 assert!(split.hot_count() > 0 || split.cold_count() > 0);
225 }
226 #[test]
227 pub(super) fn test_coverage_report() {
228 let mut rep = CoverageReport::new();
229 rep.total_lines = 100;
230 rep.covered_lines = 80;
231 rep.total_branches = 40;
232 rep.covered_branches = 30;
233 rep.add_function("foo", true);
234 rep.add_function("bar", false);
235 assert!((rep.line_coverage_pct() - 80.0).abs() < 0.01);
236 assert!((rep.branch_coverage_pct() - 75.0).abs() < 0.01);
237 assert!((rep.function_coverage_pct() - 50.0).abs() < 0.01);
238 let summary = rep.summary();
239 assert!(summary.contains("Lines:"));
240 }
241 #[test]
242 pub(super) fn test_pgo_workflow() {
243 let wf = PgoWorkflow::new_instrumentation();
244 let flags = wf.emit_flags();
245 assert!(flags.iter().any(|f| f.contains("-fprofile-generate")));
246 let wf2 = PgoWorkflow::new_optimization("profile.profdata");
247 let flags2 = wf2.emit_flags();
248 assert!(flags2.iter().any(|f| f.contains("-fprofile-use")));
249 }
250 #[test]
251 pub(super) fn test_sample_record() {
252 let mut rec = SampleRecord::new("compute");
253 rec.head_samples = 100;
254 rec.body_samples = 500;
255 rec.add_callsite(42, "allocate", 50);
256 assert_eq!(rec.total_samples(), 600);
257 let text = rec.emit_prof_text();
258 assert!(text.contains("compute:100"));
259 assert!(text.contains("allocate"));
260 }
261 #[test]
262 pub(super) fn test_profile_summary() {
263 let profiles = vec![
264 FunctionProfile {
265 name: "a".to_string(),
266 total_calls: 1000,
267 blocks: vec![],
268 edges: vec![],
269 average_call_depth: 0.0,
270 },
271 FunctionProfile {
272 name: "b".to_string(),
273 total_calls: 200,
274 blocks: vec![],
275 edges: vec![],
276 average_call_depth: 0.0,
277 },
278 ];
279 let summary = ProfileSummary::compute_from_profiles(&profiles);
280 assert_eq!(summary.total_samples, 1200);
281 assert_eq!(summary.max_function_count, 1000);
282 assert!(!summary.is_empty());
283 }
284}
285#[cfg(test)]
286mod pgo_advanced_tests {
287 use super::*;
288 #[test]
289 pub(super) fn test_raw_profile_data() {
290 let mut raw = RawProfileData::new(1);
291 raw.add_counter(100);
292 raw.add_counter(200);
293 assert_eq!(raw.num_counters, 2);
294 assert_eq!(raw.total_count(), 300);
295 assert_eq!(raw.max_count(), 200);
296 let other = RawProfileData {
297 version: 1,
298 num_counters: 2,
299 data: vec![50, 50],
300 };
301 raw.merge(&other);
302 assert_eq!(raw.data, vec![150, 250]);
303 }
304 #[test]
305 pub(super) fn test_profile_merger() {
306 let mut merger = ProfileMerger::new(MergeWeightMode::Equal);
307 merger.add_profile(RawProfileData {
308 version: 1,
309 num_counters: 2,
310 data: vec![100, 200],
311 });
312 merger.add_profile(RawProfileData {
313 version: 1,
314 num_counters: 2,
315 data: vec![200, 400],
316 });
317 let merged = merger.merge_all().expect("merge should succeed");
318 assert_eq!(merged.data, vec![150, 300]);
319 }
320 #[test]
321 pub(super) fn test_loop_iteration_profile() {
322 let mut lp = LoopIterationProfile::new(0, "compute");
323 lp.record_execution(4);
324 lp.record_execution(4);
325 lp.record_execution(4);
326 assert!(lp.is_constant_trip_count());
327 assert_eq!(lp.trip_count_max, 4);
328 assert!((lp.trip_count_avg - 4.0).abs() < 0.01);
329 assert_eq!(lp.estimated_unroll_factor(), 2);
330 }
331 #[test]
332 pub(super) fn test_memory_access_pattern() {
333 let mut pat = MemoryAccessPattern::new(0);
334 pat.is_sequential = true;
335 pat.stride = 8;
336 pat.cache_hit_rate = 0.95;
337 assert!(pat.is_cache_friendly());
338 assert_eq!(pat.prefetch_distance(), 8);
339 let pat2 = MemoryAccessPattern::new(1);
340 assert_eq!(pat2.prefetch_distance(), 0);
341 }
342 #[test]
343 pub(super) fn test_allocation_profile() {
344 let mut ap = AllocationProfile::new("allocator_fn");
345 let mut site = AllocationSiteProfile::new(0);
346 site.alloc_count = 100;
347 site.avg_size = 64.0;
348 site.max_size = 128;
349 site.live_at_exit = 0;
350 assert!(site.is_short_lived());
351 assert!(site.stack_promotion_candidate());
352 ap.add_site(site);
353 assert_eq!(ap.total_allocations(), 100);
354 assert_eq!(ap.stack_promotion_candidates().len(), 1);
355 }
356 #[test]
357 pub(super) fn test_virtual_call_record() {
358 let mut vcr = VirtualCallRecord::new(0);
359 vcr.record_call("TypeA", 990);
360 vcr.record_call("TypeB", 10);
361 assert_eq!(vcr.total_calls, 1000);
362 assert!(vcr.dominant_target().is_some());
363 let (name, ratio) = vcr.dominant_target().expect("expected Some/Ok value");
364 assert_eq!(name, "TypeA");
365 assert!((ratio - 0.99).abs() < 0.001);
366 assert!(vcr.is_monomorphic());
367 assert!(vcr.is_bimorphic());
368 }
369 #[test]
370 pub(super) fn test_devirtualization_pass() {
371 let mut pass = DevirtualizationPass::new();
372 let mut vcr = VirtualCallRecord::new(0);
373 vcr.record_call("ConcreteType", 999);
374 vcr.record_call("OtherType", 1);
375 pass.add_record(vcr);
376 assert_eq!(pass.devirtualize_candidates().len(), 1);
377 }
378 #[test]
379 pub(super) fn test_pgo_annotated_function() {
380 let f =
381 PgoAnnotatedFunction::new("hot_fn", 10000).with_inline_hint(InlineHint::AlwaysInline);
382 let attrs = f.emit_llvm_attrs();
383 assert!(attrs.contains("func_entry_count"));
384 assert!(attrs.contains("alwaysinline"));
385 }
386 #[test]
387 pub(super) fn test_bolt_config() {
388 let cfg = BoltInstrumentationConfig::default_bolt();
389 let flags = cfg.emit_flags();
390 assert!(flags.iter().any(|f| f.contains("reorder-blocks")));
391 assert!(flags.iter().any(|f| f.contains("split-functions")));
392 }
393 #[test]
394 pub(super) fn test_auto_fdo() {
395 let cfg = AutoFdoConfig::new("/usr/bin/myapp");
396 let perf_cmd = cfg.emit_perf_command();
397 assert!(perf_cmd.contains("perf record"));
398 assert!(perf_cmd.contains("4000"));
399 let gcov_cmd = cfg.emit_create_gcov_command();
400 assert!(gcov_cmd.contains("create_gcov"));
401 }
402 #[test]
403 pub(super) fn test_thin_lto_pgo() {
404 let mut data = ThinLtoPgoData::new(12345);
405 assert!(data.is_empty());
406 let mut p = FunctionProfile::new("hot");
407 p.total_calls = 1000;
408 data.add_profile(p);
409 assert!(!data.is_empty());
410 let hot = data.hot_function_names(500);
411 assert_eq!(hot, vec!["hot"]);
412 }
413 #[test]
414 pub(super) fn test_context_sensitive_profile() {
415 let mut root = ContextSensitiveProfile::new(vec!["main".to_string()], 1000);
416 let child =
417 ContextSensitiveProfile::new(vec!["main".to_string(), "compute".to_string()], 500);
418 root.add_child(child);
419 assert_eq!(root.depth(), 1);
420 assert_eq!(root.total_count_in_subtree(), 1500);
421 let flat = root.flatten();
422 assert_eq!(flat.len(), 2);
423 }
424}
425#[cfg(test)]
426mod pgo_reporting_tests {
427 use super::*;
428 #[test]
429 pub(super) fn test_pgo_decision_description() {
430 let d = PgoDecision::Inlined {
431 callee: "foo".to_string(),
432 benefit: 2.5,
433 };
434 assert!(d.description().contains("Inlined foo"));
435 assert!(d.is_beneficial());
436 let d2 = PgoDecision::NotInlined {
437 callee: "bar".to_string(),
438 reason: "too large".to_string(),
439 };
440 assert!(!d2.is_beneficial());
441 }
442 #[test]
443 pub(super) fn test_optimization_log() {
444 let mut log = PgoOptimizationLog::new();
445 log.record(
446 "main",
447 PgoDecision::Inlined {
448 callee: "helper".to_string(),
449 benefit: 1.0,
450 },
451 );
452 log.record(
453 "main",
454 PgoDecision::NotInlined {
455 callee: "big_fn".to_string(),
456 reason: "size limit".to_string(),
457 },
458 );
459 log.record(
460 "compute",
461 PgoDecision::Unrolled {
462 loop_id: 0,
463 factor: 4,
464 },
465 );
466 assert_eq!(log.total_beneficial, 2);
467 assert_eq!(log.total_non_beneficial, 1);
468 let report = log.generate_report();
469 assert!(report.contains("PGO Optimization Report"));
470 assert!(report.contains("Inlined helper"));
471 let main_decisions = log.filter_by_function("main");
472 assert_eq!(main_decisions.len(), 2);
473 }
474 #[test]
475 pub(super) fn test_propeller_profile() {
476 let mut profile = PropellerProfile::new("myapp.elf");
477 profile.add_function(PropellerFunctionInfo {
478 name: "main".to_string(),
479 address: 0x1000,
480 size: 100,
481 entry_count: 1000,
482 });
483 profile.add_edge(PropellerEdge {
484 from_addr: 0x1000,
485 to_addr: 0x1050,
486 weight: 800,
487 });
488 assert_eq!(profile.total_edge_weight(), 800);
489 let proto = profile.emit_protobuf_format();
490 assert!(proto.contains("binary_id: \"myapp.elf\""));
491 assert!(proto.contains("name: \"main\""));
492 }
493 #[test]
494 pub(super) fn test_sample_based_profile() {
495 let mut gen = SampleBasedProfileGenerator::new(100);
496 gen.add_trace(vec!["hot_fn".to_string(), "caller".to_string()]);
497 gen.add_trace(vec!["hot_fn".to_string(), "other".to_string()]);
498 gen.add_trace(vec!["cold_fn".to_string()]);
499 let flat = gen.build_flat_profile();
500 assert_eq!(
501 *flat.get("hot_fn").expect("value should be present in map"),
502 2
503 );
504 assert_eq!(
505 *flat.get("cold_fn").expect("value should be present in map"),
506 1
507 );
508 let top = gen.top_functions(1);
509 assert_eq!(top[0].0, "hot_fn");
510 }
511 #[test]
512 pub(super) fn test_statistics_report_from_log() {
513 let mut log = PgoOptimizationLog::new();
514 log.record(
515 "f",
516 PgoDecision::Inlined {
517 callee: "g".to_string(),
518 benefit: 1.0,
519 },
520 );
521 log.record(
522 "f",
523 PgoDecision::Devirtualized {
524 callsite: 0,
525 target: "A".to_string(),
526 },
527 );
528 log.record("f", PgoDecision::StackPromotion { site_id: 1 });
529 log.record(
530 "f",
531 PgoDecision::Unrolled {
532 loop_id: 0,
533 factor: 4,
534 },
535 );
536 log.record(
537 "f",
538 PgoDecision::Vectorized {
539 loop_id: 1,
540 width: 4,
541 },
542 );
543 log.record(
544 "f",
545 PgoDecision::BlockReordered {
546 function: "f".to_string(),
547 blocks: 10,
548 },
549 );
550 let report = PgoStatisticsReport::from_log(&log);
551 assert_eq!(report.inlined_callsites, 1);
552 assert_eq!(report.devirtualized_sites, 1);
553 assert_eq!(report.stack_promoted_sites, 1);
554 assert_eq!(report.loops_unrolled, 1);
555 assert_eq!(report.loops_vectorized, 1);
556 assert_eq!(report.blocks_reordered, 10);
557 let summary = report.format_summary();
558 assert!(summary.contains("Inlined: 1"));
559 }
560 #[test]
561 pub(super) fn test_whole_program_devirt() {
562 let mut wpd = WholeProgramDevirt::new();
563 wpd.register_vtable("Animal", vec!["speak".to_string(), "move".to_string()]);
564 assert_eq!(wpd.class_count(), 1);
565 let mut vcr = VirtualCallRecord::new(0);
566 vcr.record_call("Dog", 850);
567 vcr.record_call("Cat", 150);
568 wpd.add_call_profile(vcr);
569 let opps = wpd.speculation_opportunities();
570 assert_eq!(opps.len(), 1);
571 assert!((opps[0].2 - 0.85).abs() < 0.01);
572 }
573}
574#[cfg(test)]
575mod pgo_instrumentation_tests {
576 use super::*;
577 #[test]
578 pub(super) fn test_instrumentation_counters() {
579 let mut counters = InstrumentationCounters::new(3, 2);
580 counters.record_entry();
581 counters.record_branch(0, true);
582 counters.record_branch(0, true);
583 counters.record_branch(0, false);
584 counters.record_value(0, 42);
585 assert_eq!(counters.function_entry, 1);
586 assert_eq!(counters.branch_taken[0], 2);
587 assert_eq!(counters.branch_not_taken[0], 1);
588 assert_eq!(counters.value_profiles[0], 1);
589 let bias = counters.branch_bias(0).expect("branch bias should exist");
590 assert!((bias - 2.0 / 3.0).abs() < 0.01);
591 let data = counters.serialize();
592 assert_eq!(data[0], 1);
593 }
594 #[test]
595 pub(super) fn test_global_registry() {
596 let mut reg = GlobalInstrumentationRegistry::new();
597 reg.register("compute", 5, 2);
598 reg.register("io_loop", 3, 0);
599 if let Some(c) = reg.get_mut("compute") {
600 c.record_entry();
601 c.record_entry();
602 }
603 assert_eq!(reg.function_count(), 2);
604 assert_eq!(reg.total_entries(), 2);
605 let records = reg.export_profile();
606 assert_eq!(records.len(), 2);
607 }
608 #[test]
609 pub(super) fn test_pgo_data_format() {
610 assert_eq!(PgoDataFormat::LlvmRaw.file_extension(), "profraw");
611 assert_eq!(PgoDataFormat::GccGcda.file_extension(), "gcda");
612 assert_eq!(PgoDataFormat::LlvmRaw.merge_tool(), "llvm-profdata");
613 let cmd =
614 PgoDataFormat::LlvmRaw.emit_merge_command(&["a.profraw", "b.profraw"], "out.profdata");
615 assert!(cmd.contains("llvm-profdata merge"));
616 assert!(cmd.contains("a.profraw"));
617 assert!(cmd.contains("out.profdata"));
618 }
619}
620#[allow(dead_code)]
621pub fn pgo_format_count(count: u64) -> String {
622 if count >= 1_000_000 {
623 format!("{:.1}M", count as f64 / 1_000_000.0)
624 } else if count >= 1_000 {
625 format!("{:.1}K", count as f64 / 1_000.0)
626 } else {
627 format!("{}", count)
628 }
629}
630#[allow(dead_code)]
631pub fn pgo_percentile(values: &mut [u64], percentile: f64) -> u64 {
632 if values.is_empty() {
633 return 0;
634 }
635 values.sort_unstable();
636 let index = ((percentile / 100.0) * (values.len() - 1) as f64).ceil() as usize;
637 values[index.min(values.len() - 1)]
638}
639#[allow(dead_code)]
640pub fn pgo_compute_speedup(baseline_cycles: u64, optimized_cycles: u64) -> f64 {
641 if optimized_cycles == 0 {
642 return f64::INFINITY;
643 }
644 baseline_cycles as f64 / optimized_cycles as f64
645}
646#[allow(dead_code)]
647pub fn pgo_estimate_branch_mispredictions(bias: f64, total_branches: u64) -> f64 {
648 let misprediction_rate = 2.0 * bias * (1.0 - bias);
649 misprediction_rate * total_branches as f64
650}
651#[allow(dead_code)]
652pub fn pgo_should_specialize(call_count: u64, total: u64, threshold: f64) -> bool {
653 if total == 0 {
654 return false;
655 }
656 let ratio = call_count as f64 / total as f64;
657 ratio >= threshold
658}
659#[allow(dead_code)]
660pub fn pgo_compute_inline_benefit(call_count: u64, callee_size: usize, context_size: usize) -> f64 {
661 let call_overhead_savings = call_count as f64 * 5.0;
662 let code_growth = callee_size as f64;
663 let context_benefit = (context_size as f64 / 100.0).min(2.0);
664 (call_overhead_savings * context_benefit) / (code_growth + 1.0)
665}
666#[cfg(test)]
667mod pgo_utility_tests {
668 use super::*;
669 #[test]
670 pub(super) fn test_format_count() {
671 assert_eq!(pgo_format_count(999), "999");
672 assert_eq!(pgo_format_count(1500), "1.5K");
673 assert_eq!(pgo_format_count(2_500_000), "2.5M");
674 }
675 #[test]
676 pub(super) fn test_percentile() {
677 let mut vals = vec![10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
678 assert_eq!(pgo_percentile(&mut vals, 50.0), 60);
679 assert_eq!(pgo_percentile(&mut vals, 0.0), 10);
680 assert_eq!(pgo_percentile(&mut vals, 100.0), 100);
681 }
682 #[test]
683 pub(super) fn test_speedup() {
684 let sp = pgo_compute_speedup(2000, 1000);
685 assert!((sp - 2.0).abs() < 0.01);
686 }
687 #[test]
688 pub(super) fn test_should_specialize() {
689 assert!(pgo_should_specialize(90, 100, 0.8));
690 assert!(!pgo_should_specialize(70, 100, 0.8));
691 }
692 #[test]
693 pub(super) fn test_inline_benefit() {
694 let benefit = pgo_compute_inline_benefit(100, 50, 200);
695 assert!(benefit > 0.0);
696 }
697}
698#[allow(dead_code)]
699pub fn pgo_version() -> &'static str {
700 "1.0.0"
701}
702#[allow(dead_code)]
703pub fn pgo_supported_phases() -> &'static [&'static str] {
704 &[
705 "instrumentation",
706 "training",
707 "optimization",
708 "verification",
709 ]
710}
711#[allow(dead_code)]
712pub fn pgo_is_complete() -> bool {
713 true
714}
715#[allow(dead_code)]
716pub fn pgo_get_hot_threshold(cfg: &PgoConfig) -> u64 {
717 cfg.hot_threshold
718}
719#[allow(dead_code)]
720pub fn pgo_inline_enabled(cfg: &PgoConfig) -> bool {
721 cfg.inline_hot
722}
723#[allow(dead_code)]
724pub fn pgo_specialize_enabled(cfg: &PgoConfig) -> bool {
725 cfg.specialize_hot
726}
727#[allow(dead_code)]
728pub fn pgo_profile_count(data: &ProfileData) -> usize {
729 data.call_counts.len()
730}
731#[allow(dead_code)]
732pub fn pgo_has_feedback(data: &ProfileData) -> bool {
733 !data.call_counts.is_empty()
734}
735#[allow(dead_code)]
736pub fn pgo_hot_function_count(data: &ProfileData) -> usize {
737 data.hot_functions.len()
738}
739#[allow(dead_code)]
740pub fn pgo_edge_count(data: &ProfileData) -> usize {
741 data.edge_counts.len()
742}
743#[allow(dead_code)]
744pub fn pgo_extra_version() -> u32 {
745 1
746}
747#[allow(dead_code)]
748pub fn pgo_extra_name() -> &'static str {
749 "pgo-extra"
750}
751#[allow(dead_code)]
752pub fn pgo_extra_enabled() -> bool {
753 true
754}
755#[allow(dead_code)]
756pub fn pgo_extra_threshold() -> u64 {
757 100
758}
759#[allow(dead_code)]
760pub fn pgo_extra_max_iter() -> u32 {
761 10
762}