Files
endorphin-aster-vm/crates/objects/src/store.rs
T

343 lines
12 KiB
Rust

use crate::heap::Heap;
use crate::{
ChangeBatch, ChangeKey, CompareOp, EndorObject, ObjectPayload, ObjectRef, Record, Value,
VmError, WeakObjectRef,
};
/// Object storage independent of the VM evaluator. Construction creates an empty
/// heap; dropping it releases all objects and their host resources. Mutable
/// access is limited to operations that validate and account for owned edges.
#[derive(Debug)]
pub struct ObjectStore {
pub(crate) heap: Heap,
function_type: Option<WeakObjectRef>,
pub(crate) field_type_checks: bool,
}
impl ObjectStore {
/// Opt-in declared-field and method substitution checks, shared with the VM.
pub fn set_field_type_checks(&mut self, enabled: bool) -> Result<(), VmError> {
if self.is_read_only() {
return Err(VmError::UnsupportedOperation(
"configure field type checks during read-only call",
));
}
self.field_type_checks = enabled;
Ok(())
}
/// Existing objects are immutable; fresh result graphs may be constructed.
/// Scopes must end in LIFO order. Read handles are borrowed, never GC roots.
pub fn begin_read_only(&mut self) -> usize {
self.heap.begin_read_only()
}
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)
}
/// Like end_read_tracking, retaining the distinction between program reads
/// and VM annotation validation. Ordinary reads always dominate.
pub fn end_read_tracking_details(
&self,
token: usize,
) -> Result<Vec<crate::ReadAccess>, VmError> {
self.heap.end_read_tracking_details(token)
}
pub fn is_read_only(&self) -> bool {
self.heap.is_read_only()
}
pub fn downgrade(&self, reference: ObjectRef) -> Result<WeakObjectRef, VmError> {
self.object(reference)?;
Ok(WeakObjectRef(reference))
}
/// Returns a new owned root, or None when the original object was released.
/// A foreign store is an error, not an expired reference.
pub fn upgrade(&mut self, weak: WeakObjectRef) -> Result<Option<ObjectRef>, VmError> {
match self.object(weak.0) {
Ok(_) => {
self.retain(weak.0)?;
Ok(Some(weak.0))
}
Err(VmError::InvalidHandle) => Ok(None),
Err(error) => Err(error),
}
}
pub fn take_changes(&mut self) -> ChangeBatch {
self.heap.take_changes()
}
pub fn change_version(&self, reference: ObjectRef) -> Result<u64, VmError> {
Ok(self.object(reference)?.header().change_version())
}
/// Replace a host payload transactionally, including all owned GC edges.
pub fn replace_payload(
&mut self,
reference: ObjectRef,
payload: impl ObjectPayload,
) -> Result<(), VmError> {
if self
.object(reference)?
.payload()
.as_any()
.is::<crate::EndorTypeObject>()
{
return Err(VmError::UnsupportedOperation(
"replace immutable type definition",
));
}
if self
.object(reference)?
.payload()
.as_any()
.is::<crate::EndorTraitObject>()
{
return Err(VmError::UnsupportedOperation(
"replace immutable trait definition",
));
}
self.validate_builtin(&payload)?;
self.heap.replace_payload(reference, payload)
}
pub fn assign_from(&mut self, target: ObjectRef, source: ObjectRef) -> Result<bool, VmError> {
self.object(target)?;
self.object(source)?;
if let Some(cell) = self
.object(target)?
.payload()
.assignment_cell(source, self)?
{
self.heap.prepare_change(target)?;
// Replacing the Cell edge may otherwise reclaim a target which is
// reachable only through the edge being removed.
self.retain(target)?;
let result = self.cell_set(cell, Value::Object(source));
if result.is_ok() {
self.heap.record_change(target, ChangeKey::Contents);
}
self.release(target)?;
return result.map(|()| true);
}
if self.is_read_only() && self.object(target)?.payload().reference_assignment_only() {
return Ok(false);
}
self.heap.prepare_change(target)?;
let changed = self
.object(target)?
.payload()
.assign_from(self.object(source)?.payload())?;
if changed {
self.heap.record_change(target, ChangeKey::Contents);
}
Ok(changed)
}
pub fn new() -> Result<Self, VmError> {
Ok(Self {
heap: Heap::new()?,
function_type: None,
field_type_checks: false,
})
}
pub fn object_count(&self) -> usize {
self.heap.len()
}
/// Returns one external strong reference. Copies of the returned handle
/// are borrowed; use retain/release to manage additional owned references.
pub fn allocate(&mut self, payload: impl ObjectPayload) -> Result<ObjectRef, VmError> {
self.validate_builtin(&payload)?;
let function = payload.is_function();
let object = self.heap.allocate(Box::new(payload))?;
if function && let Some(weak) = self.function_type {
let result = (|| {
let definition = self.upgrade(weak)?.ok_or(VmError::InvalidArgument(
"configured Function type has expired",
))?;
let result = self.attach_type(object, definition);
self.release(definition)?;
result
})();
if let Err(error) = result {
self.release(object)?;
return Err(error);
}
}
Ok(object)
}
pub fn set_function_type(&mut self, definition: ObjectRef) -> Result<(), VmError> {
self.payload::<crate::EndorTypeObject>(definition, "Type")?;
if self.is_read_only() {
return Err(VmError::UnsupportedOperation(
"configure Function type during read-only call",
));
}
let weak = self.downgrade(definition)?;
if self.function_type.is_some_and(|old| old != weak) {
return Err(VmError::InvalidArgument(
"Function type is already configured",
));
}
self.function_type = Some(weak);
Ok(())
}
pub fn new_object(&mut self) -> Result<ObjectRef, VmError> {
self.allocate(Record)
}
pub fn object(&self, reference: ObjectRef) -> Result<&EndorObject, VmError> {
self.heap.record_read(reference);
self.heap.get(reference)
}
/// Borrowed storage path: depend on Contents for intermediate objects and
/// Attribute(name) on the final storage object. Every handle is validated.
pub fn attribute_path(&self, reference: ObjectRef) -> Result<Vec<ObjectRef>, VmError> {
let mut path = Vec::new();
let mut seen = std::collections::BTreeSet::new();
let mut current = reference;
loop {
if !seen.insert(current) {
return Err(VmError::InvalidArgument("cyclic attribute storage"));
}
path.push(current);
match self.object(current)?.payload().attribute_target(self)? {
Some(target) => current = target,
None => return Ok(path),
}
}
}
pub fn retain(&mut self, reference: ObjectRef) -> Result<(), VmError> {
self.heap.retain(reference)
}
pub fn release(&mut self, reference: ObjectRef) -> Result<(), VmError> {
self.heap.release(reference)
}
pub fn ref_count(&self, reference: ObjectRef) -> Result<usize, VmError> {
Ok(self.heap.get(reference)?.header().ref_count())
}
pub fn is_same(&self, left: ObjectRef, right: ObjectRef) -> Result<bool, VmError> {
self.heap.get(left)?;
self.heap.get(right)?;
Ok(left == right)
}
pub fn repr(&self, reference: ObjectRef) -> Result<String, VmError> {
self.heap.get(reference)?.payload().repr(reference)
}
pub fn str(&self, reference: ObjectRef) -> Result<String, VmError> {
self.heap.get(reference)?.payload().str(reference)
}
pub fn is_true(&self, reference: ObjectRef) -> Result<bool, VmError> {
self.heap.get(reference)?.payload().is_true()
}
pub fn hash(&self, reference: ObjectRef) -> Result<u64, VmError> {
self.heap.get(reference)?.payload().hash(reference)
}
pub fn compare(
&self,
left: ObjectRef,
right: ObjectRef,
operation: CompareOp,
) -> Result<bool, VmError> {
let left_payload = self.heap.get(left)?.payload();
let right_payload = self.heap.get(right)?.payload();
if let Some(result) = left_payload.compare(right_payload, operation)? {
return Ok(result);
}
if let Some(result) = right_payload.compare(left_payload, operation.reflected())? {
return Ok(result);
}
match operation {
CompareOp::Eq => Ok(left == right),
CompareOp::Ne => Ok(left != right),
_ => Err(VmError::UnsupportedOperation("ordering")),
}
}
/// Reads a borrowed value. An Object result needs retain() if the host
/// wants it to survive removal of the attribute that currently owns it.
pub fn get_attr(&self, reference: ObjectRef, name: &str) -> Result<Value, VmError> {
self.lookup_attr(reference, name)
.map(|attribute| match attribute {
crate::types::AttributeLookup::Value(value) => value,
crate::types::AttributeLookup::Method { function, .. } => Value::Object(function),
})
}
/// Object-valued attributes own internal strong references independently
/// of the caller's external references. Failure leaves the field unchanged.
pub fn set_attr(
&mut self,
reference: ObjectRef,
name: impl Into<String>,
value: Value,
) -> Result<(), VmError> {
let name = name.into();
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("write object type"));
}
if self.field_type_checks {
self.check_field_write(reference, &name, &value)?;
}
let storage = *self
.attribute_path(reference)?
.last()
.expect("nonempty attribute path");
self.heap.set_attr(storage, name, value)
}
pub fn del_attr(&mut self, reference: ObjectRef, name: &str) -> Result<(), VmError> {
if self.field_type_checks && self.declared_field(reference, name)?.is_some() {
return Err(VmError::UnsupportedOperation("delete declared class field"));
}
if name == "type" && self.type_of(reference)?.is_some() {
return Err(VmError::UnsupportedOperation("delete object type"));
}
let storage = *self
.attribute_path(reference)?
.last()
.expect("nonempty attribute path");
self.heap.del_attr(storage, name)
}
/// Validate a borrowed object handle without taking an owned reference.
pub fn validate_value(&self, value: &Value) -> Result<(), VmError> {
if let Value::Object(reference) = value {
self.heap.get(*reference)?;
}
Ok(())
}
/// Explicit full collection. Execution slots use owned roots, so values
/// on live execution stacks participate in the same root accounting.
pub fn collect_cycles(&mut self) -> usize {
self.heap.collect_cycles()
}
}