This commit is contained in:
bQUARKz 2026-02-20 10:17:04 +00:00
parent e894ea1a8c
commit 3fba722b50
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
2 changed files with 93 additions and 37 deletions

View File

@ -11,9 +11,11 @@ pub struct StoredObject {
/// When present, `header.payload_len` must equal `array_elems.len() as u32`. /// When present, `header.payload_len` must equal `array_elems.len() as u32`.
pub array_elems: Option<Vec<Value>>, pub array_elems: Option<Vec<Value>>,
/// Optional captured environment for `ObjectKind::Closure`. /// Optional captured environment for `ObjectKind::Closure`.
/// `header.payload_len` stores the fixed-size metadata length (8 bytes): /// Invariants for closures:
/// [fn_id: u32][env_len: u32]. /// - `header.payload_len == 8` and `payload` bytes are `[fn_id: u32][env_len: u32]` (LE).
/// The actual env slots are stored here to remain GC-visible. /// - The actual `env_len` Value slots are stored here (not in `payload`) so
/// they stay directly GC-visible. The GC must traverse exactly `env_len`
/// entries from this slice, in order.
pub closure_env: Option<Vec<Value>>, pub closure_env: Option<Vec<Value>>,
} }
@ -94,40 +96,44 @@ impl Heap {
.map(|o| &mut o.header) .map(|o| &mut o.header)
} }
/// Internal: enumerate inner `HeapRef` children of an object. /// Internal: enumerate inner `HeapRef` children of an object without allocating.
fn children_of(&self, r: HeapRef) -> impl Iterator<Item = HeapRef> + '_ { /// Note: This helper is no longer used by GC mark; kept for potential diagnostics.
fn children_of(&self, r: HeapRef) -> Box<dyn Iterator<Item = HeapRef> + '_> {
let idx = r.0 as usize; let idx = r.0 as usize;
self.objects if let Some(Some(o)) = self.objects.get(idx) {
.get(idx) match o.header.kind {
.and_then(|slot| slot.as_ref())
.map(|o| match o.header.kind {
ObjectKind::Array => { ObjectKind::Array => {
// Traverse only Value::HeapRef inside the array. let it = o
o.array_elems .array_elems
.as_ref() .as_deref()
.into_iter()
.flat_map(|v| v.iter())
.filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None })
.collect::<Vec<_>>()
.into_iter() .into_iter()
.flat_map(|slice| slice.iter())
.filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None });
return Box::new(it);
} }
ObjectKind::Closure => { ObjectKind::Closure => {
// Traverse only Value::HeapRef inside the closure env. // Read env_len from payload; traverse exactly that many entries.
o.closure_env debug_assert_eq!(o.header.kind, ObjectKind::Closure);
.as_ref() debug_assert_eq!(o.payload.len(), 8, "closure payload metadata must be 8 bytes");
.into_iter() let mut nbytes = [0u8; 4];
.flat_map(|v| v.iter()) nbytes.copy_from_slice(&o.payload[4..8]);
.filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None }) let env_len = u32::from_le_bytes(nbytes) as usize;
.collect::<Vec<_>>() let it = o
.closure_env
.as_deref()
.map(|slice| {
debug_assert_eq!(slice.len(), env_len, "closure env length must match encoded env_len");
&slice[..env_len]
})
.into_iter() .into_iter()
.flat_map(|slice| slice.iter())
.filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None });
return Box::new(it);
} }
// These kinds have no inner references in this PR. _ => return Box::new(std::iter::empty()),
ObjectKind::String | ObjectKind::Bytes | ObjectKind::UserData | ObjectKind::Unknown => { }
Vec::new().into_iter() }
} Box::new(std::iter::empty())
})
.into_iter()
.flatten()
} }
/// Read the `fn_id` stored in a closure object. Returns None if kind mismatch or invalid ref. /// Read the `fn_id` stored in a closure object. Returns None if kind mismatch or invalid ref.
@ -136,6 +142,7 @@ impl Heap {
let slot = self.objects.get(idx)?.as_ref()?; let slot = self.objects.get(idx)?.as_ref()?;
if slot.header.kind != ObjectKind::Closure { return None; } if slot.header.kind != ObjectKind::Closure { return None; }
if slot.payload.len() < 8 { return None; } if slot.payload.len() < 8 { return None; }
debug_assert_eq!(slot.header.payload_len, 8);
let mut bytes = [0u8; 4]; let mut bytes = [0u8; 4];
bytes.copy_from_slice(&slot.payload[0..4]); bytes.copy_from_slice(&slot.payload[0..4]);
Some(u32::from_le_bytes(bytes)) Some(u32::from_le_bytes(bytes))
@ -146,6 +153,14 @@ impl Heap {
let idx = r.0 as usize; let idx = r.0 as usize;
let slot = self.objects.get(idx)?.as_ref()?; let slot = self.objects.get(idx)?.as_ref()?;
if slot.header.kind != ObjectKind::Closure { return None; } if slot.header.kind != ObjectKind::Closure { return None; }
if slot.payload.len() >= 8 {
let mut nbytes = [0u8; 4];
nbytes.copy_from_slice(&slot.payload[4..8]);
let env_len = u32::from_le_bytes(nbytes) as usize;
if let Some(env) = slot.closure_env.as_deref() {
debug_assert_eq!(env.len(), env_len);
}
}
slot.closure_env.as_deref() slot.closure_env.as_deref()
} }
@ -164,12 +179,40 @@ impl Heap {
// Set mark bit. // Set mark bit.
if let Some(h) = self.header_mut(r) { h.set_marked(true); } if let Some(h) = self.header_mut(r) { h.set_marked(true); }
// Push children. // Push children by scanning payload directly (no intermediate Vec allocs).
for child in self.children_of(r) { let idx = r.0 as usize;
if self.is_valid(child) { if let Some(Some(obj)) = self.objects.get(idx) {
// Check child's mark state cheaply to reduce stack churn. match obj.header.kind {
let marked = self.header(child).map(|h| h.is_marked()).unwrap_or(false); ObjectKind::Array => {
if !marked { stack.push(child); } if let Some(elems) = obj.array_elems.as_ref() {
for val in elems.iter() {
if let Value::HeapRef(child) = val {
if self.is_valid(*child) {
let marked = self.header(*child).map(|h| h.is_marked()).unwrap_or(false);
if !marked { stack.push(*child); }
}
}
}
}
}
ObjectKind::Closure => {
debug_assert_eq!(obj.payload.len(), 8, "closure payload must be 8 bytes");
let mut nbytes = [0u8; 4];
nbytes.copy_from_slice(&obj.payload[4..8]);
let env_len = u32::from_le_bytes(nbytes) as usize;
if let Some(env) = obj.closure_env.as_ref() {
debug_assert_eq!(env.len(), env_len, "closure env len must match encoded env_len");
for val in env[..env_len].iter() {
if let Value::HeapRef(child) = val {
if self.is_valid(*child) {
let marked = self.header(*child).map(|h| h.is_marked()).unwrap_or(false);
if !marked { stack.push(*child); }
}
}
}
}
}
_ => {}
} }
} }
} }

View File

@ -25,6 +25,13 @@
//! element count (arrays) or byte length (strings). Exact interpretation is //! element count (arrays) or byte length (strings). Exact interpretation is
//! defined by each object kind; the GC treats it as an opaque metadata field. //! defined by each object kind; the GC treats it as an opaque metadata field.
//! //!
//! Closure-specific note: for `ObjectKind::Closure`, `payload_len` is the
//! fixed size `8` and the payload layout is exactly two little-endian `u32`s:
//! `[fn_id][env_len]`. The captured environment values themselves are NOT in
//! the raw payload; they live in a separate GC-visible area managed by the
//! heap (see `Heap`), and the GC must traverse exactly `env_len` values from
//! that environment slice.
//!
//! Notes: //! Notes:
//! - The GC only relies on `flags` (mark bit) and `kind` to traverse/trace. //! - The GC only relies on `flags` (mark bit) and `kind` to traverse/trace.
//! Actual traversal logic will be implemented elsewhere in future PRs. //! Actual traversal logic will be implemented elsewhere in future PRs.
@ -52,7 +59,13 @@ pub enum ObjectKind {
/// Homogeneous array of VM values/handles. `payload_len` is element count. /// Homogeneous array of VM values/handles. `payload_len` is element count.
Array = 2, Array = 2,
/// Compiled closure/function value. Fixed-size payload (implementation-defined). /// Compiled closure/function value.
///
/// Invariants for `payload_len` and payload layout:
/// - `payload_len == 8` (fixed).
/// - payload bytes are `[fn_id: u32][env_len: u32]` (little-endian).
/// - The `env_len` captured values are stored out-of-line in the heap so
/// they remain directly visible to the GC during traversal.
Closure = 3, Closure = 3,
/// Byte buffer / blob. `payload_len` is the number of bytes. /// Byte buffer / blob. `payload_len` is the number of bytes.