lexe_enclave/
allocator.rs1use std::{
9 alloc::{GlobalAlloc, Layout, System},
10 sync::atomic::{AtomicUsize, Ordering},
11};
12
13#[cfg(target_env = "sgx")]
14unsafe extern "C" {
15 static HEAP_SIZE: usize;
22}
23
24pub struct InstrumentedSystemAllocator {
30 _private: (),
31}
32
33struct Counters {
35 current: AtomicUsize,
36 max: AtomicUsize,
37}
38
39static COUNTERS: Counters = Counters::new();
41
42#[derive(Copy, Clone, Debug, Eq, PartialEq)]
44pub struct AllocatorStats {
45 pub current_bytes: usize,
47 pub heap_size: Option<usize>,
49 pub max_bytes: usize,
51}
52
53pub fn stats() -> AllocatorStats {
55 let (current_bytes, max_bytes) = COUNTERS.snapshot();
56
57 AllocatorStats {
58 current_bytes,
59 heap_size: heap_size(),
60 max_bytes,
61 }
62}
63
64fn heap_size() -> Option<usize> {
66 cfg_if::cfg_if! {
67 if #[cfg(target_env = "sgx")] {
68 Some(unsafe { HEAP_SIZE })
70 } else {
71 None
72 }
73 }
74}
75
76impl InstrumentedSystemAllocator {
79 pub const fn new() -> Self {
80 Self { _private: () }
81 }
82}
83
84unsafe impl GlobalAlloc for InstrumentedSystemAllocator {
85 #[inline]
86 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
87 let ptr = unsafe { System.alloc(layout) };
89 COUNTERS.record_allocation(ptr, layout.size());
90 ptr
91 }
92
93 #[inline]
94 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
95 let ptr = unsafe { System.alloc_zeroed(layout) };
97 COUNTERS.record_allocation(ptr, layout.size());
98 ptr
99 }
100
101 #[inline]
102 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
103 unsafe { System.dealloc(ptr, layout) };
105 COUNTERS.remove(layout.size());
106 }
107
108 #[inline]
109 unsafe fn realloc(
110 &self,
111 ptr: *mut u8,
112 layout: Layout,
113 new_size: usize,
114 ) -> *mut u8 {
115 let new_ptr = unsafe { System.realloc(ptr, layout, new_size) };
117 COUNTERS.record_reallocation(new_ptr, layout.size(), new_size);
118 new_ptr
119 }
120}
121
122impl Counters {
125 const fn new() -> Self {
126 Self {
127 current: AtomicUsize::new(0),
128 max: AtomicUsize::new(0),
129 }
130 }
131
132 #[inline]
133 fn record_allocation(&self, ptr: *mut u8, size: usize) {
134 if !ptr.is_null() {
135 self.add(size);
136 }
137 }
138
139 #[inline]
140 fn record_reallocation(
141 &self,
142 ptr: *mut u8,
143 old_size: usize,
144 new_size: usize,
145 ) {
146 if ptr.is_null() {
147 return;
148 }
149
150 if new_size > old_size {
151 self.add(new_size - old_size);
152 } else if new_size < old_size {
153 self.remove(old_size - new_size);
154 }
155 }
156
157 #[inline]
158 fn add(&self, size: usize) {
159 let current = self.current.fetch_add(size, Ordering::Relaxed) + size;
160 self.max.fetch_max(current, Ordering::Relaxed);
161 }
162
163 #[inline]
164 fn remove(&self, size: usize) {
165 self.current.fetch_sub(size, Ordering::Relaxed);
166 }
167
168 fn snapshot(&self) -> (usize, usize) {
169 let current = self.current.load(Ordering::Relaxed);
170 let max = self.max.load(Ordering::Relaxed).max(current);
172 (current, max)
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use std::ptr::{self, NonNull};
179
180 use super::*;
181
182 #[test]
183 fn tracks_allocation_lifecycle() {
184 let counters = Counters::new();
185 let ptr = NonNull::<u8>::dangling().as_ptr();
186
187 counters.record_allocation(ptr::null_mut(), 100);
188 assert_eq!(counters.snapshot(), (0, 0));
189
190 counters.record_allocation(ptr, 20);
191 counters.record_allocation(ptr, 30);
192 assert_eq!(counters.snapshot(), (50, 50));
193
194 counters.remove(20);
195 assert_eq!(counters.snapshot(), (30, 50));
196
197 counters.record_reallocation(ptr::null_mut(), 30, 100);
198 assert_eq!(counters.snapshot(), (30, 50));
199
200 counters.record_reallocation(ptr, 30, 60);
201 assert_eq!(counters.snapshot(), (60, 60));
202
203 counters.record_reallocation(ptr, 60, 10);
204 assert_eq!(counters.snapshot(), (10, 60));
205
206 counters.remove(10);
207 assert_eq!(counters.snapshot(), (0, 60));
208 }
209}