docs(objects): explain exports and storage arithmetic regressions

This commit is contained in:
2026-11-15 17:42:18 +08:00
parent e5da8055e0
commit 9bf887af1e
3 changed files with 90 additions and 4 deletions
+26 -3
View File
@@ -1,6 +1,6 @@
//! Core Endor objects, values, validated executable data and object storage.
//! This crate has no VM evaluator or external dependencies. Handles, reference
//! accounting and mutable payload replacement remain encapsulated here.
//! Endor 对象、数值、已验证代码和存储的公共 API;compiler/core 和宿主都从这里导入。
//! 不包含 VM 指令求值器;数值精确运算使用 num_* 依赖,句柄/计数/载荷修改仍封装在本 crate。
//! 下方示例由文档测试调用:创建拥有根、读写属性,最后 release,不把复制句柄误当新增根。
//!
//! ```
//! use objects::{ObjectStore, Value};
@@ -13,41 +13,64 @@
//! # Ok::<(), objects::VmError>(())
//! ```
// 内部内置载荷:字符串、字典、函数、闭包槽和异常。
mod builtins;
// 内部变更通知结构,消费者据此失效缓存。
mod changes;
// 代码布局和构造校验,不执行代码。
mod code;
// 各层共享错误,避免对象层依赖 VM。
mod error;
// 私有堆:代际身份、根/边计数、只读追踪和回收。
mod heap;
// 指令协议及字节编解码,供编译器和执行器共用。
mod instruction;
// 宿主回调契约、上下文和结果根移交。
mod native;
// 无损数值表示和受预算约束的精确运算。
mod number;
// 直接值、载荷协议及公共对象实体。
mod object;
// 受控对象操作门面,宿主可独立于 VM 使用。
mod store;
// 字典/Cell/函数等内置载荷的 Store 扩展。
mod store_builtins;
// 名义类型、能力、字段注解及子模块中的契约/效果分析。
mod types;
// 向宿主公开内置载荷,分配和修改仍经 Store。
pub use builtins::{
EndorBaseExceptionObject, EndorCellObject, EndorDictObject, EndorFunctionObject,
EndorStringObject, EndorTracebackEntry,
};
// 向缓存消费者公开借用变更通知,不提供对象拥有根。
pub use changes::{ChangeBatch, ChangeKey, ObjectChange};
// 编译器构造与 VM 共享执行数据需要的代码 API。
pub use code::{CodeLayout, EndorCode, EndorCodeObject, SourceSignature};
// 调用者结构化匹配的统一错误。
pub use error::VmError;
// 只公开句柄和读取观察结果,不导出可变 Heap。
pub use heap::{ObjectRef, ReadAccess, ReadDependency, ReadSet, TrackedReads, WeakObjectRef};
// 编译器/宿主组装指令,执行器读取解码结果。
pub use instruction::{DecodedInstruction, Instruction, Opcode};
// 宿主实现原生函数并声明参数、返回值和效果。
pub use native::{
CallArity, CallEffect, CallSignature, CallType, ContextNativeFunction, NativeCallable,
NativeContext, NativeResult,
};
// 编译器字面量、VM 算术和宿主显式转换共用数值 API。
pub use number::{
ArithmeticOp, EndorDecimal, EndorInteger, EndorNumber, EndorNumberObject, EndorRational,
EndorReal,
};
// 宿主实现载荷及等待协议,保留旧原生函数别名兼容。
pub use object::{
AwaitPoll, CompareOp, EndorNativeCall, EndorNativeFunctionObject, EndorObject, NativeCall,
NativeFunction, ObjectHeader, ObjectPayload, Record, Value,
};
// 独立存储的主要入口,VM 也持有同一门面。
pub use store::ObjectStore;
// 宿主注册类型/能力,执行器检查构造和调用契约。
pub use types::{
AnnotationType, ClassField, EndorTraitObject, EndorTypeObject, MethodRequirement, RootTypes,
TypeConstructor, TypedMethodRequirement,
+11
View File
@@ -1,20 +1,27 @@
//! number.rs 的公开 API 回归:比较数学值与存储表示分开,且构造/运算使用不同预算。
use objects::{ArithmeticOp, CompareOp, EndorNumber, EndorReal};
/// 测试辅助:用 numeric_compare 断言数学相等,不能用 Rust Eq 比较不同表示。
fn equal(a: &EndorNumber, b: &EndorNumber) {
assert!(a.numeric_compare(b, CompareOp::Eq).unwrap());
}
#[test]
/// 测试框架调用:验证精确跨表示求和、整数溢出提升、有理数等价和大整数混合浮点比较。
fn exact_real_arithmetic_keeps_large_values_and_cross_representation_equivalence() {
// 十进制保留尾零,后续与分数求和应保持精确数学值。
let tenth = EndorNumber::decimal("0.10").unwrap();
// 分数构造不要求约分,算术结果仍应等价于精确 0.3。
let fifth = EndorNumber::rational("2", "10").unwrap();
equal(
&tenth.arithmetic(&fifth, ArithmeticOp::Add).unwrap(),
&EndorNumber::decimal("0.3").unwrap(),
);
// 机器最大整数加一必须提升到大整数,不能溢出或丢精度。
let large = EndorNumber::integer("9223372036854775807").unwrap();
equal(
&large.arithmetic(&1_i64.into(), ArithmeticOp::Add).unwrap(),
&EndorNumber::integer("9223372036854775808").unwrap(),
);
// 最小负整数转换需单独处理其超出正 i64 范围的幅值。
let min = EndorNumber::integer("-9223372036854775808").unwrap();
assert_eq!(min.as_i64(), Some(i64::MIN));
equal(
@@ -33,12 +40,15 @@ fn exact_real_arithmetic_keeps_large_values_and_cross_representation_equivalence
);
}
#[test]
/// 测试框架调用:验证 IEEE 符号零/NaN、零除/非有限结果拒绝,以及巨大指数可存不可运算。
fn ieee_signed_zero_nan_and_operation_limits_are_explicit() {
// 浮点取负翻符号位,+0 应变 -0 而不是规范成整数零。
let negative = EndorNumber::binary_float(0.).negated().unwrap();
assert_eq!(
negative.as_real(),
Some(&EndorReal::BinaryFloat((-0.0_f64).to_bits()))
);
// 同一 NaN 数学比较也不等于自身,Ne 必须为真。
let nan = EndorNumber::binary_float(f64::NAN);
assert!(!nan.numeric_compare(&nan, CompareOp::Eq).unwrap());
assert!(nan.numeric_compare(&nan, CompareOp::Ne).unwrap());
@@ -52,6 +62,7 @@ fn ieee_signed_zero_nan_and_operation_limits_are_explicit() {
.arithmetic(&2_i64.into(), ArithmeticOp::Multiply)
.is_err()
);
// 极长指数语法合法且可原样显示,但实际运算要触发预算保护。
let huge = EndorNumber::decimal("1e99999999999999999").unwrap();
assert_eq!(huge.to_string(), "1e99999999999999999");
assert!(huge.arithmetic(&1_i64.into(), ArithmeticOp::Add).is_err());
+53 -1
View File
@@ -1,3 +1,5 @@
//! 独立 ObjectStore 回归场景,由 Rust 测试框架调用;不需要 VM 执行指令。
//! 场景覆盖根/边所有权、失败原子性与读取作用域。
use std::sync::Arc;
use objects::{
@@ -6,13 +8,21 @@ use objects::{
};
#[test]
/// 验证容器替换保持对象地址、跨堆边被拒绝且失去外部根的互引环可回收。
fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() {
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
let mut store = ObjectStore::new().unwrap();
// 独立堆用于证明句柄归属不能仅以槽号判断。
let mut other = ObjectStore::new().unwrap();
// 字典与下面 Cell 各自有一份外部根,Cell 另外持有字典强边。
let dictionary = store.new_dict().unwrap();
// Cell 初始拥有字典内部边,后续反向写入形成环。
let cell = store.new_cell(Some(Value::Object(dictionary))).unwrap();
// 异堆对象保持自身外部根,不应被本堆写入接管。
let foreign = other.new_object().unwrap();
// 保存对象实体地址,验证替换载荷不会移动外层稳定 Box。
let address = store.object(dictionary).unwrap() as *const _;
// 字典键使用直接文本,不产生托管强边。
let key = Value::Text("cell".into());
store
@@ -37,10 +47,15 @@ fn standalone_storage_preserves_identity_validates_edges_and_collects_cycles() {
}
#[test]
/// 验证无效函数构造不分配对象,两个函数共享同一执行数据并通过代码边保留常量。
fn standalone_function_factories_validate_and_own_shared_executable_data() {
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
let mut store = ObjectStore::new().unwrap();
// 字符串最初由测试根持有,随后代码常量也建立内部边。
let constant = store.new_string("kept by code").unwrap();
// 函数全局空间在两个函数载荷中各持有一条内部边。
let globals = store.new_dict().unwrap();
// 最小已验证代码只装载常量再返回,不在本测试执行。
let code = EndorCode::from_instructions(
&[
Instruction::new(Opcode::LoadConst, 0),
@@ -50,7 +65,9 @@ fn standalone_function_factories_validate_and_own_shared_executable_data() {
CodeLayout::default(),
)
.unwrap();
// 分配代码载荷后常量由其 trace 强边持有。
let code = store.new_code(code).unwrap();
// 记录失败构造前对象数,断言失败不得泄漏半成品。
let before = store.object_count();
assert_eq!(
store.new_function("bad", code, globals, vec![Value::Nil], vec![]),
@@ -60,50 +77,66 @@ fn standalone_function_factories_validate_and_own_shared_executable_data() {
);
assert_eq!(store.object_count(), before);
assert_eq!(store.ref_count(code).unwrap(), 1);
// 第一个函数持有代码和全局字典,不复制代码对象。
let first = store
.new_function("first", code, globals, vec![], vec![])
.unwrap();
// 第二个函数独立持有相同代码/全局边,供释放顺序验证。
let second = store
.new_function("second", code, globals, vec![], vec![])
.unwrap();
// 借用第一个函数记录的代码句柄,不新增根。
let first_code = store
.payload::<EndorFunctionObject>(first, "function")
.unwrap()
.code();
// 借用第二个函数代码句柄,与第一函数应指向相同执行数据。
let second_code = store
.payload::<EndorFunctionObject>(second, "function")
.unwrap()
.code();
// 取得 Rust Arc 只用于指针共享验证,它不替代托管代码根。
let first_executable = store
.payload::<EndorCodeObject>(first_code, "code")
.unwrap()
.shared_code();
// 第二份 Arc 与第一份应共享同一 EndorCode 分配。
let second_executable = store
.payload::<EndorCodeObject>(second_code, "code")
.unwrap()
.shared_code();
assert!(Arc::ptr_eq(&first_executable, &second_executable));
// 先释放 Rust 共享引用,再测试托管函数/代码边生命周期。
drop(first_executable);
drop(second_executable);
// 归还测试拥有根后,常量仍应通过两个函数到代码的图存活。
store.release(constant).unwrap();
store.release(code).unwrap();
store.release(globals).unwrap();
assert_eq!(store.str(constant).unwrap(), "kept by code");
// 移除一函数仅减一条代码边,另一函数仍保留代码和常量。
store.release(first).unwrap();
assert_eq!(store.ref_count(code).unwrap(), 1);
// 最后一个函数释放后,代码/全局/常量无根链一起回收。
store.release(second).unwrap();
assert_eq!(store.object_count(), 0);
}
#[test]
/// 验证已有对象受保护、新结果可写、嵌套层会保护外层新对象及退出后恢复权限。
fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_results() {
// 每个测试建立独立存储,避免对象/作用域状态跨场景污染。
let mut store = objects::ObjectStore::new().unwrap();
// 显式自举两个类型根,清理时需归还它们并收集类型图环。
let roots = store.bootstrap_types().unwrap();
// 此记录在进入只读前分配,属性和类型附着都应被写屏障保护。
let record = store.new_object().unwrap();
// 已有字典受同一只读屏障保护,不只保护普通属性。
let dict = store.new_dict().unwrap();
let cell = store
.new_cell(Some(objects::Value::Number(1_i64.into())))
.unwrap();
// 进入时快照已有对象,之后分配的 fresh 不在本层保护集合。
let scope = store.begin_read_only();
assert!(
store
@@ -129,57 +162,76 @@ fn read_only_scopes_block_existing_container_and_type_writes_but_allow_fresh_res
store.cell_get(cell).unwrap(),
objects::Value::Number(1_i64.into())
);
// 在只读调用内分配的新结果可初始化属性。
let fresh = store.new_object().unwrap();
store.set_attr(fresh, "value", objects::Value::Nil).unwrap();
// Record 不原地赋值,只读引用重绑定可以返回 false 而不修改旧对象。
assert!(!store.assign_from(record, fresh).unwrap());
// 嵌套层快照/区间只影响自身,结束必须按 LIFO。
let nested = store.begin_read_only();
assert!(
store
.set_attr(fresh, "value", objects::Value::Bool(true))
.is_err()
);
// 退出内层后 fresh 又不受外层快照保护。
store.end_read_only(nested).unwrap();
store
.set_attr(fresh, "value", objects::Value::Bool(true))
.unwrap();
// 结束外层只读同时取得借用读取集合,Cell 读取应被记录。
let reads = store.end_read_only(scope).unwrap();
assert!(reads.contains(&cell));
store
.set_attr(record, "value", objects::Value::Nil)
.unwrap();
// 逐项归还测试拥有根;类型/实例内部边由 Heap 清理,不手动归还。
for object in [record, dict, cell, fresh, roots.object, roots.type_type] {
store.release(object).unwrap();
}
// 显式回收自举类型图中的环,最后验证堆为空。
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());
// 进入时快照已有对象,之后分配的 fresh 不在本层保护集合。
let scope = store.begin_read_only();
// 外层区间开始后,其内所有读取都要包含在最终结果中。
let outer = store.begin_read_tracking().unwrap();
store.object(first).unwrap();
// 嵌套层快照/区间只影响自身,结束必须按 LIFO。
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();
}