docs(objects): explain heap tracking and reference lifecycle
This commit is contained in:
+213
-18
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user