vyre-conform 0.1.0

Conformance suite for vyre backends — proves byte-identical output to CPU reference
Documentation
//! Engine-level invariant harness collection for certificates.

use std::any::Any;
use std::panic::{catch_unwind, AssertUnwindSafe};

use super::{EngineResult, EngineStatus};
use crate::pipeline::execution::InputCase;
use crate::spec::types::OpSpec;

pub(super) fn run_engine_harnesses(
    backend: &dyn vyre::VyreBackend,
    specs: &[OpSpec],
) -> Vec<EngineResult> {
    vec![
        guarded_engine_result("determinism", || determinism_result(backend, specs)),
        guarded_engine_result("barrier", || barrier_result(backend)),
        guarded_engine_result("atomics", || atomics_result(backend)),
        guarded_engine_result("wire_format", || wire_format_result(backend)),
    ]
}

fn guarded_engine_result<F>(id: &str, run: F) -> EngineResult
where
    F: FnOnce() -> EngineResult,
{
    match catch_unwind(AssertUnwindSafe(run)) {
        Ok(result) => result,
        Err(payload) => engine_result(
            id,
            id,
            vec![format!(
                "Fix: engine invariant harness `{id}` panicked: {}.",
                panic_message(payload.as_ref())
            )],
            0,
        ),
    }
}

fn panic_message(payload: &(dyn Any + Send)) -> String {
    if let Some(message) = payload.downcast_ref::<&str>() {
        return (*message).to_string();
    }
    if let Some(message) = payload.downcast_ref::<String>() {
        return message.clone();
    }
    "non-string panic payload".to_string()
}

fn determinism_result(backend: &dyn vyre::VyreBackend, specs: &[OpSpec]) -> EngineResult {
    let mut failures = Vec::new();
    let mut cases = 0_u64;
    for spec in specs {
        let input_len = spec.signature.min_input_bytes().max(4);
        let input = InputCase::new("certificate-engine", "zero".to_string(), vec![0; input_len]);
        let report = crate::enforce::enforcers::determinism::enforce_determinism(
            backend,
            spec,
            &[input],
            10,
        );
        cases = cases.saturating_add(10);
        let op_id = report.op_id;
        failures.extend(
            report
                .divergences
                .into_iter()
                .map(|d| format!("{op_id}: {}", d.message)),
        );
    }
    engine_result("determinism", "Deterministic", failures, cases)
}

fn barrier_result(backend: &dyn vyre::VyreBackend) -> EngineResult {
    let barrier = crate::enforce::enforcers::barrier::enforce_barrier(backend);
    let failures = barrier
        .passes
        .iter()
        .filter(|p| {
            !matches!(
                p.verdict,
                crate::enforce::enforcers::barrier::BarrierVerdict::Passed
            )
        })
        .map(|p| format!("{}: {:?}", p.pass_name, p.verdict))
        .collect();
    engine_result("barrier", "I8", failures, barrier.passes.len() as u64)
}

fn atomics_result(backend: &dyn vyre::VyreBackend) -> EngineResult {
    let atomics = crate::enforce::enforcers::atomics::enforce_atomics(backend);
    let failures = atomics.findings.iter().map(|f| f.message.clone()).collect();
    engine_result("atomics", "I7", failures, atomics.findings.len() as u64)
}

fn wire_format_result(backend: &dyn vyre::VyreBackend) -> EngineResult {
    let wire = crate::enforce::enforcers::wire_format_eq::enforce_wire_format_equivalence(backend);
    let failures = wire.violations.iter().map(|v| format!("{v:?}")).collect();
    engine_result("wire_format", "I4", failures, wire.tested as u64)
}

fn engine_result(
    id: &str,
    invariant: &str,
    failures: Vec<String>,
    cases_tested: u64,
) -> EngineResult {
    let status = if failures.is_empty() {
        EngineStatus::Pass
    } else {
        EngineStatus::Fail
    };
    EngineResult {
        id: id.to_string(),
        status,
        invariants_verified: if status == EngineStatus::Pass {
            vec![invariant.to_string()]
        } else {
            Vec::new()
        },
        invariants_failed: failures,
        cases_tested,
    }
}