Skip to content

Commit e90dadb

Browse files
committed
Merge PR pybind#5948: vectorcall
2 parents 2c9191e + e09c222 commit e90dadb

File tree

4 files changed

+345
-222
lines changed

4 files changed

+345
-222
lines changed

include/pybind11/cast.h

Lines changed: 108 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -2078,8 +2078,7 @@ using is_pos_only = std::is_same<intrinsic_t<T>, pos_only>;
20782078
// forward declaration (definition in attr.h)
20792079
struct function_record;
20802080

2081-
/// (Inline size chosen mostly arbitrarily; 6 should pad function_call out to two cache lines
2082-
/// (16 pointers) in size.)
2081+
/// Inline size chosen mostly arbitrarily.
20832082
constexpr std::size_t arg_vector_small_size = 6;
20842083

20852084
/// Internal data associated with a single function call
@@ -2191,94 +2190,130 @@ class argument_loader {
21912190
std::tuple<make_caster<Args>...> argcasters;
21922191
};
21932192

2194-
/// Helper class which collects only positional arguments for a Python function call.
2195-
/// A fancier version below can collect any argument, but this one is optimal for simple calls.
2193+
// [workaround(intel)] Separate function required here
2194+
// We need to put this into a separate function because the Intel compiler
2195+
// fails to compile enable_if_t<!all_of<is_positional<Args>...>::value>
2196+
// (tested with ICC 2021.1 Beta 20200827).
2197+
template <typename... Args>
2198+
constexpr bool args_has_keyword_or_ds() {
2199+
return any_of<is_keyword_or_ds<Args>...>::value;
2200+
}
2201+
2202+
/// Helper class which collects positional, keyword, * and ** arguments for a Python function call
21962203
template <return_value_policy policy>
2197-
class simple_collector {
2204+
class unpacking_collector {
21982205
public:
21992206
template <typename... Ts>
2200-
explicit simple_collector(Ts &&...values)
2201-
: m_args(pybind11::make_tuple<policy>(std::forward<Ts>(values)...)) {}
2202-
2203-
const tuple &args() const & { return m_args; }
2204-
dict kwargs() const { return {}; }
2207+
explicit unpacking_collector(Ts &&...values)
2208+
: m_names(reinterpret_steal<tuple>(
2209+
handle())) // initialize to null to avoid useless allocation of 0-length tuple
2210+
{
2211+
/*
2212+
Python can sometimes utilize an extra space before the arguments to prepend `self`.
2213+
This is important enough that there is a special flag for it:
2214+
PY_VECTORCALL_ARGUMENTS_OFFSET.
2215+
All we have to do is allocate an extra space at the beginning of this array, and set the
2216+
flag. Note that the extra space is not passed directly in to vectorcall.
2217+
*/
2218+
m_args.reserve(sizeof...(values) + 1);
2219+
m_args.push_back_null();
2220+
2221+
if (args_has_keyword_or_ds<Ts...>()) {
2222+
list names_list;
2223+
2224+
// collect_arguments guarantees this can't be constructed with kwargs before the last
2225+
// positional so we don't need to worry about Ts... being in anything but normal python
2226+
// order.
2227+
using expander = int[];
2228+
(void) expander{0, (process(names_list, std::forward<Ts>(values)), 0)...};
2229+
2230+
m_names = reinterpret_steal<tuple>(PyList_AsTuple(names_list.ptr()));
2231+
} else {
2232+
auto not_used
2233+
= reinterpret_steal<list>(handle()); // initialize as null (to avoid an allocation)
22052234

2206-
tuple args() && { return std::move(m_args); }
2235+
using expander = int[];
2236+
(void) expander{0, (process(not_used, std::forward<Ts>(values)), 0)...};
2237+
}
2238+
}
22072239

22082240
/// Call a Python function and pass the collected arguments
22092241
object call(PyObject *ptr) const {
2210-
PyObject *result = PyObject_CallObject(ptr, m_args.ptr());
2242+
size_t nargs = m_args.size() - 1; // -1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
2243+
if (m_names) {
2244+
nargs -= m_names.size();
2245+
}
2246+
PyObject *result = _PyObject_Vectorcall(
2247+
ptr, m_args.data() + 1, nargs | PY_VECTORCALL_ARGUMENTS_OFFSET, m_names.ptr());
22112248
if (!result) {
22122249
throw error_already_set();
22132250
}
22142251
return reinterpret_steal<object>(result);
22152252
}
22162253

2217-
private:
2218-
tuple m_args;
2219-
};
2220-
2221-
/// Helper class which collects positional, keyword, * and ** arguments for a Python function call
2222-
template <return_value_policy policy>
2223-
class unpacking_collector {
2224-
public:
2225-
template <typename... Ts>
2226-
explicit unpacking_collector(Ts &&...values) {
2227-
// Tuples aren't (easily) resizable so a list is needed for collection,
2228-
// but the actual function call strictly requires a tuple.
2229-
auto args_list = list();
2230-
using expander = int[];
2231-
(void) expander{0, (process(args_list, std::forward<Ts>(values)), 0)...};
2232-
2233-
m_args = std::move(args_list);
2254+
tuple args() const {
2255+
size_t nargs = m_args.size() - 1; // -1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
2256+
if (m_names) {
2257+
nargs -= m_names.size();
2258+
}
2259+
tuple val(nargs);
2260+
for (size_t i = 0; i < nargs; ++i) {
2261+
// +1 for PY_VECTORCALL_ARGUMENTS_OFFSET (see ctor)
2262+
val[i] = reinterpret_borrow<object>(m_args[i + 1]);
2263+
}
2264+
return val;
22342265
}
22352266

2236-
const tuple &args() const & { return m_args; }
2237-
const dict &kwargs() const & { return m_kwargs; }
2238-
2239-
tuple args() && { return std::move(m_args); }
2240-
dict kwargs() && { return std::move(m_kwargs); }
2241-
2242-
/// Call a Python function and pass the collected arguments
2243-
object call(PyObject *ptr) const {
2244-
PyObject *result = PyObject_Call(ptr, m_args.ptr(), m_kwargs.ptr());
2245-
if (!result) {
2246-
throw error_already_set();
2267+
dict kwargs() const {
2268+
dict val;
2269+
if (m_names) {
2270+
size_t offset = m_args.size() - m_names.size();
2271+
for (size_t i = 0; i < m_names.size(); ++i, ++offset) {
2272+
val[m_names[i]] = reinterpret_borrow<object>(m_args[offset]);
2273+
}
22472274
}
2248-
return reinterpret_steal<object>(result);
2275+
return val;
22492276
}
22502277

22512278
private:
2279+
// normal argument, possibly needing conversion
22522280
template <typename T>
2253-
void process(list &args_list, T &&x) {
2254-
auto o = reinterpret_steal<object>(
2255-
detail::make_caster<T>::cast(std::forward<T>(x), policy, {}));
2256-
if (!o) {
2281+
void process(list & /*names_list*/, T &&x) {
2282+
handle h = detail::make_caster<T>::cast(std::forward<T>(x), policy, {});
2283+
if (!h) {
22572284
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
2258-
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()));
2285+
throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size() - 1));
22592286
#else
2260-
throw cast_error_unable_to_convert_call_arg(std::to_string(args_list.size()),
2287+
throw cast_error_unable_to_convert_call_arg(std::to_string(m_args.size() - 1),
22612288
type_id<T>());
22622289
#endif
22632290
}
2264-
args_list.append(std::move(o));
2291+
m_args.push_back_steal(h.ptr()); // cast returns a new reference
22652292
}
22662293

2267-
void process(list &args_list, detail::args_proxy ap) {
2294+
// * unpacking
2295+
void process(list & /*names_list*/, detail::args_proxy ap) {
2296+
if (!ap) {
2297+
return;
2298+
}
22682299
for (auto a : ap) {
2269-
args_list.append(a);
2300+
m_args.push_back_borrow(a.ptr());
22702301
}
22712302
}
22722303

2273-
void process(list & /*args_list*/, arg_v a) {
2304+
// named argument
2305+
// NOLINTNEXTLINE(performance-unnecessary-value-param)
2306+
void process(list &names_list, arg_v a) {
2307+
assert(names_list);
22742308
if (!a.name) {
22752309
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
22762310
nameless_argument_error();
22772311
#else
22782312
nameless_argument_error(a.type);
22792313
#endif
22802314
}
2281-
if (m_kwargs.contains(a.name)) {
2315+
auto name = str(a.name);
2316+
if (names_list.contains(name)) {
22822317
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
22832318
multiple_values_error();
22842319
#else
@@ -2292,22 +2327,27 @@ class unpacking_collector {
22922327
throw cast_error_unable_to_convert_call_arg(a.name, a.type);
22932328
#endif
22942329
}
2295-
m_kwargs[a.name] = std::move(a.value);
2330+
names_list.append(std::move(name));
2331+
m_args.push_back_borrow(a.value.ptr());
22962332
}
22972333

2298-
void process(list & /*args_list*/, detail::kwargs_proxy kp) {
2334+
// ** unpacking
2335+
void process(list &names_list, detail::kwargs_proxy kp) {
22992336
if (!kp) {
23002337
return;
23012338
}
2302-
for (auto k : reinterpret_borrow<dict>(kp)) {
2303-
if (m_kwargs.contains(k.first)) {
2339+
assert(names_list);
2340+
for (auto &&k : reinterpret_borrow<dict>(kp)) {
2341+
auto name = str(k.first);
2342+
if (names_list.contains(name)) {
23042343
#if !defined(PYBIND11_DETAILED_ERROR_MESSAGES)
23052344
multiple_values_error();
23062345
#else
2307-
multiple_values_error(str(k.first));
2346+
multiple_values_error(name);
23082347
#endif
23092348
}
2310-
m_kwargs[k.first] = k.second;
2349+
names_list.append(std::move(name));
2350+
m_args.push_back_borrow(k.second.ptr());
23112351
}
23122352
}
23132353

@@ -2333,39 +2373,20 @@ class unpacking_collector {
23332373
}
23342374

23352375
private:
2336-
tuple m_args;
2337-
dict m_kwargs;
2376+
ref_small_vector<arg_vector_small_size> m_args;
2377+
tuple m_names;
23382378
};
23392379

2340-
// [workaround(intel)] Separate function required here
2341-
// We need to put this into a separate function because the Intel compiler
2342-
// fails to compile enable_if_t<!all_of<is_positional<Args>...>::value>
2343-
// (tested with ICC 2021.1 Beta 20200827).
2344-
template <typename... Args>
2345-
constexpr bool args_are_all_positional() {
2346-
return all_of<is_positional<Args>...>::value;
2347-
}
2348-
2349-
/// Collect only positional arguments for a Python function call
2350-
template <return_value_policy policy,
2351-
typename... Args,
2352-
typename = enable_if_t<args_are_all_positional<Args...>()>>
2353-
simple_collector<policy> collect_arguments(Args &&...args) {
2354-
return simple_collector<policy>(std::forward<Args>(args)...);
2355-
}
2356-
2357-
/// Collect all arguments, including keywords and unpacking (only instantiated when needed)
2358-
template <return_value_policy policy,
2359-
typename... Args,
2360-
typename = enable_if_t<!args_are_all_positional<Args...>()>>
2380+
/// Collect all arguments, including keywords and unpacking
2381+
template <return_value_policy policy, typename... Args>
23612382
unpacking_collector<policy> collect_arguments(Args &&...args) {
23622383
// Following argument order rules for generalized unpacking according to PEP 448
2363-
static_assert(constexpr_last<is_positional, Args...>()
2364-
< constexpr_first<is_keyword_or_ds, Args...>()
2365-
&& constexpr_last<is_s_unpacking, Args...>()
2366-
< constexpr_first<is_ds_unpacking, Args...>(),
2367-
"Invalid function call: positional args must precede keywords and ** unpacking; "
2368-
"* unpacking must precede ** unpacking");
2384+
static_assert(
2385+
constexpr_last<is_positional, Args...>() < constexpr_first<is_keyword_or_ds, Args...>(),
2386+
"Invalid function call: positional args must precede keywords and */** unpacking;");
2387+
static_assert(constexpr_last<is_s_unpacking, Args...>()
2388+
< constexpr_first<is_ds_unpacking, Args...>(),
2389+
"Invalid function call: * unpacking must precede ** unpacking");
23692390
return unpacking_collector<policy>(std::forward<Args>(args)...);
23702391
}
23712392

0 commit comments

Comments
 (0)