Skip to main content

oxilean_codegen/pgo/
functions.rs

1//! Auto-generated module
2//!
3//! 🤖 Generated with [SplitRS](https://github.com/cool-japan/splitrs)
4
5use 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}