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`.
|
/// 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
|
||||||
.into_iter()
|
.closure_env
|
||||||
}
|
.as_deref()
|
||||||
// These kinds have no inner references in this PR.
|
.map(|slice| {
|
||||||
ObjectKind::String | ObjectKind::Bytes | ObjectKind::UserData | ObjectKind::Unknown => {
|
debug_assert_eq!(slice.len(), env_len, "closure env length must match encoded env_len");
|
||||||
Vec::new().into_iter()
|
&slice[..env_len]
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.flatten()
|
.flat_map(|slice| slice.iter())
|
||||||
|
.filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None });
|
||||||
|
return Box::new(it);
|
||||||
|
}
|
||||||
|
_ => 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.
|
/// 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); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user