#![doc = include_str!("../README.md")]
pub mod equiv;
pub mod parse;
pub mod symexec;
pub mod verdict;
use logicaffeine_compile::compile::interpret_program;
use logicaffeine_verify::{BitVecOp, VerifyExpr};
pub use symexec::{SymSummary, SymValue};
pub use verdict::{SoundnessReport, TvError};
pub fn summarize_logos(source: &str) -> Result<SymSummary, TvError> {
match parse::with_program(source, false, symexec::execute) {
Ok(Ok(summary)) => Ok(summary),
Ok(Err(symexec::Unsupported(reason))) => Err(TvError::Unsupported(reason)),
Err(e) => Err(TvError::Parse(e)),
}
}
pub fn check_encoder_sound(source: &str) -> SoundnessReport {
match logicaffeine_compile::classify_source(source) {
Err(e) => return SoundnessReport::ParseFailed { detail: format!("{e:?}") },
Ok(logicaffeine_compile::concurrency::Determinacy::Nondeterminate { .. }) => {
return check_seeded_sweep(source);
}
Ok(logicaffeine_compile::concurrency::Determinacy::Determinate) => {}
}
let summary = match summarize_logos(source) {
Ok(s) => s,
Err(TvError::Unsupported(reason)) => return SoundnessReport::Unsupported { reason },
Err(TvError::Parse(e)) => {
return SoundnessReport::ParseFailed {
detail: format!("{e:?}"),
}
}
};
check_against_interp(&summary, interpret_program(source).map_err(|e| format!("{e:?}")))
}
fn check_against_interp(summary: &SymSummary, interp: Result<String, String>) -> SoundnessReport {
match interp {
Err(e) => {
if equiv::is_valid(&summary.errored) {
SoundnessReport::Agrees
} else {
SoundnessReport::Disagrees {
detail: format!("interpreter errored ({e}) but encoder did not prove `errored`"),
}
}
}
Ok(out) => {
if !equiv::is_valid(&VerifyExpr::not(summary.errored.clone())) {
return SoundnessReport::Disagrees {
detail: "encoder admits an error on an input where the interpreter succeeded"
.to_string(),
};
}
compare_outputs(&summary.outputs, &out)
}
}
}
fn check_seeded_sweep(source: &str) -> SoundnessReport {
const SEEDS: [u64; 5] = [0, 1, 2, 7, 42];
for seed in SEEDS {
let summary = match parse::with_program(source, false, |stmts, interner| {
symexec::execute_seeded(stmts, interner, seed)
}) {
Ok(Ok(s)) => s,
Ok(Err(symexec::Unsupported(reason))) => return SoundnessReport::Unsupported { reason },
Err(e) => return SoundnessReport::ParseFailed { detail: format!("{e:?}") },
};
let run = logicaffeine_compile::run_treewalker_concurrent_seeded(source, seed);
let interp = match run.error {
Some(e) => Err(e),
None => Ok(run.lines.join("\n")),
};
match check_against_interp(&summary, interp) {
SoundnessReport::Agrees => continue,
SoundnessReport::Disagrees { detail } => {
return SoundnessReport::SeedReplayDisagrees {
detail: format!("seed {seed}: {detail}"),
}
}
other => return other, }
}
SoundnessReport::SeedReplayAgrees
}
enum Expected {
Int(i64),
Bool(bool),
}
fn parse_expected(line: &str) -> Option<Expected> {
match line {
"true" => Some(Expected::Bool(true)),
"false" => Some(Expected::Bool(false)),
_ => line.parse::<i64>().ok().map(Expected::Int),
}
}
fn compare_outputs(outputs: &[SymValue], interp_out: &str) -> SoundnessReport {
let lines: Vec<&str> = if interp_out.is_empty() {
Vec::new()
} else {
interp_out.split('\n').collect()
};
if outputs.len() != lines.len() {
return SoundnessReport::Disagrees {
detail: format!(
"output count: encoder produced {} line(s), interpreter produced {} ({:?})",
outputs.len(),
lines.len(),
lines
),
};
}
for (i, (slot, line)) in outputs.iter().zip(lines.iter()).enumerate() {
let expected = match parse_expected(line) {
Some(e) => e,
None => {
return SoundnessReport::Unsupported {
reason: format!("non-Int/Bool output line {i}: {line:?}"),
}
}
};
let pred = match (slot, expected) {
(SymValue::Int(e), Expected::Int(n)) => {
VerifyExpr::bv_binary(BitVecOp::Eq, e.clone(), VerifyExpr::bv_const(64, n as u64))
}
(SymValue::Bool(e), Expected::Bool(b)) => VerifyExpr::iff(e.clone(), VerifyExpr::bool(b)),
(slot, _) => {
return SoundnessReport::Disagrees {
detail: format!(
"output {i}: kind mismatch (encoder {} vs interpreter {line:?})",
kind_of(slot)
),
}
}
};
if !equiv::is_valid(&pred) {
return SoundnessReport::Disagrees {
detail: format!("output {i}: encoder value disagrees with interpreter {line:?}"),
};
}
}
SoundnessReport::Agrees
}
fn kind_of(v: &SymValue) -> &'static str {
match v {
SymValue::Int(_) => "Int",
SymValue::Bool(_) => "Bool",
SymValue::Chan(_) => "Chan",
}
}