1#![doc = include_str!("../README.md")]
2
3pub mod equiv;
4pub mod parse;
5pub mod symexec;
6pub mod verdict;
7
8use logicaffeine_compile::compile::interpret_program;
9use logicaffeine_verify::{BitVecOp, VerifyExpr};
10
11pub use symexec::{SymSummary, SymValue};
12pub use verdict::{SoundnessReport, TvError};
13
14pub fn summarize_logos(source: &str) -> Result<SymSummary, TvError> {
17 match parse::with_program(source, false, symexec::execute) {
18 Ok(Ok(summary)) => Ok(summary),
19 Ok(Err(symexec::Unsupported(reason))) => Err(TvError::Unsupported(reason)),
20 Err(e) => Err(TvError::Parse(e)),
21 }
22}
23
24pub fn check_encoder_sound(source: &str) -> SoundnessReport {
32 match logicaffeine_compile::classify_source(source) {
38 Err(e) => return SoundnessReport::ParseFailed { detail: format!("{e:?}") },
39 Ok(logicaffeine_compile::concurrency::Determinacy::Nondeterminate { .. }) => {
40 return check_seeded_sweep(source);
41 }
42 Ok(logicaffeine_compile::concurrency::Determinacy::Determinate) => {}
43 }
44
45 let summary = match summarize_logos(source) {
46 Ok(s) => s,
47 Err(TvError::Unsupported(reason)) => return SoundnessReport::Unsupported { reason },
48 Err(TvError::Parse(e)) => {
49 return SoundnessReport::ParseFailed {
50 detail: format!("{e:?}"),
51 }
52 }
53 };
54 check_against_interp(&summary, interpret_program(source).map_err(|e| format!("{e:?}")))
55}
56
57fn check_against_interp(summary: &SymSummary, interp: Result<String, String>) -> SoundnessReport {
61 match interp {
62 Err(e) => {
63 if equiv::is_valid(&summary.errored) {
65 SoundnessReport::Agrees
66 } else {
67 SoundnessReport::Disagrees {
68 detail: format!("interpreter errored ({e}) but encoder did not prove `errored`"),
69 }
70 }
71 }
72 Ok(out) => {
73 if !equiv::is_valid(&VerifyExpr::not(summary.errored.clone())) {
76 return SoundnessReport::Disagrees {
77 detail: "encoder admits an error on an input where the interpreter succeeded"
78 .to_string(),
79 };
80 }
81 compare_outputs(&summary.outputs, &out)
82 }
83 }
84}
85
86fn check_seeded_sweep(source: &str) -> SoundnessReport {
92 const SEEDS: [u64; 5] = [0, 1, 2, 7, 42];
93 for seed in SEEDS {
94 let summary = match parse::with_program(source, false, |stmts, interner| {
95 symexec::execute_seeded(stmts, interner, seed)
96 }) {
97 Ok(Ok(s)) => s,
98 Ok(Err(symexec::Unsupported(reason))) => return SoundnessReport::Unsupported { reason },
99 Err(e) => return SoundnessReport::ParseFailed { detail: format!("{e:?}") },
100 };
101 let run = logicaffeine_compile::run_treewalker_concurrent_seeded(source, seed);
102 let interp = match run.error {
103 Some(e) => Err(e),
104 None => Ok(run.lines.join("\n")),
105 };
106 match check_against_interp(&summary, interp) {
107 SoundnessReport::Agrees => continue,
108 SoundnessReport::Disagrees { detail } => {
109 return SoundnessReport::SeedReplayDisagrees {
110 detail: format!("seed {seed}: {detail}"),
111 }
112 }
113 other => return other, }
115 }
116 SoundnessReport::SeedReplayAgrees
117}
118
119enum Expected {
121 Int(i64),
122 Bool(bool),
123}
124
125fn parse_expected(line: &str) -> Option<Expected> {
126 match line {
127 "true" => Some(Expected::Bool(true)),
128 "false" => Some(Expected::Bool(false)),
129 _ => line.parse::<i64>().ok().map(Expected::Int),
130 }
131}
132
133fn compare_outputs(outputs: &[SymValue], interp_out: &str) -> SoundnessReport {
134 let lines: Vec<&str> = if interp_out.is_empty() {
135 Vec::new()
136 } else {
137 interp_out.split('\n').collect()
138 };
139
140 if outputs.len() != lines.len() {
141 return SoundnessReport::Disagrees {
142 detail: format!(
143 "output count: encoder produced {} line(s), interpreter produced {} ({:?})",
144 outputs.len(),
145 lines.len(),
146 lines
147 ),
148 };
149 }
150
151 for (i, (slot, line)) in outputs.iter().zip(lines.iter()).enumerate() {
152 let expected = match parse_expected(line) {
153 Some(e) => e,
154 None => {
155 return SoundnessReport::Unsupported {
156 reason: format!("non-Int/Bool output line {i}: {line:?}"),
157 }
158 }
159 };
160 let pred = match (slot, expected) {
161 (SymValue::Int(e), Expected::Int(n)) => {
162 VerifyExpr::bv_binary(BitVecOp::Eq, e.clone(), VerifyExpr::bv_const(64, n as u64))
163 }
164 (SymValue::Bool(e), Expected::Bool(b)) => VerifyExpr::iff(e.clone(), VerifyExpr::bool(b)),
165 (slot, _) => {
166 return SoundnessReport::Disagrees {
167 detail: format!(
168 "output {i}: kind mismatch (encoder {} vs interpreter {line:?})",
169 kind_of(slot)
170 ),
171 }
172 }
173 };
174 if !equiv::is_valid(&pred) {
175 return SoundnessReport::Disagrees {
176 detail: format!("output {i}: encoder value disagrees with interpreter {line:?}"),
177 };
178 }
179 }
180
181 SoundnessReport::Agrees
182}
183
184fn kind_of(v: &SymValue) -> &'static str {
185 match v {
186 SymValue::Int(_) => "Int",
187 SymValue::Bool(_) => "Bool",
188 SymValue::Chan(_) => "Chan",
189 }
190}