1use std::collections::{BTreeMap, BTreeSet};
18
19use serde::Deserialize;
20
21use crate::fuser::{AvailableKernels, FuseError, FusionPlan, Policy, fuse};
22use crate::ir::Circuit;
23use crate::rules::{Mode, Rule, RuleError, parse_file};
24
25#[derive(Debug, Clone, PartialEq, Eq)]
27pub struct RuntimeRoute {
28 pub id: String,
30 pub when: BTreeMap<String, String>,
32 pub modes: BTreeSet<Mode>,
34 pub rows: (u64, u64),
36 pub plans_as: BTreeMap<String, String>,
38 pub why: String,
40 pub cite: String,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct RuleSet {
47 pub rules: Vec<Rule>,
49 pub runtime: Vec<RuntimeRoute>,
51}
52
53#[derive(Debug, Deserialize)]
55#[serde(deny_unknown_fields)]
56pub(crate) struct RouteFile {
57 pub(crate) id: String,
58 when: BTreeMap<String, String>,
59 modes: Vec<String>,
60 rows: [u64; 2],
61 plans_as: BTreeMap<String, String>,
62 why: String,
63 cite: String,
64}
65
66pub fn parse_rule_set(text: &str) -> Result<RuleSet, RuleError> {
68 let (rules, files) = parse_file(text)?;
69 let mut seen = BTreeSet::new();
70 let mut runtime = Vec::with_capacity(files.len());
71 for f in files {
72 if !seen.insert(f.id.clone()) {
73 return Err(bad(&f.id, "duplicate id".into()));
74 }
75 runtime.push(route(f)?);
76 }
77 check_ids(&rules, &runtime)?;
78 Ok(RuleSet { rules, runtime })
79}
80
81pub fn check_ids(rules: &[Rule], runtime: &[RuntimeRoute]) -> Result<(), RuleError> {
83 match runtime.iter().find(|r| rules.iter().any(|x| x.id == r.id)) {
84 Some(r) => Err(bad(&r.id, "shares its id with a rule".into())),
85 None => Ok(()),
86 }
87}
88
89fn bad(id: &str, detail: String) -> RuleError {
90 RuleError::Runtime {
91 route: id.to_string(),
92 detail,
93 }
94}
95
96fn route(f: RouteFile) -> Result<RuntimeRoute, RuleError> {
97 if f.plans_as.is_empty() {
98 return Err(bad(&f.id, "`plans_as` is empty".into()));
99 }
100 for (k, v) in &f.plans_as {
101 match f.when.get(k) {
102 Some(w) if w != v => {}
103 Some(_) => {
104 return Err(bad(
105 &f.id,
106 format!("`plans_as` keeps `{k}` at its `when` value"),
107 ));
108 }
109 None => {
110 return Err(bad(
111 &f.id,
112 format!("`plans_as` sets `{k}`, which `when` does not fix"),
113 ));
114 }
115 }
116 }
117 let modes = f
118 .modes
119 .iter()
120 .map(|m| Mode::parse(m).ok_or_else(|| bad(&f.id, format!("unknown mode `{m}`"))))
121 .collect::<Result<BTreeSet<Mode>, RuleError>>()?;
122 if modes.is_empty() {
123 return Err(bad(&f.id, "no modes".into()));
124 }
125 if f.rows[0] == 0 || f.rows[0] > f.rows[1] {
126 return Err(bad(&f.id, format!("rows {:?}", f.rows)));
127 }
128 if f.why.trim().is_empty() || f.cite.trim().is_empty() {
129 return Err(bad(&f.id, "`why` and `cite` must be stated".into()));
130 }
131 Ok(RuntimeRoute {
132 id: f.id,
133 when: f.when,
134 modes,
135 rows: (f.rows[0], f.rows[1]),
136 plans_as: f.plans_as,
137 why: f.why,
138 cite: f.cite,
139 })
140}
141
142pub fn overlay(base: &mut Vec<RuntimeRoute>, own: Vec<RuntimeRoute>) {
144 for r in own {
145 match base.iter_mut().find(|b| b.id == r.id) {
146 Some(slot) => *slot = r,
147 None => base.push(r),
148 }
149 }
150}
151
152impl RuntimeRoute {
153 pub fn applies(&self, policy: &Policy, mode: Mode, rows: u64) -> bool {
155 self.modes.contains(&mode)
156 && (self.rows.0..=self.rows.1).contains(&rows)
157 && self
158 .when
159 .iter()
160 .all(|(k, v)| policy.settings.get(k) == Some(v))
161 }
162
163 pub fn policy(&self, policy: &Policy) -> Policy {
165 let mut out = policy.clone();
166 for (k, v) in &self.plans_as {
167 out.settings.insert(k.clone(), v.clone());
168 }
169 out
170 }
171
172 pub fn plans_as_text(&self) -> String {
174 let s: Vec<String> = self
175 .plans_as
176 .iter()
177 .map(|(k, v)| format!("{k}={v}"))
178 .collect();
179 s.join(" ")
180 }
181}
182
183pub fn route_arms(
186 circuit: &Circuit,
187 set: (&[Rule], &[RuntimeRoute]),
188 available: &AvailableKernels,
189 policy: &Policy,
190 primary: &FusionPlan,
191) -> Result<Vec<(RuntimeRoute, FusionPlan)>, FuseError> {
192 let (rules, runtime) = set;
193 let (mode, rows) = (primary.mode, primary.rows);
194 let mut out = Vec::new();
195 for r in runtime.iter().filter(|r| r.applies(policy, mode, rows)) {
196 let arm = fuse(circuit, rules, available, &r.policy(policy), mode, rows)?;
197 if arm.digest != primary.digest {
198 out.push((r.clone(), arm));
199 }
200 }
201 Ok(out)
202}