metrale_circuit/hardware/
tc_policy_lint.rs1use std::collections::BTreeSet;
13
14use super::{TcPolicy, unit_of_group};
15use crate::format::Format;
16use crate::ir::OpKind;
17use crate::rules::{Mode, Rule};
18use crate::venn::families::Families;
19
20fn weights_meet(w: &super::Weights, pattern: Option<Format>) -> bool {
22 match (w, pattern) {
23 (super::Weights::Any, _) | (_, None) => true,
24 (super::Weights::Only(set), Some(f)) => set.contains(&f),
25 }
26}
27
28pub fn lint_rules(rules: &[Rule], families: &Families, policy: &TcPolicy) -> Vec<String> {
30 let mut out = Vec::new();
31 for rule in rules.iter().filter(|r| !r.kernels.is_empty()) {
32 let unit = match unit_of_group(families, &rule.kernels) {
33 Ok(u) => u,
34 Err(e) => {
35 out.push(format!("rule `{}`: {e}", rule.id));
36 continue;
37 }
38 };
39 if unit.as_ref().is_some_and(|u| u.is_tensor_core()) {
40 continue;
41 }
42 for p in &rule.pattern {
43 let ops: Vec<OpKind> = if p.roles.is_empty() {
44 vec![p.op]
45 } else {
46 p.roles.iter().map(|r| OpKind::Linear(*r)).collect()
47 };
48 for op in ops {
49 for req in &policy.require {
50 let modes: BTreeSet<Mode> =
51 rule.modes.intersection(&req.modes).copied().collect();
52 let (lo, hi) = (rule.rows.0.max(req.min_rows), rule.rows.1);
53 if modes.is_empty()
54 || lo > hi
55 || !req.ops.iter().any(|o| o.matches(&op))
56 || !weights_meet(&req.weights, p.weight)
57 {
58 continue;
59 }
60 let allowed = policy.exempt.iter().any(|e| {
61 e.ops.iter().any(|o| o.matches(&op))
62 && modes.is_subset(&e.modes)
63 && e.rows.0 <= lo
64 && hi <= e.rows.1
65 && rule.kernels.iter().all(|k| e.kernels.contains(k))
66 });
67 if !allowed {
68 let modes: Vec<&str> = modes.iter().map(|m| m.name()).collect();
69 out.push(format!(
70 "rule `{}` runs {} off tensor cores at {} rows {lo}-{hi}, and no exemption lists it",
71 rule.id,
72 op.name(),
73 modes.join("/")
74 ));
75 }
76 }
77 }
78 }
79 }
80 out
81}