diff --git a/crates/core/src/README.md b/crates/core/src/README.md index 0663f5f..baf1177 100644 --- a/crates/core/src/README.md +++ b/crates/core/src/README.md @@ -11,10 +11,10 @@ | [builtins.rs](builtins.rs) | String、Dict、Cell、Function、BaseException 载荷与 trace 边 | | [number.rs](number.rs) | EndorNumber、Integer/Decimal/Rational/BinaryFloat/Complex 表示及装箱载荷 | | [vm_builtins.rs](vm_builtins.rs) | 内置载荷校验、代码/函数工厂、Dict/Cell 操作、键语义和可变引用更新 | -| [instruction.rs](instruction.rs) | Opcode、逻辑指令、EXTENDED_ARG 编解码、指令位置与比较参数 | +| [instruction.rs](instruction.rs) | Opcode、逻辑指令、EXTENDED_ARG 编解码、MakeFunction 计数构造、指令位置与比较参数 | | [code.rs](code.rs) | CodeLayout、EndorCode、共享 EndorCodeObject、操作数/控制流/栈校验 | | [frame.rs](frame.rs) | EndorFrame、栈条目、执行位置元数据、ExecutionLimits | -| [eval.rs](eval.rs) | execute/call、参数绑定、帧切换、取指分派、结果根转移与失败清理 | +| [eval.rs](eval.rs) | execute/call、参数绑定、帧切换、取指分派、字节码创建函数、结果根转移与失败清理 | ## 常见修改入口 diff --git a/crates/core/src/code.rs b/crates/core/src/code.rs index 884db8c..8c5feb6 100644 --- a/crates/core/src/code.rs +++ b/crates/core/src/code.rs @@ -3,7 +3,7 @@ use std::collections::{BTreeSet, VecDeque}; use std::sync::Arc; use crate::builtins::trace_value; -use crate::instruction::{compare_operation, decode, invalid}; +use crate::instruction::{compare_operation, decode, function_counts, invalid}; use crate::{DecodedInstruction, Instruction, ObjectPayload, ObjectRef, Opcode, Value, VmError}; /// Local slots start with positional parameters. Captured locals occupy their @@ -330,6 +330,11 @@ fn verify( .ok_or_else(|| invalid(instruction.offset, "map operand overflow"))?; (consumed, consumed, 1) } + Opcode::MakeFunction => { + let (defaults, closure) = function_counts(instruction.instruction.argument); + let consumed = defaults + closure + 1; + (consumed, consumed, 1) + } Opcode::ReturnValue => (1, 1, 0), _ => (0, 0, 0), }; diff --git a/crates/core/src/eval.rs b/crates/core/src/eval.rs index 5e9dc2f..2618fb3 100644 --- a/crates/core/src/eval.rs +++ b/crates/core/src/eval.rs @@ -1,6 +1,6 @@ use crate::code::jump_target; use crate::frame::StackEntry; -use crate::instruction::{compare_operation, invalid}; +use crate::instruction::{compare_operation, function_counts, invalid}; use crate::{ CompareOp, EndorCellObject, EndorCodeObject, EndorFrame, EndorFunctionObject, EndorStringObject, ExecutionLimits, ExecutionLocation, ObjectRef, Opcode, Value, Vm, VmError, @@ -314,6 +314,31 @@ impl Vm { Ok(dictionary) } + fn make_function(&mut self, frame: &EndorFrame, argument: u32) -> Result { + let (default_count, closure_count) = function_counts(argument); + let Value::Object(code) = Self::top_value(frame)? else { + return Err(VmError::ObjectKindMismatch("code")); + }; + let name = self + .payload::(code, "code")? + .code() + .name() + .to_owned(); + let start = frame.stack.len() - default_count - closure_count - 1; + let defaults = (start..start + default_count) + .map(|index| Self::stack_value(frame, index)) + .collect::, _>>()?; + let closure = (start + default_count..frame.stack.len() - 1) + .map(|index| match Self::stack_value(frame, index)? { + Value::Object(cell) => Ok(cell), + _ => Err(VmError::ObjectKindMismatch("cell")), + }) + .collect::, _>>()?; + // Keep every input root on the stack until allocation has validated + // and retained all function edges. Failure leaves cleanup to the frame. + self.new_function(name, code, frame.globals, defaults, closure) + } + fn eval_frames( &mut self, frames: &mut Vec, @@ -528,6 +553,12 @@ impl Vm { .stack .push(StackEntry::Value(Value::Object(dictionary))); } + Opcode::MakeFunction => { + let function = self.make_function(frame, decoded.instruction.argument)?; + let (defaults, closure) = function_counts(decoded.instruction.argument); + self.discard(frame, defaults + closure + 1); + frame.stack.push(StackEntry::Value(Value::Object(function))); + } Opcode::ExtendedArg => unreachable!("EXTENDED_ARG was decoded before evaluation"), } } diff --git a/crates/core/src/instruction.rs b/crates/core/src/instruction.rs index 1e730e3..0d09756 100644 --- a/crates/core/src/instruction.rs +++ b/crates/core/src/instruction.rs @@ -33,6 +33,7 @@ pub enum Opcode { LoadAttr = 25, StoreAttr = 26, BuildMap = 27, + MakeFunction = 28, ExtendedArg = 255, } @@ -67,6 +68,7 @@ impl Opcode { 25 => Self::LoadAttr, 26 => Self::StoreAttr, 27 => Self::BuildMap, + 28 => Self::MakeFunction, 255 => Self::ExtendedArg, _ => return None, }) @@ -100,6 +102,17 @@ impl Instruction { Self::new(opcode, 0) } + /// Build a function from [defaults..., closure cells..., code] on the stack. + /// The low 16 operand bits count trailing positional defaults; the high + /// 16 bits count cells in the code's free-variable order. No tuples or + /// subsequent mutation of the function object are required. + pub const fn make_function(defaults: u16, closure: u16) -> Self { + Self::new( + Opcode::MakeFunction, + (defaults as u32) | ((closure as u32) << 16), + ) + } + pub const fn compare(operation: CompareOp) -> Self { Self::new( Opcode::CompareOp, @@ -198,6 +211,10 @@ pub(crate) fn compare_operation(argument: u32) -> Option { }) } +pub(crate) fn function_counts(argument: u32) -> (usize, usize) { + ((argument & 0xffff) as usize, (argument >> 16) as usize) +} + pub(crate) fn invalid(offset: usize, reason: &'static str) -> VmError { VmError::InvalidBytecode { offset, reason } } diff --git a/crates/core/tests/README.md b/crates/core/tests/README.md index 3d86d1f..e0eeec5 100644 --- a/crates/core/tests/README.md +++ b/crates/core/tests/README.md @@ -6,6 +6,6 @@ | --- | --- | | [runtime.rs](runtime.rs) | 空堆启动、默认操作、引用/借用、属性原子性、稳定地址、长链/循环、原生回调、VM 销毁 | | [builtins.rs](builtins.rs) | 无损 Number、Unicode 字符串、Dict 顺序与键、Cell、函数环境/共享代码、异常数据 | -| [execution.rs](execution.rs) | wordcode 与校验、控制流、参数/默认值、嵌套帧、闭包、返回值存活、失败清理、限额 | +| [execution.rs](execution.rs) | wordcode 与校验、控制流、参数/默认值、嵌套帧、字节码函数创建/默认值/环境捕获、闭包、返回值存活、失败清理、限额 | 堆内部伪造句柄与计数溢出检查在 src/heap.rs,公开示例由 src/lib.rs rustdoc 提供。文件职责见 [src](../src/README.md),检查方式和结果边界见 [验证](../../../docs/operations/validation.md)。本地不执行测试;存在测试文件不代表某次提交已通过。返回 [core](../README.md)。 diff --git a/crates/core/tests/execution.rs b/crates/core/tests/execution.rs index 871f76d..6cd7bce 100644 --- a/crates/core/tests/execution.rs +++ b/crates/core/tests/execution.rs @@ -3,8 +3,9 @@ use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; use endor_core::{ - CodeLayout, CompareOp, EndorCode, EndorCodeObject, EndorNativeFunctionObject, ExecutionLimits, - Instruction, ObjectPayload, ObjectRef, Opcode, Value, Vm, VmError, + CodeLayout, CompareOp, EndorCode, EndorCodeObject, EndorFunctionObject, + EndorNativeFunctionObject, ExecutionLimits, Instruction, ObjectPayload, ObjectRef, Opcode, + Value, Vm, VmError, }; fn op(opcode: Opcode, argument: u32) -> Instruction { @@ -759,3 +760,505 @@ fn implicit_receiver_slot_is_distinct_from_the_null_call_marker() { // Nil is an actual implicit argument here, not the internal NULL marker. assert_eq!(vm.execute(code, globals).unwrap(), Value::Nil); } + +#[test] +fn function_creation_operands_round_trip_and_require_all_inputs() { + assert_eq!( + Instruction::encode_all(&[Instruction::make_function(1, 1)]).unwrap(), + vec![255, 1, 255, 0, 28, 1] + ); + assert_eq!( + Instruction::encode_all(&[Instruction::make_function(u16::MAX, u16::MAX)]).unwrap(), + vec![255, 255, 255, 255, 255, 255, 28, 255] + ); + let code = EndorCode::from_instructions( + &[ + op(Opcode::LoadConst, 0), + op(Opcode::LoadConst, 0), + op(Opcode::LoadConst, 0), + Instruction::make_function(1, 1), + simple(Opcode::ReturnValue), + ], + vec![Value::Nil], + CodeLayout::default(), + ) + .unwrap(); + assert_eq!( + code.instructions()[3].instruction, + Instruction::make_function(1, 1) + ); + assert_eq!(code.instructions()[3].next_offset, 6); + assert_eq!(code.stack_size(), 3); + for (defaults, closure) in [(0, 0), (1, 0), (0, 1), (1, 1), (u16::MAX, u16::MAX)] { + assert!(matches!( + EndorCode::from_instructions( + &[ + Instruction::make_function(defaults, closure), + simple(Opcode::ReturnValue) + ], + vec![], + CodeLayout::default(), + ), + Err(VmError::InvalidBytecode { .. }) + )); + } + for creation in [ + Instruction::make_function(1, 0), + Instruction::make_function(0, 1), + ] { + assert!( + EndorCode::from_instructions( + &[ + op(Opcode::LoadConst, 0), + creation, + simple(Opcode::ReturnValue) + ], + vec![Value::Nil], + CodeLayout::default(), + ) + .is_err() + ); + } +} + +#[test] +fn bytecode_defines_global_functions_and_calls_them_with_defaults() { + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + let inner = EndorCode::from_instructions( + &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], + vec![], + CodeLayout { + parameters: vec!["value".into()], + locals: vec!["value".into()], + ..CodeLayout::default() + }, + ) + .unwrap() + .with_location("identity", "functions.endor", 2); + let inner = vm.new_code(inner).unwrap(); + let module = make_code( + &mut vm, + &[ + op(Opcode::LoadConst, 0), + op(Opcode::LoadConst, 1), + Instruction::make_function(1, 0), + op(Opcode::StoreGlobal, 0), + op(Opcode::LoadGlobal, 0), + simple(Opcode::PushNull), + op(Opcode::Call, 0), + simple(Opcode::ReturnValue), + ], + vec![text("default"), Value::Object(inner)], + CodeLayout { + names: vec!["identity".into()], + ..CodeLayout::default() + }, + ); + vm.release(inner).unwrap(); + assert_eq!(vm.execute(module, globals).unwrap(), text("default")); + let Some(Value::Object(first)) = vm.dict_get(globals, &text("identity")).unwrap() else { + panic!("expected a function"); + }; + vm.retain(first).unwrap(); + let data = vm + .payload::(first, "function") + .unwrap(); + assert_eq!(data.name(), "identity"); + assert_eq!(data.code(), inner); + assert_eq!(data.globals(), globals); + assert_eq!( + vm.call(first, &[text("explicit")]).unwrap(), + text("explicit") + ); + assert_eq!(vm.execute(module, globals).unwrap(), text("default")); + let Some(Value::Object(second)) = vm.dict_get(globals, &text("identity")).unwrap() else { + panic!("expected a second function"); + }; + assert_ne!(first, second); + assert_eq!( + vm.payload::(second, "function") + .unwrap() + .code(), + inner + ); + vm.release(first).unwrap(); + vm.release(module).unwrap(); + vm.release(globals).unwrap(); + // The global function, its code and its globals form a collectible cycle. + assert_eq!(vm.collect_cycles(), 3); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn bytecode_function_defaults_keep_temporary_objects_alive_after_factory_returns() { + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + let inner = make_code( + &mut vm, + &[op(Opcode::LoadFast, 1), simple(Opcode::ReturnValue)], + vec![], + CodeLayout { + parameters: vec!["required".into(), "defaulted".into(), "tag".into()], + locals: vec!["required".into(), "defaulted".into(), "tag".into()], + ..CodeLayout::default() + }, + ); + let factory = make_code( + &mut vm, + &[ + op(Opcode::BuildMap, 0), + op(Opcode::LoadConst, 1), + op(Opcode::LoadConst, 0), + Instruction::make_function(2, 0), + simple(Opcode::ReturnValue), + ], + vec![Value::Object(inner), text("tag")], + CodeLayout::default(), + ); + vm.release(inner).unwrap(); + let Value::Object(function) = vm.execute(factory, globals).unwrap() else { + panic!("expected a function"); + }; + vm.release(factory).unwrap(); + vm.release(globals).unwrap(); + assert!(matches!( + vm.call(function, &[]), + Err(VmError::ArgumentCount { required: 1, .. }) + )); + let first = vm.call(function, &[Value::Nil]).unwrap(); + let second = vm.call(function, &[Value::Nil]).unwrap(); + assert_eq!(first, second); // Defaults are evaluated once per creation. + let Value::Object(default) = first else { + panic!("expected a default map"); + }; + assert_eq!(vm.ref_count(default).unwrap(), 3); // Function and two results. + vm.release(function).unwrap(); + vm.release_value(first).unwrap(); + assert_eq!(vm.ref_count(default).unwrap(), 1); + vm.release_value(second).unwrap(); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn bytecode_nested_functions_share_cells_and_bind_defaults_after_outer_returns() { + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + let read = make_code( + &mut vm, + &[op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)], + vec![], + CodeLayout { + free_variables: vec!["x".into(), "sentinel".into()], + ..CodeLayout::default() + }, + ); + let write = make_code( + &mut vm, + &[ + op(Opcode::LoadFast, 0), + op(Opcode::StoreDeref, 1), + op(Opcode::LoadDeref, 1), + simple(Opcode::ReturnValue), + ], + vec![], + CodeLayout { + parameters: vec!["value".into()], + locals: vec!["value".into()], + free_variables: vec!["x".into(), "sentinel".into()], + ..CodeLayout::default() + }, + ); + let outer = make_code( + &mut vm, + &[ + op(Opcode::LoadConst, 0), + op(Opcode::LoadClosure, 0), + op(Opcode::LoadClosure, 1), + op(Opcode::LoadConst, 1), + Instruction::make_function(0, 2), + op(Opcode::LoadConst, 2), + op(Opcode::LoadConst, 3), + op(Opcode::LoadClosure, 0), + op(Opcode::LoadClosure, 1), + op(Opcode::LoadConst, 4), + Instruction::make_function(1, 2), + op(Opcode::BuildMap, 2), + simple(Opcode::ReturnValue), + ], + vec![ + text("read"), + Value::Object(read), + text("write"), + text("default"), + Value::Object(write), + ], + CodeLayout { + parameters: vec!["x".into(), "sentinel".into()], + locals: vec!["x".into(), "sentinel".into()], + cell_variables: vec!["x".into(), "sentinel".into()], + ..CodeLayout::default() + }, + ); + let outer_function = vm + .new_function("outer", outer, globals, vec![], vec![]) + .unwrap(); + let Value::Object(pair) = vm + .call(outer_function, &[text("first"), text("sentinel")]) + .unwrap() + else { + panic!("expected a function pair"); + }; + for reference in [read, write, outer, outer_function, globals] { + vm.release(reference).unwrap(); + } + let Some(Value::Object(read)) = vm.dict_get(pair, &text("read")).unwrap() else { + panic!("expected read"); + }; + let Some(Value::Object(write)) = vm.dict_get(pair, &text("write")).unwrap() else { + panic!("expected write"); + }; + assert_eq!( + vm.payload::(read, "function") + .unwrap() + .closure(), + vm.payload::(write, "function") + .unwrap() + .closure(), + ); + assert_eq!(vm.collect_cycles(), 0); + assert_eq!(vm.call(read, &[]).unwrap(), text("first")); + assert_eq!(vm.call(write, &[text("updated")]).unwrap(), text("updated")); + assert_eq!(vm.call(read, &[]).unwrap(), text("updated")); + assert_eq!(vm.call(write, &[]).unwrap(), text("default")); + assert_eq!(vm.call(read, &[]).unwrap(), text("default")); + vm.release(pair).unwrap(); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn bytecode_function_creation_rejects_bad_inputs_without_leaking_roots() { + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + let plain = return_constant(&mut vm, Value::Nil); + let captured = make_code( + &mut vm, + &[op(Opcode::LoadDeref, 0), simple(Opcode::ReturnValue)], + vec![], + CodeLayout { + free_variables: vec!["x".into()], + ..CodeLayout::default() + }, + ); + let cell = vm.new_cell(None).unwrap(); + let mismatch = VmError::InvalidArgument("function defaults or closure do not match code"); + let cases = [ + ( + vec![op(Opcode::LoadConst, 0), Instruction::make_function(0, 0)], + vec![Value::Nil], + VmError::ObjectKindMismatch("code"), + ), + ( + vec![op(Opcode::BuildMap, 0), Instruction::make_function(0, 0)], + vec![], + VmError::ObjectKindMismatch("code"), + ), + ( + vec![simple(Opcode::PushNull), Instruction::make_function(0, 0)], + vec![], + invalid_null(), + ), + ( + vec![ + op(Opcode::BuildMap, 0), + op(Opcode::LoadConst, 0), + Instruction::make_function(1, 0), + ], + vec![Value::Object(plain)], + mismatch.clone(), + ), + ( + vec![op(Opcode::LoadConst, 0), Instruction::make_function(0, 0)], + vec![Value::Object(captured)], + mismatch.clone(), + ), + ( + vec![ + op(Opcode::LoadConst, 0), + op(Opcode::LoadConst, 1), + Instruction::make_function(0, 1), + ], + vec![Value::Object(cell), Value::Object(plain)], + mismatch, + ), + ( + vec![ + op(Opcode::LoadConst, 0), + op(Opcode::LoadConst, 1), + Instruction::make_function(0, 1), + ], + vec![Value::Nil, Value::Object(captured)], + VmError::ObjectKindMismatch("cell"), + ), + ( + vec![ + op(Opcode::BuildMap, 0), + op(Opcode::LoadConst, 0), + Instruction::make_function(0, 1), + ], + vec![Value::Object(captured)], + VmError::ObjectKindMismatch("cell"), + ), + ( + vec![ + simple(Opcode::PushNull), + op(Opcode::LoadConst, 0), + Instruction::make_function(0, 1), + ], + vec![Value::Object(captured)], + invalid_null(), + ), + ( + vec![ + simple(Opcode::PushNull), + op(Opcode::LoadConst, 0), + Instruction::make_function(1, 0), + ], + vec![Value::Object(plain)], + invalid_null(), + ), + ]; + for (mut instructions, constants, expected) in cases { + instructions.push(simple(Opcode::ReturnValue)); + let code = make_code(&mut vm, &instructions, constants, CodeLayout::default()); + let count = vm.object_count(); + let roots = + [plain, captured, cell, globals].map(|reference| vm.ref_count(reference).unwrap()); + let error = vm.execute(code, globals).unwrap_err(); + if matches!(expected, VmError::InvalidBytecode { .. }) { + assert!(matches!( + error, + VmError::InvalidBytecode { + reason: "NULL call marker used as a value", + .. + } + )); + } else { + assert_eq!(error, expected); + } + assert_eq!(vm.object_count(), count); + assert_eq!( + [plain, captured, cell, globals].map(|reference| vm.ref_count(reference).unwrap()), + roots + ); + vm.release(code).unwrap(); + } + for reference in [plain, captured, cell, globals] { + vm.release(reference).unwrap(); + } + assert_eq!(vm.object_count(), 0); +} + +fn invalid_null() -> VmError { + VmError::InvalidBytecode { + offset: 0, + reason: "NULL call marker used as a value", + } +} + +#[test] +fn bytecode_functions_capture_builtins_at_creation_and_share_live_globals() { + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + let original = vm.new_dict().unwrap(); + let replacement = vm.new_dict().unwrap(); + vm.dict_set(original, text("answer"), text("original")) + .unwrap(); + vm.dict_set(replacement, text("answer"), text("replacement")) + .unwrap(); + vm.dict_set(globals, text("__builtins__"), Value::Object(original)) + .unwrap(); + let inner = make_code( + &mut vm, + &[op(Opcode::LoadGlobal, 0), simple(Opcode::ReturnValue)], + vec![], + CodeLayout { + names: vec!["answer".into()], + ..CodeLayout::default() + }, + ); + let factory = make_code( + &mut vm, + &[ + op(Opcode::LoadConst, 0), + Instruction::make_function(0, 0), + simple(Opcode::ReturnValue), + ], + vec![Value::Object(inner)], + CodeLayout::default(), + ); + let Value::Object(first) = vm.execute(factory, globals).unwrap() else { + panic!("expected first function"); + }; + vm.dict_set(globals, text("__builtins__"), Value::Object(replacement)) + .unwrap(); + let Value::Object(second) = vm.execute(factory, globals).unwrap() else { + panic!("expected second function"); + }; + for reference in [original, replacement, factory, inner, globals] { + vm.release(reference).unwrap(); + } + assert_eq!(vm.call(first, &[]).unwrap(), text("original")); + assert_eq!(vm.call(second, &[]).unwrap(), text("replacement")); + vm.dict_set(globals, text("answer"), text("global")) + .unwrap(); + assert_eq!(vm.call(first, &[]).unwrap(), text("global")); + assert_eq!(vm.call(second, &[]).unwrap(), text("global")); + vm.release(first).unwrap(); + vm.release(second).unwrap(); + assert_eq!(vm.object_count(), 0); +} + +#[test] +fn later_execution_failure_releases_a_new_function_and_its_temporary_default() { + let mut vm = Vm::new().unwrap(); + let globals = vm.new_dict().unwrap(); + let inner = make_code( + &mut vm, + &[op(Opcode::LoadFast, 0), simple(Opcode::ReturnValue)], + vec![], + CodeLayout { + parameters: vec!["value".into()], + locals: vec!["value".into()], + ..CodeLayout::default() + }, + ); + let fail = vm + .allocate(EndorNativeFunctionObject::new("fail", fail)) + .unwrap(); + let factory = make_code( + &mut vm, + &[ + op(Opcode::LoadConst, 0), + simple(Opcode::PushNull), + op(Opcode::BuildMap, 0), + op(Opcode::LoadConst, 1), + Instruction::make_function(1, 0), + op(Opcode::Call, 1), + simple(Opcode::ReturnValue), + ], + vec![Value::Object(fail), Value::Object(inner)], + CodeLayout::default(), + ); + vm.release(fail).unwrap(); + vm.release(inner).unwrap(); + let count = vm.object_count(); + assert_eq!( + vm.execute(factory, globals), + Err(VmError::InvalidArgument("host failure")) + ); + assert_eq!(vm.object_count(), count); + vm.release(factory).unwrap(); + vm.release(globals).unwrap(); + assert_eq!(vm.object_count(), 0); +} diff --git a/docs/README.md b/docs/README.md index a168d23..802343b 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,7 +9,7 @@ | 各个 crate 如何分层,哪些能力已实现 | [架构](architecture/README.md) | [crates](../crates/README.md) | | EndorObject、对象头、句柄、默认操作 | [对象模型](runtime/objects.md) | [core/src](../crates/core/src/README.md) | | Number、String、Dict、Cell、Function、异常数据 | [基本对象](runtime/builtins.md) | [core/src](../crates/core/src/README.md) | -| 操作码、代码验证、局部槽、调用协议 | [字节码 VM](runtime/bytecode.md) | instruction.rs / code.rs / frame.rs / eval.rs;见 [文件表](../crates/core/src/README.md) | +| 操作码、代码验证、局部槽、函数定义与调用协议 | [字节码 VM](runtime/bytecode.md) | instruction.rs / code.rs / frame.rs / eval.rs;见 [文件表](../crates/core/src/README.md) | | 基础信息保存在哪里、旧库如何迁移 | [宿主文件信息](operations/host-information.md) | [host](../crates/host/README.md) | | UTF-8 在日本地区是否适用,其他编码怎样进入 VM | [文本编码](runtime/text-encoding.md) | [core](../crates/core/README.md)、[host](../crates/host/README.md) | | 数据库连接给谁用,是否自动建表或保存基础信息 | [数据库特殊类](runtime/database-objects.md) | [storage](../crates/storage/README.md) | diff --git a/docs/architecture/README.md b/docs/architecture/README.md index c4bd01d..49edfb0 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -26,7 +26,7 @@ CLI、Server 依赖 Host 保存文件信息,不依赖 Storage;Core 与 Stora - Vm 从空堆启动。EndorObject 是实现层公共结构,不是语言根类;没有 Python type、继承或元类。 - 对象地址稳定,公开句柄校验 VM 身份和槽位代数;句柄不是跨进程 ID。 - 引用计数负责通常的释放,显式全堆追踪负责不可达循环;执行帧中的对象值也作为根。 -- 函数共享不可变代码对象,语言调用使用显式帧向量;字节码参考 CPython 的职责划分,不兼容其操作码 ABI。 +- 函数共享不可变代码对象,既可由宿主工厂创建,也可由 MakeFunction 字节码绑定默认值与闭包后创建;语言调用使用显式帧向量;字节码参考 CPython 的职责划分,不兼容其操作码 ABI。 - Number 优先无损表示;数值算术、相等和哈希尚未定义。异常对象保存数据,尚无 raise/try/except 传播。 - 内核不读取环境或修改 locale。宿主处理监听和信号;数据库配置由真正消费它的对象适配层显式传入,见 [配置](../operations/configuration.md)。 diff --git a/docs/contributing/handoff.md b/docs/contributing/handoff.md index a994915..bf8181c 100644 --- a/docs/contributing/handoff.md +++ b/docs/contributing/handoff.md @@ -10,7 +10,7 @@ - EndorObject、稳定 Box、代际句柄、显式强引用及手动循环回收;VM 从空堆启动,没有 Python type/元类或语言根类。 - Function、NativeFunction、Dict、Cell、异常数据、UTF-8 String 和无损表示优先的 Number。 -- 小型 wordcode、共享代码对象、操作数/控制流/栈校验、显式执行帧及 execute/call。对象返回值持有根,用后 release_value;借用读取不自动 retain。 +- 小型 wordcode、共享代码对象、操作数/控制流/栈校验、显式执行帧及 execute/call;MakeFunction 可在执行中创建带默认参数与共享闭包 Cell 的函数。对象返回值持有根,用后 release_value;借用读取不自动 retain。 - Host 文件信息独立于数据库:host.version、deployment.release 与 metadata put/get 使用 UTF-8 JSON。旧 PostgreSQL 基础信息只读导入缺失文件,不删除原库,见 [文件信息](../operations/host-information.md)。 - Storage 只提供显式 DatabaseConfig/DatabaseConnection;没有默认数据库、自动应用建表或基础信息 KV。特殊类的名称/接口尚未定义,没有将连接接入 VM。 - CLI 引导与文件信息,Server HTTP 存活/只读 VM 信息;生产 PostgreSQL 作为未来特殊类的预置资源保留。 @@ -21,7 +21,7 @@ ## 尚未实现 -源码 lexer/parser、MAKE_FUNCTION 等扩展指令、数值算术/相等/哈希、完整异常传播、可变 VM/异步数据库回调、生成器、自动 GC、堆快照、内存配额、取消及不可信代码沙箱。宿主回调 panic 后的执行根恢复也未完成。不能把未来设计写成已有能力。 +源码 lexer/parser 与编译器、迭代器等扩展指令、数值算术/相等/哈希、完整异常传播、可变 VM/异步数据库回调、生成器、自动 GC、堆快照、内存配额、取消及不可信代码沙箱。宿主回调 panic 后的执行根恢复也未完成。不能把未来设计写成已有能力。 ## 工作约定 @@ -33,4 +33,4 @@ ## 下一步 -用户下一步尚未指定。先完成交接确认,不自动启动解析器、改数据库类接口、运行测试或跟踪 CI。需要继续哪个方向时由用户决定。 +函数创建指令完成后可以开始设计源码前端:先明确 Endor 的最小语法、token 与 AST,再依次实现 lexer、parser、名称/闭包分析及字节码生成。目标路径为源码 → token → AST → 现有字节码 → VM,不改成 AST 直接解释。最初可覆盖字符串/布尔/Nil、变量、位置参数函数、调用、return 和闭包;数字运算与异常传播按后续需求补齐,不作为开始前端的前置要求。当前尚未启动前端实现,也未把 Python 示例视作 Endor 语法规范;前端包的边界与语法仍待确定。 diff --git a/docs/operations/validation.md b/docs/operations/validation.md index 3fcdfdc..8f24201 100644 --- a/docs/operations/validation.md +++ b/docs/operations/validation.md @@ -27,7 +27,7 @@ ci-ces-control.sh 负责准备全新专用临时 PostgreSQL 和测试 URL;成 | 堆内部边界 | 非法索引、槽位代数、代数耗尽、计数溢出原子性 | [core/src](../../crates/core/src/README.md) 的 heap.rs 私有单元测试 | | 对象运行时 | 地址稳定、引用/借用、属性、长链、循环、原生操作、VM 销毁 | [core/tests](../../crates/core/tests/README.md) 的 runtime.rs | | 基本对象 | Number 表示、字符串、Dict/Cell 引用与更新、函数环境、异常数据 | 同目录 builtins.rs | -| 字节码 | 编解码、非法指令/控制流、跳转、调用、闭包、结果存活、失败清理与限额 | 同目录 execution.rs | +| 字节码 | 编解码、非法指令/控制流、跳转、调用、闭包、字节码创建函数/默认值/环境捕获、函数创建及后续失败清理、结果存活与限额 | 同目录 execution.rs | | 公开示例 | 对象操作、手写指令执行与释放 | [core/src](../../crates/core/src/README.md) 的 lib.rs rustdoc | | 宿主文件信息 | UTF-8 值/路径、持久读取、同键替换、并发、坏记录、幂等迁移与保留新值 | [host/tests](../../crates/host/tests/README.md) | | 数据库连接 | 不自动建表、显式配置、隔离/共享、调用方数据重连、失败不降级、真实 PostgreSQL | [storage/tests](../../crates/storage/tests/README.md) | diff --git a/docs/runtime/README.md b/docs/runtime/README.md index ec2c677..e035d42 100644 --- a/docs/runtime/README.md +++ b/docs/runtime/README.md @@ -6,7 +6,7 @@ | --- | --- | | [对象模型](objects.md) | EndorObject/ObjectHeader、代际句柄、操作协议、托管引用与循环回收 | | [基本对象与 Number](builtins.md) | 函数、原生函数、字典、Cell、异常数据、字符串与无损数字 | -| [字节码 VM](bytecode.md) | 指令编码、代码校验、局部槽、执行帧、调用/返回与边界 | +| [字节码 VM](bytecode.md) | 指令编码、代码校验、局部槽、执行帧、函数创建、调用/返回与边界 | | [文本编码](text-encoding.md) | UTF-8、日文、外部 Shift_JIS/CP932 与宿主解码边界 | | [数据库特殊类](database-objects.md) | 显式连接、调用方数据模型、未来类接入与资源生命周期 | | [生命周期](lifecycle.md) | 宿主持有 Vm、帧执行根、结果所有权、错误退出和整体销毁 | diff --git a/docs/runtime/builtins.md b/docs/runtime/builtins.md index 4c959e4..c0fcf06 100644 --- a/docs/runtime/builtins.md +++ b/docs/runtime/builtins.md @@ -18,7 +18,7 @@ ## Function 与 NativeFunction -Python 代码中的 `def greet(name): ...` 创建的是语言函数。函数需要关联代码,以及代码执行时使用的全局环境、默认参数和闭包。`EndorFunctionObject` 保存这些信息;分配时验证 globals 是字典、closure 中每一项是 Cell,并检查默认参数数量、闭包数量和代码的参数信息一致。 +Python 代码中的 `def greet(name): ...` 创建的是语言函数。函数需要关联代码,以及代码执行时使用的全局环境、默认参数和闭包。宿主可调用 new_function 工厂,执行中的字节码也可使用 [MakeFunction](bytecode.md#在字节码中创建函数) 创建函数;两条路径使用相同校验。`EndorFunctionObject` 保存这些信息;分配时验证 globals 是字典、closure 中每一项是 Cell,并检查默认参数数量、闭包数量和代码的参数信息一致。 CPython 的 `len`、`print` 等宿主实现的内建函数则通过 C 函数入口调用。Endor 使用 Rust,所以命名为 `EndorNativeFunctionObject`,不把 C 写入名称。 @@ -119,7 +119,7 @@ fn main() -> Result<(), endor_core::VmError> { } ``` -当前已能通过字节码读取、写入和删除共享 Cell,并创建捕获参数的 Cell。上面的 Python 闭包示例说明语义目标;Endor 尚无 Python 源码解析器,函数对象目前由宿主工厂建立。 +当前已能通过字节码读取、写入和删除共享 Cell,并创建捕获参数的 Cell。LoadClosure 与 MakeFunction 可以在外围函数运行时创建共享这些 Cell 的嵌套函数,外围帧返回后 Cell 仍由闭包保活。上面的 Python 代码说明闭包语义;Endor 尚无源码解析器,代码对象仍由宿主准备。 ## 字符串与异常的准确含义 diff --git a/docs/runtime/bytecode.md b/docs/runtime/bytecode.md index e6b9c66..d62ce67 100644 --- a/docs/runtime/bytecode.md +++ b/docs/runtime/bytecode.md @@ -22,14 +22,14 @@ 所有执行位置和跳转距离以**两字节代码单元**计数,包含扩展参数单元;不是字节偏移,也不一定等于逻辑指令下标。前跳从当前逻辑指令之后的位置加距离;后跳从该位置减距离。目标必须是完整逻辑指令的开头,不能跳进扩展参数中间。 -首版有 28 个可执行操作码和一个由编解码器处理的 EXTENDED_ARG: +当前有 29 个可执行操作码和一个由编解码器处理的 EXTENDED_ARG: | 指令组 | 内容 | | --- | --- | | 常量与局部变量 | `LoadConst`、`LoadFast`、`StoreFast`、`DeleteFast` | | 名称与属性 | `LoadGlobal`、`StoreGlobal`、`DeleteGlobal`、`LoadAttr`、`StoreAttr` | | 栈操作 | `Nop`、`PopTop`、`Copy`、`Swap`、`PushNull` | -| 调用与返回 | `Call`、`ReturnValue` | +| 函数定义、调用与返回 | `MakeFunction`、`Call`、`ReturnValue` | | 控制流 | `JumpForward`、`JumpBackward`、`PopJumpIfFalse`、`PopJumpIfTrue` | | 真值与比较 | `ToBool`、`UnaryNot`、`CompareOp` | | 闭包 | `LoadDeref`、`StoreDeref`、`DeleteDeref`、`LoadClosure` | @@ -79,6 +79,37 @@ Vm::execute(code, globals) 普通函数调用采用 `[callable, NULL, arg0, ...]` 栈布局,CALL 参数是显式位置参数个数。NULL 是内部标记,与 `Value::Nil` 不同;如果第二个位置是实际值,就把它作为隐式接收者加在参数前。尚未实现属性描述符或自动方法绑定。 +## 在字节码中创建函数 + +`MakeFunction`(操作码编号 28)将代码对象、已经求值的默认参数和共享 Cell 组装为一个新的语言函数。使用 `Instruction::make_function(default_count, closure_count)` 构造指令,两个计数均为 u16(0~65535):参数低 16 位为默认参数数,高 16 位为闭包 Cell 数;大参数继续由 EXTENDED_ARG 编解码。不含默认参数和闭包时也可使用 `Instruction::simple(Opcode::MakeFunction)`。 + +执行前后栈形状如下,右边为栈顶: + +```text +[..., default0, ..., defaultN, cell0, ..., cellM, code] +→ [..., function] +``` + +默认值按尾部位置参数的声明顺序排列,Cell 按目标代码的 free_variables 顺序排列。没有默认值或闭包时省略对应区域;每个默认值、每个 Cell 和代码对象都占一个栈项。函数名取自目标代码的 name;globals 使用当前帧的 globals,builtins 在创建时从该 globals 的可选 __builtins__ 字典捕获,与宿主 new_function 工厂一致。 + +例如,无参数的函数定义与调用可生成如下逻辑指令。函数体代码对象预先放入当前代码的常量表中: + +```text +LoadConst body_code_index +MakeFunction 0 +StoreGlobal function_name_index +LoadGlobal function_name_index +PushNull +Call 0 +ReturnValue +``` + +嵌套函数使用 `LoadClosure` 取出当前帧的共享 Cell,再压入内层代码对象并执行 `MakeFunction`。创建函数不运行函数体;默认值表达式应先由外围代码求值,创建时保存其结果,因此同一函数的后续调用共享这些默认值。重复执行创建指令会产生不同函数对象,它们可以共享同一代码对象。 + +静态校验根据两个计数检查所需栈高度、合流高度和最大栈深。执行时复用 new_function 校验代码对象种类、默认参数数量、闭包数量与 Cell 种类;NULL 调用标记不能用作任何输入值。类型和数量不匹配通过现有 VmError 返回。所有输入在完整校验及分配成功前都保持在栈中,成功后由函数持有其托管引用,再消费输入并压入函数的一份根;失败时沿现有帧清理路径释放输入。 + +函数创建后不提供修改其默认值或闭包的指令。当前一条 MakeFunction 直接构造完整函数,不要求 tuple 对象或 CPython 3.13 起的 SET_FUNCTION_ATTRIBUTE;操作数格式和操作码编号均为 Endor 自有协议。 + ## 对象引用与返回值 每个帧持有函数的一份执行根,函数继续保活代码、全局字典、内建字典、默认值及闭包。局部槽和求值栈中的每个对象值也各自持有执行根,重复出现分别计数。执行根复用现有外部强引用计数,所以循环回收能保留它们。 @@ -117,7 +148,7 @@ fn main() -> Result<(), endor_core::VmError> { } ``` -该示例直接提供指令,不表示已经支持 Endor 或 Python 源码解析。函数和嵌套函数目前由宿主工厂创建,尚无 MAKE_FUNCTION 指令;后续编译器接入时再补函数定义所需的操作。 +该示例直接提供指令,不表示已经支持 Endor 或 Python 源码解析。宿主仍负责准备代码对象和执行环境;函数既可由宿主 new_function 工厂创建,也可由上述 MakeFunction 在字节码运行期间创建。 ## 限额、诊断与边界 @@ -125,11 +156,11 @@ fn main() -> Result<(), endor_core::VmError> { 运行错误仍返回 `VmError`。`last_traceback()` 另提供从外到内的函数名、文件、代码首行和出错指令偏移;它保存独立元数据,不保活已经清理的帧。首行是代码声明的 first_line,不是由行号表推导出的实际错误源码行。成功的新一次调用会清空旧记录。尚无语言异常对象传播、raise/try/except 或完整源码行号表。 -暂未实现:lexer/parser、源码编译器、关键字/可变参数、生成器/协程、迭代器专用指令、数值算术、MAKE_FUNCTION、自动方法绑定、指令特化和 JIT。 +暂未实现:lexer/parser、源码编译器、关键字/可变参数、生成器/协程、迭代器专用指令、数值算术、自动方法绑定、指令特化和 JIT。 ## 验证 -[execution.rs](../../crates/core/tests/execution.rs) 覆盖 wordcode、控制流校验、变量、循环、嵌套调用与默认参数、闭包、容器和返回值所有权、错误清理及帧/指令限额;已有 builtins 测试已迁移共享代码对象和持有式调用返回值。lib.rs 另有可供远端 rustdoc 执行的例子。 +[execution.rs](../../crates/core/tests/execution.rs) 覆盖 wordcode、控制流校验、变量、循环、嵌套调用与默认参数、闭包、字节码函数定义/默认值/环境捕获、容器和返回值所有权、函数创建失败与后续错误清理及帧/指令限额;已有 builtins 测试已迁移共享代码对象和持有式调用返回值。lib.rs 另有可供远端 rustdoc 执行的例子。 验证命令与结果边界统一见 [验证](../operations/validation.md)。限额与环境隔离见 [配置](../operations/configuration.md),文件职责见 [core 源码导航](../../crates/core/src/README.md)。 diff --git a/docs/runtime/lifecycle.md b/docs/runtime/lifecycle.md index 8327f1c..0f6f9aa 100644 --- a/docs/runtime/lifecycle.md +++ b/docs/runtime/lifecycle.md @@ -16,6 +16,8 @@ VM 由宿主持有,VM 持有对象堆,每次调用临时创建执行帧。 ObjectRef 拷贝、Value::clone 和句柄变量离开 Rust 作用域,都不会自动 retain/release。宿主为每份显式持有配对 retain/release。借用读取在所有者删除内容前需要 retain,详见 [对象模型](objects.md)。 +MakeFunction 成功时将新函数的一份分配根压入求值栈;函数通过托管边保活代码、globals、builtins、默认值及闭包 Cell,再释放原输入栈根。返回新函数后,这些依赖不随外围帧清理而销毁。创建失败保留原栈供错误清理,创建成功后的后续错误也会释放临时函数及其独占依赖。 + ## 返回与失败 call/execute 返回 Value::Object 时,结果拥有一份根。执行器将这份根从帧移交给调用者或宿主,避免帧清理回收结果;使用后须 release_value,或取出对象句柄后 release。Native 回调原始返回值仍是借用值,由 VM 在移除输入根之前保活。