Unify heap objects with typed headers and bootstrap Type
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 14m16s
Verify databases and deploy Aster VM / verify-and-deploy (push) Successful in 14m16s
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
# Aster VM
|
||||
|
||||
Rust 编写的可嵌入语言运行时,当前为第一阶段:启动时引导根类 `Object`,并由对象工厂创建类和实例。这里的“顶级类”指根类;“生成器”指运行时引导与对象工厂。
|
||||
Rust 编写的可嵌入语言运行时,当前为第一阶段:启动时引导根类 `Object` 和基础元类 `Type`,并由对象工厂创建类和实例。所有堆对象都有公共对象头和类型句柄,类本身也是语言对象。这里的“顶级类”指根类;“生成器”指运行时引导与对象工厂。
|
||||
|
||||
本项目尚无词法/语法前端、字节码执行器、渲染器、GC 或完整元类体系,不承诺 Python 兼容。数据库只保存宿主元数据,不保存或恢复 VM 堆。
|
||||
|
||||
@@ -24,7 +24,7 @@ cargo build --workspace --locked
|
||||
cargo run -p aster-cli -- bootstrap
|
||||
```
|
||||
|
||||
预期输出含 `root=Object parent=None`、`instances_distinct=true heap_objects=4`。这条命令不读取数据库配置、不打开数据库;即使设置了错误的 `DATABASE_URL` 也不影响引导。
|
||||
预期输出含 `root=Object parent=None`、`instances_distinct=true heap_objects=5`(Object、Type、Example 与两个实例)。这条命令不读取数据库配置、不打开数据库;即使设置了错误的 `DATABASE_URL` 也不影响引导。
|
||||
|
||||
```rust
|
||||
use aster_core::{Value, Vm};
|
||||
@@ -34,7 +34,9 @@ fn main() -> Result<(), aster_core::VmError> {
|
||||
let point = vm.create_class("Point", vm.root_class())?;
|
||||
let instance = vm.create_instance(point)?;
|
||||
vm.set_field(instance, "x", Value::Integer(42))?;
|
||||
assert_eq!(vm.instance(instance)?.class(), point);
|
||||
assert_eq!(vm.type_of(instance.into())?, point);
|
||||
assert_eq!(vm.type_of(point.into())?, vm.type_class());
|
||||
assert_eq!(vm.type_of(vm.type_class().into())?, vm.type_class());
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
@@ -65,7 +67,7 @@ curl --fail http://127.0.0.1:8080/health
|
||||
curl --fail http://127.0.0.1:8080/bootstrap
|
||||
```
|
||||
|
||||
`ASTER_LISTEN` 默认 `127.0.0.1:8080`。宿主启动时创建 VM、连接数据库、初始化表并写入 `host.version=0.1.0`;初始化失败即退出。`/health` 实际探测数据库,成功返回 200,失联返回 503;`/bootstrap` 返回 `Object`、无父类、堆对象数 1,多次读取不会重复引导。支持 Ctrl-C/Unix SIGTERM 优雅退出。
|
||||
`ASTER_LISTEN` 默认 `127.0.0.1:8080`。宿主启动时创建 VM、连接数据库、初始化表并写入 `host.version=0.1.0`;初始化失败即退出。`/health` 实际探测数据库,成功返回 200,失联返回 503;`/bootstrap` 返回 `Object`、无父类、堆对象数 2(Object 和 Type),多次读取不会重复引导。支持 Ctrl-C/Unix SIGTERM 优雅退出。
|
||||
|
||||
这两个端点不提供执行语言、对象写入或业务 API。服务没有认证层;示例 Compose 端口仅绑定宿主回环地址,外部访问由后续宿主部署决定。
|
||||
|
||||
@@ -156,7 +158,9 @@ docker stop aster-pg-test
|
||||
|
||||
## 当前语义与后续开发
|
||||
|
||||
- `Object` 是每个 VM 启动时唯一分配的根类,没有父类。类描述与类对象合为一个堆对象;实例保存类句柄。普通类必须指定已有父类,名称只是标签,无全局命名空间;保留精确名称 `Object`,其他同名类允许存在。
|
||||
- Rust 的 `Object` 是统一堆对象结构,包含 `ObjectHeader` 和 `ObjectData`。对象头保存类型句柄;载荷区分 `Class` 元数据和 `Instance` 字段。`Class` 结构本身只是载荷,不再单独代表一个语言对象。`vm.type_of(reference)` 对类和实例都读取同一个公共对象头,替代旧的 `vm.instance(reference)?.class()`。
|
||||
- 语言根类 `Object` 唯一且没有父类;它的类型是 `Type`。`Type` 的父类是 Object,类型是 Type 自身。普通类的类型为 Type,实例的类型为其所属类。当前仅支持这一基础元类:禁止直接 `create_instance(Type)` 或继承 Type,自定义元类、类型调用协议与描述符尚未实现。
|
||||
- 普通类必须指定已有父类,名称只是标签,无全局命名空间;保留精确名称 `Object` 和 `Type`,其他同名类允许存在。
|
||||
- 对外只提供私有字段的 `ObjectRef`/`ClassRef`/`InstanceRef`。句柄包含进程内 VM 身份与槽位,所有访问检查归属、范围、种类;写入对象字段也检查引用归属。句柄不是持久 ID,不支持外部构造或反序列化。
|
||||
- 堆仅分配,不单独回收、不复用槽位;随 VM 销毁统一释放。宿主持有的句柄不延长 VM 生命周期。未来 GC 需要另行设计根集、代际槽位或稳定间接句柄,不能直接开始复用当前索引。
|
||||
- Nil/布尔/整数/浮点直接放入 `Value`,字符串由 Rust `String` 持有,类和实例使用堆句柄。尚未定义完整语言数值、方法查找、继承执行或异常语义。
|
||||
|
||||
@@ -42,8 +42,8 @@ pub enum Value {
|
||||
Object(ObjectRef),
|
||||
}
|
||||
|
||||
/// The class object and its immutable description are one heap allocation.
|
||||
/// There is deliberately no metaclass or implicit class-of-class relationship.
|
||||
/// Immutable class metadata, stored in a language object's payload.
|
||||
/// This Rust structure is not a language object on its own.
|
||||
#[derive(Debug)]
|
||||
pub struct Class {
|
||||
pub(crate) name: String,
|
||||
@@ -62,26 +62,59 @@ impl Class {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Instance {
|
||||
pub(crate) class: ClassRef,
|
||||
pub(crate) fields: BTreeMap<String, Value>,
|
||||
}
|
||||
|
||||
impl Instance {
|
||||
pub fn class(&self) -> ClassRef {
|
||||
self.class
|
||||
}
|
||||
|
||||
pub fn field(&self, name: &str) -> Option<&Value> {
|
||||
self.fields.get(name)
|
||||
}
|
||||
}
|
||||
|
||||
/// Common header for every heap object, including class objects.
|
||||
/// The class handle is immutable and is always local to the owning VM.
|
||||
#[derive(Debug)]
|
||||
pub enum Object {
|
||||
pub struct ObjectHeader {
|
||||
class: ClassRef,
|
||||
}
|
||||
|
||||
impl ObjectHeader {
|
||||
pub fn class(&self) -> ClassRef {
|
||||
self.class
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ObjectData {
|
||||
Class(Class),
|
||||
Instance(Instance),
|
||||
}
|
||||
|
||||
/// A language object consists of a common header and a kind-specific payload.
|
||||
/// Object links use checked handles rather than addresses into the arena.
|
||||
#[derive(Debug)]
|
||||
pub struct Object {
|
||||
header: ObjectHeader,
|
||||
pub(crate) data: ObjectData,
|
||||
}
|
||||
|
||||
impl Object {
|
||||
pub(crate) fn new(class: ClassRef, data: ObjectData) -> Self {
|
||||
Self {
|
||||
header: ObjectHeader { class },
|
||||
data,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn header(&self) -> &ObjectHeader {
|
||||
&self.header
|
||||
}
|
||||
|
||||
pub fn data(&self) -> &ObjectData {
|
||||
&self.data
|
||||
}
|
||||
}
|
||||
|
||||
/// Allocation-only arena. No deletion or slot reuse until a GC model is chosen.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct Heap {
|
||||
@@ -90,6 +123,35 @@ pub(crate) struct Heap {
|
||||
}
|
||||
|
||||
impl Heap {
|
||||
/// Builds both foundational classes before exposing the heap. The two
|
||||
/// private handles permit Type's self-reference without null placeholders.
|
||||
pub(crate) fn bootstrap() -> Result<(Self, ClassRef, ClassRef), VmError> {
|
||||
let mut heap = Self::new()?;
|
||||
let root = ClassRef(ObjectRef {
|
||||
heap_id: heap.id,
|
||||
index: 0,
|
||||
});
|
||||
let type_class = ClassRef(ObjectRef {
|
||||
heap_id: heap.id,
|
||||
index: 1,
|
||||
});
|
||||
heap.allocate(Object::new(
|
||||
type_class,
|
||||
ObjectData::Class(Class {
|
||||
name: "Object".into(),
|
||||
parent: None,
|
||||
}),
|
||||
));
|
||||
heap.allocate(Object::new(
|
||||
type_class,
|
||||
ObjectData::Class(Class {
|
||||
name: "Type".into(),
|
||||
parent: Some(root),
|
||||
}),
|
||||
));
|
||||
Ok((heap, root, type_class))
|
||||
}
|
||||
|
||||
pub(crate) fn new() -> Result<Self, VmError> {
|
||||
let id = NEXT_HEAP_ID
|
||||
.fetch_update(Ordering::Relaxed, Ordering::Relaxed, |id| id.checked_add(1))
|
||||
@@ -165,6 +227,16 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_lookup_rejects_an_invalid_slot() {
|
||||
let vm = crate::Vm::new().unwrap();
|
||||
let invalid = ObjectRef {
|
||||
heap_id: vm.root_class().0.heap_id,
|
||||
index: usize::MAX,
|
||||
};
|
||||
assert_eq!(vm.type_of(invalid), Err(VmError::InvalidHandle));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_value_reference_cannot_replace_a_field() {
|
||||
let mut vm = crate::Vm::new().unwrap();
|
||||
|
||||
@@ -6,12 +6,15 @@
|
||||
//! let point = vm.create_class("Point", vm.root_class())?;
|
||||
//! let instance = vm.create_instance(point)?;
|
||||
//! vm.set_field(instance, "x", Value::Integer(42))?;
|
||||
//! assert_eq!(vm.instance(instance)?.class(), point);
|
||||
//! assert_eq!(vm.type_of(instance.into())?, point);
|
||||
//! assert_eq!(vm.type_of(point.into())?, vm.type_class());
|
||||
//! # Ok::<(), aster_core::VmError>(())
|
||||
//! ```
|
||||
|
||||
mod heap;
|
||||
mod vm;
|
||||
|
||||
pub use heap::{Class, ClassRef, Instance, InstanceRef, Object, ObjectRef, Value};
|
||||
pub use heap::{
|
||||
Class, ClassRef, Instance, InstanceRef, Object, ObjectData, ObjectHeader, ObjectRef, Value,
|
||||
};
|
||||
pub use vm::{Vm, VmError};
|
||||
|
||||
+50
-28
@@ -2,7 +2,7 @@ use std::collections::BTreeMap;
|
||||
use std::fmt;
|
||||
|
||||
use crate::heap::Heap;
|
||||
use crate::{Class, ClassRef, Instance, InstanceRef, Object, ObjectRef, Value};
|
||||
use crate::{Class, ClassRef, Instance, InstanceRef, Object, ObjectData, ObjectRef, Value};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum VmError {
|
||||
@@ -12,6 +12,7 @@ pub enum VmError {
|
||||
ExpectedInstance,
|
||||
EmptyClassName,
|
||||
ReservedClassName,
|
||||
UnsupportedMetaclass,
|
||||
IdentityExhausted,
|
||||
}
|
||||
|
||||
@@ -23,7 +24,8 @@ impl fmt::Display for VmError {
|
||||
Self::ExpectedClass => "object is not a class",
|
||||
Self::ExpectedInstance => "object is not an instance",
|
||||
Self::EmptyClassName => "class name cannot be blank",
|
||||
Self::ReservedClassName => "Object is reserved for the root class",
|
||||
Self::ReservedClassName => "Object and Type are reserved for the bootstrap classes",
|
||||
Self::UnsupportedMetaclass => "Type only supports class creation through create_class",
|
||||
Self::IdentityExhausted => "process-local VM identities exhausted",
|
||||
})
|
||||
}
|
||||
@@ -31,22 +33,24 @@ impl fmt::Display for VmError {
|
||||
|
||||
impl std::error::Error for VmError {}
|
||||
|
||||
/// Owns its heap and exactly one root class. Not Clone: handles have one owner.
|
||||
/// Owns its heap, the root Object class and its Type metaclass.
|
||||
/// Not Clone: handles have one owner.
|
||||
#[derive(Debug)]
|
||||
pub struct Vm {
|
||||
heap: Heap,
|
||||
root: ClassRef,
|
||||
type_class: ClassRef,
|
||||
}
|
||||
|
||||
impl Vm {
|
||||
/// Bootstraps Object before returning. Does not perform any host I/O.
|
||||
/// Bootstraps Object and Type before returning. Does not perform host I/O.
|
||||
pub fn new() -> Result<Self, VmError> {
|
||||
let mut heap = Heap::new()?;
|
||||
let root = ClassRef(heap.allocate(Object::Class(Class {
|
||||
name: "Object".into(),
|
||||
parent: None,
|
||||
})));
|
||||
Ok(Self { heap, root })
|
||||
let (heap, root, type_class) = Heap::bootstrap()?;
|
||||
Ok(Self {
|
||||
heap,
|
||||
root,
|
||||
type_class,
|
||||
})
|
||||
}
|
||||
|
||||
/// Idempotent: construction has already performed bootstrap exactly once.
|
||||
@@ -58,6 +62,10 @@ impl Vm {
|
||||
self.root
|
||||
}
|
||||
|
||||
pub fn type_class(&self) -> ClassRef {
|
||||
self.type_class
|
||||
}
|
||||
|
||||
pub fn object_count(&self) -> usize {
|
||||
self.heap.len()
|
||||
}
|
||||
@@ -70,26 +78,35 @@ impl Vm {
|
||||
parent: ClassRef,
|
||||
) -> Result<ClassRef, VmError> {
|
||||
self.class(parent)?;
|
||||
if parent == self.type_class {
|
||||
return Err(VmError::UnsupportedMetaclass);
|
||||
}
|
||||
let name = name.into();
|
||||
if name.trim().is_empty() {
|
||||
return Err(VmError::EmptyClassName);
|
||||
}
|
||||
if name == "Object" {
|
||||
if name == "Object" || name == "Type" {
|
||||
return Err(VmError::ReservedClassName);
|
||||
}
|
||||
Ok(ClassRef(self.heap.allocate(Object::Class(Class {
|
||||
name,
|
||||
parent: Some(parent),
|
||||
}))))
|
||||
Ok(ClassRef(self.heap.allocate(Object::new(
|
||||
self.type_class,
|
||||
ObjectData::Class(Class {
|
||||
name,
|
||||
parent: Some(parent),
|
||||
}),
|
||||
))))
|
||||
}
|
||||
|
||||
pub fn create_instance(&mut self, class: ClassRef) -> Result<InstanceRef, VmError> {
|
||||
self.class(class)?;
|
||||
Ok(InstanceRef(self.heap.allocate(Object::Instance(
|
||||
Instance {
|
||||
class,
|
||||
if class == self.type_class {
|
||||
return Err(VmError::UnsupportedMetaclass);
|
||||
}
|
||||
Ok(InstanceRef(self.heap.allocate(Object::new(
|
||||
class,
|
||||
ObjectData::Instance(Instance {
|
||||
fields: BTreeMap::new(),
|
||||
},
|
||||
}),
|
||||
))))
|
||||
}
|
||||
|
||||
@@ -97,17 +114,22 @@ impl Vm {
|
||||
self.heap.get(reference)
|
||||
}
|
||||
|
||||
/// Reads the same common header for class objects and ordinary instances.
|
||||
pub fn type_of(&self, reference: ObjectRef) -> Result<ClassRef, VmError> {
|
||||
Ok(self.heap.get(reference)?.header().class())
|
||||
}
|
||||
|
||||
pub fn class(&self, reference: ClassRef) -> Result<&Class, VmError> {
|
||||
match self.heap.get(reference.0)? {
|
||||
Object::Class(class) => Ok(class),
|
||||
Object::Instance(_) => Err(VmError::ExpectedClass),
|
||||
match self.heap.get(reference.0)?.data() {
|
||||
ObjectData::Class(class) => Ok(class),
|
||||
ObjectData::Instance(_) => Err(VmError::ExpectedClass),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instance(&self, reference: InstanceRef) -> Result<&Instance, VmError> {
|
||||
match self.heap.get(reference.0)? {
|
||||
Object::Instance(instance) => Ok(instance),
|
||||
Object::Class(_) => Err(VmError::ExpectedInstance),
|
||||
match self.heap.get(reference.0)?.data() {
|
||||
ObjectData::Instance(instance) => Ok(instance),
|
||||
ObjectData::Class(_) => Err(VmError::ExpectedInstance),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,12 +157,12 @@ impl Vm {
|
||||
if let Value::Object(reference) = &value {
|
||||
self.heap.get(*reference)?;
|
||||
}
|
||||
match self.heap.get_mut(instance.0)? {
|
||||
Object::Instance(instance) => {
|
||||
match &mut self.heap.get_mut(instance.0)?.data {
|
||||
ObjectData::Instance(instance) => {
|
||||
instance.fields.insert(name.into(), value);
|
||||
Ok(())
|
||||
}
|
||||
Object::Class(_) => Err(VmError::ExpectedInstance),
|
||||
ObjectData::Class(_) => Err(VmError::ExpectedInstance),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use aster_core::{Value, Vm, VmError};
|
||||
use aster_core::{ObjectData, Value, Vm, VmError};
|
||||
|
||||
#[test]
|
||||
fn bootstrap_is_unique_and_root_has_no_parent() {
|
||||
@@ -6,11 +6,30 @@ fn bootstrap_is_unique_and_root_has_no_parent() {
|
||||
let root = vm.root_class();
|
||||
assert_eq!(vm.bootstrap(), root);
|
||||
assert_eq!(vm.bootstrap(), root);
|
||||
assert_eq!(vm.object_count(), 1);
|
||||
assert_eq!(vm.object_count(), 2);
|
||||
assert_eq!(vm.class(root).unwrap().name(), "Object");
|
||||
assert_eq!(vm.class(root).unwrap().parent(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bootstrap_types_have_headers_and_distinct_inheritance_links() {
|
||||
let vm = Vm::new().unwrap();
|
||||
let root = vm.root_class();
|
||||
let type_class = vm.type_class();
|
||||
assert_ne!(root, type_class);
|
||||
assert_eq!(vm.class(type_class).unwrap().name(), "Type");
|
||||
assert_eq!(vm.class(type_class).unwrap().parent(), Some(root));
|
||||
assert_eq!(vm.type_of(root.into()).unwrap(), type_class);
|
||||
assert_eq!(vm.type_of(type_class.into()).unwrap(), type_class);
|
||||
for class in [root, type_class] {
|
||||
let object = vm.object(class.into()).unwrap();
|
||||
assert_eq!(object.header().class(), type_class);
|
||||
assert!(matches!(object.data(), ObjectData::Class(_)));
|
||||
assert_eq!(vm.as_class(class.into()).unwrap(), class);
|
||||
assert_eq!(vm.as_instance(class.into()), Err(VmError::ExpectedInstance));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn factories_preserve_class_links_and_distinct_identity() {
|
||||
let mut vm = Vm::new().unwrap();
|
||||
@@ -22,8 +41,13 @@ fn factories_preserve_class_links_and_distinct_identity() {
|
||||
let plain = vm.create_instance(root).unwrap();
|
||||
assert_ne!(first, second);
|
||||
assert_eq!(vm.class(child).unwrap().parent(), Some(class));
|
||||
assert_eq!(vm.instance(first).unwrap().class(), class);
|
||||
assert_eq!(vm.instance(plain).unwrap().class(), root);
|
||||
assert_eq!(vm.type_of(class.into()).unwrap(), vm.type_class());
|
||||
assert_eq!(vm.type_of(child.into()).unwrap(), vm.type_class());
|
||||
assert_eq!(vm.type_of(first.into()).unwrap(), class);
|
||||
assert_eq!(vm.type_of(plain.into()).unwrap(), root);
|
||||
let object = vm.object(first.into()).unwrap();
|
||||
assert_eq!(object.header().class(), class);
|
||||
assert!(matches!(object.data(), ObjectData::Instance(_)));
|
||||
vm.set_field(first, "x", Value::Integer(1)).unwrap();
|
||||
vm.set_field(first, "next", Value::Object(second.into()))
|
||||
.unwrap();
|
||||
@@ -58,6 +82,13 @@ fn foreign_handles_are_rejected_without_mutation() {
|
||||
Err(VmError::ForeignHandle)
|
||||
));
|
||||
assert!(matches!(vm.instance(foreign), Err(VmError::ForeignHandle)));
|
||||
for reference in [
|
||||
foreign.into(),
|
||||
other.root_class().into(),
|
||||
other.type_class().into(),
|
||||
] {
|
||||
assert_eq!(vm.type_of(reference), Err(VmError::ForeignHandle));
|
||||
}
|
||||
assert_eq!(
|
||||
vm.set_field(foreign, "x", Value::Nil),
|
||||
Err(VmError::ForeignHandle)
|
||||
@@ -92,16 +123,35 @@ fn invalid_names_do_not_allocate_and_names_are_not_a_namespace() {
|
||||
vm.create_class(" ", vm.root_class()),
|
||||
Err(VmError::EmptyClassName)
|
||||
);
|
||||
assert_eq!(
|
||||
vm.create_class("Object", vm.root_class()),
|
||||
Err(VmError::ReservedClassName)
|
||||
);
|
||||
assert_eq!(vm.object_count(), 1);
|
||||
for name in ["Object", "Type"] {
|
||||
assert_eq!(
|
||||
vm.create_class(name, vm.root_class()),
|
||||
Err(VmError::ReservedClassName)
|
||||
);
|
||||
}
|
||||
assert_eq!(vm.object_count(), 2);
|
||||
let a = vm.create_class("Label", vm.root_class()).unwrap();
|
||||
let b = vm.create_class("Label", vm.root_class()).unwrap();
|
||||
assert_ne!(a, b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn type_cannot_be_used_to_bypass_the_class_factory() {
|
||||
let mut vm = Vm::new().unwrap();
|
||||
let type_class = vm.type_class();
|
||||
let count = vm.object_count();
|
||||
assert_eq!(
|
||||
vm.create_instance(type_class),
|
||||
Err(VmError::UnsupportedMetaclass)
|
||||
);
|
||||
assert_eq!(
|
||||
vm.create_class("CustomType", type_class),
|
||||
Err(VmError::UnsupportedMetaclass)
|
||||
);
|
||||
assert_eq!(vm.object_count(), count);
|
||||
assert_eq!(vm.type_of(type_class.into()).unwrap(), type_class);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handles_remain_valid_when_the_arena_grows() {
|
||||
let mut vm = Vm::new().unwrap();
|
||||
@@ -111,7 +161,9 @@ fn handles_remain_valid_when_the_arena_grows() {
|
||||
for _ in 0..4096 {
|
||||
vm.create_instance(root).unwrap();
|
||||
}
|
||||
assert_eq!(vm.instance(first).unwrap().class(), root);
|
||||
assert_eq!(vm.type_of(first.into()).unwrap(), root);
|
||||
assert_eq!(vm.type_of(root.into()).unwrap(), vm.type_class());
|
||||
assert_eq!(vm.type_of(vm.type_class().into()).unwrap(), vm.type_class());
|
||||
assert_eq!(
|
||||
vm.instance(first).unwrap().field("stable"),
|
||||
Some(&Value::Bool(true))
|
||||
|
||||
+18
-6
@@ -2,15 +2,27 @@
|
||||
|
||||
## 内核
|
||||
|
||||
`Vm` 拥有私有 `Heap`。`Vm::new` 分配一个 `Object::Class(Class)`,其描述为名称 Object、父类 None,然后保存 `ClassRef`。不存在可观察的“半初始化 VM”;`bootstrap` 只返回已建立的根类句柄。因此根类不依赖数据库、全局命名空间、元类、网络或异步运行时。
|
||||
`Vm` 拥有私有 `Heap`。堆中每个条目都是统一的 Rust `Object`:公共 `ObjectHeader` 保存类型句柄,`ObjectData` 保存类元数据或实例字段。`Class` 只是类载荷,不是独立于公共对象头的语言对象。实例不再重复保存类型,统一通过 `vm.type_of(ObjectRef)` 查询对象头;`vm.class`/`vm.instance` 读取相应载荷。
|
||||
|
||||
`create_class(name, parent)` 校验已有父类后创建不可变描述,父类不可事后改写,不能形成继承环;只有内核启动路径可以创建没有父类的类。`create_instance` 校验类句柄再创建独立字段表。没有复制原型或隐式字段继承。未实现方法与实例字段查找的语言规则。
|
||||
`Vm::new` 通过私有 `Heap::bootstrap` 一次性建立 Object 和 Type。Object 无父类,类型为 Type;Type 的父类为 Object,类型为自身。初始化器在独占的空堆中计算两个槽位的私有句柄并填入完整对象,不使用裸指针或可空的临时类型引用;完整堆建立后才返回。不存在可观察的“半初始化 VM”,`bootstrap` 只返回已有根类句柄。此过程不依赖数据库、全局命名空间、网络或异步运行时。
|
||||
|
||||
```text
|
||||
堆条目:Object { header: ObjectHeader { class }, data: ObjectData }
|
||||
|
||||
对象 对象头中的类型 类载荷中的父类
|
||||
Object Type 无
|
||||
Type Type Object
|
||||
用户类 C Type 创建时指定的父类
|
||||
C 的实例 C 不适用
|
||||
```
|
||||
|
||||
`create_class(name, parent)` 校验已有父类后创建不可变描述,并在公共对象头中设置类型为 Type。父类不可事后改写,不能形成继承环;只有内核启动路径可以创建没有父类的类。精确名称 Object 和 Type 保留。`create_instance` 校验类句柄,再创建以该类为类型的对象头和独立字段表。当前禁止直接实例化 Type 或继承 Type,返回 `UnsupportedMetaclass` 且不分配对象,避免把类工厂与普通实例工厂混用;尚未实现自定义元类、类型调用、描述符、方法与继承属性查找。
|
||||
|
||||
句柄结构是私有 VM 身份加 `usize` 槽位。VM 身份用原子计数器分配,耗尽时返回错误,不回绕复用。`Vec` 扩容会移动 Rust 对象,但外部不保存它们的地址,旧句柄继续通过索引访问。当前绝不删除槽位,所以无代际号。VM 不实现 Clone,复制句柄不能复制 VM 身份。
|
||||
|
||||
检查顺序是归属、范围、种类。外部没有从整数构造句柄的 API;低层无效槽位与伪造类型用私有单元测试覆盖。通用句柄可通过 `as_class/as_instance` 做校验后的转换。不同 VM 的引用、已经销毁 VM 的引用,在新 VM 中都报 `ForeignHandle`。同一 VM 中不在有效槽位的引用报 `InvalidHandle`。写字段前检查目标和引用值,失败不会覆盖原字段。
|
||||
|
||||
`Value` 中标量和字符串不强制堆分配,引用目前仅指类或实例。当前字符串值是按 Rust 所有权复制的文本,并非具有对象身份的字符串实例。`Float` 使用 Rust f64/PartialEq 行为,尚未作为最终语言语义承诺。类不是自身或其他类的实例,根类也没有隐式元类。
|
||||
`Value` 中标量和字符串不强制堆分配,引用目前仅指类或实例。当前字符串值是按 Rust 所有权复制的文本,并非具有对象身份的字符串实例。`Float` 使用 Rust f64/PartialEq 行为,尚未作为最终语言语义承诺。统一对象头适用于堆对象,不表示所有标量都已对象化,也不复制 CPython 的引用计数或内存布局。Type 的自身类型引用是对象图中的环,继承关系仍然无环。
|
||||
|
||||
没有 GC,循环引用不形成 Rust 引用计数环,因为边只是句柄;所有内存随 VM 释放。长期运行时分配只增不减,尚无资源限额或内存不足的可恢复错误处理。宿主必须控制使用范围。
|
||||
|
||||
@@ -22,12 +34,12 @@ SQLite 文件模式自动创建父目录、开启 WAL,连接池最多 5 条连
|
||||
|
||||
PostgreSQL 最多 5 条连接,获取/建立连接超时 5 秒,启动真实连接而非惰性连接。其配置失败或初始化失败均上抛,不进入 SQLite 分支。SQLite 的缺省只发生在**未配置** DATABASE_URL 时。
|
||||
|
||||
元数据仅供宿主管理,不代表类、实例、句柄或整堆快照。重启宿主会创建新的 VM、新的 Object 和新句柄,只保留文件/数据库内的元数据。服务仅写入 `host.version`,不会把句柄调试值写入数据库。
|
||||
元数据仅供宿主管理,不代表类、实例、句柄或整堆快照。重启宿主会创建新的 VM、新的 Object/Type 和新句柄,只保留文件/数据库内的元数据。服务仅写入 `host.version`,不会把句柄调试值写入数据库。
|
||||
|
||||
`aster-server` 保存只读 VM 和共享 Storage。GET /bootstrap 只读根类;GET /health 探测数据库可用性。没有对象操作 API、语言执行 API 或鉴权协议。HTTP/Tokio/SQLx 都不进入内核。后续浏览器宿主应通过明确的能力接口桥接,不能把浏览器类型塞进核心对象工厂。
|
||||
|
||||
## 已决定与未决定
|
||||
|
||||
已决定:唯一无父类 Object、类和实例分离、实例引用类、句柄由 VM 校验、存储属于宿主。
|
||||
已决定:唯一无父类 Object、基础元类 Type、堆对象统一对象头、类与实例使用不同载荷、类型与父类分开、句柄由 VM 校验、存储属于宿主。
|
||||
|
||||
未决定:字节码格式、调用栈、方法解析、语言异常、外部对象协议、线程间共享 VM、GC/根集、堆序列化、浏览器通信、完整字符串对象/数值对象语义。不得在后续交接中把这些当成已实现契约。
|
||||
未决定:自定义元类、字节码格式、调用栈、方法解析、语言异常、外部对象协议、线程间共享 VM、GC/根集、堆序列化、浏览器通信、完整字符串对象/数值对象语义。不得在后续交接中把这些当成已实现契约。
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
核心 API 示例在 `aster-core/src/lib.rs`,由 rustdoc 实际编译执行。公开语义测试在 `aster-core/tests/runtime.rs`;伪造句柄仅在私有堆单元测试中构造,禁止为测试添加公开非法构造器。
|
||||
|
||||
堆对象通过 `ObjectHeader` 统一保存类型,`Class`/`Instance` 仅为不同载荷。旧的 `vm.instance(handle)?.class()` 改为 `vm.type_of(handle.into())`,类对象也使用同一接口。启动分配两个基础对象:Object 与 Type;CLI 示例另建一个类和两个实例后共 5 个对象,HTTP `/bootstrap` 的初始对象数为 2。新增测试覆盖 Type 自身类型、类型与父类分离、跨 VM 类型查询、扩容后类型引用稳定,以及拒绝通过普通工厂创建自定义元类。
|
||||
|
||||
存储测试用 `tempfile` 隔离文件,关闭池后重开,验证独立连接生命周期中的持久化。内存测试验证克隆共享、独立 store 隔离、特殊字符参数绑定、更新/缺失键。PostgreSQL 失败测试用只监听但不处理协议的本地 TCP socket,检验真实连接超时和不降级;成功测试显式 ignore,需要用户提供专用测试库。
|
||||
|
||||
尚未运行的冒烟脚本用于跨进程调用 CLI、启动 HTTP 宿主、探测真实 TCP 端点、发送 SIGTERM 并确认正常退出。它只创建临时目录,不改 `.env`,不操作外部服务器。端口通过临时绑定选择,释放后启动间存在很小的端口竞争窗口;在指定验证环境中若冲突,可重试整个脚本。
|
||||
|
||||
@@ -48,3 +48,9 @@
|
||||
准备阶段只读检查:Docker 29.1.3、Compose 2.40.3、x86_64、约 1.7 GiB 内存/4 GiB swap、73 GiB 可用磁盘;没有主机 Rust 或 buildx,已缓存 PostgreSQL 17 镜像。选用单任务 Rust 构建、legacy Docker builder,默认生产端口 18081(服务器回环地址),PostgreSQL 数据目录独立。
|
||||
|
||||
此处不预先宣称远端 CI 或部署已成功;最终结果以 Gitea Actions 对应运行与服务器状态核验为准。
|
||||
|
||||
## 统一对象头变更(2026-10-18)
|
||||
|
||||
本次把堆条目改为 `ObjectHeader` 加载荷,所有类与实例通过同一对象头引用类型;启动同时建立 Object 和 Type。新增或更新类型引导、类型查询、无效句柄、跨 VM 隔离、工厂边界、扩容稳定性及文档示例的检查,并同步远端 CLI/HTTP 冒烟的对象数预期。
|
||||
|
||||
本次未在本地运行 Rust 构建、测试、格式化脚本或冒烟脚本,也未下载依赖。本记录提交时尚未取得新版本远端结果,实际通过与否以本次提交对应的 Gitea CI 记录为准;之前已成功的 #160/#161 仅验证旧版对象模型,不代表本次修改已通过。
|
||||
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Local end-to-end smoke test, no Docker or external DB required."""
|
||||
"""Remote CI end-to-end smoke test, no external database required."""
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
@@ -32,7 +32,7 @@ def main():
|
||||
isolated_env = {**env, "DATABASE_URL": "invalid-database-url"}
|
||||
bootstrap = run(cli, "bootstrap", env=isolated_env)
|
||||
assert "root=Object parent=None" in bootstrap, bootstrap
|
||||
assert "instances_distinct=true heap_objects=4" in bootstrap, bootstrap
|
||||
assert "instances_distinct=true heap_objects=5" in bootstrap, bootstrap
|
||||
assert not Path(env["SQLITE_PATH"]).exists(), "bootstrap must not open storage"
|
||||
assert "sqlite" in run(cli, "metadata", "put", "smoke", "持久化成功", env=env)
|
||||
assert run(cli, "metadata", "get", "smoke", env=env) == "持久化成功"
|
||||
@@ -61,7 +61,7 @@ def main():
|
||||
for _ in range(2):
|
||||
with urllib.request.urlopen(f"http://127.0.0.1:{port}/bootstrap", timeout=2) as response:
|
||||
info = json.load(response)
|
||||
assert info == {"root_class": "Object", "root_has_parent": False, "heap_objects": 1}, info
|
||||
assert info == {"root_class": "Object", "root_has_parent": False, "heap_objects": 2}, info
|
||||
finally:
|
||||
process.send_signal(signal.SIGTERM)
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user