1use super::analyzer::{FunctionCost, ProgramCost};
2use super::visit::next_power_of_two;
3use crate::diagnostic::Diagnostic;
4use crate::span::Span;
5
6impl ProgramCost {
9 pub fn format_report(&self) -> String {
11 let short = self.short_names();
12 let n = short.len();
13 let mut out = String::new();
14 out.push_str(&format!("Cost report: {}\n", self.program_name));
15
16 out.push_str(&format!("{:<24}", "Function"));
18 for name in &short {
19 out.push_str(&format!(" {:>6}", name));
20 }
21 out.push_str(" dominant\n");
22 let line_width = 24 + n * 7 + 10;
23 out.push_str(&"-".repeat(line_width));
24 out.push('\n');
25
26 for func in &self.functions {
27 out.push_str(&format!("{:<24}", func.name));
28 for i in 0..n {
29 out.push_str(&format!(" {:>6}", func.cost.get(i)));
30 }
31 out.push_str(&format!(" {}\n", func.cost.dominant_table(&short)));
32 if let Some((per_iter, bound)) = &func.per_iteration {
33 out.push_str(&format!(" per iteration (x{})", bound));
34 let label_len = format!(" per iteration (x{})", bound).len();
35 for _ in label_len..24 {
37 out.push(' ');
38 }
39 for i in 0..n {
40 out.push_str(&format!(" {:>6}", per_iter.get(i)));
41 }
42 out.push('\n');
43 }
44 }
45
46 out.push_str(&"-".repeat(line_width));
47 out.push('\n');
48 out.push_str(&format!("{:<24}", "TOTAL"));
49 for i in 0..n {
50 out.push_str(&format!(" {:>6}", self.total.get(i)));
51 }
52 out.push_str(&format!(" {}\n", self.total.dominant_table(&short)));
53 out.push('\n');
54 out.push_str(&format!(
55 "Padded height: {}\n",
56 self.padded_height
57 ));
58 out.push_str(&format!(
59 "Program attestation: {} hash rows\n",
60 self.attestation_hash_rows
61 ));
62 let secs = self.estimated_proving_ns / 1_000_000_000;
63 let tenths = (self.estimated_proving_ns / 100_000_000) % 10;
64 out.push_str(&format!("Estimated proving time: ~{}.{}s\n", secs, tenths));
65
66 let headroom = self.padded_height - self.total.max_height();
68 if headroom < self.padded_height / 8 {
69 out.push_str(&format!(
70 "\nwarning: {} rows below padded height boundary ({})\n",
71 headroom, self.padded_height
72 ));
73 out.push_str(&format!(
74 " adding {}+ rows to any table will double proving cost to {}\n",
75 headroom + 1,
76 self.padded_height * 2
77 ));
78 }
79
80 out
81 }
82
83 pub fn format_hotspots(&self, top_n: usize) -> String {
85 let short = self.short_names();
86 let mut out = String::new();
87 out.push_str(&format!("Top {} cost contributors:\n", top_n));
88
89 let dominant = self.total.dominant_table(&short);
90 let dominant_idx = self.dominant_index();
91 let dominant_total = self.total.get(dominant_idx);
92
93 let mut ranked: Vec<&FunctionCost> = self.functions.iter().collect();
94 ranked.sort_by(|a, b| {
95 let av = a.cost.get(dominant_idx);
96 let bv = b.cost.get(dominant_idx);
97 bv.cmp(&av)
98 });
99
100 for (i, func) in ranked.iter().take(top_n).enumerate() {
101 let val = func.cost.get(dominant_idx);
102 let pct = if dominant_total > 0 {
103 val * 100 / dominant_total
104 } else {
105 0
106 };
107 out.push_str(&format!(
108 " {}. {:<24} {:>6} {} rows ({}% of {} table)\n",
109 i + 1,
110 func.name,
111 val,
112 dominant,
113 pct,
114 dominant
115 ));
116 }
117
118 out.push_str(&format!(
119 "\nDominant table: {} ({} rows). Reduce {} operations to lower padded height.\n",
120 dominant, dominant_total, dominant
121 ));
122
123 out
124 }
125
126 pub fn optimization_hints(&self) -> Vec<Diagnostic> {
128 let short = self.short_names();
129 let mut hints = Vec::new();
130
131 if self.total.count >= 2 && self.total.get(0) > 0 {
134 let dominant_idx = self.dominant_index();
135 if dominant_idx > 0 {
136 let dominant_val = self.total.get(dominant_idx);
137 let primary_val = self.total.get(0);
138 if dominant_val > 2 * primary_val {
140 let dominant_name = short.get(dominant_idx).unwrap_or(&"?");
141 let primary_name = short.first().unwrap_or(&"?");
142 let ratio_10 = if primary_val > 0 {
144 dominant_val * 10 / primary_val
145 } else {
146 0
147 };
148 let mut diag = Diagnostic::warning(
149 format!(
150 "hint[H0001]: {} table is {}.{}x taller than {} table",
151 dominant_name,
152 ratio_10 / 10,
153 ratio_10 % 10,
154 primary_name
155 ),
156 Span::dummy(),
157 );
158 diag.notes.push(format!(
159 "{} optimizations will not reduce proving cost",
160 primary_name
161 ));
162 diag.help = Some(format!(
163 "focus on reducing {} table usage to lower padded height",
164 dominant_name
165 ));
166 hints.push(diag);
167 }
168 }
169 }
170
171 let max_height = self.total.max_height().max(self.attestation_hash_rows);
173 let headroom = self.padded_height - max_height;
174 if headroom > self.padded_height / 4 && self.padded_height >= 16 {
175 let headroom_pct = if self.padded_height > 0 {
176 headroom * 100 / self.padded_height
177 } else {
178 0
179 };
180 let mut diag = Diagnostic::warning(
181 format!(
182 "hint[H0002]: padded height is {}, but max table height is only {}",
183 self.padded_height, max_height
184 ),
185 Span::dummy(),
186 );
187 diag.notes.push(format!(
188 "you have {} rows of headroom ({}%) before the next doubling",
189 headroom, headroom_pct
190 ));
191 diag.help = Some(format!(
192 "this program could be {}% more complex at zero additional proving cost",
193 headroom_pct
194 ));
195 hints.push(diag);
196 }
197
198 for (fn_name, end_val, bound) in &self.loop_bound_waste {
201 if *bound == 0 {
202 let mut diag = Diagnostic::warning(
204 format!(
205 "hint[H0004]: loop in '{}' has non-constant bound, cost assumes {} iteration(s)",
206 fn_name, end_val
207 ),
208 Span::dummy(),
209 );
210 diag.help = Some(
211 "add a `bounded N` annotation to set a realistic worst-case iteration count"
212 .to_string(),
213 );
214 hints.push(diag);
215 } else {
216 let actual = *end_val.max(&1);
217 let ratio = *bound / actual;
218 let mut diag = Diagnostic::warning(
219 format!(
220 "hint[H0004]: loop in '{}' bounded {} but iterates only {} times",
221 fn_name, bound, end_val
222 ),
223 Span::dummy(),
224 );
225 diag.notes.push(format!(
226 "declared bound is {}x the actual iteration count",
227 ratio
228 ));
229 diag.help = Some(format!(
230 "tightening the bound to {} would reduce worst-case cost",
231 next_power_of_two(*end_val)
232 ));
233 hints.push(diag);
234 }
235 }
236
237 hints
238 }
239}