pr6.7
This commit is contained in:
parent
e894ea1a8c
commit
3fba722b50
@ -11,9 +11,11 @@ pub struct StoredObject {
|
||||
/// When present, `header.payload_len` must equal `array_elems.len() as u32`.
|
||||
pub array_elems: Option<Vec<Value>>,
|
||||
/// Optional captured environment for `ObjectKind::Closure`.
|
||||
/// `header.payload_len` stores the fixed-size metadata length (8 bytes):
|
||||
/// [fn_id: u32][env_len: u32].
|
||||
/// The actual env slots are stored here to remain GC-visible.
|
||||
/// Invariants for closures:
|
||||
/// - `header.payload_len == 8` and `payload` bytes are `[fn_id: u32][env_len: u32]` (LE).
|
||||
/// - 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>>,
|
||||
}
|
||||
|
||||
@ -94,40 +96,44 @@ impl Heap {
|
||||
.map(|o| &mut o.header)
|
||||
}
|
||||
|
||||
/// Internal: enumerate inner `HeapRef` children of an object.
|
||||
fn children_of(&self, r: HeapRef) -> impl Iterator<Item = HeapRef> + '_ {
|
||||
/// Internal: enumerate inner `HeapRef` children of an object without allocating.
|
||||
/// 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;
|
||||
self.objects
|
||||
.get(idx)
|
||||
.and_then(|slot| slot.as_ref())
|
||||
.map(|o| match o.header.kind {
|
||||
if let Some(Some(o)) = self.objects.get(idx) {
|
||||
match o.header.kind {
|
||||
ObjectKind::Array => {
|
||||
// Traverse only Value::HeapRef inside the array.
|
||||
o.array_elems
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.flat_map(|v| v.iter())
|
||||
.filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None })
|
||||
.collect::<Vec<_>>()
|
||||
let it = o
|
||||
.array_elems
|
||||
.as_deref()
|
||||
.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 => {
|
||||
// Traverse only Value::HeapRef inside the closure env.
|
||||
o.closure_env
|
||||
.as_ref()
|
||||
.into_iter()
|
||||
.flat_map(|v| v.iter())
|
||||
.filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None })
|
||||
.collect::<Vec<_>>()
|
||||
// Read env_len from payload; traverse exactly that many entries.
|
||||
debug_assert_eq!(o.header.kind, ObjectKind::Closure);
|
||||
debug_assert_eq!(o.payload.len(), 8, "closure payload metadata must be 8 bytes");
|
||||
let mut nbytes = [0u8; 4];
|
||||
nbytes.copy_from_slice(&o.payload[4..8]);
|
||||
let env_len = u32::from_le_bytes(nbytes) as usize;
|
||||
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()
|
||||
.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.
|
||||
ObjectKind::String | ObjectKind::Bytes | ObjectKind::UserData | ObjectKind::Unknown => {
|
||||
Vec::new().into_iter()
|
||||
}
|
||||
})
|
||||
.into_iter()
|
||||
.flatten()
|
||||
_ => return Box::new(std::iter::empty()),
|
||||
}
|
||||
}
|
||||
Box::new(std::iter::empty())
|
||||
}
|
||||
|
||||
/// 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()?;
|
||||
if slot.header.kind != ObjectKind::Closure { return None; }
|
||||
if slot.payload.len() < 8 { return None; }
|
||||
debug_assert_eq!(slot.header.payload_len, 8);
|
||||
let mut bytes = [0u8; 4];
|
||||
bytes.copy_from_slice(&slot.payload[0..4]);
|
||||
Some(u32::from_le_bytes(bytes))
|
||||
@ -146,6 +153,14 @@ impl Heap {
|
||||
let idx = r.0 as usize;
|
||||
let slot = self.objects.get(idx)?.as_ref()?;
|
||||
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()
|
||||
}
|
||||
|
||||
@ -164,12 +179,40 @@ impl Heap {
|
||||
// Set mark bit.
|
||||
if let Some(h) = self.header_mut(r) { h.set_marked(true); }
|
||||
|
||||
// Push children.
|
||||
for child in self.children_of(r) {
|
||||
if self.is_valid(child) {
|
||||
// Check child's mark state cheaply to reduce stack churn.
|
||||
let marked = self.header(child).map(|h| h.is_marked()).unwrap_or(false);
|
||||
if !marked { stack.push(child); }
|
||||
// Push children by scanning payload directly (no intermediate Vec allocs).
|
||||
let idx = r.0 as usize;
|
||||
if let Some(Some(obj)) = self.objects.get(idx) {
|
||||
match obj.header.kind {
|
||||
ObjectKind::Array => {
|
||||
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); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -25,6 +25,13 @@
|
||||
//! element count (arrays) or byte length (strings). Exact interpretation is
|
||||
//! 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:
|
||||
//! - The GC only relies on `flags` (mark bit) and `kind` to traverse/trace.
|
||||
//! 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.
|
||||
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,
|
||||
|
||||
/// Byte buffer / blob. `payload_len` is the number of bytes.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user