1use std::collections::{BTreeMap, BTreeSet};
10
11use serde::Deserialize;
12
13use super::{KernelId, Mode, Numerics, PatternOp, Repeat, Rule, RuleError};
14use crate::format::Format;
15use crate::ir::{LayerKind, LinearRole, OpKind};
16
17#[derive(Deserialize)]
18#[serde(deny_unknown_fields)]
19struct RulesFile {
20 schema: u32,
21 rule: Vec<RuleFile>,
22 #[serde(default)]
23 runtime: Vec<crate::runtime::RouteFile>,
24}
25
26#[derive(Deserialize)]
27#[serde(deny_unknown_fields)]
28struct RuleFile {
29 id: String,
30 pattern: Vec<PatternFile>,
31 kernels: Vec<KernelFile>,
32 repeat: String,
33 copies: Option<String>,
34 emitter: String,
35 rows: [u64; 2],
36 modes: Vec<String>,
37 #[serde(default)]
38 requires: Vec<String>,
39 #[serde(default)]
40 when: BTreeMap<String, String>,
41 numerics: String,
42 microtest: Option<String>,
43 lever: Option<String>,
44 priority: i64,
45 cite: String,
46 #[serde(default)]
47 run: Vec<RunFile>,
48}
49
50#[derive(Deserialize)]
52#[serde(deny_unknown_fields)]
53struct RunFile {
54 k: [u64; 2],
55 n: [u64; 2],
56 contiguous: Option<bool>,
57 carried: Option<bool>,
58 launches: Vec<LaunchFile>,
59 copies: Option<String>,
60}
61
62#[derive(Deserialize)]
63#[serde(deny_unknown_fields)]
64struct LaunchFile {
65 module: String,
66 func: String,
67 times: String,
68}
69
70#[derive(Deserialize)]
71#[serde(deny_unknown_fields)]
72struct KernelFile {
73 module: String,
74 func: String,
75}
76
77#[derive(Deserialize)]
78#[serde(deny_unknown_fields)]
79struct PatternFile {
80 op: String,
81 role: Option<String>,
82 #[serde(default)]
83 roles: Vec<String>,
84 layer_kind: Option<String>,
85 format: Option<String>,
86 local: Option<String>,
87 weight: Option<String>,
88 input: Option<String>,
89 writes: Option<String>,
90 #[serde(default)]
91 keep: bool,
92 #[serde(default)]
93 stored: bool,
94 #[serde(default)]
95 sibling: bool,
96 holds: Option<String>,
97 #[serde(default)]
98 steps: BTreeMap<String, String>,
99}
100
101pub fn parse_rules(text: &str) -> Result<Vec<Rule>, RuleError> {
105 let (rules, routes) = parse_file(text)?;
106 if let Some(r) = routes.first() {
107 return Err(RuleError::Runtime {
108 route: r.id.clone(),
109 detail: "a rule set with runtime routes is read with `parse_rule_set`".into(),
110 });
111 }
112 Ok(rules)
113}
114
115pub(crate) fn parse_file(
117 text: &str,
118) -> Result<(Vec<Rule>, Vec<crate::runtime::RouteFile>), RuleError> {
119 let file: RulesFile = toml::from_str(text).map_err(|e| RuleError::Parse(e.to_string()))?;
120 if file.schema != 1 {
121 return Err(RuleError::Parse(format!(
122 "schema {} (this build reads 1)",
123 file.schema
124 )));
125 }
126 let mut seen = BTreeSet::new();
127 let mut out = Vec::with_capacity(file.rule.len());
128 for r in file.rule {
129 if !seen.insert(r.id.clone()) {
130 return Err(RuleError::DuplicateId(r.id));
131 }
132 out.push(rule(r)?);
133 }
134 Ok((out, file.runtime))
135}
136
137fn rule(r: RuleFile) -> Result<Rule, RuleError> {
138 let shape = |detail: &str| RuleError::Shape {
139 rule: r.id.clone(),
140 detail: detail.to_string(),
141 };
142 if r.pattern.is_empty() {
143 return Err(shape("empty pattern"));
144 }
145 if r.modes.is_empty() {
146 return Err(shape("empty modes"));
147 }
148 if r.rows[0] == 0 || r.rows[0] > r.rows[1] {
149 return Err(shape("rows must be [lo, hi] with 1 <= lo <= hi"));
150 }
151 if r.cite.trim().is_empty() {
152 return Err(shape("empty cite"));
153 }
154 let mut modes = BTreeSet::new();
155 for m in &r.modes {
156 modes.insert(Mode::parse(m).ok_or_else(|| shape(&format!("unknown mode `{m}`")))?);
157 }
158 let repeat = Repeat::parse(&r.repeat).ok_or_else(|| {
159 shape(&format!(
160 "unknown repeat `{}` (once | per_row | per_row_but_last | per_run | chunk<n>)",
161 r.repeat
162 ))
163 })?;
164 let kernels: Vec<KernelId> = r
165 .kernels
166 .iter()
167 .map(|k| KernelId {
168 module: k.module.clone(),
169 func: k.func.clone(),
170 })
171 .collect();
172 let runs = runs(&r, &kernels)?;
173 if (repeat == Repeat::PerRun) == runs.is_empty() {
174 return Err(shape(
175 "`per_run` needs `[[rule.run]]` selectors, and only a `per_run` rule may have them",
176 ));
177 }
178 if repeat == Repeat::PerRun
179 && (r.copies.is_some() || modes.iter().any(|m| *m != Mode::VerifyBatch))
180 {
181 return Err(shape(
182 "a `per_run` rule serves `verify_batch` only and states copies per run",
183 ));
184 }
185 let copies = r
186 .copies
187 .as_deref()
188 .map(|c| {
189 Repeat::parse(c).ok_or_else(|| {
190 shape(&format!(
191 "unknown copies `{c}` (once | per_row | per_row_but_last | chunk<n>)"
192 ))
193 })
194 })
195 .transpose()?;
196 if r.pattern[0].sibling {
197 return Err(shape("the first pattern element cannot be a sibling"));
198 }
199 let numerics = numerics(&r)?;
200 let mut pattern = Vec::with_capacity(r.pattern.len());
201 for p in &r.pattern {
202 pattern.push(pattern_op(&r.id, p)?);
203 }
204 Ok(Rule {
205 pattern,
206 kernels,
207 repeat,
208 copies,
209 emitter: r.emitter,
210 rows: (r.rows[0], r.rows[1]),
211 modes,
212 requires: r.requires.into_iter().collect(),
213 when: r.when,
214 numerics,
215 priority: r.priority,
216 cite: r.cite,
217 runs,
218 id: r.id,
219 })
220}
221
222fn runs(r: &RuleFile, kernels: &[KernelId]) -> Result<Vec<crate::runs::RunSelect>, RuleError> {
224 use crate::runs::{RunSelect, Times};
225 let bad = |detail: String| RuleError::Shape {
226 rule: r.id.clone(),
227 detail,
228 };
229 let times = |s: &str| {
230 Times::parse(s).ok_or_else(|| {
231 bad(format!(
232 "unknown times `{s}` (once | per_seq | per_row | per_seq_row_but_last)"
233 ))
234 })
235 };
236 let mut out = Vec::with_capacity(r.run.len());
237 for f in &r.run {
238 if f.k[0] == 0 || f.k[0] > f.k[1] || f.n[0] == 0 || f.n[0] > f.n[1] {
239 return Err(bad(format!("run selector k {:?} n {:?}", f.k, f.n)));
240 }
241 if f.launches.is_empty() {
242 return Err(bad("a run selector with no launches".into()));
243 }
244 let mut launches = Vec::with_capacity(f.launches.len());
245 for l in &f.launches {
246 let id = KernelId {
247 module: l.module.clone(),
248 func: l.func.clone(),
249 };
250 if !kernels.contains(&id) {
251 return Err(bad(format!(
252 "run selector launches {id}, which `kernels` does not list"
253 )));
254 }
255 launches.push((id, times(&l.times)?));
256 }
257 out.push(RunSelect {
258 k: (f.k[0], f.k[1]),
259 n: (f.n[0], f.n[1]),
260 contiguous: f.contiguous,
261 carried: f.carried,
262 launches,
263 copies: f.copies.as_deref().map(times).transpose()?,
264 });
265 }
266 Ok(out)
267}
268
269fn numerics(r: &RuleFile) -> Result<Numerics, RuleError> {
270 let stray = |what: &str| RuleError::Numerics {
271 rule: r.id.clone(),
272 detail: format!("`{what}` is not allowed on a `{}` rule", r.numerics),
273 };
274 match r.numerics.as_str() {
275 "bit_identical" => {
276 if r.lever.is_some() {
277 return Err(stray("lever"));
278 }
279 match &r.microtest {
280 Some(m) if !m.trim().is_empty() => Ok(Numerics::BitIdentical {
281 microtest: m.clone(),
282 }),
283 _ => Err(RuleError::MissingMicrotest(r.id.clone())),
284 }
285 }
286 "reference" => {
287 if r.lever.is_some() {
288 return Err(stray("lever"));
289 }
290 if r.microtest.is_some() {
291 return Err(stray("microtest"));
292 }
293 Ok(Numerics::Reference)
294 }
295 "differs" => {
296 if r.microtest.is_some() {
297 return Err(stray("microtest"));
298 }
299 match &r.lever {
300 Some(l) if !l.trim().is_empty() => Ok(Numerics::Differs { lever: l.clone() }),
301 _ => Err(RuleError::MissingLever(r.id.clone())),
302 }
303 }
304 other => Err(RuleError::Numerics {
305 rule: r.id.clone(),
306 detail: format!("unknown numerics class `{other}`"),
307 }),
308 }
309}
310
311fn pattern_op(rule: &str, p: &PatternFile) -> Result<PatternOp, RuleError> {
312 let err = |detail: String| RuleError::Op {
313 rule: rule.to_string(),
314 detail,
315 };
316 let fmt = |s: &Option<String>| -> Result<Option<Format>, RuleError> {
317 s.as_deref()
318 .map(Format::parse)
319 .transpose()
320 .map_err(|e| err(e.to_string()))
321 };
322 let mut roles = BTreeSet::new();
323 for r in &p.roles {
324 roles
325 .insert(LinearRole::parse(r).ok_or_else(|| err(format!("unknown linear role `{r}`")))?);
326 }
327 let role = match (p.role.as_deref(), p.roles.first()) {
328 (Some(_), Some(_)) => return Err(err("give `role` or `roles`, not both".into())),
329 (Some(r), None) => Some(r),
330 (None, first) => first.map(String::as_str),
331 };
332 let op = OpKind::parse(&p.op, role, fmt(&p.format)?).map_err(|e| err(e.to_string()))?;
333 let layer_kind = p
334 .layer_kind
335 .as_deref()
336 .map(|k| LayerKind::parse(k).ok_or_else(|| err(format!("unknown layer kind `{k}`"))))
337 .transpose()?;
338 let mut steps = BTreeMap::new();
340 for (name, text) in &p.steps {
341 let kind = crate::pipeline::vocab::parse_step(name)
342 .filter(|k| crate::pipeline::vocab::steps_of(&op).contains(k))
343 .ok_or_else(|| {
344 err(format!(
345 "`{name}` is not a pipeline step of `{}`",
346 op.name()
347 ))
348 })?;
349 let value = crate::pipeline::vocab::parse_value(kind, text)
350 .map_err(|e| err(format!("step `{name}`: {e}")))?;
351 steps.insert(kind, value);
352 }
353 Ok(PatternOp {
354 op,
355 roles,
356 layer_kind,
357 local: p.local.clone(),
358 weight: fmt(&p.weight)?,
359 input: fmt(&p.input)?,
360 writes: fmt(&p.writes)?,
361 keep: p.keep,
362 stored: p.stored,
363 sibling: p.sibling,
364 holds: fmt(&p.holds)?,
365 steps,
366 })
367}