docs(objects): explain builtin store operations and ownership

This commit is contained in:
2026-11-12 21:42:24 +08:00
parent a7078adc54
commit 523ab6c745
+74 -5
View File
@@ -1,3 +1,8 @@
//! ObjectStore 的内置载荷入口;core::Vm 的同名方法把创建和访问委托到这里。
//! eval 使用字典实现全局命名空间,使用 Cell 让多个闭包共享可更新的捕获槽。
//! 返回句柄的 new_* 经 allocate 建立一份外部强引用;读取时复制 Value/ObjectRef
//! 不会自动 retain。修改已有容器统一经 Heap::replace_payload 更新强边与变更记录。
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
@@ -8,11 +13,16 @@ use crate::{
};
impl ObjectStore {
/// allocate 与 ObjectStore::replace_payload 在写入前调用,检查特定内置载荷的结构约束。
/// 这里只检查函数、异常的特定关系;通用引用边的有效性仍由 Heap 的 trace 路径检查。
pub(crate) fn validate_builtin(&self, payload: &dyn ObjectPayload) -> Result<(), VmError> {
// 源码函数载荷必须引用代码对象,默认值和捕获槽必须与该代码的布局一致。
if let Some(function) = payload.as_any().downcast_ref::<EndorFunctionObject>() {
// 借用代码元数据,不执行指令,也不为该借用新增引用计数。
let code = self
.payload::<EndorCodeObject>(function.code(), "code")?
.code();
// 默认值可以只覆盖末尾部分参数;闭包槽数量则必须与自由变量数量完全一致。
if function.defaults().len() > code.parameters().len()
|| function.closure().len() != code.free_variables().len()
{
@@ -20,15 +30,20 @@ impl ObjectStore {
"function defaults or closure do not match code",
));
}
// 全局空间及可选的内置空间都必须是字典;无需读取它们的全部键值内容。
self.dictionary_kind(function.globals())?;
// 只有创建时捕获了内置空间才检查;None 不要求补建默认字典。
if let Some(builtins) = function.builtins() {
self.dictionary_kind(builtins)?;
}
// 每个捕获引用必须指向 Cell;槽的内容可以暂时为空,稍后再由执行器写入。
for cell in function.closure() {
self.payload::<EndorCellObject>(*cell, "cell")?;
}
}
// 异常链只接受异常载荷,避免后续格式化或异常传播拿到其他对象种类。
if let Some(exception) = payload.as_any().downcast_ref::<EndorBaseExceptionObject>() {
// flatten 跳过不存在的 cause/context,存在的两条边逐一检查。
for reference in [exception.cause(), exception.context()]
.into_iter()
.flatten()
@@ -39,6 +54,8 @@ impl ObjectStore {
Ok(())
}
/// VM、内置访问器和宿主通过此入口借用具体 Rust 载荷;expected 仅用于错误提示。
/// object 会验证句柄并记录对象读取;这里的 Rust downcast 不等同于语言层名义类型判断。
pub fn payload<T: ObjectPayload>(
&self,
reference: ObjectRef,
@@ -51,14 +68,18 @@ impl ObjectStore {
.ok_or(VmError::ObjectKindMismatch(expected))
}
/// Vm::new_string 委托的堆字符串构造入口;文本存入不可变字符串载荷。
pub fn new_string(&mut self, text: impl Into<String>) -> Result<ObjectRef, VmError> {
self.allocate(EndorStringObject::new(text))
}
/// Vm::new_code 将已构造的指令/元数据封装为代码对象;此处不启动执行。
pub fn new_code(&mut self, code: EndorCode) -> Result<ObjectRef, VmError> {
self.allocate(EndorCodeObject::new(code))
}
/// Vm 的执行入口及 eval 创建闭包时调用,组合代码、全局空间、默认值和捕获槽。
/// name 用作函数标识;code 指向可执行代码;closure 按该代码的自由变量顺序排列。
pub fn new_function(
&mut self,
name: impl Into<String>,
@@ -67,15 +88,20 @@ impl ObjectStore {
defaults: Vec<Value>,
closure: Vec<ObjectRef>,
) -> Result<ObjectRef, VmError> {
// 先检查全局空间,再读取它在函数创建时指定的内置命名空间。
self.dictionary_kind(globals)?;
// 保存的是字典身份:字典内部后续更新可见,但改写 globals.__builtins__ 不会重绑定此字段。
let builtins = match self.dict_get_name(globals, "__builtins__")? {
// 有该键时必须是字典对象;普通对象或标量(包括 Nil)都不能充当内置空间。
Some(Value::Object(reference)) => {
self.dictionary_kind(reference)?;
Some(reference)
}
// 缺少键表示无内置空间,与显式存放 Nil 不同。
None => None,
_ => return Err(VmError::ObjectKindMismatch("dict")),
};
// allocate 再校验代码/槽布局,并由 trace 为这些字段引用的对象建立内部强边。
self.allocate(EndorFunctionObject::new(
name.into(),
code,
@@ -86,25 +112,30 @@ impl ObjectStore {
))
}
/// Vm::new_number 构造堆上的数值对象,与 Value::Number 的直接数值表示分开。
pub fn new_number(&mut self, number: EndorNumber) -> Result<ObjectRef, VmError> {
self.allocate(EndorNumberObject::new(number))
}
/// Vm::new_dict 及 eval 的字典构造路径使用,初始没有条目或内部引用边。
pub fn new_dict(&mut self) -> Result<ObjectRef, VmError> {
self.allocate(EndorDictObject::new())
}
/// Returns borrowed keys and values in insertion order.
/// Vm::dict_items 和宿主迭代入口按插入顺序借用条目;不复制容器,也不新增外部强引用。
/// payload 路径记录整个对象的读取,因此迭代依赖整个字典内容。
pub fn dict_items(&self, dictionary: ObjectRef) -> Result<&[(Value, Value)], VmError> {
Ok(self
.payload::<EndorDictObject>(dictionary, "dict")?
.entries())
}
/// 函数校验与按名称查找共用的种类检查,只观察“仍是字典”,不观察其全部内容。
pub(crate) fn dictionary_kind(
&self,
dictionary: ObjectRef,
) -> Result<&EndorDictObject, VmError> {
// 直接走 heap.get:若改用 payload/object,会附带过宽的整对象读取依赖。
let dict = self
.heap
.get(dictionary)?
@@ -112,25 +143,32 @@ impl ObjectStore {
.as_any()
.downcast_ref::<EndorDictObject>()
.ok_or(VmError::ObjectKindMismatch("dict"))?;
// 仅在依赖收集上下文中登记种类观察;有效句柄和 downcast 已在上面检查。
self.heap.record_dictionary_kind(dictionary);
Ok(dict)
}
/// Name lookup records both present and missing text keys. General dict_get
/// and iteration continue to observe the entire dictionary contents.
/// eval 查找全局名和内置名时调用:既记录命中值,也记录“此名称不存在”。
/// 这里只查一个字典;全局未命中后再查 builtins 的顺序由 eval 决定。
/// 通用 dict_get 和条目迭代仍采用整字典依赖。
pub fn dict_get_name(
&self,
dictionary: ObjectRef,
name: &str,
) -> Result<Option<Value>, VmError> {
// 先确认字典种类,再以文本键查找,避免无关名称变更污染本次读取的依赖。
let dict = self.dictionary_kind(dictionary)?;
// clone 复制返回的 Value 表示;若其中含对象句柄,它仍是借用句柄。
let value = self
.dict_position(dict, &Value::Text(name.into()))?
.map(|index| dict.entries[index].1.clone());
// None 也要登记,使未来新增这个名称能使原先的缺失观察失效。
self.heap.record_name(dictionary, name, &value);
Ok(value)
}
/// Vm::dict_get 与宿主通用键查找入口;返回 None 表示缺键,Some(Nil) 表示存了空值。
pub fn dict_get(&self, dictionary: ObjectRef, key: &Value) -> Result<Option<Value>, VmError> {
// 通用读取保守地观察整个字典;先检查键可哈希,再进行线性相等比较。
let dict = self.payload::<EndorDictObject>(dictionary, "dict")?;
self.key_hash(key)?;
Ok(self
@@ -138,43 +176,58 @@ impl ObjectStore {
.map(|index| dict.entries[index].1.clone()))
}
/// Vm::dict_set、eval 构造字典和写入全局名时调用;修改保持字典对象身份。
pub fn dict_set(
&mut self,
dictionary: ObjectRef,
key: Value,
value: Value,
) -> Result<(), VmError> {
// 先复制载荷准备新状态;Rust clone 不 retain,提交前仍由旧字典持有原有引用边。
let mut dict = self.payload::<EndorDictObject>(dictionary, "dict")?.clone();
self.key_hash(&key)?;
// 对象值必须来自本堆且仍有效;标量无需额外句柄检查。
self.validate_value(&value)?;
if let Some(index) = self.dict_position(&dict, &key)? {
// Preserve the first key object and insertion position.
// 相等键只覆盖值,保留第一次插入的键对象及其顺序,不保存本次传入的替代键。
dict.entries[index].1 = value;
} else {
// 新键追加到末尾;因此遍历顺序就是首次插入顺序。
dict.entries.push((key, value));
}
// Heap 在实际替换前检查新边和只读屏障,先保留新边再释放旧边,并记录 Contents 变更。
self.heap.replace_payload(dictionary, dict)
}
/// Vm::dict_delete 删除一个键;返回值说明是否存在该键,错误通过 Result 单独传播。
pub fn dict_delete(&mut self, dictionary: ObjectRef, key: &Value) -> Result<bool, VmError> {
// 先复制载荷准备新状态;Rust clone 不 retain,提交前仍由旧字典持有原有引用边。
let mut dict = self.payload::<EndorDictObject>(dictionary, "dict")?.clone();
self.key_hash(key)?;
// 缺键直接返回,不提交替换,因此也不会触发已有对象的写屏障或产生变更记录。
let Some(index) = self.dict_position(&dict, key)? else {
return Ok(false);
};
// 删除后其余条目的相对顺序不变;被删除键值的强边在 Heap 提交时才释放。
dict.entries.remove(index);
// Heap 在实际替换前检查新边和只读屏障,先保留新边再释放旧边,并记录 Contents 变更。
self.heap.replace_payload(dictionary, dict)?;
Ok(true)
}
/// Vm::dict_clear 用空载荷替换条目集合;对象身份保留,原键值的强边由 Heap 释放。
pub fn dict_clear(&mut self, dictionary: ObjectRef) -> Result<(), VmError> {
// 先确认字典种类;即使原本为空仍走替换路径,受相同只读保护约束。
self.payload::<EndorDictObject>(dictionary, "dict")?;
self.heap
.replace_payload(dictionary, EndorDictObject::new())
}
/// 字典读写共用的线性定位;条目向量没有哈希索引,hash 只负责准入校验。
fn dict_position(&self, dict: &EndorDictObject, key: &Value) -> Result<Option<usize>, VmError> {
// 只比较键,忽略值;相等比较失败时立即返回错误,不把失败解释为不相等。
for (index, (existing, _)) in dict.entries.iter().enumerate() {
// 返回第一个相等键的位置,供读值、覆盖值或删除整条记录使用。
if self.key_equal(existing, key)? {
return Ok(Some(index));
}
@@ -182,23 +235,30 @@ impl ObjectStore {
Ok(None)
}
/// 通用字典操作检查键是否支持哈希;返回值当前不用于条目定位,也不用于持久化。
fn key_hash(&self, value: &Value) -> Result<u64, VmError> {
// 对象键先验证归属和存活,随后才允许访问它的载荷哈希实现。
self.validate_value(value)?;
// 直接值使用标准哈希器;对象委托各自实现,可由载荷拒绝不可哈希种类。
let mut hasher = DefaultHasher::new();
match value {
Value::Nil => "nil".hash(&mut hasher),
Value::Bool(value) => value.hash(&mut hasher),
Value::Text(value) => value.hash(&mut hasher),
// 数值直接表示尚未定义字典哈希语义,因此明确拒绝,不隐式转换成文本或整数。
Value::Number(_) => return Err(VmError::UnsupportedOperation("numeric hash")),
Value::Object(reference) => return self.hash(*reference),
}
Ok(hasher.finish())
}
/// dict_position 的键比较:直接文本与堆字符串按内容互通,其余按对象协议或 Value 比较。
fn key_equal(&self, left: &Value, right: &Value) -> Result<bool, VmError> {
// 将两种字符串表示临时取成拥有的 String;其他对象返回 None,仍保留后续比较路径。
let text = |value: &Value| -> Result<Option<String>, VmError> {
Ok(match value {
Value::Text(text) => Some(text.clone()),
// 借用载荷尝试字符串 downcast;非字符串不是错误,无效句柄才会传播错误。
Value::Object(reference) => self
.object(*reference)?
.payload()
@@ -208,9 +268,11 @@ impl ObjectStore {
_ => None,
})
};
// 两边都是文本才做内容相等;不做 Unicode 规范化或大小写折叠。
if let (Some(left), Some(right)) = (text(left)?, text(right)?) {
return Ok(left == right);
}
// 两个对象走 Store::compare 的正向/反向协议及身份回退;其他组合使用 Value 的相等实现。
match (left, right) {
(Value::Object(left), Value::Object(right)) => {
self.compare(*left, *right, CompareOp::Eq)
@@ -219,11 +281,13 @@ impl ObjectStore {
}
}
/// Vm::new_cell 在 eval 把局部槽提升为闭包共享槽时调用;None 表示尚未初始化。
pub fn new_cell(&mut self, value: Option<Value>) -> Result<ObjectRef, VmError> {
self.allocate(EndorCellObject::new(value))
}
/// Reads a borrowed value; an empty cell is not Value::Nil.
/// Vm::cell_get 供自由变量读取及赋值前读取旧值使用;复制出的对象句柄仍是借用。
/// 空槽返回 EmptyCell;已初始化为 Some(Value::Nil) 则正常返回 Nil。
pub fn cell_get(&self, cell: ObjectRef) -> Result<Value, VmError> {
self.payload::<EndorCellObject>(cell, "cell")?
.value()
@@ -231,13 +295,18 @@ impl ObjectStore {
.ok_or(VmError::EmptyCell)
}
/// Vm::cell_set 在自由变量赋值时调用;所有共享该 Cell 的闭包随后读取相同的新值。
pub fn cell_set(&mut self, cell: ObjectRef, value: Value) -> Result<(), VmError> {
// 确认是 Cell 后由 Heap 检查引用边和只读屏障,更新时保留 Cell 身份。
self.payload::<EndorCellObject>(cell, "cell")?;
self.heap
.replace_payload(cell, EndorCellObject::new(Some(value)))
}
/// Vm::cell_clear 在删除捕获变量时调用;恢复未初始化状态,并释放旧值的内部强边。
/// 即使槽原本为空仍提交替换,不能通过重复清空绕过只读屏障。
pub fn cell_clear(&mut self, cell: ObjectRef) -> Result<(), VmError> {
// 确认是 Cell 后由 Heap 检查引用边和只读屏障,更新时保留 Cell 身份。
self.payload::<EndorCellObject>(cell, "cell")?;
self.heap.replace_payload(cell, EndorCellObject::default())
}