Skip to main content

oxilean_runtime/region_alloc/
types.rs

1//! Region-based memory allocator types for bulk-freeable proof-checking data.
2
3use std::marker::PhantomData;
4
5/// Unique region identifier.
6#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct RegionId(pub u32);
8
9impl RegionId {
10    /// Create a new RegionId from a raw u32 value.
11    pub fn new(id: u32) -> Self {
12        RegionId(id)
13    }
14
15    /// Get the raw u32 identifier.
16    pub fn raw(self) -> u32 {
17        self.0
18    }
19}
20
21impl std::fmt::Display for RegionId {
22    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
23        write!(f, "region#{}", self.0)
24    }
25}
26
27/// A contiguous memory region backed by a `Vec<u8>`.
28///
29/// The region acts as a bump allocator within a fixed-capacity buffer.
30/// Allocation advances `offset` by the requested (aligned) size.
31#[derive(Debug)]
32pub struct Region {
33    /// Unique identifier for this region.
34    pub id: RegionId,
35    /// Raw backing storage.
36    pub data: Vec<u8>,
37    /// Current allocation watermark (next free byte index).
38    pub offset: usize,
39    /// Total capacity of `data` in bytes.
40    pub capacity: usize,
41}
42
43impl Region {
44    /// Create a new region with the given id and capacity.
45    pub fn new(id: RegionId, capacity: usize) -> Self {
46        let capacity = capacity.max(1);
47        Region {
48            id,
49            data: vec![0u8; capacity],
50            offset: 0,
51            capacity,
52        }
53    }
54
55    /// Bytes still available for allocation.
56    pub fn remaining(&self) -> usize {
57        self.capacity.saturating_sub(self.offset)
58    }
59
60    /// Bytes already allocated.
61    pub fn used(&self) -> usize {
62        self.offset
63    }
64
65    /// Whether this region has no active allocations.
66    pub fn is_empty(&self) -> bool {
67        self.offset == 0
68    }
69
70    /// Reset the region so all memory becomes available again.
71    pub fn reset(&mut self) {
72        self.offset = 0;
73        // Zero the backing buffer so stale data cannot leak.
74        for b in &mut self.data {
75            *b = 0;
76        }
77    }
78}
79
80/// A lifetime-tied handle to a specific region.
81///
82/// The `'a` lifetime ensures the handle cannot outlive the allocator
83/// that owns the underlying `Region`.
84#[derive(Clone, Copy, Debug, PartialEq, Eq)]
85pub struct RegionHandle<'a> {
86    /// The region this handle refers to.
87    pub id: RegionId,
88    /// Ties the handle lifetime to the allocator borrow.
89    pub phantom: PhantomData<&'a ()>,
90}
91
92impl<'a> RegionHandle<'a> {
93    /// Create a handle for the given region id with an explicit lifetime.
94    pub fn new(id: RegionId) -> Self {
95        RegionHandle {
96            id,
97            phantom: PhantomData,
98        }
99    }
100
101    /// Expose the underlying region id.
102    pub fn region_id(self) -> RegionId {
103        self.id
104    }
105}
106
107impl<'a> std::fmt::Display for RegionHandle<'a> {
108    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
109        write!(f, "handle({})", self.id)
110    }
111}
112
113/// Allocation statistics snapshot.
114#[derive(Clone, Debug, Default, PartialEq, Eq)]
115pub struct AllocStats {
116    /// Total number of regions ever created (including recycled).
117    pub regions_created: usize,
118    /// Total number of regions freed (returned to the free list).
119    pub regions_freed: usize,
120    /// Total bytes allocated across all active regions.
121    pub total_allocated: usize,
122    /// Total capacity freed (bytes in freed regions).
123    pub total_freed: usize,
124    /// Peak in-use byte count observed.
125    pub peak_usage: usize,
126}
127
128impl AllocStats {
129    /// Net active regions (created minus freed).
130    pub fn active_regions(&self) -> usize {
131        self.regions_created.saturating_sub(self.regions_freed)
132    }
133
134    /// Efficiency ratio: bytes allocated vs total capacity allocated so far.
135    pub fn utilization(&self, total_capacity: usize) -> f64 {
136        if total_capacity == 0 {
137            return 0.0;
138        }
139        self.total_allocated as f64 / total_capacity as f64
140    }
141}
142
143impl std::fmt::Display for AllocStats {
144    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
145        write!(
146            f,
147            "AllocStats {{ created: {}, freed: {}, allocated: {}, peak: {} }}",
148            self.regions_created, self.regions_freed, self.total_allocated, self.peak_usage
149        )
150    }
151}
152
153/// Configuration for a `RegionAllocator`.
154#[derive(Clone, Debug)]
155pub struct RegionConfig {
156    /// Initial capacity (bytes) of each newly created region.
157    pub initial_capacity: usize,
158    /// Multiplicative growth factor when a region must be expanded.
159    /// Values outside `(1.0, 16.0]` are clamped.
160    pub growth_factor: f64,
161    /// Maximum number of live regions the allocator will manage.
162    pub max_regions: usize,
163}
164
165impl RegionConfig {
166    /// Construct a configuration, clamping `growth_factor` to `(1.0, 16.0]`.
167    pub fn new(initial_capacity: usize, growth_factor: f64, max_regions: usize) -> Self {
168        let growth_factor = growth_factor.clamp(1.001, 16.0);
169        RegionConfig {
170            initial_capacity: initial_capacity.max(64),
171            growth_factor,
172            max_regions: max_regions.max(1),
173        }
174    }
175
176    /// Return next capacity after growing by the configured factor.
177    pub fn grow(&self, current: usize) -> usize {
178        ((current as f64 * self.growth_factor) as usize).max(current + 64)
179    }
180}
181
182impl Default for RegionConfig {
183    fn default() -> Self {
184        RegionConfig::new(4096, 2.0, 256)
185    }
186}
187
188/// A region-based (arena) allocator that manages a pool of `Region`s.
189///
190/// Regions can be bulk-freed by calling `free_region`, which returns
191/// them to an internal free list for reuse without individual deallocation.
192pub struct RegionAllocator {
193    /// All managed regions, indexed by `RegionId::raw()`.
194    pub regions: Vec<Region>,
195    /// Ids of regions that are free for reuse.
196    pub free_list: Vec<RegionId>,
197    /// Default capacity for newly created regions.
198    pub default_capacity: usize,
199    /// Allocator configuration.
200    pub(super) config: RegionConfig,
201    /// Running statistics.
202    pub(super) stats: AllocStats,
203    /// Next fresh id to assign (monotonically increasing).
204    pub(super) next_id: u32,
205    /// Current total bytes allocated across all active (non-freed) regions.
206    pub(super) current_usage: usize,
207}