cao_lang/vm/
runtime.rs

1pub mod cao_lang_function;
2pub mod cao_lang_object;
3pub mod cao_lang_string;
4pub mod cao_lang_table;
5
6use std::{alloc::Layout, pin::Pin, ptr::NonNull};
7
8use crate::{
9    alloc::{AllocProxy, Allocator, CaoLangAllocator},
10    collections::{bounded_stack::BoundedStack, value_stack::ValueStack},
11    prelude::*,
12    value::Value,
13    vm::runtime::cao_lang_object::CaoLangObjectBody,
14};
15use tracing::debug;
16
17use self::{
18    cao_lang_function::{CaoLangClosure, CaoLangFunction, CaoLangNativeFunction, CaoLangUpvalue},
19    cao_lang_object::{CaoLangObject, GcMarker, ObjectGcGuard},
20    cao_lang_string::CaoLangString,
21};
22
23pub struct RuntimeData {
24    pub(crate) value_stack: ValueStack,
25    pub(crate) call_stack: BoundedStack<CallFrame>,
26    pub(crate) global_vars: Vec<Value>,
27    pub(crate) memory: AllocProxy,
28    pub(crate) object_list: Vec<NonNull<CaoLangObject>>,
29    pub(crate) current_program: *const CaoCompiledProgram,
30    pub(crate) open_upvalues: *mut CaoLangObject,
31}
32
33impl Drop for RuntimeData {
34    fn drop(&mut self) {
35        self.clear();
36    }
37}
38
39pub(crate) struct CallFrame {
40    /// Store src addresses of Function calls
41    pub src_instr_ptr: u32,
42    /// Store return addresses of Function calls
43    pub dst_instr_ptr: u32,
44    /// beginning of the local stack
45    pub stack_offset: u32,
46    pub closure: *mut CaoLangClosure,
47}
48
49impl RuntimeData {
50    pub fn new(
51        memory_limit: usize,
52        stack_size: usize,
53        call_stack_size: usize,
54    ) -> Result<Pin<Box<Self>>, ExecutionErrorPayload> {
55        // we have a chicken-egg problem if we want to store the allocator in this structure
56        let allocator = CaoLangAllocator::new(std::ptr::null_mut(), memory_limit);
57        let memory: AllocProxy = allocator.into();
58        let mut res = Box::pin(Self {
59            value_stack: ValueStack::new(stack_size),
60            call_stack: BoundedStack::new(call_stack_size),
61            global_vars: Vec::with_capacity(16),
62            object_list: Vec::with_capacity(16),
63            memory,
64            current_program: std::ptr::null(),
65            open_upvalues: std::ptr::null_mut(),
66        });
67        unsafe {
68            let reference: &mut Self = Pin::get_mut(res.as_mut());
69            res.memory.get_inner().runtime = reference as *mut Self;
70        }
71        Ok(res)
72    }
73
74    /// Initialize a new cao-lang table and return a pointer to it
75    pub fn init_table(&mut self) -> Result<ObjectGcGuard, ExecutionErrorPayload> {
76        unsafe {
77            let obj_ptr = self
78                .memory
79                .alloc(Layout::new::<CaoLangObject>())
80                .map_err(|err| {
81                    debug!("Failed to allocate table {:?}", err);
82                    ExecutionErrorPayload::OutOfMemory
83                })?;
84            let table = CaoLangTable::with_capacity(8, self.memory.clone()).map_err(|err| {
85                debug!("Failed to init table {:?}", err);
86                ExecutionErrorPayload::OutOfMemory
87            })?;
88
89            let obj_ptr: NonNull<CaoLangObject> = obj_ptr.cast();
90            let obj = CaoLangObject {
91                marker: GcMarker::White,
92                body: CaoLangObjectBody::Table(table),
93            };
94            std::ptr::write(obj_ptr.as_ptr(), obj);
95            self.object_list.push(obj_ptr);
96            Ok(ObjectGcGuard::new(obj_ptr))
97        }
98    }
99
100    pub fn init_native_function(
101        &mut self,
102        handle: Handle,
103    ) -> Result<ObjectGcGuard, ExecutionErrorPayload> {
104        unsafe {
105            let obj_ptr = self
106                .memory
107                .alloc(Layout::new::<CaoLangObject>())
108                .map_err(|err| {
109                    debug!("Failed to allocate NativeFunction {:?}", err);
110                    ExecutionErrorPayload::OutOfMemory
111                })?;
112
113            let obj_ptr: NonNull<CaoLangObject> = obj_ptr.cast();
114            let obj = CaoLangObject {
115                marker: GcMarker::White,
116                body: CaoLangObjectBody::NativeFunction(CaoLangNativeFunction { handle }),
117            };
118            std::ptr::write(obj_ptr.as_ptr(), obj);
119            self.object_list.push(obj_ptr);
120
121            Ok(ObjectGcGuard::new(obj_ptr))
122        }
123    }
124
125    pub fn init_function(
126        &mut self,
127        handle: Handle,
128        arity: u32,
129    ) -> Result<ObjectGcGuard, ExecutionErrorPayload> {
130        unsafe {
131            let obj_ptr = self
132                .memory
133                .alloc(Layout::new::<CaoLangObject>())
134                .map_err(|err| {
135                    debug!("Failed to allocate table {:?}", err);
136                    ExecutionErrorPayload::OutOfMemory
137                })?;
138
139            let obj_ptr: NonNull<CaoLangObject> = obj_ptr.cast();
140            let obj = CaoLangObject {
141                marker: GcMarker::White,
142                body: CaoLangObjectBody::Function(CaoLangFunction { handle, arity }),
143            };
144            std::ptr::write(obj_ptr.as_ptr(), obj);
145            self.object_list.push(obj_ptr);
146
147            Ok(ObjectGcGuard::new(obj_ptr))
148        }
149    }
150
151    pub fn init_closure(
152        &mut self,
153        handle: Handle,
154        arity: u32,
155    ) -> Result<ObjectGcGuard, ExecutionErrorPayload> {
156        unsafe {
157            let obj_ptr = self
158                .memory
159                .alloc(Layout::new::<CaoLangObject>())
160                .map_err(|err| {
161                    debug!("Failed to allocate table {:?}", err);
162                    ExecutionErrorPayload::OutOfMemory
163                })?;
164
165            let obj_ptr: NonNull<CaoLangObject> = obj_ptr.cast();
166            let obj = CaoLangObject {
167                marker: GcMarker::White,
168                body: CaoLangObjectBody::Closure(CaoLangClosure {
169                    function: CaoLangFunction { handle, arity },
170                    upvalues: vec![],
171                }),
172            };
173            std::ptr::write(obj_ptr.as_ptr(), obj);
174            self.object_list.push(obj_ptr);
175
176            Ok(ObjectGcGuard::new(obj_ptr))
177        }
178    }
179
180    pub fn init_upvalue(
181        &mut self,
182        location: *mut Value,
183    ) -> Result<ObjectGcGuard, ExecutionErrorPayload> {
184        unsafe {
185            let obj_ptr = self
186                .memory
187                .alloc(Layout::new::<CaoLangObject>())
188                .map_err(|err| {
189                    debug!("Failed to allocate table {:?}", err);
190                    ExecutionErrorPayload::OutOfMemory
191                })?;
192
193            let obj_ptr: NonNull<CaoLangObject> = obj_ptr.cast();
194            let obj = CaoLangObject {
195                marker: GcMarker::White,
196                body: CaoLangObjectBody::Upvalue(CaoLangUpvalue {
197                    location,
198                    value: Value::Nil,
199                    next: std::ptr::null_mut(),
200                }),
201            };
202            std::ptr::write(obj_ptr.as_ptr(), obj);
203            self.object_list.push(obj_ptr);
204
205            Ok(ObjectGcGuard::new(obj_ptr))
206        }
207    }
208
209    pub fn init_string(&mut self, payload: &str) -> Result<ObjectGcGuard, ExecutionErrorPayload> {
210        unsafe {
211            let obj_ptr = self
212                .memory
213                .alloc(Layout::new::<CaoLangObject>())
214                .map_err(|err| {
215                    debug!("Failed to allocate table {:?}", err);
216                    ExecutionErrorPayload::OutOfMemory
217                })?;
218
219            let layout = CaoLangString::layout(payload.len());
220            let mut ptr = self
221                .memory
222                .alloc(layout)
223                .map_err(|_| ExecutionErrorPayload::OutOfMemory)?;
224
225            let result: *mut u8 = ptr.as_mut();
226            std::ptr::copy(payload.as_ptr(), result, payload.len());
227
228            let obj_ptr: NonNull<CaoLangObject> = obj_ptr.cast();
229            let obj = CaoLangObject {
230                marker: GcMarker::White,
231                body: CaoLangObjectBody::String(CaoLangString {
232                    len: payload.len(),
233                    ptr,
234                    alloc: self.memory.clone(),
235                }),
236            };
237            std::ptr::write(obj_ptr.as_ptr(), obj);
238            self.object_list.push(obj_ptr);
239
240            Ok(ObjectGcGuard::new(obj_ptr))
241        }
242    }
243
244    pub fn free_object(&mut self, obj: NonNull<CaoLangObject>) {
245        unsafe {
246            std::ptr::drop_in_place(obj.as_ptr());
247            self.memory
248                .dealloc(obj.cast(), Layout::new::<CaoLangObject>());
249        }
250    }
251
252    pub fn clear(&mut self) {
253        self.clear_objects();
254        self.value_stack.clear();
255        self.global_vars.clear();
256        self.call_stack.clear();
257        self.open_upvalues = std::ptr::null_mut();
258    }
259
260    fn clear_objects(&mut self) {
261        for obj_ptr in std::mem::take(&mut self.object_list).into_iter() {
262            self.free_object(obj_ptr);
263        }
264    }
265
266    pub fn set_memory_limit(&mut self, capacity: usize) {
267        self.clear();
268        unsafe {
269            self.memory
270                .get_inner()
271                .limit
272                .store(capacity, std::sync::atomic::Ordering::Relaxed);
273        }
274    }
275
276    /// Types implementing Drop are not supported, thus the `Copy` bound
277    pub fn write_to_memory<T: Sized + Copy>(
278        &mut self,
279        val: T,
280    ) -> Result<*mut T, ExecutionErrorPayload> {
281        let l = std::alloc::Layout::new::<T>();
282        unsafe {
283            let ptr = self
284                .memory
285                .alloc(l)
286                .map_err(|_| ExecutionErrorPayload::OutOfMemory)?;
287
288            std::ptr::write(ptr.as_ptr() as *mut T, val);
289            Ok(ptr.as_ptr() as *mut T)
290        }
291    }
292
293    pub fn gc(&mut self) {
294        debug!("• GC");
295        // mark all roots for collection
296        let mut progress_tracker = Vec::with_capacity(self.value_stack.len());
297        for val in self.value_stack.iter() {
298            if let Value::Object(mut t) = val {
299                unsafe {
300                    let t = t.as_mut();
301                    t.marker = GcMarker::Gray;
302                    progress_tracker.push(t);
303                }
304            }
305        }
306        // mark globals
307        for val in self.global_vars.iter() {
308            if let Value::Object(mut t) = val {
309                unsafe {
310                    let t = t.as_mut();
311                    t.marker = GcMarker::Gray;
312                    progress_tracker.push(t);
313                }
314            }
315        }
316
317        macro_rules! checked_enqueue_value {
318            ($val: ident) => {
319                if let Value::Object(mut value) = $val {
320                    let t = value.as_mut();
321                    if matches!(t.marker, GcMarker::White) {
322                        t.marker = GcMarker::Gray;
323                        progress_tracker.push(t);
324                    }
325                }
326            };
327        }
328
329        // mark referenced objects for collection
330        while let Some(obj) = progress_tracker.pop() {
331            obj.marker = GcMarker::Black;
332            match &mut obj.body {
333                CaoLangObjectBody::Table(obj) => {
334                    for (key, value) in obj.iter() {
335                        unsafe {
336                            checked_enqueue_value!(key);
337                            checked_enqueue_value!(value);
338                        }
339                    }
340                }
341                CaoLangObjectBody::Closure(c) => {
342                    for upvalue in &mut c.upvalues {
343                        unsafe {
344                            let t = upvalue.as_mut();
345                            if matches!(t.marker, GcMarker::White) {
346                                t.marker = GcMarker::Gray;
347                                progress_tracker.push(t);
348                            }
349                        }
350                    }
351                }
352                CaoLangObjectBody::String(_) => {
353                    // strings don't have children
354                }
355                CaoLangObjectBody::Function(_) => {
356                    // function objects don't have children
357                }
358                CaoLangObjectBody::NativeFunction(_) => {
359                    // native function objects don't have children
360                }
361                CaoLangObjectBody::Upvalue(u) => unsafe {
362                    if let Some(t) = u.location.as_mut() {
363                        checked_enqueue_value!(t);
364                    }
365                },
366            }
367        }
368        // sweep
369        //
370        let mut collected = Vec::with_capacity(self.object_list.len());
371        for (i, object) in self.object_list.iter().copied().enumerate() {
372            unsafe {
373                let obj = object.as_ref();
374                if matches!(obj.marker, GcMarker::White) {
375                    collected.push(i);
376                }
377            }
378        }
379        for i in collected.into_iter().rev() {
380            let obj = self.object_list.swap_remove(i);
381            self.free_object(obj);
382        }
383        // unmark remaning objects
384        for table in self.object_list.iter_mut() {
385            unsafe {
386                let table = table.as_mut();
387                if !matches!(table.marker, GcMarker::Protected) {
388                    table.marker = GcMarker::White;
389                }
390            }
391        }
392        debug!("✓ GC");
393    }
394
395    pub fn capture_upvalue() {}
396}
397
398#[cfg(test)]
399mod tests {
400    use std::ops::DerefMut;
401
402    use super::*;
403
404    #[test]
405    fn field_table_can_be_queried_by_str_test() {
406        let mut vm = Vm::new(()).unwrap();
407
408        let s = vm.init_string("poggers").unwrap();
409        let mut o = vm.init_table().unwrap();
410        let o = o.deref_mut().as_table_mut().unwrap();
411
412        o.insert(Value::Object(s.into_inner()), Value::Integer(42))
413            .unwrap();
414
415        let res = o.get("poggers").unwrap();
416
417        assert_eq!(res, &Value::Integer(42));
418    }
419}