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 pub src_instr_ptr: u32,
42 pub dst_instr_ptr: u32,
44 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 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 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 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 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 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 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 }
355 CaoLangObjectBody::Function(_) => {
356 }
358 CaoLangObjectBody::NativeFunction(_) => {
359 }
361 CaoLangObjectBody::Upvalue(u) => unsafe {
362 if let Some(t) = u.location.as_mut() {
363 checked_enqueue_value!(t);
364 }
365 },
366 }
367 }
368 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 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}