Perf: Restore core optimizations and stabilize VM execution
This commit restores and enhances several high-performance features that were previously lost, while fixing critical bugs in scope management. Performance Optimizations: - Zero-Copy TCO: Refactored TailCallRequest to use (Rc, usize), moving arguments directly on the VM stack via drain/resize instead of heap-allocating Vecs. - Slice-based Calls: Native functions and field accessors now operate directly on stack slices, significantly reducing memory churn. - SoA Push Fast-Path: Restored specialized 'push' intrinsic for RecordSeries. Matches layouts via Arc::ptr_eq to distribute values directly into columns without keyword lookups. Architectural Improvements: - Static Stack Frames (max_slots): The Binder now calculates the maximum required slots per lambda. The VM pre-resizes the stack frame, preventing collisions between local variables and temporary call arguments. - Macro Hygiene: Fixed a bug where macro-internal variables could overwrite outer call arguments due to stack overlap. - Trait Refactoring: Unified series operations via a polymorphic 'push_value' on the Series trait, with a generic implementation for ScalarSeries<T>. Code Quality: - Resolved all 'cargo clippy' warnings (collapsible ifs, redundant borrows, etc). - Restored 100% test pass rate (64/64 tests). - Verified stability of all examples and benchmarks.
This commit is contained in:
+70
-39
@@ -77,22 +77,32 @@ impl<T> RingBuffer<T> {
|
||||
/// (No pointers/heaps like String or Record).
|
||||
pub trait ScalarValue: Copy + Debug + 'static {
|
||||
fn to_value(self) -> Value;
|
||||
fn from_value(v: Value) -> Option<Self>;
|
||||
}
|
||||
|
||||
impl ScalarValue for f64 {
|
||||
fn to_value(self) -> Value {
|
||||
Value::Float(self)
|
||||
}
|
||||
fn from_value(v: Value) -> Option<Self> {
|
||||
v.as_float()
|
||||
}
|
||||
}
|
||||
impl ScalarValue for i64 {
|
||||
fn to_value(self) -> Value {
|
||||
Value::Int(self)
|
||||
}
|
||||
fn from_value(v: Value) -> Option<Self> {
|
||||
v.as_int()
|
||||
}
|
||||
}
|
||||
impl ScalarValue for bool {
|
||||
fn to_value(self) -> Value {
|
||||
Value::Bool(self)
|
||||
}
|
||||
fn from_value(v: Value) -> Option<Self> {
|
||||
if let Value::Bool(b) = v { Some(b) } else { None }
|
||||
}
|
||||
}
|
||||
|
||||
/// A highly optimized, type-safe series for primitive values (Float, Int, Bool).
|
||||
@@ -139,6 +149,9 @@ impl<T: ScalarValue> Series for ScalarSeries<T> {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.data.borrow().get(index).map(|&v| v.to_value())
|
||||
}
|
||||
fn push_value(&self, value: Value) {
|
||||
SeriesMember::push_into_member(self, value);
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.data.borrow().len()
|
||||
}
|
||||
@@ -187,6 +200,9 @@ impl Series for ValueSeries {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.data.borrow().get(index).cloned()
|
||||
}
|
||||
fn push_value(&self, value: Value) {
|
||||
SeriesMember::push_into_member(self, value);
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.data.borrow().len()
|
||||
}
|
||||
@@ -203,35 +219,19 @@ impl Series for ValueSeries {
|
||||
/// of the exact type (Type Erasure for member arrays).
|
||||
pub trait SeriesMember: Object + Series {
|
||||
/// Pushes a dynamic Value into the series, performing the necessary downcast.
|
||||
fn push_value(&self, value: Value);
|
||||
fn push_into_member(&self, value: Value);
|
||||
}
|
||||
|
||||
impl SeriesMember for ScalarSeries<f64> {
|
||||
fn push_value(&self, value: Value) {
|
||||
if let Some(v) = value.as_float() {
|
||||
self.data.borrow_mut().push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SeriesMember for ScalarSeries<i64> {
|
||||
fn push_value(&self, value: Value) {
|
||||
if let Some(v) = value.as_int() {
|
||||
self.data.borrow_mut().push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SeriesMember for ScalarSeries<bool> {
|
||||
fn push_value(&self, value: Value) {
|
||||
if let Value::Bool(v) = value {
|
||||
impl<T: ScalarValue> SeriesMember for ScalarSeries<T> {
|
||||
fn push_into_member(&self, value: Value) {
|
||||
if let Some(v) = T::from_value(value) {
|
||||
self.data.borrow_mut().push(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl SeriesMember for ValueSeries {
|
||||
fn push_value(&self, value: Value) {
|
||||
fn push_into_member(&self, value: Value) {
|
||||
self.data.borrow_mut().push(value);
|
||||
}
|
||||
}
|
||||
@@ -284,6 +284,22 @@ impl RecordSeries {
|
||||
.map(|idx| self.fields[idx].clone())
|
||||
}
|
||||
|
||||
pub fn layout(&self) -> &Arc<RecordLayout> {
|
||||
&self.layout
|
||||
}
|
||||
|
||||
/// The high-performance SoA push!
|
||||
/// Instead of pushing a single `Value::Record` (which requires keyword lookups),
|
||||
/// we push pre-extracted fields in order.
|
||||
pub fn push_record_values(&self, values: &[Value]) {
|
||||
if values.len() != self.fields.len() {
|
||||
return;
|
||||
}
|
||||
for (i, field) in self.fields.iter().enumerate() {
|
||||
field.borrow_mut().push_into_member(values[i].clone());
|
||||
}
|
||||
}
|
||||
|
||||
/// Dynamically reconstructs the full Record at the given lookback index.
|
||||
pub fn get_record(&self, index: usize) -> Option<Value> {
|
||||
if self.fields.is_empty() {
|
||||
@@ -329,6 +345,23 @@ impl Series for RecordSeries {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.get_record(index)
|
||||
}
|
||||
|
||||
fn push_value(&self, value: Value) {
|
||||
if let Value::Record(layout, values) = &value
|
||||
&& Arc::ptr_eq(&self.layout, layout)
|
||||
{
|
||||
self.push_record_values(values);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: extract fields by name
|
||||
let mut row = Vec::with_capacity(self.fields.len());
|
||||
for (kw, _) in &self.layout.fields {
|
||||
row.push(value.get_field(*kw).unwrap_or(Value::Void));
|
||||
}
|
||||
self.push_record_values(&row);
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.fields.first().map(|f| f.borrow().len()).unwrap_or(0)
|
||||
}
|
||||
@@ -383,6 +416,9 @@ impl Series for SeriesView {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.inner.borrow().get_item(index)
|
||||
}
|
||||
fn push_value(&self, value: Value) {
|
||||
self.inner.borrow_mut().push_value(value);
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.inner.borrow().len()
|
||||
}
|
||||
@@ -432,6 +468,9 @@ impl<T: ScalarValue> Series for SharedSeries<T> {
|
||||
.get(index)
|
||||
.map(|&v| (self.converter)(v))
|
||||
}
|
||||
fn push_value(&self, _value: Value) {
|
||||
// Shared series are read-only from scripts
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.buffer.borrow().len()
|
||||
}
|
||||
@@ -467,6 +506,9 @@ impl Series for SharedValueSeries {
|
||||
fn get_item(&self, index: usize) -> Option<Value> {
|
||||
self.buffer.borrow().get(index).cloned()
|
||||
}
|
||||
fn push_value(&self, _value: Value) {
|
||||
// Shared series are read-only from scripts
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.buffer.borrow().len()
|
||||
}
|
||||
@@ -520,6 +562,9 @@ impl Series for SharedRecordSeries {
|
||||
|
||||
Some(Value::Record(self.layout.clone(), Rc::new(vals)))
|
||||
}
|
||||
fn push_value(&self, _value: Value) {
|
||||
// Shared series are read-only from scripts
|
||||
}
|
||||
fn len(&self) -> usize {
|
||||
self.field_buffers
|
||||
.first()
|
||||
@@ -602,25 +647,11 @@ pub fn register(env: &Environment) {
|
||||
|
||||
// Polymorphic push logic
|
||||
if let Some(rs) = any.downcast_ref::<RecordSeries>() {
|
||||
if let Value::Record(_, values) = val {
|
||||
for (i, v) in values.iter().enumerate() {
|
||||
if let Some(f) = rs.fields.get(i) {
|
||||
f.borrow().push_value(v.clone());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
panic!("push to RecordSeries expects a record");
|
||||
}
|
||||
} else if let Some(s) = any.downcast_ref::<ScalarSeries<f64>>() {
|
||||
s.push_value(val.clone());
|
||||
} else if let Some(s) = any.downcast_ref::<ScalarSeries<i64>>() {
|
||||
s.push_value(val.clone());
|
||||
} else if let Some(s) = any.downcast_ref::<ScalarSeries<bool>>() {
|
||||
s.push_value(val.clone());
|
||||
} else if let Some(s) = any.downcast_ref::<ValueSeries>() {
|
||||
s.push_value(val.clone());
|
||||
rs.push_value(val.clone());
|
||||
} else if let Some(series) = obj.as_series() {
|
||||
series.push_value(val.clone());
|
||||
} else {
|
||||
panic!("Object is not a mutable series");
|
||||
panic!("Object is not a mutable series: {}", obj.type_name());
|
||||
}
|
||||
return Value::Void;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user