Skip to main content

creusot_std/
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::prelude::{Clone, Default, *};
29use core::cmp::Ordering;
30
31/// A peano integer wrapping a 64-bits integer.
32///
33/// See the [module](crate::peano) explanation.
34#[derive(Default, Clone, Copy, Eq)]
35#[non_exhaustive]
36#[repr(transparent)]
37pub struct PeanoInt(pub u64);
38
39impl DeepModel for PeanoInt {
40    type DeepModelTy = u64;
41    #[logic(open, inline)]
42    fn deep_model(self) -> u64 {
43        self.0
44    }
45}
46
47impl PartialOrdLogic for PeanoInt {
48    #[logic(open, inline)]
49    fn lt_log(self, o: Self) -> bool {
50        self.0 < o.0
51    }
52
53    #[logic(open, inline)]
54    fn le_log(self, o: Self) -> bool {
55        self.0 <= o.0
56    }
57
58    #[logic]
59    #[ensures(!(self < self))]
60    fn irreflexive(self) {}
61
62    #[logic]
63    #[requires(x < y)]
64    #[requires(y < z)]
65    #[ensures(x < z)]
66    fn transitive(x: Self, y: Self, z: Self) {}
67
68    #[logic]
69    #[ensures((self <= other) == (self < other || self == other))]
70    fn le_lt_log(self, other: Self) {}
71}
72
73impl OrdLogic for PeanoInt {
74    #[logic]
75    #[ensures(self < other || self == other || other < self)]
76    fn lt_log_total(self, other: Self) {}
77}
78
79impl View for PeanoInt {
80    type ViewTy = u64;
81    #[logic(open, inline)]
82    fn view(self) -> u64 {
83        self.0
84    }
85}
86
87impl PartialOrd for PeanoInt {
88    #[check(ghost)]
89    #[ensures(result == Some((*self).cmp_log(*other)))]
90    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
91        Some(self.cmp(other))
92    }
93
94    // FIXME: those implementations are only here to specify that they are `ghost`.
95    // We should have a mechanism to say 'These functions are ghost if `partial_cpm` is ghost'.
96
97    #[check(ghost)]
98    #[ensures(result == (self@ < other@))]
99    fn lt(&self, other: &Self) -> bool {
100        matches!(self.partial_cmp(other), Some(Ordering::Less))
101    }
102    #[check(ghost)]
103    #[ensures(result == (self@ <= other@))]
104    fn le(&self, other: &Self) -> bool {
105        matches!(self.partial_cmp(other), Some(Ordering::Less | Ordering::Equal))
106    }
107    #[check(ghost)]
108    #[ensures(result == (self@ > other@))]
109    fn gt(&self, other: &Self) -> bool {
110        matches!(self.partial_cmp(other), Some(Ordering::Greater))
111    }
112    #[check(ghost)]
113    #[ensures(result == (self@ >= other@))]
114    fn ge(&self, other: &Self) -> bool {
115        matches!(self.partial_cmp(other), Some(Ordering::Greater | Ordering::Equal))
116    }
117}
118impl Ord for PeanoInt {
119    #[check(ghost)]
120    #[ensures(result == (*self).cmp_log(*other))]
121    fn cmp(&self, other: &Self) -> Ordering {
122        self.0.cmp(&other.0)
123    }
124}
125impl PartialEq for PeanoInt {
126    #[check(ghost)]
127    #[ensures(result == (*self == *other))]
128    fn eq(&self, other: &Self) -> bool {
129        self.0 == other.0
130    }
131}
132
133impl PeanoInt {
134    /// Create a new peano integer with value `0`.
135    #[check(ghost)]
136    #[ensures(result.0 == 0u64)]
137    pub fn new() -> Self {
138        Self(0)
139    }
140
141    /// Increase the integer by one.
142    ///
143    /// This method guarantees that increments cannot get optimized together, e.g. that
144    /// ```rust
145    /// # use creusot_std::peano::PeanoInt;
146    /// let mut x = PeanoInt::new();
147    /// for _ in 0..1_000_000 {
148    ///     x.incr();
149    /// }
150    /// ```
151    /// Does not get optimized down to a single addition.
152    ///
153    /// Since the backing integer is 64 bits long, no program could ever actually reach
154    /// the point where the integer overflows.
155    #[trusted]
156    #[check(terminates)]
157    #[ensures(result.0@ == self.0@ + 1)]
158    pub fn incr(self) -> Self {
159        // Use volatile read, to avoid optimizing successive increments.
160        // SAFETY: using `read_volatile` on a reference of a `Copy` object is always safe.
161        let x = unsafe { core::ptr::read_volatile(&self.0) };
162        Self(x + 1)
163    }
164
165    /// Get the underlying integer.
166    #[check(ghost)]
167    #[ensures(result == self.0)]
168    pub fn to_u64(self) -> u64 {
169        self.0
170    }
171
172    /// Get the underlying integer.
173    #[check(ghost)]
174    #[trusted]
175    #[ensures(result@ == self.0@)]
176    pub fn to_i64(self) -> i64 {
177        self.0 as i64
178    }
179
180    /// Get the underlying integer.
181    #[check(ghost)]
182    #[ensures(result@ == self.0@)]
183    pub fn to_u128(self) -> u128 {
184        self.0 as u128
185    }
186
187    /// Get the underlying integer.
188    #[check(ghost)]
189    #[ensures(result@ == self.0@)]
190    pub fn to_i128(self) -> i128 {
191        self.0 as i128
192    }
193}
194
195impl From<PeanoInt> for u64 {
196    #[check(ghost)]
197    #[ensures(result == val.0)]
198    fn from(val: PeanoInt) -> Self {
199        val.to_u64()
200    }
201}
202
203impl From<PeanoInt> for i64 {
204    #[check(ghost)]
205    #[ensures(result@ == val.0@)]
206    fn from(val: PeanoInt) -> Self {
207        val.to_i64()
208    }
209}
210
211impl From<PeanoInt> for u128 {
212    #[check(ghost)]
213    #[ensures(result@ == val.0@)]
214    fn from(val: PeanoInt) -> Self {
215        val.to_u128()
216    }
217}
218
219impl From<PeanoInt> for i128 {
220    #[check(ghost)]
221    #[ensures(result@ == val.0@)]
222    fn from(val: PeanoInt) -> Self {
223        val.to_i128()
224    }
225}