From 9861a6d58696e2dfcf312d4738f74b134b28d258 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:18:34 +0800 Subject: [PATCH] docs(objects): explain heap tracking and reference lifecycle --- crates/objects/src/heap.rs | 231 ++++++++++++++++++++++++++++++++++--- 1 file changed, 213 insertions(+), 18 deletions(-) diff --git a/crates/objects/src/heap.rs b/crates/objects/src/heap.rs index cafdb27..c10d265 100644 --- a/crates/objects/src/heap.rs +++ b/crates/objects/src/heap.rs @@ -1,3 +1,7 @@ +//! ObjectStore 的底层对象堆;不执行语言指令,负责稳定对象盒、代际句柄、引用边和回收。 +//! Store/types/store_builtins 先做领域校验,再调用这里的受控修改入口。 +//! 读追踪用 Mutex 支持 &self 协议路径记录观察;对象修改仍要求 &mut self,并非并发可变堆。 + use std::collections::{BTreeMap, BTreeSet, HashMap}; use std::fmt; use std::sync::Mutex; @@ -8,81 +12,111 @@ use crate::changes::MAX_PENDING_CHANGES; use crate::{ChangeBatch, ChangeKey, ObjectChange}; use crate::{EndorObject, ObjectHeader, ObjectPayload, Value, VmError}; +// 进程级单调身份源;Relaxed 只需要原子唯一性,不承担对象发布或内存同步。 static NEXT_HEAP_ID: AtomicU64 = AtomicU64::new(1); -/// Checked, process-local reference. Copying it never retains an object. -/// The generation prevents a released handle from aliasing a reused slot. +/// 进程内受校验句柄;复制不会 retain。heap_id 防跨堆,generation 防止旧句柄别名复用槽。 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct ObjectRef { + // 分配它的堆身份。 heap_id: u64, + // slots 向量中的位置,不是对象内存地址。 index: usize, + // 槽释放后递增的代际。 generation: u64, } impl fmt::Display for ObjectRef { + /// ObjectRef 的日志/错误格式化入口;输出进程内身份三元组,不代表可持久化或跨进程引用。 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}:{}:{}", self.heap_id, self.index, self.generation) } } -/// A non-owning reference. Only the originating store can upgrade it; slot -/// reuse never makes a dead weak reference point at a different object. +/// 非拥有弱句柄;仅原 Store 可升级,代际检查保证槽复用不会复活过期弱引用。 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WeakObjectRef(pub(crate) ObjectRef); #[derive(Debug)] struct Slot { + // 当前槽代际;空槽仍保留历史代际,耗尽后不进入 free。 generation: u64, + // Box 保持对象地址稳定,外层 slots 扩容不会移动对象实体。 object: Option>, } #[derive(Debug)] +/// 一层只读作用域;protected 是进入时的存活身份快照,其余字段只收集借用观察。 struct ReadOnly { + // 该层不可修改的已有对象,后续新对象不在此集合中。 protected: BTreeSet, + // 兼容的对象级去重读取集合。 reads: Mutex>, + // 子区间栈;bool 表示是否发生程序内容读取(而非仅验证)。 regions: Mutex>>, + // 按依赖键去重,保留该层首次观察的版本/名称值。 dependencies: Mutex>, + // 与 regions 同步压栈/弹栈的精确子区间依赖。 dependency_regions: Mutex>>, } #[derive(Debug)] +/// ObjectStore 独占的对象槽及生命周期状态;其他模块不能直接修改槽或引用计数。 pub(crate) struct Heap { + // 本堆的进程内唯一身份。 id: u64, + // 已分配过的槽,可含空槽;对象实体单独装箱。 slots: Vec, + // 可复用空槽索引,按栈顺序取回。 free: Vec, + // 实际占用槽数,分配/摘除时同步维护。 live: usize, + // 同一对象/变更键只保留最新版本,限制通知内存占用。 changes: BTreeMap<(ObjectRef, ChangeKey), u64>, + // 本批已溢出:停止精确通知,要求消费者全面重验依赖。 changes_overflow: bool, + // 活跃只读作用域栈,任意一层保护对象就禁止修改。 read_only: Vec, + // 当前线程的验证读取嵌套深度,RAII 退出时递减。 validation_reads: Mutex>, } -/// Borrowed read identity; content_read distinguishes ordinary reads from -/// annotation validation alone. Neither form retains the object. +/// 借用读取身份及用途分类,不创建 GC 根;普通内容读取会覆盖“仅验证”的分类。 #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct ReadAccess { + /// 读取的对象身份。 pub object: ObjectRef, + /// true 表示至少一次程序内容读取,false 表示只有注解验证读取。 pub content_read: bool, } -/// Borrowed observations; Object handles never retain the observed graph. -/// Named values are immutable comparison snapshots, not managed owning edges. +/// 缓存重验的借用观察;包括 Name.value 内句柄都不持有对象图,不是托管拥有边。 #[derive(Debug, Clone, PartialEq)] pub enum ReadDependency { + /// 整对象版本变化即失效。 Object { + /// 被观察对象的完整身份。 object: ObjectRef, + /// 读取时的版本快照。 version: u64, }, + /// 只要求原对象仍是字典,不依赖其他条目的变化。 DictionaryKind { + /// 已确认具有字典载荷的对象身份。 object: ObjectRef, }, + /// 指定文本名称的命中/缺失值快照;None 与 Some(Nil) 区分。 Name { + /// 接受名称查找的字典身份。 object: ObjectRef, + /// 本次观察的文本键,不代表全局与内置空间的合并查找。 name: String, + /// 首次查到的值或缺失;含对象句柄时也只是比较快照。 value: Option, }, } impl ReadDependency { + /// 缓存/读取分析使用者取得观察所属对象,不创建外部拥有根。 pub fn object(&self) -> ObjectRef { match self { Self::Object { object, .. } @@ -92,6 +126,7 @@ impl ReadDependency { } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +/// 精确依赖的排序/去重键;与 ReadDependency 分开,观察值不参与键身份。 enum ReadKey { Object(ObjectRef), DictionaryKind(ObjectRef), @@ -99,30 +134,41 @@ enum ReadKey { } #[derive(Debug)] pub struct TrackedReads { + /// 子区间对象读取与用途分类。 pub accesses: Vec, + /// 精确观察列表,供 Store::read_dependency_valid 重验。 pub dependencies: Vec, } #[derive(Debug)] pub struct ReadSet { + /// 整个作用域读取到的借用对象身份。 pub objects: Vec, + /// 精确观察列表,供 Store::read_dependency_valid 重验。 pub dependencies: Vec, } +/// 仅借用堆的验证读取守卫;不拥有对象,也不延长只读作用域寿命。 struct ValidationScope<'a> { + // 被借用的堆,守卫存活期间保证分类表可访问。 heap: &'a Heap, + // 进入验证的线程,用于归还同一线程的嵌套深度。 thread: ThreadId, } impl Drop for ValidationScope<'_> { + /// 验证读取 RAII 清理:compute 正常返回、提前报错或展开栈时都归还当前线程的一层标记。 fn drop(&mut self) { + // 在锁下找到本线程深度;守卫只在成功增加深度后创建。 let mut active = self .heap .validation_reads .lock() .expect("read tracker poisoned"); + // 嵌套验证只减少本层,不清除仍活跃的外层验证标记。 let depth = active .get_mut(&self.thread) .expect("active validation scope"); *depth -= 1; + // 最外层退出时移除线程项,后续读取重新归类为程序内容读取。 if *depth == 0 { active.remove(&self.thread); } @@ -130,23 +176,32 @@ impl Drop for ValidationScope<'_> { } impl Heap { + /// ObjectStore::new 创建空堆;分配独立进程内身份,计数器耗尽时报错而不循环复用。 pub(crate) fn new() -> Result { + // checked_add 防止身份绕回;fetch_update 返回更新前的唯一值。 let id = NEXT_HEAP_ID .fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1)) .map_err(|_| VmError::IdentityExhausted)?; + // 初始没有对象、空槽、通知或读取作用域。 Ok(Self { id, + // 已分配过的槽,可含空槽;对象实体单独装箱。 slots: Vec::new(), + // 可复用空槽索引,按栈顺序取回。 free: Vec::new(), live: 0, + // 同一对象/变更键只保留最新版本,限制通知内存占用。 changes: BTreeMap::new(), changes_overflow: false, read_only: vec![], + // 当前线程的验证读取嵌套深度,RAII 退出时递减。 validation_reads: Mutex::new(HashMap::new()), }) } + /// types 的字段/方法契约变更校验遍历存活对象时使用;输出借用身份,不增加根计数。 pub(crate) fn live_references(&self) -> impl Iterator + '_ { + // 空槽跳过;按每个当前代际重建身份,而不是复用历史外部句柄。 self.slots.iter().enumerate().filter_map(|(index, slot)| { slot.object.as_ref().map(|_| ObjectRef { heap_id: self.id, @@ -156,7 +211,9 @@ impl Heap { }) } + /// Store/VM 进入只读调用时快照现有身份;返回当前嵌套深度作为结束令牌。 pub(crate) fn begin_read_only(&mut self) -> usize { + // 只快照已存在对象;外层新建对象在随后嵌套层进入时也会变为该层受保护对象。 let protected = self .slots .iter() @@ -169,23 +226,32 @@ impl Heap { }) }) .collect(); + // 新层观察容器独立,之后的读取还会传播到适用的外层。 self.read_only.push(ReadOnly { protected, + // 兼容的对象级去重读取集合。 reads: Mutex::new(BTreeSet::new()), + // 子区间栈;bool 表示是否发生程序内容读取(而非仅验证)。 regions: Mutex::new(Vec::new()), + // 按依赖键去重,保留该层首次观察的版本/名称值。 dependencies: Mutex::new(BTreeMap::new()), + // 与 regions 同步压栈/弹栈的精确子区间依赖。 dependency_regions: Mutex::new(Vec::new()), }); self.read_only.len() } + /// Store 的兼容结束入口,仅返回读取对象身份,不保留精确观察键。 pub(crate) fn end_read_only(&mut self, scope: usize) -> Result, VmError> { Ok(self.end_read_only_precise(scope)?.objects) } + /// Store/VM 结束最内层只读作用域并取走全部观察;错误令牌不弹出状态。 pub(crate) fn end_read_only_precise(&mut self, scope: usize) -> Result { + // 令牌是深度而非永久唯一 ID;调用方必须遵守同一作用域内严格 LIFO。 if scope == 0 || scope != self.read_only.len() { return Err(VmError::InvalidArgument("read-only scope nesting mismatch")); } + // 检查已保证栈顶存在,取走 Mutex 内的数据,输出按有序集合/键排列。 let scope = self.read_only.pop().unwrap(); Ok(ReadSet { objects: scope @@ -203,11 +269,15 @@ impl Heap { }) } + /// Store/VM 在当前只读调用内开始一个子调用区间,同时建立分类与精确依赖两套容器。 pub(crate) fn begin_read_tracking(&self) -> Result { + // 子区间必须依附当前最内层只读作用域,不能独立开启读取追踪。 let scope = self.read_only.last().ok_or(VmError::InvalidArgument( "read tracking requires a read-only scope", ))?; + // 锁的取得顺序与结束路径一致:先分类区间,再精确依赖区间。 let mut regions = scope.regions.lock().expect("read tracker poisoned"); + // 两套栈使用相同深度令牌,不能只创建其中一套。 regions.push(BTreeMap::new()); scope .dependency_regions @@ -217,6 +287,7 @@ impl Heap { Ok(regions.len()) } + /// 兼容子区间结束入口,只保留读取对象列表,丢弃分类与精确依赖。 pub(crate) fn end_read_tracking(&self, token: usize) -> Result, VmError> { Ok(self .end_read_tracking_details(token)? @@ -225,24 +296,30 @@ impl Heap { .collect()) } + /// Store/VM 结束子区间,保留程序内容读取与注解验证读取的分类。 pub(crate) fn end_read_tracking_details( &self, token: usize, ) -> Result, VmError> { Ok(self.end_read_tracking_precise(token)?.accesses) } + /// 精确结束子调用区间;同时弹出两套追踪栈,外层区间和只读作用域继续有效。 pub(crate) fn end_read_tracking_precise(&self, token: usize) -> Result { + // 子区间必须依附当前最内层只读作用域,不能独立开启读取追踪。 let scope = self.read_only.last().ok_or(VmError::InvalidArgument( "read tracking requires a read-only scope", ))?; + // 锁的取得顺序与结束路径一致:先分类区间,再精确依赖区间。 let mut regions = scope.regions.lock().expect("read tracker poisoned"); let mut precise = scope .dependency_regions .lock() .expect("read tracker poisoned"); + // 同时检查两套栈,错误不弹出任何一边,避免分类与精确观察错位。 if token == 0 || token != regions.len() || token != precise.len() { return Err(VmError::InvalidArgument("read tracking nesting mismatch")); } + // 将最内层区间移出;外层已在记录时同步收到观察,不需要结束时再合并。 Ok(TrackedReads { accesses: regions .pop() @@ -257,30 +334,39 @@ impl Heap { }) } + /// Store 的写配置保护和 VM 效果判断查询是否存在只读作用域。 pub(crate) fn is_read_only(&self) -> bool { !self.read_only.is_empty() } + /// types::matches_annotation 在类型校验期间调用,将该线程内的读取标为验证用途。 pub(crate) fn with_validation_reads( &self, compute: impl FnOnce() -> Result, ) -> Result { + // 无活跃只读作用域就没有需要收集/分类的观察。 if self.read_only.is_empty() { return compute(); } + // 分类标记按线程隔离,嵌套深度使内层退出不影响外层。 let thread = std::thread::current().id(); { + // 限定锁作用域,不能持锁执行用户传入的 compute。 let mut active = self.validation_reads.lock().expect("read tracker poisoned"); + // 溢出时不写回新深度,也不建立清理守卫。 let depth = active.entry(thread).or_default(); *depth = depth .checked_add(1) .ok_or(VmError::ExecutionLimit("validation scopes"))?; } + // 守卫覆盖整个 compute;? 提前返回或 panic 展开也会恢复分类标记。 let _scope = ValidationScope { heap: self, thread }; compute() } + /// Store::object 取得对象后登记整对象版本观察;本方法不验证句柄也不 retain。 pub(crate) fn record_read(&self, reference: ObjectRef, version: u64) { + // 无活跃只读作用域就没有需要收集/分类的观察。 if self.read_only.is_empty() { return; } @@ -291,17 +377,20 @@ impl Heap { } }); } + /// store_builtins::dictionary_kind 已验证后登记“仍是字典”,不依赖字典全部条目。 pub(crate) fn record_dictionary_kind(&self, reference: ObjectRef) { self.record_observation(reference, ReadKey::DictionaryKind(reference), || { ReadDependency::DictionaryKind { object: reference } }); } + /// store_builtins::dict_get_name 登记命中值或缺失快照,供之后按名称重验依赖。 pub(crate) fn record_name( &self, reference: ObjectRef, name: &str, value: &Option, ) { + // 无活跃只读作用域就没有需要收集/分类的观察。 if self.read_only.is_empty() { return; } @@ -313,28 +402,35 @@ impl Heap { } }); } + /// 三种 record_* 共用的收集器,把观察传播给每个保护该对象的外层作用域及其活跃子区间。 fn record_observation( &self, reference: ObjectRef, key: ReadKey, build: impl Fn() -> ReadDependency, ) { + // 无活跃只读作用域就没有需要收集/分类的观察。 if self.read_only.is_empty() { return; } + // 当前线程在任何验证层内都先分类为验证读取;区间中若另有普通读取仍以普通为准。 let content = !self .validation_reads .lock() .expect("read tracker poisoned") .contains_key(&std::thread::current().id()); + // 所有适用外层都要收到读取,不能只记录最内层导致父调用漏依赖。 for scope in &self.read_only { + // 本层新建结果不属于本层外部输入,不作为该层已有对象依赖。 if scope.protected.contains(&reference) { + // 同键保留首次观察;build 惰性生成快照,未命中时才克隆名称值。 scope .dependencies .lock() .expect("read tracker poisoned") .entry(key.clone()) .or_insert_with(&build); + // 每个活跃子区间独立保存该精确观察。 for region in scope .dependency_regions .lock() @@ -343,11 +439,13 @@ impl Heap { { region.entry(key.clone()).or_insert_with(&build); } + // 兼容对象集合只去重身份,不区分读取用途。 scope .reads .lock() .expect("read tracker poisoned") .insert(reference); + // 用 OR 合并分类,避免后来发生的验证读取覆盖已有程序内容读取。 for region in scope .regions .lock() @@ -363,25 +461,32 @@ impl Heap { } } + /// Store::allocate 提交已校验的载荷;trace 引用边先检查,成功返回一份外部拥有根。 pub(crate) fn allocate( &mut self, payload: Box, ) -> Result { + // 收集每条载荷强边,包括重复项;仅构造身份快照,不立即改计数。 let mut edges = Vec::new(); payload.trace(&mut |reference| edges.push(reference)); + // 先验证所有目标及累积计数溢出,失败时尚未改变堆。 let increments = self.prepare_increments(&edges)?; + // 优先复用已释放且代际未耗尽的空槽,否则追加一个新槽。 let index = self.free.pop().unwrap_or(self.slots.len()); + // 新槽从代际零开始;复用槽保留 take 已递增的代际。 if index == self.slots.len() { self.slots.push(Slot { generation: 0, object: None, }); } + // 完整身份把堆、位置和当前代际绑定,不能用 index 单独代替。 let reference = ObjectRef { heap_id: self.id, index, generation: self.slots[index].generation, }; + // 新对象初始一份外部根、没有名义类型和属性;对象实体在 Box 中地址稳定。 let object = Box::new(EndorObject { header: ObjectHeader { identity: reference, @@ -395,11 +500,13 @@ impl Heap { payload_edges: edges, }); self.apply_increments(increments); + // 引用增量已提交后才发布占用槽,live 与占用状态一起增加。 self.slots[index].object = Some(object); self.live += 1; Ok(reference) } + /// 所有堆操作的只读验证入口;检查堆归属、槽范围、代际及占用状态,不登记读取依赖。 pub(crate) fn get(&self, reference: ObjectRef) -> Result<&EndorObject, VmError> { self.validate_owner(reference)?; self.slots @@ -409,22 +516,27 @@ impl Heap { .ok_or(VmError::InvalidHandle) } + /// types::attach_type 完成类型合法性校验后调用;建立不可替换的头部类型强边。 pub(crate) fn attach_type( &mut self, reference: ObjectRef, type_object: ObjectRef, ) -> Result<(), VmError> { + // 同一类型重复附着是无操作,不产生版本变化或写屏障检查。 if self.get(reference)?.header.type_object == Some(type_object) { return Ok(()); } + // 一旦附着不能改成另一种类型。 if self.get(reference)?.header.type_object.is_some() { return Err(VmError::InvalidArgument("object already has a type")); } + // 防止动态 type 属性与托管头部类型产生两个冲突来源。 if self.get(reference)?.attributes.contains_key("type") { return Err(VmError::InvalidArgument( "type attribute conflicts with managed type", )); } + // 类型边和目标写权限全部预检后,才共同提交计数、头部和版本通知。 let increments = self.prepare_increments(&[type_object])?; self.prepare_change(reference)?; self.apply_increments(increments); @@ -433,21 +545,24 @@ impl Heap { Ok(()) } - /// Transactional copy-on-update for built-in containers. The EndorObject - /// box and identity stay stable; only its payload and edge snapshot change. - /// All new edges are validated/retained before any old edge is released. + /// Store/内置容器的复制后替换入口;保持对象盒与身份,只更新载荷和边快照。 + /// 所有新边先验证并保留,再释放旧边;同一目标同时出现在新旧边中也不会中途被回收。 pub(crate) fn replace_payload( &mut self, reference: ObjectRef, payload: impl ObjectPayload, ) -> Result<(), VmError> { self.get(reference)?; + // 收集每条载荷强边,包括重复项;仅构造身份快照,不立即改计数。 let mut edges = Vec::new(); payload.trace(&mut |target| edges.push(target)); + // 先验证所有目标及累积计数溢出,失败时尚未改变堆。 let increments = self.prepare_increments(&edges)?; self.prepare_change(reference)?; self.apply_increments(increments); + // 写屏障和引用增量已提交,此处只更换载荷及其快照,不移动外层对象。 let object = self.get_mut(reference)?; + // 暂存旧边/旧载荷,等新状态与通知建立后再释放旧状态。 let old_edges = std::mem::replace(&mut object.payload_edges, edges); let old_payload = std::mem::replace(&mut object.payload, Box::new(payload)); self.record_change(reference, ChangeKey::Contents); @@ -456,28 +571,36 @@ impl Heap { Ok(()) } + /// 内部受控修改借用;仍先复用 get 的完整校验,不向 Store 外公开任意可变对象。 fn get_mut(&mut self, reference: ObjectRef) -> Result<&mut EndorObject, VmError> { self.get(reference)?; Ok(self.slots[reference.index].object.as_deref_mut().unwrap()) } + /// get 首先区分跨堆句柄,避免把异堆对象误认为本堆的同号槽。 fn validate_owner(&self, reference: ObjectRef) -> Result<(), VmError> { + // owner 错误独立于槽是否存在,弱引用升级不能把跨堆误判成过期。 if reference.heap_id != self.id { return Err(VmError::ForeignHandle); } Ok(()) } + /// 分配/替换载荷、类型附着、属性写入共同预检引用边;只计算增量,不实际改计数。 fn prepare_increments( &self, references: &[ObjectRef], ) -> Result, VmError> { + // 按目标聚合重复边,既保留多重拥有关系,又能一次预检总增量。 let mut counts = BTreeMap::::new(); + // 第一遍验证每条边并累计次数;没有任何已提交的计数修改。 for reference in references { self.get(*reference)?; + // 连重复次数本身也要 checked_add,不能依赖后续总计数检查替代。 let count = counts.entry(*reference).or_default(); *count = count.checked_add(1).ok_or(VmError::RefCountOverflow)?; } + // 第二遍确保每个目标的当前强计数能容纳完整增量。 for (reference, count) in &counts { self.get(*reference)? .header @@ -488,18 +611,24 @@ impl Heap { Ok(counts) } + /// 上述写入入口在预检和写屏障均通过后提交内部强边增量;不增加外部根。 fn apply_increments(&mut self, increments: BTreeMap) { + // 仅消费刚刚预检的增量;这段提交路径没有新的可恢复失败分支。 for (reference, count) in increments { self.get_mut(reference).unwrap().header.strong_refs += count; } } + /// Store/VM 将借用句柄变为额外外部根;两个计数全部预检成功后再共同更新。 pub(crate) fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> { + // 取得已验证的头部;外部根与总强计数必须保持一致。 let header = &mut self.get_mut(reference)?.header; + // 先计算两个新值,第二项溢出时第一项也尚未写回。 let strong = header .strong_refs .checked_add(1) .ok_or(VmError::RefCountOverflow)?; + // 外部根同样独立检查溢出,不把内部强边误计为宿主根。 let external = header .external_refs .checked_add(1) @@ -509,13 +638,17 @@ impl Heap { Ok(()) } + /// Store/VM 归还一份外部根;总强计数归零时启动非递归级联释放。 pub(crate) fn release(&mut self, reference: ObjectRef) -> Result<(), VmError> { + // 取得已验证的头部;外部根与总强计数必须保持一致。 let header = &mut self.get_mut(reference)?.header; + // 只有拥有的外部根可 release;借用或内部边不能从这里归还。 if header.external_refs == 0 { return Err(VmError::NoOwnedReference); } header.external_refs -= 1; header.strong_refs -= 1; + // 仍有内部边时留待后续释放或环回收;只有总强计数为零才立即析构。 let pending = if header.strong_refs == 0 { vec![reference] } else { @@ -525,6 +658,7 @@ impl Heap { Ok(()) } + /// Store 定位实际属性存储后写入;先保留新值强边,再替换并释放旧值边。 pub(crate) fn set_attr( &mut self, reference: ObjectRef, @@ -532,24 +666,29 @@ impl Heap { value: Value, ) -> Result<(), VmError> { self.check_attributes(reference)?; + // 只有对象变体形成内部强边,直接标量不参与托管对象计数。 let edges: Vec<_> = match &value { Value::Object(target) => vec![*target], _ => Vec::new(), }; + // 先验证所有目标及累积计数溢出,失败时尚未改变堆。 let increments = self.prepare_increments(&edges)?; self.prepare_change(reference)?; self.apply_increments(increments); + // 写屏障通过后移出旧值;属性更新本身不消耗调用者的外部根。 let old = self .get_mut(reference)? .attributes .insert(name.clone(), value); self.record_change(reference, ChangeKey::Attribute(name)); + // 新对象引用已先保留,最后释放旧属性拥有的那一条内部边。 if let Some(Value::Object(target)) = old { self.release_edges(vec![target]); } Ok(()) } + /// types 的属性查找读取自身字段表;返回借用 Value 表示,继承/转发由上层处理。 pub(crate) fn get_attr(&self, reference: ObjectRef, name: &str) -> Result { self.check_attributes(reference)?; self.get(reference)? @@ -559,23 +698,28 @@ impl Heap { .ok_or_else(|| VmError::AttributeNotFound(name.into())) } + /// Store 定位存储并通过类型保护后删除属性,成功才发通知并释放旧值强边。 pub(crate) fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> { self.check_attributes(reference)?; self.prepare_change(reference)?; + // 写屏障通过后移出旧值;属性更新本身不消耗调用者的外部根。 let old = self .get_mut(reference)? .attributes .remove(name) .ok_or_else(|| VmError::AttributeNotFound(name.into()))?; self.record_change(reference, ChangeKey::Attribute(name.into())); + // 删除成功后释放该属性原有的对象边,标量仅由 Rust Drop 清理。 if let Value::Object(target) = old { self.release_edges(vec![target]); } Ok(()) } + /// 所有受控写入在实际修改前调用:拒绝受保护对象,并预检版本是否还能递增。 pub(crate) fn prepare_change(&self, reference: ObjectRef) -> Result<(), VmError> { self.get(reference)?; + // 任意外层保护都有效;不能借进入/退出更内层作用域绕过只读限制。 if self .read_only .iter() @@ -593,28 +737,36 @@ impl Heap { Ok(()) } + /// 受控写入成功后递增对象版本,再合并通知;调用者必须先通过 prepare_change。 pub(crate) fn record_change(&mut self, reference: ObjectRef, key: ChangeKey) { + // prepare_change 已验证版本可递增;失败说明内部调用顺序破坏了约定。 let object = self.get_mut(reference).expect("validated change target"); object.header.change_version += 1; + // 先保存递增后版本,即使不发通知也保持版本真实反映修改。 let version = object.header.change_version; - // Fresh result construction has no existing observers. Keep versions, - // but do not notify application dependencies about derived allocations. + // 只读期间允许的写入只涉及新结果;保留版本,但不向应用发布派生结果构造通知。 if self.is_read_only() { return; } + // 本批已丢失精确信息,继续积累条目也无法补全,等待消费者全面重验。 if self.changes_overflow { return; } + // 同对象/键合并为最新版本;其他键仍分别保留。 let entry = (reference, key); + // 达上限时仅新增键触发溢出;已有键可继续更新。溢出后清空并设置全面重验标志。 if self.changes.len() >= MAX_PENDING_CHANGES && !self.changes.contains_key(&entry) { self.changes.clear(); self.changes_overflow = true; } else { + // 未溢出或仅更新已有键,保存该次提交的版本。 self.changes.insert(entry, version); } } + /// Store/宿主取走本批通知及溢出标志,使后续写入开始新的批次。 pub(crate) fn take_changes(&mut self) -> ChangeBatch { + // mem::take 将记录和标志一起归还调用者,本堆恢复空批次。 ChangeBatch { changes: std::mem::take(&mut self.changes) .into_iter() @@ -628,18 +780,25 @@ impl Heap { } } + /// 属性读写共用载荷能力检查;不允许字段的载荷即使没有该属性也先报协议错误。 fn check_attributes(&self, reference: ObjectRef) -> Result<(), VmError> { + // get 先验证身份;协议能力由载荷声明,不由属性表是否为空推断。 if !self.get(reference)?.payload.allows_attributes() { return Err(VmError::UnsupportedOperation("attributes")); } Ok(()) } + /// 载荷替换或属性更新移除旧内部边;逐边减强计数,随后统一处理新出现的零计数对象。 fn release_edges(&mut self, edges: Vec) { + // 工作栈只保存待处理身份,不拥有额外根;用于批量级联或可达性遍历。 let mut pending = Vec::new(); + // 每条旧边恰好减一次,重复边不能去重。 for target in edges { + // 已登记强边的目标应仍存活;这里 unwrap 表示内部图计数不变量。 let header = &mut self.get_mut(target).unwrap().header; header.strong_refs -= 1; + // 最后一条强边移除后加入工作栈,稍后再展开其拥有边。 if header.strong_refs == 0 { pending.push(target); } @@ -647,11 +806,13 @@ impl Heap { self.drain_zero_counts(pending); } - /// Removes the object before running Drop. Reuse increments its generation; - /// an exhausted generation permanently retires that slot. + /// 级联释放和环回收先从槽中摘除对象,再允许 Rust Drop;只接收已知存活身份。 fn take(&mut self, reference: ObjectRef) -> Box { + // 取走盒后槽立即不可访问,析构尚未发生。 let slot = &mut self.slots[reference.index]; + // 所有调用方都先证明对象存在,因此这里无需恢复性错误分支。 let object = slot.object.take().unwrap(); + // 仅能递增代际的空槽才加入 free;耗尽时永久停用,防身份绕回。 if let Some(next) = slot.generation.checked_add(1) { slot.generation = next; self.free.push(reference.index); @@ -660,12 +821,18 @@ impl Heap { object } + /// release/release_edges 共用工作栈,避免长引用链触发递归析构栈溢出。 fn drain_zero_counts(&mut self, mut pending: Vec) { + // 用显式工作栈逐个处理,避免对深链递归。 while let Some(reference) = pending.pop() { + // 先摘除自己,再递减出边;归零对象不可能仍有一条计入自身的有效强入边。 let object = self.take(reference); + // edges 枚举头部类型、载荷快照和属性边,均参与生命周期维护。 for target in object.edges() { + // 已登记强边的目标应仍存活;这里 unwrap 表示内部图计数不变量。 let header = &mut self.get_mut(target).unwrap().header; header.strong_refs -= 1; + // 最后一条强边移除后加入工作栈,稍后再展开其拥有边。 if header.strong_refs == 0 { pending.push(target); } @@ -674,25 +841,31 @@ impl Heap { } } - /// Full, explicit cycle collection. External references are roots; every - /// managed attribute and declared payload edge participates in tracing. + /// Store 显式全堆环回收:从外部根标记可达对象,余下对象统一摘除并释放。 pub(crate) fn collect_cycles(&mut self) -> usize { + // 标记按槽索引存储;本次 &mut self 独占期间槽不会被其他调用修改。 let mut marked = vec![false; self.slots.len()]; + // 工作栈只保存待处理身份,不拥有额外根;用于批量级联或可达性遍历。 let mut pending = Vec::new(); + // 只有外部根作为起点;纯内部互引环即使 strong_refs 非零也不是根。 for slot in &self.slots { + // 跳过空槽和没有外部根的对象,后者仍可能经其他根遍历到。 if let Some(object) = &slot.object && object.header.external_refs > 0 { pending.push(object.header.identity); } } + // 从根沿强边展开对象图,工作栈允许重复入栈,marked 保证只展开一次。 while let Some(reference) = pending.pop() { + // 已标记身份无需重复展开,保证带环图遍历会终止。 if marked[reference.index] { continue; } marked[reference.index] = true; pending.extend(self.get(reference).unwrap().edges()); } + // 所有未标记的占用槽都是垃圾;空槽不进入集合。 let garbage: Vec<_> = self .slots .iter() @@ -700,23 +873,29 @@ impl Heap { .filter(|(index, _)| !marked[*index]) .filter_map(|(_, slot)| slot.object.as_ref().map(|object| object.header.identity)) .collect(); + // 先摘除整批垃圾,避免环内互引让析构依赖逐对象处理的次序。 let objects: Vec<_> = garbage .iter() .map(|reference| self.take(*reference)) .collect(); - // All garbage slots are detached before touching edges or running Drop. + // 垃圾槽已全部摘除:垃圾到垃圾的边无需再减计数,只有指向幸存者的边要归还。 + // 用已取出的对象盒枚举边快照,不尝试通过失效句柄重新借用垃圾对象。 for object in &objects { + // edges 枚举头部类型、载荷快照和属性边,均参与生命周期维护。 for target in object.edges() { + // 仍可 get 的目标是外部根可达幸存者;它的强计数扣除来自垃圾的一条边。 if let Ok(survivor) = self.get_mut(target) { survivor.header.strong_refs -= 1; } } } + // 返回摘除对象数;最后统一 Drop 释放宿主资源。 let collected = objects.len(); drop(objects); collected } + /// Store::object_count 查询当前存活对象数,不包含已释放空槽。 pub(crate) fn len(&self) -> usize { self.live } @@ -728,13 +907,18 @@ mod tests { use crate::Record; #[test] + /// 内嵌回归测试:伪造越界槽与错误代际,分别验证不可变/可变入口都拒绝。 fn invalid_slots_and_generations_are_rejected() { + // 测试直接使用内部 Heap,以便注入公共 Store API 不允许构造的异常状态。 let mut heap = Heap::new().unwrap(); + // 一个真实有效的对象作为基准,之后只篡改句柄中的单个维度。 let reference = heap.allocate(Box::new(Record)).unwrap(); + // 保持堆身份与代际有效,仅让槽越界,隔离范围检查。 let invalid = ObjectRef { index: usize::MAX, ..reference }; + // 保持槽位置有效,仅伪造代际,覆盖可变借用路径的同一校验。 let stale = ObjectRef { generation: 1, ..reference @@ -744,15 +928,21 @@ mod tests { } #[test] + /// 内嵌回归测试:槽复用会改变代际,代际耗尽则永久退休,不让旧句柄指向新对象。 fn reused_slots_never_alias_and_exhausted_generations_retire() { + // 测试直接使用内部 Heap,以便注入公共 Store API 不允许构造的异常状态。 let mut heap = Heap::new().unwrap(); + // 先释放第一个对象,使它的槽成为下一次分配的复用候选。 let first = heap.allocate(Box::new(Record)).unwrap(); heap.release(first).unwrap(); + // 复用索引相同、代际不同,旧身份必须失效。 let second = heap.allocate(Box::new(Record)).unwrap(); assert_eq!(first.index, second.index); assert_ne!(first.generation, second.generation); assert!(matches!(heap.get(first), Err(VmError::InvalidHandle))); + // 内部故障注入:把槽及对象身份同步改成最高代际,再验证释放后的永久退休。 heap.slots[second.index].generation = u64::MAX; + // 构造与被注入槽一致的当前有效身份,避免把测试误变成代际不匹配。 let last = ObjectRef { generation: u64::MAX, ..second @@ -764,15 +954,20 @@ mod tests { .header .identity = last; heap.release(last).unwrap(); + // 耗尽槽不得复用,新的对象只能取得其他索引。 let next = heap.allocate(Box::new(Record)).unwrap(); assert_ne!(next.index, last.index); } #[test] + /// 内嵌回归测试:注入强计数上限,确认 retain 与属性写入溢出时没有部分提交。 fn failed_reference_increments_leave_counts_and_attributes_unchanged() { + // 测试直接使用内部 Heap,以便注入公共 Store API 不允许构造的异常状态。 let mut heap = Heap::new().unwrap(); + // owner 接收属性边,target 模拟无法再增加强计数的对象。 let owner = heap.allocate(Box::new(Record)).unwrap(); let target = heap.allocate(Box::new(Record)).unwrap(); + // 仅改强计数,外部根仍为一;后面的断言检查失败是否意外改动 external_refs。 heap.get_mut(target).unwrap().header.strong_refs = usize::MAX; assert_eq!(heap.retain(target), Err(VmError::RefCountOverflow)); assert_eq!(heap.get(target).unwrap().header.external_refs, 1);