logicaffeine_verify/
liveness.rs1use crate::ir::VerifyExpr;
14use crate::equivalence::{Trace, CycleState, SignalValue};
15use crate::kinduction;
16use std::collections::{HashMap, HashSet};
17
18#[derive(Debug)]
20pub enum LivenessResult {
21 Live,
23 NotLive { trace: Trace, loop_point: usize },
25 Unknown,
27}
28
29pub fn check_liveness(
34 init: &VerifyExpr,
35 transition: &VerifyExpr,
36 fairness: &[VerifyExpr],
37 property: &VerifyExpr,
38 max_k: u32,
39) -> LivenessResult {
40
41 let mut all_vars = HashSet::new();
43 collect_vars(init, &mut all_vars);
44 collect_vars(transition, &mut all_vars);
45 collect_vars(property, &mut all_vars);
46 for f in fairness {
47 collect_vars(f, &mut all_vars);
48 }
49 let signal_names = extract_signal_names(&all_vars);
50
51 for k in 1..=max_k {
55 let solver = crate::solver::new_solver();
56
57 let init_0 = kinduction::instantiate_at(init, 0);
59 solver.assert(&encode_bool(&init_0));
60
61 for t in 0..k {
63 let trans = kinduction::instantiate_transition(transition, t);
64 solver.assert(&encode_bool(&trans));
65 }
66
67 for fair in fairness {
69 let mut fair_options: Vec<z3::ast::Bool> = Vec::new();
71 for t in 0..=k {
72 let fair_t = kinduction::instantiate_at(fair, t);
73 fair_options.push(encode_bool(&fair_t));
74 }
75 let fair_refs: Vec<&z3::ast::Bool> = fair_options.iter().collect();
76 if !fair_refs.is_empty() {
77 let some_fair = z3::ast::Bool::or(&fair_refs);
78 solver.assert(&some_fair);
79 }
80 }
81
82 for t in 0..=k {
84 let prop_t = kinduction::instantiate_at(property, t);
85 solver.assert(&encode_bool(&prop_t).not());
86 }
87
88 match solver.check() {
89 z3::SatResult::Sat => {
90 if k == max_k {
92 let trace = extract_liveness_trace(&solver, k, &signal_names);
94 let loop_point = find_loop_point(&trace);
95 return LivenessResult::NotLive { trace, loop_point };
96 }
97 }
99 z3::SatResult::Unsat => {
100 return LivenessResult::Live;
102 }
103 z3::SatResult::Unknown => return LivenessResult::Unknown,
104 }
105 }
106
107 let solver = crate::solver::new_solver();
109 let init_0 = kinduction::instantiate_at(init, 0);
110 solver.assert(&encode_bool(&init_0));
111 for t in 0..max_k {
112 let trans = kinduction::instantiate_transition(transition, t);
113 solver.assert(&encode_bool(&trans));
114 }
115 for t in 0..=max_k {
116 let prop_t = kinduction::instantiate_at(property, t);
117 solver.assert(&encode_bool(&prop_t).not());
118 }
119
120 if matches!(solver.check(), z3::SatResult::Sat) {
121 let trace = extract_liveness_trace(&solver, max_k, &signal_names);
122 let loop_point = find_loop_point(&trace);
123 LivenessResult::NotLive { trace, loop_point }
124 } else {
125 LivenessResult::Live
126 }
127}
128
129fn extract_signal_names(all_vars: &HashSet<String>) -> Vec<String> {
131 let mut signals = HashSet::new();
132 for v in all_vars {
133 let base = v.replace("@0", "").replace("@t1", "").replace("@t", "");
134 if !base.is_empty() {
135 signals.insert(base);
136 }
137 }
138 signals.into_iter().collect()
139}
140
141fn extract_liveness_trace(
143 solver: &z3::Solver,
144 k: u32,
145 signal_names: &[String],
146) -> Trace {
147 let model = match solver.get_model() {
148 Some(m) => m,
149 None => return Trace { cycles: vec![CycleState { cycle: 0, signals: HashMap::new() }] },
150 };
151
152 let mut cycles = Vec::new();
153 for step in 0..=k {
154 let mut signals = HashMap::new();
155 for sig in signal_names {
156 let var_name = format!("{}@{}", sig, step);
157 let bool_var = z3::ast::Bool::new_const(var_name.as_str());
158 if let Some(val) = model.eval(&bool_var, true) {
159 if let Some(b) = val.as_bool() {
160 signals.insert(sig.clone(), SignalValue::Bool(b));
161 continue;
162 }
163 }
164 let int_var = z3::ast::Int::new_const(var_name.as_str());
165 if let Some(val) = model.eval(&int_var, true) {
166 if let Some(n) = val.as_i64() {
167 signals.insert(sig.clone(), SignalValue::Int(n));
168 continue;
169 }
170 }
171 }
172 if !signals.is_empty() {
173 cycles.push(CycleState { cycle: step as usize, signals });
174 }
175 }
176
177 if cycles.is_empty() {
178 let mut signals = HashMap::new();
180 for sig in signal_names {
181 signals.insert(sig.clone(), SignalValue::Unknown);
182 }
183 cycles.push(CycleState { cycle: 0, signals });
184 }
185
186 Trace { cycles }
187}
188
189fn find_loop_point(trace: &Trace) -> usize {
191 if trace.cycles.len() <= 1 {
192 return 0;
193 }
194 let last = &trace.cycles[trace.cycles.len() - 1];
197 for (i, cycle) in trace.cycles.iter().enumerate() {
198 if i < trace.cycles.len() - 1 && states_match(&cycle.signals, &last.signals) {
199 return i;
200 }
201 }
202 trace.cycles.len() / 2
204}
205
206fn states_match(a: &HashMap<String, SignalValue>, b: &HashMap<String, SignalValue>) -> bool {
208 if a.len() != b.len() { return false; }
209 for (key, val_a) in a {
210 match b.get(key) {
211 Some(val_b) => {
212 let sa = format!("{:?}", val_a);
213 let sb = format!("{:?}", val_b);
214 if sa != sb { return false; }
215 }
216 None => return false,
217 }
218 }
219 true
220}
221
222fn collect_vars(expr: &VerifyExpr, vars: &mut HashSet<String>) {
223 match expr {
224 VerifyExpr::Var(name) => { vars.insert(name.clone()); }
225 VerifyExpr::Binary { left, right, .. } => {
226 collect_vars(left, vars);
227 collect_vars(right, vars);
228 }
229 VerifyExpr::Not(inner) => collect_vars(inner, vars),
230 VerifyExpr::Iff(l, r) => {
231 collect_vars(l, vars);
232 collect_vars(r, vars);
233 }
234 VerifyExpr::ForAll { body, .. } | VerifyExpr::Exists { body, .. } => {
235 collect_vars(body, vars);
236 }
237 _ => {}
238 }
239}
240
241fn encode_bool(expr: &VerifyExpr) -> z3::ast::Bool {
242 let mut bool_vars = HashMap::new();
243 let mut int_vars = HashMap::new();
244 let mut all_vars = std::collections::HashSet::new();
245 crate::equivalence::collect_vars_pub(expr, &mut all_vars);
246 for name in &all_vars {
247 bool_vars.insert(name.clone(), z3::ast::Bool::new_const(name.as_str()));
248 }
249 crate::equivalence::collect_int_vars_pub(expr, &mut int_vars);
250 kinduction::encode_expr_bool(expr, &bool_vars, &int_vars)
251}