metrale_circuit/pipeline/
check.rs1use std::collections::BTreeMap;
22
23use super::require::{Need, required};
24use super::{Mismatch, NodePipeline, PipelineError, Stated, differences};
25use crate::fuser::{FusionPlan, Group};
26use crate::hardware::plan::NOVEL_EMITTER;
27use crate::ir::{Circuit, Node, NodeIdx, OpKind};
28use crate::rules::Rule;
29use crate::venn::Subject;
30use crate::venn::classify::point_of;
31use crate::venn::families::{Families, Family};
32
33#[derive(Debug, Clone, Default, PartialEq, Eq)]
35pub struct PlanPipelines {
36 pub nodes: Vec<Option<NodePipeline>>,
38 pub stated: BTreeMap<NodeIdx, Stated>,
40}
41
42impl PlanPipelines {
43 pub fn line(&self, n: NodeIdx) -> Option<String> {
46 let p = self.nodes.get(n)?.as_ref()?;
47 Some(p.render(self.stated.get(&n).unwrap_or(&Stated::default())))
48 }
49
50 pub fn facts(&self, n: NodeIdx) -> Option<Vec<String>> {
52 let p = self.nodes.get(n)?.as_ref()?;
53 Some(p.facts(self.stated.get(&n).unwrap_or(&Stated::default())))
54 }
55}
56
57pub fn family_of<'f>(families: &'f Families, g: &Group, node: &Node) -> Option<&'f Family> {
59 let listed = |f: &Family| {
60 if g.kernels.is_empty() {
61 f.emitters.contains(&g.emitter)
62 } else {
63 g.kernels.iter().any(|k| f.kernels.contains(k))
64 }
65 };
66 let rank = |f: &Family| -> Option<u8> {
67 f.ops
68 .iter()
69 .filter(|s| s.names(&node.op))
70 .filter(|s| s.weight.is_empty() || node.weight.is_some_and(|w| s.weight.contains(&w)))
71 .filter_map(|s| match node.op {
72 OpKind::Linear(r) if s.roles.contains(&r) => Some(0),
73 OpKind::Linear(_) if s.roles.is_empty() => Some(1),
74 OpKind::Linear(_) => None,
75 _ => Some(1),
76 })
77 .min()
78 };
79 families
80 .families
81 .iter()
82 .filter(|f| listed(f))
83 .filter_map(|f| rank(f).map(|r| (r, f)))
84 .min_by_key(|(r, _)| *r)
85 .map(|(_, f)| f)
86}
87
88pub fn check_plan(
91 c: &Circuit,
92 plan: &FusionPlan,
93 rules: &[Rule],
94 families: &Families,
95 settings: &BTreeMap<String, String>,
96) -> Result<PlanPipelines, PipelineError> {
97 let need = Need::of_plan(c, plan, rules, settings);
98 let subject = Subject {
99 recipe: "",
100 circuit: c,
101 settings,
102 plan: None,
103 };
104 let mut out = PlanPipelines {
105 nodes: vec![None; c.nodes.len()],
106 stated: BTreeMap::new(),
107 };
108 let mut mismatches = Vec::new();
109 for g in &plan.groups {
110 for &n in &g.nodes {
111 let node = &c.nodes[n];
112 let req = required(&need, n)?;
113 let stated = Stated {
114 steps: need.stated_steps(n).collect(),
115 inputs: node
116 .inputs
117 .iter()
118 .enumerate()
119 .filter(|(_, e)| need.held_input(n, **e))
120 .map(|(i, _)| i)
121 .collect(),
122 outputs: node
123 .outputs
124 .iter()
125 .enumerate()
126 .filter(|(_, e)| need.holds_in_group(**e))
127 .map(|(i, _)| i)
128 .collect(),
129 };
130 if stated != Stated::default() {
131 out.stated.insert(n, stated);
132 }
133 if g.emitter != NOVEL_EMITTER {
134 let found = match declared(families, g, n, &subject) {
137 Ok((_, decl)) if decl == req => None,
138 Ok((fam, decl)) => Some((fam.to_string(), differences(&req, &decl))),
139 Err(PipelineError::Declared { family, detail, .. }) => {
140 Some((family, vec![detail]))
141 }
142 Err(PipelineError::Undeclared { kernels, .. }) => Some((
143 "-".to_string(),
144 vec![format!(
145 "no kernel family declares a pipeline for {kernels}"
146 )],
147 )),
148 Err(e) => Some(("-".to_string(), vec![e.to_string()])),
149 };
150 if let Some((family, diffs)) = found {
151 mismatches.push(Mismatch {
152 node: node.id.clone(),
153 rule: g.rule.clone(),
154 family,
155 diffs,
156 });
157 }
158 }
159 out.nodes[n] = Some(req);
160 }
161 }
162 if mismatches.is_empty() {
163 Ok(out)
164 } else {
165 Err(PipelineError::Mismatch(mismatches))
166 }
167}
168
169fn declared<'f>(
170 families: &'f Families,
171 g: &Group,
172 n: NodeIdx,
173 subject: &Subject<'_>,
174) -> Result<(&'f str, NodePipeline), PipelineError> {
175 let node = &subject.circuit.nodes[n];
176 let kernels = || {
177 if g.kernels.is_empty() {
178 format!("({} emitter)", g.emitter)
179 } else {
180 g.kernels
181 .iter()
182 .map(|k| k.to_string())
183 .collect::<Vec<_>>()
184 .join(" + ")
185 }
186 };
187 let fam = family_of(families, g, node).ok_or_else(|| PipelineError::Undeclared {
188 node: node.id.clone(),
189 op: node.op.name(),
190 kernels: kernels(),
191 })?;
192 let fail = |detail: String| PipelineError::Declared {
193 node: node.id.clone(),
194 op: node.op.name(),
195 family: fam.id.clone(),
196 detail,
197 };
198 let (point, _) = point_of(fam, subject, node).map_err(|e| fail(e.to_string()))?;
199 let p = super::declare::declared_for(
200 &fam.pipeline,
201 &fam.point_pipelines(),
202 &g.kernels,
203 &super::declare::Site::of(subject.circuit, n),
204 &point,
205 )
206 .map_err(fail)?;
207 Ok((fam.id.as_str(), p))
208}
209
210#[cfg(test)]
211#[path = "check_tests.rs"]
212mod check_tests;