1use std::fmt::Write as _;
14
15use crate::fuser::{EdgeState, FusionPlan, Group, Policy};
16use crate::ir::{Circuit, NodeIdx};
17use crate::rules::Numerics;
18use crate::runtime::RuntimeRoute;
19
20pub type Header = Vec<(String, String)>;
23
24pub fn with_settings(header: &Header, policy: &Policy) -> Header {
27 header
28 .iter()
29 .map(|(k, v)| {
30 if k == "settings" {
31 let s: Vec<String> = policy
32 .settings
33 .iter()
34 .map(|(k, v)| format!("{k}={v}"))
35 .collect();
36 (k.clone(), s.join(" "))
37 } else {
38 (k.clone(), v.clone())
39 }
40 })
41 .collect()
42}
43
44pub struct Notes<'a> {
48 pub group: &'a dyn Fn(&Group) -> Option<String>,
50 pub node: &'a dyn Fn(NodeIdx) -> Option<String>,
52}
53
54impl Notes<'_> {
55 pub const NONE: Notes<'static> = Notes {
57 group: &|_| None,
58 node: &|_| None,
59 };
60}
61
62pub fn route_section(
66 circuit: &Circuit,
67 route: &RuntimeRoute,
68 plan: &FusionPlan,
69 header: &Header,
70 notes: &Notes<'_>,
71) -> String {
72 format!(
73 "\n# runtime route `{}`: when {}; planned as `{}` ({})\n\n{}",
74 route.id,
75 route.why,
76 route.plans_as_text(),
77 route.cite,
78 render_noted(circuit, plan, header, notes)
79 )
80}
81
82pub fn render(circuit: &Circuit, plan: &FusionPlan, header: &Header) -> String {
84 render_noted(circuit, plan, header, &Notes::NONE)
85}
86
87pub fn render_noted(
91 circuit: &Circuit,
92 plan: &FusionPlan,
93 header: &Header,
94 notes: &Notes<'_>,
95) -> String {
96 let mut s = String::new();
97 let _ = writeln!(
98 s,
99 "# circuit plan: {} {} n={}",
100 plan.arch,
101 plan.mode.name(),
102 plan.rows
103 );
104 for (k, v) in header {
105 let _ = writeln!(s, "# {k}: {v}");
106 }
107 if let Some(t) = &plan.table {
108 let _ = writeln!(s, "# row table: {t}");
109 }
110 let fused = plan
111 .edge_states
112 .iter()
113 .filter(|s| matches!(s, Some(EdgeState::Fused(_))))
114 .count();
115 let materialized = plan
116 .edge_states
117 .iter()
118 .filter(|s| **s == Some(EdgeState::Materialized))
119 .count();
120 let _ = writeln!(s, "digest: {}", plan.digest);
121 let _ = writeln!(
122 s,
123 "groups: {} launches: {}{} edges: {} fused, {} materialized",
124 plan.groups.len(),
125 plan.launches(),
126 match plan.copies() {
127 0 => String::new(),
128 c => format!(" copies: {c}"),
129 },
130 fused,
131 materialized
132 );
133 let inputs: Vec<String> = circuit
134 .edges
135 .iter()
136 .enumerate()
137 .filter(|(e, edge)| {
138 plan.edge_states[*e].is_some()
139 && edge
140 .producer
141 .is_none_or(|p| !plan.groups.iter().any(|g| g.nodes.contains(&p)))
142 })
143 .map(|(e, edge)| format!("{} {}", edge.id, plan.edge_formats[e]))
144 .collect();
145 if !inputs.is_empty() {
146 let _ = writeln!(s, "inputs: {}", inputs.join(", "));
147 }
148 let mut last_layer: Option<Option<usize>> = None;
149 for (g, grp) in plan.groups.iter().enumerate() {
150 let first = &circuit.nodes[grp.nodes[0]];
151 if last_layer != Some(first.layer) {
152 let title = match first.layer {
153 Some(i) => format!("layer {i} ({})", circuit.layer_kinds[i].name()),
154 None => first.block.clone(),
155 };
156 let _ = writeln!(s, "== {title}");
157 last_layer = Some(first.layer);
158 }
159 let mut line = group_line(circuit, plan, g);
160 if let Some(n) = (notes.group)(grp) {
161 let _ = write!(line, " compute={n}");
162 }
163 let _ = writeln!(s, "{line}");
164 for &n in &grp.nodes {
165 if let Some(p) = (notes.node)(n) {
166 let _ = writeln!(s, " {}: {p}", circuit.nodes[n].local);
167 }
168 }
169 }
170 s
171}
172
173fn group_line(circuit: &Circuit, plan: &FusionPlan, g: usize) -> String {
174 let grp = &plan.groups[g];
175 let kernels = if grp.emitter == crate::hardware::plan::NOVEL_EMITTER {
179 "(novel: no kernel on this class)".to_string()
180 } else if !grp.runs.is_empty() {
181 grp.runs
183 .iter()
184 .map(|r| {
185 let l: Vec<String> = r
186 .launches
187 .iter()
188 .map(|(k, t)| match t.count(r.run) {
189 1 => k.to_string(),
190 c => format!("{k} x{c}"),
191 })
192 .collect();
193 let copies = match r.copy_count() {
194 0 => String::new(),
195 c => format!(" + copy x{c}"),
196 };
197 format!(
198 "[{}x{}{}: {}{copies}]",
199 r.run.k,
200 r.run.n,
201 if r.run.contiguous { "" } else { "!" },
202 l.join(" + ")
203 )
204 })
205 .collect::<Vec<_>>()
206 .join(" ")
207 } else if grp.kernels.is_empty() {
208 "(host)".to_string()
209 } else {
210 grp.kernels
211 .iter()
212 .map(|k| k.to_string())
213 .collect::<Vec<_>>()
214 .join(" + ")
215 };
216 let repeat = match grp.repeat.count(plan.rows) {
217 None | Some(1) => String::new(),
218 Some(n) => format!(" x{n}"),
219 };
220 let repeat = match grp.copies.and_then(|c| c.count(plan.rows)) {
221 Some(c) => format!("{repeat} + copy x{c}"),
222 None => repeat,
223 };
224 let numerics = match &grp.numerics {
225 Numerics::Reference => "reference".to_string(),
226 Numerics::BitIdentical { microtest } => format!("bit_identical({microtest})"),
227 Numerics::Differs { lever } => format!("differs({lever})"),
228 };
229 let block = circuit.nodes[grp.nodes[0]]
230 .id
231 .rsplit_once('.')
232 .map_or("", |(b, _)| b);
233 let members: Vec<&str> = grp
234 .nodes
235 .iter()
236 .map(|&n| circuit.nodes[n].local.as_str())
237 .collect();
238 let mut line = format!(
239 "g{g:04} {block} [{}] {kernels}{repeat} {numerics} rule={}",
240 members.join(","),
241 grp.rule
242 );
243 let mut fused = Vec::new();
244 let mut writes = Vec::new();
245 for &n in &grp.nodes {
246 for &e in &circuit.nodes[n].outputs {
247 let edge = &circuit.edges[e];
248 let local = edge
249 .id
250 .rsplit_once('.')
251 .map_or(edge.id.as_str(), |(_, l)| l);
252 match plan.edge_states[e] {
253 Some(EdgeState::Fused(_)) => fused.push(local.to_string()),
254 Some(EdgeState::Materialized) => {
255 writes.push(format!("{local}:{}", plan.edge_formats[e]))
256 }
257 None => {}
258 }
259 }
260 }
261 if !fused.is_empty() {
262 let _ = write!(line, " fuses={{{}}}", fused.join(","));
263 }
264 if !writes.is_empty() {
265 let _ = write!(line, " writes={{{}}}", writes.join(","));
266 }
267 line
268}