1#[cfg(not(creusot))]
5use core::cell::UnsafeCell;
6use std::alloc::Layout;
7
8#[cfg(creusot)]
9use creusot_std::model::DeepModel;
10
11mod byte_range;
12use byte_range::{ByteRangeError, ByteRangeTracker, Range};
13
14use crate::{IArena, IField, IHeap, IPtr, IRuntime, IShape, IShapeStore, IStructType, Idx};
15
16pub struct VRuntime;
18
19#[cfg(not(creusot))]
20struct VShapeStoreCell(UnsafeCell<VShapeStore>);
21
22#[cfg(not(creusot))]
24unsafe impl Sync for VShapeStoreCell {}
25
26#[cfg(not(creusot))]
27static VSHAPE_STORE: VShapeStoreCell = VShapeStoreCell(UnsafeCell::new(VShapeStore::new()));
28
29#[cfg(not(creusot))]
30pub fn vshape_store() -> &'static VShapeStore {
31 unsafe { &*VSHAPE_STORE.0.get() }
32}
33
34#[cfg(not(creusot))]
36pub fn vshape_register(shape: VShapeDef) -> VShapeHandle {
37 unsafe { (&mut *VSHAPE_STORE.0.get()).add(shape) }
38}
39
40#[cfg(not(creusot))]
42pub fn vshape_view(handle: VShapeHandle) -> VShapeView<'static, VShapeStore> {
43 unsafe { (&*VSHAPE_STORE.0.get()).view(handle) }
44}
45
46#[cfg(not(creusot))]
51pub unsafe fn vshape_store_reset() {
52 unsafe { *VSHAPE_STORE.0.get() = VShapeStore::new() }
53}
54
55#[cfg(creusot)]
56pub fn vshape_store() -> &'static VShapeStore {
57 panic!("vshape_store is unavailable under creusot")
58}
59
60#[cfg(creusot)]
61pub fn vshape_register(_shape: VShapeDef) -> VShapeHandle {
62 panic!("vshape_register is unavailable under creusot")
63}
64
65#[cfg(creusot)]
66pub fn vshape_view(_handle: VShapeHandle) -> VShapeView<'static, VShapeStore> {
67 panic!("vshape_view is unavailable under creusot")
68}
69
70#[cfg(creusot)]
71pub unsafe fn vshape_store_reset() {
72 }
74
75impl IRuntime for VRuntime {
76 type Shape = VShapeView<'static, VShapeStore>;
77 type Heap = VHeap<Self::Shape>;
78 type Arena<T> = VArena<T, MAX_VARENA_SLOTS>;
79
80 fn heap() -> Self::Heap {
81 VHeap::new()
82 }
83
84 fn arena<T>() -> Self::Arena<T> {
85 VArena::new()
86 }
87}
88
89pub const MAX_SHAPES_PER_STORE: usize = 8;
95
96pub const MAX_FIELDS_PER_STRUCT: usize = 8;
98
99#[cfg_attr(creusot, derive(DeepModel))]
103#[derive(Debug, Clone, Copy, PartialEq, Eq)]
104pub struct VShapeHandle(pub u8);
105
106#[cfg_attr(creusot, derive(DeepModel))]
111#[derive(Debug, Clone, Copy, PartialEq, Eq)]
112pub struct VFieldDef {
113 pub offset: usize,
115
116 pub shape_handle: VShapeHandle,
118}
119
120#[cfg_attr(creusot, derive(DeepModel))]
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub struct VStructDef {
124 pub field_count: u8,
126
127 pub fields: [VFieldDef; MAX_FIELDS_PER_STRUCT],
129}
130
131#[cfg_attr(creusot, derive(DeepModel))]
138#[derive(Debug, Clone, Copy, PartialEq, Eq)]
139pub struct VShapeDef {
140 pub layout: VLayout,
142 pub def: VDef,
145}
146
147#[cfg_attr(creusot, derive(DeepModel))]
149#[derive(Debug, Clone, Copy, PartialEq, Eq)]
150pub enum VDef {
151 Scalar,
153 Struct(VStructDef),
155 }
157
158#[cfg_attr(creusot, derive(DeepModel))]
164#[derive(Debug, Clone, Copy, PartialEq, Eq)]
165pub struct VShapeStore {
166 pub shape_count: u8,
168
169 pub shapes: [VShapeDef; MAX_SHAPES_PER_STORE],
171}
172
173#[derive(Clone, Copy)]
177pub struct VShapeView<'a, Store: IShapeStore> {
178 pub store: &'a Store,
179 pub handle: Store::Handle,
180}
181
182impl VShapeStore {
183 pub const fn new() -> Self {
185 Self {
186 shape_count: 0,
187 shapes: [VShapeDef {
188 layout: VLayout::new::<()>(),
189 def: VDef::Scalar,
190 }; MAX_SHAPES_PER_STORE],
191 }
192 }
193
194 pub fn add(&mut self, shape: VShapeDef) -> VShapeHandle {
196 assert!(
197 (self.shape_count as usize) < MAX_SHAPES_PER_STORE,
198 "shape store full"
199 );
200 let handle = VShapeHandle(self.shape_count);
201 self.shapes[self.shape_count as usize] = shape;
202 self.shape_count += 1;
203 handle
204 }
205
206 pub fn get_def(&self, handle: VShapeHandle) -> &VShapeDef {
208 assert!(
209 (handle.0 as usize) < self.shape_count as usize,
210 "invalid handle"
211 );
212 &self.shapes[handle.0 as usize]
213 }
214
215 pub fn view(&self, handle: VShapeHandle) -> VShapeView<'_, Self> {
217 VShapeView {
218 store: self,
219 handle,
220 }
221 }
222}
223
224impl Default for VShapeStore {
225 fn default() -> Self {
226 Self::new()
227 }
228}
229
230impl IShapeStore for VShapeStore {
231 type Handle = VShapeHandle;
232 type View<'a>
233 = VShapeView<'a, VShapeStore>
234 where
235 Self: 'a;
236
237 fn get<'a>(&'a self, handle: Self::Handle) -> Self::View<'a> {
238 self.view(handle)
239 }
240}
241
242impl IShapeStore for &'static VShapeStore {
243 type Handle = VShapeHandle;
244 type View<'a>
245 = VShapeView<'a, VShapeStore>
246 where
247 Self: 'a;
248
249 fn get<'a>(&'a self, handle: Self::Handle) -> Self::View<'a> {
250 VShapeView {
251 store: *self,
252 handle,
253 }
254 }
255}
256
257#[derive(Clone, Copy)]
259pub struct VFieldView<'a> {
260 pub store: &'a VShapeStore,
261 pub def: &'a VFieldDef,
262}
263
264#[derive(Clone, Copy)]
266pub struct VStructView<'a> {
267 pub store: &'a VShapeStore,
268 pub def: &'a VStructDef,
269}
270
271impl<'a> PartialEq for VShapeView<'a, VShapeStore> {
272 fn eq(&self, other: &Self) -> bool {
273 core::ptr::eq(self.store, other.store) && self.handle == other.handle
276 }
277}
278
279impl<'a> Eq for VShapeView<'a, VShapeStore> {}
280
281impl<'a> IShape for VShapeView<'a, VShapeStore> {
282 type StructType = VStructView<'a>;
283 type Field = VFieldView<'a>;
284
285 #[inline]
286 fn layout(&self) -> Option<Layout> {
287 #[cfg(creusot)]
288 {
289 Some(self.store.get_def(self.handle).layout.to_layout())
290 }
291 #[cfg(not(creusot))]
292 {
293 Some(self.store.get_def(self.handle).layout)
294 }
295 }
296
297 #[inline]
298 fn is_struct(&self) -> bool {
299 matches!(self.store.get_def(self.handle).def, VDef::Struct(_))
300 }
301
302 #[inline]
303 fn as_struct(&self) -> Option<Self::StructType> {
304 match &self.store.get_def(self.handle).def {
305 VDef::Struct(s) => Some(VStructView {
306 store: self.store,
307 def: s,
308 }),
309 _ => None,
310 }
311 }
312}
313
314impl<'a> IStructType for VStructView<'a> {
315 type Field = VFieldView<'a>;
316
317 #[inline]
318 fn field_count(&self) -> usize {
319 self.def.field_count as usize
320 }
321
322 #[inline]
323 fn field(&self, idx: usize) -> Option<Self::Field> {
324 if idx < self.def.field_count as usize {
325 Some(VFieldView {
326 store: self.store,
327 def: &self.def.fields[idx],
328 })
329 } else {
330 None
331 }
332 }
333}
334
335impl<'a> IField for VFieldView<'a> {
336 type Shape = VShapeView<'a, VShapeStore>;
337
338 #[inline]
339 fn offset(&self) -> usize {
340 self.def.offset
341 }
342
343 #[inline]
344 fn shape(&self) -> Self::Shape {
345 self.store.view(self.def.shape_handle)
347 }
348}
349
350impl VShapeDef {
355 pub const fn scalar(layout: VLayout) -> Self {
357 Self {
358 layout,
359 def: VDef::Scalar,
360 }
361 }
362
363 pub fn struct_with_fields(store: &VShapeStore, fields: &[(usize, VShapeHandle)]) -> Self {
368 assert!(fields.len() <= MAX_FIELDS_PER_STRUCT, "too many fields");
369
370 let mut size = 0usize;
372 let mut align = 1usize;
373
374 for &(offset, shape_handle) in fields {
375 let field_layout = store.get_def(shape_handle).layout;
376 align = align.max(field_layout.align());
377 let field_end = offset + field_layout.size();
378 size = size.max(field_end);
379 }
380
381 size = (size + align - 1) & !(align - 1);
383
384 let layout = VLayout::from_size_align(size, align).expect("valid layout");
385
386 let mut field_array = [VFieldDef {
387 offset: 0,
388 shape_handle: VShapeHandle(0),
389 }; MAX_FIELDS_PER_STRUCT];
390 for (i, &(offset, shape_handle)) in fields.iter().enumerate() {
391 field_array[i] = VFieldDef {
392 offset,
393 shape_handle,
394 };
395 }
396
397 Self {
398 layout,
399 def: VDef::Struct(VStructDef {
400 field_count: fields.len() as u8,
401 fields: field_array,
402 }),
403 }
404 }
405}
406
407impl VFieldDef {
408 pub const fn new(offset: usize, shape_handle: VShapeHandle) -> Self {
410 Self {
411 offset,
412 shape_handle,
413 }
414 }
415}
416
417#[cfg(kani)]
422impl kani::Arbitrary for VShapeDef {
423 fn any() -> Self {
424 let is_struct: bool = kani::any();
425
426 if is_struct {
427 let field_count: u8 = kani::any();
429 kani::assume(field_count > 0 && field_count <= 4);
430
431 let mut fields = [VFieldDef {
432 offset: 0,
433 shape_handle: VShapeHandle(0),
434 }; MAX_FIELDS_PER_STRUCT];
435
436 let mut offset = 0usize;
437 for i in 0..(field_count as usize) {
438 let field_size: usize = kani::any();
439 kani::assume(field_size > 0 && field_size <= 8);
440
441 fields[i] = VFieldDef::new(offset, VShapeHandle(0));
444 offset += field_size;
445 }
446
447 kani::assume(offset <= 64);
448
449 let layout = VLayout::from_size_align(offset, 1).unwrap();
450
451 VShapeDef {
452 layout,
453 def: VDef::Struct(VStructDef {
454 field_count,
455 fields,
456 }),
457 }
458 } else {
459 let size: usize = kani::any();
461 let align_pow: u8 = kani::any();
462 kani::assume(size <= 64);
463 kani::assume(align_pow <= 3);
464 let align = 1usize << align_pow;
465 kani::assume(size == 0 || size % align == 0);
466
467 let layout = VLayout::from_size_align(size, align).unwrap();
468 VShapeDef::scalar(layout)
469 }
470 }
471}
472
473#[cfg(kani)]
475impl kani::Arbitrary for VShapeStore {
476 fn any() -> Self {
477 let mut store = VShapeStore::new();
478
479 let num_scalars: u8 = kani::any();
481 kani::assume(num_scalars >= 1 && num_scalars <= 4);
482
483 for _ in 0..num_scalars {
484 let size: usize = kani::any();
485 kani::assume(size > 0 && size <= 8);
486 let layout = VLayout::from_size_align(size, 1).unwrap();
487 store.add(VShapeDef::scalar(layout));
488 }
489
490 let add_struct: bool = kani::any();
492 if add_struct {
493 let field_count: u8 = kani::any();
494 kani::assume(field_count > 0 && field_count <= 4);
495
496 let mut fields = [VFieldDef {
497 offset: 0,
498 shape_handle: VShapeHandle(0),
499 }; MAX_FIELDS_PER_STRUCT];
500
501 let mut offset = 0usize;
502 for i in 0..(field_count as usize) {
503 let shape_idx: u8 = kani::any();
505 kani::assume(shape_idx < num_scalars);
506
507 let field_layout = store.get_def(VShapeHandle(shape_idx)).layout;
508 fields[i] = VFieldDef::new(offset, VShapeHandle(shape_idx));
509 offset += field_layout.size();
510 }
511
512 kani::assume(offset <= 64);
513
514 let layout = VLayout::from_size_align(offset, 1).unwrap();
515 store.add(VShapeDef {
516 layout,
517 def: VDef::Struct(VStructDef {
518 field_count,
519 fields,
520 }),
521 });
522 }
523
524 store
525 }
526}
527
528#[derive(Debug, Clone, Copy, PartialEq, Eq)]
536pub struct VPtr {
537 pub alloc_id: u8,
539 pub offset: u32,
541 pub size: u32,
543}
544
545impl IPtr for VPtr {
546 #[inline]
547 unsafe fn byte_add(self, n: usize) -> Self {
548 VPtr::offset(self, n)
549 }
550}
551
552impl VPtr {
553 #[inline]
555 pub const fn new(alloc_id: u8, size: u32) -> Self {
556 Self {
557 alloc_id,
558 offset: 0,
559 size,
560 }
561 }
562
563 #[inline]
568 pub fn offset(self, n: usize) -> Self {
569 let new_offset = self.offset.checked_add(n as u32).expect("offset overflow");
570 #[cfg(not(creusot))]
571 assert!(
572 new_offset <= self.size,
573 "pointer arithmetic out of bounds: offset {} + {} = {} > size {}",
574 self.offset,
575 n,
576 new_offset,
577 self.size
578 );
579 #[cfg(creusot)]
580 {
581 if new_offset > self.size {
582 panic!("pointer arithmetic out of bounds");
583 }
584 }
585 Self {
586 alloc_id: self.alloc_id,
587 offset: new_offset,
588 size: self.size,
589 }
590 }
591
592 #[inline]
594 pub const fn offset_bytes(self) -> usize {
595 self.offset as usize
596 }
597
598 #[inline]
600 pub const fn alloc_id(self) -> u8 {
601 self.alloc_id
602 }
603
604 #[inline]
606 pub const fn alloc_size(self) -> usize {
607 self.size as usize
608 }
609
610 #[inline]
612 pub const fn is_at_start(self) -> bool {
613 self.offset == 0
614 }
615
616 #[inline]
618 pub const fn remaining(self) -> usize {
619 (self.size - self.offset) as usize
620 }
621}
622
623pub const MAX_VHEAP_ALLOCS: usize = 8;
629
630#[derive(Debug, Clone, Copy, PartialEq, Eq)]
632pub enum VHeapError {
633 AllocationFreed,
635 ShapeMismatch,
637 NotAtStart,
639 NotFullyUninitialized,
641 ByteRange(ByteRangeError),
643 TooManyAllocations,
645}
646
647impl From<ByteRangeError> for VHeapError {
648 fn from(e: ByteRangeError) -> Self {
649 VHeapError::ByteRange(e)
650 }
651}
652
653#[cfg(all(not(kani), not(creusot)))]
659#[derive(Clone)]
660pub struct HeapOp {
661 pub kind: HeapOpKind,
663 pub range: Option<Range>,
665 pub backtrace: String,
667}
668
669#[cfg(all(not(kani), not(creusot)))]
670impl std::fmt::Debug for HeapOp {
671 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
672 write!(f, "{:?}", self.kind)?;
673 if let Some(range) = self.range {
674 write!(f, " [{}..{}]", range.start, range.end)?;
675 }
676 write!(f, "\n{}", self.backtrace)
677 }
678}
679
680#[derive(Debug, Clone, Copy)]
682pub enum HeapOpKind {
683 Alloc,
684 MarkInit,
685 MarkUninit,
686 DropInPlace,
687 Dealloc,
688 Memcpy,
689 DefaultInPlace,
690}
691
692#[cfg(all(not(kani), not(creusot)))]
694#[derive(Clone, Default, Debug)]
695pub struct AllocHistory {
696 pub ops: Vec<HeapOp>,
697}
698
699#[cfg(all(not(kani), not(creusot)))]
700impl AllocHistory {
701 const fn new() -> Self {
702 Self { ops: Vec::new() }
703 }
704
705 fn record(&mut self, kind: HeapOpKind, range: Option<Range>) {
706 let bt = std::backtrace::Backtrace::capture();
707 self.ops.push(HeapOp {
708 kind,
709 range,
710 backtrace: bt.to_string(),
711 });
712 }
713
714 fn print(&self, alloc_id: u8) {
715 eprintln!("=== History for allocation {} ===", alloc_id);
716 for (i, op) in self.ops.iter().enumerate() {
717 eprintln!("--- Op {} ---", i);
718 eprintln!("{:?}", op);
719 }
720 eprintln!("=== End history ===");
721 }
722}
723
724#[cfg(all(not(kani), not(creusot)))]
726struct LayoutRange {
727 start: u32,
728 end: u32,
729 depth: usize,
730 shape_kind: &'static str,
731 is_init: bool,
732}
733
734#[cfg(all(not(kani), not(creusot)))]
736fn print_allocation_layout<S: IShape>(tracker: &ByteRangeTracker, shape: S, alloc_size: u32) {
737 let mut ranges: Vec<LayoutRange> = Vec::new();
738
739 fn collect_ranges<S: IShape>(
741 ranges: &mut Vec<LayoutRange>,
742 tracker: &ByteRangeTracker,
743 shape: S,
744 base_offset: u32,
745 depth: usize,
746 ) {
747 let layout = shape.layout().expect("IShape requires sized types");
748 let size = layout.size() as u32;
749 if size == 0 {
750 return;
751 }
752
753 let start = base_offset;
754 let end = base_offset + size;
755 let is_init = tracker.is_init(start, end);
756
757 let shape_kind = if shape.is_struct() {
758 "struct"
759 } else {
760 "scalar"
761 };
762
763 ranges.push(LayoutRange {
764 start,
765 end,
766 depth,
767 shape_kind,
768 is_init,
769 });
770
771 if let Some(st) = shape.as_struct() {
773 for i in 0..st.field_count() {
774 if let Some(field) = st.field(i) {
775 let field_shape = field.shape();
776 let field_offset = base_offset + field.offset() as u32;
777 collect_ranges(ranges, tracker, field_shape, field_offset, depth + 1);
778 }
779 }
780 }
781 }
782
783 collect_ranges(&mut ranges, tracker, shape, 0, 0);
784
785 ranges.sort_by(|a, b| b.depth.cmp(&a.depth).then(a.start.cmp(&b.start)));
787
788 eprintln!("=== Allocation Layout (size={}) ===", alloc_size);
789 eprintln!("Legend: [####] = initialized, [....] = uninitialized");
790 eprintln!();
791
792 let _max_depth = ranges.iter().map(|r| r.depth).max().unwrap_or(0);
794
795 ranges.sort_by(|a, b| a.depth.cmp(&b.depth).then(a.start.cmp(&b.start)));
797
798 for range in &ranges {
799 let indent = " ".repeat(range.depth);
800 let width = (range.end - range.start) as usize;
801 let bar = if range.is_init {
802 "#".repeat(width.min(40))
803 } else {
804 ".".repeat(width.min(40))
805 };
806 let status = if range.is_init { "INIT" } else { "UNINIT" };
807 eprintln!(
808 "{}[{:3}..{:3}] {} {} [{}]",
809 indent, range.start, range.end, range.shape_kind, status, bar
810 );
811 }
812
813 eprintln!();
815 eprintln!("Raw initialized ranges:");
816 let init_ranges = tracker.ranges();
817 if init_ranges.is_empty() {
818 eprintln!(" (none)");
819 } else {
820 for range in init_ranges {
821 eprintln!(" [{}..{})", range.start, range.end);
822 }
823 }
824 eprintln!("=== End Layout ===");
825}
826
827#[derive(Debug)]
832pub struct VHeap<S: IShape> {
833 allocs: [Option<(ByteRangeTracker, S)>; MAX_VHEAP_ALLOCS],
836 next_id: u8,
838 #[cfg(all(not(kani), not(creusot)))]
840 history: [AllocHistory; MAX_VHEAP_ALLOCS],
841}
842
843impl<S: IShape> VHeap<S> {
844 pub fn new() -> Self {
846 Self {
847 allocs: [const { None }; MAX_VHEAP_ALLOCS],
848 next_id: 0,
849 #[cfg(all(not(kani), not(creusot)))]
850 history: [const { AllocHistory::new() }; MAX_VHEAP_ALLOCS],
851 }
852 }
853
854 #[cfg(all(not(kani), not(creusot)))]
856 fn record_op(&mut self, alloc_id: u8, kind: HeapOpKind, range: Option<Range>) {
857 self.history[alloc_id as usize].record(kind, range);
858 }
859
860 #[cfg(any(kani, creusot))]
861 fn record_op(&mut self, _alloc_id: u8, _kind: HeapOpKind, _range: Option<Range>) {}
862
863 #[cfg(all(not(kani), not(creusot)))]
865 fn print_history(&self, alloc_id: u8) {
866 self.history[alloc_id as usize].print(alloc_id);
867 if let Some((tracker, shape)) = &self.allocs[alloc_id as usize] {
869 let alloc_size = shape.layout().map(|l| l.size() as u32).unwrap_or(0);
870 print_allocation_layout(tracker, *shape, alloc_size);
871 }
872 }
873
874 #[cfg(any(kani, creusot))]
875 fn print_history(&self, _alloc_id: u8) {}
876
877 fn assert_with_history(&self, cond: bool, alloc_id: u8, msg: &str) {
879 if !cond {
880 self.print_history(alloc_id);
881 panic!("{}", msg);
882 }
883 }
884
885 #[cfg(test)]
887 pub fn assert_no_leaks(&self) {
888 for (i, alloc) in self.allocs.iter().enumerate() {
889 assert!(alloc.is_none(), "allocation {} not freed", i);
890 }
891 }
892
893 fn get_tracker(&self, alloc_id: u8) -> &(ByteRangeTracker, S) {
895 self.allocs[alloc_id as usize]
896 .as_ref()
897 .expect("allocation already freed")
898 }
899
900 fn get_tracker_mut(&mut self, alloc_id: u8) -> &mut (ByteRangeTracker, S) {
902 self.allocs[alloc_id as usize]
903 .as_mut()
904 .expect("allocation already freed")
905 }
906
907 #[cfg(not(creusot))]
908 fn matches_subshape(stored: S, offset: usize, target: S) -> bool {
909 if offset == 0 && stored == target {
910 return true;
911 }
912
913 if !stored.is_struct() {
914 return false;
915 }
916
917 let st = stored.as_struct().expect("struct shape");
918 let field_count = st.field_count();
919 for i in 0..field_count {
920 let field = st.field(i).expect("field index in range");
921 let field_shape = field.shape();
922 let field_size = field_shape
923 .layout()
924 .expect("IShape requires sized types")
925 .size();
926 let start = field.offset();
927 let end = start + field_size;
928 if offset >= start && offset < end {
929 return Self::matches_subshape(field_shape, offset - start, target);
930 }
931 }
932
933 false
934 }
935
936 #[cfg(creusot)]
937 fn matches_subshape(_stored: S, _offset: usize, _target: S) -> bool {
938 true
939 }
940}
941
942impl<S: IShape> Default for VHeap<S> {
943 fn default() -> Self {
944 Self::new()
945 }
946}
947
948impl<S: IShape> IHeap<S> for VHeap<S> {
949 type Ptr = VPtr;
950
951 unsafe fn alloc(&mut self, shape: S) -> VPtr {
952 let id = self.next_id;
953 #[cfg(not(creusot))]
954 assert!(
955 (id as usize) < MAX_VHEAP_ALLOCS,
956 "too many allocations (max {})",
957 MAX_VHEAP_ALLOCS
958 );
959 #[cfg(creusot)]
960 {
961 if (id as usize) >= MAX_VHEAP_ALLOCS {
962 panic!("too many allocations");
963 }
964 }
965
966 let layout = shape.layout().expect("IShape requires sized types");
967 self.allocs[id as usize] = Some((ByteRangeTracker::new(), shape));
968 self.next_id += 1;
969
970 self.record_op(
971 id,
972 HeapOpKind::Alloc,
973 Some(Range::new(0, layout.size() as u32)),
974 );
975
976 VPtr::new(id, layout.size() as u32)
977 }
978
979 unsafe fn dealloc(&mut self, ptr: VPtr, shape: S) {
980 let id = ptr.alloc_id();
981 self.assert_with_history(
982 ptr.is_at_start(),
983 id,
984 "dealloc: pointer not at allocation start",
985 );
986
987 let (tracker, stored_shape) = self.get_tracker(id);
988 #[cfg(not(creusot))]
989 self.assert_with_history(*stored_shape == shape, id, "dealloc: shape mismatch");
990 #[cfg(creusot)]
991 {
992 if *stored_shape != shape {
993 self.print_history(id);
994 panic!("dealloc: shape mismatch");
995 }
996 }
997 self.assert_with_history(
998 tracker.is_empty(),
999 id,
1000 "dealloc: allocation still has initialized bytes",
1001 );
1002
1003 self.record_op(id, HeapOpKind::Dealloc, None);
1004 self.allocs[id as usize] = None;
1005 }
1006
1007 unsafe fn memcpy(&mut self, dst: VPtr, src: VPtr, len: usize) {
1008 if len == 0 {
1009 return;
1010 }
1011
1012 if dst.offset_bytes() + len > dst.alloc_size() {
1014 self.print_history(dst.alloc_id());
1015 panic!("memcpy: dst out of bounds");
1016 }
1017 if src.offset_bytes() + len > src.alloc_size() {
1018 self.print_history(src.alloc_id());
1019 panic!("memcpy: src out of bounds");
1020 }
1021
1022 let (src_tracker, _) = self.get_tracker(src.alloc_id());
1024 if !src_tracker.is_init(src.offset, src.offset + len as u32) {
1025 self.print_history(src.alloc_id());
1026 panic!("memcpy: src range not initialized");
1027 }
1028
1029 self.record_op(
1031 dst.alloc_id(),
1032 HeapOpKind::Memcpy,
1033 Some(Range::new(dst.offset, dst.offset + len as u32)),
1034 );
1035
1036 let (dst_tracker, _) = self.get_tracker_mut(dst.alloc_id());
1038 if let Err(e) = dst_tracker.mark_init(dst.offset, dst.offset + len as u32) {
1039 self.print_history(dst.alloc_id());
1040 panic!(
1041 "memcpy: dst range already initialized (forgot to drop?): {:?}",
1042 e
1043 );
1044 }
1045 }
1046
1047 unsafe fn drop_in_place(&mut self, ptr: VPtr, shape: S) {
1048 let layout = shape.layout().expect("IShape requires sized types");
1049 if layout.size() == 0 {
1050 return; }
1052
1053 let alloc_id = ptr.alloc_id();
1054 self.record_op(
1055 alloc_id,
1056 HeapOpKind::DropInPlace,
1057 Some(Range::new(ptr.offset, ptr.offset + layout.size() as u32)),
1058 );
1059
1060 let (tracker, stored_shape) = self.get_tracker_mut(alloc_id);
1061
1062 if !Self::matches_subshape(*stored_shape, ptr.offset_bytes(), shape) {
1063 self.print_history(alloc_id);
1064 panic!("drop_in_place: shape mismatch");
1065 }
1066
1067 if ptr.offset_bytes() + layout.size() > ptr.alloc_size() {
1068 self.print_history(alloc_id);
1069 panic!("drop_in_place: out of bounds");
1070 }
1071
1072 let base = ptr.offset;
1073 let end = base + layout.size() as u32;
1074
1075 if shape.is_struct() {
1076 let st = shape.as_struct().expect("struct shape");
1077 let field_count = st.field_count();
1078
1079 for i in 0..field_count {
1080 let field = st.field(i).expect("field index in range");
1081 let field_shape = field.shape();
1082 let field_size = field_shape
1083 .layout()
1084 .expect("IShape requires sized types")
1085 .size() as u32;
1086 if field_size == 0 {
1087 continue;
1088 }
1089 let start = base + field.offset() as u32;
1090 let field_end = start + field_size;
1091 if field_end > end {
1092 self.print_history(alloc_id);
1093 panic!("drop_in_place: field out of bounds");
1094 }
1095
1096 if let Err(e) = tracker.mark_uninit(start, field_end) {
1097 self.print_history(alloc_id);
1098 panic!("drop_in_place: field range not initialized: {:?}", e);
1099 }
1100 }
1101
1102 if let Err(e) = tracker.clear_range(base, end) {
1103 self.print_history(alloc_id);
1104 panic!("drop_in_place: clear_range failed: {:?}", e);
1105 }
1106 return;
1107 }
1108
1109 if let Err(e) = tracker.mark_uninit(base, end) {
1110 self.print_history(alloc_id);
1111 panic!("drop_in_place: range not initialized: {:?}", e);
1112 }
1113 }
1114
1115 unsafe fn default_in_place(&mut self, ptr: VPtr, shape: S) -> bool {
1116 let layout = match shape.layout() {
1117 Some(layout) => layout,
1118 None => return false,
1119 };
1120 let len = layout.size();
1121 if len == 0 {
1122 return true;
1123 }
1124
1125 let alloc_id = ptr.alloc_id();
1126
1127 if ptr.offset_bytes() + len > ptr.alloc_size() {
1129 self.print_history(alloc_id);
1130 panic!("default_in_place: out of bounds");
1131 }
1132
1133 self.record_op(
1134 alloc_id,
1135 HeapOpKind::DefaultInPlace,
1136 Some(Range::new(ptr.offset, ptr.offset + len as u32)),
1137 );
1138
1139 let (tracker, _) = self.get_tracker_mut(alloc_id);
1140 if let Err(e) = tracker.mark_init(ptr.offset, ptr.offset + len as u32) {
1141 self.print_history(alloc_id);
1142 panic!("default_in_place: range already initialized: {:?}", e);
1143 }
1144 true
1145 }
1146}
1147
1148pub const MAX_VARENA_SLOTS: usize = 16;
1154
1155#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1157enum VSlotState {
1158 Empty,
1159 Occupied,
1160}
1161
1162pub struct VArena<T, const N: usize = MAX_VARENA_SLOTS> {
1166 slots: [Option<T>; N],
1168 states: [VSlotState; N],
1170 next: usize,
1172}
1173
1174impl<T, const N: usize> VArena<T, N> {
1175 pub fn new() -> Self {
1177 Self {
1178 slots: core::array::from_fn(|_| None),
1179 states: [VSlotState::Empty; N],
1180 next: 1, }
1182 }
1183}
1184
1185impl<T, const N: usize> Default for VArena<T, N> {
1186 fn default() -> Self {
1187 Self::new()
1188 }
1189}
1190
1191impl<T, const N: usize> IArena<T> for VArena<T, N> {
1192 fn alloc(&mut self, value: T) -> Idx<T> {
1193 for i in self.next..N {
1195 if self.states[i] == VSlotState::Empty {
1196 self.slots[i] = Some(value);
1197 self.states[i] = VSlotState::Occupied;
1198 self.next = i + 1;
1199 return Idx::from_raw(i as u32);
1200 }
1201 }
1202 for i in 1..self.next {
1204 if self.states[i] == VSlotState::Empty {
1205 self.slots[i] = Some(value);
1206 self.states[i] = VSlotState::Occupied;
1207 self.next = i + 1;
1208 return Idx::from_raw(i as u32);
1209 }
1210 }
1211 panic!("arena full");
1212 }
1213
1214 fn free(&mut self, id: Idx<T>) -> T {
1215 assert!(id.is_valid(), "cannot free sentinel index");
1216 let idx = id.index();
1217 assert!(idx < N, "index out of bounds");
1218 assert!(
1219 self.states[idx] == VSlotState::Occupied,
1220 "double-free or freeing empty slot"
1221 );
1222
1223 self.states[idx] = VSlotState::Empty;
1224 self.slots[idx].take().expect("slot was occupied but empty")
1225 }
1226
1227 fn get(&self, id: Idx<T>) -> &T {
1228 assert!(id.is_valid(), "cannot get sentinel index");
1229 let idx = id.index();
1230 assert!(idx < N, "index out of bounds");
1231 assert!(
1232 self.states[idx] == VSlotState::Occupied,
1233 "slot is not occupied"
1234 );
1235
1236 self.slots[idx]
1237 .as_ref()
1238 .expect("slot was occupied but empty")
1239 }
1240
1241 fn get_mut(&mut self, id: Idx<T>) -> &mut T {
1242 assert!(id.is_valid(), "cannot get sentinel index");
1243 let idx = id.index();
1244 assert!(idx < N, "index out of bounds");
1245 assert!(
1246 self.states[idx] == VSlotState::Occupied,
1247 "slot is not occupied"
1248 );
1249
1250 self.slots[idx]
1251 .as_mut()
1252 .expect("slot was occupied but empty")
1253 }
1254}
1255
1256#[cfg(test)]
1261mod tests;
1262#[cfg(test)]
1263mod verus_bridge;
1264
1265#[cfg(kani)]
1266mod proofs;
1267#[cfg(creusot)]
1268#[derive(Debug, Clone, Copy, PartialEq, Eq, DeepModel)]
1269pub struct VLayout {
1270 pub size: usize,
1271 pub align: usize,
1272}
1273
1274#[cfg(creusot)]
1275#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1276pub struct VLayoutError;
1277
1278#[cfg(creusot)]
1279impl VLayout {
1280 #[inline]
1281 pub const fn new<T>() -> Self {
1282 Self {
1283 size: core::mem::size_of::<T>(),
1284 align: core::mem::align_of::<T>(),
1285 }
1286 }
1287
1288 #[inline]
1289 pub const fn size(self) -> usize {
1290 self.size
1291 }
1292
1293 #[inline]
1294 pub const fn align(self) -> usize {
1295 self.align
1296 }
1297
1298 pub fn from_size_align(size: usize, align: usize) -> Result<Self, VLayoutError> {
1299 if align == 0 || !align.is_power_of_two() {
1300 return Err(VLayoutError);
1301 }
1302 Ok(Self { size, align })
1303 }
1304
1305 #[inline]
1306 pub fn to_layout(self) -> Layout {
1307 Layout::from_size_align(self.size, self.align).expect("valid layout")
1308 }
1309}
1310
1311#[cfg(not(creusot))]
1312pub type VLayout = Layout;
1313
1314#[cfg(not(creusot))]
1315pub type VLayoutError = std::alloc::LayoutError;