metrale_circuit/hardware/
tc_policy_lint.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! 2026-10-02: The tensor-core policy over a class's whole rule set, not only the plans a report
4//! runs: every rule that could cover a policy op off tensor cores, at any mode and row count the
5//! rule and the policy share, needs an exemption that lists the op, those modes, those rows and
6//! every kernel of the rule. A new FUSIONS.toml rule or kernel that would violate the policy at a
7//! row count no golden plan or report exercises fails here.
8//!
9//! Owner: metrale-circuit (hardware).
10//! Invariants: pure; the answer is a list of messages, empty when the rule set complies.
11
12use 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
20/// 2026-10-02: Whether a requirement's weights can meet a pattern element's (`None`: any).
21fn 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
28/// 2026-10-02: Every rule-level violation of `policy` in `rules`, one message each.
29pub 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}