From a07d3dd1832ad80d3d8da9cfa389ac6a3e38bf94 Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 20 May 2026 13:42:36 +0200 Subject: [PATCH 1/3] Add call functions to c-api --- crates/capi/src/abstract_.rs | 91 ++++++++++++++++++++++++++++++ crates/vm/src/function/argument.rs | 36 ++++++++++-- 2 files changed, 121 insertions(+), 6 deletions(-) diff --git a/crates/capi/src/abstract_.rs b/crates/capi/src/abstract_.rs index 854b3e000b9..93ce8003732 100644 --- a/crates/capi/src/abstract_.rs +++ b/crates/capi/src/abstract_.rs @@ -1,5 +1,96 @@ 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}; +use rustpython_vm::{AsObject, PyObjectRef}; + +const PY_VECTORCALL_ARGUMENTS_OFFSET: usize = 1usize << (usize::BITS as usize - 1); + +#[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 = unsafe { &*args }.try_downcast_ref::(vm)?; + + let kwargs: Option = unsafe { kwargs.as_ref() } + .map(|kwargs| kwargs.try_downcast_ref::(vm)?.try_into()) + .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 = 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 { diff --git a/crates/vm/src/function/argument.rs b/crates/vm/src/function/argument.rs index 40408531cbe..bfbae753845 100644 --- a/crates/vm/src/function/argument.rs +++ b/crates/vm/src/function/argument.rs @@ -1,9 +1,4 @@ -use crate::{ - AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyTupleRef, PyTypeRef}, - convert::ToPyObject, - object::{Traverse, TraverseFn}, -}; +use crate::{AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{PyBaseExceptionRef, PyDict, PyStr, PyTuple, PyTupleRef, PyTypeRef}, convert::ToPyObject, object::{Traverse, TraverseFn}, Py}; use core::ops::RangeInclusive; use indexmap::IndexMap; use itertools::Itertools; @@ -438,6 +433,29 @@ impl FromIterator<(String, T)> for KwArgs { } } +impl TryFrom<&Py> for KwArgs { + type Error = PyBaseExceptionRef; + + fn try_from(kwargs: &Py) -> Result { + kwargs + .items_vec() + .into_iter() + .map(|(key, value)| { + let key = key + .downcast_ref::() + .map(|s| s.to_string()) + .ok_or_else(|| { + crate::vm::thread::with_current_vm(|vm| { + vm.new_type_error("keywords must be strings") + }) + })?; + Ok((key, value)) + }) + .collect::, _>>() + .map(Self) + } +} + impl Default for KwArgs { fn default() -> Self { Self(IndexMap::new()) @@ -508,6 +526,12 @@ impl From> for PosArgs { } } +impl From<&Py> for PosArgs { + fn from(args: &Py) -> Self { + Self(args.iter().cloned().collect()) + } +} + impl From<()> for PosArgs { fn from(_args: ()) -> Self { Self(Vec::new()) From 6d47176b7b92e111687ad42e8f62626554f8017f Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 20 May 2026 13:53:03 +0200 Subject: [PATCH 2/3] Review --- .cspell.dict/cpython.txt | 1 + crates/capi/src/abstract_.rs | 12 ++++++++---- crates/vm/src/function/argument.rs | 7 ++++++- 3 files changed, 15 insertions(+), 5 deletions(-) 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 93ce8003732..ad31eb31566 100644 --- a/crates/capi/src/abstract_.rs +++ b/crates/capi/src/abstract_.rs @@ -48,10 +48,14 @@ pub unsafe extern "C" fn PyObject_Vectorcall( }; let args_len = num_positional_args + kwnames.map_or(0, <[PyObjectRef]>::len); - let args = unsafe { slice::from_raw_parts(args, args_len) } - .iter() - .map(|arg| unsafe { &**arg }.to_owned()) - .collect::>(); + 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) diff --git a/crates/vm/src/function/argument.rs b/crates/vm/src/function/argument.rs index bfbae753845..0b5955122ac 100644 --- a/crates/vm/src/function/argument.rs +++ b/crates/vm/src/function/argument.rs @@ -1,4 +1,9 @@ -use crate::{AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, builtins::{PyBaseExceptionRef, PyDict, PyStr, PyTuple, PyTupleRef, PyTypeRef}, convert::ToPyObject, object::{Traverse, TraverseFn}, Py}; +use crate::{ + AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, + builtins::{PyBaseExceptionRef, PyDict, PyStr, PyTuple, PyTupleRef, PyTypeRef}, + convert::ToPyObject, + object::{Traverse, TraverseFn}, +}; use core::ops::RangeInclusive; use indexmap::IndexMap; use itertools::Itertools; From 95305759ebd794674bf427722ffd7a48a90e1e8a Mon Sep 17 00:00:00 2001 From: Bas Schoenmaeckers Date: Wed, 20 May 2026 15:36:30 +0200 Subject: [PATCH 3/3] Move conversion impl to c-api --- crates/capi/src/abstract_.rs | 26 +++++++++++++++++++---- crates/vm/src/function/argument.rs | 33 ++---------------------------- 2 files changed, 24 insertions(+), 35 deletions(-) diff --git a/crates/capi/src/abstract_.rs b/crates/capi/src/abstract_.rs index ad31eb31566..3c109cd1ed5 100644 --- a/crates/capi/src/abstract_.rs +++ b/crates/capi/src/abstract_.rs @@ -2,11 +2,29 @@ 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}; -use rustpython_vm::{AsObject, PyObjectRef}; +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, @@ -15,10 +33,10 @@ pub unsafe extern "C" fn PyObject_Call( ) -> *mut PyObject { with_vm(|vm| { let callable = unsafe { &*callable }; - let args = unsafe { &*args }.try_downcast_ref::(vm)?; + let args = tuple_to_args(unsafe { &*args }.try_downcast_ref::(vm)?); let kwargs: Option = unsafe { kwargs.as_ref() } - .map(|kwargs| kwargs.try_downcast_ref::(vm)?.try_into()) + .map(|kwargs| dict_to_kwargs(vm, kwargs.try_downcast_ref::(vm)?)) .transpose()?; callable.call_with_args(FuncArgs::new(args, kwargs.unwrap_or_default()), vm) diff --git a/crates/vm/src/function/argument.rs b/crates/vm/src/function/argument.rs index 0b5955122ac..40408531cbe 100644 --- a/crates/vm/src/function/argument.rs +++ b/crates/vm/src/function/argument.rs @@ -1,6 +1,6 @@ use crate::{ - AsObject, Py, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, - builtins::{PyBaseExceptionRef, PyDict, PyStr, PyTuple, PyTupleRef, PyTypeRef}, + AsObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject, VirtualMachine, + builtins::{PyBaseExceptionRef, PyTupleRef, PyTypeRef}, convert::ToPyObject, object::{Traverse, TraverseFn}, }; @@ -438,29 +438,6 @@ impl FromIterator<(String, T)> for KwArgs { } } -impl TryFrom<&Py> for KwArgs { - type Error = PyBaseExceptionRef; - - fn try_from(kwargs: &Py) -> Result { - kwargs - .items_vec() - .into_iter() - .map(|(key, value)| { - let key = key - .downcast_ref::() - .map(|s| s.to_string()) - .ok_or_else(|| { - crate::vm::thread::with_current_vm(|vm| { - vm.new_type_error("keywords must be strings") - }) - })?; - Ok((key, value)) - }) - .collect::, _>>() - .map(Self) - } -} - impl Default for KwArgs { fn default() -> Self { Self(IndexMap::new()) @@ -531,12 +508,6 @@ impl From> for PosArgs { } } -impl From<&Py> for PosArgs { - fn from(args: &Py) -> Self { - Self(args.iter().cloned().collect()) - } -} - impl From<()> for PosArgs { fn from(_args: ()) -> Self { Self(Vec::new())