diff --git a/crates/console/prometeu-vm/src/heap.rs b/crates/console/prometeu-vm/src/heap.rs new file mode 100644 index 00000000..e43dd4f5 --- /dev/null +++ b/crates/console/prometeu-vm/src/heap.rs @@ -0,0 +1,74 @@ +use crate::{ObjectHeader, ObjectKind}; +use prometeu_bytecode::HeapRef; + +/// Internal stored object: header plus opaque payload bytes. +#[derive(Debug, Clone)] +pub struct StoredObject { + pub header: ObjectHeader, + pub payload: Vec, +} + +/// Simple vector-backed heap. No GC or compaction. +#[derive(Debug, Default, Clone)] +pub struct Heap { + objects: Vec, +} + +impl Heap { + pub fn new() -> Self { Self { objects: Vec::new() } } + + /// Allocate a new object with the given kind and raw payload bytes. + /// Returns an opaque `HeapRef` handle. + pub fn allocate_object(&mut self, kind: ObjectKind, payload: &[u8]) -> HeapRef { + let header = ObjectHeader::new(kind, payload.len() as u32); + let obj = StoredObject { header, payload: payload.to_vec() }; + let idx = self.objects.len(); + self.objects.push(obj); + HeapRef(idx as u32) + } + + /// Returns true if this handle refers to an allocated object. + pub fn is_valid(&self, r: HeapRef) -> bool { + (r.0 as usize) < self.objects.len() + } + + /// Get immutable access to an object's header by handle. + pub fn header(&self, r: HeapRef) -> Option<&ObjectHeader> { + self.objects.get(r.0 as usize).map(|o| &o.header) + } + + /// Current number of allocated objects. + pub fn len(&self) -> usize { self.objects.len() } + pub fn is_empty(&self) -> bool { self.objects.is_empty() } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn basic_allocation_returns_valid_refs() { + let mut heap = Heap::new(); + + let r1 = heap.allocate_object(ObjectKind::String, b"hello"); + let r2 = heap.allocate_object(ObjectKind::Bytes, &[1, 2, 3, 4]); + let r3 = heap.allocate_object(ObjectKind::Array, &[]); + + assert!(heap.is_valid(r1)); + assert!(heap.is_valid(r2)); + assert!(heap.is_valid(r3)); + assert_eq!(heap.len(), 3); + + let h1 = heap.header(r1).unwrap(); + assert_eq!(h1.kind, ObjectKind::String); + assert_eq!(h1.payload_len, 5); + + let h2 = heap.header(r2).unwrap(); + assert_eq!(h2.kind, ObjectKind::Bytes); + assert_eq!(h2.payload_len, 4); + + let h3 = heap.header(r3).unwrap(); + assert_eq!(h3.kind, ObjectKind::Array); + assert_eq!(h3.payload_len, 0); + } +} diff --git a/crates/console/prometeu-vm/src/lib.rs b/crates/console/prometeu-vm/src/lib.rs index ecc5539a..4331ce4a 100644 --- a/crates/console/prometeu-vm/src/lib.rs +++ b/crates/console/prometeu-vm/src/lib.rs @@ -4,7 +4,9 @@ pub mod verifier; mod virtual_machine; pub mod vm_init_error; pub mod object; +pub mod heap; pub use prometeu_hal::{HostContext, HostReturn, NativeInterface, SyscallId}; pub use virtual_machine::{BudgetReport, LogicalFrameEndingReason, VirtualMachine}; pub use object::{object_flags, ObjectHeader, ObjectKind}; +pub use heap::{Heap, StoredObject}; diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index 4dcb6706..5e7f00d3 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -5,6 +5,7 @@ use crate::{HostContext, NativeInterface}; use prometeu_bytecode::isa::core::CoreOpCode as OpCode; use prometeu_bytecode::ProgramImage; use prometeu_bytecode::Value; +use crate::heap::Heap; use prometeu_bytecode::{ TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE, TrapInfo, @@ -83,7 +84,7 @@ pub struct VirtualMachine { /// The loaded executable (Bytecode + Constant Pool), that is the ROM translated. pub program: ProgramImage, /// Heap Memory: Dynamic allocation pool. - pub heap: Vec, + pub heap: Heap, /// Total virtual cycles consumed since the VM started. pub cycles: u64, /// Stop flag: true if a `HALT` opcode was encountered. @@ -114,7 +115,7 @@ impl VirtualMachine { None, std::collections::HashMap::new(), ), - heap: Vec::new(), + heap: Heap::new(), cycles: 0, halted: false, breakpoints: std::collections::HashSet::new(), @@ -135,7 +136,7 @@ impl VirtualMachine { self.operand_stack.clear(); self.call_stack.clear(); self.globals.clear(); - self.heap.clear(); + self.heap = Heap::new(); self.cycles = 0; self.halted = true; // execution is impossible until a successful load diff --git a/files/TODOs.md b/files/TODOs.md index 056b3646..db514f60 100644 --- a/files/TODOs.md +++ b/files/TODOs.md @@ -1,103 +1,3 @@ -# PR-3.3 — Implement Basic Heap Allocator (No GC Yet) - -### Briefing - -We need a simple heap allocator that can store objects and return handles. This PR introduces a minimal allocator without garbage collection. - -### Target - -* Create a heap structure that stores objects. -* Return `HeapRef` handles for allocated objects. - -### Work items - -* Implement a `Heap` struct. -* Store objects in a vector or arena-like container. -* Provide methods such as: - - * `allocate_object(kind, payload)`. - * Return a `HeapRef` handle. -* Integrate heap into VM state. - -### Acceptance checklist - -* [ ] Heap exists and can allocate objects. -* [ ] VM can hold a heap instance. -* [ ] Allocation returns valid `HeapRef`. -* [ ] `cargo test` passes. - -### Tests - -* Add a unit test allocating a few objects and verifying handles are valid. - -### Junie instructions - -**You MAY:** - -* Use a simple vector-backed heap. -* Implement minimal allocation logic. - -**You MUST NOT:** - -* Implement garbage collection here. -* Add compaction or generational strategies. - -**If unclear:** - -* Ask before choosing allocation layout. - ---- - -# PR-3.4 — Define GC Root Set (Stack, Frames, Globals) - -### Briefing - -The GC must know where roots are located. This PR defines the root set abstraction without running a full GC yet. - -### Target - -* Identify and enumerate all GC roots. -* Provide a mechanism to iterate them. - -### Work items - -* Define a root traversal interface or helper. -* Enumerate roots from: - - * Value stack. - * Call frames. - * Globals or constant pools if applicable. -* Provide a function like `visit_roots(visitor)`. - -### Acceptance checklist - -* [ ] Root set traversal exists. -* [ ] Stack and frames are included as roots. -* [ ] Code compiles. -* [ ] `cargo test` passes. - -### Tests - -* Add a test that inserts a `HeapRef` in the stack and confirms it is visited by root traversal. - -### Junie instructions - -**You MAY:** - -* Add root iteration helpers. -* Traverse stack and frames. - -**You MUST NOT:** - -* Implement marking logic yet. -* Change frame or stack architecture. - -**If unclear:** - -* Ask which structures must be roots. - ---- - # PR-3.5 — Implement Mark Phase (Reachability Traversal) ### Briefing