#![allow(unsafe_code)]
use std::cell::Cell;
use std::cell::RefCell;
use std::cell::UnsafeCell;
use std::ffi::c_void;
use std::hash::Hash;
use std::hash::Hasher;
use std::ops::Deref;
use std::pin::Pin;
use std::ptr::NonNull;
use std::sync::atomic::AtomicU32;
use std::sync::atomic::Ordering;
use hermes_atom_table::AtomBytes;
use hermes_atom_table::AtomTable;
use hermes_support::deque::Deque;
use crate::node::Node;
use crate::NodeId;
use crate::node_child::NodeList;
use crate::visitor::Visitor;
use hermes_support::HeapSize;
const FREE_ENTRY: u32 = 0;
#[inline]
unsafe fn container_of<Outer, Field>(field: *const Field, offset: usize) -> *const Outer {
unsafe { field.byte_sub(offset).cast::<Outer>() }
}
#[derive(Debug)]
struct StorageEntry<'ctx> {
ctx_id_markbit: Cell<u32>,
count: Cell<u32>,
inner: Node<'ctx>,
}
impl<'ctx> StorageEntry<'ctx> {
unsafe fn from_node<'a>(node: &'a Node<'a>) -> &'a StorageEntry<'a> {
let inner_offset = core::mem::offset_of!(StorageEntry, inner);
unsafe { &*container_of::<StorageEntry<'a>, Node<'a>>(node, inner_offset) }
}
#[inline]
fn set_markbit(&self, bit: bool) {
let id = self.ctx_id_markbit.get();
if bit {
self.ctx_id_markbit.set(id | 1 << 31);
} else {
self.ctx_id_markbit.set(id & !(1 << 31));
}
}
#[inline]
fn markbit(&self) -> bool {
(self.ctx_id_markbit.get() >> 31) != 0
}
fn is_free(&self) -> bool {
self.ctx_id_markbit.get() == FREE_ENTRY
}
}
#[derive(Debug)]
pub(crate) struct NodeListElement<'ctx> {
ctx_id_markbit: Cell<u32>,
pub inner: *const Node<'ctx>,
pub next: Cell<*const NodeListElement<'ctx>>,
}
impl<'ctx> NodeListElement<'ctx> {
#[inline]
fn set_markbit(&self, bit: bool) {
let id = self.ctx_id_markbit.get();
if bit {
self.ctx_id_markbit.set(id | 1 << 31);
} else {
self.ctx_id_markbit.set(id & !(1 << 31));
}
}
#[inline]
fn markbit(&self) -> bool {
(self.ctx_id_markbit.get() >> 31) != 0
}
fn is_free(&self) -> bool {
self.ctx_id_markbit.get() == FREE_ENTRY
}
}
pub(crate) fn list_elem_parts<'gc>(
ptr: *const NodeListElement<'gc>,
) -> (&'gc Node<'gc>, *const NodeListElement<'gc>) {
let elem = unsafe { &*ptr };
debug_assert!(!elem.inner.is_null(), "NodeList node must not be null");
(unsafe { &*elem.inner }, elem.next.get())
}
#[derive(Debug)]
struct NodeRcCounter {
ctx_id: u32,
count: Cell<usize>,
}
#[derive(Debug)]
pub struct Context<'ast> {
id: u32,
nodes: UnsafeCell<Deque<StorageEntry<'ast>>>,
free_nodes: UnsafeCell<Vec<NonNull<StorageEntry<'ast>>>>,
list_elements: UnsafeCell<Deque<NodeListElement<'ast>>>,
free_list_elements: UnsafeCell<Vec<NonNull<NodeListElement<'ast>>>>,
noderc_count: Pin<Box<NodeRcCounter>>,
pub atom_table: AtomTable,
markbit_marked: bool,
strict_mode: bool,
enable_eval: bool,
parse_flow: bool,
parse_flow_ambiguous: bool,
parse_flow_component_syntax: bool,
parse_flow_records: bool,
parse_flow_match: bool,
parse_ts: bool,
parse_jsx: bool,
pub warn_undefined: bool,
preemptive_function_compilation_threshold: u32,
next_node_id: Cell<u32>,
freed_node_ids: RefCell<Vec<NodeId>>,
}
impl Default for Context<'_> {
fn default() -> Self {
Self::new()
}
}
impl<'ast> Context<'ast> {
pub fn new() -> Self {
static NEXT_ID: AtomicU32 = AtomicU32::new(FREE_ENTRY + 1);
let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
Self {
id,
nodes: Default::default(),
free_nodes: Default::default(),
list_elements: Default::default(),
free_list_elements: Default::default(),
noderc_count: Pin::new(Box::new(NodeRcCounter {
ctx_id: id,
count: Cell::new(0),
})),
atom_table: Default::default(),
markbit_marked: true,
strict_mode: false,
enable_eval: true,
parse_flow: false,
parse_flow_ambiguous: false,
parse_flow_component_syntax: false,
parse_flow_records: false,
parse_flow_match: false,
parse_ts: false,
parse_jsx: false,
warn_undefined: false,
preemptive_function_compilation_threshold: 0,
next_node_id: Cell::new(1),
freed_node_ids: RefCell::new(Vec::new()),
}
}
pub fn lock<'ctx>(&'ctx mut self) -> GCLock<'ast, 'ctx> {
GCLock::new(self)
}
pub(crate) fn alloc<'s>(&'s self, n: Node<'_>) -> &'s Node<'s> {
let free = unsafe { &mut *self.free_nodes.get() };
let nodes: &mut Deque<StorageEntry<'ast>> = unsafe { &mut *self.nodes.get() };
let node = unsafe { std::mem::transmute::<Node<'_>, Node<'_>>(n) };
let entry: &StorageEntry<'ast> = if let Some(mut entry) = free.pop() {
let entry: &mut StorageEntry<'ast> = unsafe { entry.as_mut() };
debug_assert!(
entry.ctx_id_markbit.get() == FREE_ENTRY,
"Incorrect context ID"
);
debug_assert!(entry.count.get() == 0, "Freed entry has pointers to it");
entry.ctx_id_markbit.set(self.id);
entry.set_markbit(!self.markbit_marked);
entry.inner = node;
entry
} else {
let entry: &StorageEntry = nodes.push(StorageEntry {
ctx_id_markbit: Cell::new(self.id),
count: Cell::new(0),
inner: node,
});
entry.set_markbit(!self.markbit_marked);
entry
};
let id = self.next_node_id.get();
self.next_node_id.set(id.checked_add(1).expect("NodeId overflow"));
entry.inner.metadata().id.set(NodeId(id));
unsafe { std::mem::transmute(&entry.inner) }
}
pub(crate) fn append_list_element<'a>(
&'a self,
prev: Option<&'a NodeListElement<'a>>,
node: &'a Node<'a>,
) -> &'a NodeListElement<'a> {
let elements: &mut Deque<NodeListElement<'ast>> = unsafe { &mut *self.list_elements.get() };
let free = unsafe { &mut *self.free_list_elements.get() };
let node: &'ast Node<'ast> = unsafe { std::mem::transmute(node) };
let prev: Option<&'ast NodeListElement<'ast>> = unsafe { std::mem::transmute(prev) };
let entry = if let Some(mut entry) = free.pop() {
let entry: &mut NodeListElement<'ast> = unsafe { entry.as_mut() };
debug_assert!(
entry.ctx_id_markbit.get() == FREE_ENTRY,
"Incorrect context ID"
);
entry.ctx_id_markbit.set(self.id);
entry.set_markbit(!self.markbit_marked);
entry.inner = node;
entry.next.set(std::ptr::null());
if let Some(prev) = prev {
prev.next.set(entry as *const _);
}
entry
} else {
let entry = elements.push(NodeListElement {
ctx_id_markbit: Cell::new(self.id),
inner: node,
next: Cell::new(std::ptr::null()),
});
entry.set_markbit(!self.markbit_marked);
if let Some(prev) = prev {
prev.next.set(entry as *const _);
}
entry
};
debug_assert!(!entry.is_free(), "Entry must not be free");
unsafe { std::mem::transmute(entry) }
}
pub fn atom_table(&self) -> &AtomTable {
&self.atom_table
}
#[inline]
pub fn atom_bytes<V: Into<Vec<u8>> + AsRef<[u8]>>(&self, value: V) -> AtomBytes {
self.atom_table.atom_bytes(value)
}
#[inline]
pub fn bytes(&self, ident: AtomBytes) -> &[u8] {
self.atom_table.bytes(ident)
}
pub fn strict_mode(&self) -> bool {
self.strict_mode
}
pub fn enable_strict_mode(&mut self) {
self.strict_mode = true;
}
pub fn enable_eval(&self) -> bool {
self.enable_eval
}
pub fn set_enable_eval(&mut self, v: bool) {
self.enable_eval = v;
}
pub fn parse_flow(&self) -> bool {
self.parse_flow
}
pub fn set_parse_flow(&mut self, v: bool) {
self.parse_flow = v;
}
pub fn parse_flow_ambiguous(&self) -> bool {
self.parse_flow_ambiguous
}
pub fn set_parse_flow_ambiguous(&mut self, v: bool) {
self.parse_flow_ambiguous = v;
}
pub fn parse_flow_component_syntax(&self) -> bool {
self.parse_flow_component_syntax
}
pub fn set_parse_flow_component_syntax(&mut self, v: bool) {
self.parse_flow_component_syntax = v;
}
pub fn parse_flow_records(&self) -> bool {
self.parse_flow_records
}
pub fn set_parse_flow_records(&mut self, v: bool) {
self.parse_flow_records = v;
}
pub fn parse_flow_match(&self) -> bool {
self.parse_flow_match
}
pub fn set_parse_flow_match(&mut self, v: bool) {
self.parse_flow_match = v;
}
pub fn parse_ts(&self) -> bool {
self.parse_ts
}
pub fn set_parse_ts(&mut self, v: bool) {
self.parse_ts = v;
}
pub fn parse_jsx(&self) -> bool {
self.parse_jsx
}
pub fn set_parse_jsx(&mut self, v: bool) {
self.parse_jsx = v;
}
pub fn preemptive_function_compilation_threshold(&self) -> u32 {
self.preemptive_function_compilation_threshold
}
pub fn set_preemptive_function_compilation_threshold(&mut self, byte_count: u32) {
self.preemptive_function_compilation_threshold = byte_count;
}
pub fn gc(&mut self) {
let nodes = unsafe { &mut *self.nodes.get() };
let free_nodes = unsafe { &mut *self.free_nodes.get() };
let list_elements = unsafe { &mut *self.list_elements.get() };
let free_list_elements = unsafe { &mut *self.free_list_elements.get() };
{
let mut roots: Vec<&StorageEntry> = vec![];
for entry in nodes.iter() {
if entry.is_free() {
continue;
}
debug_assert!(
entry.markbit() != self.markbit_marked,
"Entry marked before start of GC: \
{:?}\nentry.markbit()={}\nmarkbit_marked={}",
&entry,
entry.markbit(),
self.markbit_marked,
);
if entry.count.get() > 0 {
roots.push(unsafe {
std::mem::transmute::<&StorageEntry<'_>, &StorageEntry<'_>>(entry)
});
}
}
struct Marker {
markbit_marked: bool,
}
impl<'gc> Visitor<'gc> for Marker {
fn visit_node(&mut self, node: &'gc Node<'gc>) {
let entry = unsafe { StorageEntry::from_node(node) };
if entry.markbit() == self.markbit_marked {
return;
}
entry.set_markbit(self.markbit_marked);
let mark = self.markbit_marked;
node.mark_lists(&mut |list: &NodeList<'gc>| {
let mut p = list.head;
while !p.is_null() {
let elem = unsafe { &*p };
elem.set_markbit(mark);
p = elem.next.get();
}
});
node.visit_children(self);
}
}
let mut marker = Marker {
markbit_marked: self.markbit_marked,
};
for root in roots {
marker.visit_node(&root.inner);
}
}
let mut freed_node_ids = self.freed_node_ids.borrow_mut();
for entry in nodes.iter_mut() {
if entry.is_free() {
continue;
}
if entry.count.get() > 0 {
continue;
}
if entry.markbit() == self.markbit_marked {
continue;
}
freed_node_ids.push(entry.inner.metadata().id.get());
entry.ctx_id_markbit.set(FREE_ENTRY);
free_nodes.push(unsafe { NonNull::new_unchecked(entry as *mut StorageEntry) });
}
for element in list_elements.iter_mut() {
if element.is_free() {
continue;
}
if element.markbit() == self.markbit_marked {
continue;
}
element.ctx_id_markbit.set(FREE_ENTRY);
free_list_elements
.push(unsafe { NonNull::new_unchecked(element as *mut NodeListElement) });
}
self.markbit_marked = !self.markbit_marked;
}
pub fn take_freed_node_ids(&mut self) -> Vec<NodeId> {
std::mem::take(&mut *self.freed_node_ids.borrow_mut())
}
pub fn num_nodes(&self) -> usize {
let nodes = unsafe { &*self.nodes.get() };
nodes.len()
}
pub fn num_list_elements(&self) -> usize {
let list_elements = unsafe { &*self.list_elements.get() };
list_elements.len()
}
pub fn num_free_nodes(&self) -> usize {
let free_nodes = unsafe { &*self.free_nodes.get() };
free_nodes.len()
}
pub fn storage_size(&self) -> usize {
let nodes = unsafe { &*self.nodes.get() };
let free_nodes = unsafe { &*self.free_nodes.get() };
let list_elements = unsafe { &*self.list_elements.get() };
let free_list_elements = unsafe { &*self.free_list_elements.get() };
let mut result = 0;
result += nodes.heap_size();
result += free_nodes.heap_size();
result += list_elements.heap_size();
result += free_list_elements.heap_size();
result
}
fn leak_noderc_targets<'s>(&'s mut self) -> &'s Deque<StorageEntry<'ast>> {
let nodes = std::mem::take(unsafe { &mut *self.nodes.get() });
let leaked_nodes: &'s Deque<StorageEntry<'ast>> = Box::leak(Box::new(nodes));
let fresh = Pin::new(Box::new(NodeRcCounter {
ctx_id: self.id,
count: Cell::new(0),
}));
std::mem::forget(std::mem::replace(&mut self.noderc_count, fresh));
leaked_nodes
}
}
impl HeapSize for Context<'_> {
fn heap_size(&self) -> usize {
let nodes = unsafe { &*self.nodes.get() };
let free_nodes = unsafe { &*self.free_nodes.get() };
let list_elements = unsafe { &*self.list_elements.get() };
let free_list_elements = unsafe { &*self.free_list_elements.get() };
let mut result = 0;
result += nodes.heap_size();
result += free_nodes.heap_size();
result += list_elements.heap_size();
result += free_list_elements.heap_size();
result += std::mem::size_of::<NodeRcCounter>();
result
}
}
impl Drop for Context<'_> {
fn drop(&mut self) {
if self.noderc_count.count.get() > 0 {
let leaked_nodes = self.leak_noderc_targets();
#[cfg(debug_assertions)]
{
for entry in leaked_nodes.iter() {
assert!(
entry.count.get() == 0,
"NodeRc must not outlive Context: {:#?}\n",
&entry.inner
);
}
}
#[cfg(not(debug_assertions))]
let _ = leaked_nodes;
panic!("NodeRc must not outlive Context");
}
}
}
thread_local! {
static GCLOCK_IN_USE: Cell<bool> = const { Cell::new(false) };
}
pub struct GCLock<'ast, 'ctx> {
ctx: &'ctx mut Context<'ast>,
}
impl Drop for GCLock<'_, '_> {
fn drop(&mut self) {
GCLOCK_IN_USE.with(|flag| {
flag.set(false);
});
}
}
impl<'ast, 'ctx> GCLock<'ast, 'ctx> {
pub fn new(ctx: &'ctx mut Context<'ast>) -> Self {
GCLOCK_IN_USE.with(|flag| {
if flag.get() {
panic!("Attempt to create multiple GCLocks in a single thread");
}
flag.set(true);
});
GCLock { ctx }
}
#[inline]
pub fn alloc<'s>(&'s self, n: Node<'s>) -> &'s Node<'s> {
self.ctx.alloc(n)
}
#[inline]
pub(crate) fn append_list_element<'s>(
&'s self,
prev: Option<&'s NodeListElement<'s>>,
n: &'s Node<'s>,
) -> &'s NodeListElement<'s> {
self.ctx.append_list_element(prev, n)
}
pub fn ctx(&self) -> &Context<'ast> {
self.ctx
}
#[inline]
pub fn atom_bytes<V: Into<Vec<u8>> + AsRef<[u8]>>(&self, value: V) -> AtomBytes {
self.ctx.atom_bytes(value)
}
#[inline]
pub fn bytes(&self, ident: AtomBytes) -> &[u8] {
self.ctx.bytes(ident)
}
}
pub struct AllocationScope<'gcl, 'ast, 'ctx> {
lock: &'gcl GCLock<'ast, 'ctx>,
nodes_watermark: usize,
list_elements_watermark: usize,
}
impl Drop for AllocationScope<'_, '_, '_> {
fn drop(&mut self) {
let ctx: &Context<'_> = self.lock.ctx;
let nodes = unsafe { &mut *ctx.nodes.get() };
#[cfg(debug_assertions)]
for entry in nodes.iter_from(self.nodes_watermark) {
debug_assert!(
entry.count.get() == 0,
"NodeRc points into a truncated AllocationScope suffix"
);
debug_assert!(!entry.is_free(), "free entry in scope suffix");
}
let mut freed_node_ids = ctx.freed_node_ids.borrow_mut();
for entry in nodes.iter_from(self.nodes_watermark) {
freed_node_ids.push(entry.inner.metadata().id.get());
}
nodes.truncate(self.nodes_watermark);
let list_elements = unsafe { &mut *ctx.list_elements.get() };
list_elements.truncate(self.list_elements_watermark);
}
}
impl<'ast, 'ctx> GCLock<'ast, 'ctx> {
pub unsafe fn alloc_scope<'s>(&'s self) -> AllocationScope<'s, 'ast, 'ctx> {
let nodes = unsafe { &*self.ctx.nodes.get() };
let list_elements = unsafe { &*self.ctx.list_elements.get() };
AllocationScope {
lock: self,
nodes_watermark: nodes.len(),
list_elements_watermark: list_elements.len(),
}
}
}
#[derive(Debug, Copy, Clone)]
pub struct NodePtr<'gc>(pub &'gc Node<'gc>);
impl<'gc> NodePtr<'gc> {
pub fn from_node(node: &'gc Node<'gc>) -> Self {
Self(node)
}
}
impl<'gc> PartialEq for NodePtr<'gc> {
fn eq(&self, other: &Self) -> bool {
std::ptr::eq(self.0, other.0)
}
}
impl Eq for NodePtr<'_> {}
impl Hash for NodePtr<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
(self.0 as *const Node).hash(state)
}
}
impl<'gc> Deref for NodePtr<'gc> {
type Target = Node<'gc>;
fn deref(&self) -> &'gc Self::Target {
self.0
}
}
impl<'gc> AsRef<Node<'gc>> for NodePtr<'gc> {
fn as_ref(&self) -> &'gc Node<'gc> {
self.0
}
}
impl<'gc> From<&'gc Node<'gc>> for NodePtr<'gc> {
fn from(node: &'gc Node<'gc>) -> Self {
NodePtr(node)
}
}
#[derive(Debug, Eq)]
pub struct NodeRc {
counter: NonNull<NodeRcCounter>,
entry: NonNull<c_void>,
}
impl Hash for NodeRc {
fn hash<H: Hasher>(&self, state: &mut H) {
self.entry.hash(state)
}
}
impl PartialEq for NodeRc {
fn eq(&self, other: &Self) -> bool {
self.entry == other.entry
}
}
impl Drop for NodeRc {
fn drop(&mut self) {
let entry = unsafe { self.entry().as_mut() };
let c = entry.count.get();
debug_assert!(c > 0);
entry.count.set(c - 1);
let noderc_count = unsafe { self.counter.as_mut() };
let c = noderc_count.count.get();
debug_assert!(c > 0);
noderc_count.count.set(c - 1);
}
}
impl Clone for NodeRc {
fn clone(&self) -> Self {
let mut cloned = NodeRc { ..*self };
let entry = unsafe { cloned.entry().as_mut() };
let c = entry.count.get();
entry.count.set(c + 1);
let noderc_count = unsafe { cloned.counter.as_mut() };
let c = noderc_count.count.get();
noderc_count.count.set(c + 1);
cloned
}
}
impl NodeRc {
pub fn from_node<'gc>(gc: &'gc GCLock, node: &'gc Node<'gc>) -> NodeRc {
unsafe { Self::from_entry(gc, StorageEntry::from_node(node)) }
}
pub fn node<'gc>(&'_ self, gc: &'gc GCLock<'_, '_>) -> &'gc Node<'_> {
unsafe {
assert_eq!(
self.counter.as_ref().ctx_id,
gc.ctx.id,
"Attempt to derefence NodeRc allocated context {} in context {}",
self.counter.as_ref().ctx_id,
gc.ctx.id
);
&self.entry().as_ref().inner
}
}
unsafe fn entry(&self) -> NonNull<StorageEntry<'_>> {
let outer = self.entry.as_ptr() as *mut StorageEntry;
NonNull::new_unchecked(outer)
}
unsafe fn from_entry(gc: &GCLock, entry: &StorageEntry<'_>) -> NodeRc {
let c = entry.count.get();
entry.count.set(c + 1);
let c = gc.ctx.noderc_count.count.get();
gc.ctx.noderc_count.count.set(c + 1);
NodeRc {
counter: NonNull::new_unchecked(gc.ctx.noderc_count.as_ref().get_ref()
as *const NodeRcCounter
as *mut NodeRcCounter),
entry: NonNull::new_unchecked(entry as *const StorageEntry as *mut c_void),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::node::*;
use crate::node_child::NodeMetadata;
use std::cell::Cell;
use std::cell::RefCell;
use std::panic::AssertUnwindSafe;
fn dummy_range() -> hermes_support::location::SMRange {
let l = hermes_support::location::SMLoc {
source: hermes_support::location::SourceId::from_index(0),
offset: 0,
};
hermes_support::location::SMRange { start: l, end: l }
}
fn num<'gc>(gc: &'gc GCLock, v: f64) -> &'gc Node<'gc> {
gc.alloc(Node::NumericLiteral(NumericLiteral {
metadata: NodeMetadata::new(dummy_range()),
value: Cell::new(v),
}))
}
#[test]
fn alloc_and_deep_match() {
let mut ctx = Context::new();
let gc = GCLock::new(&mut ctx);
let l = num(&gc, 1.0);
let r = num(&gc, 2.0);
let op = gc.atom_bytes("+".as_bytes());
let bin = gc.alloc(Node::BinaryExpression(BinaryExpression {
metadata: NodeMetadata::new(dummy_range()),
left: l,
right: r,
operator: Cell::new(op),
}));
if let Node::BinaryExpression(b) = bin {
assert!(matches!(b.left, Node::NumericLiteral(n) if n.value.get() == 1.0));
} else {
panic!()
}
}
#[test]
fn cell_mutation_in_place() {
let mut ctx = Context::new();
let gc = GCLock::new(&mut ctx);
let n = num(&gc, 3.0);
if let Node::NumericLiteral(x) = n {
x.value.set(9.0);
}
assert!(matches!(n, Node::NumericLiteral(x) if x.value.get() == 9.0));
}
#[test]
#[should_panic(expected = "multiple GCLocks")]
fn single_gclock_per_thread() {
let mut a = Context::new();
let mut b = Context::new();
let _g1 = GCLock::new(&mut a);
let _g2 = GCLock::new(&mut b); }
#[test]
fn from_iter_roundtrip() {
let mut ctx = Context::new();
let gc = GCLock::new(&mut ctx);
let empty = NodeList::empty();
assert_eq!(empty.iter().count(), 0);
let a = num(&gc, 1.0);
let b = num(&gc, 2.0);
let c = num(&gc, 3.0);
let list = NodeList::from_iter(&gc, [a, b, c]);
assert_eq!(list.iter().count(), 3);
let values: Vec<f64> = list
.iter()
.map(|n| {
if let Node::NumericLiteral(nl) = n {
nl.value.get()
} else {
panic!("expected NumericLiteral")
}
})
.collect();
assert_eq!(values, vec![1.0, 2.0, 3.0]);
}
#[test]
fn noderc_roundtrip() {
let mut ctx = Context::new();
let rc = {
let gc = GCLock::new(&mut ctx);
let n = num(&gc, 42.0);
NodeRc::from_node(&gc, n)
};
let gc2 = GCLock::new(&mut ctx);
let node = rc.node(&gc2);
assert!(matches!(node, Node::NumericLiteral(nl) if nl.value.get() == 42.0));
drop(rc);
}
#[test]
fn storage_entry_recovery_matches_allocation() {
let mut ctx = Context::new();
let rc = {
let gc = GCLock::new(&mut ctx);
let n = num(&gc, 7.0);
let nodes = unsafe { &*gc.ctx().nodes.get() };
let allocated = nodes.iter().last().expect("one entry") as *const StorageEntry as usize;
let entry = unsafe { StorageEntry::from_node(n) };
assert_eq!(
entry as *const StorageEntry as usize, allocated,
"StorageEntry::from_node must recover the allocated entry"
);
assert!(
std::ptr::eq(&entry.inner, n),
"recovered entry holds the node"
);
assert_eq!(entry.ctx_id_markbit.get() & !(1 << 31), gc.ctx().id);
let rc = NodeRc::from_node(&gc, n);
assert_eq!(
rc.entry.as_ptr() as usize,
allocated,
"NodeRc::from_node must point at the allocated entry"
);
assert_eq!(entry.count.get(), 1, "the NodeRc took the entry's refcount");
rc
};
let gc2 = GCLock::new(&mut ctx);
assert!(matches!(rc.node(&gc2), Node::NumericLiteral(n) if n.value.get() == 7.0));
drop(rc);
}
#[test]
fn container_of_is_byte_stride() {
#[repr(C)]
struct Outer {
ctx_id_markbit: Cell<u32>,
count: Cell<u32>,
inner: [u64; 4],
}
let outer = Outer {
ctx_id_markbit: Cell::new(1),
count: Cell::new(0),
inner: [7; 4],
};
let offset = core::mem::offset_of!(Outer, inner);
assert_ne!(offset, 0, "the stand-in must exercise a non-zero offset");
let recovered = unsafe { container_of::<Outer, [u64; 4]>(&outer.inner, offset) };
assert_eq!(
recovered as usize, &outer as *const Outer as usize,
"container_of must step back in bytes, not in units of the field type"
);
}
fn orphan_noderc(escaped: &RefCell<Option<NodeRc>>) {
let mut ctx = Context::new();
{
let gc = GCLock::new(&mut ctx);
*escaped.borrow_mut() = Some(NodeRc::from_node(&gc, num(&gc, 5.0)));
}
drop(ctx); }
#[test]
#[should_panic(expected = "NodeRc must not outlive Context")]
fn noderc_outliving_context_panics() {
let escaped = RefCell::new(None);
orphan_noderc(&escaped);
}
#[test]
fn noderc_outliving_context_is_survivable() {
let escaped: RefCell<Option<NodeRc>> = RefCell::new(None);
let result = std::panic::catch_unwind(AssertUnwindSafe(|| {
orphan_noderc(&escaped);
}));
assert!(result.is_err(), "the guard must still panic");
let rc = escaped
.borrow_mut()
.take()
.expect("handle outlived the panic");
let cloned = rc.clone(); drop(cloned);
drop(rc);
let mut v: Vec<Vec<u64>> = (0..512u64).map(|i| vec![i; 64]).collect();
v.truncate(0);
drop(v);
}
#[test]
fn alloc_scope_truncates_nodes_and_lists() {
let mut ctx = Context::new();
let gc = GCLock::new(&mut ctx);
let base_nodes = gc.ctx().num_nodes();
let base_elems = gc.ctx().num_list_elements();
let survivor = num(&gc, 99.0);
{
let _scope = unsafe { gc.alloc_scope() };
for _ in 0..100 {
num(&gc, 0.0);
}
let a = num(&gc, 1.0);
let _list = NodeList::from_iter(&gc, [a]);
assert_eq!(gc.ctx().num_nodes(), base_nodes + 102);
assert!(gc.ctx().num_list_elements() > base_elems);
}
assert_eq!(gc.ctx().num_nodes(), base_nodes + 1);
assert_eq!(gc.ctx().num_list_elements(), base_elems);
assert!(matches!(survivor, Node::NumericLiteral(n) if n.value.get() == 99.0));
}
#[test]
fn alloc_scope_nests() {
let mut ctx = Context::new();
let gc = GCLock::new(&mut ctx);
let base = gc.ctx().num_nodes();
{
let _outer = unsafe { gc.alloc_scope() };
num(&gc, 1.0); {
let _inner = unsafe { gc.alloc_scope() };
for _ in 0..50 {
num(&gc, 0.0);
}
}
assert_eq!(gc.ctx().num_nodes(), base + 1, "inner scope reclaimed");
for _ in 0..10 {
num(&gc, 0.0);
}
assert_eq!(gc.ctx().num_nodes(), base + 11);
}
assert_eq!(gc.ctx().num_nodes(), base);
}
}