pr7.1
This commit is contained in:
parent
3fba722b50
commit
49025b1055
@ -1,3 +1,4 @@
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct CallFrame {
|
||||
pub return_pc: u32,
|
||||
pub stack_base: usize,
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
use crate::{ObjectHeader, ObjectKind};
|
||||
use crate::call_frame::CallFrame;
|
||||
use prometeu_bytecode::{HeapRef, Value};
|
||||
|
||||
/// 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`
|
||||
/// entries from this slice, in order.
|
||||
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.
|
||||
@ -33,7 +55,7 @@ impl Heap {
|
||||
/// 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(), 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();
|
||||
// No free-list reuse in this PR: append and keep indices stable.
|
||||
self.objects.push(Some(obj));
|
||||
@ -44,7 +66,7 @@ impl Heap {
|
||||
/// `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), closure_env: None };
|
||||
let obj = StoredObject { header, payload: Vec::new(), array_elems: Some(elements), closure_env: None, coroutine: None };
|
||||
let idx = self.objects.len();
|
||||
// No free-list reuse in this PR: append and keep indices stable.
|
||||
self.objects.push(Some(obj));
|
||||
@ -67,6 +89,29 @@ impl Heap {
|
||||
payload,
|
||||
array_elems: None,
|
||||
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();
|
||||
self.objects.push(Some(obj));
|
||||
@ -130,6 +175,16 @@ impl Heap {
|
||||
.filter_map(|val| if let Value::HeapRef(h) = val { Some(*h) } else { None });
|
||||
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()),
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
|
||||
#[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]
|
||||
fn mark_reachable_through_array() {
|
||||
let mut heap = Heap::new();
|
||||
|
||||
@ -74,6 +74,15 @@ pub enum ObjectKind {
|
||||
/// User-defined/native host object. Payload shape is host-defined.
|
||||
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.
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
## Briefing
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user