vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
//! Independence certificate — rejects tests that derive their expected
//! value from the op under test.
//!
//! # The threat
//!
//! The load-bearing property of vyre-conform is that every test's oracle is
//! *independent* of the code it tests. A test that looks like:
//!
//! ```ignore
//! let expected = specs::primitive::add::spec().cpu_fn(input);
//! assert_eq!(backend.dispatch(...), expected);
//! ```
//!
//! proves nothing: any change to `add::spec().cpu_fn` — including a bug —
//! automatically "updates" the expected value, and the assertion holds by
//! definition. This is the single worst sin a meta-test can commit, and
//! the one failure mode humans routinely ship in test suites.
//!
//! # The gate
//!
//! The independence certificate is a compile-time (emission-time) syn
//! walk over every generated test. It rejects any test whose assertion
//! would be satisfied by a tautology. Emitted tests that do not earn a
//! certificate are never written to `tests_generated/` — the generator
//! returns [`crate::generate::emit::GenError::TautologicalTest`] instead.
//!
//! # What is allowed
//!
//! - Hand-written literal expected values: `let expected = 42u32;`
//! - Reference interpreter results:
//!   `let expected = vyre_reference::run(&program, &inputs)?;`
//! - Law formulas evaluated symbolically over the op's output (e.g.,
//!   commutativity: `assert_eq!(f(a,b), f(b,a))` — no expected binding
//!   at all; the law is the oracle).
//! - Spec-table rows looked up by index: `let expected = ADD_ROWS[0].expected;`
//!
//! # What is forbidden
//!
//! - Binding the `expected`/`target`/`want` variable to a call that
//!   resolves into the op-under-test's implementation path
//!   (`specs::primitive::<op>::...`, `ops::<op>::...`, or anything whose
//!   last path segment matches the op's id).
//! - Calling `cpu_fn(...)` or `wgsl_fn(...)` on a spec whose id matches
//!   the op under test.
//! - Any form of "compute expected by invoking the thing we're testing."
//!
//! # Limitations
//!
//! This is a static, conservative check. It prefers false positives to
//! false negatives — a test that uses an unusual binding name can simply
//! rename the variable to something outside the `EXPECTED_NAMES` set, and
//! a contributor who needs to bypass the check for a legitimate case can
//! use `#[allow(tautological_test)]` which logs a warning but does not
//! block emission. The warning is picked up by the daily calibration
//! audit (PLAN.md §13) so bypasses are reviewed by a human.

use std::collections::{HashMap, HashSet};

use syn::spanned::Spanned;
use syn::visit::{self, Visit};
use syn::{Expr, ExprCall, ExprMethodCall, ExprPath, Local, Pat, PatIdent};

/// Paths the gate considers "through the op under test" regardless of
/// the op's own name. These are always forbidden on the right-hand side
/// of an expected-binding, because they name functions whose output is
/// exactly the implementation output we are testing.
const FORBIDDEN_CALL_SEGMENTS: &[&str] = &["cpu_fn", "wgsl_fn"];

/// Paths that are explicitly allowed on the RHS of an expected-binding.
/// The reference interpreter is the oracle for I3 and I8 and must be
/// independent of every backend — so calling it is the CORRECT way to
/// derive expected values in backend parity tests.
const ALLOWED_CALL_SEGMENTS: &[&str] = &["run", "resolve"];

/// Macros whose expansion is inert for independence purposes after the
/// token stream has been checked for forbidden operation segments.
const ALLOWED_MACRO_SEGMENTS: &[&str] = &[
    "assert",
    "assert_eq",
    "assert_ne",
    "debug_assert",
    "debug_assert_eq",
    "debug_assert_ne",
    "format",
    "panic",
    "vec",
];

/// Result of running the independence check on one generated test.
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct IndependenceCertificate {
    /// The op id whose test this certificate covers (copied from the
    /// generator context, not inferred from the source).
    pub op_name: String,
    /// The verdict: is the test independent of the op, or suspect?
    pub verdict: CertificateVerdict,
}

/// The verdict of the independence check.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum CertificateVerdict {
    /// The test passes the static independence check. Safe to emit.
    Independent,
    /// The test derives its expected value from a call path that leads
    /// back into the op under test, a spec's `cpu_fn`, or similar. The
    /// gate refuses to emit.
    SuspectedTautology {
        /// Source line of the offending expected-binding.
        line: usize,
        /// Qualified path of the forbidden call, e.g.,
        /// `specs::primitive::add::spec::cpu_fn`.
        call_path: String,
        /// Actionable hint written into the generator error surface.
        hint: String,
    },
}

/// Walk `rust_source` and return the independence certificate for a test
/// of `op_under_test`.
///
/// # Errors
///
/// Returns a parse error if `rust_source` is not syntactically valid
/// Rust. Callers upstream of this function in `emit::write_test` already
/// validate the source via `syn::parse_file`, so this path is normally
/// unreachable — but we still return an honest error rather than
/// panicking, per LAW 1.
#[inline]
pub fn verify_test_independence(
    rust_source: &str,
    op_under_test: &str,
) -> Result<IndependenceCertificate, IndependenceError> {
    let file = syn::parse_file(rust_source).map_err(|err| IndependenceError::InvalidRust {
        reason: err.to_string(),
    })?;

    for item in &file.items {
        if let syn::Item::Fn(f) = item {
            if has_allow_tautological_test(&f.attrs) {
                tracing::warn!(
                    op = op_under_test,
                    function = %f.sig.ident,
                    "independence check bypassed via #[allow(tautological_test)]"
                );
                return Ok(IndependenceCertificate {
                    op_name: op_under_test.to_string(),
                    verdict: CertificateVerdict::Independent,
                });
            }
        }
    }

    let forbidden_segments = build_forbidden_set(op_under_test);

    // F1: Collect local functions for transitive scanning
    let mut local_fns = HashMap::new();
    for item in &file.items {
        if let syn::Item::Fn(f) = item {
            local_fns.insert(f.sig.ident.to_string(), f);
        }
    }

    let mut visitor = IndependenceVisitor {
        op_under_test,
        forbidden: &forbidden_segments,
        verdict: CertificateVerdict::Independent,
        local_fns,
        local_closures: HashMap::new(),
        scanning_stack: HashSet::new(),
    };
    visitor.visit_file(&file);

    Ok(IndependenceCertificate {
        op_name: op_under_test.to_string(),
        verdict: visitor.verdict,
    })
}

/// Static independence check errors.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum IndependenceError {
    /// The source passed in does not parse as Rust. Upstream callers
    /// should have rejected this first; returning an error rather than
    /// panicking keeps the contract honest.
    InvalidRust {
        /// Parser diagnostic from `syn`.
        reason: String,
    },
}

impl std::fmt::Display for IndependenceError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::InvalidRust { reason } => write!(
                f,
                "independence checker received invalid Rust. Fix: {reason}"
            ),
        }
    }
}

impl std::error::Error for IndependenceError {}

fn has_allow_tautological_test(attrs: &[syn::Attribute]) -> bool {
    attrs.iter().any(|attr| {
        let syn::Meta::List(meta_list) = &attr.meta else {
            return false;
        };
        meta_list.path.is_ident("allow")
            && meta_list.tokens.to_string().contains("tautological_test")
    })
}

fn build_forbidden_set(op_under_test: &str) -> HashSet<String> {
    assert!(!op_under_test.is_empty(), "op_under_test must be non-empty");
    assert!(
        !op_under_test.ends_with('.') && !op_under_test.ends_with("::"),
        "op_under_test `{op_under_test}` must not end with a path delimiter. \
         Fix: strip trailing `.` or `::`.",
    );

    let mut set: HashSet<String> = FORBIDDEN_CALL_SEGMENTS
        .iter()
        .copied()
        .map(String::from)
        .collect();
    // Terminal segment of a dotted id (e.g., "primitive.bitwise.xor" -> "xor").
    let dotted_last: &str = op_under_test.rsplit('.').next().unwrap_or("");
    if !dotted_last.is_empty() {
        set.insert(dotted_last.to_string());
    }
    // Terminal segment of a module-style id (e.g.,
    // "primitive::bitwise::xor" -> "xor").
    let module_last: &str = op_under_test.rsplit("::").next().unwrap_or("");
    if !module_last.is_empty() {
        set.insert(module_last.to_string());
    }
    set
}

struct IndependenceVisitor<'a> {
    op_under_test: &'a str,
    forbidden: &'a HashSet<String>,
    verdict: CertificateVerdict,
    /// F1: Map of local function names to their definitions for transitive scanning.
    local_fns: HashMap<String, &'a syn::ItemFn>,
    /// F2: Map of locally-bound closures for transitive scanning.
    local_closures: HashMap<String, &'a syn::ExprClosure>,
    /// F1/F2: Recursion stack to prevent infinite loops in the call graph.
    scanning_stack: HashSet<String>,
}

impl<'ast> Visit<'ast> for IndependenceVisitor<'ast> {
    fn visit_local(&mut self, local: &'ast Local) {
        // F2: Track local closures so calls to them can be transitively scanned.
        if let Pat::Ident(PatIdent { ident, .. }) = &local.pat {
            if let Some(init) = &local.init {
                if let Expr::Closure(closure) = &*init.expr {
                    self.local_closures.insert(ident.to_string(), closure);
                }
            }
        }
        visit::visit_local(self, local);
    }

    fn visit_expr(&mut self, i: &'ast Expr) {
        if matches!(self.verdict, CertificateVerdict::Independent) {
            match i {
                Expr::Call(call) => self.scan_call(call, expr_line(i)),
                Expr::MethodCall(method) => self.scan_method_call(method, expr_line(i)),
                Expr::Macro(m) => self.scan_macro_direct(&m.mac, expr_line(i)),
                _ => {}
            }
        }
        visit::visit_expr(self, i);
    }

    fn visit_macro(&mut self, i: &'ast syn::Macro) {
        if matches!(self.verdict, CertificateVerdict::Independent) {
            self.scan_macro_direct(i, span_line(i.span()));
        }
        visit::visit_macro(self, i);
    }
}

impl IndependenceVisitor<'_> {
    fn scan_call(&mut self, call: &ExprCall, line: usize) {
        let Expr::Path(ExprPath { path, .. }) = call.func.as_ref() else {
            return;
        };
        let last = match path.segments.last() {
            Some(segment) => segment.ident.to_string(),
            None => return,
        };

        if ALLOWED_CALL_SEGMENTS.contains(&last.as_str()) {
            return;
        }

        if self.forbidden.contains(&last) {
            self.verdict = CertificateVerdict::SuspectedTautology {
                line,
                call_path: path_to_string(path),
                hint: format!(
                    "this test derives its oracle from a call to `{last}`, which \
                     resolves into the op under test ({op}). Fix: derive expected \
                     from (a) a hand-written literal in the spec table, (b) \
                     vyre_reference::run on an independent IR program, or (c) \
                     a law formula evaluated on the output.",
                    op = self.op_under_test,
                ),
            };
            return;
        }

        // F1/F2: Intra-source call graph / closure laundering
        if !self.scanning_stack.contains(&last) {
            if let Some(f) = self.local_fns.get(&last).copied() {
                self.scanning_stack.insert(last.clone());
                // Transitively scan the helper function body
                visit::visit_item_fn(self, f);
                self.scanning_stack.remove(&last);
            } else if let Some(closure) = self.local_closures.get(&last).copied() {
                self.scanning_stack.insert(last.clone());
                // Transitively scan the closure body
                visit::visit_expr(self, &closure.body);
                self.scanning_stack.remove(&last);
            }
        }
    }

    fn scan_method_call(&mut self, method: &ExprMethodCall, line: usize) {
        let ident = method.method.to_string();
        if ALLOWED_CALL_SEGMENTS.contains(&ident.as_str()) {
            return;
        }
        if self.forbidden.contains(&ident) {
            self.verdict = CertificateVerdict::SuspectedTautology {
                line,
                call_path: format!(".{ident}()"),
                hint: format!(
                    "this test derives its oracle from `.{ident}()`, which on \
                     a spec object resolves into the op under test ({op}). Derive \
                     expected from a literal, the reference interpreter, or a law \
                     formula — never from a call on the spec being tested.",
                    op = self.op_under_test,
                ),
            };
        }
    }

    fn scan_macro_direct(&mut self, mac: &syn::Macro, line: usize) {
        let Some(last) = mac.path.segments.last() else {
            self.reject_unverified_macro("<empty>", line);
            return;
        };
        let macro_name = last.ident.to_string();
        if self.forbidden.contains(&macro_name) {
            self.verdict = CertificateVerdict::SuspectedTautology {
                line,
                call_path: format!("{}!", path_to_string(&mac.path)),
                hint: format!(
                    "this test invokes macro `{macro_name}!` which resolves into \
                     the op under test ({op}). Fix: derive expected from a literal \
                     spec row, vyre_reference::run, or a law formula.",
                    op = self.op_under_test,
                ),
            };
            return;
        }

        let token_text = mac.tokens.to_string();
        if let Some(segment) = self
            .forbidden
            .iter()
            .find(|segment| token_contains_segment(&token_text, segment))
        {
            self.verdict = CertificateVerdict::SuspectedTautology {
                line,
                call_path: format!("{}!(... {segment} ...)", path_to_string(&mac.path)),
                hint: format!(
                    "this macro invocation contains `{segment}` while computing \
                     an oracle for {op}. Macros are opaque before expansion, so \
                     the independence gate rejects the value. Fix: move the oracle \
                     to a literal spec row, vyre_reference::run, or an explicit law formula.",
                    op = self.op_under_test,
                ),
            };
            return;
        }

        if !ALLOWED_MACRO_SEGMENTS.contains(&macro_name.as_str()) {
            self.reject_unverified_macro(&macro_name, line);
        }
    }

    fn reject_unverified_macro(&mut self, macro_name: &str, line: usize) {
        self.verdict = CertificateVerdict::SuspectedTautology {
            line,
            call_path: format!("{macro_name}!"),
            hint: format!(
                "macro `{macro_name}!` is not in the independence gate's safe \
                 macro list, so its expansion cannot be verified before emit. \
                 Fix: replace it with explicit Rust expressions or add a narrow \
                 audited scanner rule before using it in generated tests for {op}.",
                op = self.op_under_test,
            ),
        };
    }
}

fn expr_line(expr: &syn::Expr) -> usize {
    use syn::spanned::Spanned;
    expr.span().start().line
}

fn span_line(span: proc_macro2::Span) -> usize {
    span.start().line
}

fn path_to_string(path: &syn::Path) -> String {
    path.segments
        .iter()
        .map(|segment| segment.ident.to_string())
        .collect::<Vec<_>>()
        .join("::")
}

fn token_contains_segment(tokens: &str, segment: &str) -> bool {
    tokens
        .split(|ch: char| !(ch.is_ascii_alphanumeric() || ch == '_'))
        .any(|token| token == segment)
}

#[cfg(test)]
mod tests;