From 37d7ed3e75b5d60aca05a212f46c264aeb5e5eaa Mon Sep 17 00:00:00 2001 From: Brummel Date: Sun, 22 Mar 2026 19:20:18 +0100 Subject: [PATCH] Remove unused TailCallRequest variant The `TailCallRequest` enum variant and its associated logic have been removed. Instead, the VM now uses an `Option<(Rc, Vec)>` field named `tail_call` to manage tail-call requests. This change simplifies the `Value` enum and centralizes tail-call handling within the `VM` struct. --- src/ast/types.rs | 6 -- src/ast/vm.rs | 140 +++++++++++++++++++++++------------------------ 2 files changed, 68 insertions(+), 78 deletions(-) diff --git a/src/ast/types.rs b/src/ast/types.rs index 98dc2fb..9410701 100644 --- a/src/ast/types.rs +++ b/src/ast/types.rs @@ -259,7 +259,6 @@ pub enum Value { Function(Rc), Object(Rc), // For compiled Closures and other opaque types Cell(Rc>), // Boxed value for captures - TailCallRequest(Box<(Rc, Vec)>), // Internal: For TCO (Boxed to keep Value small) } impl PartialEq for Value { @@ -280,9 +279,6 @@ impl PartialEq for Value { (Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b), (Value::Object(a), Value::Object(b)) => Rc::ptr_eq(a, b), (Value::Cell(a), Value::Cell(b)) => Rc::ptr_eq(a, b), - (Value::TailCallRequest(a), Value::TailCallRequest(b)) => { - Rc::ptr_eq(&a.0, &b.0) && a.1 == b.1 - } _ => false, } } @@ -623,7 +619,6 @@ impl Value { Value::Function(_) => StaticType::Any, // Dynamic function Value::Object(o) => StaticType::Object(o.type_name()), Value::Cell(c) => c.borrow().static_type(), - Value::TailCallRequest(_) => StaticType::Any, // Internal state, but typable as Any } } } @@ -667,7 +662,6 @@ impl fmt::Display for Value { Value::Function(f_meta) => write!(f, "", f_meta.purity), Value::Object(o) => write!(f, "<{:?}>", o), Value::Cell(c) => write!(f, "{}", c.borrow()), - Value::TailCallRequest(_) => write!(f, ""), } } } diff --git a/src/ast/vm.rs b/src/ast/vm.rs index a6613ba..42c07c3 100644 --- a/src/ast/vm.rs +++ b/src/ast/vm.rs @@ -133,6 +133,9 @@ pub struct VM { stack: Vec, globals: Rc>>, frames: Vec, + /// Side-channel for tail-call signaling. Set by `eval_internal` when a tail-call + /// is requested; consumed by `resolve_tail_calls` and the inline TCO loop. + tail_call: Option<(Rc, Vec)>, } impl VM { @@ -141,6 +144,7 @@ impl VM { stack: Vec::new(), globals, frames: Vec::new(), + tail_call: None, } } @@ -154,65 +158,63 @@ impl VM { mut result: Result, ) -> Result { loop { - match result { - Ok(Value::TailCallRequest(payload)) => { - let (next_obj, next_args) = *payload; - if let Ok(closure_rc) = next_obj.clone().into_rc_any().downcast::() { - self.stack.clear(); - // frames should be empty here since we popped before entering the loop - self.frames.push(CallFrame { - stack_base: 0, - closure: Some(closure_rc.clone()), - }); - let closure = closure_rc.as_ref(); - if let Some(count) = closure.positional_count - && next_args.len() == count as usize - { - self.stack.extend(next_args); - } else { - self.unpack(closure.parameter_node.as_ref(), &next_args, &mut 0)?; - } - - // PRE-ALLOCATION - let current_stack = self.stack.len() as u32; - if closure.stack_size > current_stack { - self.stack.resize(closure.stack_size as usize, Value::Void); - } - - result = self.eval_observed(observer, &closure.exec_node); - self.frames.pop(); - } else if let Some(series) = next_obj.as_series() { - if next_args.len() != 1 { - return Err(format!( - "{} indexer expects exactly 1 argument (the lookback index), got {}", - next_obj.type_name(), - next_args.len() - )); - } - if let Value::Int(idx) = &next_args[0] { - if *idx < 0 { - return Err(format!( - "{} lookback index cannot be negative: {}", - next_obj.type_name(), - idx - )); - } - result = Ok(series.get_item(*idx as usize).unwrap_or(Value::Void)); - } else { - return Err(format!( - "{} index must be an integer, got {}", - next_obj.type_name(), - next_args[0] - )); - } + if let Some((next_obj, next_args)) = self.tail_call.take() { + if let Ok(closure_rc) = next_obj.clone().into_rc_any().downcast::() { + self.stack.clear(); + // frames should be empty here since we popped before entering the loop + self.frames.push(CallFrame { + stack_base: 0, + closure: Some(closure_rc.clone()), + }); + let closure = closure_rc.as_ref(); + if let Some(count) = closure.positional_count + && next_args.len() == count as usize + { + self.stack.extend(next_args); } else { + self.unpack(closure.parameter_node.as_ref(), &next_args, &mut 0)?; + } + + // PRE-ALLOCATION + let current_stack = self.stack.len() as u32; + if closure.stack_size > current_stack { + self.stack.resize(closure.stack_size as usize, Value::Void); + } + + result = self.eval_observed(observer, &closure.exec_node); + self.frames.pop(); + } else if let Some(series) = next_obj.as_series() { + if next_args.len() != 1 { return Err(format!( - "Tail call target is not callable: {}", - next_obj.type_name() + "{} indexer expects exactly 1 argument (the lookback index), got {}", + next_obj.type_name(), + next_args.len() )); } + if let Value::Int(idx) = &next_args[0] { + if *idx < 0 { + return Err(format!( + "{} lookback index cannot be negative: {}", + next_obj.type_name(), + idx + )); + } + result = Ok(series.get_item(*idx as usize).unwrap_or(Value::Void)); + } else { + return Err(format!( + "{} index must be an integer, got {}", + next_obj.type_name(), + next_args[0] + )); + } + } else { + return Err(format!( + "Tail call target is not callable: {}", + next_obj.type_name() + )); } - _ => return result, + } else { + return result; } } } @@ -527,7 +529,8 @@ impl VM { match func_val { Value::Object(obj) => { - return Ok(Value::TailCallRequest(Box::new((obj, arg_vals)))); + self.tail_call = Some((obj, arg_vals)); + return Ok(Value::Void); } Value::Function(f) => return Ok((f.func)(&arg_vals)), Value::FieldAccessor(k) => { @@ -753,24 +756,19 @@ impl VM { Value::Function(_) => "Function", Value::Object(_) => "Object", Value::Cell(_) => "Cell", - Value::TailCallRequest(_) => "TailCallRequest", }; return Err(format!("Attempt to call non-function: {} (Variant: {})", current_func, variant_name)); } }; - match result { - Ok(Value::TailCallRequest(payload)) => { - let (next_obj, next_args) = *payload; - current_func = Value::Object(next_obj); - self.stack.truncate(base); - self.stack.extend(next_args); - continue; - } - res => { - self.stack.truncate(base); - return res; - } + if let Some((next_obj, next_args)) = self.tail_call.take() { + current_func = Value::Object(next_obj); + self.stack.truncate(base); + self.stack.extend(next_args); + continue; + } else { + self.stack.truncate(base); + return result; } } } @@ -785,10 +783,8 @@ impl VM { let frame = self.frames.last().ok_or("No call frame for 'again'")?; if let Some(closure_obj) = &frame.closure { - Ok(Value::TailCallRequest(Box::new(( - closure_obj.clone(), - arg_vals, - )))) + self.tail_call = Some((closure_obj.clone() as Rc, arg_vals)); + Ok(Value::Void) } else { Err("'again' called outside of a closure".to_string()) }