From fce5ccf88367e377b842e4ed250ed475d2afad48 Mon Sep 17 00:00:00 2001 From: Codex Bot Date: Sun, 15 Nov 2026 17:27:15 +0800 Subject: [PATCH] docs(objects): explain native contracts and scoped root transfers --- crates/objects/src/native.rs | 118 ++++++++++++++++++++++++++++++++++- 1 file changed, 117 insertions(+), 1 deletion(-) diff --git a/crates/objects/src/native.rs b/crates/objects/src/native.rs index 44c1746..f85e0cb 100644 --- a/crates/objects/src/native.rs +++ b/crates/objects/src/native.rs @@ -1,3 +1,7 @@ +//! 宿主原生调用协议:Store 校验声明,VM 分派调用,NativeContext 管理临时根与输出所有权。 +//! 返回 Invoke 把调用交回 VM 的帧循环,避免 Rust 回调递归进入求值器。 +//! ReadOnly 是宿主承诺加托管写屏障;本模块不能拦截任意 Rust 回调自行执行的外部 I/O。 + use std::{fmt, sync::Arc}; use crate::{ObjectPayload, ObjectRef, ObjectStore, Value, VmError, WeakObjectRef}; @@ -6,7 +10,9 @@ use crate::{ObjectPayload, ObjectRef, ObjectStore, Value, VmError, WeakObjectRef /// callable through the VM's existing frame loop, without recursively entering it. #[derive(Debug)] pub enum NativeResult { + /// 回调内为借用结果;execute 成功输出时其中对象已获得移交给 VM 的根。 Return(Value), + /// 委托目标与参数在 execute 成功输出时各有拥有根,VM 接管后负责释放。 Invoke { function: ObjectRef, arguments: Vec, @@ -17,27 +23,36 @@ pub enum NativeResult { /// The store validates metadata before use; usize::MAX denotes an open upper bound. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct CallArity { + /// 最少位置参数数量。 pub required: usize, + /// 最多位置参数数量,usize::MAX 表示开放上界。 pub maximum: usize, } impl CallArity { + /// 宿主声明固定位置参数数量,含显式接收者。 pub const fn exact(count: usize) -> Self { Self::range(count, count) } + /// 宿主声明闭区间,构造时不验证上下界;使用前 validate。 pub const fn range(required: usize, maximum: usize) -> Self { Self { required, maximum } } + /// VM/契约检查查询数量是否落在闭区间,不改变元数据。 pub fn accepts(self, count: usize) -> bool { (self.required..=self.maximum).contains(&count) } + /// Store/调用入口拒绝颠倒的上下界;成功返回原数量声明。 pub(crate) fn validate(self) -> Result { + // 上界不能小于下界,否则任何实际调用都无法满足有效区间。 if self.required > self.maximum { return Err(VmError::InvalidArgument("invalid callable arity metadata")); } Ok(self) } + /// 调用入口先验证声明,再生成携带函数名和实参数量的错误。 pub(crate) fn check(self, name: String, provided: usize) -> Result<(), VmError> { self.validate()?; + // 元数据合法后再区分用户提供的数量不匹配。 if !self.accepts(provided) { return Err(VmError::ArgumentCount { name, @@ -54,11 +69,14 @@ impl CallArity { /// existing objects or perform external I/O. Unknown legacy calls remain unknown. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum CallEffect { + /// 可分配结果,但承诺不改已有对象或执行外部 I/O。 ReadOnly, /// The native body itself is read-only; delegated effects are dynamic and /// checked by the VM in the same execution/heap scope. Delegated, + /// 声明可能修改已有托管对象,只读执行不允许。 Mutating, + /// 声明包含外部效果,只读执行不允许。 External, } @@ -66,7 +84,9 @@ pub enum CallEffect { /// all definition edges even when an earlier alternative already matches. #[derive(Debug, Clone)] pub enum CallType { + /// 单个已解析注解,不是源码字符串。 Single(crate::AnnotationType), + /// 平坦非空候选列表;所有定义引用都须有效。 Union(Vec), } impl From for CallType { @@ -75,31 +95,40 @@ impl From for CallType { } } impl CallType { + /// 宿主构造类型或 Unit 的平坦联合,供可空参数/返回值约束使用。 pub fn nullable(value: crate::AnnotationType) -> Self { Self::Union(vec![value, crate::AnnotationType::Unit]) } + /// 内部检查和契约分析将单类型与联合统一为借用切片。 pub(crate) fn alternatives(&self) -> &[crate::AnnotationType] { + // 单项通过 from_ref 借用,联合直接借用 Vec,不额外分配或复制定义引用。 match self { Self::Single(value) => std::slice::from_ref(value), Self::Union(values) => values, } } + /// 载荷 trace/调用保护枚举所有名义类型与能力引用,不因提前匹配而漏边。 pub fn references(&self) -> impl Iterator + '_ { self.alternatives() .iter() .copied() .filter_map(crate::AnnotationType::reference) } + /// 契约分析检查联合是否含 Any,不代表其他分支的定义边可省略。 pub fn is_any(&self) -> bool { self.alternatives() .iter() .any(|value| matches!(value, crate::AnnotationType::Any)) } + /// 调用/签名检查确认联合非空,且每个托管类型/能力引用有效并有正确载荷。 pub fn validate(&self, store: &ObjectStore) -> Result<(), VmError> { + // 空联合没有可接受值,必须作为声明错误拒绝。 if self.alternatives().is_empty() { return Err(VmError::InvalidArgument("empty native union type")); } + // 即使首个分支是 Any,也校验后续全部定义,不允许无效句柄藏在联合中。 for value in self.alternatives() { + // 只有名义/能力注解包含定义句柄,其他直接注解没有需验证的对象。 match *value { crate::AnnotationType::Nominal(reference) => { store.payload::(reference, "Type")?; @@ -112,6 +141,7 @@ impl CallType { } Ok(()) } + /// 参数/返回检查先验证全部分支,再按顺序尝试匹配;全不匹配才生成类型错误。 pub fn check( &self, store: &ObjectStore, @@ -120,11 +150,14 @@ impl CallType { value: &Value, ) -> Result<(), VmError> { self.validate(store)?; + // 通过完整声明校验后才允许按首个匹配项短路。 for expected in self.alternatives() { + // 匹配即成功;协议失败仍传播错误,而非当作不匹配。 if store.matches_annotation(value, *expected)? { return Ok(()); } } + // 所有项均不匹配时才组装诊断类型名,不在成功热路径构造文本。 let names = self .alternatives() .iter() @@ -143,13 +176,19 @@ impl CallType { /// Missing fixed slots are covered by variadic; optionality comes from arity. #[derive(Debug, Clone)] pub struct CallSignature { + /// 参数数量区间决定可省略的尾部参数,不由类型列表长度直接决定。 pub arity: CallArity, + /// 固定位置参数约束,含需要显式传入的接收者。 pub parameters: Vec, + /// 固定列表之外的参数统一使用此约束。 pub variadic: Option, + /// 直接/委托完成后应检查的结果约束。 pub returns: CallType, + /// 回调效果声明,供只读许可与方法替换契约判断。 pub effect: CallEffect, } impl CallSignature { + /// 宿主构造固定参数签名,不含可变参数;传入类型尚需使用前验证。 pub fn exact( parameters: Vec, returns: crate::AnnotationType, @@ -163,6 +202,7 @@ impl CallSignature { effect, } } + /// 载荷 trace 和临时保护枚举固定、可变及返回类型中的全部托管引用。 pub fn references(&self) -> impl Iterator + '_ { self.parameters .iter() @@ -170,13 +210,16 @@ impl CallSignature { .chain([&self.returns]) .flat_map(CallType::references) } + /// Store/执行入口校验数量区间和类型覆盖完整性,并验证全部定义引用。 pub fn validate(&self, store: &ObjectStore) -> Result<(), VmError> { self.arity.validate()?; + // 没有可变约束时固定类型列表必须覆盖最大参数数;有可变约束时不能超出上界。 if self.parameters.len() > self.arity.maximum || (self.variadic.is_none() && self.parameters.len() != self.arity.maximum) { return Err(VmError::InvalidArgument("incomplete native signature")); } + // 参数、可变项与返回项的定义引用都校验,不只验证本次实际传入的参数。 for expected in self .parameters .iter() @@ -187,6 +230,7 @@ impl CallSignature { } Ok(()) } + /// 原生调用进入回调前校验数量、效果许可和逐项类型,包括显式接收者。 pub fn check_arguments( &self, store: &ObjectStore, @@ -195,6 +239,7 @@ impl CallSignature { ) -> Result<(), VmError> { self.validate(store)?; self.arity.check(name.into(), arguments.len())?; + // Delegated 只保证宿主本体只读,被委托目标的效果由 VM 在相同作用域继续检查。 if store.is_read_only() && !matches!(self.effect, CallEffect::ReadOnly | CallEffect::Delegated) { @@ -202,7 +247,9 @@ impl CallSignature { "native effect is forbidden during read-only execution", )); } + // 按位置选择固定约束,超出固定部分才回退到可变约束。 for (index, value) in arguments.iter().enumerate() { + // 数量合法不代表类型覆盖完整,因此缺少约束仍作为声明错误。 let expected = self .parameters .get(index) @@ -212,6 +259,7 @@ impl CallSignature { } Ok(()) } + /// 原生调用返回后校验结果类型;不重新验证整份参数元数据。 pub fn check_return( &self, store: &ObjectStore, @@ -222,6 +270,7 @@ impl CallSignature { } } +/// 宿主实现接口;VM 克隆分派器 Arc 后调用 invoke,不持有载荷借用跨可变 Store 操作。 pub trait NativeCallable: fmt::Debug + Send + Sync + std::any::Any { /// Immutable resolved types; referenced definitions must be traced by the /// payload. ContextNativeFunction snapshots and traces them automatically. @@ -234,6 +283,7 @@ pub trait NativeCallable: fmt::Debug + Send + Sync + std::any::Any { self.signature().map(|signature| signature.arity) } + /// 宿主在临时根作用域中执行;参数借用,结果可直接返回或委托 VM 调用其他函数。 fn invoke( &self, context: &mut NativeContext<'_>, @@ -245,8 +295,11 @@ pub trait NativeCallable: fmt::Debug + Send + Sync + std::any::Any { /// are temporary owned roots. Return/Invoke promotes only its output roots; /// Drop releases everything else, including on error or unwinding. pub struct NativeContext<'a> { + // 整次回调独占借用存储,提供受控修改与查询。 store: &'a mut ObjectStore, + // 本作用域拥有的根清单,重复身份代表多份拥有根,不能去重。 roots: Vec, + // 当前调用函数的借用身份,execute 另外将其加入保护根。 callee: ObjectRef, } @@ -256,6 +309,7 @@ impl<'a> NativeContext<'a> { self.callee } + /// 宿主经 Store 附着不可替换的名义类型,沿用只读屏障。 pub fn attach_type( &mut self, object: ObjectRef, @@ -264,6 +318,7 @@ impl<'a> NativeContext<'a> { self.store.attach_type(object, type_object) } + /// 宿主注册能力,经 Store 校验,不直接改类型表。 pub fn implement_trait( &mut self, definition: ObjectRef, @@ -272,27 +327,33 @@ impl<'a> NativeContext<'a> { self.store.implement_trait(definition, capability) } + /// 宿主只读查询存储,修改使用上下文受控入口。 pub fn store(&self) -> &ObjectStore { self.store } + /// 分配载荷并登记临时拥有根;未作为输出移交时由 Drop 释放。 pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result { + // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.allocate(payload)?; self.roots.push(object); Ok(object) } + /// 创建 Record,经 allocate 统一登记临时根。 pub fn new_object(&mut self) -> Result { self.allocate(crate::Record) } /// Build a language function under this native scope using validated code. + /// 从代码创建无默认值/无捕获槽的源码函数,并登记临时根。 pub fn new_function( &mut self, name: impl Into, code: ObjectRef, globals: ObjectRef, ) -> Result { + // Store 校验代码布局和命名空间;这里不自动创建默认值或捕获槽。 let function = self .store .new_function(name, code, globals, vec![], vec![])?; @@ -300,18 +361,23 @@ impl<'a> NativeContext<'a> { Ok(function) } + /// 创建空字典并登记其外部根。 pub fn new_dict(&mut self) -> Result { + // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.new_dict()?; self.roots.push(object); Ok(object) } + /// 创建空/已初始化 Cell 并登记外部根。 pub fn new_cell(&mut self, value: Option) -> Result { + // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.new_cell(value)?; self.roots.push(object); Ok(object) } + /// 属性写入交给 Store/Heap 维护强边、类型约束和写屏障。 pub fn set_attr( &mut self, object: ObjectRef, @@ -321,22 +387,27 @@ impl<'a> NativeContext<'a> { self.store.set_attr(object, name, value) } + /// 属性删除交给 Store 校验,成功释放旧字段强边。 pub fn del_attr(&mut self, object: ObjectRef, name: &str) -> Result<(), VmError> { self.store.del_attr(object, name) } + /// 受控字典写入,保持容器身份和引用计数。 pub fn dict_set(&mut self, object: ObjectRef, key: Value, value: Value) -> Result<(), VmError> { self.store.dict_set(object, key, value) } + /// 受控删除字典键;缺键返回 false,协议失败另行报错。 pub fn dict_delete(&mut self, object: ObjectRef, key: &Value) -> Result { self.store.dict_delete(object, key) } + /// 受控更新共享 Cell,先保留新边再释放旧边。 pub fn cell_set(&mut self, object: ObjectRef, value: Value) -> Result<(), VmError> { self.store.cell_set(object, value) } + /// 替换载荷并重建边快照,保持对象身份。 pub fn replace_payload( &mut self, object: ObjectRef, @@ -345,15 +416,20 @@ impl<'a> NativeContext<'a> { self.store.replace_payload(object, payload) } + /// 升级成功的弱引用根登记为临时根;过期 None 不登记。 pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result, VmError> { + // 只有分配/升级成功后才登记根,错误路径不留下虚假的释放条目。 let object = self.store.upgrade(weak)?; + // 已过期弱引用不产生根,成功升级的一份根交给 Drop 管理。 if let Some(object) = object { self.roots.push(object); } Ok(object) } + /// 给借用对象 retain 一份临时根;直接值不需要托管计数。 fn protect(&mut self, value: &Value) -> Result<(), VmError> { + // retain 先成功才入清单,避免失败时 Drop 多释放一份根。 if let Value::Object(object) = value { self.store.retain(*object)?; self.roots.push(*object); @@ -363,58 +439,77 @@ impl<'a> NativeContext<'a> { /// Internal execution bridge. Every returned object reference owns a root; /// consumers must release Return values or all Invoke arguments/function. + /// VM 执行桥:校验/保护输入,执行回调,验证直接结果,最后移交输出根。 pub fn execute( + // 整次回调独占借用存储,提供受控修改与查询。 store: &'a mut ObjectStore, function: ObjectRef, arguments: &[Value], + // 可共享回调分派器;执行前克隆 Arc,避免持有载荷借用。 callable: Arc, ) -> Result { + // 执行回调前检查分派器声明的数量约束。 if let Some(arity) = callable.arity() { arity.check( store.object(function)?.payload().kind_name().into(), arguments.len(), )?; } + // 从载荷快照取得完整签名,与 callable 的动态返回值分开。 let signature = store.native_signature(function)?; + // 诊断名在可变借用上下文建立前复制,避免跨调用持有载荷引用。 let name = store.object(function)?.payload().kind_name().to_owned(); + // 可选完整签名存在时才校验/保护;缺失元数据保留既有调用协议。 if let Some(signature) = &signature { signature.check_arguments(store, &name, arguments)?; } + // 首参为对象时查询接收者的方法契约及应保持的返回约束。 let trait_returns = if let Some(Value::Object(receiver)) = arguments.first() { store.check_method_contract_call(function, *receiver, false)?; store.method_trait_returns(function, *receiver)? } else { Vec::new() }; + // 从这里起所有已登记根由 RAII Drop 清理,错误和展开路径同样有效。 let mut context = Self { store, roots: Vec::new(), callee: function, }; context.protect(&Value::Object(function))?; + // 可选完整签名存在时才校验/保护;缺失元数据保留既有调用协议。 if let Some(signature) = &signature { for reference in signature.references() { context.protect(&Value::Object(reference))?; } } + // 能力返回约束也可能持有类型定义,执行回调时必须保持其存活。 for (_, expected) in &trait_returns { + // 每条声明引用独立保护,重复边对应多份根并会逐次释放。 for reference in expected.references() { context.protect(&Value::Object(reference))?; } } + // 输入每个对象参数取得临时根,防回调删除原持有边后参数失效。 for argument in arguments { context.protect(argument)?; } + // 回调错误直接传播,context 自动释放输入保护和回调内创建的临时对象根。 let result = callable.invoke(&mut context, arguments)?; + // 直接结果立即检查;Invoke 的最终结果由 VM 的委托返回检查继续处理。 if let (Some(signature), NativeResult::Return(value)) = (&signature, &result) { signature.check_return(context.store(), &name, value)?; } + // 直接结果还须满足接收者能力方法要求,不只满足原生自身签名。 if let NativeResult::Return(value) = &result { + // 每个能力的返回约束都要满足,不能在第一个成功后跳过其他能力。 for (name, expected) in &trait_returns { expected.check(context.store(), name, "return".into(), value)?; } } + // 记录移交边界;新增输出根验证成功后才从清单中移走。 let transferred_from = context.roots.len(); + // 给每个输出位置 retain;中途失败则清单仍完整,由 Drop 回收已取得的输出根。 match &result { NativeResult::Return(value) => context.protect(value)?, NativeResult::Invoke { @@ -422,19 +517,22 @@ impl<'a> NativeContext<'a> { arguments, } => { context.protect(&Value::Object(*function))?; + // 每个委托参数取得待移交的输出根,重复对象参数也逐项保留。 for argument in arguments { context.protect(argument)?; } } } - // Transfer exactly the output roots; all other roots remain scoped. + // truncate 只移走输出根的清单条目,不 release;调用者接管这些拥有根,其他根仍由 Drop 释放。 context.roots.truncate(transferred_from); Ok(result) } } impl Drop for NativeContext<'_> { + /// 作用域结束、回调错误或栈展开时回收全部未移交拥有根。 fn drop(&mut self) { + // 按取得的反序归还所有未移交根;每项代表一份所有权,不能按身份去重。 for root in self.roots.drain(..).rev() { self.store .release(root) @@ -446,9 +544,13 @@ impl Drop for NativeContext<'_> { /// A context-aware function with explicit, immutable captured object edges. #[derive(Debug)] pub struct ContextNativeFunction { + // 宿主静态诊断名,不承担语言类型身份。 name: &'static str, + // 可共享回调分派器;执行前克隆 Arc,避免持有载荷借用。 callable: Arc, + // 显式不可变托管捕获边,由 trace 逐条登记。 captures: Vec, + // 构造时从 callable 快照,作为不可变元数据并参与 trace。 signature: Option, } @@ -456,14 +558,19 @@ impl ContextNativeFunction { /// Borrow the concrete host implementation for intrinsic dispatch checks. /// Function identity or a mutable class-table slot alone is not proof that /// its current payload still implements a host intrinsic. + /// 宿主内建分派借用具体回调实现;类型不匹配返回 None,不以函数身份猜测实现。 pub fn implementation(&self) -> Option<&T> { + // 仅做 Rust Any 下转型,不执行回调,不检查语言层名义类型。 let implementation: &dyn std::any::Any = self.callable.as_ref(); implementation.downcast_ref() } + /// 宿主包装上下文回调,快照签名与显式捕获边,随后由 Store 分配。 pub fn new( + // 宿主静态诊断名,不承担语言类型身份。 name: &'static str, callable: impl NativeCallable, + // 显式不可变托管捕获边,由 trace 逐条登记。 captures: Vec, ) -> Self { Self { @@ -476,31 +583,40 @@ impl ContextNativeFunction { } impl ObjectPayload for ContextNativeFunction { + // Store 分配时可附着 Function 类型;可调用性和函数分类分开。 fn is_function(&self) -> bool { true } + // 本载荷无原地赋值实现,只读调用可以直接重绑定局部引用。 fn reference_assignment_only(&self) -> bool { true } + // Store/宿主 intrinsic 分派所需的载荷下转型入口。 fn as_any(&self) -> &dyn std::any::Any { self } + // 使用宿主配置的静态名生成诊断。 fn kind_name(&self) -> &'static str { self.name } + // Heap 快照签名定义边及显式捕获边;不能在分配后私下修改这些引用。 fn trace(&self, visit: &mut dyn FnMut(ObjectRef)) { + // 签名中的参数、可变参数和返回约束定义也属于载荷拥有边。 if let Some(signature) = &self.signature { for reference in signature.references() { visit(reference); } } + // 每个捕获项都拥有独立强边,包括重复捕获。 for capture in &self.captures { visit(*capture); } } + // Store 查询构造时的签名快照,clone 不新增托管根。 fn call_signature(&self) -> Option { self.signature.clone() } + // VM 克隆回调 Arc 后释放载荷借用,再创建可变 NativeContext。 fn native_callable(&self) -> Option> { Some(self.callable.clone()) }