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>),
|
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
@@ -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())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user