rustpython_vm/
datastack.rs1use alloc::alloc::{alloc, dealloc};
8use core::alloc::Layout;
9use core::ptr;
10
11const MIN_CHUNK_SIZE: usize = if cfg!(target_arch = "wasm32") {
14 4 * 1024
15} else {
16 16 * 1024
17};
18
19const MINIMUM_OVERHEAD: usize = 1000 * core::mem::size_of::<usize>();
22
23const ALIGN: usize = 16;
25
26#[repr(C)]
30struct DataStackChunk {
31 previous: *mut Self,
33 size: usize,
35 saved_top: usize,
38}
39
40impl DataStackChunk {
41 #[inline(always)]
43 fn data_start(&self) -> *mut u8 {
44 let header_end = (self as *const Self as usize) + core::mem::size_of::<Self>();
45 let aligned = (header_end + ALIGN - 1) & !(ALIGN - 1);
46 aligned as *mut u8
47 }
48
49 #[inline(always)]
51 fn data_limit(&self) -> *mut u8 {
52 unsafe { (self as *const Self as *mut u8).add(self.size) }
53 }
54}
55
56pub struct DataStack {
58 chunk: *mut DataStackChunk,
60 top: *mut u8,
62 limit: *mut u8,
64 reusable_frame: Option<(*mut u8, usize)>,
67}
68
69impl DataStack {
70 #[must_use]
72 pub fn new() -> Self {
73 let chunk = Self::alloc_chunk(MIN_CHUNK_SIZE, ptr::null_mut());
74 let top = unsafe { (*chunk).data_start() };
75 let limit = unsafe { (*chunk).data_limit() };
76 let top = unsafe { top.add(ALIGN) };
79 Self {
80 chunk,
81 top,
82 limit,
83 reusable_frame: None,
84 }
85 }
86
87 #[inline(always)]
89 #[must_use]
90 pub fn has_space(&self, size: usize) -> bool {
91 let aligned_size = (size + ALIGN - 1) & !(ALIGN - 1);
92 (self.limit as usize).saturating_sub(self.top as usize) >= aligned_size
93 }
94
95 #[inline(always)]
101 pub fn push(&mut self, size: usize) -> *mut u8 {
102 self.reusable_frame = None;
103 self.push_inner(size)
104 }
105
106 #[inline(always)]
109 pub fn push_frame(&mut self, size: usize) -> (*mut u8, bool) {
110 let reusable_frame = self.reusable_frame.take();
111 let ptr = self.push_inner(size);
112 let reused = reusable_frame.is_some_and(|(base, old_size)| base == ptr && old_size == size);
117 (ptr, reused)
118 }
119
120 #[inline(always)]
121 fn push_inner(&mut self, size: usize) -> *mut u8 {
122 let aligned_size = (size + ALIGN - 1) & !(ALIGN - 1);
123 unsafe {
124 if self.top.add(aligned_size) <= self.limit {
125 let ptr = self.top;
126 self.top = self.top.add(aligned_size);
127 ptr
128 } else {
129 self.push_slow(aligned_size)
130 }
131 }
132 }
133
134 #[cold]
136 #[inline(never)]
137 fn push_slow(&mut self, aligned_size: usize) -> *mut u8 {
138 let mut chunk_size = MIN_CHUNK_SIZE;
139 let needed = aligned_size
140 .checked_add(MINIMUM_OVERHEAD)
141 .and_then(|v| v.checked_add(core::mem::size_of::<DataStackChunk>()))
142 .and_then(|v| v.checked_add(ALIGN))
143 .expect("DataStack chunk size overflow");
144 while chunk_size < needed {
145 chunk_size = chunk_size
146 .checked_mul(2)
147 .expect("DataStack chunk size overflow");
148 }
149 unsafe {
151 (*self.chunk).saved_top = self.top as usize - self.chunk as usize;
152 }
153 let new_chunk = Self::alloc_chunk(chunk_size, self.chunk);
154 self.chunk = new_chunk;
155 let start = unsafe { (*new_chunk).data_start() };
156 self.limit = unsafe { (*new_chunk).data_limit() };
157 self.top = unsafe { start.add(aligned_size) };
158 start
159 }
160
161 #[inline(always)]
168 pub unsafe fn pop(&mut self, base: *mut u8) {
169 self.reusable_frame = None;
170 unsafe { self.pop_inner(base) };
171 }
172
173 #[inline(always)]
180 pub unsafe fn pop_frame(&mut self, base: *mut u8, size: usize) {
181 unsafe { self.pop_inner(base) };
182 self.reusable_frame = Some((base, size));
183 }
184
185 #[inline(always)]
186 unsafe fn pop_inner(&mut self, base: *mut u8) {
187 debug_assert!(!base.is_null());
188 if self.is_in_current_chunk(base) {
189 self.top = base;
191 } else {
192 unsafe { self.pop_slow(base) };
194 }
195 }
196
197 #[inline(always)]
201 fn is_in_current_chunk(&self, ptr: *mut u8) -> bool {
202 let chunk_start = unsafe { (*self.chunk).data_start() };
203 ptr >= chunk_start && ptr <= self.limit
204 }
205
206 #[cold]
208 #[inline(never)]
209 unsafe fn pop_slow(&mut self, base: *mut u8) {
210 loop {
211 let old_chunk = self.chunk;
212 let prev = unsafe { (*old_chunk).previous };
213 debug_assert!(!prev.is_null(), "tried to pop past the root chunk");
214 unsafe { Self::free_chunk(old_chunk) };
215 self.chunk = prev;
216 self.limit = unsafe { (*prev).data_limit() };
217 if self.is_in_current_chunk(base) {
218 self.top = base;
219 return;
220 }
221 }
222 }
223
224 fn alloc_chunk(size: usize, previous: *mut DataStackChunk) -> *mut DataStackChunk {
226 let layout = Layout::from_size_align(size, ALIGN).expect("invalid chunk layout");
227 let ptr = unsafe { alloc(layout) };
228 if ptr.is_null() {
229 alloc::alloc::handle_alloc_error(layout);
230 }
231 let chunk = ptr as *mut DataStackChunk;
232 unsafe {
233 (*chunk).previous = previous;
234 (*chunk).size = size;
235 (*chunk).saved_top = 0;
236 }
237 chunk
238 }
239
240 unsafe fn free_chunk(chunk: *mut DataStackChunk) {
242 let size = unsafe { (*chunk).size };
243 let layout = Layout::from_size_align(size, ALIGN).expect("invalid chunk layout");
244 unsafe { dealloc(chunk as *mut u8, layout) };
245 }
246}
247
248unsafe impl Send for DataStack {}
251
252impl Default for DataStack {
253 fn default() -> Self {
254 Self::new()
255 }
256}
257
258impl Drop for DataStack {
259 fn drop(&mut self) {
260 let mut chunk = self.chunk;
261 while !chunk.is_null() {
262 let prev = unsafe { (*chunk).previous };
263 unsafe { Self::free_chunk(chunk) };
264 chunk = prev;
265 }
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use super::*;
272
273 #[test]
274 fn basic_push_pop() {
275 let mut ds = DataStack::new();
276 let p1 = ds.push(64);
277 assert!(!p1.is_null());
278 let p2 = ds.push(128);
279 assert!(!p2.is_null());
280 assert!(p2 > p1);
281 unsafe {
282 ds.pop(p2);
283 ds.pop(p1);
284 }
285 }
286
287 #[test]
288 fn cross_chunk_push_pop() {
289 let mut ds = DataStack::new();
290 let mut ptrs = Vec::new();
292 for _ in 0..100 {
293 ptrs.push(ds.push(1024));
294 }
295 for p in ptrs.into_iter().rev() {
297 unsafe { ds.pop(p) };
298 }
299 }
300
301 #[test]
302 fn alignment() {
303 let mut ds = DataStack::new();
304 for size in [1, 7, 15, 16, 17, 31, 32, 33, 64, 100] {
305 let p = ds.push(size);
306 assert_eq!(p as usize % ALIGN, 0, "alignment violated for size {size}");
307 unsafe { ds.pop(p) };
308 }
309 }
310}