metrale_circuit/
digest.rs1use std::fmt::Write as _;
20
21use sha2::{Digest, Sha256};
22
23use crate::fuser::{EdgeState, FusionPlan};
24use crate::ir::Circuit;
25
26pub const DIGEST_SCHEMA: u32 = 2;
28
29pub fn canonical(circuit: &Circuit, plan: &FusionPlan) -> String {
31 let mut s = String::new();
32 let _ = writeln!(s, "metrale-circuit-plan schema={DIGEST_SCHEMA}");
33 let _ = writeln!(
34 s,
35 "plan arch={} mode={} rows={}",
36 plan.arch,
37 plan.mode.name(),
38 plan.rows
39 );
40 if let Some(t) = &plan.table {
42 let _ = writeln!(s, "table {t}");
43 }
44 for (i, g) in plan.groups.iter().enumerate() {
45 let kernels: Vec<String> = g.kernels.iter().map(|k| k.to_string()).collect();
46 let nodes: Vec<&str> = g
47 .nodes
48 .iter()
49 .map(|&n| circuit.nodes[n].id.as_str())
50 .collect();
51 let _ = writeln!(
52 s,
53 "group {i} kernels=[{}] repeat={}{} emitter={} numerics={:?} nodes=[{}]",
54 kernels.join(","),
55 g.repeat.name(),
56 g.copies
57 .map(|c| format!(" copies={}", c.name()))
58 .unwrap_or_default(),
59 g.emitter,
60 g.numerics,
61 nodes.join(",")
62 );
63 for r in &g.runs {
64 let launches: Vec<String> = r
65 .launches
66 .iter()
67 .map(|(k, t)| format!("{k}*{}", t.name()))
68 .collect();
69 let _ = writeln!(
70 s,
71 " run k={} n={} contiguous={} launches=[{}] copies={}",
72 r.run.k,
73 r.run.n,
74 r.run.contiguous,
75 launches.join(","),
76 r.copies.map_or("none", |c| c.name())
77 );
78 }
79 }
80 for (e, edge) in circuit.edges.iter().enumerate() {
81 let Some(state) = plan.edge_states[e] else {
82 continue;
83 };
84 let state = match state {
85 EdgeState::Materialized => "M".to_string(),
86 EdgeState::Fused(g) => format!("F{g}"),
87 };
88 let _ = writeln!(
89 s,
90 "edge {} stored={} rows={} dim={}={} state={state}",
91 edge.id,
92 plan.edge_formats[e].name(),
93 edge.rows.text(),
94 edge.dim.text(),
95 edge.dim_value
96 );
97 }
98 s
99}
100
101fn hex(bytes: &[u8]) -> String {
102 bytes.iter().fold(String::with_capacity(64), |mut acc, b| {
103 let _ = write!(acc, "{b:02x}");
104 acc
105 })
106}
107
108pub fn plan_digest(circuit: &Circuit, plan: &FusionPlan) -> String {
110 hex(&Sha256::digest(canonical(circuit, plan).as_bytes()))
111}
112
113pub fn rules_digest(fusions_text: &str) -> String {
115 hex(&Sha256::digest(fusions_text.as_bytes()))
116}
117
118pub fn plans_digest<'a>(digests: impl IntoIterator<Item = &'a str>) -> String {
121 let mut h = Sha256::new();
122 for d in digests {
123 h.update(d.as_bytes());
124 h.update(b"\n");
125 }
126 hex(&h.finalize())
127}