Skip to main content

trame_runtime/
lib.rs

1//! All traits used to represent a runtime on which trame can operate.
2
3#![cfg_attr(kani, feature(stmt_expr_attributes))]
4#![cfg_attr(kani, feature(proc_macro_hygiene))]
5
6#[cfg(not(creusot))]
7pub mod live;
8
9#[cfg(not(creusot))]
10pub mod verified;
11
12#[cfg(creusot)]
13pub mod creusot_rt;
14
15use std::{alloc::Layout, marker::PhantomData};
16
17#[cfg(creusot)]
18use creusot_std::model::DeepModel;
19
20#[cfg(creusot)]
21use creusot_std::macros::{ensures, logic, requires};
22
23#[cfg(creusot)]
24use creusot_std::prelude::trusted;
25
26#[cfg(creusot)]
27pub type VLayout = creusot_rt::CLayout;
28
29#[cfg(creusot)]
30pub type VLayoutError = creusot_rt::CLayoutError;
31
32#[cfg(not(creusot))]
33pub use verified::{VLayout, VLayoutError};
34
35/// A heap and a shape implementation, over which Trame can be parameterized
36pub trait IRuntime {
37    type Shape: IShape;
38    type Heap: IHeap<Self::Shape, Ptr: IPtr>;
39    type Arena<T>: IArena<T>;
40
41    fn heap() -> Self::Heap;
42    fn arena<T>() -> Self::Arena<T>;
43}
44
45/// Marker trait for runtimes that use real facet shapes and raw pointers.
46#[cfg(not(creusot))]
47pub trait LiveRuntime:
48    IRuntime<Shape = &'static facet_core::Shape, Heap: IHeap<&'static facet_core::Shape, Ptr = *mut u8>>
49{
50}
51
52#[cfg(not(creusot))]
53impl<T> LiveRuntime for T where
54    T: IRuntime<
55            Shape = &'static facet_core::Shape,
56            Heap: IHeap<&'static facet_core::Shape, Ptr = *mut u8>,
57        >
58{
59}
60
61// ==================================================================
62// Shape
63// ==================================================================
64
65/// A store of shapes that can be looked up by handle.
66pub trait IShapeStore: Clone {
67    /// The handle type used to reference shapes in this store.
68    type Handle: Copy;
69
70    /// The view type produced by this store.
71    type View<'a>: IShape
72    where
73        Self: 'a;
74
75    /// Look up a shape by handle.
76    fn get<'a>(&'a self, handle: Self::Handle) -> Self::View<'a>;
77}
78
79#[cfg(creusot)]
80pub trait IShapeExtra: DeepModel {
81    #[logic]
82    fn size_logic(self) -> usize;
83}
84
85#[cfg(creusot)]
86impl<T: DeepModel> IShapeExtra for T {
87    #[logic(opaque)]
88    fn size_logic(self) -> usize {
89        dead
90    }
91}
92
93#[cfg(not(creusot))]
94pub trait IShapeExtra {}
95
96#[cfg(not(creusot))]
97impl<T> IShapeExtra for T {}
98
99/// Common interface for shapes.
100///
101/// Implemented by:
102/// - `&'static facet_core::Shape` (real shapes)
103/// - store-specific shape views (synthetic shapes for verification)
104///
105pub trait IShape: Copy + PartialEq + IShapeExtra {
106    /// The struct type returned by `as_struct()`.
107    type StructType: IStructType<Field = Self::Field>;
108
109    /// The field type used by struct types.
110    type Field: IField<Shape = Self>;
111
112    /// Get the layout (size and alignment) of this shape.
113    ///
114    /// Returns `None` for unsized types.
115    fn layout(&self) -> Option<Layout>;
116
117    /// Check if this is a struct type.
118    fn is_struct(&self) -> bool;
119
120    /// Get struct-specific information, if this is a struct.
121    fn as_struct(&self) -> Option<Self::StructType>;
122}
123
124/// Interface for struct type information.
125pub trait IStructType: Copy {
126    /// The field type.
127    type Field: IField;
128
129    /// Number of fields in this struct.
130    fn field_count(&self) -> usize;
131
132    /// Get field by index.
133    fn field(&self, idx: usize) -> Option<Self::Field>;
134}
135
136/// Interface for field information.
137pub trait IField: Copy {
138    /// The shape type.
139    type Shape: IShape;
140
141    /// Byte offset of this field within the struct.
142    fn offset(&self) -> usize;
143
144    /// Shape of this field's type.
145    fn shape(&self) -> Self::Shape;
146}
147
148// ==================================================================
149// Heap
150// ==================================================================
151
152/// Heap for memory operations, generic over shape type.
153pub trait IHeap<S: IShape> {
154    /// Pointer type used by this heap.
155    type Ptr: IPtr;
156
157    /// Allocate a region for a value of the given shape.
158    ///
159    /// # Safety
160    /// The caller must ensure `shape` is valid for allocation and that any
161    /// constraints required by the heap implementation are satisfied.
162    unsafe fn alloc(&mut self, shape: S) -> Self::Ptr;
163
164    /// Deallocate a region.
165    ///
166    /// # Safety
167    /// The caller must ensure `ptr` points to the start of a live allocation
168    /// previously returned by `alloc`, that the allocation corresponds to
169    /// `shape`, and that no bytes in the region are still initialized.
170    unsafe fn dealloc(&mut self, ptr: Self::Ptr, shape: S);
171
172    /// Copy `len` bytes from `src` to `dst`.
173    ///
174    /// # Safety
175    /// The caller must ensure both ranges are in-bounds for their allocations,
176    /// that `src` is fully initialized, `dst` is fully uninitialized, and the
177    /// ranges do not overlap.
178    #[cfg_attr(creusot, requires(self.range_init(src, len)))]
179    #[cfg_attr(creusot, ensures(self.range_init(dst, len)))]
180    #[cfg_attr(creusot, ensures(forall<ptr2, shape2> ptr2 != dst ==> (^self).can_drop(ptr2, shape2) == (*self).can_drop(ptr2, shape2)))]
181    #[cfg_attr(creusot, ensures(forall<ptr2, range2> ptr2 != dst ==> (^self).range_init(ptr2, range2) == (*self).range_init(ptr2, range2)))]
182    unsafe fn memcpy(&mut self, dst: Self::Ptr, src: Self::Ptr, len: usize);
183
184    /// Drop the value at `ptr` and mark the range as uninitialized.
185    ///
186    /// # Safety
187    /// The caller must ensure `ptr` points to a value of type `shape`, the
188    /// value is fully initialized, and the allocation is still live.
189    #[cfg_attr(creusot, requires(self.can_drop(ptr, shape)))]
190    #[cfg_attr(creusot, ensures(!(^self).can_drop(ptr, shape) && !(^self).range_init(ptr, shape.size_logic())))]
191    #[cfg_attr(creusot, ensures(forall<ptr2, shape2> ptr2 != ptr ==> (^self).can_drop(ptr2, shape2) == (*self).can_drop(ptr2, shape2)))]
192    #[cfg_attr(creusot, ensures(forall<ptr2, range2> ptr2 != ptr ==> (^self).range_init(ptr2, range2) == (*self).range_init(ptr2, range2)))]
193    unsafe fn drop_in_place(&mut self, ptr: Self::Ptr, shape: S);
194
195    /// Creusot-only predicate describing when a drop is permitted.
196    #[cfg(creusot)]
197    #[logic]
198    fn can_drop(&self, ptr: Self::Ptr, shape: S) -> bool;
199
200    /// Creusot-only predicate describing when a byte range is initialized.
201    ///
202    /// The range is interpreted as the `len` bytes starting at `ptr`.
203    #[cfg(creusot)]
204    #[logic]
205    fn range_init(&self, ptr: Self::Ptr, len: usize) -> bool;
206
207    /// Default-initialize the value at `ptr` and mark the range as initialized.
208    ///
209    /// Returns `false` if the shape has no default.
210    ///
211    /// # Safety
212    /// The caller must ensure the destination range is uninitialized, in-bounds,
213    /// and corresponds to `shape`.
214    #[cfg_attr(creusot, ensures(result ==> self.can_drop(ptr, shape) && self.range_init(ptr, shape.size_logic())))]
215    #[cfg_attr(creusot, ensures(!result ==> (^self).can_drop(ptr, shape) == (*self).can_drop(ptr, shape)))]
216    #[cfg_attr(creusot, ensures(!result ==> (^self).range_init(ptr, shape.size_logic()) == (*self).range_init(ptr, shape.size_logic())))]
217    #[cfg_attr(creusot, ensures(forall<ptr2, shape2> ptr2 != ptr ==> (^self).can_drop(ptr2, shape2) == (*self).can_drop(ptr2, shape2)))]
218    #[cfg_attr(creusot, ensures(forall<ptr2, range2> ptr2 != ptr ==> (^self).range_init(ptr2, range2) == (*self).range_init(ptr2, range2)))]
219    unsafe fn default_in_place(&mut self, ptr: Self::Ptr, shape: S) -> bool;
220}
221
222/// Pointer type
223pub trait IPtr: Copy {
224    /// Compute a new pointer at a byte offset from this one.
225    ///
226    /// # Safety
227    /// The caller must ensure the resulting pointer is in-bounds.
228    unsafe fn byte_add(self, n: usize) -> Self;
229}
230
231#[cfg(not(creusot))]
232impl IPtr for *mut u8 {
233    #[inline]
234    unsafe fn byte_add(self, n: usize) -> Self {
235        // SAFETY: caller ensures the resulting pointer is in-bounds.
236        unsafe { self.byte_add(n) }
237    }
238}
239
240// ============================================================================
241// Arena
242// ==================================================================
243
244/// Arena for allocating and managing items.
245pub trait IArena<T> {
246    /// Allocate a new item, returning its index.
247    fn alloc(&mut self, value: T) -> Idx<T>;
248
249    /// Free an item, returning it.
250    ///
251    /// # Panics
252    /// Panics if the index is invalid or already freed.
253    fn free(&mut self, id: Idx<T>) -> T;
254
255    /// Get a reference to an item.
256    ///
257    /// # Panics
258    /// Panics if the index is invalid or freed.
259    #[cfg_attr(creusot, requires(self.contains(id)))]
260    #[cfg_attr(creusot, ensures(*result == self.get_logic(id)))]
261    fn get(&self, id: Idx<T>) -> &T;
262
263    /// Get a mutable reference to an item.
264    ///
265    /// # Panics
266    /// Panics if the index is invalid or freed.
267    #[cfg_attr(creusot, requires(self.contains(id)))]
268    #[cfg_attr(creusot, ensures(*result == (*self).get_logic(id)))]
269    #[cfg_attr(creusot, ensures(^result == (^self).get_logic(id)))]
270    #[cfg_attr(creusot, ensures((^self).contains(id)))]
271    #[cfg_attr(creusot, ensures(forall<j> j != id ==> (*self).contains(j) == (^self).contains(j)))]
272    #[cfg_attr(creusot, ensures(forall<j> j != id ==> (*self).get_logic(j) == (^self).get_logic(j)))]
273    fn get_mut(&mut self, id: Idx<T>) -> &mut T;
274
275    #[cfg(creusot)]
276    #[logic]
277    fn contains(self, id: Idx<T>) -> bool;
278
279    #[cfg(creusot)]
280    #[logic]
281    fn get_logic(self, id: Idx<T>) -> T;
282}
283
284/// A typed index into an arena.
285///
286/// The phantom type prevents mixing indices from different arenas.
287#[derive(Debug)]
288pub struct Idx<T> {
289    pub raw: u32,
290    _ty: PhantomData<fn() -> T>,
291}
292
293#[cfg(creusot)]
294impl<T> creusot_std::model::DeepModel for Idx<T> {
295    type DeepModelTy = u32;
296
297    #[creusot_std::macros::logic(open, inline)]
298    fn deep_model(self) -> Self::DeepModelTy {
299        self.raw
300    }
301}
302
303impl<T> Clone for Idx<T> {
304    fn clone(&self) -> Self {
305        *self
306    }
307}
308
309impl<T> Copy for Idx<T> {}
310
311impl<T> PartialEq for Idx<T> {
312    #[cfg_attr(
313        creusot,
314        creusot_std::macros::ensures(result == (self.deep_model() == other.deep_model()))
315    )]
316    fn eq(&self, other: &Self) -> bool {
317        self.raw == other.raw
318    }
319}
320
321impl<T> Eq for Idx<T> {}
322
323impl<T> Idx<T> {
324    /// Sentinel: slot not started (reserved, slot 0)
325    pub const fn not_started() -> Self {
326        Self {
327            raw: 0,
328            _ty: PhantomData,
329        }
330    }
331
332    /// Sentinel: slot completed/freed
333    pub const fn complete() -> Self {
334        Self {
335            raw: u32::MAX,
336            _ty: PhantomData,
337        }
338    }
339
340    /// Sentinel: slot not started (reserved, slot 0)
341    #[cfg(not(creusot))]
342    pub const NOT_STARTED: Self = Self::not_started();
343
344    /// Sentinel: slot completed/freed
345    #[cfg(not(creusot))]
346    pub const COMPLETE: Self = Self::complete();
347
348    #[inline]
349    pub fn is_not_started(self) -> bool {
350        self.raw == 0
351    }
352
353    #[inline]
354    pub fn is_complete(self) -> bool {
355        self.raw == u32::MAX
356    }
357
358    #[inline]
359    pub fn is_valid(self) -> bool {
360        self.raw != 0 && self.raw != u32::MAX
361    }
362
363    #[inline]
364    pub fn same(self, other: Self) -> bool {
365        self.raw == other.raw
366    }
367
368    #[inline]
369    #[cfg_attr(creusot, trusted)]
370    fn index(self) -> usize {
371        debug_assert!(self.is_valid(), "cannot get index of sentinel");
372        self.raw as usize
373    }
374
375    /// Create an index from a raw value (for internal use).
376    fn from_raw(raw: u32) -> Self {
377        Self {
378            raw,
379            _ty: PhantomData,
380        }
381    }
382}
383
384// ==================================================================
385// Tests
386// ==================================================================
387
388// #[cfg(test)]
389// mod tests {
390//     use super::Idx;
391
392//     #[test]
393//     fn idx_sentinels() {
394//         assert!(Idx::<u32>::NOT_STARTED.is_not_started());
395//         assert!(!Idx::<u32>::NOT_STARTED.is_complete());
396//         assert!(!Idx::<u32>::NOT_STARTED.is_valid());
397
398//         assert!(!Idx::<u32>::COMPLETE.is_not_started());
399//         assert!(Idx::<u32>::COMPLETE.is_complete());
400//         assert!(!Idx::<u32>::COMPLETE.is_valid());
401//     }
402// }
403
404// #[cfg(kani)]
405// mod kani_proofs {
406//     use super::Idx;
407
408//     #[kani::proof]
409//     fn idx_sentinels_are_distinct() {
410//         let not_started: Idx<u32> = Idx::NOT_STARTED;
411//         let complete: Idx<u32> = Idx::COMPLETE;
412
413//         kani::assert(not_started.raw != complete.raw, "sentinels must differ");
414//         kani::assert(!not_started.is_valid(), "NOT_STARTED is not valid");
415//         kani::assert(!complete.is_valid(), "COMPLETE is not valid");
416//     }
417
418//     #[kani::proof]
419//     fn is_valid_excludes_sentinels() {
420//         let raw: u32 = kani::any();
421
422//         let idx: Idx<u32> = Idx::from_raw(raw);
423
424//         let expected_valid = raw != 0 && raw != u32::MAX;
425//         kani::assert(idx.is_valid() == expected_valid, "is_valid correctness");
426//     }
427// }