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:
@@ -259,7 +259,6 @@ pub enum Value {
|
||||
Function(Rc<NativeFunction>),
|
||||
Object(Rc<dyn Object>), // For compiled Closures and other opaque types
|
||||
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 {
|
||||
@@ -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, "<native fn, {:?}>", f_meta.purity),
|
||||
Value::Object(o) => write!(f, "<{:?}>", o),
|
||||
Value::Cell(c) => write!(f, "{}", c.borrow()),
|
||||
Value::TailCallRequest(_) => write!(f, "<tail call request>"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+68
-72
@@ -133,6 +133,9 @@ pub struct VM {
|
||||
stack: Vec<Value>,
|
||||
globals: Rc<RefCell<Vec<Value>>>,
|
||||
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 {
|
||||
@@ -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<Value, String>,
|
||||
) -> Result<Value, String> {
|
||||
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::<Closure>() {
|
||||
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::<Closure>() {
|
||||
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<dyn Object>, arg_vals));
|
||||
Ok(Value::Void)
|
||||
} else {
|
||||
Err("'again' called outside of a closure".to_string())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user