X Tutup
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
53 changes: 53 additions & 0 deletions crates/vm/src/builtins/descriptor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -426,10 +426,63 @@ impl GetDescriptor for PyMemberDescriptor {
}
}

/// Vectorcall for method_descriptor: calls native method directly
fn vectorcall_method_descriptor(
zelf_obj: &PyObject,
args: Vec<PyObjectRef>,
nargs: usize,
kwnames: Option<&[PyObjectRef]>,
vm: &VirtualMachine,
) -> PyResult {
let zelf: &Py<PyMethodDescriptor> = zelf_obj.downcast_ref().unwrap();
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
(zelf.method.func)(vm, func_args)
}

/// Vectorcall for wrapper_descriptor: calls wrapped slot function
fn vectorcall_wrapper(
zelf_obj: &PyObject,
mut args: Vec<PyObjectRef>,
nargs: usize,
kwnames: Option<&[PyObjectRef]>,
vm: &VirtualMachine,
) -> PyResult {
let zelf: &Py<PyWrapper> = zelf_obj.downcast_ref().unwrap();
// First positional arg is self
if nargs == 0 {
return Err(vm.new_type_error(format!(
"descriptor '{}' of '{}' object needs an argument",
zelf.name.as_str(),
zelf.typ.name()
)));
}
let obj = args.remove(0);
if !obj.fast_isinstance(zelf.typ) {
return Err(vm.new_type_error(format!(
"descriptor '{}' requires a '{}' object but received a '{}'",
zelf.name.as_str(),
zelf.typ.name(),
obj.class().name()
)));
}
let rest = FuncArgs::from_vectorcall_owned(args, nargs - 1, kwnames);
zelf.wrapped.call(obj, rest, vm)
}

pub fn init(ctx: &'static Context) {
PyMemberDescriptor::extend_class(ctx, ctx.types.member_descriptor_type);
PyMethodDescriptor::extend_class(ctx, ctx.types.method_descriptor_type);
ctx.types
.method_descriptor_type
.slots
.vectorcall
.store(Some(vectorcall_method_descriptor));
PyWrapper::extend_class(ctx, ctx.types.wrapper_descriptor_type);
ctx.types
.wrapper_descriptor_type
.slots
.vectorcall
.store(Some(vectorcall_wrapper));
PyMethodWrapper::extend_class(ctx, ctx.types.method_wrapper_type);
}

Expand Down
6 changes: 3 additions & 3 deletions crates/vm/src/builtins/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1280,14 +1280,14 @@ pub(crate) fn vectorcall_function(
// FAST PATH: simple positional-only call, exact arg count.
// Move owned args directly into fastlocals — no clone needed.
let locals = if code.flags.contains(bytecode::CodeFlags::NEWLOCALS) {
ArgMapping::from_dict_exact(vm.ctx.new_dict())
None // lazy allocation — most frames never access locals dict
} else {
ArgMapping::from_dict_exact(zelf.globals.clone())
Some(ArgMapping::from_dict_exact(zelf.globals.clone()))
};

let frame = Frame::new(
code.to_owned(),
Scope::new(Some(locals), zelf.globals.clone()),
Scope::new(locals, zelf.globals.clone()),
zelf.builtins.clone(),
zelf.closure.as_ref().map_or(&[], |c| c.as_slice()),
Some(zelf.to_owned().into()),
Expand Down
29 changes: 29 additions & 0 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2339,8 +2339,37 @@ fn subtype_set_dict(obj: PyObjectRef, value: PyObjectRef, vm: &VirtualMachine) -
* The magical type type
*/

/// Vectorcall for PyType (PEP 590).
/// Fast path: type(x) returns x.__class__ without constructing FuncArgs.
fn vectorcall_type(
zelf_obj: &PyObject,
args: Vec<PyObjectRef>,
nargs: usize,
kwnames: Option<&[PyObjectRef]>,
vm: &VirtualMachine,
) -> PyResult {
let zelf: &Py<PyType> = zelf_obj.downcast_ref().unwrap();

// type(x) fast path: single positional arg, no kwargs
if zelf.is(vm.ctx.types.type_type) {
let no_kwargs = kwnames.is_none_or(|kw| kw.is_empty());
if nargs == 1 && no_kwargs {
return Ok(args[0].obj_type());
}
}

// Fallback: construct FuncArgs and use standard call
let func_args = FuncArgs::from_vectorcall_owned(args, nargs, kwnames);
PyType::call(zelf, func_args, vm)
}

pub(crate) fn init(ctx: &'static Context) {
PyType::extend_class(ctx, ctx.types.type_type);
ctx.types
.type_type
.slots
.vectorcall
.store(Some(vectorcall_type));
}

pub(crate) fn call_slot_new(
Expand Down
127 changes: 36 additions & 91 deletions crates/vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3847,8 +3847,7 @@ impl ExecutingFrame<'_> {
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
let args = self.collect_positional_args(nargs);
self.execute_call(args, vm)
self.execute_call_vectorcall(nargs, vm)
}
Instruction::CallMethodDescriptorO => {
let instr_idx = self.lasti() as usize - 1;
Expand Down Expand Up @@ -3885,8 +3884,7 @@ impl ExecutingFrame<'_> {
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
let args = self.collect_positional_args(nargs);
self.execute_call(args, vm)
self.execute_call_vectorcall(nargs, vm)
}
Instruction::CallMethodDescriptorFast => {
let instr_idx = self.lasti() as usize - 1;
Expand Down Expand Up @@ -3924,8 +3922,7 @@ impl ExecutingFrame<'_> {
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
let args = self.collect_positional_args(nargs);
self.execute_call(args, vm)
self.execute_call_vectorcall(nargs, vm)
}
Instruction::CallBuiltinClass => {
let instr_idx = self.lasti() as usize - 1;
Expand All @@ -3934,26 +3931,12 @@ impl ExecutingFrame<'_> {
let nargs: u32 = arg.into();
let callable = self.nth_value(nargs + 1);
let callable_tag = callable as *const PyObject as u32;
if cached_tag == callable_tag && callable.downcast_ref::<PyType>().is_some() {
let args = self.collect_positional_args(nargs);
let self_or_null = self.pop_value_opt();
let callable = self.pop_value();
let final_args = if let Some(self_val) = self_or_null {
let mut args = args;
args.prepend_arg(self_val);
args
} else {
args
};
let result = callable.call(final_args, vm)?;
self.push_value(result);
return Ok(None);
if !(cached_tag == callable_tag && callable.downcast_ref::<PyType>().is_some()) {
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
}
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
let args = self.collect_positional_args(nargs);
self.execute_call(args, vm)
self.execute_call_vectorcall(nargs, vm)
}
Instruction::CallAllocAndEnterInit => {
let instr_idx = self.lasti() as usize - 1;
Expand Down Expand Up @@ -4013,8 +3996,7 @@ impl ExecutingFrame<'_> {
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
let args = self.collect_positional_args(nargs);
self.execute_call(args, vm)
self.execute_call_vectorcall(nargs, vm)
}
Instruction::CallMethodDescriptorFastWithKeywords => {
// Native function interface is uniform regardless of keyword support
Expand Down Expand Up @@ -4053,8 +4035,7 @@ impl ExecutingFrame<'_> {
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
let args = self.collect_positional_args(nargs);
self.execute_call(args, vm)
self.execute_call_vectorcall(nargs, vm)
}
Instruction::CallBuiltinFastWithKeywords => {
// Native function interface is uniform regardless of keyword support
Expand Down Expand Up @@ -4087,8 +4068,7 @@ impl ExecutingFrame<'_> {
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
let args = self.collect_positional_args(nargs);
self.execute_call(args, vm)
self.execute_call_vectorcall(nargs, vm)
}
Instruction::CallNonPyGeneral => {
let instr_idx = self.lasti() as usize - 1;
Expand All @@ -4097,15 +4077,12 @@ impl ExecutingFrame<'_> {
let nargs: u32 = arg.into();
let callable = self.nth_value(nargs + 1);
let callable_tag = callable as *const PyObject as u32;
if cached_tag == callable_tag {
let args = self.collect_positional_args(nargs);
return self.execute_call(args, vm);
if cached_tag != callable_tag {
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
}
self.deoptimize(Instruction::Call {
argc: Arg::marker(),
});
let args = self.collect_positional_args(nargs);
self.execute_call(args, vm)
self.execute_call_vectorcall(nargs, vm)
}
Instruction::CallKwPy => {
let instr_idx = self.lasti() as usize - 1;
Expand Down Expand Up @@ -4196,15 +4173,12 @@ impl ExecutingFrame<'_> {
let nargs: u32 = arg.into();
let callable = self.nth_value(nargs + 2);
let callable_tag = callable as *const PyObject as u32;
if cached_tag == callable_tag {
let args = self.collect_keyword_args(nargs);
return self.execute_call(args, vm);
if cached_tag != callable_tag {
self.deoptimize(Instruction::CallKw {
argc: Arg::marker(),
});
}
self.deoptimize(Instruction::CallKw {
argc: Arg::marker(),
});
let args = self.collect_keyword_args(nargs);
self.execute_call(args, vm)
self.execute_call_kw_vectorcall(nargs, vm)
}
Instruction::LoadSuperAttrAttr => {
let oparg = u32::from(arg);
Expand Down Expand Up @@ -5626,22 +5600,15 @@ impl ExecutingFrame<'_> {
fn execute_call_vectorcall(&mut self, nargs: u32, vm: &VirtualMachine) -> FrameResult {
let nargs_usize = nargs as usize;
let stack_len = self.state.stack.len();
debug_assert!(
stack_len >= nargs_usize + 2,
"CALL stack underflow: need callable + self_or_null + {nargs_usize} args, have {stack_len}"
);
let callable_idx = stack_len - nargs_usize - 2;
let self_or_null_idx = stack_len - nargs_usize - 1;
let args_start = stack_len - nargs_usize;

// Check if callable has vectorcall slot
let has_vectorcall = self.state.stack[callable_idx]
.as_ref()
.is_some_and(|sr| sr.as_object().class().slots.vectorcall.load().is_some());

if !has_vectorcall {
// Fallback to existing FuncArgs path
let args = self.collect_positional_args(nargs);
return self.execute_call(args, vm);
}

// Build args slice: [self_or_null?, arg1, ..., argN]
// Build args: [self?, arg1, ..., argN]
let self_or_null = self.state.stack[self_or_null_idx]
.take()
.map(|sr| sr.to_pyobj());
Expand All @@ -5664,6 +5631,7 @@ impl ExecutingFrame<'_> {
let callable_obj = self.state.stack[callable_idx].take().unwrap().to_pyobj();
self.state.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)?;
self.push_value(result);
Ok(None)
Expand All @@ -5680,50 +5648,26 @@ impl ExecutingFrame<'_> {
.downcast_ref::<PyTuple>()
.expect("kwarg names should be tuple");
let kw_count = kwarg_names_tuple.len();
debug_assert!(kw_count <= nargs_usize, "CALL_KW kw_count exceeds nargs");

let stack_len = self.state.stack.len();
debug_assert!(
stack_len >= nargs_usize + 2,
"CALL_KW stack underflow: need callable + self_or_null + {nargs_usize} args, have {stack_len}"
);
let callable_idx = stack_len - nargs_usize - 2;
let self_or_null_idx = stack_len - nargs_usize - 1;
let args_start = stack_len - nargs_usize;

// Check if callable has vectorcall slot
let has_vectorcall = self.state.stack[callable_idx]
.as_ref()
.is_some_and(|sr| sr.as_object().class().slots.vectorcall.load().is_some());

if !has_vectorcall {
// Fallback: reconstruct kwarg_names iterator and use existing path
let kwarg_names_iter = kwarg_names_tuple.as_slice().iter().map(|pyobj| {
pyobj
.downcast_ref::<PyUtf8Str>()
.unwrap()
.as_str()
.to_owned()
});
let args = self.pop_multiple(nargs_usize);
let func_args = FuncArgs::with_kwargs_names(args, kwarg_names_iter);
// pop self_or_null and callable
let self_or_null = self.pop_value_opt();
let callable = self.pop_value();
let final_args = if let Some(self_val) = self_or_null {
let mut args = func_args;
args.prepend_arg(self_val);
args
} else {
func_args
};
let value = callable.call(final_args, vm)?;
self.push_value(value);
return Ok(None);
}

// Build args: [self?, pos_arg1, ..., pos_argM, kw_val1, ..., kw_valK]
let self_or_null = self.state.stack[self_or_null_idx]
.take()
.map(|sr| sr.to_pyobj());
let has_self = self_or_null.is_some();

let pos_count = nargs_usize - kw_count;
let pos_count = nargs_usize
.checked_sub(kw_count)
.expect("CALL_KW: kw_count exceeds nargs");
let effective_nargs = if has_self { pos_count + 1 } else { pos_count };

// Build the full args slice: positional (including self) + kwarg values
Expand All @@ -5740,6 +5684,7 @@ impl ExecutingFrame<'_> {
let callable_obj = self.state.stack[callable_idx].take().unwrap().to_pyobj();
self.state.stack.truncate(callable_idx);

// invoke_vectorcall falls back to FuncArgs if no vectorcall slot
let kwnames = kwarg_names_tuple.as_slice();
let result = callable_obj.vectorcall(args_vec, effective_nargs, Some(kwnames), vm)?;
self.push_value(result);
Expand Down
Loading
X Tutup