padlock_core/analysis/
scorer.rs1use crate::analysis::{false_sharing, locality, padding, reorder};
4use crate::ir::StructLayout;
5
6#[derive(Debug, Clone)]
7pub struct ScoreBreakdown {
8 pub total: f64,
10 pub padding_deduction: f64,
12 pub false_sharing_deduction: f64,
14 pub locality_deduction: f64,
16}
17
18pub fn score(layout: &StructLayout) -> f64 {
20 score_with_breakdown(layout).total
21}
22
23pub fn score_with_breakdown(layout: &StructLayout) -> ScoreBreakdown {
24 let mut deductions = 0.0f64;
25
26 let gaps = padding::find_padding(layout);
28 let wasted: usize = gaps.iter().map(|g| g.bytes).sum();
29 let padding_deduction = if layout.total_size > 0 {
30 (wasted as f64 / layout.total_size as f64 * 40.0).min(40.0)
31 } else {
32 0.0
33 };
34 deductions += padding_deduction;
35
36 let false_sharing_deduction = if layout.is_union {
39 0.0
40 } else if false_sharing::has_false_sharing(layout) {
41 30.0
42 } else if !false_sharing::find_sharing_conflicts(layout).is_empty() {
43 10.0
44 } else {
45 0.0
46 };
47 deductions += false_sharing_deduction;
48
49 let locality_deduction = if locality::has_locality_issue(layout) {
51 15.0
52 } else {
53 0.0
54 };
55 deductions += locality_deduction;
56
57 let (_, savings) = reorder::reorder_savings(layout);
59 let reorder_deduction = if layout.total_size > 0 && savings > 0 {
60 (savings as f64 / layout.total_size as f64 * 15.0).min(15.0)
61 } else {
62 0.0
63 };
64 deductions += reorder_deduction;
65
66 let total = (100.0 - deductions).clamp(0.0, 100.0);
67
68 ScoreBreakdown {
69 total,
70 padding_deduction,
71 false_sharing_deduction,
72 locality_deduction,
73 }
74}
75
76#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::ir::test_fixtures::{connection_layout, packed_layout};
82
83 #[test]
84 fn score_in_range() {
85 let s = score(&connection_layout());
86 assert!((0.0..=100.0).contains(&s), "score out of range: {s}");
87 }
88
89 #[test]
90 fn packed_scores_higher_than_misaligned() {
91 assert!(score(&packed_layout()) > score(&connection_layout()));
92 }
93
94 #[test]
95 fn perfect_layout_scores_100() {
96 use crate::arch::X86_64_SYSV;
98 use crate::ir::{AccessPattern, Field, StructLayout, TypeInfo};
99 let layout = StructLayout {
100 name: "Single".into(),
101 total_size: 8,
102 align: 8,
103 fields: vec![Field {
104 name: "x".into(),
105 ty: TypeInfo::Primitive {
106 name: "u64".into(),
107 size: 8,
108 align: 8,
109 },
110 offset: 0,
111 size: 8,
112 align: 8,
113 source_file: None,
114 source_line: None,
115 access: AccessPattern::Unknown,
116 }],
117 source_file: None,
118 source_line: None,
119 arch: &X86_64_SYSV,
120 is_packed: false,
121 is_union: false,
122 is_repr_rust: false,
123 suppressed_findings: Vec::new(),
124 };
125 assert!((score(&layout) - 100.0).abs() < 1e-9);
126 }
127
128 #[test]
129 fn connection_loses_padding_and_reorder_points() {
130 let bd = score_with_breakdown(&connection_layout());
131 assert!(bd.padding_deduction > 0.0);
132 assert!(bd.total < 100.0);
133 }
134}