From 3fba722b50d329e53c137de52d0d6607c48d9867 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 20 Feb 2026 10:17:04 +0000 Subject: [PATCH] pr6.7 --- crates/console/prometeu-vm/src/heap.rs | 115 ++++++++++++++++------- crates/console/prometeu-vm/src/object.rs | 15 ++- 2 files changed, 93 insertions(+), 37 deletions(-) diff --git a/crates/console/prometeu-vm/src/heap.rs b/crates/console/prometeu-vm/src/heap.rs index f4d2b7fb..ff3d675b 100644 --- a/crates/console/prometeu-vm/src/heap.rs +++ b/crates/console/prometeu-vm/src/heap.rs @@ -11,9 +11,11 @@ pub struct StoredObject { /// When present, `header.payload_len` must equal `array_elems.len() as u32`. pub array_elems: Option>, /// 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>, } @@ -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 + '_ { + /// 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 + '_> { 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::>() + 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::>() + // 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); } + } + } + } + } + } + _ => {} } } } diff --git a/crates/console/prometeu-vm/src/object.rs b/crates/console/prometeu-vm/src/object.rs index 3f8bf563..8c8c428e 100644 --- a/crates/console/prometeu-vm/src/object.rs +++ b/crates/console/prometeu-vm/src/object.rs @@ -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.