use crate::env::Scope;
use crate::gc::Trace;
use crate::heap::Handle;
use crate::nanbox::NanBox;
use crate::object::Object;
use crate::rope::Rope;
use alloc::rc::Rc;
use alloc::vec::Vec;
use core::cell::RefCell;
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum PromiseStatus {
Pending,
Fulfilled,
Rejected,
}
pub struct Reaction {
pub on_fulfilled: NanBox,
pub on_rejected: NanBox,
pub result: Handle,
pub finally: bool,
}
pub struct PromiseState {
pub status: PromiseStatus,
pub value: NanBox,
pub reactions: Vec<Reaction>,
}
pub type ExternFree = unsafe fn(ptr: *mut u8, len: usize);
pub enum ByteStore {
Owned(Vec<u8>),
External(ExternalBytes),
}
pub struct ExternalBytes {
ptr: *mut u8,
len: usize,
free: Option<ExternFree>,
}
impl ByteStore {
#[must_use]
#[allow(unsafe_code)]
pub unsafe fn external(ptr: *mut u8, len: usize, free: Option<ExternFree>) -> Self {
ByteStore::External(ExternalBytes { ptr, len, free })
}
#[must_use]
pub fn len(&self) -> usize {
match self {
ByteStore::Owned(v) => v.len(),
ByteStore::External(e) => e.len,
}
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
match self {
ByteStore::Owned(v) => v,
#[allow(unsafe_code)]
ByteStore::External(e) => unsafe { core::slice::from_raw_parts(e.ptr, e.len) },
}
}
pub fn as_mut_slice(&mut self) -> &mut [u8] {
match self {
ByteStore::Owned(v) => v,
#[allow(unsafe_code)]
ByteStore::External(e) => unsafe { core::slice::from_raw_parts_mut(e.ptr, e.len) },
}
}
}
impl Drop for ExternalBytes {
fn drop(&mut self) {
if let Some(free) = self.free {
#[allow(unsafe_code)]
unsafe {
free(self.ptr, self.len);
}
}
}
}
pub enum Cell {
Object(Object),
Str(Rope),
Array(Vec<NanBox>),
Bytes(ByteStore),
TypedArray {
buffer: Handle,
array_buffer: Handle,
byte_offset: usize,
length: usize,
kind: u8,
},
Function {
func_id: u32,
env: Scope,
},
Native(u16),
BoundNative {
id: u16,
target: Handle,
},
Promise(Rc<RefCell<PromiseState>>),
Date(f64),
RegExp {
source: alloc::boxed::Box<str>,
flags: alloc::boxed::Box<str>,
last_index: usize,
#[cfg(feature = "regex")]
compiled: core::cell::RefCell<Option<alloc::rc::Rc<crate::regex::Regex>>>,
},
Class {
class_id: u32,
env: Scope,
},
Collection {
is_set: bool,
is_weak: bool,
entries: Vec<(NanBox, NanBox)>,
},
Symbol {
description: alloc::boxed::Box<str>,
id: u64,
},
BigInt(crate::bignum::BigInt),
Proxy {
target: Handle,
handler: Handle,
revoked: bool,
},
}
impl Cell {
#[must_use]
pub fn as_object(&self) -> Option<&Object> {
match self {
Cell::Object(o) => Some(o),
_ => None,
}
}
pub fn as_object_mut(&mut self) -> Option<&mut Object> {
match self {
Cell::Object(o) => Some(o),
_ => None,
}
}
#[must_use]
pub fn as_str(&self) -> Option<&Rope> {
match self {
Cell::Str(s) => Some(s),
_ => None,
}
}
#[must_use]
pub fn as_array(&self) -> Option<&[NanBox]> {
match self {
Cell::Array(a) => Some(a),
_ => None,
}
}
pub fn as_array_mut(&mut self) -> Option<&mut Vec<NanBox>> {
match self {
Cell::Array(a) => Some(a),
_ => None,
}
}
#[must_use]
pub fn as_bytes(&self) -> Option<&[u8]> {
match self {
Cell::Bytes(b) => Some(b.as_slice()),
_ => None,
}
}
pub fn as_byte_store_mut(&mut self) -> Option<&mut ByteStore> {
match self {
Cell::Bytes(b) => Some(b),
_ => None,
}
}
#[must_use]
pub fn as_typed_array(&self) -> Option<(Handle, usize, usize, u8)> {
match self {
Cell::TypedArray {
buffer,
byte_offset,
length,
kind,
..
} => Some((*buffer, *byte_offset, *length, *kind)),
_ => None,
}
}
#[must_use]
pub fn typed_array_object(&self) -> Option<Handle> {
match self {
Cell::TypedArray { array_buffer, .. } => Some(*array_buffer),
_ => None,
}
}
#[must_use]
pub fn as_function(&self) -> Option<(u32, &Scope)> {
match self {
Cell::Function { func_id, env } => Some((*func_id, env)),
_ => None,
}
}
#[must_use]
pub fn as_native(&self) -> Option<u16> {
match self {
Cell::Native(id) => Some(*id),
_ => None,
}
}
#[must_use]
pub fn as_bound_native(&self) -> Option<(u16, Handle)> {
match self {
Cell::BoundNative { id, target } => Some((*id, *target)),
_ => None,
}
}
#[must_use]
pub fn as_promise(&self) -> Option<&Rc<RefCell<PromiseState>>> {
match self {
Cell::Promise(p) => Some(p),
_ => None,
}
}
#[must_use]
pub fn as_date(&self) -> Option<f64> {
match self {
Cell::Date(ms) => Some(*ms),
_ => None,
}
}
#[must_use]
pub fn as_regexp(&self) -> Option<(&str, &str)> {
match self {
Cell::RegExp { source, flags, .. } => Some((source, flags)),
_ => None,
}
}
#[must_use]
pub fn as_class(&self) -> Option<(u32, &Scope)> {
match self {
Cell::Class { class_id, env } => Some((*class_id, env)),
_ => None,
}
}
pub fn as_collection_mut(&mut self) -> Option<(bool, &mut Vec<(NanBox, NanBox)>)> {
match self {
Cell::Collection {
is_set, entries, ..
} => Some((*is_set, entries)),
_ => None,
}
}
#[must_use]
pub fn as_collection(&self) -> Option<(bool, &[(NanBox, NanBox)])> {
match self {
Cell::Collection {
is_set, entries, ..
} => Some((*is_set, entries)),
_ => None,
}
}
#[must_use]
pub fn type_of(&self) -> &'static str {
match self {
Cell::Str(_) => "string",
Cell::Function { .. }
| Cell::Native(_)
| Cell::BoundNative { .. }
| Cell::Class { .. } => "function",
Cell::Symbol { .. } => "symbol",
Cell::BigInt(_) => "bigint",
Cell::Object(_)
| Cell::Array(_)
| Cell::TypedArray { .. }
| Cell::Collection { .. }
| Cell::Promise(_)
| Cell::Date(_)
| Cell::RegExp { .. }
| Cell::Proxy { .. } => "object",
Cell::Bytes(_) => "object",
}
}
#[must_use]
pub fn as_symbol(&self) -> Option<(&str, u64)> {
match self {
Cell::Symbol { description, id } => Some((description, *id)),
_ => None,
}
}
#[must_use]
pub fn as_bigint(&self) -> Option<&crate::bignum::BigInt> {
match self {
Cell::BigInt(n) => Some(n),
_ => None,
}
}
#[must_use]
pub fn as_proxy(&self) -> Option<(Handle, Handle)> {
match self {
Cell::Proxy {
target, handler, ..
} => Some((*target, *handler)),
_ => None,
}
}
#[must_use]
pub fn proxy_revoked(&self) -> Option<bool> {
match self {
Cell::Proxy { revoked, .. } => Some(*revoked),
_ => None,
}
}
pub fn revoke_proxy(&mut self) {
if let Cell::Proxy { revoked, .. } = self {
*revoked = true;
}
}
}
impl Trace for Cell {
fn trace(&self, visit: &mut dyn FnMut(Handle)) {
match self {
Cell::Object(o) => o.trace_handles(visit),
Cell::Array(elems) => {
for e in elems {
if let Some(raw) = e.as_handle() {
visit(Handle::from_raw(raw));
}
}
}
Cell::Function { env, .. } | Cell::Class { env, .. } => env.for_each_handle(visit),
Cell::Collection { entries, .. } => {
for (k, v) in entries {
if let Some(raw) = k.as_handle() {
visit(Handle::from_raw(raw));
}
if let Some(raw) = v.as_handle() {
visit(Handle::from_raw(raw));
}
}
}
Cell::Promise(p) => {
let s = p.borrow();
if let Some(raw) = s.value.as_handle() {
visit(Handle::from_raw(raw));
}
for r in &s.reactions {
for h in [r.on_fulfilled, r.on_rejected] {
if let Some(raw) = h.as_handle() {
visit(Handle::from_raw(raw));
}
}
visit(r.result);
}
}
Cell::TypedArray {
buffer,
array_buffer,
..
} => {
visit(*buffer);
visit(*array_buffer);
}
Cell::BoundNative { target, .. } => visit(*target),
Cell::Proxy {
target, handler, ..
} => {
visit(*target);
visit(*handler);
}
Cell::Str(_)
| Cell::Native(_)
| Cell::Date(_)
| Cell::RegExp { .. }
| Cell::Symbol { .. }
| Cell::BigInt(_)
| Cell::Bytes(_) => {}
}
}
}
impl crate::gc::Relocate for Cell {
fn relocate(&mut self, forward: &dyn Fn(Handle) -> Handle) {
let fwd = |v: &mut NanBox| {
if let Some(raw) = v.as_handle() {
*v = NanBox::handle(forward(Handle::from_raw(raw)).to_raw());
}
};
match self {
Cell::Object(o) => o.relocate_handles(forward),
Cell::Array(elems) => elems.iter_mut().for_each(fwd),
Cell::Function { env, .. } | Cell::Class { env, .. } => env.relocate_handles(forward),
Cell::Collection { entries, .. } => {
for (k, v) in entries {
fwd(k);
fwd(v);
}
}
Cell::Promise(p) => {
let mut s = p.borrow_mut();
fwd(&mut s.value);
for r in &mut s.reactions {
fwd(&mut r.on_fulfilled);
fwd(&mut r.on_rejected);
r.result = forward(r.result);
}
}
Cell::TypedArray {
buffer,
array_buffer,
..
} => {
*buffer = forward(*buffer);
*array_buffer = forward(*array_buffer);
}
Cell::BoundNative { target, .. } => *target = forward(*target),
Cell::Proxy {
target, handler, ..
} => {
*target = forward(*target);
*handler = forward(*handler);
}
Cell::Str(_)
| Cell::Native(_)
| Cell::Date(_)
| Cell::RegExp { .. }
| Cell::Symbol { .. }
| Cell::BigInt(_)
| Cell::Bytes(_) => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::heap::Heap;
use crate::shape::Shape;
use alloc::rc::Rc;
#[test]
fn accessors_select_the_right_kind() {
let obj = Cell::Object(Object::new(Shape::root()));
assert!(obj.as_object().is_some());
assert!(obj.as_str().is_none());
assert!(obj.as_array().is_none());
assert_eq!(obj.type_of(), "object");
let s = Cell::Str(Rope::from("hi"));
assert_eq!(s.as_str().map(Rope::materialize).as_deref(), Some("hi"));
assert_eq!(s.type_of(), "string");
let a = Cell::Array(alloc::vec![NanBox::number(1.0), NanBox::number(2.0)]);
assert_eq!(a.as_array().map(<[_]>::len), Some(2));
assert_eq!(a.type_of(), "object");
}
#[test]
fn byte_store_owned_and_external() {
let mut s = ByteStore::Owned(alloc::vec![1u8, 2, 3]);
assert_eq!(s.len(), 3);
assert_eq!(s.as_slice(), &[1, 2, 3]);
s.as_mut_slice()[1] = 9;
assert_eq!(s.as_slice(), &[1, 9, 3]);
let mut region = alloc::boxed::Box::new([10u8, 20, 30]);
let ptr = region.as_mut_ptr();
#[allow(unsafe_code)]
let mut ext = unsafe { ByteStore::external(ptr, 3, None) };
assert_eq!(ext.as_slice(), &[10, 20, 30]);
ext.as_mut_slice()[0] = 99;
assert_eq!(region[0], 99); }
#[test]
fn external_free_hook_runs_once_on_drop() {
use core::sync::atomic::{AtomicUsize, Ordering};
static FREED: AtomicUsize = AtomicUsize::new(0);
#[allow(unsafe_code)]
unsafe fn record_free(_p: *mut u8, _l: usize) {
FREED.fetch_add(1, Ordering::SeqCst);
}
let mut region = alloc::boxed::Box::new([0u8; 4]);
let ptr = region.as_mut_ptr();
#[allow(unsafe_code)]
let store = unsafe { ByteStore::external(ptr, 4, Some(record_free)) };
let cell = Cell::Bytes(store);
assert_eq!(FREED.load(Ordering::SeqCst), 0);
drop(cell); assert_eq!(FREED.load(Ordering::SeqCst), 1);
}
#[test]
fn gc_traces_arrays_and_objects_uniformly() {
let mut heap: Heap<Cell> = Heap::new();
let root_shape = Shape::root();
let leaf = heap.alloc(Cell::Object(Object::new(Rc::clone(&root_shape))));
let mut mid = Object::new(Rc::clone(&root_shape));
mid.set("leaf", NanBox::handle(leaf.to_raw()));
let mid_h = heap.alloc(Cell::Object(mid));
let arr = heap.alloc(Cell::Array(alloc::vec![NanBox::handle(mid_h.to_raw())]));
let _garbage = heap.alloc(Cell::Str(Rope::from("unreferenced")));
assert_eq!(heap.len(), 4);
let stats = crate::gc::collect(&mut heap, &[arr]);
assert_eq!(stats.marked, 3); assert_eq!(stats.swept, 1); assert!(heap.is_live(arr) && heap.is_live(mid_h) && heap.is_live(leaf));
}
}