Track subcall read regions within read-only calls

This commit is contained in:
2026-10-21 16:31:04 +08:00
parent 4a2f81cb48
commit b7daabdaad
7 changed files with 76 additions and 2 deletions
+1 -1
View File
@@ -11,7 +11,7 @@
| [changes.rs](changes.rs) | 合并变更批次、变更键与有界通知额度 |
| [number.rs](number.rs) | Integer/Decimal/Rational/BinaryFloat/Complex 的无损表示和装箱载荷 |
| [builtins.rs](builtins.rs) | String、Dict、Cell、Function、BaseException 载荷与 trace 边 |
| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收、只读变更屏障/读取跟踪及私有边界测试 |
| [heap.rs](heap.rs) | 私有 Heap、不可伪造 ObjectRef、稳定 Box、引用事务、迭代释放、循环回收、只读变更屏障/全调用及嵌套子调用读取跟踪及私有边界测试 |
| [store.rs](store.rs) | ObjectStore、空堆创建、对象操作、retain/release 和回收入口 |
| [store_builtins.rs](store_builtins.rs) | 载荷校验、代码/函数工厂、Dict/Cell 操作及键语义 |
| [instruction.rs](instruction.rs) | Opcode、Instruction、wordcode 编解码、MakeFunction/比较参数与跳转读取接口 |
+30
View File
@@ -39,6 +39,7 @@ struct Slot {
struct ReadOnly {
protected: BTreeSet<ObjectRef>,
reads: Mutex<BTreeSet<ObjectRef>>,
regions: Mutex<Vec<BTreeSet<ObjectRef>>>,
}
#[derive(Debug)]
@@ -84,6 +85,7 @@ impl Heap {
self.read_only.push(ReadOnly {
protected,
reads: Mutex::new(BTreeSet::new()),
regions: Mutex::new(Vec::new()),
});
self.read_only.len()
}
@@ -103,6 +105,26 @@ impl Heap {
.collect())
}
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(BTreeSet::new());
Ok(regions.len())
}
pub(crate) fn end_read_tracking(&self, token: usize) -> Result<Vec<ObjectRef>, 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");
if token == 0 || token != regions.len() {
return Err(VmError::InvalidArgument("read tracking nesting mismatch"));
}
Ok(regions.pop().unwrap().into_iter().collect())
}
pub(crate) fn is_read_only(&self) -> bool {
!self.read_only.is_empty()
}
@@ -115,6 +137,14 @@ impl Heap {
.lock()
.expect("read tracker poisoned")
.insert(reference);
for region in scope
.regions
.lock()
.expect("read tracker poisoned")
.iter_mut()
{
region.insert(reference);
}
}
}
}
+10
View File
@@ -21,6 +21,16 @@ impl ObjectStore {
pub fn end_read_only(&mut self, scope: usize) -> Result<Vec<ObjectRef>, VmError> {
self.heap.end_read_only(scope)
}
/// Track one subcall inside the active read-only scope. Regions nest in
/// LIFO order and do not clear the enclosing call's read dependencies.
/// Handles returned by end_read_tracking are borrowed, never owning roots.
pub fn begin_read_tracking(&self) -> Result<usize, VmError> {
self.heap.begin_read_tracking()
}
pub fn end_read_tracking(&self, token: usize) -> Result<Vec<ObjectRef>, VmError> {
self.heap.end_read_tracking(token)
}
pub fn is_read_only(&self) -> bool {
self.heap.is_read_only()
}
+2
View File
@@ -8,3 +8,5 @@
| [store.rs](store.rs) | 只读嵌套、容器与类型写入屏障、稳定地址、跨存储句柄拒绝与更新原子性、Dict/Cell 循环回收、函数校验失败无泄漏、共享代码与常量生命周期 |
非法句柄索引、槽位代数与计数溢出仍在 src/heap.rs 的私有单元测试内,不开放句柄构造器。VM 委托 API 与求值行为继续由 [core/tests](../../core/tests/README.md) 检查。随工作区在指定远端运行,本地不测试;现有测试源码不代表已通过,见 [验证](../../../docs/operations/validation.md)。返回 [objects](../README.md)。
store.rs 还覆盖只读子调用读取区域、重复读取、LIFO 校验与未关闭区域的调用结束清理;这些用例按约定不在本地运行。
+30
View File
@@ -153,3 +153,33 @@ fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_res
store.collect_cycles();
assert_eq!(store.object_count(), 0);
}
#[test]
fn read_tracking_regions_repeat_shared_reads_and_preserve_enclosing_dependencies() {
let mut store = ObjectStore::new().unwrap();
let first = store.new_object().unwrap();
let second = store.new_object().unwrap();
assert!(store.begin_read_tracking().is_err());
let scope = store.begin_read_only();
let outer = store.begin_read_tracking().unwrap();
store.object(first).unwrap();
let nested = store.begin_read_tracking().unwrap();
store.object(first).unwrap();
store.object(second).unwrap();
assert!(store.end_read_tracking(outer).is_err());
assert_eq!(
store.end_read_tracking(nested).unwrap(),
vec![first, second]
);
assert_eq!(store.end_read_tracking(outer).unwrap(), vec![first, second]);
let repeated = store.begin_read_tracking().unwrap();
store.object(first).unwrap();
assert_eq!(store.end_read_tracking(repeated).unwrap(), vec![first]);
// Ending the call discards an unfinished region, as on callback failure.
store.begin_read_tracking().unwrap();
assert_eq!(store.end_read_only(scope).unwrap(), vec![first, second]);
assert!(store.end_read_tracking(1).is_err());
assert!(!store.is_read_only());
store.release(first).unwrap();
store.release(second).unwrap();
}