1use std::sync::atomic::AtomicU64;
5use std::sync::Arc;
6
7unsafe impl Send for Mutator {}
9
10use catallaxy_gc::{Header, LocalHeap, ObjState, ThreadRecord};
11use num_bigint::BigInt;
12use num_traits::ToPrimitive;
13
14use crate::class::{Class, BUILTINS};
15use crate::dict::DictTable;
16use crate::object::*;
17use crate::value::{fits_small, Value};
18
19unsafe fn copy_parts<'a>(mut dst: *mut u8, parts: impl Iterator<Item = &'a str>) {
24 for p in parts {
25 std::ptr::copy_nonoverlapping(p.as_ptr(), dst, p.len());
26 dst = dst.add(p.len());
27 }
28}
29
30#[derive(Debug)]
32pub enum Trap {
33 Panic(String),
35 Unwind { frame: u64, kind: u8, value: Value },
37 Exit(i32),
39}
40
41pub type RtResult<T> = Result<T, Trap>;
42
43pub fn panic<T>(msg: impl Into<String>) -> RtResult<T> {
44 Err(Trap::Panic(msg.into()))
45}
46
47pub fn check_alloc(count: usize, size: usize) -> RtResult<usize> {
51 let Some(bytes) = count.checked_mul(size) else {
52 return panic(format!("MemoryError: cannot allocate {count} items of {size} bytes"));
53 };
54 catallaxy_gc::limits::check_request(bytes).map_err(|e| Trap::Panic(format!("MemoryError: {e}")))?;
55 Ok(bytes)
56}
57
58pub fn try_vec<T>(n: usize) -> RtResult<Vec<T>> {
61 let bytes = check_alloc(n, std::mem::size_of::<T>())?;
62 let mut v = Vec::new();
63 if v.try_reserve_exact(n).is_err() {
64 return refused(format!("cannot allocate {bytes} bytes"));
65 }
66 Ok(v)
67}
68
69#[cold]
72pub fn refused<T>(what: impl std::fmt::Display) -> RtResult<T> {
73 catallaxy_gc::limits::release_headroom();
74 panic(format!("MemoryError: {what} (the allocator refused)"))
75}
76
77#[inline]
81pub fn try_push<T>(v: &mut Vec<T>, x: T) -> RtResult<()> {
82 if v.len() == v.capacity() {
83 grow_vec(v)?;
84 }
85 v.push(x);
86 Ok(())
87}
88
89#[cold]
90#[inline(never)]
91fn grow_vec<T>(v: &mut Vec<T>) -> RtResult<()> {
92 if v.try_reserve(1).is_err() {
94 let bytes = v.capacity().max(4).saturating_mul(2).saturating_mul(std::mem::size_of::<T>());
95 return refused(format!("cannot allocate {bytes} bytes"));
96 }
97 Ok(())
98}
99
100pub fn try_collect<T>(items: impl IntoIterator<Item = T>) -> RtResult<Vec<T>> {
102 let items = items.into_iter();
103 let mut v = try_vec(items.size_hint().0)?;
104 for x in items {
105 try_push(&mut v, x)?;
106 }
107 Ok(v)
108}
109
110pub fn try_string(bytes: usize) -> RtResult<String> {
112 check_alloc(bytes, 1)?;
113 let mut s = String::new();
114 if s.try_reserve_exact(bytes).is_err() {
115 return refused(format!("cannot allocate {bytes} bytes"));
116 }
117 Ok(s)
118}
119
120pub struct Mutator {
121 pub tid: u16,
122 pub rec: Arc<ThreadRecord>,
123 heap: *mut LocalHeap,
126 handles: *mut Vec<Value>,
129}
130
131#[derive(Copy, Clone)]
133pub struct Handle(usize);
134
135impl Mutator {
136 pub fn new(rec: Arc<ThreadRecord>) -> Self {
138 let heap = Box::into_raw(Box::new(LocalHeap::new(rec.tid)));
139 let handles = Box::into_raw(Box::new(Vec::new()));
140 Mutator { tid: rec.tid, rec, heap, handles }
141 }
142
143 pub fn set_young_alloc(&mut self, on: bool) {
145 unsafe { (*self.heap).set_young_alloc(on) }
146 }
147
148 pub fn sharing(other: &Mutator) -> Self {
151 Mutator {
152 tid: other.tid,
153 rec: other.rec.clone(),
154 heap: other.heap,
155 handles: Box::into_raw(Box::new(Vec::new())),
156 }
157 }
158
159 pub unsafe fn free_handles(&mut self) {
164 drop(Box::from_raw(self.handles));
165 self.handles = std::ptr::null_mut();
166 }
167
168 pub fn heap_ptr(&self) -> *mut LocalHeap {
170 self.heap
171 }
172
173 pub fn release_pages(&mut self) {
175 unsafe { (*self.heap).release_pages() }
179 }
180
181 pub fn scope<R>(&mut self, f: impl FnOnce(&mut Mutator) -> R) -> R {
185 let base = self.handles_len();
186 let r = f(self);
187 unsafe { (*self.handles).truncate(base) };
189 r
190 }
191 pub fn root(&mut self, v: Value) -> Handle {
192 unsafe {
193 (*self.handles).push(v);
194 Handle((*self.handles).len() - 1)
195 }
196 }
197 pub fn get(&self, h: Handle) -> Value {
198 unsafe { (&(*self.handles))[h.0] }
199 }
200 pub fn set(&mut self, h: Handle, v: Value) {
201 unsafe { (&mut (*self.handles))[h.0] = v };
202 }
203 fn handles_len(&self) -> usize {
204 unsafe { (*self.handles).len() }
205 }
206 pub fn handle_mark(&self) -> usize {
209 self.handles_len()
210 }
211 pub fn release_handles(&mut self, mark: usize) {
213 unsafe { (*self.handles).truncate(mark) };
214 }
215 pub fn handle_values(&self) -> &[Value] {
217 unsafe { &*self.handles }
218 }
219 pub fn handles_ptr(&self) -> *const Vec<Value> {
221 self.handles
222 }
223
224 #[inline]
227 pub fn alloc_raw(&mut self, size: usize, cls: &'static Class) -> *mut Header {
228 unsafe { (*self.heap).alloc(size, &cls.gc as *const _) }
230 }
231
232 pub fn alloc_instance(&mut self, cls: &'static Class) -> Value {
233 let n = cls.nfields();
234 let h = self.alloc_raw(InstanceObj::size_for(n), cls);
235 let inst = unsafe { &*(h as *const InstanceObj) };
236 for i in 0..n {
237 inst.set(i, Value::NIL);
238 }
239 Value::from_ptr(h)
240 }
241
242 pub fn alloc_str(&mut self, s: &str) -> Value {
243 self.alloc_str_parts(&[s])
244 }
245
246 pub fn alloc_str_parts(&mut self, parts: &[&str]) -> Value {
248 let len: usize = parts.iter().map(|p| p.len()).sum();
249 let chars = (len >= StrObj::INDEX_MIN)
250 .then(|| parts.iter().map(|p| if p.is_ascii() { p.len() } else { p.chars().count() }).sum());
251 unsafe { self.alloc_str_with(len, chars, |dst| copy_parts(dst, parts.iter().copied())) }
253 }
254
255 pub fn alloc_concat<'a>(&mut self, n: usize, part: impl Fn(usize) -> &'a StrObj) -> Value {
258 let len: usize = (0..n).map(|i| part(i).len).sum();
259 let chars = (len >= StrObj::INDEX_MIN).then(|| (0..n).map(|i| part(i).char_count()).sum());
260 unsafe { self.alloc_str_with(len, chars, |dst| copy_parts(dst, (0..n).map(|i| part(i).as_str()))) }
262 }
263
264 pub fn try_alloc_str(&mut self, s: &str) -> RtResult<Value> {
269 let chars = (s.len() >= StrObj::INDEX_MIN).then(|| if s.is_ascii() { s.len() } else { s.chars().count() });
270 unsafe { self.try_alloc_str_with(s.len(), chars, |dst| copy_parts(dst, std::iter::once(s))) }
272 }
273
274 pub fn try_alloc_concat<'a>(&mut self, n: usize, part: impl Fn(usize) -> &'a StrObj) -> RtResult<Value> {
276 let len: usize = (0..n).map(|i| part(i).len).sum();
277 let chars = (len >= StrObj::INDEX_MIN).then(|| (0..n).map(|i| part(i).char_count()).sum());
278 unsafe { self.try_alloc_str_with(len, chars, |dst| copy_parts(dst, (0..n).map(|i| part(i).as_str()))) }
280 }
281
282 #[inline]
286 pub fn try_alloc_str_nogc(&mut self, s: &str) -> RtResult<Value> {
287 let chars = (s.len() >= StrObj::INDEX_MIN).then(|| if s.is_ascii() { s.len() } else { s.chars().count() });
288 let (size, word) = StrObj::layout(s.len(), chars);
289 if size > catallaxy_gc::MAX_SMALL {
290 check_alloc(size, 1)?;
291 }
292 unsafe {
294 let h = (*self.heap).try_alloc_uninit(size, &BUILTINS.string.gc as *const _);
295 if h.is_null() {
296 return refused(format!("cannot allocate a string of {} bytes", s.len()));
297 }
298 Ok(self.init_str(h, size, s.len(), word, |dst| copy_parts(dst, std::iter::once(s))))
299 }
300 }
301
302 #[inline]
307 pub fn collect_if_refused(&self) {
308 if catallaxy_gc::limits::memory_refused() {
309 catallaxy_gc::collect_to_free(&self.rec);
310 }
311 }
312
313 pub fn try_vec_collecting<T>(&self, n: usize) -> RtResult<Vec<T>> {
317 self.collect_if_refused();
318 let bytes = check_alloc(n, std::mem::size_of::<T>())?;
319 let mut v = Vec::new();
320 if v.try_reserve_exact(n).is_err() {
321 catallaxy_gc::collect_to_free(&self.rec);
322 if v.try_reserve_exact(n).is_err() {
323 return refused(format!("cannot allocate {bytes} bytes"));
324 }
325 }
326 Ok(v)
327 }
328
329 #[inline]
334 unsafe fn try_alloc_str_with(
335 &mut self,
336 len: usize,
337 chars: Option<usize>,
338 fill: impl FnOnce(*mut u8),
339 ) -> RtResult<Value> {
340 let (size, word) = StrObj::layout(len, chars);
341 if size > catallaxy_gc::MAX_SMALL {
342 check_alloc(size, 1)?;
345 }
346 let vtable = &BUILTINS.string.gc as *const _;
347 let mut h = (*self.heap).try_alloc_uninit(size, vtable);
348 if h.is_null() {
349 h = self.retry_str(size, len)?;
350 }
351 Ok(self.init_str(h, size, len, word, fill))
352 }
353
354 #[cold]
357 #[inline(never)]
358 fn retry_str(&mut self, size: usize, len: usize) -> RtResult<*mut Header> {
359 catallaxy_gc::collect_to_free(&self.rec);
360 let h = unsafe { (*self.heap).try_alloc_uninit(size, &BUILTINS.string.gc as *const _) };
362 if h.is_null() {
363 return refused(format!("cannot allocate a string of {len} bytes"));
364 }
365 Ok(h)
366 }
367
368 unsafe fn alloc_str_with(&mut self, len: usize, chars: Option<usize>, fill: impl FnOnce(*mut u8)) -> Value {
374 debug_assert!(len < StrObj::INDEX_MIN || chars.is_some());
375 let (size, word) = StrObj::layout(len, chars);
376 let h = (*self.heap).alloc_uninit(size, &BUILTINS.string.gc as *const _);
378 self.init_str(h, size, len, word, fill)
379 }
380
381 #[inline(always)]
386 unsafe fn init_str(
387 &mut self,
388 h: *mut Header,
389 size: usize,
390 len: usize,
391 word: u64,
392 fill: impl FnOnce(*mut u8),
393 ) -> Value {
394 let o = h as *mut StrObj;
395 std::ptr::addr_of_mut!((*o).len).write(len);
396 std::ptr::addr_of_mut!((*o).hash).write(AtomicU64::new(0));
397 std::ptr::addr_of_mut!((*o).chars).write(AtomicU64::new(word));
398 let data = (h as *mut u8).add(StrObj::BASE);
399 fill(data);
400 std::ptr::write_bytes(data.add(len), 0, size - StrObj::BASE - len);
402 (*h).set_state(ObjState::Frozen);
403 Value::from_ptr(h)
404 }
405
406 pub fn int_from_big(&mut self, b: BigInt) -> Value {
408 if let Some(i) = b.to_i64() {
409 if fits_small(i) {
410 return Value::small_int(i);
411 }
412 }
413 let h = self.alloc_raw(std::mem::size_of::<BigIntObj>(), BUILTINS.bigint);
414 unsafe {
415 std::ptr::write(&mut (*(h as *mut BigIntObj)).value, b);
416 (*h).set_state(ObjState::Frozen);
417 }
418 Value::from_ptr(h)
419 }
420
421 #[inline]
422 pub fn int(&mut self, i: i64) -> Value {
423 if fits_small(i) {
424 Value::small_int(i)
425 } else {
426 self.int_from_big(BigInt::from(i))
427 }
428 }
429
430 pub fn alloc_list(&mut self, items: Vec<Value>) -> Value {
433 let icap = ListObj::inline_cap_for(items.len());
434 let h = self.alloc_raw(ListObj::size_for(icap), BUILTINS.list);
435 unsafe { ListObj::init(h as *mut ListObj, items, icap) };
436 Value::from_ptr(h)
437 }
438
439 pub fn try_alloc_list(&mut self, items: Vec<Value>) -> RtResult<Value> {
442 let icap = ListObj::inline_cap_for(items.len());
443 let h = self.alloc_raw(ListObj::size_for(icap), BUILTINS.list);
444 match unsafe { ListObj::try_init(h as *mut ListObj, items, icap) } {
446 Ok(()) => Ok(Value::from_ptr(h)),
447 Err(layout) => refused(format!("cannot allocate a list of {} bytes", layout.size())),
448 }
449 }
450
451 pub fn alloc_list_from_slice(&mut self, items: &[Value], cap: usize) -> Value {
453 let icap = ListObj::inline_cap_for(cap.max(items.len()));
454 let h = self.alloc_raw(ListObj::size_for(icap), BUILTINS.list);
455 unsafe { ListObj::init_from_slice(h as *mut ListObj, items, cap, icap) };
456 Value::from_ptr(h)
457 }
458
459 pub fn try_alloc_list_from_slice(&mut self, items: &[Value], cap: usize) -> RtResult<Value> {
462 let icap = ListObj::inline_cap_for(cap.max(items.len()));
463 let h = self.alloc_raw(ListObj::size_for(icap), BUILTINS.list);
464 match unsafe { ListObj::try_init_from_slice(h as *mut ListObj, items, cap, icap) } {
465 Ok(()) => Ok(Value::from_ptr(h)),
466 Err(layout) => refused(format!("cannot allocate a list of {} bytes", layout.size())),
467 }
468 }
469
470 pub fn alloc_dict(&mut self, table: DictTable) -> Value {
471 self.alloc_table(table, BUILTINS.dict)
472 }
473
474 pub fn alloc_set(&mut self, table: DictTable) -> Value {
475 self.alloc_table(table, BUILTINS.set)
476 }
477
478 fn alloc_table(&mut self, table: DictTable, cls: &'static Class) -> Value {
479 let h = self.alloc_raw(std::mem::size_of::<DictObj>(), cls);
480 unsafe { std::ptr::write(std::ptr::addr_of_mut!((*(h as *mut DictObj)).table), table) };
481 Value::from_ptr(h)
482 }
483
484 pub fn alloc_tuple(&mut self, items: &[Value]) -> Value {
485 let h = self.alloc_raw(TupleObj::size_for(items.len()), BUILTINS.tuple);
486 unsafe {
487 let t = &mut *(h as *mut TupleObj);
488 t.len = items.len();
489 std::ptr::copy_nonoverlapping(items.as_ptr(), t.elems_mut(), items.len());
490 }
491 Value::from_ptr(h)
492 }
493
494 pub fn alloc_closure(&mut self, func: u32, caps: &[Value]) -> Value {
495 let h = self.alloc_raw(ClosureObj::size_for(caps.len()), BUILTINS.closure);
496 unsafe {
497 let c = &mut *(h as *mut ClosureObj);
498 c.func = func;
499 c.ncaps = caps.len() as u32;
500 std::ptr::copy_nonoverlapping(caps.as_ptr(), c.caps_mut(), caps.len());
501 }
502 Value::from_ptr(h)
503 }
504
505 pub fn alloc_cell(&mut self, v: Value) -> Value {
506 let h = self.alloc_raw(std::mem::size_of::<CellObj>(), BUILTINS.cell);
507 unsafe { (*(h as *mut CellObj)).value = AtomicU64::new(v.0) };
508 Value::from_ptr(h)
509 }
510
511 pub fn alloc_string_builder(&mut self, s: String) -> Value {
512 let h = self.alloc_raw(std::mem::size_of::<StringBuilderObj>(), BUILTINS.string_builder);
513 unsafe { std::ptr::write((*(h as *mut StringBuilderObj)).buf.get(), s) };
514 Value::from_ptr(h)
515 }
516
517 pub fn alloc_mutex(&mut self, v: Value) -> Value {
518 let h = self.alloc_raw(std::mem::size_of::<MutexObj>(), BUILTINS.mutex);
519 unsafe {
520 std::ptr::write(&mut (*(h as *mut MutexObj)).raw, parking_lot::ReentrantMutex::new(()));
521 (*(h as *mut MutexObj)).value = AtomicU64::new(v.0);
522 (*h).set_state(ObjState::Shared);
523 }
524 Value::from_ptr(h)
525 }
526
527 pub fn alloc_atomic(&mut self, v: Value) -> Value {
528 let h = self.alloc_raw(std::mem::size_of::<AtomicObj>(), BUILTINS.atomic);
529 unsafe {
530 (*(h as *mut AtomicObj)).value = AtomicU64::new(v.0);
531 (*h).set_state(ObjState::Shared);
532 }
533 Value::from_ptr(h)
534 }
535
536 pub fn alloc_channel(&mut self, capacity: usize) -> Value {
537 let h = self.alloc_raw(std::mem::size_of::<ChannelObj>(), BUILTINS.channel);
538 let inner = Box::new(ChannelInner {
539 queue: parking_lot::Mutex::new(ChannelState { items: Default::default(), closed: false }),
540 not_empty: parking_lot::Condvar::new(),
541 not_full: parking_lot::Condvar::new(),
542 capacity,
543 waiters: parking_lot::Mutex::new(Vec::new()),
544 });
545 unsafe {
546 std::ptr::write(&mut (*(h as *mut ChannelObj)).inner, inner);
547 (*h).set_state(ObjState::Shared);
548 }
549 Value::from_ptr(h)
550 }
551
552 pub fn alloc_thread(&mut self) -> Value {
553 let h = self.alloc_raw(std::mem::size_of::<ThreadObj>(), BUILTINS.thread);
554 let inner = std::sync::Arc::new(ThreadInner {
555 outcome: parking_lot::Mutex::new(ThreadOutcome::Running),
556 done: parking_lot::Condvar::new(),
557 handle: parking_lot::Mutex::new(None),
558 tid: AtomicU64::new(0),
559 });
560 unsafe {
561 std::ptr::write(&mut (*(h as *mut ThreadObj)).inner, inner);
562 (*h).set_state(ObjState::Shared);
563 }
564 Value::from_ptr(h)
565 }
566
567 pub fn alloc_native(&mut self, data: Box<dyn NativeData>) -> Value {
568 let h = self.alloc_raw(std::mem::size_of::<NativeObj>(), BUILTINS.native);
569 unsafe { std::ptr::write(&mut (*(h as *mut NativeObj)).data, data) };
570 Value::from_ptr(h)
571 }
572
573 pub fn alloc_native_frozen(&mut self, data: Box<dyn NativeData>) -> Value {
576 let v = self.alloc_native(data);
577 unsafe { (*v.as_ptr()).set_state(ObjState::Frozen) };
579 v
580 }
581
582 pub fn alloc_range(&mut self, first: Value, last: Value, exclusive: bool) -> Value {
583 let v = self.alloc_instance(BUILTINS.range);
584 let r = unsafe { as_instance(v) };
585 r.set(0, first);
586 r.set(1, last);
587 r.set(2, Value::bool(exclusive));
588 unsafe { (*v.as_ptr()).set_state(ObjState::Frozen) };
589 v
590 }
591}
592
593pub fn note_external(bytes: usize) {
595 if bytes > 0 {
596 catallaxy_gc::stats::note_allocated(bytes);
597 }
598}
599
600pub struct HandleScope<'m> {
611 m: *mut Mutator,
612 base: usize,
613 _borrow: std::marker::PhantomData<&'m mut Mutator>,
614}
615
616#[derive(Copy, Clone)]
618pub struct Local<'s> {
619 index: usize,
620 _scope: std::marker::PhantomData<&'s ()>,
621}
622
623impl<'m> HandleScope<'m> {
624 pub fn new(m: &'m mut Mutator) -> Self {
625 let base = m.handles_len();
626 HandleScope { m, base, _borrow: std::marker::PhantomData }
627 }
628
629 pub unsafe fn detached(m: *mut Mutator) -> HandleScope<'static> {
635 let base = (*m).handles_len();
636 HandleScope { m, base, _borrow: std::marker::PhantomData }
637 }
638
639 pub fn root<'s>(&'s self, v: Value) -> Local<'s> {
641 let hs = unsafe { &mut *(*self.m).handles };
644 hs.push(v);
645 Local { index: hs.len() - 1, _scope: std::marker::PhantomData }
646 }
647
648 pub fn get(&self, h: Local<'_>) -> Value {
649 unsafe { (&(*(*self.m).handles))[h.index] }
650 }
651
652 pub fn set(&self, h: Local<'_>, v: Value) {
653 unsafe { (&mut (*(*self.m).handles))[h.index] = v }
654 }
655
656 #[allow(clippy::mut_from_ref)]
658 pub fn mutator(&self) -> &mut Mutator {
659 unsafe { &mut *self.m }
660 }
661}
662
663impl Drop for HandleScope<'_> {
664 fn drop(&mut self) {
665 unsafe { (*(*self.m).handles).truncate(self.base) };
666 }
667}