pr6.3
This commit is contained in:
parent
35f5b8fa86
commit
29736e9577
@ -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<u16> 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,
|
||||
}
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<Value> = 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)]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user