Refactor VM field access to use Rc clones

The VM's field access logic has been refactored to clone `Rc` pointers
for addresses and field identifiers. This change aims to improve
efficiency by avoiding unnecessary dereferencing and copying of values
when accessing fields within records and objects.

Additionally, the `Value::Object` handling in `BoundKind::GetField` has
been refined to more explicitly manage `RecordSeries` and `StreamNode`
types, ensuring that field access is handled polymorphically and
efficiently without copying underlying data structures.
This commit is contained in:
Michael Schimmel
2026-03-13 18:44:43 +01:00
parent b87a6d7ada
commit d035da9d91
2 changed files with 7 additions and 29 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ use crate::ast::compiler::bound_nodes::{
}; };
use crate::ast::compiler::dumper::Dumper; use crate::ast::compiler::dumper::Dumper;
use crate::ast::compiler::lambda_collector::LambdaCollector; use crate::ast::compiler::lambda_collector::LambdaCollector;
use crate::ast::compiler::lowering::{ExecNode, Lowering}; use crate::ast::compiler::lowering::{Lowering, ExecNode};
use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry}; use crate::ast::compiler::macros::{MacroEvaluator, MacroExpander, MacroRegistry};
use crate::ast::compiler::optimizer::Optimizer; use crate::ast::compiler::optimizer::Optimizer;
use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer}; use crate::ast::compiler::specializer::{FunctionRegistry, MonoCache, Specializer};
+6 -28
View File
@@ -335,7 +335,7 @@ impl VM {
val.clone() val.clone()
}; };
self.set_value(*addr, store_val)?; self.set_value(addr.clone(), store_val)?;
// Define always evaluates to the unwrapped value for immediate use. // Define always evaluates to the unwrapped value for immediate use.
Ok(val) Ok(val)
} }
@@ -351,18 +351,14 @@ impl VM {
} }
Ok(val) Ok(val)
} }
BoundKind::Get { addr, .. } => self.get_value(*addr), BoundKind::Get { addr, .. } => self.get_value(addr.clone()),
BoundKind::FieldAccessor(k) => Ok(Value::FieldAccessor(*k)), BoundKind::FieldAccessor(k) => Ok(Value::FieldAccessor(k.clone())),
BoundKind::GetField { rec, field } => { BoundKind::GetField { rec, field } => {
let rec_val = self.eval_internal(obs, rec)?; let rec_val = self.eval_internal(obs, rec)?;
// In Rust, pattern matching (`match`) is the idiomatic way to handle variants safely.
// Previously, this only handled `Value::Record`. Now, we handle objects (like `RecordSeries`) polymorphically.
match rec_val { match rec_val {
// Case 1: The classic Record.
// This is a struct-like tuple containing an Arc<RecordLayout> and a Vec<Value>.
Value::Record(layout, values) => { Value::Record(layout, values) => {
if let Some(idx) = layout.index_of(*field) { if let Some(idx) = layout.index_of(*field) {
Ok(values[idx].clone()) Ok(values[idx].clone())
@@ -370,28 +366,14 @@ impl VM {
Err(format!("Record does not have field :{}", field.name())) Err(format!("Record does not have field :{}", field.name()))
} }
} }
// Case 2: A dynamic Object (our SoA / Struct-of-Arrays optimization).
// `Value::Object` holds an `Rc<dyn Object>` - a reference-counted trait object (type-erased).
Value::Object(obj) => { Value::Object(obj) => {
// 1. We get the raw `&dyn Any` reference (Rust's standard mechanism for runtime type reflection).
let any_ptr = obj.as_any(); let any_ptr = obj.as_any();
// 2. Downcast! We check at runtime if the pointer actually points to a `RecordSeries`.
// `downcast_ref` is very fast (essentially an O(1) type ID comparison under the hood).
if let Some(record_series) = if let Some(record_series) =
any_ptr.downcast_ref::<crate::ast::rtl::series::RecordSeries>() any_ptr.downcast_ref::<crate::ast::rtl::series::RecordSeries>()
{ {
// 3. We call our highly performant 0-copy method on the series.
// It returns an `Rc<RefCell<dyn SeriesMember>>`, which is a shared pointer
// to the concrete column array (e.g., a `ScalarSeries<f64>`).
if let Some(field_series) = record_series.field(*field) { if let Some(field_series) = record_series.field(*field) {
// 4. We wrap this RefCell in our `SeriesView` struct.
// The `SeriesView` acts as a pure `Object` for the VM, holding the reference.
// CRITICAL: No array elements are copied here! This is pure, fast pointer juggling.
// This single operation turns a SoA `RecordSeries` into a high-speed `FloatSeries` view.
let view = let view =
crate::ast::rtl::series::SeriesView::new(field_series, *field); crate::ast::rtl::series::SeriesView::new(field_series, field.clone());
return Ok(Value::Object(std::rc::Rc::new(view))); return Ok(Value::Object(std::rc::Rc::new(view)));
} else { } else {
return Err(format!( return Err(format!(
@@ -400,18 +382,14 @@ impl VM {
)); ));
} }
} else if let Some(sn) = obj.as_any().downcast_ref::<crate::ast::rtl::streams::StreamNode>() { } else if let Some(sn) = obj.as_any().downcast_ref::<crate::ast::rtl::streams::StreamNode>() {
let mapped = crate::ast::rtl::streams::build_map_stream(sn.inner.clone(), *field); let mapped = crate::ast::rtl::streams::build_map_stream(sn.inner.clone(), field.clone());
return Ok(Value::Object(Rc::new(mapped))); return Ok(Value::Object(Rc::new(mapped)));
} }
// Fallback if it's another type of object that is not a RecordSeries.
Err(format!( Err(format!(
"Attempt to access field on non-record object: {}", "Attempt to access field on non-record object: {}",
obj.type_name() obj.type_name()
)) ))
} }
// Error handling for primitives (Int, Float, etc.).
_ => Err(format!( _ => Err(format!(
"Attempt to access field on non-record: {}", "Attempt to access field on non-record: {}",
rec_val rec_val
@@ -421,7 +399,7 @@ impl VM {
BoundKind::Set { addr, value } => { BoundKind::Set { addr, value } => {
let val = self.eval_internal(obs, value)?; let val = self.eval_internal(obs, value)?;
self.set_value(*addr, val.clone())?; self.set_value(addr.clone(), val.clone())?;
Ok(val) Ok(val)
} }
BoundKind::If { BoundKind::If {