Remove unused TailCallRequest variant

The `TailCallRequest` enum variant and its associated logic have been
removed. Instead, the VM now uses an `Option<(Rc<dyn Object>,
Vec<Value>)>` field named `tail_call` to manage tail-call requests. This
change simplifies the `Value` enum and centralizes tail-call handling
within the `VM` struct.
This commit is contained in:
2026-03-22 19:20:18 +01:00
parent f6c963cb41
commit 37d7ed3e75
2 changed files with 68 additions and 78 deletions
-6
View File
@@ -259,7 +259,6 @@ pub enum Value {
Function(Rc<NativeFunction>), Function(Rc<NativeFunction>),
Object(Rc<dyn Object>), // For compiled Closures and other opaque types Object(Rc<dyn Object>), // For compiled Closures and other opaque types
Cell(Rc<RefCell<Value>>), // Boxed value for captures Cell(Rc<RefCell<Value>>), // Boxed value for captures
TailCallRequest(Box<(Rc<dyn Object>, Vec<Value>)>), // Internal: For TCO (Boxed to keep Value small)
} }
impl PartialEq for Value { impl PartialEq for Value {
@@ -280,9 +279,6 @@ impl PartialEq for Value {
(Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b), (Value::Function(a), Value::Function(b)) => Rc::ptr_eq(a, b),
(Value::Object(a), Value::Object(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::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, _ => false,
} }
} }
@@ -623,7 +619,6 @@ impl Value {
Value::Function(_) => StaticType::Any, // Dynamic function Value::Function(_) => StaticType::Any, // Dynamic function
Value::Object(o) => StaticType::Object(o.type_name()), Value::Object(o) => StaticType::Object(o.type_name()),
Value::Cell(c) => c.borrow().static_type(), 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, "<native fn, {:?}>", f_meta.purity), Value::Function(f_meta) => write!(f, "<native fn, {:?}>", f_meta.purity),
Value::Object(o) => write!(f, "<{:?}>", o), Value::Object(o) => write!(f, "<{:?}>", o),
Value::Cell(c) => write!(f, "{}", c.borrow()), Value::Cell(c) => write!(f, "{}", c.borrow()),
Value::TailCallRequest(_) => write!(f, "<tail call request>"),
} }
} }
} }
+68 -72
View File
@@ -133,6 +133,9 @@ pub struct VM {
stack: Vec<Value>, stack: Vec<Value>,
globals: Rc<RefCell<Vec<Value>>>, globals: Rc<RefCell<Vec<Value>>>,
frames: Vec<CallFrame>, frames: Vec<CallFrame>,
/// 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<dyn Object>, Vec<Value>)>,
} }
impl VM { impl VM {
@@ -141,6 +144,7 @@ impl VM {
stack: Vec::new(), stack: Vec::new(),
globals, globals,
frames: Vec::new(), frames: Vec::new(),
tail_call: None,
} }
} }
@@ -154,65 +158,63 @@ impl VM {
mut result: Result<Value, String>, mut result: Result<Value, String>,
) -> Result<Value, String> { ) -> Result<Value, String> {
loop { loop {
match result { if let Some((next_obj, next_args)) = self.tail_call.take() {
Ok(Value::TailCallRequest(payload)) => { if let Ok(closure_rc) = next_obj.clone().into_rc_any().downcast::<Closure>() {
let (next_obj, next_args) = *payload; self.stack.clear();
if let Ok(closure_rc) = next_obj.clone().into_rc_any().downcast::<Closure>() { // frames should be empty here since we popped before entering the loop
self.stack.clear(); self.frames.push(CallFrame {
// frames should be empty here since we popped before entering the loop stack_base: 0,
self.frames.push(CallFrame { closure: Some(closure_rc.clone()),
stack_base: 0, });
closure: Some(closure_rc.clone()), let closure = closure_rc.as_ref();
}); if let Some(count) = closure.positional_count
let closure = closure_rc.as_ref(); && next_args.len() == count as usize
if let Some(count) = closure.positional_count {
&& next_args.len() == count as usize self.stack.extend(next_args);
{
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]
));
}
} else { } 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!( return Err(format!(
"Tail call target is not callable: {}", "{} indexer expects exactly 1 argument (the lookback index), got {}",
next_obj.type_name() 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 { match func_val {
Value::Object(obj) => { 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::Function(f) => return Ok((f.func)(&arg_vals)),
Value::FieldAccessor(k) => { Value::FieldAccessor(k) => {
@@ -753,24 +756,19 @@ impl VM {
Value::Function(_) => "Function", Value::Function(_) => "Function",
Value::Object(_) => "Object", Value::Object(_) => "Object",
Value::Cell(_) => "Cell", Value::Cell(_) => "Cell",
Value::TailCallRequest(_) => "TailCallRequest",
}; };
return Err(format!("Attempt to call non-function: {} (Variant: {})", current_func, variant_name)); return Err(format!("Attempt to call non-function: {} (Variant: {})", current_func, variant_name));
} }
}; };
match result { if let Some((next_obj, next_args)) = self.tail_call.take() {
Ok(Value::TailCallRequest(payload)) => { current_func = Value::Object(next_obj);
let (next_obj, next_args) = *payload; self.stack.truncate(base);
current_func = Value::Object(next_obj); self.stack.extend(next_args);
self.stack.truncate(base); continue;
self.stack.extend(next_args); } else {
continue; self.stack.truncate(base);
} return result;
res => {
self.stack.truncate(base);
return res;
}
} }
} }
} }
@@ -785,10 +783,8 @@ impl VM {
let frame = self.frames.last().ok_or("No call frame for 'again'")?; let frame = self.frames.last().ok_or("No call frame for 'again'")?;
if let Some(closure_obj) = &frame.closure { if let Some(closure_obj) = &frame.closure {
Ok(Value::TailCallRequest(Box::new(( self.tail_call = Some((closure_obj.clone() as Rc<dyn Object>, arg_vals));
closure_obj.clone(), Ok(Value::Void)
arg_vals,
))))
} else { } else {
Err("'again' called outside of a closure".to_string()) Err("'again' called outside of a closure".to_string())
} }