pr3.5
This commit is contained in:
parent
13f025e448
commit
966f0f9a8f
@ -1,11 +1,15 @@
|
|||||||
use crate::{ObjectHeader, ObjectKind};
|
use crate::{ObjectHeader, ObjectKind};
|
||||||
use prometeu_bytecode::HeapRef;
|
use prometeu_bytecode::{HeapRef, Value};
|
||||||
|
|
||||||
/// Internal stored object: header plus opaque payload bytes.
|
/// Internal stored object: header plus opaque payload bytes.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct StoredObject {
|
pub struct StoredObject {
|
||||||
pub header: ObjectHeader,
|
pub header: ObjectHeader,
|
||||||
|
/// Raw payload bytes for byte-oriented kinds (e.g., String, Bytes).
|
||||||
pub payload: Vec<u8>,
|
pub payload: Vec<u8>,
|
||||||
|
/// Optional typed elements for `ObjectKind::Array`.
|
||||||
|
/// When present, `header.payload_len` must equal `array_elems.len() as u32`.
|
||||||
|
pub array_elems: Option<Vec<Value>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simple vector-backed heap. No GC or compaction.
|
/// Simple vector-backed heap. No GC or compaction.
|
||||||
@ -21,7 +25,17 @@ impl Heap {
|
|||||||
/// Returns an opaque `HeapRef` handle.
|
/// Returns an opaque `HeapRef` handle.
|
||||||
pub fn allocate_object(&mut self, kind: ObjectKind, payload: &[u8]) -> HeapRef {
|
pub fn allocate_object(&mut self, kind: ObjectKind, payload: &[u8]) -> HeapRef {
|
||||||
let header = ObjectHeader::new(kind, payload.len() as u32);
|
let header = ObjectHeader::new(kind, payload.len() as u32);
|
||||||
let obj = StoredObject { header, payload: payload.to_vec() };
|
let obj = StoredObject { header, payload: payload.to_vec(), array_elems: None };
|
||||||
|
let idx = self.objects.len();
|
||||||
|
self.objects.push(obj);
|
||||||
|
HeapRef(idx as u32)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Allocate a new `Array` object with the given `Value` elements.
|
||||||
|
/// `payload_len` stores the element count; raw `payload` bytes are empty.
|
||||||
|
pub fn allocate_array(&mut self, elements: Vec<Value>) -> HeapRef {
|
||||||
|
let header = ObjectHeader::new(ObjectKind::Array, elements.len() as u32);
|
||||||
|
let obj = StoredObject { header, payload: Vec::new(), array_elems: Some(elements) };
|
||||||
let idx = self.objects.len();
|
let idx = self.objects.len();
|
||||||
self.objects.push(obj);
|
self.objects.push(obj);
|
||||||
HeapRef(idx as u32)
|
HeapRef(idx as u32)
|
||||||
@ -37,6 +51,59 @@ impl Heap {
|
|||||||
self.objects.get(r.0 as usize).map(|o| &o.header)
|
self.objects.get(r.0 as usize).map(|o| &o.header)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Internal: get mutable access to an object's header by handle.
|
||||||
|
fn header_mut(&mut self, r: HeapRef) -> Option<&mut ObjectHeader> {
|
||||||
|
self.objects.get_mut(r.0 as usize).map(|o| &mut o.header)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Internal: enumerate inner `HeapRef` children of an object.
|
||||||
|
fn children_of(&self, r: HeapRef) -> impl Iterator<Item = HeapRef> + '_ {
|
||||||
|
let idx = r.0 as usize;
|
||||||
|
self.objects.get(idx).into_iter().flat_map(|o| {
|
||||||
|
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<_>>()
|
||||||
|
}
|
||||||
|
// These kinds have no inner references in this PR.
|
||||||
|
ObjectKind::String | ObjectKind::Bytes | ObjectKind::Closure | ObjectKind::UserData | ObjectKind::Unknown => {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mark phase: starting from the given roots, traverse and set mark bits
|
||||||
|
/// on all reachable objects. Uses an explicit stack to avoid recursion.
|
||||||
|
pub fn mark_from_roots<I: IntoIterator<Item = HeapRef>>(&mut self, roots: I) {
|
||||||
|
let mut stack: Vec<HeapRef> = roots.into_iter().collect();
|
||||||
|
|
||||||
|
while let Some(r) = stack.pop() {
|
||||||
|
if !self.is_valid(r) { continue; }
|
||||||
|
|
||||||
|
// If already marked, skip.
|
||||||
|
let already_marked = self.header(r).map(|h| h.is_marked()).unwrap_or(false);
|
||||||
|
if already_marked { continue; }
|
||||||
|
|
||||||
|
// 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); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Current number of allocated objects.
|
/// Current number of allocated objects.
|
||||||
pub fn len(&self) -> usize { self.objects.len() }
|
pub fn len(&self) -> usize { self.objects.len() }
|
||||||
pub fn is_empty(&self) -> bool { self.objects.is_empty() }
|
pub fn is_empty(&self) -> bool { self.objects.is_empty() }
|
||||||
@ -52,7 +119,7 @@ mod tests {
|
|||||||
|
|
||||||
let r1 = heap.allocate_object(ObjectKind::String, b"hello");
|
let r1 = heap.allocate_object(ObjectKind::String, b"hello");
|
||||||
let r2 = heap.allocate_object(ObjectKind::Bytes, &[1, 2, 3, 4]);
|
let r2 = heap.allocate_object(ObjectKind::Bytes, &[1, 2, 3, 4]);
|
||||||
let r3 = heap.allocate_object(ObjectKind::Array, &[]);
|
let r3 = heap.allocate_array(vec![]);
|
||||||
|
|
||||||
assert!(heap.is_valid(r1));
|
assert!(heap.is_valid(r1));
|
||||||
assert!(heap.is_valid(r2));
|
assert!(heap.is_valid(r2));
|
||||||
@ -71,4 +138,70 @@ mod tests {
|
|||||||
assert_eq!(h3.kind, ObjectKind::Array);
|
assert_eq!(h3.kind, ObjectKind::Array);
|
||||||
assert_eq!(h3.payload_len, 0);
|
assert_eq!(h3.payload_len, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mark_reachable_through_array() {
|
||||||
|
let mut heap = Heap::new();
|
||||||
|
|
||||||
|
// Target object B (unreferenced yet)
|
||||||
|
let b = heap.allocate_object(ObjectKind::Bytes, &[9, 9, 9]);
|
||||||
|
// Array A that contains a reference to B among other primitives
|
||||||
|
let a = heap.allocate_array(vec![
|
||||||
|
Value::Int32(1),
|
||||||
|
Value::HeapRef(b),
|
||||||
|
Value::Boolean(false),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Mark starting from root A
|
||||||
|
heap.mark_from_roots([a]);
|
||||||
|
|
||||||
|
// Both A and B must be marked; random other objects are not allocated
|
||||||
|
assert!(heap.header(a).unwrap().is_marked());
|
||||||
|
assert!(heap.header(b).unwrap().is_marked());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mark_does_not_mark_unreachable() {
|
||||||
|
let mut heap = Heap::new();
|
||||||
|
|
||||||
|
let unreachable = heap.allocate_object(ObjectKind::String, b"orphan");
|
||||||
|
let root = heap.allocate_object(ObjectKind::Bytes, &[1, 2, 3]);
|
||||||
|
|
||||||
|
heap.mark_from_roots([root]);
|
||||||
|
|
||||||
|
assert!(heap.header(root).unwrap().is_marked());
|
||||||
|
assert!(!heap.header(unreachable).unwrap().is_marked());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn mark_handles_cycles() {
|
||||||
|
let mut heap = Heap::new();
|
||||||
|
|
||||||
|
// Create two arrays that reference each other: A -> B, B -> A
|
||||||
|
let a_placeholder = HeapRef(0); // temporary
|
||||||
|
let b_placeholder = HeapRef(0);
|
||||||
|
|
||||||
|
// Allocate empty arrays first to get handles
|
||||||
|
let a = heap.allocate_array(vec![]);
|
||||||
|
let b = heap.allocate_array(vec![]);
|
||||||
|
|
||||||
|
// Now mutate their internal vectors via re-allocation pattern:
|
||||||
|
// replace with arrays containing cross-references. Since our simple
|
||||||
|
// heap doesn't support in-place element edits via API, simulate by
|
||||||
|
// directly editing stored objects.
|
||||||
|
if let Some(obj) = heap.objects.get_mut(a.0 as usize) {
|
||||||
|
obj.array_elems = Some(vec![Value::HeapRef(b)]);
|
||||||
|
obj.header.payload_len = 1;
|
||||||
|
}
|
||||||
|
if let Some(obj) = heap.objects.get_mut(b.0 as usize) {
|
||||||
|
obj.array_elems = Some(vec![Value::HeapRef(a)]);
|
||||||
|
obj.header.payload_len = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark from A; should terminate and mark both.
|
||||||
|
heap.mark_from_roots([a]);
|
||||||
|
|
||||||
|
assert!(heap.header(a).unwrap().is_marked());
|
||||||
|
assert!(heap.header(b).unwrap().is_marked());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,54 +1,3 @@
|
|||||||
# PR-3.5 — Implement Mark Phase (Reachability Traversal)
|
|
||||||
|
|
||||||
### Briefing
|
|
||||||
|
|
||||||
This PR introduces the mark phase of a simple mark-sweep collector. It must traverse from roots and mark reachable objects.
|
|
||||||
|
|
||||||
### Target
|
|
||||||
|
|
||||||
* Implement reachability marking from root set.
|
|
||||||
* Set mark bits on visited objects.
|
|
||||||
|
|
||||||
### Work items
|
|
||||||
|
|
||||||
* Implement a `mark()` function in the heap.
|
|
||||||
* Traverse roots using the root iterator.
|
|
||||||
* For each `HeapRef`, mark the object.
|
|
||||||
* Recursively traverse references inside objects.
|
|
||||||
* Ensure no infinite loops on cycles.
|
|
||||||
|
|
||||||
### Acceptance checklist
|
|
||||||
|
|
||||||
* [ ] Mark phase visits all reachable objects.
|
|
||||||
* [ ] Cycles are handled safely.
|
|
||||||
* [ ] Unreachable objects remain unmarked.
|
|
||||||
* [ ] `cargo test` passes.
|
|
||||||
|
|
||||||
### Tests
|
|
||||||
|
|
||||||
* Add tests:
|
|
||||||
|
|
||||||
* Reachable object stays marked.
|
|
||||||
* Unreachable object remains unmarked.
|
|
||||||
* Cyclic references do not crash.
|
|
||||||
|
|
||||||
### Junie instructions
|
|
||||||
|
|
||||||
**You MAY:**
|
|
||||||
|
|
||||||
* Implement a simple DFS or stack-based marking.
|
|
||||||
|
|
||||||
**You MUST NOT:**
|
|
||||||
|
|
||||||
* Add generational, incremental, or parallel GC.
|
|
||||||
* Change object layout.
|
|
||||||
|
|
||||||
**If unclear:**
|
|
||||||
|
|
||||||
* Ask before deciding traversal structure.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
# PR-3.6 — Implement Sweep Phase (Reclaim Unmarked Objects)
|
# PR-3.6 — Implement Sweep Phase (Reclaim Unmarked Objects)
|
||||||
|
|
||||||
### Briefing
|
### Briefing
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user