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:
+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