Skip to main content

litex/verify/
verify_or_fact.rs

1use crate::prelude::*;
2use crate::verify::verify_equality_by_builtin_rules::objs_equal_by_display_string;
3use std::result::Result;
4
5/// Two atomic facts of the form `s > t` / `s <= t` (or `<` / `>=`) with the same left and right
6/// operands; the disjunction is a trivial order split (totally ordered carriers such as `R`).
7fn order_split_or_is_exhaustive_pair(a: &AtomicFact, b: &AtomicFact) -> bool {
8    use AtomicFact::*;
9    match (a, b) {
10        (GreaterFact(g), LessEqualFact(le)) => {
11            objs_equal_by_display_string(&g.left, &le.left)
12                && objs_equal_by_display_string(&g.right, &le.right)
13        }
14        (LessFact(l), GreaterEqualFact(ge)) => {
15            objs_equal_by_display_string(&l.left, &ge.left)
16                && objs_equal_by_display_string(&l.right, &ge.right)
17        }
18        (LessEqualFact(le), GreaterFact(g)) => {
19            objs_equal_by_display_string(&le.left, &g.left)
20                && objs_equal_by_display_string(&le.right, &g.right)
21        }
22        (GreaterEqualFact(ge), LessFact(l)) => {
23            objs_equal_by_display_string(&ge.left, &l.left)
24                && objs_equal_by_display_string(&ge.right, &l.right)
25        }
26        _ => false,
27    }
28}
29
30fn obj_is_literal_neg_one_for_abs_or_builtin(obj: &Obj) -> bool {
31    match obj {
32        Obj::Number(n) => n.normalized_value == "-1",
33        _ => false,
34    }
35}
36
37fn obj_is_negation_of_for_abs_or_builtin(obj: &Obj, expected_arg: &Obj) -> bool {
38    match obj {
39        Obj::Mul(m) => {
40            (obj_is_literal_neg_one_for_abs_or_builtin(m.left.as_ref())
41                && objs_equal_by_display_string(m.right.as_ref(), expected_arg))
42                || (obj_is_literal_neg_one_for_abs_or_builtin(m.right.as_ref())
43                    && objs_equal_by_display_string(m.left.as_ref(), expected_arg))
44        }
45        _ => false,
46    }
47}
48
49fn abs_sign_split_or_is_exhaustive_pair(a: &AtomicFact, b: &AtomicFact) -> bool {
50    let (AtomicFact::EqualFact(first), AtomicFact::EqualFact(second)) = (a, b) else {
51        return false;
52    };
53    let (arg, first_other) = match (&first.left, &first.right) {
54        (Obj::Abs(abs), other) => (abs.arg.as_ref(), other),
55        (other, Obj::Abs(abs)) => (abs.arg.as_ref(), other),
56        _ => return false,
57    };
58    if !objs_equal_by_display_string(arg, first_other) {
59        return false;
60    }
61    let (second_arg, second_other) = match (&second.left, &second.right) {
62        (Obj::Abs(abs), other) => (abs.arg.as_ref(), other),
63        (other, Obj::Abs(abs)) => (abs.arg.as_ref(), other),
64        _ => return false,
65    };
66    objs_equal_by_display_string(arg, second_arg)
67        && obj_is_negation_of_for_abs_or_builtin(second_other, arg)
68}
69
70fn positive_integer_number_to_usize_for_mod_or_builtin(number: &Number) -> Option<usize> {
71    let value = normalized_nonnegative_integer_number_to_usize_for_mod_or_builtin(number)?;
72    if value == 0 {
73        return None;
74    }
75    Some(value)
76}
77
78fn nonnegative_integer_number_to_usize_for_mod_or_builtin(number: &Number) -> Option<usize> {
79    normalized_nonnegative_integer_number_to_usize_for_mod_or_builtin(number)
80}
81
82fn normalized_nonnegative_integer_number_to_usize_for_mod_or_builtin(
83    number: &Number,
84) -> Option<usize> {
85    let value = number.normalized_value.trim();
86    if value.starts_with('-') {
87        return None;
88    }
89    let unsigned = value.trim_start_matches('+');
90    let integer_part = match unsigned.find('.') {
91        Some(index) => {
92            let fractional_part = &unsigned[index + 1..];
93            if !fractional_part.chars().all(|c| c == '0') {
94                return None;
95            }
96            &unsigned[..index]
97        }
98        None => unsigned,
99    };
100    integer_part.parse::<usize>().ok()
101}
102
103fn mod_obj_and_residue_from_atomic_equal_for_mod_or_builtin(
104    atomic_fact: &AtomicFact,
105) -> Option<(&Obj, &Number, &Number)> {
106    let AtomicFact::EqualFact(eq) = atomic_fact else {
107        return None;
108    };
109    match (&eq.left, &eq.right) {
110        (Obj::Mod(m), Obj::Number(residue)) => match m.right.as_ref() {
111            Obj::Number(modulus) => Some((m.left.as_ref(), modulus, residue)),
112            _ => None,
113        },
114        (Obj::Number(residue), Obj::Mod(m)) => match m.right.as_ref() {
115            Obj::Number(modulus) => Some((m.left.as_ref(), modulus, residue)),
116            _ => None,
117        },
118        _ => None,
119    }
120}
121
122// Remainder by a positive integer covers exactly one residue class.
123// If m > 1, `x % m = 0 or x % m = 1 or ... or x % m = m - 1` is exhaustive.
124// Example: `forall x Z: x % 2 = 0 or x % 2 = 1`.
125fn mod_positive_integer_residue_or_is_exhaustive(or_fact: &OrFact) -> bool {
126    if or_fact.facts.is_empty() {
127        return false;
128    }
129
130    let first_atomic = match &or_fact.facts[0] {
131        AndChainAtomicFact::AtomicFact(atomic) => atomic,
132        _ => return false,
133    };
134    let Some((first_obj, first_modulus, first_residue)) =
135        mod_obj_and_residue_from_atomic_equal_for_mod_or_builtin(first_atomic)
136    else {
137        return false;
138    };
139    let Some(modulus_value) = positive_integer_number_to_usize_for_mod_or_builtin(first_modulus)
140    else {
141        return false;
142    };
143    if modulus_value <= 1 || modulus_value != or_fact.facts.len() {
144        return false;
145    }
146
147    let mut seen_residues = vec![false; modulus_value];
148    let Some(first_residue_value) =
149        nonnegative_integer_number_to_usize_for_mod_or_builtin(first_residue)
150    else {
151        return false;
152    };
153    if first_residue_value >= modulus_value {
154        return false;
155    }
156    seen_residues[first_residue_value] = true;
157
158    for fact in or_fact.facts.iter().skip(1) {
159        let AndChainAtomicFact::AtomicFact(atomic) = fact else {
160            return false;
161        };
162        let Some((obj, modulus, residue)) =
163            mod_obj_and_residue_from_atomic_equal_for_mod_or_builtin(atomic)
164        else {
165            return false;
166        };
167        if !objs_equal_by_display_string(obj, first_obj)
168            || !objs_equal_by_display_string(
169                &Obj::Number(modulus.clone()),
170                &Obj::Number(first_modulus.clone()),
171            )
172        {
173            return false;
174        }
175        let Some(residue_value) = nonnegative_integer_number_to_usize_for_mod_or_builtin(residue)
176        else {
177            return false;
178        };
179        if residue_value >= modulus_value || seen_residues[residue_value] {
180            return false;
181        }
182        seen_residues[residue_value] = true;
183    }
184
185    seen_residues.iter().all(|seen| *seen)
186}
187
188fn obj_is_literal_zero_for_or_builtin(obj: &Obj) -> bool {
189    match obj {
190        Obj::Number(n) => n.normalized_value == "0",
191        _ => false,
192    }
193}
194
195fn nonzero_operand_from_atomic_fact_for_square_sum_or_builtin(atomic: &AtomicFact) -> Option<Obj> {
196    let AtomicFact::NotEqualFact(not_equal) = atomic else {
197        return None;
198    };
199    if obj_is_literal_zero_for_or_builtin(&not_equal.right) {
200        return Some(not_equal.left.clone());
201    }
202    if obj_is_literal_zero_for_or_builtin(&not_equal.left) {
203        return Some(not_equal.right.clone());
204    }
205    None
206}
207
208fn square_pow_sum_not_equal_zero_fact_for_or_builtin(
209    left_base: Obj,
210    right_base: Obj,
211    line_file: LineFile,
212) -> AtomicFact {
213    let two_obj: Obj = Number::new("2".to_string()).into();
214    let zero_obj: Obj = Number::new("0".to_string()).into();
215    let left_square: Obj = Pow::new(left_base, two_obj.clone()).into();
216    let right_square: Obj = Pow::new(right_base, two_obj).into();
217    let square_sum: Obj = Add::new(left_square, right_square).into();
218    NotEqualFact::new(square_sum, zero_obj, line_file).into()
219}
220
221fn square_mul_sum_not_equal_zero_fact_for_or_builtin(
222    left_base: Obj,
223    right_base: Obj,
224    line_file: LineFile,
225) -> AtomicFact {
226    let zero_obj: Obj = Number::new("0".to_string()).into();
227    let left_square: Obj = Mul::new(left_base.clone(), left_base).into();
228    let right_square: Obj = Mul::new(right_base.clone(), right_base).into();
229    let square_sum: Obj = Add::new(left_square, right_square).into();
230    NotEqualFact::new(square_sum, zero_obj, line_file).into()
231}
232
233impl Runtime {
234    pub fn verify_or_fact(
235        &mut self,
236        or_fact: &OrFact,
237        verify_state: &VerifyState,
238    ) -> Result<StmtResult, RuntimeError> {
239        if let Some(cached_result) =
240            self.verify_fact_from_cache_using_display_string(&or_fact.clone().into())
241        {
242            return Ok(cached_result);
243        }
244
245        if !verify_state.well_defined_already_verified {
246            if let Err(e) = self.verify_or_fact_well_defined(or_fact, verify_state) {
247                return Err({
248                    VerifyRuntimeError(RuntimeErrorStruct::new(
249                        Some(Fact::from(or_fact.clone()).into_stmt()),
250                        String::new(),
251                        or_fact.line_file.clone(),
252                        Some(e),
253                        vec![],
254                    ))
255                    .into()
256                });
257            }
258        }
259
260        let verify_state_for_children = verify_state.make_state_with_req_ok_set_to_true();
261
262        if or_fact.facts.len() == 2 {
263            if let (
264                AndChainAtomicFact::AtomicFact(first_atomic),
265                AndChainAtomicFact::AtomicFact(second_atomic),
266            ) = (&or_fact.facts[0], &or_fact.facts[1])
267            {
268                if first_atomic.make_reversed().to_string() == second_atomic.to_string() {
269                    return Ok(
270                        (FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
271                            or_fact.clone().into(),
272                            "or: complementary atomic facts (make_reversed first equals second)"
273                                .to_string(),
274                            Vec::new(),
275                        ))
276                        .into(),
277                    );
278                }
279                if order_split_or_is_exhaustive_pair(first_atomic, second_atomic)
280                    || order_split_or_is_exhaustive_pair(second_atomic, first_atomic)
281                {
282                    return Ok(
283                        (FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
284                            or_fact.clone().into(),
285                            "or: complementary order relations (strict vs non-strict) on the same terms"
286                                .to_string(),
287                            Vec::new(),
288                        ))
289                        .into(),
290                    );
291                }
292                if abs_sign_split_or_is_exhaustive_pair(first_atomic, second_atomic)
293                    || abs_sign_split_or_is_exhaustive_pair(second_atomic, first_atomic)
294                {
295                    return Ok(
296                        (FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
297                            or_fact.clone().into(),
298                            "or: abs(x) is x or -x".to_string(),
299                            Vec::new(),
300                        ))
301                        .into(),
302                    );
303                }
304            }
305        }
306
307        if mod_positive_integer_residue_or_is_exhaustive(or_fact) {
308            return Ok(
309                (FactualStmtSuccess::new_with_verified_by_builtin_rules_recording_stmt(
310                    or_fact.clone().into(),
311                    "or: complete residue classes modulo a positive integer".to_string(),
312                    Vec::new(),
313                ))
314                .into(),
315            );
316        }
317
318        if let Some(result) =
319            self.try_verify_component_nonzero_or_from_known_square_sum_not_equal_zero(or_fact)?
320        {
321            return Ok(result);
322        }
323
324        for fact in or_fact.facts.iter() {
325            let result = self.verify_and_chain_atomic_fact(fact, &verify_state_for_children)?;
326            if result.is_true() {
327                return Ok((FactualStmtSuccess::new_with_verified_by_known_fact(
328                    or_fact.clone().into(),
329                    VerifiedByResult::wrap_bys(vec![VerifiedBysEnum::cited_fact(
330                        or_fact.clone().into(),
331                        fact.clone().into(),
332                        None,
333                    )]),
334                    Vec::new(),
335                ))
336                .into());
337            }
338        }
339
340        let result = self.verify_or_fact_with_known_or_facts(or_fact)?;
341        if result.is_true() {
342            return Ok(result);
343        }
344
345        let result = self.verify_or_fact_with_known_forall(or_fact, verify_state)?;
346        if result.is_true() {
347            return Ok(result);
348        }
349
350        Ok((StmtUnknown::new()).into())
351    }
352
353    fn try_verify_component_nonzero_or_from_known_square_sum_not_equal_zero(
354        &mut self,
355        or_fact: &OrFact,
356    ) -> Result<Option<StmtResult>, RuntimeError> {
357        if or_fact.facts.len() != 2 {
358            return Ok(None);
359        }
360        let (
361            AndChainAtomicFact::AtomicFact(first_atomic),
362            AndChainAtomicFact::AtomicFact(second_atomic),
363        ) = (&or_fact.facts[0], &or_fact.facts[1])
364        else {
365            return Ok(None);
366        };
367        let Some(first_base) =
368            nonzero_operand_from_atomic_fact_for_square_sum_or_builtin(first_atomic)
369        else {
370            return Ok(None);
371        };
372        let Some(second_base) =
373            nonzero_operand_from_atomic_fact_for_square_sum_or_builtin(second_atomic)
374        else {
375            return Ok(None);
376        };
377
378        let line_file = or_fact.line_file.clone();
379        let candidates = vec![
380            square_pow_sum_not_equal_zero_fact_for_or_builtin(
381                first_base.clone(),
382                second_base.clone(),
383                line_file.clone(),
384            ),
385            square_pow_sum_not_equal_zero_fact_for_or_builtin(
386                second_base.clone(),
387                first_base.clone(),
388                line_file.clone(),
389            ),
390            square_mul_sum_not_equal_zero_fact_for_or_builtin(
391                first_base.clone(),
392                second_base.clone(),
393                line_file.clone(),
394            ),
395            square_mul_sum_not_equal_zero_fact_for_or_builtin(second_base, first_base, line_file),
396        ];
397
398        for candidate in candidates {
399            let result =
400                self.verify_non_equational_atomic_fact_with_known_atomic_facts(&candidate)?;
401            if result.is_true() {
402                return Ok(Some(
403                    FactualStmtSuccess::new_with_verified_by_builtin_rules_label_and_steps(
404                        or_fact.clone().into(),
405                        InferResult::new(),
406                        "or: square sum nonzero implies one component nonzero".to_string(),
407                        vec![result],
408                    )
409                    .into(),
410                ));
411            }
412        }
413        Ok(None)
414    }
415
416    pub fn verify_or_fact_with_known_or_facts(
417        &mut self,
418        or_fact: &OrFact,
419    ) -> Result<StmtResult, RuntimeError> {
420        let args_in_or_fact = or_fact.get_args_from_fact();
421        let mut all_objs_equal_to_each_arg: Vec<Vec<String>> = Vec::new();
422        for arg in args_in_or_fact.iter() {
423            let mut all_objs_equal_to_current_arg =
424                self.get_all_objs_equal_to_given(&arg.to_string());
425            if all_objs_equal_to_current_arg.is_empty() {
426                all_objs_equal_to_current_arg.push(arg.to_string());
427            }
428            all_objs_equal_to_each_arg.push(all_objs_equal_to_current_arg);
429        }
430
431        for environment in self.iter_environments_from_top() {
432            let result = Self::verify_or_fact_with_known_or_facts_with_facts_in_environment(
433                environment,
434                or_fact,
435                &all_objs_equal_to_each_arg,
436            )?;
437            if result.is_true() {
438                return Ok(result);
439            }
440        }
441
442        Ok((StmtUnknown::new()).into())
443    }
444
445    pub fn verify_or_fact_with_known_or_facts_with_facts_in_environment(
446        environment: &Environment,
447        or_fact: &OrFact,
448        all_objs_equal_to_each_arg: &Vec<Vec<String>>,
449    ) -> Result<StmtResult, RuntimeError> {
450        if let Some(known_or_facts) = environment.known_or_facts.get(&or_fact.key()) {
451            for known_or_fact in known_or_facts.iter() {
452                let result = Self::_verify_or_fact_the_same_type_and_return_matched_args(
453                    known_or_fact,
454                    or_fact,
455                )?;
456                let mut all_args_match = true;
457                if let Some(matched_args) = result {
458                    for (index, matched_arg) in matched_args.iter().enumerate() {
459                        let known_arg_string = matched_arg.0.to_string();
460
461                        if known_arg_string != matched_arg.1.to_string()
462                            && !all_objs_equal_to_each_arg[index].contains(&known_arg_string)
463                        {
464                            all_args_match = false;
465                            break;
466                        }
467                    }
468                }
469
470                if all_args_match {
471                    return Ok((FactualStmtSuccess::new_with_verified_by_known_fact(
472                        or_fact.clone().into(),
473                        VerifiedByResult::wrap_bys(vec![VerifiedBysEnum::cited_fact(
474                            or_fact.clone().into(),
475                            known_or_fact.clone().into(),
476                            None,
477                        )]),
478                        Vec::new(),
479                    ))
480                    .into());
481                }
482            }
483        }
484
485        return Ok((StmtUnknown::new()).into());
486    }
487}