Skip to main content

litex/infer/
infer_numeric_order_sign.rs

1use crate::prelude::*;
2use crate::verify::{compare_normalized_number_str_to_zero, NumberCompareResult};
3
4impl Runtime {
5    // Order atom with exactly one side a resolved numeric literal: may store `0 < x` or `x <= 0` for the other side.
6    // Example: `2 < a` (literal left) infers `0 < a` when the constant branch applies; `b < 0` pairs use the `<= 0` path on `b`.
7    //
8    // Additionally: comparing `x` with `0` on the **right** (`x < 0`, `x <= 0`, …) may infer the
9    // opposite sign on `(-1)*x` (e.g. `x < 0` => `(-1)*x >= 0`). We do **not** infer from `0 < x`
10    // (literal 0 on the left): that would require `x $in R` to store `(-1)*x < 0`, which often
11    // fails for scoped parameters. Verification builtins still prove such goals when needed.
12    // Skips operands already of the form `(-1)*u` so we do not chain `(-1)*((-1)*n)`.
13    pub fn infer_numeric_order_sign_from_order_atomic(
14        &mut self,
15        atomic_fact: &AtomicFact,
16    ) -> Result<InferResult, RuntimeError> {
17        let mut acc = match atomic_fact {
18            AtomicFact::GreaterEqualFact(f) => self.infer_numeric_order_sign_greater_equal(f),
19            AtomicFact::GreaterFact(f) => self.infer_numeric_order_sign_greater(f),
20            AtomicFact::LessEqualFact(f) => self.infer_numeric_order_sign_less_equal(f),
21            AtomicFact::LessFact(f) => self.infer_numeric_order_sign_less(f),
22            _ => Ok(InferResult::new()),
23        }?;
24        let flip = self.infer_flip_mul_minus_one_order_vs_zero(atomic_fact)?;
25        acc.new_infer_result_inside(flip);
26        Ok(acc)
27    }
28
29    fn order_flip_infer_operand_ok(&self, x: &Obj) -> bool {
30        self.peel_mul_by_literal_neg_one(x).is_none()
31    }
32
33    fn obj_mul_literal_neg_one(&self, x: Obj) -> Obj {
34        Mul::new(Number::new("-1".to_string()).into(), x).into()
35    }
36
37    fn atomic_fact_infer_opposite_mul_minus_one_target(
38        &self,
39        atomic_fact: &AtomicFact,
40    ) -> Option<AtomicFact> {
41        let z = Number::new("0".to_string()).into();
42        match atomic_fact {
43            AtomicFact::LessFact(f) if self.obj_is_resolved_zero(&f.right) => {
44                if !self.order_flip_infer_operand_ok(&f.left) {
45                    return None;
46                }
47                Some(
48                    GreaterEqualFact::new(
49                        self.obj_mul_literal_neg_one(f.left.clone()),
50                        z,
51                        f.line_file.clone(),
52                    )
53                    .into(),
54                )
55            }
56            AtomicFact::LessEqualFact(f) if self.obj_is_resolved_zero(&f.right) => {
57                if !self.order_flip_infer_operand_ok(&f.left) {
58                    return None;
59                }
60                Some(
61                    GreaterEqualFact::new(
62                        self.obj_mul_literal_neg_one(f.left.clone()),
63                        z,
64                        f.line_file.clone(),
65                    )
66                    .into(),
67                )
68            }
69            AtomicFact::GreaterFact(f) if self.obj_is_resolved_zero(&f.right) => {
70                if !self.order_flip_infer_operand_ok(&f.left) {
71                    return None;
72                }
73                Some(
74                    LessFact::new(
75                        self.obj_mul_literal_neg_one(f.left.clone()),
76                        z,
77                        f.line_file.clone(),
78                    )
79                    .into(),
80                )
81            }
82            AtomicFact::GreaterEqualFact(f) if self.obj_is_resolved_zero(&f.right) => {
83                if !self.order_flip_infer_operand_ok(&f.left) {
84                    return None;
85                }
86                Some(
87                    LessEqualFact::new(
88                        self.obj_mul_literal_neg_one(f.left.clone()),
89                        z,
90                        f.line_file.clone(),
91                    )
92                    .into(),
93                )
94            }
95            // No infer when literal `0` is on the **left** (e.g. `0 < a` from `a > k`, k>0).
96            // Flipping would store `(-1)*a < 0`, which requires `a $in R` for well-defined; parameters in a
97            // finite list or other scopes may not have that yet.
98            _ => None,
99        }
100    }
101
102    fn infer_flip_mul_minus_one_order_vs_zero(
103        &mut self,
104        atomic_fact: &AtomicFact,
105    ) -> Result<InferResult, RuntimeError> {
106        let Some(inferred_atomic) =
107            self.atomic_fact_infer_opposite_mul_minus_one_target(atomic_fact)
108        else {
109            return Ok(InferResult::new());
110        };
111        let fact_to_store: Fact = inferred_atomic.clone().into();
112        let mut infer_result = InferResult::new();
113        infer_result.new_fact(&fact_to_store);
114        // Do not run full `verify_fact_well_defined` here: well-defined for the flipped atom can re-enter
115        // `verify_fn_obj_well_defined` (e.g. intermediate `… $in N`) and this infer path again,
116        // causing mutual recursion / stack overflow (see `examples/euler_phi.lit`).
117        let inner = self
118            .store_atomic_fact_without_well_defined_verified_and_infer(inferred_atomic)
119            .map_err(|previous_error| {
120                RuntimeError::from(InferRuntimeError(RuntimeErrorStruct::new(
121                    None,
122                    "infer opposite sign mul (-1): failed to store inferred order fact".to_string(),
123                    atomic_fact.line_file(),
124                    Some(previous_error),
125                    vec![],
126                )))
127            })?;
128        infer_result.new_infer_result_inside(inner);
129        Ok(infer_result)
130    }
131
132    fn infer_numeric_order_sign_greater_equal(
133        &mut self,
134        f: &GreaterEqualFact,
135    ) -> Result<InferResult, RuntimeError> {
136        let left_num = self.resolve_obj_to_number(&f.left);
137        let right_num = self.resolve_obj_to_number(&f.right);
138        match (left_num, right_num) {
139            (Some(_), Some(_)) | (None, None) => Ok(InferResult::new()),
140            (None, Some(k)) => {
141                // L >= k and k > 0 => store `0 < L`
142                if matches!(
143                    compare_normalized_number_str_to_zero(&k.normalized_value),
144                    NumberCompareResult::Greater
145                ) {
146                    self.infer_store_gt_zero(f.left.clone(), f.line_file.clone())
147                } else {
148                    Ok(InferResult::new())
149                }
150            }
151            (Some(k), None) => {
152                // k >= R => R <= k; k < 0 => R <= 0
153                if matches!(
154                    compare_normalized_number_str_to_zero(&k.normalized_value),
155                    NumberCompareResult::Less
156                ) {
157                    self.infer_store_le_zero(f.right.clone(), f.line_file.clone())
158                } else {
159                    Ok(InferResult::new())
160                }
161            }
162        }
163    }
164
165    fn infer_numeric_order_sign_greater(
166        &mut self,
167        f: &GreaterFact,
168    ) -> Result<InferResult, RuntimeError> {
169        let left_num = self.resolve_obj_to_number(&f.left);
170        let right_num = self.resolve_obj_to_number(&f.right);
171        match (left_num, right_num) {
172            (Some(_), Some(_)) | (None, None) => Ok(InferResult::new()),
173            (None, Some(k)) => {
174                // L > k and k > 0 => store `0 < L`. If k == 0 the premise is already `0 < L`; do not re-store (avoids infinite infer).
175                if matches!(
176                    compare_normalized_number_str_to_zero(&k.normalized_value),
177                    NumberCompareResult::Greater
178                ) {
179                    self.infer_store_gt_zero(f.left.clone(), f.line_file.clone())
180                } else {
181                    Ok(InferResult::new())
182                }
183            }
184            (Some(k), None) => {
185                // k > R => R < k; k <= 0 => R <= 0
186                if matches!(
187                    compare_normalized_number_str_to_zero(&k.normalized_value),
188                    NumberCompareResult::Less | NumberCompareResult::Equal
189                ) {
190                    self.infer_store_le_zero(f.right.clone(), f.line_file.clone())
191                } else {
192                    Ok(InferResult::new())
193                }
194            }
195        }
196    }
197
198    fn infer_numeric_order_sign_less_equal(
199        &mut self,
200        f: &LessEqualFact,
201    ) -> Result<InferResult, RuntimeError> {
202        let left_num = self.resolve_obj_to_number(&f.left);
203        let right_num = self.resolve_obj_to_number(&f.right);
204        match (left_num, right_num) {
205            (Some(_), Some(_)) | (None, None) => Ok(InferResult::new()),
206            (None, Some(k)) => {
207                // L <= k and k < 0 => L <= 0
208                if matches!(
209                    compare_normalized_number_str_to_zero(&k.normalized_value),
210                    NumberCompareResult::Less
211                ) {
212                    self.infer_store_le_zero(f.left.clone(), f.line_file.clone())
213                } else {
214                    Ok(InferResult::new())
215                }
216            }
217            (Some(k), None) => {
218                // k <= R => R >= k; k > 0 => store `0 < R`
219                if matches!(
220                    compare_normalized_number_str_to_zero(&k.normalized_value),
221                    NumberCompareResult::Greater
222                ) {
223                    self.infer_store_gt_zero(f.right.clone(), f.line_file.clone())
224                } else {
225                    Ok(InferResult::new())
226                }
227            }
228        }
229    }
230
231    fn infer_numeric_order_sign_less(&mut self, f: &LessFact) -> Result<InferResult, RuntimeError> {
232        let left_num = self.resolve_obj_to_number(&f.left);
233        let right_num = self.resolve_obj_to_number(&f.right);
234        match (left_num, right_num) {
235            (Some(_), Some(_)) | (None, None) => Ok(InferResult::new()),
236            (None, Some(k)) => {
237                // L < k and k <= 0 => L <= 0
238                if matches!(
239                    compare_normalized_number_str_to_zero(&k.normalized_value),
240                    NumberCompareResult::Less | NumberCompareResult::Equal
241                ) {
242                    self.infer_store_le_zero(f.left.clone(), f.line_file.clone())
243                } else {
244                    Ok(InferResult::new())
245                }
246            }
247            (Some(k), None) => {
248                // k < R and k > 0 => store `0 < R`. If k == 0, premise is already `0 < R`; do not re-store (avoids infinite infer).
249                if matches!(
250                    compare_normalized_number_str_to_zero(&k.normalized_value),
251                    NumberCompareResult::Greater
252                ) {
253                    self.infer_store_gt_zero(f.right.clone(), f.line_file.clone())
254                } else {
255                    Ok(InferResult::new())
256                }
257            }
258        }
259    }
260
261    fn infer_store_gt_zero(
262        &mut self,
263        x: Obj,
264        line_file: LineFile,
265    ) -> Result<InferResult, RuntimeError> {
266        let fact_to_store =
267            LessFact::new(Number::new("0".to_string()).into(), x, line_file.clone()).into();
268        let mut infer_result = InferResult::new();
269        infer_result.new_fact(&fact_to_store);
270        self.verify_well_defined_and_store_and_infer_with_default_verify_state(fact_to_store)
271            .map_err(|previous_error| {
272                RuntimeError::from(InferRuntimeError(RuntimeErrorStruct::new(
273                    None,
274                    "infer numeric order sign: failed to store inferred (0 < x) bound".to_string(),
275                    line_file,
276                    Some(previous_error),
277                    vec![],
278                )))
279            })?;
280        Ok(infer_result)
281    }
282
283    fn infer_store_le_zero(
284        &mut self,
285        x: Obj,
286        line_file: LineFile,
287    ) -> Result<InferResult, RuntimeError> {
288        let fact_to_store =
289            LessEqualFact::new(x, Number::new("0".to_string()).into(), line_file.clone()).into();
290        let mut infer_result = InferResult::new();
291        infer_result.new_fact(&fact_to_store);
292        self.verify_well_defined_and_store_and_infer_with_default_verify_state(fact_to_store)
293            .map_err(|previous_error| {
294                RuntimeError::from(InferRuntimeError(RuntimeErrorStruct::new(
295                    None,
296                    "infer numeric order sign: failed to store inferred <= 0 bound".to_string(),
297                    line_file,
298                    Some(previous_error),
299                    vec![],
300                )))
301            })?;
302        Ok(infer_result)
303    }
304}
305
306#[cfg(test)]
307mod tests {
308    use crate::verify::{compare_normalized_number_str_to_zero, NumberCompareResult};
309
310    #[test]
311    fn compare_to_zero_matches_expectations() {
312        assert!(matches!(
313            compare_normalized_number_str_to_zero("1"),
314            NumberCompareResult::Greater
315        ));
316        assert!(matches!(
317            compare_normalized_number_str_to_zero("0"),
318            NumberCompareResult::Equal
319        ));
320        assert!(matches!(
321            compare_normalized_number_str_to_zero("-2"),
322            NumberCompareResult::Less
323        ));
324    }
325}