1use crate::ir::VerifyExpr;
15use crate::equivalence::{Trace, CycleState, SignalValue};
16use crate::kinduction;
17use std::collections::{HashMap, HashSet};
18use z3::{ast::Ast, ast::Bool, ast::Int, SatResult};
19
20#[derive(Debug)]
22pub enum Ic3Result {
23 Safe { invariant: VerifyExpr },
25 Unsafe { trace: Trace },
27 Unknown,
29}
30
31#[derive(Clone)]
34struct Frame {
35 clauses: Vec<VerifyExpr>,
36}
37
38impl Frame {
39 fn new() -> Self {
40 Frame { clauses: Vec::new() }
41 }
42
43 fn add_clause(&mut self, clause: VerifyExpr) {
44 let dbg = format!("{:?}", clause);
46 for existing in &self.clauses {
47 if format!("{:?}", existing) == dbg {
48 return;
49 }
50 }
51 self.clauses.push(clause);
52 }
53
54 fn to_expr(&self) -> VerifyExpr {
55 if self.clauses.is_empty() {
56 return VerifyExpr::bool(true);
57 }
58 let mut expr = self.clauses[0].clone();
59 for clause in &self.clauses[1..] {
60 expr = VerifyExpr::and(expr, clause.clone());
61 }
62 expr
63 }
64}
65
66pub fn ic3(
73 init: &VerifyExpr,
74 transition: &VerifyExpr,
75 property: &VerifyExpr,
76 max_frames: u32,
77) -> Ic3Result {
78
79 let mut all_vars = HashSet::new();
81 collect_all_vars(init, &mut all_vars);
82 collect_all_vars(transition, &mut all_vars);
83 collect_all_vars(property, &mut all_vars);
84 let signal_names: Vec<String> = extract_signal_names(&all_vars);
86
87 let init_violation = VerifyExpr::and(
89 kinduction::instantiate_at(init, 0),
90 VerifyExpr::not(kinduction::instantiate_at(property, 0)),
91 );
92 if is_sat(&init_violation) {
93 let trace = extract_trace_from_bmc(init, transition, property, 1, &signal_names);
94 return Ic3Result::Unsafe { trace };
95 }
96
97 for k in 1..max_frames {
99 let bmc_check = build_bmc_check(init, transition, property, k);
100 if is_sat(&bmc_check) {
101 let trace = extract_trace_from_bmc(init, transition, property, k, &signal_names);
102 return Ic3Result::Unsafe { trace };
103 }
104 }
105
106 let mut frames: Vec<Frame> = vec![Frame::new()];
109 frames[0].add_clause(init.clone());
110 frames[0].add_clause(property.clone());
111
112 for _iteration in 0..max_frames {
113 let mut new_frame = Frame::new();
115 new_frame.add_clause(property.clone());
116 frames.push(new_frame);
117 let k = frames.len() - 1;
118
119 let mut blocked = true;
121 for _block_attempt in 0..50 {
122 let frame_expr = frames[k - 1].to_expr();
124 let cti_formula = VerifyExpr::and(
125 kinduction::instantiate_at(&frame_expr, 0),
126 VerifyExpr::and(
127 kinduction::instantiate_transition(transition, 0),
128 VerifyExpr::not(kinduction::instantiate_at(property, 1)),
129 ),
130 );
131
132 if !is_sat(&cti_formula) {
133 blocked = true;
135 break;
136 }
137
138 let bad_state = extract_cti_state(&cti_formula, &signal_names);
140
141 if is_reachable_from_init(init, transition, &bad_state, k as u32, &signal_names) {
143 let trace = extract_trace_from_bmc(
145 init, transition, property, k as u32, &signal_names,
146 );
147 return Ic3Result::Unsafe { trace };
148 }
149
150 let blocking_clause = generalize_blocking_clause(
153 transition, property, &bad_state, &frames[k - 1],
154 );
155 for fi in 1..k {
157 frames[fi].add_clause(blocking_clause.clone());
158 }
159 blocked = false;
160 }
161
162 propagate_clauses(transition, &mut frames, k);
164
165 if check_convergence(&frames, k) {
167 let invariant = frames[k].to_expr();
168 return Ic3Result::Safe { invariant };
169 }
170 }
171
172 let kind_result = kinduction::k_induction(init, transition, property, &[], max_frames);
174 match kind_result {
175 kinduction::KInductionResult::Proven { .. } => {
176 let inv = frames.last().map(|f| f.to_expr()).unwrap_or_else(|| property.clone());
179 Ic3Result::Safe { invariant: inv }
180 }
181 kinduction::KInductionResult::Counterexample { trace, .. } => Ic3Result::Unsafe { trace },
182 _ => Ic3Result::Unknown,
183 }
184}
185
186fn extract_signal_names(all_vars: &HashSet<String>) -> Vec<String> {
188 let mut signals = HashSet::new();
189 for v in all_vars {
190 let base = v.replace("@0", "").replace("@t1", "").replace("@t", "");
191 if !base.is_empty() {
192 signals.insert(base);
193 }
194 }
195 signals.into_iter().collect()
196}
197
198fn collect_all_vars(expr: &VerifyExpr, vars: &mut HashSet<String>) {
200 match expr {
201 VerifyExpr::Var(name) => { vars.insert(name.clone()); }
202 VerifyExpr::Binary { left, right, .. } => {
203 collect_all_vars(left, vars);
204 collect_all_vars(right, vars);
205 }
206 VerifyExpr::Not(inner) => collect_all_vars(inner, vars),
207 VerifyExpr::Iff(l, r) => {
208 collect_all_vars(l, vars);
209 collect_all_vars(r, vars);
210 }
211 VerifyExpr::ForAll { body, .. } | VerifyExpr::Exists { body, .. } => {
212 collect_all_vars(body, vars);
213 }
214 _ => {}
215 }
216}
217
218fn build_bmc_check(
220 init: &VerifyExpr,
221 transition: &VerifyExpr,
222 property: &VerifyExpr,
223 k: u32,
224) -> VerifyExpr {
225 let mut formula = kinduction::instantiate_at(init, 0);
226 for t in 0..k {
227 let trans = kinduction::instantiate_transition(transition, t);
228 formula = VerifyExpr::and(formula, trans);
229 }
230 let not_prop_k = VerifyExpr::not(kinduction::instantiate_at(property, k));
232 VerifyExpr::and(formula, not_prop_k)
233}
234
235fn extract_cti_state(
238 formula: &VerifyExpr,
239 signal_names: &[String],
240) -> VerifyExpr {
241 let solver = crate::solver::new_solver();
242 let encoded = encode_bool(formula);
243 solver.assert(&encoded);
244
245 if !matches!(solver.check(), SatResult::Sat) {
246 return VerifyExpr::bool(false);
247 }
248 let model = solver.get_model().unwrap();
249
250 let mut literals = Vec::new();
252 for sig in signal_names {
253 let var_name = format!("{}@0", sig);
254 let bool_var = Bool::new_const(var_name.as_str());
256 if let Some(val) = model.eval(&bool_var, true) {
257 if let Some(b) = val.as_bool() {
258 if b {
259 literals.push(VerifyExpr::var(&format!("{}@t", sig)));
260 } else {
261 literals.push(VerifyExpr::not(VerifyExpr::var(&format!("{}@t", sig))));
262 }
263 }
264 }
265 let int_var = Int::new_const(var_name.as_str());
267 if let Some(val) = model.eval(&int_var, true) {
268 if let Some(n) = val.as_i64() {
269 literals.push(VerifyExpr::eq(
270 VerifyExpr::var(&format!("{}@t", sig)),
271 VerifyExpr::int(n),
272 ));
273 }
274 }
275 }
276
277 if literals.is_empty() {
278 VerifyExpr::bool(true)
279 } else {
280 let mut conj = literals[0].clone();
281 for lit in &literals[1..] {
282 conj = VerifyExpr::and(conj, lit.clone());
283 }
284 conj
285 }
286}
287
288fn generalize_blocking_clause(
292 transition: &VerifyExpr,
293 property: &VerifyExpr,
294 bad_state: &VerifyExpr,
295 _frame: &Frame,
296) -> VerifyExpr {
297 let full_clause = VerifyExpr::not(bad_state.clone());
299
300 let literals = extract_literals(bad_state);
302 if literals.len() <= 1 {
303 return full_clause;
304 }
305
306 let mut kept_literals = literals.clone();
308 for i in 0..literals.len() {
309 if kept_literals.len() <= 1 {
310 break;
311 }
312 let mut candidate: Vec<VerifyExpr> = Vec::new();
314 for (j, lit) in kept_literals.iter().enumerate() {
315 if j != i {
316 candidate.push(lit.clone());
317 }
318 }
319
320 let candidate_state = conjoin(&candidate);
322 let _candidate_clause = VerifyExpr::not(candidate_state.clone());
323
324 let check = VerifyExpr::and(
328 kinduction::instantiate_at(&candidate_state, 0),
329 VerifyExpr::and(
330 kinduction::instantiate_transition(transition, 0),
331 VerifyExpr::not(kinduction::instantiate_at(property, 1)),
332 ),
333 );
334 if is_sat(&check) {
336 } else {
338 kept_literals = candidate;
340 }
341 }
342
343 if kept_literals.is_empty() {
344 full_clause
345 } else {
346 VerifyExpr::not(conjoin(&kept_literals))
347 }
348}
349
350fn extract_literals(expr: &VerifyExpr) -> Vec<VerifyExpr> {
352 match expr {
353 VerifyExpr::Binary { op: crate::ir::VerifyOp::And, left, right } => {
354 let mut lits = extract_literals(left);
355 lits.extend(extract_literals(right));
356 lits
357 }
358 _ => vec![expr.clone()],
359 }
360}
361
362fn conjoin(exprs: &[VerifyExpr]) -> VerifyExpr {
364 if exprs.is_empty() {
365 return VerifyExpr::bool(true);
366 }
367 let mut result = exprs[0].clone();
368 for e in &exprs[1..] {
369 result = VerifyExpr::and(result, e.clone());
370 }
371 result
372}
373
374fn is_reachable_from_init(
376 init: &VerifyExpr,
377 transition: &VerifyExpr,
378 bad_state: &VerifyExpr,
379 k: u32,
380 _signal_names: &[String],
381) -> bool {
382 for depth in 0..k {
383 let mut formula = kinduction::instantiate_at(init, 0);
384 for t in 0..depth {
385 formula = VerifyExpr::and(formula, kinduction::instantiate_transition(transition, t));
386 }
387 let bad_at_depth = kinduction::instantiate_at(bad_state, depth);
389 let check = VerifyExpr::and(formula, bad_at_depth);
390 if is_sat(&check) {
391 return true;
392 }
393 }
394 false
395}
396
397fn propagate_clauses(
399 transition: &VerifyExpr,
400 frames: &mut Vec<Frame>,
401 k: usize,
402) {
403 if k == 0 { return; }
404 let clauses_to_try: Vec<VerifyExpr> = frames[k - 1].clauses.clone();
405
406 for clause in clauses_to_try {
407 let frame_k_expr = frames[k].to_expr();
410 let check = VerifyExpr::and(
411 kinduction::instantiate_at(&frame_k_expr, 0),
412 VerifyExpr::and(
413 kinduction::instantiate_at(&clause, 0),
414 VerifyExpr::and(
415 kinduction::instantiate_transition(transition, 0),
416 VerifyExpr::not(kinduction::instantiate_at(&clause, 1)),
417 ),
418 ),
419 );
420 if !is_sat(&check) {
421 frames[k].add_clause(clause);
422 }
423 }
424}
425
426fn check_convergence(frames: &[Frame], k: usize) -> bool {
428 if k == 0 { return false; }
429
430 let fk = frames[k].to_expr();
431 let fk_prev = frames[k - 1].to_expr();
432
433 let fwd = VerifyExpr::and(
435 kinduction::instantiate_at(&fk_prev, 0),
436 VerifyExpr::not(kinduction::instantiate_at(&fk, 0)),
437 );
438 if is_sat(&fwd) {
439 return false;
440 }
441
442 let bwd = VerifyExpr::and(
444 kinduction::instantiate_at(&fk, 0),
445 VerifyExpr::not(kinduction::instantiate_at(&fk_prev, 0)),
446 );
447 !is_sat(&bwd)
448}
449
450fn extract_trace_from_bmc(
452 init: &VerifyExpr,
453 transition: &VerifyExpr,
454 property: &VerifyExpr,
455 depth: u32,
456 signal_names: &[String],
457) -> Trace {
458 let solver = crate::solver::new_solver();
459
460 let init_0 = kinduction::instantiate_at(init, 0);
462 solver.assert(&encode_bool(&init_0));
463
464 for t in 0..depth {
465 let trans = kinduction::instantiate_transition(transition, t);
466 solver.assert(&encode_bool(&trans));
467 }
468
469 let not_prop = VerifyExpr::not(kinduction::instantiate_at(property, depth));
471 solver.assert(&encode_bool(¬_prop));
472
473 if !matches!(solver.check(), SatResult::Sat) {
474 return Trace { cycles: vec![] };
475 }
476 let model = solver.get_model().unwrap();
477
478 let mut cycles = Vec::new();
480 for step in 0..=depth {
481 let mut signals = HashMap::new();
482 for sig in signal_names {
483 let var_name = format!("{}@{}", sig, step);
484
485 let bool_var = Bool::new_const(var_name.as_str());
487 if let Some(val) = model.eval(&bool_var, true) {
488 if let Some(b) = val.as_bool() {
489 signals.insert(sig.clone(), SignalValue::Bool(b));
490 continue;
491 }
492 }
493
494 let int_var = Int::new_const(var_name.as_str());
496 if let Some(val) = model.eval(&int_var, true) {
497 if let Some(n) = val.as_i64() {
498 signals.insert(sig.clone(), SignalValue::Int(n));
499 continue;
500 }
501 }
502 }
503 if !signals.is_empty() {
504 cycles.push(CycleState { cycle: step as usize, signals });
505 }
506 }
507
508 if cycles.is_empty() {
510 let mut signals = HashMap::new();
511 for sig in signal_names {
512 signals.insert(sig.clone(), SignalValue::Unknown);
513 }
514 cycles.push(CycleState { cycle: 0, signals });
515 }
516
517 Trace { cycles }
518}
519
520fn is_sat(expr: &VerifyExpr) -> bool {
522 let solver = crate::solver::new_solver();
523 let encoded = encode_bool(expr);
524 solver.assert(&encoded);
525 matches!(solver.check(), z3::SatResult::Sat)
526}
527
528pub fn check_sat(expr: &VerifyExpr) -> bool {
530 is_sat(expr)
531}
532
533fn encode_bool(expr: &VerifyExpr) -> z3::ast::Bool {
534 let mut bool_vars = HashMap::new();
535 let mut int_vars = HashMap::new();
536 let mut all_vars = std::collections::HashSet::new();
537 crate::equivalence::collect_vars_pub(expr, &mut all_vars);
538 for name in &all_vars {
539 bool_vars.insert(name.clone(), z3::ast::Bool::new_const(name.as_str()));
540 }
541 crate::equivalence::collect_int_vars_pub(expr, &mut int_vars);
542 kinduction::encode_expr_bool(expr, &bool_vars, &int_vars)
543}