diff --git a/.cspell.dict/cpython.txt b/.cspell.dict/cpython.txt index c04448cadf4..688982cd7d9 100644 --- a/.cspell.dict/cpython.txt +++ b/.cspell.dict/cpython.txt @@ -133,6 +133,7 @@ mult multibytecodec nameobj nameop +nargsf ncells nconsts newargs diff --git a/crates/capi/src/abstract_.rs b/crates/capi/src/abstract_.rs index 854b3e000b9..3c109cd1ed5 100644 --- a/crates/capi/src/abstract_.rs +++ b/crates/capi/src/abstract_.rs @@ -1,5 +1,118 @@ use crate::{PyObject, pystate::with_vm}; +use alloc::slice; use core::ffi::c_int; +use rustpython_vm::builtins::{PyDict, PyStr, PyTuple}; +use rustpython_vm::function::{FuncArgs, KwArgs, PosArgs}; +use rustpython_vm::{AsObject, Py, PyObjectRef, PyResult, VirtualMachine}; + +const PY_VECTORCALL_ARGUMENTS_OFFSET: usize = 1usize << (usize::BITS as usize - 1); + +fn tuple_to_args(tuple: &Py) -> PosArgs { + tuple.iter().cloned().collect::>().into() +} + +fn dict_to_kwargs(vm: &VirtualMachine, dict: &Py) -> PyResult { + dict.items_vec() + .into_iter() + .map(|(key, value)| { + let key = key + .downcast_ref::() + .map(|s| s.to_string()) + .ok_or_else(|| vm.new_type_error("keywords must be strings"))?; + Ok((key, value)) + }) + .collect::>() + .map(KwArgs::new) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_Call( + callable: *mut PyObject, + args: *mut PyObject, + kwargs: *mut PyObject, +) -> *mut PyObject { + with_vm(|vm| { + let callable = unsafe { &*callable }; + let args = tuple_to_args(unsafe { &*args }.try_downcast_ref::(vm)?); + + let kwargs: Option = unsafe { kwargs.as_ref() } + .map(|kwargs| dict_to_kwargs(vm, kwargs.try_downcast_ref::(vm)?)) + .transpose()?; + + callable.call_with_args(FuncArgs::new(args, kwargs.unwrap_or_default()), vm) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_CallNoArgs(callable: *mut PyObject) -> *mut PyObject { + with_vm(|vm| unsafe { &*callable }.call((), vm)) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_Vectorcall( + callable: *mut PyObject, + args: *const *mut PyObject, + nargsf: usize, + kwnames: *mut PyObject, +) -> *mut PyObject { + with_vm(|vm| { + let num_positional_args = nargsf & !PY_VECTORCALL_ARGUMENTS_OFFSET; + + let kwnames: Option<&[PyObjectRef]> = unsafe { + kwnames + .as_ref() + .map(|tuple| Ok(&***tuple.try_downcast_ref::(vm)?)) + .transpose()? + }; + + let args_len = num_positional_args + kwnames.map_or(0, <[PyObjectRef]>::len); + let args = if args_len == 0 { + Vec::new() + } else { + unsafe { slice::from_raw_parts(args, args_len) } + .iter() + .map(|arg| unsafe { &**arg }.to_owned()) + .collect::>() + }; + + let callable = unsafe { &*callable }; + callable.vectorcall(args, num_positional_args, kwnames, vm) + }) +} + +#[unsafe(no_mangle)] +pub unsafe extern "C" fn PyObject_VectorcallMethod( + name: *mut PyObject, + args: *const *mut PyObject, + nargsf: usize, + kwnames: *mut PyObject, +) -> *mut PyObject { + with_vm(|vm| { + let args_len = nargsf & !PY_VECTORCALL_ARGUMENTS_OFFSET; + + if args_len == 0 { + return Err( + vm.new_system_error("PyObject_VectorcallMethod called with no receiver".to_owned()) + ); + } + + let (receiver, args) = unsafe { slice::from_raw_parts(args, args_len) } + .split_first() + .expect("args_len > 0 should guarantee a receiver"); + + let method_name = unsafe { (&*name).try_downcast_ref::(vm)? }; + let callable = unsafe { (&**receiver).get_attr(method_name, vm)? }; + + Ok(unsafe { + PyObject_Vectorcall( + callable.as_object().as_raw().cast_mut(), + args.as_ptr(), + nargsf - 1, + kwnames, + ) + }) + }) +} #[unsafe(no_mangle)] pub unsafe extern "C" fn PyObject_GetItem(obj: *mut PyObject, key: *mut PyObject) -> *mut PyObject {