From 418a034195c5c8b517e7404686555c097efe7a4b Mon Sep 17 00:00:00 2001 From: b-pass Date: Tue, 23 Dec 2025 23:17:31 -0500 Subject: [PATCH] Attempt to collect args into array for vectorcall --- include/pybind11/cast.h | 120 ++++++++++++++++++++++++++++------------ 1 file changed, 86 insertions(+), 34 deletions(-) diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index 310b77b34..5f258411c 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -2198,24 +2198,50 @@ class simple_collector { public: template explicit simple_collector(Ts &&...values) - : m_args(pybind11::make_tuple(std::forward(values)...)) {} + { + m_args.reserve(sizeof...(values)+1); + m_args.push_back(object()); // dummy first argument so we can use PY_VECTORCALL_ARGUMENTS_OFFSET - const tuple &args() const & { return m_args; } - dict kwargs() const { return {}; } - - tuple args() && { return std::move(m_args); } + using expander = int[]; + (void) expander{0, (process(std::forward(values)), 0)...}; + } /// Call a Python function and pass the collected arguments object call(PyObject *ptr) const { - PyObject *result = PyObject_CallObject(ptr, m_args.ptr()); + static_assert(sizeof(m_args[0]) == sizeof(PyObject*), "pybind11::object must be castable to PyObject* for this to work"); + PyObject *result = PyObject_Vectorcall(ptr, reinterpret_cast(m_args.data()+1), (m_args.size()-1)|PY_VECTORCALL_ARGUMENTS_OFFSET, nullptr); if (!result) { throw error_already_set(); } return reinterpret_steal(result); } + tuple args() const { + tuple val(m_args.size() - 1); + for (size_t i = 1; i < m_args.size(); ++i) + val[i-1] = m_args[i]; + return val; + } + + dict kwargs() const { return {}; } + private: - tuple m_args; + template + void process(T &&x) { + auto o = reinterpret_steal( + detail::make_caster::cast(std::forward(x), policy, {})); + if (!o) { +#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) + throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size())); +#else + throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size()), + type_id()); +#endif + } + m_args.push_back(std::move(o)); + } + + small_vector m_args; }; /// Helper class which collects positional, keyword, * and ** arguments for a Python function call @@ -2224,53 +2250,73 @@ class unpacking_collector { public: template explicit unpacking_collector(Ts &&...values) { - // Tuples aren't (easily) resizable so a list is needed for collection, - // but the actual function call strictly requires a tuple. - auto args_list = list(); + auto names_list = list(); + m_args.reserve(sizeof...(values) + 1); + m_args.push_back(object()); // dummy first argument so we can use PY_VECTORCALL_ARGUMENTS_OFFSET + using expander = int[]; - (void) expander{0, (process(args_list, std::forward(values)), 0)...}; + (void) expander{0, (process(names_list, std::forward(values)), 0)...}; - m_args = std::move(args_list); + m_names = tuple(names_list.size()); + for (size_t i = 0; i < names_list.size(); ++i) + m_names[i] = std::move(names_list[i]); } - const tuple &args() const & { return m_args; } - const dict &kwargs() const & { return m_kwargs; } - - tuple args() && { return std::move(m_args); } - dict kwargs() && { return std::move(m_kwargs); } - /// Call a Python function and pass the collected arguments object call(PyObject *ptr) const { - PyObject *result = PyObject_Call(ptr, m_args.ptr(), m_kwargs.ptr()); + static_assert(sizeof(m_args[0]) == sizeof(PyObject*), "pybind11::object must be castable to PyObject* for this to work"); + PyObject *result = PyObject_Vectorcall(ptr, reinterpret_cast(m_args.data()+1), (m_args.size()-1)|PY_VECTORCALL_ARGUMENTS_OFFSET, m_names.ptr()); if (!result) { throw error_already_set(); } return reinterpret_steal(result); } + tuple args() const { + // -1 to account for PY_VECTORCALL_ARGUMENTS_OFFSET + size_t count = m_args.size() - 1 - m_names.size(); + tuple val(count); + for (size_t i = 0; i < count; ++i) { + // +1 to account for PY_VECTORCALL_ARGUMENTS_OFFSET + val[i] = m_args[i + 1]; + } + return val; + } + + dict kwargs() const { + size_t offset = m_args.size() - m_names.size(); + dict val; + for (size_t i = 0; i < m_names.size(); ++i, ++offset) { + val[m_names[i]] = m_args[offset]; + } + return val; + } + private: template - void process(list &args_list, T &&x) { + void process(list &/*names_list*/, T &&x) { auto o = reinterpret_steal( detail::make_caster::cast(std::forward(x), policy, {})); if (!o) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) - throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size())); + throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size())); #else - throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()), + throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size()), type_id()); #endif } - args_list.append(std::move(o)); + m_args.push_back(std::move(o)); + // collect_arguments() below guarantees that no keyword args have preceeded this arg. } - void process(list &args_list, detail::args_proxy ap) { + void process(list &/*names_list*/, detail::args_proxy ap) { for (auto a : ap) { - args_list.append(a); + m_args.push_back(reinterpret_borrow(a)); } + // collect_arguments() below guarantees that no keyword args have preceeded this arg. } - void process(list & /*args_list*/, arg_v a) { + void process(list &names_list, arg_v a) { if (!a.name) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) nameless_argument_error(); @@ -2278,7 +2324,8 @@ private: nameless_argument_error(a.type); #endif } - if (m_kwargs.contains(a.name)) { + auto name = str(a.name); + if (names_list.contains(name)) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) multiple_values_error(); #else @@ -2292,23 +2339,28 @@ private: throw cast_error_unable_to_convert_call_arg(a.name, a.type); #endif } - m_kwargs[a.name] = std::move(a.value); + names_list.append(std::move(name)); + m_args.push_back(std::move(a.value)); + // collect_arguments() below guarantees that no positional args will come after this arg } - void process(list & /*args_list*/, detail::kwargs_proxy kp) { + void process(list &names_list, detail::kwargs_proxy kp) { if (!kp) { return; } for (auto k : reinterpret_borrow(kp)) { - if (m_kwargs.contains(k.first)) { + auto name = str(k.first); + if (names_list.contains(name)) { #if !defined(PYBIND11_DETAILED_ERROR_MESSAGES) multiple_values_error(); #else - multiple_values_error(str(k.first)); + multiple_values_error(name); #endif } - m_kwargs[k.first] = k.second; + names_list.append(std::move(name)); + m_args.push_back(reinterpret_borrow(k.second)); } + // collect_arguments() below guarantees that no positional args will come after these args } [[noreturn]] static void nameless_argument_error() { @@ -2333,8 +2385,8 @@ private: } private: - tuple m_args; - dict m_kwargs; + small_vector m_args; + tuple m_names; }; // [workaround(intel)] Separate function required here