This commit is contained in:
bQUARKz 2026-02-20 10:33:08 +00:00
parent 3fba722b50
commit 49025b1055
Signed by: bquarkz
SSH Key Fingerprint: SHA256:Z7dgqoglWwoK6j6u4QC87OveEq74WOhFN+gitsxtkf8
4 changed files with 115 additions and 63 deletions

View File

@ -1,3 +1,4 @@
#[derive(Debug, Clone)]
pub struct CallFrame { pub struct CallFrame {
pub return_pc: u32, pub return_pc: u32,
pub stack_base: usize, pub stack_base: usize,

View File

@ -1,4 +1,5 @@
use crate::{ObjectHeader, ObjectKind}; use crate::{ObjectHeader, ObjectKind};
use crate::call_frame::CallFrame;
use prometeu_bytecode::{HeapRef, Value}; use prometeu_bytecode::{HeapRef, Value};
/// Internal stored object: header plus opaque payload bytes. /// Internal stored object: header plus opaque payload bytes.
@ -17,6 +18,27 @@ pub struct StoredObject {
/// they stay directly GC-visible. The GC must traverse exactly `env_len` /// they stay directly GC-visible. The GC must traverse exactly `env_len`
/// entries from this slice, in order. /// entries from this slice, in order.
pub closure_env: Option<Vec<Value>>, pub closure_env: Option<Vec<Value>>,
/// Optional coroutine data for `ObjectKind::Coroutine`.
pub coroutine: Option<CoroutineData>,
}
/// Execution state of a coroutine.
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
pub enum CoroutineState {
Ready,
Running,
Sleeping,
Finished,
Faulted,
}
/// Stored payload for coroutine objects.
#[derive(Debug, Clone)]
pub struct CoroutineData {
pub state: CoroutineState,
pub wake_tick: u64,
pub stack: Vec<Value>,
pub frames: Vec<CallFrame>,
} }
/// Simple vector-backed heap. No GC or compaction. /// Simple vector-backed heap. No GC or compaction.
@ -33,7 +55,7 @@ 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(), array_elems: None, closure_env: None }; let obj = StoredObject { header, payload: payload.to_vec(), array_elems: None, closure_env: None, coroutine: None };
let idx = self.objects.len(); let idx = self.objects.len();
// No free-list reuse in this PR: append and keep indices stable. // No free-list reuse in this PR: append and keep indices stable.
self.objects.push(Some(obj)); self.objects.push(Some(obj));
@ -44,7 +66,7 @@ impl Heap {
/// `payload_len` stores the element count; raw `payload` bytes are empty. /// `payload_len` stores the element count; raw `payload` bytes are empty.
pub fn allocate_array(&mut self, elements: Vec<Value>) -> HeapRef { pub fn allocate_array(&mut self, elements: Vec<Value>) -> HeapRef {
let header = ObjectHeader::new(ObjectKind::Array, elements.len() as u32); let header = ObjectHeader::new(ObjectKind::Array, elements.len() as u32);
let obj = StoredObject { header, payload: Vec::new(), array_elems: Some(elements), closure_env: None }; let obj = StoredObject { header, payload: Vec::new(), array_elems: Some(elements), closure_env: None, coroutine: None };
let idx = self.objects.len(); let idx = self.objects.len();
// No free-list reuse in this PR: append and keep indices stable. // No free-list reuse in this PR: append and keep indices stable.
self.objects.push(Some(obj)); self.objects.push(Some(obj));
@ -67,6 +89,29 @@ impl Heap {
payload, payload,
array_elems: None, array_elems: None,
closure_env: Some(env_values.to_vec()), closure_env: Some(env_values.to_vec()),
coroutine: None,
};
let idx = self.objects.len();
self.objects.push(Some(obj));
HeapRef(idx as u32)
}
/// Allocate a new `Coroutine` object with provided initial data.
/// `payload_len` is 0; stack and frames are stored out-of-line for GC visibility.
pub fn allocate_coroutine(
&mut self,
state: CoroutineState,
wake_tick: u64,
stack: Vec<Value>,
frames: Vec<CallFrame>,
) -> HeapRef {
let header = ObjectHeader::new(ObjectKind::Coroutine, 0);
let obj = StoredObject {
header,
payload: Vec::new(),
array_elems: None,
closure_env: None,
coroutine: Some(CoroutineData { state, wake_tick, stack, frames }),
}; };
let idx = self.objects.len(); let idx = self.objects.len();
self.objects.push(Some(obj)); self.objects.push(Some(obj));
@ -130,6 +175,16 @@ impl Heap {
.filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None }); .filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None });
return Box::new(it); return Box::new(it);
} }
ObjectKind::Coroutine => {
if let Some(co) = o.coroutine.as_ref() {
let it = co
.stack
.iter()
.filter_map(|v| if let Value::HeapRef(h) = v { Some(*h) } else { None });
return Box::new(it);
}
return Box::new(std::iter::empty());
}
_ => return Box::new(std::iter::empty()), _ => return Box::new(std::iter::empty()),
} }
} }
@ -212,6 +267,18 @@ impl Heap {
} }
} }
} }
ObjectKind::Coroutine => {
if let Some(co) = obj.coroutine.as_ref() {
for val in co.stack.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); }
}
}
}
}
}
_ => {} _ => {}
} }
} }
@ -270,6 +337,42 @@ mod tests {
assert_eq!(h3.payload_len, 0); assert_eq!(h3.payload_len, 0);
} }
#[test]
fn allocate_and_transition_coroutine() {
let mut heap = Heap::new();
// Create a coroutine with a small stack containing a HeapRef to verify GC traversal later.
let obj_ref = heap.allocate_object(ObjectKind::Bytes, &[4, 5, 6]);
let coro = heap.allocate_coroutine(
CoroutineState::Ready,
0,
vec![Value::Int32(1), Value::HeapRef(obj_ref)],
vec![CallFrame { return_pc: 0, stack_base: 0, func_idx: 0 }],
);
let hdr = heap.header(coro).unwrap();
assert_eq!(hdr.kind, ObjectKind::Coroutine);
assert_eq!(hdr.payload_len, 0);
// Manually mutate state transitions via access to inner data.
{
let slot = heap.objects.get_mut(coro.0 as usize).and_then(|s| s.as_mut()).unwrap();
let co = slot.coroutine.as_mut().unwrap();
assert_eq!(co.state, CoroutineState::Ready);
co.state = CoroutineState::Running;
assert_eq!(co.state, CoroutineState::Running);
co.state = CoroutineState::Sleeping;
co.wake_tick = 42;
assert_eq!(co.wake_tick, 42);
co.state = CoroutineState::Finished;
assert_eq!(co.state, CoroutineState::Finished);
}
// GC should mark the object referenced from the coroutine stack when the coroutine is a root.
heap.mark_from_roots([coro]);
assert!(heap.header(obj_ref).unwrap().is_marked());
}
#[test] #[test]
fn mark_reachable_through_array() { fn mark_reachable_through_array() {
let mut heap = Heap::new(); let mut heap = Heap::new();

View File

@ -74,6 +74,15 @@ pub enum ObjectKind {
/// User-defined/native host object. Payload shape is host-defined. /// User-defined/native host object. Payload shape is host-defined.
UserData = 5, UserData = 5,
/// Coroutine object: suspended execution context with its own stack/frames.
///
/// Notes:
/// - Stack/frames are stored in typed fields inside the heap storage
/// (not inside raw `payload` bytes) so the GC can traverse their
/// contained `HeapRef`s directly.
/// - `payload_len` is 0 for this fixed-layout object.
Coroutine = 6,
// Future kinds must be appended here to keep tag numbers stable. // Future kinds must be appended here to keep tag numbers stable.
} }

View File

@ -1,64 +1,3 @@
# PR-7 — Coroutines (Cooperative, Deterministic, No Mailbox)
Coroutines are the **only concurrency model** in the Prometeu VM.
This phase introduces:
* Cooperative scheduling
* Deterministic execution order
* SPAWN / YIELD / SLEEP
* Switching only at safepoints (FRAME_SYNC)
* Full GC integration
No mailbox. No message passing. No preemption.
Each PR below is self-contained and must compile independently.
---
# PR-7.1 — Coroutine Heap Object
## Briefing
A coroutine is a suspended execution context with its own stack and call frames.
No mailbox is implemented in this phase.
## Target
Define `ObjectKind::Coroutine` with:
* `state: enum { Ready, Running, Sleeping, Finished, Faulted }`
* `wake_tick: u64`
* `stack: Vec<Value>`
* `frames: Vec<CallFrame>`
Rules:
* Allocated in GC heap.
* Addressed via `HeapRef`.
* Stack and frames stored inside the coroutine object.
## Checklist
* [ ] Coroutine heap object defined.
* [ ] Stack/frames encapsulated.
* [ ] No RC/HIP remnants.
* [ ] Compiles and tests pass.
## Tests
* Allocate coroutine object.
* Validate state transitions manually.
## Junie Rules
You MAY extend heap object kinds.
You MUST NOT implement scheduling yet.
If stack representation is unclear, STOP and ask.
---
# PR-7.2 — Deterministic Scheduler Core # PR-7.2 — Deterministic Scheduler Core
## Briefing ## Briefing