From 35f5b8fa86a35e0fdd0b08492fbc9c7ef7785b79 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 20 Feb 2026 06:56:20 +0000 Subject: [PATCH] pr6.2 --- .../console/prometeu-bytecode/src/opcode.rs | 8 + .../prometeu-bytecode/src/opcode_spec.rs | 12 + .../prometeu-vm/src/virtual_machine.rs | 100 ++++++++ files/TODOs.md | 232 +++++++----------- 4 files changed, 206 insertions(+), 146 deletions(-) diff --git a/crates/console/prometeu-bytecode/src/opcode.rs b/crates/console/prometeu-bytecode/src/opcode.rs index 817649d8..9096344e 100644 --- a/crates/console/prometeu-bytecode/src/opcode.rs +++ b/crates/console/prometeu-bytecode/src/opcode.rs @@ -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 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, } diff --git a/crates/console/prometeu-bytecode/src/opcode_spec.rs b/crates/console/prometeu-bytecode/src/opcode_spec.rs index 70349b3e..17b492ed 100644 --- a/crates/console/prometeu-bytecode/src/opcode_spec.rs +++ b/crates/console/prometeu-bytecode/src/opcode_spec.rs @@ -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, diff --git a/crates/console/prometeu-vm/src/virtual_machine.rs b/crates/console/prometeu-vm/src/virtual_machine.rs index 7e8c5ba6..b0751b7f 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -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 = 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)); + } } diff --git a/files/TODOs.md b/files/TODOs.md index 7f25ea78..5d8fd448 100644 --- a/files/TODOs.md +++ b/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. \ No newline at end of file