From 29736e9577310613c7e4e1fef87ad0da4b381a07 Mon Sep 17 00:00:00 2001 From: bQUARKz Date: Fri, 20 Feb 2026 07:03:55 +0000 Subject: [PATCH] pr6.3 --- .../console/prometeu-bytecode/src/opcode.rs | 11 + .../prometeu-bytecode/src/opcode_spec.rs | 12 + .../prometeu-vm/src/virtual_machine.rs | 261 ++++++++++++++++++ files/TODOs.md | 93 ------- files/VM RESET.md | 2 + 5 files changed, 286 insertions(+), 93 deletions(-) diff --git a/crates/console/prometeu-bytecode/src/opcode.rs b/crates/console/prometeu-bytecode/src/opcode.rs index 9096344e..676d8333 100644 --- a/crates/console/prometeu-bytecode/src/opcode.rs +++ b/crates/console/prometeu-bytecode/src/opcode.rs @@ -161,6 +161,15 @@ pub enum OpCode { /// 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, + /// Calls a closure value with hidden arg0 semantics (Model B). + /// Operand: arg_count (u32) — number of user-supplied args (excludes hidden arg0) + /// Stack before: [..., argN, ..., arg1, closure_ref] + /// Behavior: + /// - Pops `closure_ref` and validates it is a Closure. + /// - Pops `arg_count` user args. + /// - Fetches `fn_id` from the closure and creates a new call frame. + /// - Injects hidden arg0 = closure_ref, followed by user args as arg1..argN. + CallClosure = 0x53, // --- 6.8 Peripherals and System --- /// Invokes a system function (Firmware/OS). @@ -222,6 +231,7 @@ impl TryFrom for OpCode { 0x50 => Ok(OpCode::Call), 0x51 => Ok(OpCode::Ret), 0x52 => Ok(OpCode::MakeClosure), + 0x53 => Ok(OpCode::CallClosure), 0x70 => Ok(OpCode::Syscall), 0x80 => Ok(OpCode::FrameSync), _ => Err(format!("Invalid OpCode: 0x{:04X}", value)), @@ -279,6 +289,7 @@ impl OpCode { OpCode::Call => 5, OpCode::Ret => 4, OpCode::MakeClosure => 8, + OpCode::CallClosure => 6, 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 17b492ed..01f5fb36 100644 --- a/crates/console/prometeu-bytecode/src/opcode_spec.rs +++ b/crates/console/prometeu-bytecode/src/opcode_spec.rs @@ -474,6 +474,18 @@ impl OpCodeSpecExt for OpCode { may_trap: false, is_safepoint: false, }, + OpCode::CallClosure => OpcodeSpec { + name: "CALL_CLOSURE", + // One u32 immediate: arg_count (user args, excludes hidden arg0) + imm_bytes: 4, + // Dynamic: pops closure_ref + arg_count; keep 0 in spec layer + pops: 0, + pushes: 0, + is_branch: false, + is_terminator: false, + may_trap: true, + 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 b0751b7f..15d3ae6d 100644 --- a/crates/console/prometeu-vm/src/virtual_machine.rs +++ b/crates/console/prometeu-vm/src/virtual_machine.rs @@ -7,6 +7,7 @@ use prometeu_bytecode::ProgramImage; use prometeu_bytecode::Value; use crate::roots::{RootVisitor, visit_value_for_roots}; use crate::heap::Heap; +use crate::object::ObjectKind; use prometeu_bytecode::{ TRAP_BAD_RET_SLOTS, TRAP_DIV_ZERO, TRAP_INVALID_FUNC, TRAP_INVALID_SYSCALL, TRAP_OOB, TRAP_STACK_UNDERFLOW, TRAP_TYPE, TrapInfo, @@ -427,6 +428,109 @@ impl VirtualMachine { let href = self.heap.alloc_closure(fn_id, &temp); self.push(Value::HeapRef(href)); } + OpCode::CallClosure => { + // Operand carries the number of user-supplied arguments (arg1..argN). + let user_arg_count = instr + .imm_u32() + .map_err(|e| LogicalFrameEndingReason::Panic(format!("{:?}", e)))? + as usize; + + // Pop the closure reference from the stack (top of stack). + let clos_val = self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?; + let href = match clos_val { + Value::HeapRef(h) => h, + other => { + return Err(self.trap( + TRAP_TYPE, + opcode as u16, + format!( + "CALL_CLOSURE expects a closure handle at TOS, got {:?}", + other + ), + start_pc as u32, + )) + } + }; + + // Validate that the heap object is indeed a Closure. + let header = self.heap.header(href).ok_or_else(|| { + self.trap( + TRAP_OOB, + opcode as u16, + format!("Invalid heap handle in CALL_CLOSURE: {:?}", href), + start_pc as u32, + ) + })?; + if header.kind != ObjectKind::Closure { + return Err(self.trap( + TRAP_TYPE, + opcode as u16, + format!( + "CALL_CLOSURE on non-closure object kind {:?}", + header.kind + ), + start_pc as u32, + )); + } + + // Pop user arguments from the operand stack (top-first), then fix order. + let mut user_args: Vec = Vec::with_capacity(user_arg_count); + for _ in 0..user_arg_count { + user_args.push(self.pop().map_err(|e| LogicalFrameEndingReason::Panic(e))?); + } + user_args.reverse(); // Now in logical order: arg1..argN + + // Resolve target function id from the closure payload. + let fn_id = self.heap.closure_fn_id(href).ok_or_else(|| { + LogicalFrameEndingReason::Panic( + "Internal error: malformed closure object (missing fn_id)".into(), + ) + })? as usize; + + let callee = self.program.functions.get(fn_id).ok_or_else(|| { + self.trap( + TRAP_INVALID_FUNC, + opcode as u16, + format!("Invalid func_id {} from closure", fn_id), + start_pc as u32, + ) + })?; + // Copy required fields to drop the immutable borrow before mutating self + let callee_param_slots = callee.param_slots as usize; + let callee_local_slots = callee.local_slots as usize; + let callee_code_offset = callee.code_offset as usize; + + // Validate arity: param_slots must equal hidden arg0 + user_arg_count. + let expected_params = 1usize + user_arg_count; + if callee_param_slots != expected_params { + return Err(self.trap( + TRAP_TYPE, + opcode as u16, + format!( + "CALL_CLOSURE arg_count mismatch: function expects {} total params (including hidden arg0), got hidden+{}", + callee_param_slots, expected_params + ), + start_pc as u32, + )); + } + + // Prepare the operand stack to match the direct CALL convention: + // push hidden arg0 (closure_ref) followed by arg1..argN. + self.push(Value::HeapRef(href)); + for v in user_args.into_iter() { self.push(v); } + + let stack_base = self + .operand_stack + .len() + .checked_sub(callee_param_slots) + .ok_or_else(|| LogicalFrameEndingReason::Panic("Stack underflow".into()))?; + + // Allocate and zero-init local slots + for _ in 0..callee_local_slots { self.operand_stack.push(Value::Null); } + + self.call_stack.push(CallFrame { return_pc: self.pc as u32, stack_base, func_idx: fn_id }); + self.pc = callee_code_offset; + } OpCode::PushConst => { let idx = instr .imm_u32() @@ -2823,4 +2927,161 @@ mod tests { assert_eq!(env[1], Value::Int32(2)); assert_eq!(env[2], Value::Int32(3)); } + + #[test] + fn test_call_closure_returns_constant() { + use prometeu_bytecode::{FunctionMeta, Value}; + + // F0 (entry): MAKE_CLOSURE fn=1, cap=0; CALL_CLOSURE argc=0; HALT + // F1 (callee): PUSH_I32 7; RET + let mut rom = Vec::new(); + let f0_start = 0usize; + rom.extend_from_slice(&(OpCode::MakeClosure as u16).to_le_bytes()); + rom.extend_from_slice(&1u32.to_le_bytes()); // fn_id + rom.extend_from_slice(&0u32.to_le_bytes()); // capture_count + rom.extend_from_slice(&(OpCode::CallClosure as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // argc = 0 user args + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + let f0_len = rom.len() - f0_start; + + // F1 code + let f1_start = rom.len() as u32; + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&7i32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes()); + let f1_len = rom.len() as u32 - f1_start; + + let mut vm = new_test_vm(rom.clone(), vec![]); + vm.program.functions = std::sync::Arc::from(vec![ + FunctionMeta { code_offset: f0_start as u32, code_len: f0_len as u32, ..Default::default() }, + FunctionMeta { code_offset: f1_start, code_len: f1_len, param_slots: 1, return_slots: 1, ..Default::default() }, + ]); + + let mut native = MockNative; + let mut ctx = HostContext::new(None); + vm.prepare_call("0"); + let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); + assert_eq!(report.reason, LogicalFrameEndingReason::Halted); + assert_eq!(vm.operand_stack.len(), 1); + assert_eq!(vm.operand_stack[0], Value::Int32(7)); + } + + #[test] + fn test_call_closure_with_captures_ignored() { + use prometeu_bytecode::{FunctionMeta, Value}; + + // F0: PUSH_I32 123; MAKE_CLOSURE fn=1 cap=1; CALL_CLOSURE 0; HALT + // F1: PUSH_I32 42; RET + let mut rom = Vec::new(); + let f0_start = 0usize; + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&123i32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::MakeClosure as u16).to_le_bytes()); + rom.extend_from_slice(&1u32.to_le_bytes()); // fn_id + rom.extend_from_slice(&1u32.to_le_bytes()); // capture_count + rom.extend_from_slice(&(OpCode::CallClosure as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // argc = 0 + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + let f0_len = rom.len() - f0_start; + + let f1_start = rom.len() as u32; + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&42i32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes()); + let f1_len = rom.len() as u32 - f1_start; + + let mut vm = new_test_vm(rom.clone(), vec![]); + vm.program.functions = std::sync::Arc::from(vec![ + FunctionMeta { code_offset: f0_start as u32, code_len: f0_len as u32, ..Default::default() }, + FunctionMeta { code_offset: f1_start, code_len: f1_len, param_slots: 1, return_slots: 1, ..Default::default() }, + ]); + + let mut native = MockNative; + let mut ctx = HostContext::new(None); + vm.prepare_call("0"); + let report = vm.run_budget(100, &mut native, &mut ctx).unwrap(); + assert_eq!(report.reason, LogicalFrameEndingReason::Halted); + assert_eq!(vm.operand_stack, vec![Value::Int32(42)]); + } + + #[test] + fn test_call_closure_on_non_closure_traps() { + use prometeu_bytecode::FunctionMeta; + + // F0: PUSH_I32 1; CALL_CLOSURE 0; HALT -> should TRAP_TYPE on CALL_CLOSURE + let mut rom = Vec::new(); + let f0_start = 0usize; + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&1i32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::CallClosure as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); + // Leave HALT for after run to ensure we trap before + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + let f0_len = rom.len() - f0_start; + + let mut vm = new_test_vm(rom.clone(), vec![]); + vm.program.functions = std::sync::Arc::from(vec![FunctionMeta { code_offset: f0_start as u32, code_len: f0_len as u32, ..Default::default() }]); + + let mut native = MockNative; + let mut ctx = HostContext::new(None); + vm.prepare_call("0"); + let report = vm.run_budget(10, &mut native, &mut ctx).unwrap(); + match report.reason { + LogicalFrameEndingReason::Trap(info) => { + assert_eq!(info.code, TRAP_TYPE); + assert_eq!(info.opcode, OpCode::CallClosure as u16); + } + other => panic!("Expected Trap(TYPE) from CALL_CLOSURE on non-closure, got {:?}", other), + } + } + + #[test] + fn test_nested_call_closure() { + use prometeu_bytecode::{FunctionMeta, Value}; + + // F0: MAKE_CLOSURE fn=1 cap=0; CALL_CLOSURE 0; CALL_CLOSURE 0; HALT + // F1: MAKE_CLOSURE fn=2 cap=0; RET // returns a closure + // F2: PUSH_I32 55; RET // returns constant + let mut rom = Vec::new(); + // F0 + let f0_start = 0usize; + rom.extend_from_slice(&(OpCode::MakeClosure as u16).to_le_bytes()); + rom.extend_from_slice(&1u32.to_le_bytes()); // fn_id = 1 + rom.extend_from_slice(&0u32.to_le_bytes()); // cap=0 + rom.extend_from_slice(&(OpCode::CallClosure as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // argc=0 -> pushes a closure from F1 + rom.extend_from_slice(&(OpCode::CallClosure as u16).to_le_bytes()); + rom.extend_from_slice(&0u32.to_le_bytes()); // argc=0 -> call returned closure F2 + rom.extend_from_slice(&(OpCode::Halt as u16).to_le_bytes()); + let f0_len = rom.len() - f0_start; + + // F1 + let f1_start = rom.len() as u32; + rom.extend_from_slice(&(OpCode::MakeClosure as u16).to_le_bytes()); + rom.extend_from_slice(&2u32.to_le_bytes()); // fn_id = 2 + rom.extend_from_slice(&0u32.to_le_bytes()); // cap=0 + rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes()); // return the HeapRef on stack + let f1_len = rom.len() as u32 - f1_start; + + // F2 + let f2_start = rom.len() as u32; + rom.extend_from_slice(&(OpCode::PushI32 as u16).to_le_bytes()); + rom.extend_from_slice(&55i32.to_le_bytes()); + rom.extend_from_slice(&(OpCode::Ret as u16).to_le_bytes()); + let f2_len = rom.len() as u32 - f2_start; + + let mut vm = new_test_vm(rom.clone(), vec![]); + vm.program.functions = std::sync::Arc::from(vec![ + FunctionMeta { code_offset: f0_start as u32, code_len: f0_len as u32, ..Default::default() }, + FunctionMeta { code_offset: f1_start, code_len: f1_len, param_slots: 1, return_slots: 1, ..Default::default() }, + FunctionMeta { code_offset: f2_start, code_len: f2_len, param_slots: 1, return_slots: 1, ..Default::default() }, + ]); + + let mut native = MockNative; + let mut ctx = HostContext::new(None); + vm.prepare_call("0"); + let report = vm.run_budget(200, &mut native, &mut ctx).unwrap(); + assert_eq!(report.reason, LogicalFrameEndingReason::Halted); + assert_eq!(vm.operand_stack, vec![Value::Int32(55)]); + } } diff --git a/files/TODOs.md b/files/TODOs.md index 5d8fd448..9ebfcdc0 100644 --- a/files/TODOs.md +++ b/files/TODOs.md @@ -1,96 +1,3 @@ -# PR-6.3 — CALL_CLOSURE (Model B Hidden Arg0) - -## Briefing - -Closures must be dynamically invokable. - -Under Model B, invocation semantics are: - -* The closure object itself becomes hidden `arg0`. -* User-supplied arguments become `arg1..argN`. -* Captures remain inside the closure and are accessed explicitly. - ---- - -## Target - -Introduce opcode: - -`CALL_CLOSURE arg_count` - -Stack before call: - -``` -[..., argN, ..., arg1, closure_ref] -``` - -Execution steps: - -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. Implement dispatch logic. -3. Integrate with call frame creation. -4. Ensure stack discipline is preserved. - ---- - -## Acceptance Checklist - -* [ ] CALL_CLOSURE implemented. -* [ ] closure_ref validated. -* [ ] arg_count respected. -* [ ] Hidden arg0 injected correctly. -* [ ] Errors thrown on non-closure call. - ---- - -## Tests - -1. Closure returning constant. -2. Closure capturing value and using it. -3. Calling non-closure results in trap. -4. Nested closure calls work. - ---- - -## Junie Instructions - -You MAY: - -* Modify interpreter call logic. -* Add tests. - -You MUST NOT: - -* Change stack model. -* Introduce coroutine semantics. -* Modify GC. - -If function signature metadata is insufficient to validate arg_count, STOP and ask. - ---- - -## Definition of Done - -Closures can be dynamically invoked with hidden arg0 semantics. - ---- - # PR-6.4 — GC Traversal for Closures (Model B) ## Briefing diff --git a/files/VM RESET.md b/files/VM RESET.md index 3f5eeeb3..44468a61 100644 --- a/files/VM RESET.md +++ b/files/VM RESET.md @@ -1,3 +1,5 @@ +vamos as PR7s todas em um unico canvas markdown ingles, devem ser auto contidas com briefing, alvo, checklist, test quando necessario e comandos do que a Junie pode ou nao fazer (Junie eh task operator nao arquiteta ou assume nada, questiona quando necessario). + 7 — Coroutines (único modelo de concorrência, cooperativo) 7.1. Definir objeto Coroutine no heap: stack/frames próprios, status, wake time, mailbox/queue se existir.