creusot_contracts/
peano.rs

1//! Peano integers
2//!
3//! Peano integers are a specialized kind of integers, that allow to increase the integer
4//! without checking for overflows.
5//!
6//! See <https://inria.hal.science/hal-01162661v1> for reference.
7//!
8//! # Usage in data structures
9//!
10//! They are useful when specifying a data structure where
11//! - checking for overflows of the length is hard (and may leak to the users of the library),
12//! - overflows are practically impossible because the length only grows by one at a time.
13//!
14//! In this case, you could use a [`PeanoInt`] to store the length.
15//!
16//! # Why not always use them ?
17//!
18//! Well, simply because you cannot add two peano integers together, at least not
19//! efficiently. If you need to do usual arithmetic operations, you should use a normal
20//! integer.
21//!
22//! # Ghost code
23//!
24//! You cannot increase a peano integer in [`ghost`] code, as it may
25//! overflow the backing integer. Since ghost code is not executed, the time argument is
26//! not applicable.
27
28use crate::{
29    logic::ord::ord_laws_impl,
30    prelude::{Clone, Default, *},
31};
32use std::cmp::Ordering;
33
34/// A peano integer wrapping a 64-bits integer.
35///
36/// See the [module](crate::peano) explanation.
37#[derive(Clone, Copy, Default, Eq)]
38#[non_exhaustive]
39#[repr(transparent)]
40pub struct PeanoInt(pub u64);
41
42impl DeepModel for PeanoInt {
43    type DeepModelTy = u64;
44    #[logic(open, inline)]
45    fn deep_model(self) -> u64 {
46        self.0
47    }
48}
49
50impl OrdLogic for PeanoInt {
51    #[logic(open, inline)]
52    fn cmp_log(self, o: Self) -> Ordering {
53        self.0.cmp_log(o.0)
54    }
55    #[logic(open, inline)]
56    fn le_log(self, o: Self) -> bool {
57        self.0.le_log(o.0)
58    }
59    #[logic(open, inline)]
60    fn lt_log(self, o: Self) -> bool {
61        self.0.lt_log(o.0)
62    }
63    #[logic(open, inline)]
64    fn ge_log(self, o: Self) -> bool {
65        self.0.ge_log(o.0)
66    }
67    #[logic(open, inline)]
68    fn gt_log(self, o: Self) -> bool {
69        self.0.gt_log(o.0)
70    }
71    ord_laws_impl! {}
72}
73
74impl View for PeanoInt {
75    type ViewTy = u64;
76    #[logic(open, inline)]
77    fn view(self) -> u64 {
78        self.0
79    }
80}
81
82impl PartialOrd for PeanoInt {
83    #[check(ghost)]
84    #[ensures(result == Some((*self).cmp_log(*other)))]
85    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
86        Some(self.cmp(other))
87    }
88
89    // FIXME: those implementations are only here to specify that they are `ghost`.
90    // We should have a mechanism to say 'These functions are ghost if `partial_cpm` is ghost'.
91
92    #[check(ghost)]
93    #[ensures(result == (self@ < other@))]
94    fn lt(&self, other: &Self) -> bool {
95        matches!(self.partial_cmp(other), Some(Ordering::Less))
96    }
97    #[check(ghost)]
98    #[ensures(result == (self@ <= other@))]
99    fn le(&self, other: &Self) -> bool {
100        matches!(self.partial_cmp(other), Some(Ordering::Less | Ordering::Equal))
101    }
102    #[check(ghost)]
103    #[ensures(result == (self@ > other@))]
104    fn gt(&self, other: &Self) -> bool {
105        matches!(self.partial_cmp(other), Some(Ordering::Greater))
106    }
107    #[check(ghost)]
108    #[ensures(result == (self@ >= other@))]
109    fn ge(&self, other: &Self) -> bool {
110        matches!(self.partial_cmp(other), Some(Ordering::Greater | Ordering::Equal))
111    }
112}
113impl Ord for PeanoInt {
114    #[check(ghost)]
115    #[ensures(result == (*self).cmp_log(*other))]
116    fn cmp(&self, other: &Self) -> Ordering {
117        self.0.cmp(&other.0)
118    }
119}
120impl PartialEq for PeanoInt {
121    #[check(ghost)]
122    #[ensures(result == (*self == *other))]
123    fn eq(&self, other: &Self) -> bool {
124        self.0 == other.0
125    }
126}
127
128impl PeanoInt {
129    /// Create a new peano integer with value `0`.
130    #[check(ghost)]
131    #[ensures(result.0 == 0u64)]
132    pub fn new() -> Self {
133        Self(0)
134    }
135
136    /// Increase the integer by one.
137    ///
138    /// This method guarantees that increments cannot get optimized together, e.g. that
139    /// ```rust
140    /// # use creusot_contracts::peano::PeanoInt;
141    /// let mut x = PeanoInt::new();
142    /// for _ in 0..1_000_000 {
143    ///     x.incr();
144    /// }
145    /// ```
146    /// Does not get optimized down to a single addition.
147    ///
148    /// Since the backing integer is 64 bits long, no program could ever actually reach
149    /// the point where the integer overflows.
150    #[trusted]
151    #[check(terminates)]
152    #[ensures(result.0@ == self.0@ + 1)]
153    pub fn incr(self) -> Self {
154        // Use volatile read, to avoid optimizing successive increments.
155        // SAFETY: using `read_volatile` on a reference of a `Copy` object is always safe.
156        let x = unsafe { std::ptr::read_volatile(&self.0) };
157        Self(x + 1)
158    }
159
160    /// Get the underlying integer.
161    #[check(ghost)]
162    #[ensures(result == self.0)]
163    pub fn to_u64(self) -> u64 {
164        self.0
165    }
166
167    /// Get the underlying integer.
168    #[check(ghost)]
169    #[trusted]
170    #[ensures(result@ == self.0@)]
171    pub fn to_i64(self) -> i64 {
172        self.0 as i64
173    }
174
175    /// Get the underlying integer.
176    #[check(ghost)]
177    #[ensures(result@ == self.0@)]
178    pub fn to_u128(self) -> u128 {
179        self.0 as u128
180    }
181
182    /// Get the underlying integer.
183    #[check(ghost)]
184    #[ensures(result@ == self.0@)]
185    pub fn to_i128(self) -> i128 {
186        self.0 as i128
187    }
188}
189
190impl From<PeanoInt> for u64 {
191    #[check(ghost)]
192    #[ensures(result == val.0)]
193    fn from(val: PeanoInt) -> Self {
194        val.to_u64()
195    }
196}
197
198impl From<PeanoInt> for i64 {
199    #[check(ghost)]
200    #[ensures(result@ == val.0@)]
201    fn from(val: PeanoInt) -> Self {
202        val.to_i64()
203    }
204}
205
206impl From<PeanoInt> for u128 {
207    #[check(ghost)]
208    #[ensures(result@ == val.0@)]
209    fn from(val: PeanoInt) -> Self {
210        val.to_u128()
211    }
212}
213
214impl From<PeanoInt> for i128 {
215    #[check(ghost)]
216    #[ensures(result@ == val.0@)]
217    fn from(val: PeanoInt) -> Self {
218        val.to_i128()
219    }
220}