feat: support opt-in shared reference assignment for host objects

This commit is contained in:
2026-10-20 20:55:25 +08:00
parent df1b77fc1a
commit 7c842789c0
7 changed files with 169 additions and 7 deletions
+27 -5
View File
@@ -220,6 +220,20 @@ impl Vm {
Self::stack_value(frame, frame.stack.len() - 1)
}
fn assignment_value(&self, previous: Option<Value>, incoming: Value) -> Result<Value, VmError> {
if let (Some(Value::Object(old)), Value::Object(new)) = (&previous, &incoming) {
if old == new
|| self
.object(*old)?
.payload()
.assign_from(self.object(*new)?.payload())?
{
return Ok(Value::Object(*old));
}
}
Ok(incoming)
}
fn push_borrowed(&mut self, frame: &mut EndorFrame, value: Value) -> Result<(), VmError> {
self.retain_value(&value)?;
frame.stack.push(StackEntry::Value(value));
@@ -372,9 +386,12 @@ impl Vm {
self.push_borrowed(frame, value)?;
}
Opcode::StoreFast => {
let value = Self::top_value(frame)?;
// The stack root is moved into the local slot.
frame.stack.pop();
let value = self.assignment_value(
frame.localsplus[argument].clone(),
Self::top_value(frame)?,
)?;
self.retain_value(&value)?;
self.discard(frame, 1);
if let Some(old) = frame.localsplus[argument].replace(value) {
self.drop_root(old);
}
@@ -398,8 +415,11 @@ impl Vm {
self.push_borrowed(frame, value)?;
}
Opcode::StoreGlobal => {
let value = Self::top_value(frame)?;
let key = Value::Text(frame.executable.layout().names[argument].clone());
let value = self.assignment_value(
self.dict_get(frame.globals, &key)?,
Self::top_value(frame)?,
)?;
self.dict_set(frame.globals, key, value)?;
self.discard(frame, 1);
}
@@ -426,7 +446,9 @@ impl Vm {
}
Opcode::StoreDeref => {
let cell = self.cell_slot(frame, argument)?;
self.cell_set(cell, Self::top_value(frame)?)?;
let value =
self.assignment_value(self.cell_get(cell).ok(), Self::top_value(frame)?)?;
self.cell_set(cell, value)?;
self.discard(frame, 1);
}
Opcode::DeleteDeref => {
+2
View File
@@ -9,3 +9,5 @@
| [execution.rs](execution.rs) | wordcode 与校验、控制流、参数/默认值、嵌套帧、字节码函数创建/默认值/环境捕获、闭包、返回值存活、失败清理、限额 |
核心对象现由 objects 定义,本目录保留既有 endor_core 导入与 Vm 方法的兼容性检查。独立对象存储检查见 [objects/tests](../../objects/tests/README.md)。堆内部伪造句柄与计数溢出检查在 objects 的 src/heap.rs,公开示例由 src/lib.rs rustdoc 提供。文件职责见 [src](../src/README.md),检查方式和结果边界见 [验证](../../../docs/operations/validation.md)。本地不执行测试;存在测试文件不代表某次提交已通过。返回 [core](../README.md)。
execution.rs 同时覆盖宿主可选 assign_from 的全局/局部赋值、别名身份、拒绝替换与执行根清理;不在本地运行。
+124
View File
@@ -35,6 +35,130 @@ fn return_constant(vm: &mut Vm, value: Value) -> ObjectRef {
)
}
#[derive(Debug)]
struct SharedReference(AtomicUsize);
impl ObjectPayload for SharedReference {
fn as_any(&self) -> &dyn Any {
self
}
fn kind_name(&self) -> &'static str {
"shared-reference"
}
fn assign_from(&self, source: &dyn ObjectPayload) -> Result<bool, VmError> {
let Some(source) = source.as_any().downcast_ref::<Self>() else {
return Ok(false);
};
let value = source.0.load(Ordering::SeqCst);
if value == 99 {
return Err(VmError::InvalidArgument("rejected replacement"));
}
self.0.store(value, Ordering::SeqCst);
Ok(true)
}
}
#[test]
fn opt_in_assignment_keeps_alias_identity_ordinary_values_and_root_accounting() {
for local in [false, true] {
let mut vm = Vm::new().unwrap();
let old = vm.allocate(SharedReference(AtomicUsize::new(1))).unwrap();
let new = vm.allocate(SharedReference(AtomicUsize::new(2))).unwrap();
let globals = vm.new_dict().unwrap();
vm.dict_set(globals, text("alias"), Value::Object(old))
.unwrap();
let (store, load) = if local {
(Opcode::StoreFast, Opcode::LoadFast)
} else {
(Opcode::StoreGlobal, Opcode::LoadGlobal)
};
let code = make_code(
&mut vm,
&[
op(Opcode::LoadConst, 0),
op(store, 0),
op(Opcode::LoadConst, 1),
op(store, 0),
op(load, 0),
simple(Opcode::ReturnValue),
],
vec![Value::Object(old), Value::Object(new)],
if local {
CodeLayout {
locals: vec!["value".into()],
..CodeLayout::default()
}
} else {
CodeLayout {
names: vec!["value".into()],
..CodeLayout::default()
}
},
);
let value = vm.execute(code, globals).unwrap();
assert_eq!(value, Value::Object(old));
assert_eq!(
vm.dict_get(globals, &text("alias")).unwrap(),
Some(Value::Object(old))
);
assert_eq!(
vm.payload::<SharedReference>(old, "shared-reference")
.unwrap()
.0
.load(Ordering::SeqCst),
2
);
vm.release_value(value).unwrap();
vm.release(code).unwrap();
vm.dict_clear(globals).unwrap();
vm.release(globals).unwrap();
vm.release(old).unwrap();
vm.release(new).unwrap();
assert_eq!(vm.object_count(), 0);
}
}
#[test]
fn failed_opt_in_assignment_preserves_target_and_cleans_execution_roots() {
let mut vm = Vm::new().unwrap();
let old = vm.allocate(SharedReference(AtomicUsize::new(1))).unwrap();
let rejected = vm.allocate(SharedReference(AtomicUsize::new(99))).unwrap();
let globals = vm.new_dict().unwrap();
vm.dict_set(globals, text("value"), Value::Object(old))
.unwrap();
let code = make_code(
&mut vm,
&[
op(Opcode::LoadConst, 0),
op(Opcode::StoreGlobal, 0),
op(Opcode::LoadConst, 1),
simple(Opcode::ReturnValue),
],
vec![Value::Object(rejected), Value::Nil],
CodeLayout {
names: vec!["value".into()],
..CodeLayout::default()
},
);
assert!(vm.execute(code, globals).is_err());
assert_eq!(
vm.payload::<SharedReference>(old, "shared-reference")
.unwrap()
.0
.load(Ordering::SeqCst),
1
);
assert_eq!(
vm.dict_get(globals, &text("value")).unwrap(),
Some(Value::Object(old))
);
vm.release(code).unwrap();
vm.dict_clear(globals).unwrap();
vm.release(globals).unwrap();
vm.release(old).unwrap();
vm.release(rejected).unwrap();
assert_eq!(vm.object_count(), 0);
}
#[test]
fn wordcode_extended_operands_decode_and_execute_without_truncation() {
let encoded = Instruction::encode_all(&[op(Opcode::LoadConst, 0x01020304)]).unwrap();
+8
View File
@@ -96,6 +96,14 @@ pub trait ObjectPayload: Any + fmt::Debug + Send + Sync {
fn call(&self, _arguments: &[Value]) -> Result<Value, VmError> {
Err(VmError::UnsupportedOperation("call"))
}
/// Opt-in shared-reference assignment. Returning true preserves the existing
/// object identity after replacing its host-owned target. Ordinary payloads
/// return false and retain normal value assignment. Implementations must
/// validate before mutation and must not add untraced managed-object edges.
fn assign_from(&self, _source: &dyn ObjectPayload) -> Result<bool, VmError> {
Ok(false)
}
}
#[derive(Debug, Default)]
+1 -1
View File
@@ -10,7 +10,7 @@
| 词法、语法、裸赋值和诊断 | [Frontend](runtime/frontend.md) | frontend/src/lexer.peg/grammar.peg |
| ASDL、AST 与编辑器接口 | [AST](runtime/ast.md) | frontend/Endor.asdl |
| 名称分析、编译和装载 | [Compiler](runtime/compiler.md) | compiler/src |
| 字节码、函数与限额 | [字节码 VM](runtime/bytecode.md) | objects/src、core/src |
| 字节码、函数、共享引用赋值与限额 | [字节码 VM](runtime/bytecode.md) | objects/src、core/src |
| 引用与 VM 生命周期 | [生命周期](runtime/lifecycle.md) | objects/src、core/src |
| UTF-8 与地区 | [文本编码](runtime/text-encoding.md) | frontend、objects |
| 嵌入及宿主入口 | [使用引擎](operations/README.md) | 独立宿主仓库 |
+1 -1
View File
@@ -21,7 +21,7 @@ Aster 是可嵌入引擎,只保留 objects、core、frontend、compiler。Obje
## 当前缺口
平台资源版本/依赖锁、源码闭包、数值算术、完整异常传播、内存配额、可靠取消、不可信代码沙箱、自动 GC 和宿主 panic 后执行根恢复尚未完成。类型注解只保存语法,不引入 Python type/元类。UI 接入适配由 Tessera 实现,不为 Tessera 修改引擎接口。
平台资源版本/依赖锁、源码闭包、数值算术、完整异常传播、内存配额、可靠取消、不可信代码沙箱、自动 GC 和宿主 panic 后执行根恢复尚未完成。类型注解只保存语法,不引入 Python type/元类。UI 接入适配由 Tessera 实现。通用 ObjectPayload::assign_from 允许宿主对象选择共享引用赋值;协议和根计数归 Aster,UI 挂载、布局、绘制仍归 Tessera,见字节码 VM 的共享引用章节。
## 工作约定
+6
View File
@@ -169,3 +169,9 @@ fn main() -> Result<(), endor_core::VmError> {
验证命令与结果边界统一见 [验证](../operations/validation.md)。限额与环境隔离见 [配置](../operations/configuration.md),数据与校验见 [objects 源码导航](../../crates/objects/src/README.md),执行职责见 [core 源码导航](../../crates/core/src/README.md)。
参考:[CPython 解释器设计](https://github.com/python/cpython/blob/v3.14.0/InternalDocs/interpreter.md)、[执行帧设计](https://github.com/python/cpython/blob/v3.14.0/InternalDocs/frames.md)、[帧结构](https://github.com/python/cpython/blob/v3.14.0/Include/internal/pycore_interpframe_structs.h)、[指令定义](https://github.com/python/cpython/blob/v3.14.0/Python/bytecodes.c)、[求值入口与执行器](https://github.com/python/cpython/blob/v3.14.0/Python/ceval.c)。
## 宿主共享引用的赋值协议
`ObjectPayload::assign_from(source)` 是可选协议,默认返回 false,普通值与对象仍替换变量绑定。StoreGlobal、StoreFast、StoreDeref 在既有值和新值均为对象时调用此协议;返回 true 表示宿主对象已替换自己的目标,绑定保留旧对象身份,其他别名随共享引用观察更新。相同句柄赋值为无操作。此协议同时适用于这些存储指令实现的声明初始化/赋值;字典 API 与 StoreAttr 不隐式调用它。
协议不传递变量名字或 UI 元数据,也不引入渲染器。实现必须先验证再修改,拒绝替换时保留旧目标;不得通过可变 payload 悄悄增加未跟踪的 VM 对象边。宿主原生关系如持有受管理对象,必须通过 VM retain/release 登记根。原生资源的内部引用可以由宿主维护,但其管理对象身份和存活根仍由 Aster 校验和计数。StoreFast 会先持有选择后的值,再释放栈值与旧槽,失败执行仍走统一帧清理。