Skip to main content

tidepool_codegen/
nursery.rs

1use crate::context::VMContext;
2
3/// Owned bump-allocator nursery for JIT-compiled code.
4///
5/// Provides the backing memory that VMContext's alloc_ptr/alloc_limit point into.
6/// No GC — panics on exhaustion.
7pub struct Nursery {
8    buffer: Vec<u8>,
9}
10
11impl Nursery {
12    /// Create a nursery with the given size in bytes.
13    pub fn new(size: usize) -> Self {
14        Self {
15            buffer: vec![0u8; size],
16        }
17    }
18
19    /// Get the start address of the nursery buffer.
20    pub fn start(&self) -> *const u8 {
21        self.buffer.as_ptr()
22    }
23
24    /// Get the size of the nursery buffer in bytes.
25    pub fn size(&self) -> usize {
26        self.buffer.len()
27    }
28
29    /// Create a VMContext pointing into this nursery.
30    ///
31    /// The returned VMContext is valid as long as this Nursery is alive
32    /// and not moved.
33    pub fn make_vmctx(&mut self, gc_trigger: unsafe extern "C" fn(*mut VMContext)) -> VMContext {
34        let start = self.buffer.as_mut_ptr();
35        // SAFETY: start points to a Vec<u8> buffer and adding its length stays within the allocation.
36        let end = unsafe { start.add(self.buffer.len()) };
37        VMContext::new(start, end as *const u8, gc_trigger)
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    extern "C" fn dummy_gc_trigger(_vmctx: *mut VMContext) {}
46
47    #[test]
48    fn test_nursery_new() {
49        let size = 1024;
50        let nursery = Nursery::new(size);
51        assert_eq!(nursery.buffer.len(), size);
52        assert!(nursery.buffer.iter().all(|&b| b == 0));
53    }
54
55    #[test]
56    fn test_make_vmctx() {
57        let size = 1024;
58        let mut nursery = Nursery::new(size);
59        let vmctx = nursery.make_vmctx(dummy_gc_trigger);
60
61        assert_eq!(vmctx.alloc_ptr, nursery.buffer.as_mut_ptr());
62        // SAFETY: nursery.buffer is a valid Vec<u8> of `size` bytes; adding size stays within bounds.
63        assert_eq!(vmctx.alloc_limit, unsafe {
64            nursery.buffer.as_ptr().add(size)
65        });
66        assert_eq!(
67            vmctx.gc_trigger as usize,
68            dummy_gc_trigger as *const () as usize
69        );
70    }
71
72    #[test]
73    fn test_vmctx_alignment() {
74        let size = 1024;
75        let mut nursery = Nursery::new(size);
76        let vmctx = nursery.make_vmctx(dummy_gc_trigger);
77
78        // alloc_ptr should be 8-byte aligned (Vec's default alignment for u8 is likely 1,
79        // but it should be 8-byte aligned on most platforms for this size)
80        assert_eq!(vmctx.alloc_ptr as usize % 8, 0);
81    }
82
83    #[test]
84    fn test_multiple_vmctx() {
85        let size = 1024;
86        let mut nursery = Nursery::new(size);
87
88        let vmctx1 = nursery.make_vmctx(dummy_gc_trigger);
89        let vmctx2 = nursery.make_vmctx(dummy_gc_trigger);
90
91        assert_eq!(vmctx1.alloc_ptr, vmctx2.alloc_ptr);
92        assert_eq!(vmctx1.alloc_limit, vmctx2.alloc_limit);
93    }
94}