docs(objects): explain heap tracking and reference lifecycle

This commit is contained in:
2026-11-15 17:18:34 +08:00
parent 6be33467c8
commit 9861a6d586
+213 -18
View File
@@ -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<Box<EndorObject>>,
}
#[derive(Debug)]
/// 一层只读作用域;protected 是进入时的存活身份快照,其余字段只收集借用观察。
struct ReadOnly {
// 该层不可修改的已有对象,后续新对象不在此集合中。
protected: BTreeSet<ObjectRef>,
// 兼容的对象级去重读取集合。
reads: Mutex<BTreeSet<ObjectRef>>,
// 子区间栈;bool 表示是否发生程序内容读取(而非仅验证)。
regions: Mutex<Vec<BTreeMap<ObjectRef, bool>>>,
// 按依赖键去重,保留该层首次观察的版本/名称值。
dependencies: Mutex<BTreeMap<ReadKey, ReadDependency>>,
// 与 regions 同步压栈/弹栈的精确子区间依赖。
dependency_regions: Mutex<Vec<BTreeMap<ReadKey, ReadDependency>>>,
}
#[derive(Debug)]
/// ObjectStore 独占的对象槽及生命周期状态;其他模块不能直接修改槽或引用计数。
pub(crate) struct Heap {
// 本堆的进程内唯一身份。
id: u64,
// 已分配过的槽,可含空槽;对象实体单独装箱。
slots: Vec<Slot>,
// 可复用空槽索引,按栈顺序取回。
free: Vec<usize>,
// 实际占用槽数,分配/摘除时同步维护。
live: usize,
// 同一对象/变更键只保留最新版本,限制通知内存占用。
changes: BTreeMap<(ObjectRef, ChangeKey), u64>,
// 本批已溢出:停止精确通知,要求消费者全面重验依赖。
changes_overflow: bool,
// 活跃只读作用域栈,任意一层保护对象就禁止修改。
read_only: Vec<ReadOnly>,
// 当前线程的验证读取嵌套深度,RAII 退出时递减。
validation_reads: Mutex<HashMap<ThreadId, usize>>,
}
/// 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<crate::Value>,
},
}
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<ReadAccess>,
/// 精确观察列表,供 Store::read_dependency_valid 重验。
pub dependencies: Vec<ReadDependency>,
}
#[derive(Debug)]
pub struct ReadSet {
/// 整个作用域读取到的借用对象身份。
pub objects: Vec<ObjectRef>,
/// 精确观察列表,供 Store::read_dependency_valid 重验。
pub dependencies: Vec<ReadDependency>,
}
/// 仅借用堆的验证读取守卫;不拥有对象,也不延长只读作用域寿命。
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<Self, VmError> {
// 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<Item = ObjectRef> + '_ {
// 空槽跳过;按每个当前代际重建身份,而不是复用历史外部句柄。
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<Vec<ObjectRef>, VmError> {
Ok(self.end_read_only_precise(scope)?.objects)
}
/// Store/VM 结束最内层只读作用域并取走全部观察;错误令牌不弹出状态。
pub(crate) fn end_read_only_precise(&mut self, scope: usize) -> Result<ReadSet, VmError> {
// 令牌是深度而非永久唯一 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<usize, VmError> {
// 子区间必须依附当前最内层只读作用域,不能独立开启读取追踪。
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<Vec<ObjectRef>, 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<Vec<ReadAccess>, VmError> {
Ok(self.end_read_tracking_precise(token)?.accesses)
}
/// 精确结束子调用区间;同时弹出两套追踪栈,外层区间和只读作用域继续有效。
pub(crate) fn end_read_tracking_precise(&self, token: usize) -> Result<TrackedReads, VmError> {
// 子区间必须依附当前最内层只读作用域,不能独立开启读取追踪。
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<T>(
&self,
compute: impl FnOnce() -> Result<T, VmError>,
) -> Result<T, VmError> {
// 无活跃只读作用域就没有需要收集/分类的观察。
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<crate::Value>,
) {
// 无活跃只读作用域就没有需要收集/分类的观察。
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<dyn ObjectPayload>,
) -> Result<ObjectRef, VmError> {
// 收集每条载荷强边,包括重复项;仅构造身份快照,不立即改计数。
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<BTreeMap<ObjectRef, usize>, VmError> {
// 按目标聚合重复边,既保留多重拥有关系,又能一次预检总增量。
let mut counts = BTreeMap::<ObjectRef, usize>::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<ObjectRef, usize>) {
// 仅消费刚刚预检的增量;这段提交路径没有新的可恢复失败分支。
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<Value, VmError> {
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<ObjectRef>) {
// 工作栈只保存待处理身份,不拥有额外根;用于批量级联或可达性遍历。
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<EndorObject> {
// 取走盒后槽立即不可访问,析构尚未发生。
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<ObjectRef>) {
// 用显式工作栈逐个处理,避免对深链递归。
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);