pr6.2
This commit is contained in:
parent
860f0db31c
commit
35f5b8fa86
@ -155,6 +155,12 @@ pub enum OpCode {
|
||||
/// Returns from the current function.
|
||||
/// Stack: [return_val] -> [return_val]
|
||||
Ret = 0x51,
|
||||
/// Creates a closure capturing values from the operand stack (Model B).
|
||||
/// Operands: fn_id (u32), capture_count (u32)
|
||||
/// Stack before: [..., captured_N, ..., captured_1]
|
||||
/// Pops capture_count values (top-first), preserves order as [captured_1..captured_N]
|
||||
/// and stores them inside the closure environment. Pushes a HeapRef to the closure.
|
||||
MakeClosure = 0x52,
|
||||
|
||||
// --- 6.8 Peripherals and System ---
|
||||
/// Invokes a system function (Firmware/OS).
|
||||
@ -215,6 +221,7 @@ impl TryFrom<u16> for OpCode {
|
||||
0x43 => Ok(OpCode::SetLocal),
|
||||
0x50 => Ok(OpCode::Call),
|
||||
0x51 => Ok(OpCode::Ret),
|
||||
0x52 => Ok(OpCode::MakeClosure),
|
||||
0x70 => Ok(OpCode::Syscall),
|
||||
0x80 => Ok(OpCode::FrameSync),
|
||||
_ => Err(format!("Invalid OpCode: 0x{:04X}", value)),
|
||||
@ -271,6 +278,7 @@ impl OpCode {
|
||||
OpCode::SetLocal => 2,
|
||||
OpCode::Call => 5,
|
||||
OpCode::Ret => 4,
|
||||
OpCode::MakeClosure => 8,
|
||||
OpCode::Syscall => 1,
|
||||
OpCode::FrameSync => 1,
|
||||
}
|
||||
|
||||
@ -462,6 +462,18 @@ impl OpCodeSpecExt for OpCode {
|
||||
may_trap: false,
|
||||
is_safepoint: false,
|
||||
},
|
||||
OpCode::MakeClosure => OpcodeSpec {
|
||||
name: "MAKE_CLOSURE",
|
||||
// Two u32 immediates: fn_id and capture_count
|
||||
imm_bytes: 8,
|
||||
// Dynamic, depends on capture_count; keep 0 here for verifier-free spec
|
||||
pops: 0,
|
||||
pushes: 1,
|
||||
is_branch: false,
|
||||
is_terminator: false,
|
||||
may_trap: false,
|
||||
is_safepoint: false,
|
||||
},
|
||||
OpCode::Syscall => OpcodeSpec {
|
||||
name: "SYSCALL",
|
||||
imm_bytes: 4,
|
||||
|
||||
@ -407,6 +407,26 @@ impl VirtualMachine {
|
||||
self.cycles += OpCode::Trap.cycles();
|
||||
return Err(LogicalFrameEndingReason::Breakpoint);
|
||||
}
|
||||
OpCode::MakeClosure => {
|
||||
// Immediate carries (fn_id, capture_count)
|
||||
let (fn_id, cap_count) = instr
|
||||
.imm_u32x2()
|
||||
.map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))?;
|
||||
|
||||
// Pop cap_count values from the operand stack, top-first.
|
||||
let mut temp: Vec<Value> = Vec::with_capacity(cap_count as usize);
|
||||
for _ in 0..cap_count {
|
||||
let v = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?;
|
||||
temp.push(v);
|
||||
}
|
||||
// Preserve order so that env[0] corresponds to captured_1 (the bottom-most
|
||||
// among the popped values): reverse the temp vector.
|
||||
temp.reverse();
|
||||
|
||||
// Allocate closure on heap and push its reference.
|
||||
let href = self.heap.alloc_closure(fn_id, &temp);
|
||||
self.push(Value::HeapRef(href));
|
||||
}
|
||||
OpCode::PushConst => {
|
||||
let idx = instr
|
||||
.imm_u32()
|
||||
@ -2723,4 +2743,84 @@ mod tests {
|
||||
|
||||
assert_eq!(vm.heap.len(), 0, "All short-lived objects must be reclaimed deterministically");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_make_closure_zero_captures() {
|
||||
use prometeu_bytecode::{FunctionMeta, Value};
|
||||
|
||||
// ROM: MAKE_CLOSURE fn_id=7, cap=0; HALT
|
||||
let mut rom = Vec::new();
|
||||
rom.extend_from_slice(&(OpCode::MakeClosure as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&7u32.to_le_bytes()); // fn_id
|
||||
rom.extend_from_slice(&0u32.to_le_bytes()); // capture_count
|
||||
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||
|
||||
let mut vm = new_test_vm(rom.clone(), vec![]);
|
||||
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
|
||||
code_offset: 0,
|
||||
code_len: rom.len() as u32,
|
||||
..Default::default()
|
||||
}]);
|
||||
|
||||
let mut native = MockNative;
|
||||
let mut ctx = HostContext::new(None);
|
||||
// step MAKE_CLOSURE
|
||||
vm.step(&mut native, &mut ctx).unwrap();
|
||||
// step HALT
|
||||
vm.step(&mut native, &mut ctx).unwrap();
|
||||
|
||||
assert!(vm.halted);
|
||||
assert_eq!(vm.operand_stack.len(), 1);
|
||||
let top = vm.peek().unwrap().clone();
|
||||
let href = match top { Value::HeapRef(h) => h, _ => panic!("Expected HeapRef on stack") };
|
||||
assert!(vm.heap.is_valid(href));
|
||||
assert_eq!(vm.heap.closure_fn_id(href), Some(7));
|
||||
let env = vm.heap.closure_env_slice(href).expect("env slice");
|
||||
assert_eq!(env.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_make_closure_multiple_captures_and_order() {
|
||||
use prometeu_bytecode::{FunctionMeta, Value};
|
||||
|
||||
// Build ROM:
|
||||
// PUSH_I32 1; PUSH_I32 2; PUSH_I32 3; // Stack: [1,2,3]
|
||||
// MAKE_CLOSURE fn_id=9, cap=3; // Pops 3 (3,2,1), env = [1,2,3]
|
||||
// HALT
|
||||
let mut rom = Vec::new();
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&1i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&2i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&3i32.to_le_bytes());
|
||||
rom.extend_from_slice(&(OpCode::MakeClosure as u16).to_le_bytes());
|
||||
rom.extend_from_slice(&9u32.to_le_bytes()); // fn_id
|
||||
rom.extend_from_slice(&3u32.to_le_bytes()); // capture_count
|
||||
rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes());
|
||||
|
||||
let mut vm = new_test_vm(rom.clone(), vec![]);
|
||||
vm.program.functions = std::sync::Arc::from(vec![FunctionMeta {
|
||||
code_offset: 0,
|
||||
code_len: rom.len() as u32,
|
||||
..Default::default()
|
||||
}]);
|
||||
|
||||
let mut native = MockNative;
|
||||
let mut ctx = HostContext::new(None);
|
||||
// Execute instructions until HALT
|
||||
while !vm.halted {
|
||||
vm.step(&mut native, &mut ctx).unwrap();
|
||||
}
|
||||
|
||||
// After HALT, stack must contain only the closure ref
|
||||
assert_eq!(vm.operand_stack.len(), 1);
|
||||
let href = match vm.pop().unwrap() { Value::HeapRef(h) => h, _ => panic!("Expected HeapRef") };
|
||||
assert_eq!(vm.heap.closure_fn_id(href), Some(9));
|
||||
let env = vm.heap.closure_env_slice(href).expect("env slice");
|
||||
assert_eq!(env.len(), 3);
|
||||
assert_eq!(env[0], Value::Int32(1));
|
||||
assert_eq!(env[1], Value::Int32(2));
|
||||
assert_eq!(env[2], Value::Int32(3));
|
||||
}
|
||||
}
|
||||
|
||||
232
files/TODOs.md
232
files/TODOs.md
@ -1,89 +1,14 @@
|
||||
# PR-6.2 — Closure Capture Materialization
|
||||
# PR-6.3 — CALL_CLOSURE (Model B Hidden Arg0)
|
||||
|
||||
## Briefing
|
||||
|
||||
Closures must capture values from the current stack frame into a heap-allocated environment.
|
||||
Closures must be dynamically invokable.
|
||||
|
||||
This PR defines:
|
||||
Under Model B, invocation semantics are:
|
||||
|
||||
* How captured values are materialized.
|
||||
* How the environment layout is constructed.
|
||||
|
||||
No CALL_CLOSURE yet.
|
||||
|
||||
---
|
||||
|
||||
## Target
|
||||
|
||||
Define bytecode semantics for closure creation:
|
||||
|
||||
Introduce instruction (placeholder name):
|
||||
|
||||
`MAKE_CLOSURE fn_id, capture_count`
|
||||
|
||||
Semantics:
|
||||
|
||||
* Pop `capture_count` values from stack (top-first).
|
||||
* Allocate closure object with those values stored in-order.
|
||||
* Push resulting `HeapRef` to stack.
|
||||
|
||||
---
|
||||
|
||||
## Work Items
|
||||
|
||||
1. Define new opcode `MAKE_CLOSURE`.
|
||||
2. Implement stack semantics.
|
||||
3. Ensure captured values are copied (not borrowed).
|
||||
4. Update interpreter to support opcode.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Checklist
|
||||
|
||||
* [ ] MAKE_CLOSURE opcode exists.
|
||||
* [ ] Stack pops correct number of values.
|
||||
* [ ] Closure allocated correctly.
|
||||
* [ ] Closure ref pushed to stack.
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
1. Create closure capturing 0 values.
|
||||
2. Create closure capturing 2 values.
|
||||
3. Validate env order correctness.
|
||||
|
||||
---
|
||||
|
||||
## Junie Instructions
|
||||
|
||||
You MAY:
|
||||
|
||||
* Add opcode.
|
||||
* Modify interpreter dispatch.
|
||||
* Add tests.
|
||||
|
||||
You MUST NOT:
|
||||
|
||||
* Implement CALL_CLOSURE yet.
|
||||
* Modify GC behavior.
|
||||
* Change verifier in this PR.
|
||||
|
||||
If capture order semantics unclear, STOP and ask.
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Closures can be created with captured environment and exist as heap values.
|
||||
|
||||
---
|
||||
|
||||
# PR-6.3 — CALL_CLOSURE Instruction
|
||||
|
||||
## Briefing
|
||||
|
||||
Closures must be invokable at runtime. This PR introduces dynamic invocation semantics for closures.
|
||||
* The closure object itself becomes hidden `arg0`.
|
||||
* User-supplied arguments become `arg1..argN`.
|
||||
* Captures remain inside the closure and are accessed explicitly.
|
||||
|
||||
---
|
||||
|
||||
@ -93,47 +18,53 @@ Introduce opcode:
|
||||
|
||||
`CALL_CLOSURE arg_count`
|
||||
|
||||
Semantics:
|
||||
Stack before call:
|
||||
|
||||
* Stack layout before call:
|
||||
```
|
||||
[..., argN, ..., arg1, closure_ref]
|
||||
```
|
||||
|
||||
```
|
||||
[... args..., closure_ref]
|
||||
```
|
||||
* Pop closure_ref.
|
||||
* Validate it is ObjectKind::Closure.
|
||||
* Pop `arg_count` arguments.
|
||||
* Create new call frame:
|
||||
Execution steps:
|
||||
|
||||
* Locals initialized with captured env first (design choice below).
|
||||
* Arguments appended after captures.
|
||||
* Jump to function entry (fn_id).
|
||||
1. Pop `closure_ref`.
|
||||
2. Validate object is `ObjectKind::Closure`.
|
||||
3. Pop `arg_count` arguments.
|
||||
4. Read `fn_id` from closure object.
|
||||
5. Create new call frame:
|
||||
|
||||
* Inject `closure_ref` as `arg0`.
|
||||
* Append user arguments as `arg1..argN`.
|
||||
6. Jump to function entry.
|
||||
|
||||
No environment copying into locals.
|
||||
|
||||
---
|
||||
|
||||
## Work Items
|
||||
|
||||
1. Add `CALL_CLOSURE` opcode.
|
||||
2. Validate closure_ref type.
|
||||
3. Integrate into call frame creation logic.
|
||||
4. Respect function signature for ret_slots.
|
||||
2. Implement dispatch logic.
|
||||
3. Integrate with call frame creation.
|
||||
4. Ensure stack discipline is preserved.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Checklist
|
||||
|
||||
* [ ] CALL_CLOSURE implemented.
|
||||
* [ ] Correct stack consumption.
|
||||
* [ ] Correct frame initialization.
|
||||
* [ ] Error on non-closure value.
|
||||
* [ ] closure_ref validated.
|
||||
* [ ] arg_count respected.
|
||||
* [ ] Hidden arg0 injected correctly.
|
||||
* [ ] Errors thrown on non-closure call.
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
1. Simple closure returning constant.
|
||||
1. Closure returning constant.
|
||||
2. Closure capturing value and using it.
|
||||
3. Error when calling non-closure.
|
||||
3. Calling non-closure results in trap.
|
||||
4. Nested closure calls work.
|
||||
|
||||
---
|
||||
|
||||
@ -141,64 +72,65 @@ Semantics:
|
||||
|
||||
You MAY:
|
||||
|
||||
* Add opcode and dispatch.
|
||||
* Modify call frame initialization.
|
||||
* Modify interpreter call logic.
|
||||
* Add tests.
|
||||
|
||||
You MUST NOT:
|
||||
|
||||
* Redesign stack model.
|
||||
* Introduce coroutine behavior here.
|
||||
* Change GC.
|
||||
* Change stack model.
|
||||
* Introduce coroutine semantics.
|
||||
* Modify GC.
|
||||
|
||||
If frame layout decision is ambiguous, STOP and ask before choosing ordering.
|
||||
If function signature metadata is insufficient to validate arg_count, STOP and ask.
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Closures can be invoked dynamically and execute correctly.
|
||||
Closures can be dynamically invoked with hidden arg0 semantics.
|
||||
|
||||
---
|
||||
|
||||
# PR-6.4 — GC Traversal for Closures
|
||||
# PR-6.4 — GC Traversal for Closures (Model B)
|
||||
|
||||
## Briefing
|
||||
|
||||
Closures introduce heap-to-heap references through their captured environments.
|
||||
|
||||
The GC must traverse:
|
||||
Under Model B, the closure object itself is passed at call time, but its environment remains stored in heap.
|
||||
|
||||
GC must traverse:
|
||||
|
||||
closure -> env -> inner HeapRefs
|
||||
|
||||
This PR updates the GC mark phase to correctly traverse closure environments.
|
||||
|
||||
---
|
||||
|
||||
## Target
|
||||
|
||||
Extend GC mark logic:
|
||||
Extend GC mark phase to handle `ObjectKind::Closure`:
|
||||
|
||||
* When encountering ObjectKind::Closure:
|
||||
When marking a closure:
|
||||
|
||||
* Iterate over env values.
|
||||
* If a value contains HeapRef → mark referenced object.
|
||||
* Iterate over env values.
|
||||
* If a value contains HeapRef → mark referenced object.
|
||||
|
||||
No compaction. No relocation.
|
||||
|
||||
---
|
||||
|
||||
## Work Items
|
||||
|
||||
1. Update mark traversal switch for Closure.
|
||||
2. Ensure no panics on malformed env.
|
||||
3. Add tests for nested closure references.
|
||||
1. Extend mark traversal switch.
|
||||
2. Ensure safe iteration over env payload.
|
||||
3. Add regression tests.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Checklist
|
||||
|
||||
* [ ] GC marks env HeapRefs.
|
||||
* [ ] Closure env scanned.
|
||||
* [ ] Nested closures retained.
|
||||
* [ ] No regression in existing GC tests.
|
||||
* [ ] Nested closures retained correctly.
|
||||
|
||||
---
|
||||
|
||||
@ -215,30 +147,34 @@ Extend GC mark logic:
|
||||
You MAY:
|
||||
|
||||
* Modify mark traversal.
|
||||
* Add GC tests.
|
||||
* Add tests.
|
||||
|
||||
You MUST NOT:
|
||||
|
||||
* Implement compaction.
|
||||
* Change sweep policy.
|
||||
* Modify sweep policy.
|
||||
* Introduce compaction.
|
||||
|
||||
If unsure whether env values may contain non-heap values, ask before assuming.
|
||||
If unsure whether Value variants can embed HeapRef, STOP and ask.
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
GC correctly traverses closure environments.
|
||||
GC correctly traverses closure environments under Model B semantics.
|
||||
|
||||
---
|
||||
|
||||
# PR-6.5 — Verifier Support for Closures
|
||||
# PR-6.5 — Verifier Support for Closures (Model B)
|
||||
|
||||
## Briefing
|
||||
|
||||
The verifier must understand closure values as a distinct type and validate dynamic calls safely.
|
||||
The verifier must understand closure values and enforce safe invocation rules.
|
||||
|
||||
Closures are heap objects but semantically represent callable values.
|
||||
Under Model B:
|
||||
|
||||
* `CALL_CLOSURE` injects hidden `arg0`.
|
||||
* User-visible arg_count excludes hidden arg.
|
||||
* Captures are accessed via explicit instructions (future PR).
|
||||
|
||||
---
|
||||
|
||||
@ -246,36 +182,43 @@ Closures are heap objects but semantically represent callable values.
|
||||
|
||||
Extend verifier to:
|
||||
|
||||
* Introduce a stack type: `ClosureValue`.
|
||||
* Validate MAKE_CLOSURE stack effects.
|
||||
* Validate CALL_CLOSURE argument counts.
|
||||
* Validate ret_slots against function signature.
|
||||
1. Introduce stack type: `ClosureValue`.
|
||||
2. Validate MAKE_CLOSURE effects.
|
||||
3. Validate CALL_CLOSURE semantics:
|
||||
|
||||
* Ensure top of stack is ClosureValue.
|
||||
* Ensure sufficient args present.
|
||||
* Validate `arg_count` matches function signature expectations.
|
||||
* Account for hidden arg0 when checking callee arg arity.
|
||||
4. Validate ret_slots against function metadata.
|
||||
|
||||
---
|
||||
|
||||
## Work Items
|
||||
|
||||
1. Add closure type to verifier type lattice.
|
||||
1. Extend type lattice with ClosureValue.
|
||||
2. Define stack transitions for MAKE_CLOSURE.
|
||||
3. Define stack transitions for CALL_CLOSURE.
|
||||
4. Ensure deterministic failure on misuse.
|
||||
4. Enforce strict failure on mismatch.
|
||||
|
||||
---
|
||||
|
||||
## Acceptance Checklist
|
||||
|
||||
* [ ] Verifier understands closure values.
|
||||
* [ ] ClosureValue type exists.
|
||||
* [ ] Invalid CALL_CLOSURE rejected.
|
||||
* [ ] Hidden arg0 accounted for.
|
||||
* [ ] ret_slots validated.
|
||||
* [ ] All tests pass.
|
||||
* [ ] All verifier tests pass.
|
||||
|
||||
---
|
||||
|
||||
## Tests
|
||||
|
||||
1. Valid closure call passes verification.
|
||||
2. CALL_CLOSURE with wrong arg count fails.
|
||||
3. CALL_CLOSURE on non-closure fails.
|
||||
2. CALL_CLOSURE with wrong arg_count fails.
|
||||
3. CALL_CLOSURE on non-closure fails verification.
|
||||
4. Nested closure calls verify correctly.
|
||||
|
||||
---
|
||||
|
||||
@ -283,21 +226,18 @@ Extend verifier to:
|
||||
|
||||
You MAY:
|
||||
|
||||
* Extend verifier type model.
|
||||
* Extend verifier model.
|
||||
* Add tests.
|
||||
|
||||
You MUST NOT:
|
||||
|
||||
* Weaken verification rules.
|
||||
* Introduce runtime-only checks instead of verifier checks.
|
||||
* Replace verifier checks with runtime-only traps.
|
||||
|
||||
If closure typing conflicts with current stack model, STOP and ask.
|
||||
If function metadata (arg_slots/ret_slots) is insufficient, STOP and request clarification.
|
||||
|
||||
---
|
||||
|
||||
## Definition of Done
|
||||
|
||||
Verifier fully supports closure creation and invocation.
|
||||
|
||||
---
|
||||
|
||||
Verifier fully supports closure creation and invocation under Model B semantics.
|
||||
Loading…
x
Reference in New Issue
Block a user