use std::sync::Arc;
use vyre_foundation::ir::Program;
use crate::backend::{BackendError, CompiledPipeline, DispatchConfig};
type ReferenceRunFn =
dyn Fn(&Program, &[Vec<u8>]) -> Result<Vec<Vec<u8>>, BackendError> + Send + Sync;
#[derive(Clone)]
pub struct ReferenceExecutor {
run: Arc<ReferenceRunFn>,
}
impl ReferenceExecutor {
pub fn new<F>(run: F) -> Self
where
F: Fn(&Program, &[Vec<u8>]) -> Result<Vec<Vec<u8>>, BackendError> + Send + Sync + 'static,
{
Self { run: Arc::new(run) }
}
pub fn run(&self, program: &Program, inputs: &[Vec<u8>]) -> Result<Vec<Vec<u8>>, BackendError> {
(self.run)(program, inputs)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConformanceCase {
label: String,
inputs: Vec<Vec<u8>>,
}
impl ConformanceCase {
#[must_use]
pub fn new(label: impl Into<String>, inputs: Vec<Vec<u8>>) -> Self {
Self {
label: label.into(),
inputs,
}
}
#[must_use]
pub fn label(&self) -> &str {
&self.label
}
#[must_use]
pub fn inputs(&self) -> &[Vec<u8>] {
&self.inputs
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ConformanceMatrix {
cases: Vec<ConformanceCase>,
}
impl ConformanceMatrix {
#[must_use]
pub fn new(cases: Vec<ConformanceCase>) -> Self {
Self { cases }
}
pub fn push(&mut self, case: ConformanceCase) {
self.cases.push(case);
}
#[must_use]
pub fn cases(&self) -> &[ConformanceCase] {
&self.cases
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.cases.is_empty()
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DivergenceEvent {
pub case_label: String,
pub program_fingerprint: [u8; 32],
pub inputs: Vec<Vec<u8>>,
pub backend_output: Vec<Vec<u8>>,
pub reference_output: Vec<Vec<u8>>,
}
#[derive(Debug, thiserror::Error)]
pub enum ConformanceError {
#[error(
"conformance matrix is empty. Fix: populate every op with at least one witness tuple from vyre-conform-spec before asserting backend parity."
)]
EmptyMatrix,
#[error(
"backend rejected witness `{case_label}`: {source}. Fix: the backend must accept every witness tuple the reference accepts for this Program."
)]
BackendRejected {
case_label: String,
#[source]
source: BackendError,
},
#[error(
"reference rejected witness `{case_label}`: {source}. Fix: inspect the Program body or witness tuple; the reference is the contract oracle for exhaustive conformance."
)]
ReferenceRejected {
case_label: String,
#[source]
source: BackendError,
},
#[error(
"backend diverged from the reference on witness `{event_case_label}`. Fix: inspect the embedded outputs and repair the backend until every witness tuple is byte-identical."
)]
Diverged {
event: Box<DivergenceEvent>,
event_case_label: String,
},
}
pub fn assert_exhaustive_byte_identity(
pipeline: &dyn CompiledPipeline,
program: &Program,
reference: &ReferenceExecutor,
matrix: &ConformanceMatrix,
config: &DispatchConfig,
) -> Result<(), ConformanceError> {
if matrix.is_empty() {
return Err(ConformanceError::EmptyMatrix);
}
let program_fingerprint = program_fingerprint(program);
let mut first_error = None;
for case in matrix.cases() {
let backend_output = match pipeline.dispatch(case.inputs(), config) {
Ok(output) => output,
Err(source) => {
first_error.get_or_insert(ConformanceError::BackendRejected {
case_label: case.label().to_string(),
source,
});
continue;
}
};
let reference_output = match reference.run(program, case.inputs()) {
Ok(output) => output,
Err(source) => {
first_error.get_or_insert(ConformanceError::ReferenceRejected {
case_label: case.label().to_string(),
source,
});
continue;
}
};
if backend_output != reference_output {
let event = DivergenceEvent {
case_label: case.label().to_string(),
program_fingerprint,
inputs: case.inputs().to_vec(),
backend_output,
reference_output,
};
first_error.get_or_insert(ConformanceError::Diverged {
event_case_label: event.case_label.clone(),
event: Box::new(event),
});
}
}
first_error.map_or(Ok(()), Err)
}
fn program_fingerprint(program: &Program) -> [u8; 32] {
vyre_foundation::optimizer::pipeline_fingerprint_bytes(program)
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Mutex;
use vyre_conform_spec::U32Witness;
use vyre_foundation::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
type FakeRun = dyn Fn(&[Vec<u8>]) -> Result<Vec<Vec<u8>>, BackendError> + Send + Sync;
struct FakePipeline {
id: String,
run: Arc<FakeRun>,
}
impl crate::backend::private::Sealed for FakePipeline {}
impl CompiledPipeline for FakePipeline {
fn id(&self) -> &str {
&self.id
}
fn dispatch(
&self,
inputs: &[Vec<u8>],
_config: &DispatchConfig,
) -> Result<Vec<Vec<u8>>, BackendError> {
(self.run)(inputs)
}
}
fn sample_program() -> Program {
Program::wrapped(
vec![
BufferDecl::storage("input", 0, BufferAccess::ReadOnly, DataType::U32)
.with_count(1),
BufferDecl::storage("output", 1, BufferAccess::ReadWrite, DataType::U32)
.with_count(1),
],
[1, 1, 1],
vec![Node::store(
"output",
Expr::u32(0),
Expr::load("input", Expr::u32(0)),
)],
)
}
fn witness_matrix() -> ConformanceMatrix {
ConformanceMatrix::new(
U32Witness::enumerate()
.into_iter()
.map(|witness| {
ConformanceCase::new(
format!("u32:{witness:#010x}"),
vec![witness.to_le_bytes().to_vec()],
)
})
.collect(),
)
}
#[test]
fn empty_matrix_is_rejected() {
let pipeline: Arc<dyn CompiledPipeline> = Arc::new(FakePipeline {
id: "fake".into(),
run: Arc::new(|inputs| Ok(inputs.to_vec())),
});
let reference = ReferenceExecutor::new(|_, inputs| Ok(inputs.to_vec()));
let error = assert_exhaustive_byte_identity(
pipeline.as_ref(),
&sample_program(),
&reference,
&ConformanceMatrix::default(),
&DispatchConfig::default(),
)
.expect_err("empty witness inventories must be rejected");
assert!(matches!(error, ConformanceError::EmptyMatrix));
}
#[test]
fn exhaustive_matrix_passes_matching_outputs() {
let pipeline: Arc<dyn CompiledPipeline> = Arc::new(FakePipeline {
id: "fake".into(),
run: Arc::new(|inputs| Ok(inputs.to_vec())),
});
let reference = ReferenceExecutor::new(|_, inputs| Ok(inputs.to_vec()));
assert_exhaustive_byte_identity(
pipeline.as_ref(),
&sample_program(),
&reference,
&witness_matrix(),
&DispatchConfig::default(),
)
.expect("Fix: matching backend/reference outputs must pass the exhaustive matrix; restore this invariant before continuing.");
}
#[test]
fn exhaustive_matrix_catches_divergence_hidden_by_sampling() {
let hidden_witness = 0xDEAD_BEEF_u32.to_le_bytes().to_vec();
let seen = Arc::new(Mutex::new(Vec::<Vec<u8>>::new()));
let seen_clone = Arc::clone(&seen);
let pipeline: Arc<dyn CompiledPipeline> = Arc::new(FakePipeline {
id: "fake".into(),
run: Arc::new(move |inputs| {
seen_clone.lock().unwrap().push(inputs[0].clone());
if inputs[0] == hidden_witness {
Ok(vec![0_u32.to_le_bytes().to_vec()])
} else {
Ok(inputs.to_vec())
}
}),
});
let reference = ReferenceExecutor::new(|_, inputs| Ok(inputs.to_vec()));
let error = assert_exhaustive_byte_identity(
pipeline.as_ref(),
&sample_program(),
&reference,
&witness_matrix(),
&DispatchConfig::default(),
)
.expect_err("one divergent witness must fail exhaustive conformance");
match error {
ConformanceError::Diverged { event, .. } => {
assert_eq!(event.case_label, "u32:0xdeadbeef");
assert_eq!(event.inputs, vec![0xDEAD_BEEF_u32.to_le_bytes().to_vec()]);
assert_eq!(event.backend_output, vec![0_u32.to_le_bytes().to_vec()]);
assert_eq!(
event.reference_output,
vec![0xDEAD_BEEF_u32.to_le_bytes().to_vec()]
);
}
other => panic!("expected divergence event, got {other:?}"),
}
assert_eq!(
seen.lock().unwrap().len(),
U32Witness::enumerate().len(),
"the conformance matrix must execute every witness tuple exactly once"
);
}
}