Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix vectorcall compatibility with LocalsPlus API
Update vectorcall dispatch functions to use localsplus stack
accessors instead of direct stack field access. Add
stack_truncate method to LocalsPlus. Update vectorcall fast
path in function.rs to use datastack and fastlocals_mut().
  • Loading branch information
youknowone committed Mar 4, 2026
commit 938fdf72e0c35ea74f0ea716b8296f2e6bc6cd0c
13 changes: 10 additions & 3 deletions crates/vm/src/builtins/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1309,26 +1309,33 @@ pub(crate) fn vectorcall_function(
zelf.builtins.clone(),
zelf.closure.as_ref().map_or(&[], |c| c.as_slice()),
Some(zelf.to_owned().into()),
true, // Always use datastack (is_simple excludes gen/coro)
vm,
)
.into_ref(&vm.ctx);

{
let fastlocals = unsafe { frame.fastlocals.borrow_mut() };
let fastlocals = unsafe { frame.fastlocals_mut() };
for (slot, arg) in fastlocals.iter_mut().zip(args.drain(..nargs)) {
*slot = Some(arg);
}
}

if let Some(cell2arg) = code.cell2arg.as_deref() {
let fastlocals = unsafe { frame.fastlocals.borrow_mut() };
let fastlocals = unsafe { frame.fastlocals_mut() };
for (cell_idx, arg_idx) in cell2arg.iter().enumerate().filter(|(_, i)| **i != -1) {
let x = fastlocals[*arg_idx as usize].take();
frame.set_cell_contents(cell_idx, x);
}
}

return vm.run_frame(frame);
let result = vm.run_frame(frame.clone());
unsafe {
if let Some(base) = frame.materialize_localsplus() {
vm.datastack_pop(base);
}
}
return result;
}

// SLOW PATH: construct FuncArgs from owned Vec and delegate to invoke()
Expand Down
33 changes: 22 additions & 11 deletions crates/vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,14 @@ impl LocalsPlus {
data.swap(base + a, base + b);
}

/// Truncate the stack to `new_len` elements, dropping excess values.
fn stack_truncate(&mut self, new_len: usize) {
debug_assert!(new_len <= self.stack_top as usize);
while self.stack_top as usize > new_len {
let _ = self.stack_pop();
}
}

/// Clear the stack, dropping all values.
fn stack_clear(&mut self) {
while self.stack_top > 0 {
Expand Down Expand Up @@ -5959,7 +5967,7 @@ impl ExecutingFrame<'_> {
#[inline]
fn execute_call_vectorcall(&mut self, nargs: u32, vm: &VirtualMachine) -> FrameResult {
let nargs_usize = nargs as usize;
let stack_len = self.stack.len();
let stack_len = self.localsplus.stack_len();
debug_assert!(
stack_len >= nargs_usize + 2,
"CALL stack underflow: need callable + self_or_null + {nargs_usize} args, have {stack_len}"
Expand All @@ -5969,7 +5977,9 @@ impl ExecutingFrame<'_> {
let args_start = stack_len - nargs_usize;

// Build args: [self?, arg1, ..., argN]
let self_or_null = self.stack[self_or_null_idx]
let self_or_null = self
.localsplus
.stack_index_mut(self_or_null_idx)
.take()
.map(|sr| sr.to_pyobj());
let has_self = self_or_null.is_some();
Expand All @@ -5984,12 +5994,12 @@ impl ExecutingFrame<'_> {
args_vec.push(self_val);
}
for stack_idx in args_start..stack_len {
let val = self.stack[stack_idx].take().unwrap().to_pyobj();
let val = self.localsplus.stack_index_mut(stack_idx).take().unwrap().to_pyobj();
args_vec.push(val);
}

let callable_obj = self.stack[callable_idx].take().unwrap().to_pyobj();
self.stack.truncate(callable_idx);
let callable_obj = self.localsplus.stack_index_mut(callable_idx).take().unwrap().to_pyobj();
self.localsplus.stack_truncate(callable_idx);

// invoke_vectorcall falls back to FuncArgs if no vectorcall slot
let result = callable_obj.vectorcall(args_vec, effective_nargs, None, vm)?;
Expand All @@ -6010,7 +6020,7 @@ impl ExecutingFrame<'_> {
let kw_count = kwarg_names_tuple.len();
debug_assert!(kw_count <= nargs_usize, "CALL_KW kw_count exceeds nargs");

let stack_len = self.stack.len();
let stack_len = self.localsplus.stack_len();
debug_assert!(
stack_len >= nargs_usize + 2,
"CALL_KW stack underflow: need callable + self_or_null + {nargs_usize} args, have {stack_len}"
Expand All @@ -6019,9 +6029,10 @@ impl ExecutingFrame<'_> {
let self_or_null_idx = stack_len - nargs_usize - 1;
let args_start = stack_len - nargs_usize;


// Build args: [self?, pos_arg1, ..., pos_argM, kw_val1, ..., kw_valK]
let self_or_null = self.stack[self_or_null_idx]
let self_or_null = self
.localsplus
.stack_index_mut(self_or_null_idx)
.take()
.map(|sr| sr.to_pyobj());
let has_self = self_or_null.is_some();
Expand All @@ -6038,12 +6049,12 @@ impl ExecutingFrame<'_> {
args_vec.push(self_val);
}
for stack_idx in args_start..stack_len {
let val = self.stack[stack_idx].take().unwrap().to_pyobj();
let val = self.localsplus.stack_index_mut(stack_idx).take().unwrap().to_pyobj();
args_vec.push(val);
}

let callable_obj = self.stack[callable_idx].take().unwrap().to_pyobj();
self.stack.truncate(callable_idx);
let callable_obj = self.localsplus.stack_index_mut(callable_idx).take().unwrap().to_pyobj();
self.localsplus.stack_truncate(callable_idx);

// invoke_vectorcall falls back to FuncArgs if no vectorcall slot
let kwnames = kwarg_names_tuple.as_slice();
Expand Down