Add bytecode function creation with defaults and shared closures
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 6m28s
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 6m28s
This commit is contained in:
@@ -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、参数绑定、帧切换、取指分派、字节码创建函数、结果根转移与失败清理 |
|
||||
|
||||
## 常见修改入口
|
||||
|
||||
|
||||
@@ -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),
|
||||
};
|
||||
|
||||
+32
-1
@@ -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<ObjectRef, VmError> {
|
||||
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::<EndorCodeObject>(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::<Result<Vec<_>, _>>()?;
|
||||
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::<Result<Vec<_>, _>>()?;
|
||||
// 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<EndorFrame>,
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<CompareOp> {
|
||||
})
|
||||
}
|
||||
|
||||
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 }
|
||||
}
|
||||
|
||||
@@ -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)。
|
||||
|
||||
@@ -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::<EndorFunctionObject>(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::<EndorFunctionObject>(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::<EndorFunctionObject>(read, "function")
|
||||
.unwrap()
|
||||
.closure(),
|
||||
vm.payload::<EndorFunctionObject>(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);
|
||||
}
|
||||
|
||||
+1
-1
@@ -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) |
|
||||
|
||||
@@ -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)。
|
||||
|
||||
|
||||
@@ -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 语法规范;前端包的边界与语法仍待确定。
|
||||
|
||||
@@ -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) |
|
||||
|
||||
@@ -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、帧执行根、结果所有权、错误退出和整体销毁 |
|
||||
|
||||
@@ -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 尚无源码解析器,代码对象仍由宿主准备。
|
||||
|
||||
## 字符串与异常的准确含义
|
||||
|
||||
|
||||
@@ -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)。
|
||||
|
||||
|
||||
@@ -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 在移除输入根之前保活。
|
||||
|
||||
Reference in New Issue
Block a user