Organize living documentation and directory guides
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 13m40s
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 13m40s
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
# 运行时设计
|
||||
|
||||
本目录解释跨越 core 多个模块的运行时契约。文件与函数定位见 [core/src README](../../crates/core/src/README.md),测试定位见 [core/tests README](../../crates/core/tests/README.md)。
|
||||
|
||||
| 主题 | 内容 |
|
||||
| --- | --- |
|
||||
| [对象模型](objects.md) | EndorObject/ObjectHeader、代际句柄、操作协议、托管引用与循环回收 |
|
||||
| [基本对象与 Number](builtins.md) | 函数、原生函数、字典、Cell、异常数据、字符串与无损数字 |
|
||||
| [字节码 VM](bytecode.md) | 指令编码、代码校验、局部槽、执行帧、调用/返回与边界 |
|
||||
| [生命周期](lifecycle.md) | 宿主持有 Vm、帧执行根、结果所有权、错误退出和整体销毁 |
|
||||
|
||||
文本/环境约束和执行限额集中在 [配置](../operations/configuration.md);四个组件之间的关系见 [系统架构](../architecture/README.md)。返回 [文档索引](../README.md)。
|
||||
@@ -0,0 +1,136 @@
|
||||
# Endor 基本对象与统一 Number
|
||||
|
||||
这些对象属于虚拟机实现层的数据结构与宿主 API,不定义 Python type、语言根类或解析器;语言函数已接入 [小型字节码执行器](bytecode.md)。它们通过 `ObjectPayload` 接入现有 `EndorObject`,共同使用对象头、稳定 Box、代际句柄、引用计数和循环回收。不是每种对象重新实现一套堆管理。
|
||||
|
||||
## 对应关系
|
||||
|
||||
| CPython 中的结构 | Endor 对应结构 | 当前实现 |
|
||||
| --- | --- | --- |
|
||||
| `PyFunctionObject` | `EndorFunctionObject` | 名称、共享代码对象、全局/内建字典、默认参数和闭包 Cell;由 Vm::call 执行 |
|
||||
| `PyDictObject` | `EndorDictObject` | 保留插入顺序的键值容器,VM 管理查询、写入、删除和清空 |
|
||||
| `PyCFunctionObject` | `EndorNativeFunctionObject` | Rust 宿主函数指针、名称与调用入口;旧名 NativeFunction 保留为别名 |
|
||||
| `PyBaseExceptionObject` | `EndorBaseExceptionObject` | 异常名称、参数、cause、context、上下文抑制、备注与回溯位置 |
|
||||
| `PyASCIIObject` 等 Unicode 布局 | `EndorStringObject` | 统一 UTF-8 字符串,支持字符数、字符读取、表示、真值、比较与哈希 |
|
||||
| `PyLongObject`、`PyFloatObject` 等 | `EndorNumberObject` / `EndorNumber` | 一个对外数字接口,内部保留不同数字的无损表示 |
|
||||
| `PyCellObject` | `EndorCellObject` | 可被多个闭包共享的可变槽;区分空槽与 Nil |
|
||||
|
||||
`EndorCode` 保存经过验证的 Endor wordcode 和静态元数据,`EndorCodeObject` 将它放进受管理的堆;函数持有代码对象句柄并可共享同一份代码。指令格式、执行帧与入口见 [字节码 VM](bytecode.md)。不是 CPython 字节码格式。
|
||||
|
||||
## Function 与 NativeFunction
|
||||
|
||||
Python 代码中的 `def greet(name): ...` 创建的是语言函数。函数需要关联代码,以及代码执行时使用的全局环境、默认参数和闭包。`EndorFunctionObject` 保存这些信息;分配时验证 globals 是字典、closure 中每一项是 Cell,并检查默认参数数量、闭包数量和代码的参数信息一致。
|
||||
|
||||
CPython 的 `len`、`print` 等宿主实现的内建函数则通过 C 函数入口调用。Endor 使用 Rust,所以命名为 `EndorNativeFunctionObject`,不把 C 写入名称。
|
||||
|
||||
```rust
|
||||
use aster_core::{EndorNativeFunctionObject, Value, Vm, VmError};
|
||||
|
||||
fn echo(arguments: &[Value]) -> Result<Value, VmError> {
|
||||
match arguments {
|
||||
[value] => Ok(value.clone()),
|
||||
_ => Err(VmError::InvalidArgument("expected one value")),
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<(), VmError> {
|
||||
let mut vm = Vm::new()?;
|
||||
let function = vm.allocate(EndorNativeFunctionObject::new("echo", echo))?;
|
||||
let value = vm.call(function, &[Value::Text("hello".into())])?;
|
||||
assert_eq!(value, Value::Text("hello".into()));
|
||||
vm.release_value(value)?;
|
||||
vm.release(function)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
Native 函数和语言函数均通过 `Vm::call(&mut self, ...)` 调用。语言函数用显式帧绑定位置参数、默认值和闭包,再由取指循环执行。Native 回调仍接收借用参数、返回借用对象值,不能在回调里修改当前 VM;VM 会将对象结果 retain 后返回,因此宿主需对 call/execute 的对象结果配对 release_value。
|
||||
|
||||
## Number:优先表示,不先做重计算
|
||||
|
||||
数字统一表示为 `Value::Number(EndorNumber)`。宿主仍可直接保存数字值;需要对象身份时才使用 `vm.new_number(number)` 装箱成 `EndorNumberObject`。
|
||||
|
||||
| 内部表示 | 适用数据 | 保留的内容 |
|
||||
| --- | --- | --- |
|
||||
| Integer | 超大整数、计数、编号 | 任意长度十进制数字和符号;去掉多余前导零,零统一为非负 |
|
||||
| Decimal | 金额、精确小数、科学计数 | 原始数字、尾随零、正负号和任意长度十进制指数;完全不经过 f64 |
|
||||
| Rational | 比例、分数 | 任意长度整数分子与非零分母;统一分母为正,不自动约分 |
|
||||
| BinaryFloat | 宿主已有的 IEEE-754 数值 | 全部 64 位,包括负零、无穷、NaN 及其 payload |
|
||||
| Complex | 复数记录 | 实部与虚部各自的 Integer、Decimal、Rational 或 BinaryFloat 表示 |
|
||||
|
||||
```rust
|
||||
use aster_core::{EndorNumber, Value};
|
||||
|
||||
fn main() -> Result<(), aster_core::VmError> {
|
||||
let id = EndorNumber::integer("1234567890123456789012345678901234567890")?;
|
||||
let amount = EndorNumber::decimal("12345678901234567890.000100")?;
|
||||
let ratio = EndorNumber::rational("1", "3")?;
|
||||
let tiny = EndorNumber::decimal("1e-999999999999999999999999999999")?;
|
||||
let host_float = EndorNumber::binary_float(f64::INFINITY);
|
||||
let value = Value::Number(amount);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
`EndorInteger`、`EndorDecimal`、`EndorRational` 是内部表示结构,不是三种语言数字类。构造器验证输入;分数不能有零分母;十进制指数不转换为机器整数,所以不会因指数很长而溢出。构造任意大整数或精确小数时传文本;已经被宿主转换为 f64 而丢失的精度无法恢复。整数的 `as_i64` 是显式、可失败的窄化读取,不会静默截断。
|
||||
|
||||
首版不实现四则运算、排序、跨表示数值相等和数值哈希。Rust 的 PartialEq / Eq 只检查表示是否相同,不能用于解释语言的 `==`:`1.0` 与 `1.00` 保留不同小数位数;`2/6` 与 `1/3` 尚未求数值等价;NaN 位模式也被保留。VM 对两个数字对象的数值比较与哈希显式报不支持。因此数字现在可以作为字典的值,暂不能作为字典键,避免先定义错误的键相等规则。没有用“全部转换 f64”的方式补齐这些操作。
|
||||
|
||||
这里的“各类数字”指上述五种可表示形式,不包括符号表达式、无限展开的精确 π、带单位数量、任意位宽二进制浮点等全部数学对象;这些可在未来扩展表示协议。
|
||||
|
||||
## Dict 与 Cell 的引用所有权
|
||||
|
||||
字典使用 `Vec<(Value, Value)>` 保留插入顺序,查询目前为 O(n),没有假装已经实现 CPython 哈希表性能。键须通过哈希能力检查;支持 Nil、Bool、文本及可哈希的对象。堆字符串与直接文本按内容匹配;覆盖已有键保留首次插入的键对象和位置。可变字典、Cell 与尚未定义数值哈希的数字不能当键。
|
||||
|
||||
`dict_items`、`dict_get`、`cell_get` 返回借用内容;这与现在返回持有式结果的 call/execute 不同。宿主需要跨越删除、覆盖或容器释放继续持有一个对象时,应先 `retain`。删除字典条目不返回一个可能已经被回收的借用对象,而是返回是否删除成功。
|
||||
|
||||
字典键和值、Cell 内容、函数环境与闭包、异常参数与因果链都作为强引用参与 GC。可变容器不暴露内部可变引用:VM 先构造新载荷,验证并保留所有新边,再替换载荷和引用快照,最后释放旧边。对象自身的 Box、身份、属性和外部引用数不变。非法/跨 VM 句柄使更新失败,原值及引用计数不变。同对象替换不会在中间短暂释放它,自环、字典与 Cell 互相引用、函数与全局字典的环都能由现有循环回收处理。
|
||||
|
||||
## Cell 为什么存在
|
||||
|
||||
```python
|
||||
def outer():
|
||||
x = "first"
|
||||
def read():
|
||||
return x
|
||||
def write(value):
|
||||
nonlocal x
|
||||
x = value
|
||||
return read, write
|
||||
|
||||
read, write = outer()
|
||||
write("updated")
|
||||
print(read()) # updated
|
||||
```
|
||||
|
||||
`read` 与 `write` 必须引用同一个变量位置。若各自复制一份 `x` 的初始值,write 重新赋值后 read 就看不到变化。Cell 是这个共享位置:函数持有 Cell,Cell 持有当前值。外层函数返回后,Cell 仍被闭包持有。
|
||||
|
||||
```rust
|
||||
use aster_core::{Value, Vm};
|
||||
|
||||
fn main() -> Result<(), aster_core::VmError> {
|
||||
let mut vm = Vm::new()?;
|
||||
let cell = vm.new_cell(None)?; // 空槽,不是 Nil。
|
||||
vm.cell_set(cell, Value::Text("first".into()))?;
|
||||
vm.cell_set(cell, Value::Text("updated".into()))?;
|
||||
assert_eq!(vm.cell_get(cell)?, Value::Text("updated".into()));
|
||||
vm.cell_clear(cell)?;
|
||||
vm.release(cell)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
当前已能通过字节码读取、写入和删除共享 Cell,并创建捕获参数的 Cell。上面的 Python 闭包示例说明语义目标;Endor 尚无 Python 源码解析器,函数对象目前由宿主工厂建立。
|
||||
|
||||
## 字符串与异常的准确含义
|
||||
|
||||
`PyASCIIObject` 是 CPython Unicode 字符串实现的公共头部,不是 Python 中另外一种 ASCII 字符串类。Endor 不复刻其紧凑布局,统一用 `EndorStringObject` 表示字符串。索引按 Unicode 标量值,不按 UTF-8 字节,也不按人眼字形簇。Rust String 不接受孤立代理码点,所以并非完整复制 Python str 的所有边界行为。repr 用 Rust 的转义文本显示,不承诺 Python repr 的逐字兼容。
|
||||
|
||||
Python 语言层所有异常的顶级基类叫 `BaseException`,其下除了普通 `Exception` 分支,还有 `SystemExit`、`KeyboardInterrupt` 等。`PyBaseExceptionObject` 是这些异常的公共 C 实例数据布局,不能把这个结构名直接当成语言类名。
|
||||
|
||||
Endor 当前用 `EndorBaseExceptionObject` 保存公共数据,名称只是错误标签;尚无异常继承和按类匹配机制,也没有 raise / try / except 执行流程。异常数据中的回溯保存独立位置记录;执行器另通过 last_traceback 提供已经清理的执行帧位置元信息。宿主 API 的 `VmError` 仍表示操作失败,不会自动分配语言异常或把全部错误强制变成堆对象。
|
||||
|
||||
来源:[CPython 函数对象](https://docs.python.org/3.14/c-api/function.html)、[Cell 对象](https://docs.python.org/3.14/c-api/cell.html)、[异常说明](https://docs.python.org/3.14/library/exceptions.html)、[Unicode 对象](https://docs.python.org/3.14/c-api/unicode.html)。实现不承诺 CPython ABI 或默认 Python 兼容性。
|
||||
|
||||
## 验证入口
|
||||
|
||||
对象行为检查在 [core/tests](../../crates/core/tests/README.md) 的 builtins.rs;执行行为在 execution.rs;验证方式见 [验证](../operations/validation.md)。文档描述当前契约,不代表某次 CI 已通过。CPython 284 项研究表保持为工作区外的研究材料,不纳入项目文档或 Git。
|
||||
@@ -0,0 +1,136 @@
|
||||
# 小型字节码 VM
|
||||
|
||||
当前提供指令与代码对象、内部执行帧、取指执行循环,语言函数已接入执行器。参考 CPython 3.14 的职责划分和栈式执行设计;不实现 lexer/parser,不读取 Python `.pyc`,不承诺 CPython 操作码编号、ABI 或完整语言兼容性。
|
||||
|
||||
## 与 CPython 的对应
|
||||
|
||||
| 职责 | CPython 3.14 | Endor |
|
||||
| --- | --- | --- |
|
||||
| 指令定义与解码 | opcode/oparg、`_Py_CODEUNIT`、`EXTENDED_ARG` | `Opcode`、`Instruction`、`DecodedInstruction` |
|
||||
| 保存可执行代码 | `PyCodeObject` | 堆中的 `EndorCodeObject`,持有不可变 `EndorCode` |
|
||||
| 一次调用的状态 | `_PyInterpreterFrame` | `EndorFrame` |
|
||||
| 执行入口 | `PyEval_EvalCode`、函数调用入口 | `Vm::execute`、`Vm::call` |
|
||||
| 帧求值 | `_PyEval_EvalFrameDefault` | `eval_frames` 的取指/分派循环 |
|
||||
|
||||
`Vm::new()` 仍从空堆启动,按宿主需求创建代码、全局字典和函数。没有自动加载 Python type、语言根类或默认 print。
|
||||
|
||||
## 指令:要做什么
|
||||
|
||||
`Instruction` 是一个操作码与 u32 参数。实际 wordcode 每个代码单元为 `[opcode: u8, argument: u8]`,先操作码、后参数,与机器端序无关。大于 255 的参数在前面使用最多三个 `EXTENDED_ARG` 单元补足高位。单个逻辑指令因此可以占多个代码单元。
|
||||
|
||||
`Instruction::encode_all` 接受逻辑指令并自动生成扩展参数。`EndorCode::with_layout` 也可以接收原始字节序列,先解码并验证再使用。
|
||||
|
||||
所有执行位置和跳转距离以**两字节代码单元**计数,包含扩展参数单元;不是字节偏移,也不一定等于逻辑指令下标。前跳从当前逻辑指令之后的位置加距离;后跳从该位置减距离。目标必须是完整逻辑指令的开头,不能跳进扩展参数中间。
|
||||
|
||||
首版有 28 个可执行操作码和一个由编解码器处理的 EXTENDED_ARG:
|
||||
|
||||
| 指令组 | 内容 |
|
||||
| --- | --- |
|
||||
| 常量与局部变量 | `LoadConst`、`LoadFast`、`StoreFast`、`DeleteFast` |
|
||||
| 名称与属性 | `LoadGlobal`、`StoreGlobal`、`DeleteGlobal`、`LoadAttr`、`StoreAttr` |
|
||||
| 栈操作 | `Nop`、`PopTop`、`Copy`、`Swap`、`PushNull` |
|
||||
| 调用与返回 | `Call`、`ReturnValue` |
|
||||
| 控制流 | `JumpForward`、`JumpBackward`、`PopJumpIfFalse`、`PopJumpIfTrue` |
|
||||
| 真值与比较 | `ToBool`、`UnaryNot`、`CompareOp` |
|
||||
| 闭包 | `LoadDeref`、`StoreDeref`、`DeleteDeref`、`LoadClosure` |
|
||||
| 容器 | `BuildMap` |
|
||||
|
||||
没有把 Number 转为 f64 来补运算。数字仍以无损表示为主;真假可以根据是否为零判断,数值比较和算术尚未定义。文本可作内容比较,普通对象使用现有比较接口。
|
||||
|
||||
代码构造时检查:未知操作码、奇数字节长度、悬空/过长扩展参数、非法索引、错误 Cell 槽、无效跳转、栈下溢、控制流汇合的栈高度不一致,以及可达路径跌出代码末尾。返回时栈必须只剩一个返回值。验证同时计算最大求值栈深度。NULL 调用标记的实际用法在执行时继续检查,它不能作为语言值返回。
|
||||
|
||||
## 代码对象:保存静态信息
|
||||
|
||||
`EndorCode` 保存 wordcode、解码结果、常量、名称表、局部变量布局、参数与自由变量信息、最大栈深度、代码名和源码位置元信息。它是不可变 Rust 数据。
|
||||
|
||||
`vm.new_code(code)` 把数据放进 `EndorCodeObject`,分配一个受管理的堆对象。代码对象持有常量中的对象引用,并参与引用计数和循环回收;函数持有代码对象句柄,所以多个函数可共享同一代码对象,代码常量也只由这一对象统一持有。
|
||||
|
||||
`CodeLayout` 规定:
|
||||
|
||||
1. `locals` 的开头必须与 `parameters` 一致,目前只支持位置参数。
|
||||
2. `cell_variables` 必须来自本地变量名;捕获的局部变量仍占原来的本地槽位,但槽内保存 Cell。
|
||||
3. `free_variables` 的 Cell 槽紧接所有本地槽位;不得与本地名称重复。
|
||||
4. `names` 提供全局名称和属性名称的索引表。
|
||||
|
||||
函数通过 `vm.new_function(name, code_ref, globals, defaults, closure)` 创建。工厂检查代码与全局字典种类、默认参数数量和闭包 Cell 数量,并从 globals 的可选 `__builtins__` 字典捕获内建环境。全局查找先查 globals,缺失再查捕获的 builtins。
|
||||
|
||||
## 执行帧:这一次运行到哪里
|
||||
|
||||
`EndorFrame` 不是堆中的语言对象,没有 EndorObject 头。它类似 CPython 的内部 `_PyInterpreterFrame`;以后可另加可见的帧包装,不把反射对象强加给每次调用。
|
||||
|
||||
帧保存函数及代码引用、共享可执行数据、globals/builtins、调用者帧索引、当前与下一条指令位置、本地/Cell/自由变量槽,以及求值栈。Rust 使用安全容器表达这些逻辑区域,没有复制 CPython 的 C 指针和柔性数组物理布局。
|
||||
|
||||
帧初始化时绑定位置参数及尾部默认参数,未绑定局部槽为空,捕获的局部槽转为 Cell,并放入闭包提供的自由变量 Cell。读取空槽或已删除变量会报错。Cell 初始化在建帧时完成,首版无需单独的 MAKE_CELL/COPY_FREE_VARS 启动指令。
|
||||
|
||||
## 执行循环与函数调用
|
||||
|
||||
```text
|
||||
Vm::execute(code, globals)
|
||||
创建临时函数
|
||||
→ Vm::call
|
||||
→ 准备第一个 EndorFrame
|
||||
→ 读取当前指令
|
||||
→ match opcode 执行操作
|
||||
→ 推进或跳转到下一位置
|
||||
→ CALL 切入新帧 / RETURN_VALUE 恢复调用者
|
||||
```
|
||||
|
||||
语言函数互调使用一个显式帧向量,不递归调用 Rust 求值器。调用者已推进的指令位置就是返回后继续的位置,`previous` 用向量索引连接调用者。NativeFunction 则通过已有的 Rust 回调入口执行。
|
||||
|
||||
普通函数调用采用 `[callable, NULL, arg0, ...]` 栈布局,CALL 参数是显式位置参数个数。NULL 是内部标记,与 `Value::Nil` 不同;如果第二个位置是实际值,就把它作为隐式接收者加在参数前。尚未实现属性描述符或自动方法绑定。
|
||||
|
||||
## 对象引用与返回值
|
||||
|
||||
每个帧持有函数的一份执行根,函数继续保活代码、全局字典、内建字典、默认值及闭包。局部槽和求值栈中的每个对象值也各自持有执行根,重复出现分别计数。执行根复用现有外部强引用计数,所以循环回收能保留它们。
|
||||
|
||||
装载借用值时先 retain,弹栈或清空槽时 release;槽之间移动直接转移持有。调用先准备并保活被调函数的参数,再清理调用者的输入。返回值从当前帧移交给调用者栈或宿主,帧清理不会释放这一份结果。
|
||||
|
||||
**API 变化:**`Vm::call` 现在接收 `&mut self`,语言函数和原生函数统一返回持有的一份结果。返回 `Value::Object` 后,宿主完成使用时调用 `vm.release_value(result)`,或取出句柄后 `release`。原生回调本身仍接收借用参数、返回借用值;VM 会验证并 retain 回调的结果,之后才弹出输入参数。即使回调返回的是刚在字节码里创建的临时参数,也不会在清理输入时失效。
|
||||
|
||||
`get_attr`、`dict_get`、`dict_items`、`cell_get` 仍返回借用内容,不属于上述调用返回值变化。
|
||||
|
||||
执行出错时,当前尚未消费的栈值仍由帧持有;所有活动帧按从内到外顺序清理。已完成的全局字典/属性写入不会回滚,但未完成操作和临时执行根会清理。字典构造失败时也会清理尚未返回的临时字典。
|
||||
|
||||
## 最小示例
|
||||
|
||||
```rust
|
||||
use aster_core::{CodeLayout, EndorCode, Instruction, Opcode, Value, Vm};
|
||||
|
||||
fn main() -> Result<(), aster_core::VmError> {
|
||||
let mut vm = Vm::new()?;
|
||||
let globals = vm.new_dict()?;
|
||||
let code = EndorCode::from_instructions(
|
||||
&[
|
||||
Instruction::new(Opcode::LoadConst, 0),
|
||||
Instruction::simple(Opcode::ReturnValue),
|
||||
],
|
||||
vec![Value::Text("Hello Endor".into())],
|
||||
CodeLayout::default(),
|
||||
)?;
|
||||
let code = vm.new_code(code)?;
|
||||
let result = vm.execute(code, globals)?;
|
||||
assert_eq!(result, Value::Text("Hello Endor".into()));
|
||||
vm.release_value(result)?;
|
||||
vm.release(code)?;
|
||||
vm.release(globals)?;
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
该示例直接提供指令,不表示已经支持 Endor 或 Python 源码解析。函数和嵌套函数目前由宿主工厂创建,尚无 MAKE_FUNCTION 指令;后续编译器接入时再补函数定义所需的操作。
|
||||
|
||||
## 限额、诊断与边界
|
||||
|
||||
`ExecutionLimits` 默认限制 1000 个活动帧,指令配额为 `usize::MAX`,正常使用不设置有限的指令预算。`execute_with_limits`/`call_with_limits` 可显式指定限额,所有嵌套语言调用共享计步器;扩展参数不单独计步。可选指令预算是 Endor 的宿主运行边界,不是 CPython 默认行为,也不会抢占正在执行的宿主回调。
|
||||
|
||||
运行错误仍返回 `VmError`。`last_traceback()` 另提供从外到内的函数名、文件、代码首行和出错指令偏移;它保存独立元数据,不保活已经清理的帧。首行是代码声明的 first_line,不是由行号表推导出的实际错误源码行。成功的新一次调用会清空旧记录。尚无语言异常对象传播、raise/try/except 或完整源码行号表。
|
||||
|
||||
暂未实现:lexer/parser、源码编译器、关键字/可变参数、生成器/协程、迭代器专用指令、数值算术、MAKE_FUNCTION、自动方法绑定、指令特化和 JIT。
|
||||
|
||||
## 验证
|
||||
|
||||
[execution.rs](../../crates/core/tests/execution.rs) 覆盖 wordcode、控制流校验、变量、循环、嵌套调用与默认参数、闭包、容器和返回值所有权、错误清理及帧/指令限额;已有 builtins 测试已迁移共享代码对象和持有式调用返回值。lib.rs 另有可供远端 rustdoc 执行的例子。
|
||||
|
||||
验证命令与结果边界统一见 [验证](../operations/validation.md)。限额与环境隔离见 [配置](../operations/configuration.md),文件职责见 [core 源码导航](../../crates/core/src/README.md)。
|
||||
|
||||
参考:[CPython 解释器设计](https://github.com/python/cpython/blob/v3.14.0/InternalDocs/interpreter.md)、[执行帧设计](https://github.com/python/cpython/blob/v3.14.0/InternalDocs/frames.md)、[帧结构](https://github.com/python/cpython/blob/v3.14.0/Include/internal/pycore_interpframe_structs.h)、[指令定义](https://github.com/python/cpython/blob/v3.14.0/Python/bytecodes.c)、[求值入口与执行器](https://github.com/python/cpython/blob/v3.14.0/Python/ceval.c)。
|
||||
@@ -0,0 +1,35 @@
|
||||
# VM、执行帧与对象的生命周期
|
||||
|
||||
VM 由宿主持有,VM 持有对象堆,每次调用临时创建执行帧。一次执行结束不等于 VM 销毁;宿主可以保留全局字典并在同一 VM 中执行多次。
|
||||
|
||||
| 层级 | 创建 | 结束 |
|
||||
| --- | --- | --- |
|
||||
| Vm | 宿主调用 Vm::new,创建空堆并取得独立堆身份 | Rust 所有者销毁;使用 Arc 时等最后一份 Arc 被释放 |
|
||||
| EndorFrame | 每次语言函数调用建立,加入本次调用的帧向量 | 函数返回或 Result::Err 时,从内到外清理 |
|
||||
| EndorObject | new_*/allocate 分配,返回一份外部强引用 | 总强引用归零、显式循环回收,或整体 VM 销毁 |
|
||||
|
||||
## 运行期间的根
|
||||
|
||||
每个执行帧持有函数的一份根引用,函数通过堆边继续保活代码、globals、builtins、默认参数和闭包。局部变量与求值栈的对象槽分别持有根;Cell 通过自己的堆边持有捕获内容。
|
||||
|
||||
堆计数关系是:总强引用数 = 宿主/执行根引用数 + 属性/载荷内部入边数。对象头 external_refs 同时统计宿主和执行根,不只是宿主变量。
|
||||
|
||||
ObjectRef 拷贝、Value::clone 和句柄变量离开 Rust 作用域,都不会自动 retain/release。宿主为每份显式持有配对 retain/release。借用读取在所有者删除内容前需要 retain,详见 [对象模型](objects.md)。
|
||||
|
||||
## 返回与失败
|
||||
|
||||
call/execute 返回 Value::Object 时,结果拥有一份根。执行器将这份根从帧移交给调用者或宿主,避免帧清理回收结果;使用后须 release_value,或取出对象句柄后 release。Native 回调原始返回值仍是借用值,由 VM 在移除输入根之前保活。
|
||||
|
||||
Result::Err 会触发活动帧清理;last_traceback 仅保存函数名、文件、代码首行与指令位置,不保活已经销毁的帧。此前已经完成的全局字典或属性写入不会回滚。
|
||||
|
||||
Rust panic 尚无专门的捕获恢复或执行根 RAII 清理机制。宿主捕获 panic 后不能据此假定当前 VM 已恢复全部执行根计数;整机进程终止与正常错误返回也不是同一条清理路径。
|
||||
|
||||
## 回收和整体销毁
|
||||
|
||||
通常释放在总引用数归零时立即执行。堆使用显式工作列表递减出边,长链不会递归析构。不可达循环通过手动 collect_cycles 从根标记后批量移除;没有自动阈值、分代或增量 GC。
|
||||
|
||||
Vm 没有独立后台线程、暂停状态或显式 shutdown 状态机。Rust 所有权销毁它所拥有的堆和剩余载荷,包括没有先做循环回收的对象;原生共享资源继续遵循各自的 Rust 所有权。外部 ObjectRef 不保活 Vm。
|
||||
|
||||
Server 的 Tokio 运行时、监听器、信号处理和 Storage 由宿主管理;优雅退出见 [运行](../operations/README.md)。数据库持久化只保留宿主元数据,不能延长或恢复 VM 对象生命周期。
|
||||
|
||||
源码定位:[core/src](../../crates/core/src/README.md) 中的 vm.rs、heap.rs、frame.rs、eval.rs。返回 [运行时索引](README.md)。
|
||||
@@ -0,0 +1,105 @@
|
||||
# EndorObject 代码说明
|
||||
|
||||
当前实现的是 Endor 的 VM 运行时基础对象,角色类似 CPython 的公共对象结构。这里没有语言根类、元类、继承或 Python type 系统。标量仍然可以直接保存在 Value 中,不要求一切装箱。
|
||||
|
||||
基本对象及统一数字表示已在此基础上加入,详见 [基本对象与 Number](builtins.md)。`builtins.rs` 定义字符串、字典、Cell、函数和异常数据;`number.rs` 定义无损数字表示;`vm_builtins.rs` 提供容器操作及关联对象校验。
|
||||
|
||||
代码文件及职责见 [core 源码导航](../../crates/core/src/README.md),对象及执行的生命周期见 [生命周期](lifecycle.md)。
|
||||
|
||||
## EndorObject 保存什么
|
||||
|
||||
```text
|
||||
EndorObject
|
||||
header 身份、总强引用数、外部强引用数
|
||||
payload 原生 Rust 数据及操作接口
|
||||
attributes VM 管理的可变属性表
|
||||
payload_edges 分配时记录的载荷引用快照
|
||||
```
|
||||
|
||||
`ObjectHeader` 不保存语言类型指针。`identity` 是 ObjectRef,用于识别同一个对象。`strong_refs` 是所有强引用的总数,`external_refs` 统计宿主持有和执行帧持有的根引用,也是循环回收的根来源。
|
||||
|
||||
`payload` 是 `Box<dyn ObjectPayload>`。Box 保存原生数据,dyn trait 让运行时通过统一接口调用不同 Rust 实现。Rust 会为 trait 分发生成所需机制;不需要创建一个 Python 式的 Type 对象。`kind_name` 只用于诊断,`as_any` 只用于原生 Rust 扩展判断自己能否处理另一份载荷。
|
||||
|
||||
`attributes` 保存字段名和 Value。对象值会被 VM 计为内部强引用;字符串和整数等直接保存。载荷可选择是否允许属性,例如 Record 允许,NativeFunction 默认不允许。
|
||||
|
||||
`payload_edges` 让原生载荷也可以持有对象。载荷的 trace 方法逐一报告这些边,分配器验证并保存快照;重复边分别计数。自定义载荷分配后不能自行修改这些边。内置字典和 Cell 通过 VM 的载荷替换协议更新:验证和保留新边,替换载荷与快照,最后释放旧边;对象 Box 保持稳定。其他可变对象关系也可放入托管属性。单纯复制一个借用句柄不算持有,也不保护它免于回收。
|
||||
|
||||
## 指针与句柄
|
||||
|
||||
Heap 使用 `Option<Box<EndorObject>>` 保存槽位,实际对象位于独立分配的 Box 中。槽位向量扩容时,移动的是 Box,存活对象本身的地址保持稳定。
|
||||
|
||||
公开的 ObjectRef 使用三部分定位:
|
||||
|
||||
```text
|
||||
heap_id 属于哪个 VM
|
||||
index 位于哪个槽位
|
||||
generation 这是该槽位的哪一次分配
|
||||
```
|
||||
|
||||
每次访问检查归属、范围、代数和存活状态。对象释放后,复用同一槽位会更新代数,所以旧句柄不会指向后来分配的新对象。代数耗尽就停用该槽位,VM 身份也不回绕。
|
||||
|
||||
公共 API 不暴露裸指针解引用,也不允许调用方构造索引。句柄是进程内引用,不是数据库持久 ID。
|
||||
|
||||
## 分配、持有与释放
|
||||
|
||||
`new_object()` 创建 Record;`allocate(payload)` 创建指定原生载荷的对象。分配成功时,总引用数和外部引用数均为 1,返回这一份外部持有对应的句柄。
|
||||
|
||||
```rust
|
||||
let object = vm.new_object()?;
|
||||
let borrowed = object; // 只有句柄复制,引用数仍为 1。
|
||||
vm.retain(borrowed)?; // 再持有一份,总数和外部数变为 2。
|
||||
vm.release(object)?; // 释放一份,仍然存活。
|
||||
vm.release(borrowed)?; // 最后一份释放,清理对象。
|
||||
```
|
||||
|
||||
ObjectRef 是 Copy,但它不是自动管理持有关系的智能指针。宿主有责任为每一份外部持有配对 retain/release。没有外部持有时不能通过 release 消耗属性拥有的内部引用;此时返回 NoOwnedReference。
|
||||
|
||||
引用数归零后,堆先摘除对象,再迭代减少它持有的出边,最后执行 Rust Drop。若某个目标也归零,就加入工作列表继续处理。长对象链不会形成递归析构栈。载荷可实现 Drop 释放原生文件、缓冲区或其他宿主资源;这里没有语言级终结器和复活协议。
|
||||
|
||||
销毁整个 Vm 时,Rust 所有权会释放全部剩余对象,包括尚未显式回收的环。复制出的句柄不能让 VM 本身继续存活。
|
||||
|
||||
## 默认操作和覆盖方式
|
||||
|
||||
ObjectPayload 提供以下默认行为,载荷可以逐项覆盖:
|
||||
|
||||
| 操作 | 默认行为 |
|
||||
| --- | --- |
|
||||
| `repr` | 诊断名和对象身份组成的文本 |
|
||||
| `str` | 调用该载荷的 repr |
|
||||
| `is_true` | true |
|
||||
| `hash` | 基于对象身份计算哈希 |
|
||||
| `compare` | 返回未实现,由 VM 尝试反向比较;Eq/Ne 最终按身份回退,排序报错 |
|
||||
| `call` | 返回不支持调用的错误 |
|
||||
| `allows_attributes` | 不允许属性,载荷可显式允许 |
|
||||
| `trace` | 没有载荷持有的对象引用 |
|
||||
|
||||
Record 使用身份语义并开启属性。NativeFunction 保存名称和 Rust 函数指针,覆盖 repr 和 call。原生函数收到 Value 参数并返回 Result<Value, VmError>;VM 在调用前后检查参数和返回值中的对象句柄。
|
||||
|
||||
原生回调当前不能修改 VM 或直接在该 VM 中分配新对象;其参数和原始返回对象值是借用句柄。Vm::call 会验证并 retain 对象结果,对外统一返回一份持有式结果。语言函数已通过 [wordcode 执行器](bytecode.md) 使用显式帧、参数槽、闭包和求值栈执行;call/execute 的结果完成使用后须 release_value。
|
||||
|
||||
compare 的反向规则是 Lt 对应 Gt、Le 对应 Ge,Eq/Ne 保持不变。它不实现 Python 的子类型优先规则。内容比较由扩展定义;如果两个对象内容相等,它们必须给出相同哈希,或者明确拒绝哈希。
|
||||
|
||||
## 属性与错误原子性
|
||||
|
||||
`get_attr` 读取借用值;`set_attr` 写入;`del_attr` 删除。对象属性持有内部强引用,删除或覆盖时会释放旧引用。宿主若希望读取到的对象在属性删除后继续存活,必须先 retain。
|
||||
|
||||
写入先验证所有句柄和计数溢出,再增加新引用,最后替换并释放旧引用。因此跨 VM、失效句柄或溢出错误不会覆盖原值;把同一个对象重复赋给同一属性,也不会在替换间隙误释放它。
|
||||
|
||||
## 循环回收
|
||||
|
||||
引用计数无法自行释放 A → B → A,所以提供显式 `collect_cycles()`:
|
||||
|
||||
1. 把 external_refs 大于零的对象作为根。
|
||||
2. 沿托管属性和载荷边标记所有可达对象。
|
||||
3. 先从槽位中摘除全部不可达对象。
|
||||
4. 扣减垃圾指向幸存对象的引用,再 Drop 全部垃圾。
|
||||
|
||||
该过程会保留可达的环,回收无人持有的自环、互引用环,以及原生载荷与托管属性混合构成的环。收集返回回收数量,未自动按阈值触发。
|
||||
|
||||
## 当前边界
|
||||
|
||||
已实现:统一对象结构、稳定地址、代际句柄、身份、显式引用计数、托管属性、原生操作覆盖、比较与哈希、原生调用、资源清理和显式循环回收。
|
||||
|
||||
尚未实现:Python type 系统、语言根类、继承和描述符、源码解析与编译器、可变 VM 调用上下文、自动/分代/增量 GC、语言终结器/复活、完整资源限额和 VM 快照;执行器已有帧数和指令数限额。数据库仍然只保存宿主元数据。
|
||||
|
||||
验证范围与执行约定见 [验证](../operations/validation.md)。
|
||||
Reference in New Issue
Block a user