pr7.1
This commit is contained in:
parent
3fba722b50
commit
49025b1055
@ -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,
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user