Skip to main content

vyre_lower/
lib.rs

1#![allow(
2    clippy::doc_lazy_continuation,
3    clippy::double_must_use,
4    clippy::manual_div_ceil,
5    clippy::needless_range_loop,
6    clippy::collapsible_if,
7    clippy::match_like_matches_macro,
8    clippy::redundant_closure
9)]
10//! Substrate-neutral lowering for vyre.
11//!
12//! Source-of-truth: `SEPARATION_AUDIT_2026-05-01.md` section S3.
13//!
14//! Backend drivers used to own lowering, emission, and dispatch in one
15//! crate. That made common lowering unshareable, let emit patterns drift,
16//! and left substrate-aware-but-driver-agnostic optimizations without a
17//! home between lower and emit.
18//!
19//! This crate creates the boundary:
20//!
21//! ```text
22//! vyre-foundation Program
23//!         ↓ lower(program)
24//! KernelDescriptor (this crate's pub type)
25//!         ↓
26//! emit crate
27//!         ↓
28//! backend artifact
29//!         ↓
30//! driver dispatch
31//! ```
32//!
33//! `KernelDescriptor` is the substrate-neutral kernel intermediate
34//! representation  -  binding layout, dispatch shape, lowered kernel
35//! body. NOT the same as `vyre_foundation::Program` (which is the
36//! pre-lowered IR with high-level constructs like `Node::Region`).
37//! Drivers stay thin: take a backend artifact + bind buffers + dispatch.
38
39pub mod analyses;
40pub mod audit;
41pub mod descriptor;
42pub mod emit_adversarial_corpus;
43pub mod error;
44pub mod lower;
45pub(crate) mod op_properties;
46pub(crate) mod operand_semantics;
47pub mod optimization_corpus;
48pub mod pre_emit;
49pub mod rewrites;
50pub mod verify;
51
52pub use audit::{
53    audit, audit_optimized, audit_with_histogram, PerfAuditReport, Recommendation,
54    RecommendationCategory,
55};
56
57/// Full-power entry point: verify the input descriptor, run the
58/// optimization pipeline, verify the optimized output. Returns the
59/// optimized descriptor + stats on success; on failure returns
60/// whichever verify step failed first.
61///
62/// `emit_optimized` in the emit crates only `debug_assert!`s the
63/// output. This entry point promotes both checks to errors that
64/// production callers can route.
65pub fn verify_then_optimize(
66    desc: &KernelDescriptor,
67) -> Result<(KernelDescriptor, rewrites::OptimizationStats), VerifyFailure> {
68    if let Err(errs) = verify::verify(desc) {
69        return Err(VerifyFailure::Input(errs));
70    }
71    let (optimized, stats) = rewrites::run_all_with_stats(desc);
72    if let Err(errs) = verify::verify(&optimized) {
73        return Err(VerifyFailure::Output(errs));
74    }
75    Ok((optimized, stats))
76}
77
78/// Which verify step failed in [`verify_then_optimize`].
79#[derive(Debug, Clone, PartialEq, Eq)]
80pub enum VerifyFailure {
81    /// Input descriptor was invalid before any rewrites ran.
82    Input(Vec<verify::VerifyError>),
83    /// The rewrite pipeline produced an invalid descriptor  -  a real
84    /// bug in the rewrite stack. The fuzz harness gates this; if you
85    /// hit it in production, it's a bug to file.
86    Output(Vec<verify::VerifyError>),
87}
88
89impl VerifyFailure {
90    pub fn errors(&self) -> &[verify::VerifyError] {
91        match self {
92            VerifyFailure::Input(e) | VerifyFailure::Output(e) => e,
93        }
94    }
95}
96
97/// Single-call diagnostic: runs every analysis vyre-lower offers
98/// (summary, histogram, perf audit, verify, optimization stats from
99/// the standard pipeline) and bundles them into a single report.
100/// Useful for tooling that wants a complete picture without N
101/// separate function calls.
102#[must_use]
103pub fn full_report(desc: &KernelDescriptor) -> FullReport {
104    let descriptor_id = desc.id.clone();
105    let summary = desc.summary();
106    let histogram = analyses::op_histogram::analyze(desc);
107    let perf = audit::audit(desc);
108    let verify_input = verify::verify(desc);
109    let (optimized_summary, verify_output, stats, optimization_ran) = if verify_input.is_ok() {
110        let (optimized, stats) = rewrites::run_all_with_stats(desc);
111        let verify_output = verify::verify(&optimized);
112        (optimized.summary(), verify_output, stats, true)
113    } else {
114        (
115            summary.clone(),
116            Ok(()),
117            optimization_skipped_stats(desc),
118            false,
119        )
120    };
121    let fix_text = build_full_report_fix_text(
122        &verify_input,
123        optimization_ran.then_some(&verify_output),
124    );
125    FullReport {
126        descriptor_id,
127        summary,
128        optimized_summary,
129        histogram,
130        perf,
131        verify_input,
132        verify_output,
133        stats,
134        optimization_ran,
135        fix_text,
136    }
137}
138
139/// Bundle returned by [`full_report`]. Five orthogonal views into
140/// the descriptor + standard pipeline output.
141#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
142pub struct FullReport {
143    #[serde(default)]
144    pub descriptor_id: String,
145    pub summary: String,
146    pub optimized_summary: String,
147    pub histogram: analyses::op_histogram::OpHistogram,
148    pub perf: PerfAuditReport,
149    pub verify_input: verify::VerifyResult,
150    #[serde(default = "default_verify_result")]
151    pub verify_output: verify::VerifyResult,
152    pub stats: rewrites::OptimizationStats,
153    #[serde(default)]
154    pub optimization_ran: bool,
155    #[serde(default)]
156    pub fix_text: String,
157}
158
159impl FullReport {
160    #[must_use]
161    pub fn verify_input_status(&self) -> &'static str {
162        verification_status(&self.verify_input)
163    }
164
165    #[must_use]
166    pub fn verify_output_status(&self) -> &'static str {
167        if self.optimization_ran {
168            verification_status(&self.verify_output)
169        } else {
170            "SKIPPED"
171        }
172    }
173
174    /// One-line headline drawn from the underlying parts. Useful for
175    /// log lines.
176    pub fn format_short(&self) -> String {
177        format!(
178            "{} | id {} | {} | {} | {} | input verify {} | output verify {}",
179            self.summary,
180            self.descriptor_id,
181            self.histogram.format_short(),
182            self.perf.format_short(),
183            self.stats.format_short(),
184            self.verify_input_status(),
185            self.verify_output_status(),
186        )
187    }
188
189    /// Multi-line human-readable view, suitable for `--verbose` CLI
190    /// output. Each section has a header and is indented for readability.
191    pub fn format_long(&self) -> String {
192        let mut out = String::new();
193        use std::fmt::Write as _;
194        let _ = writeln!(out, "Kernel:");
195        let _ = writeln!(out, "  descriptor id: {}", self.descriptor_id);
196        let _ = writeln!(out, "  raw:       {}", self.summary);
197        let _ = writeln!(out, "  optimized: {}", self.optimized_summary);
198        let _ = writeln!(out, "Histogram:");
199        let _ = writeln!(out, "  {}", self.histogram.format_short());
200        if let Some((cat, n)) = self.histogram.dominant() {
201            let _ = writeln!(out, "  dominant: {cat} ({n})");
202        }
203        let _ = writeln!(out, "Perf audit:");
204        let _ = writeln!(out, "  {}", self.perf.format_short());
205        for r in &self.perf.recommendations {
206            let _ = writeln!(
207                out,
208                "  - [p{}] {:?}: {} (≤{:.2}× speedup)",
209                r.priority, r.category, r.message, r.estimated_speedup_upper_bound
210            );
211        }
212        let _ = writeln!(out, "Optimization:");
213        let _ = writeln!(out, "  {}", self.stats.format_short());
214        let _ = writeln!(out, "Verify (input):");
215        write_verify_section(&mut out, &self.verify_input);
216        let _ = writeln!(out, "Verify (output):");
217        if self.optimization_ran {
218            write_verify_section(&mut out, &self.verify_output);
219        } else {
220            let _ = writeln!(out, "  SKIPPED (input verification failed)");
221        }
222        if !self.fix_text.is_empty() {
223            let _ = writeln!(out, "Fix:");
224            let _ = writeln!(out, "  {}", self.fix_text);
225        }
226        out
227    }
228}
229
230fn default_verify_result() -> verify::VerifyResult {
231    Ok(())
232}
233
234fn optimization_skipped_stats(desc: &KernelDescriptor) -> rewrites::OptimizationStats {
235    rewrites::OptimizationStats {
236        ops_before: desc.body.ops.len(),
237        ops_after: desc.body.ops.len(),
238        bindings_before: desc.bindings.slots.len(),
239        bindings_after: desc.bindings.slots.len(),
240        literals_before: desc.body.literals.len(),
241        literals_after: desc.body.literals.len(),
242        iterations: 0,
243        converged: false,
244    }
245}
246
247fn verification_status(result: &verify::VerifyResult) -> &'static str {
248    if result.is_ok() {
249        "OK"
250    } else {
251        "FAIL"
252    }
253}
254
255fn write_verify_section(out: &mut String, result: &verify::VerifyResult) {
256    use std::fmt::Write as _;
257    match result {
258        Ok(()) => {
259            let _ = writeln!(out, "  OK");
260        }
261        Err(errs) => {
262            let _ = writeln!(out, "  FAIL ({} errors)", errs.len());
263            for e in errs {
264                let _ = writeln!(out, "    {:?}", e);
265            }
266        }
267    }
268}
269
270fn build_full_report_fix_text(
271    verify_input: &verify::VerifyResult,
272    verify_output: Option<&verify::VerifyResult>,
273) -> String {
274    let mut messages = Vec::new();
275    push_verify_fix_text("input", verify_input, &mut messages);
276    if let Some(verify_output) = verify_output {
277        push_verify_fix_text("output", verify_output, &mut messages);
278    }
279    messages.join(" ")
280}
281
282fn push_verify_fix_text(
283    stage: &str,
284    result: &verify::VerifyResult,
285    messages: &mut Vec<String>,
286) {
287    if let Err(errs) = result {
288        if errs.is_empty() {
289            messages.push(format!(
290                "Fix: {stage} descriptor verification returned an empty error list; treat this as a verifier contract bug and preserve the descriptor for triage."
291            ));
292        } else {
293            messages.push(format!(
294                "Fix: {stage} descriptor verification failed with {} error(s); repair the descriptor before emitting benchmark evidence. First error: {:?}",
295                errs.len(),
296                errs[0]
297            ));
298        }
299    }
300}
301
302impl std::fmt::Display for FullReport {
303    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
304        f.write_str(&self.format_short())
305    }
306}
307pub use verify::{verify, VerifyError, VerifyErrorKind, VerifyResult};
308
309pub use descriptor::{
310    BindingLayout, BindingSlot, BindingVisibility, DescriptorIntent, DescriptorIntentError,
311    DescriptorIntentEvidence, DescriptorIntentKind, DescriptorIntentSet,
312    DescriptorIntentStrategy, Dispatch, IntentAnnotatedDescriptor, KernelBody,
313    KernelDescriptor, KernelOp, KernelOpKind, LiteralValue, MatrixMmaElement,
314    MatrixMmaLayout, MatrixMmaShape, MemoryClass, OpaqueExprData, OpaqueNodeData,
315    scan_construct_intent_mapping, ScanConstructIntentClass, ScanConstructIntentMapping,
316    DESCRIPTOR_INTENT_SCHEMA_VERSION, SCAN_CONSTRUCT_INTENT_MAPPINGS, TRAP_SIDECAR_NAME,
317    TRAP_SIDECAR_WORDS,
318};
319pub use error::LowerError;
320/// Re-exported so consumers matching/constructing `KernelOpKind::SubgroupReduce`
321/// can name the reduction operator without depending on `vyre-foundation`.
322pub use vyre_foundation::ir::SubgroupReduceOp;
323pub use lower::lower;
324pub use pre_emit::{lower_for_emit, prepare_program_for_emit, LoweredKernel, PreEmitError};
325
326#[cfg(test)]
327mod verify_then_optimize_tests {
328    use super::*;
329
330    #[test]
331    fn valid_input_returns_optimized_and_stats() {
332        let desc = KernelDescriptor {
333            id: "k".into(),
334            bindings: BindingLayout { slots: vec![] },
335            dispatch: Dispatch::new(64, 1, 1),
336            body: KernelBody {
337                ops: vec![KernelOp {
338                    kind: KernelOpKind::Literal,
339                    operands: vec![0],
340                    result: Some(0),
341                }],
342                child_bodies: vec![],
343                literals: vec![LiteralValue::U32(7)],
344            },
345        };
346        let (out, stats) = verify_then_optimize(&desc).unwrap();
347        assert_eq!(out.id, "k");
348        assert!(stats.iterations >= 1);
349    }
350
351    #[test]
352    fn invalid_input_returns_input_failure() {
353        // Descriptor with zero workgroup_size dim  -  caught by verify.
354        let desc = KernelDescriptor {
355            id: "bad".into(),
356            bindings: BindingLayout { slots: vec![] },
357            dispatch: Dispatch::new(0, 1, 1),
358            body: KernelBody {
359                ops: vec![],
360                child_bodies: vec![],
361                literals: vec![],
362            },
363        };
364        let r = verify_then_optimize(&desc);
365        assert!(matches!(r, Err(VerifyFailure::Input(_))));
366    }
367
368    #[test]
369    fn full_report_runs_every_layer_without_panic() {
370        let desc = KernelDescriptor {
371            id: "fr".into(),
372            bindings: BindingLayout { slots: vec![] },
373            dispatch: Dispatch::new(64, 1, 1),
374            body: KernelBody {
375                ops: vec![
376                    KernelOp {
377                        kind: KernelOpKind::Literal,
378                        operands: vec![0],
379                        result: Some(0),
380                    },
381                    KernelOp {
382                        kind: KernelOpKind::Literal,
383                        operands: vec![0],
384                        result: Some(1),
385                    },
386                ],
387                child_bodies: vec![],
388                literals: vec![LiteralValue::U32(7)],
389            },
390        };
391        let report = full_report(&desc);
392        assert_eq!(report.descriptor_id, "fr");
393        assert!(report.summary.contains("fr:"));
394        assert_eq!(report.histogram.literal, 2);
395        assert_eq!(report.perf.kernel_id, "fr");
396        assert!(report.verify_input.is_ok());
397        assert!(report.verify_output.is_ok());
398        assert!(report.optimization_ran);
399        assert_eq!(report.verify_input_status(), "OK");
400        assert_eq!(report.verify_output_status(), "OK");
401        assert!(report.stats.iterations >= 1);
402        assert!(report.fix_text.is_empty());
403        // Display delegates to format_short.
404        let s = format!("{report}");
405        assert!(s.contains("fr:"));
406        assert!(s.contains("id fr"));
407        assert!(s.contains("OK"));
408    }
409
410    #[test]
411    fn full_report_serializes_to_json() {
412        let desc = KernelDescriptor {
413            id: "fr".into(),
414            bindings: BindingLayout { slots: vec![] },
415            dispatch: Dispatch::new(64, 1, 1),
416            body: KernelBody {
417                ops: vec![KernelOp {
418                    kind: KernelOpKind::Literal,
419                    operands: vec![0],
420                    result: Some(0),
421                }],
422                child_bodies: vec![],
423                literals: vec![LiteralValue::U32(7)],
424            },
425        };
426        let report = full_report(&desc);
427        assert_eq!(report.descriptor_id, "fr");
428        let json = serde_json::to_string(&report).expect("Fix: serialize");
429        assert!(json.contains("\"descriptor_id\""));
430        assert!(json.contains("\"summary\""));
431        assert!(json.contains("\"histogram\""));
432        assert!(json.contains("\"perf\""));
433        assert!(json.contains("\"verify_input\""));
434        assert!(json.contains("\"verify_output\""));
435        assert!(json.contains("\"stats\""));
436        assert!(json.contains("\"optimization_ran\""));
437        assert!(json.contains("\"fix_text\""));
438
439        // Round-trip back through Deserialize.
440        let _back: FullReport = serde_json::from_str(&json).expect("Fix: round-trip");
441    }
442
443    #[test]
444    fn full_report_format_long_includes_all_sections() {
445        let desc = KernelDescriptor {
446            id: "fr".into(),
447            bindings: BindingLayout { slots: vec![] },
448            dispatch: Dispatch::new(64, 1, 1),
449            body: KernelBody {
450                ops: vec![KernelOp {
451                    kind: KernelOpKind::Literal,
452                    operands: vec![0],
453                    result: Some(0),
454                }],
455                child_bodies: vec![],
456                literals: vec![LiteralValue::U32(7)],
457            },
458        };
459        let r = full_report(&desc);
460        let long = r.format_long();
461        assert!(long.contains("Kernel:"));
462        assert!(long.contains("descriptor id: fr"));
463        assert!(long.contains("Histogram:"));
464        assert!(long.contains("Perf audit:"));
465        assert!(long.contains("Optimization:"));
466        assert!(long.contains("Verify (input):"));
467        assert!(long.contains("Verify (output):"));
468        assert!(long.contains("OK"));
469    }
470
471    #[test]
472    fn full_report_records_verify_fix_text_for_bad_descriptor() {
473        let desc = KernelDescriptor {
474            id: "bad".into(),
475            bindings: BindingLayout { slots: vec![] },
476            dispatch: Dispatch::new(0, 1, 1),
477            body: KernelBody {
478                ops: vec![],
479                child_bodies: vec![],
480                literals: vec![],
481            },
482        };
483        let report = full_report(&desc);
484        assert_eq!(report.descriptor_id, "bad");
485        assert_eq!(report.verify_input_status(), "FAIL");
486        assert_eq!(report.verify_output_status(), "SKIPPED");
487        assert!(!report.optimization_ran);
488        assert_eq!(report.stats.iterations, 0);
489        assert!(
490            report
491                .fix_text
492                .contains("Fix: input descriptor verification failed"),
493            "Fix: invalid descriptor reports must carry operator-actionable verifier repair text."
494        );
495        let long = report.format_long();
496        assert!(long.contains("Verify (input):"));
497        assert!(long.contains("Verify (output):"));
498        assert!(long.contains("Fix:"));
499    }
500
501    #[test]
502    fn errors_accessor_yields_underlying() {
503        let desc = KernelDescriptor {
504            id: "bad".into(),
505            bindings: BindingLayout { slots: vec![] },
506            dispatch: Dispatch::new(0, 1, 1),
507            body: KernelBody {
508                ops: vec![],
509                child_bodies: vec![],
510                literals: vec![],
511            },
512        };
513        let f = verify_then_optimize(&desc).unwrap_err();
514        assert_ne!(f.errors().len(), 0);
515    }
516}