Skip to content

Commit 37cf37b

Browse files
committed
Pack all function call data into a single struct
This cleans up the previous commit slightly by further reducing the function call arguments to a single struct (containing the function_record, arguments vector, and parent). Although this doesn't currently change anything, it does allow for future functionality to have a place for precalls to store temporary objects that need to be destroyed after a function call (whether or not the call succeeds). As a concrete example, with this change pybind#625 could be easily implemented (I think) by adding a std::unique_ptr<gil_scoped_release> member to the `function_call` struct with a precall that actually constructs it. Without this, the precall can't do that: the postcall won't be invoked if the call throws an exception. This doesn't seems to affect the .so size noticeably (either way).
1 parent 9755491 commit 37cf37b

File tree

3 files changed

+66
-44
lines changed

3 files changed

+66
-44
lines changed

include/pybind11/attr.h

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@ struct undefined_t;
6464
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
6565
template <typename... Args> struct init;
6666
template <typename... Args> struct init_alias;
67-
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_arguments args, handle ret);
67+
struct function_call;
68+
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
6869

6970
/// Internal data structure which holds metadata about a keyword argument
7071
struct argument_record {
@@ -95,7 +96,7 @@ struct function_record {
9596
std::vector<argument_record> args;
9697

9798
/// Pointer to lambda function which converts arguments and performs the actual call
98-
handle (*impl) (function_record *, function_arguments, handle) = nullptr;
99+
handle (*impl) (function_call &) = nullptr;
99100

100101
/// Storage for the wrapped function pointer and captured data, if any
101102
void *data[3] = { };
@@ -204,6 +205,22 @@ struct type_record {
204205
}
205206
};
206207

208+
/// Internal data associated with a single function call
209+
struct function_call {
210+
function_call(const function_record &f, handle p) : func{f}, parent{p} {
211+
args.reserve(f.nargs);
212+
}
213+
214+
/// The function data:
215+
const function_record &func;
216+
217+
/// Arguments passed to the function:
218+
std::vector<handle> args;
219+
220+
/// The parent, if any
221+
handle parent;
222+
};
223+
207224
/**
208225
* Partial template specializations to process custom attributes provided to
209226
* cpp_function_ and class_. These are either used to initialize the respective
@@ -216,8 +233,8 @@ template <typename T> struct process_attribute_default {
216233
/// Default implementation: do nothing
217234
static void init(const T &, function_record *) { }
218235
static void init(const T &, type_record *) { }
219-
static void precall(function_arguments) { }
220-
static void postcall(function_arguments, handle) { }
236+
static void precall(function_call &) { }
237+
static void postcall(function_call &, handle) { }
221238
};
222239

223240
/// Process an attribute specifying the function's name
@@ -345,13 +362,13 @@ struct process_attribute<arithmetic> : process_attribute_default<arithmetic> {};
345362
*/
346363
template <size_t Nurse, size_t Patient> struct process_attribute<keep_alive<Nurse, Patient>> : public process_attribute_default<keep_alive<Nurse, Patient>> {
347364
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
348-
static void precall(function_arguments args) { keep_alive_impl(Nurse, Patient, args, handle()); }
365+
static void precall(function_call &call) { keep_alive_impl(Nurse, Patient, call, handle()); }
349366
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N != 0 && P != 0, int> = 0>
350-
static void postcall(function_arguments, handle) { }
367+
static void postcall(function_call &, handle) { }
351368
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
352-
static void precall(function_arguments) { }
369+
static void precall(function_call &) { }
353370
template <size_t N = Nurse, size_t P = Patient, enable_if_t<N == 0 || P == 0, int> = 0>
354-
static void postcall(function_arguments args, handle ret) { keep_alive_impl(Nurse, Patient, args, ret); }
371+
static void postcall(function_call &call, handle ret) { keep_alive_impl(Nurse, Patient, call, ret); }
355372
};
356373

357374
/// Recursively iterate over variadic template arguments
@@ -364,12 +381,12 @@ template <typename... Args> struct process_attributes {
364381
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::init(args, r), 0) ... };
365382
ignore_unused(unused);
366383
}
367-
static void precall(function_arguments fn_args) {
368-
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::precall(fn_args), 0) ... };
384+
static void precall(function_call &call) {
385+
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::precall(call), 0) ... };
369386
ignore_unused(unused);
370387
}
371-
static void postcall(function_arguments fn_args, handle fn_ret) {
372-
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::postcall(fn_args, fn_ret), 0) ... };
388+
static void postcall(function_call &call, handle fn_ret) {
389+
int unused[] = { 0, (process_attribute<typename std::decay<Args>::type>::postcall(call, fn_ret), 0) ... };
373390
ignore_unused(unused);
374391
}
375392
};

include/pybind11/cast.h

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1220,12 +1220,11 @@ NAMESPACE_BEGIN(detail)
12201220
// forward declaration
12211221
struct function_record;
12221222

1223-
using function_arguments = const std::vector<handle> &;
1224-
12251223
/// Helper class which loads arguments for C++ functions called from Python
12261224
template <typename... Args>
12271225
class argument_loader {
12281226
using indices = make_index_sequence<sizeof...(Args)>;
1227+
using function_arguments = const std::vector<handle> &;
12291228

12301229
template <typename Arg> using argument_is_args = std::is_same<intrinsic_t<Arg>, args>;
12311230
template <typename Arg> using argument_is_kwargs = std::is_same<intrinsic_t<Arg>, kwargs>;

include/pybind11/pybind11.h

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -118,31 +118,31 @@ class cpp_function : public function {
118118
"The number of named arguments does not match the function signature");
119119

120120
/* Dispatch code which converts function arguments and performs the actual function call */
121-
rec->impl = [](detail::function_record *rec, detail::function_arguments args, handle parent) -> handle {
121+
rec->impl = [](detail::function_call &call) -> handle {
122122
cast_in args_converter;
123123

124124
/* Try to cast the function arguments into the C++ domain */
125-
if (!args_converter.load_args(args))
125+
if (!args_converter.load_args(call.args))
126126
return PYBIND11_TRY_NEXT_OVERLOAD;
127127

128128
/* Invoke call policy pre-call hook */
129-
detail::process_attributes<Extra...>::precall(args);
129+
detail::process_attributes<Extra...>::precall(call);
130130

131131
/* Get a pointer to the capture object */
132-
capture *cap = (capture *) (sizeof(capture) <= sizeof(rec->data)
133-
? &rec->data : rec->data[0]);
132+
capture *cap = (capture *) (sizeof(capture) <= sizeof(call.func.data)
133+
? &call.func.data : call.func.data[0]);
134134

135135
/* Override policy for rvalues -- always move */
136136
constexpr auto is_rvalue = !std::is_pointer<Return>::value
137137
&& !std::is_lvalue_reference<Return>::value;
138-
const auto policy = is_rvalue ? return_value_policy::move : rec->policy;
138+
const auto policy = is_rvalue ? return_value_policy::move : call.func.policy;
139139

140140
/* Perform the function call */
141141
handle result = cast_out::cast(args_converter.template call<Return>(cap->f),
142-
policy, parent);
142+
policy, call.parent);
143143

144144
/* Invoke call policy post-call hook */
145-
detail::process_attributes<Extra...>::postcall(args, result);
145+
detail::process_attributes<Extra...>::postcall(call, result);
146146

147147
return result;
148148
};
@@ -381,9 +381,11 @@ class cpp_function : public function {
381381

382382
/// Main dispatch logic for calls to functions bound using pybind11
383383
static PyObject *dispatcher(PyObject *self, PyObject *args_in, PyObject *kwargs_in) {
384+
using namespace detail;
385+
384386
/* Iterator over the list of potentially admissible overloads */
385-
detail::function_record *overloads = (detail::function_record *) PyCapsule_GetPointer(self, nullptr),
386-
*it = overloads;
387+
function_record *overloads = (function_record *) PyCapsule_GetPointer(self, nullptr),
388+
*it = overloads;
387389

388390
/* Need to know how many arguments + keyword arguments there are to pick the right overload */
389391
const size_t n_args_in = (size_t) PyTuple_GET_SIZE(args_in);
@@ -411,18 +413,18 @@ class cpp_function : public function {
411413
result other than PYBIND11_TRY_NEXT_OVERLOAD.
412414
*/
413415

414-
size_t pos_args = it->nargs; // Number of positional arguments that we need
415-
if (it->has_args) --pos_args; // (but don't count py::args
416-
if (it->has_kwargs) --pos_args; // or py::kwargs)
416+
function_record &func = *it;
417+
size_t pos_args = func.nargs; // Number of positional arguments that we need
418+
if (func.has_args) --pos_args; // (but don't count py::args
419+
if (func.has_kwargs) --pos_args; // or py::kwargs)
417420

418-
if (!it->has_args && n_args_in > pos_args)
421+
if (!func.has_args && n_args_in > pos_args)
419422
continue; // Too many arguments for this overload
420423

421-
if (n_args_in < pos_args && it->args.size() < pos_args)
424+
if (n_args_in < pos_args && func.args.size() < pos_args)
422425
continue; // Not enough arguments given, and not enough defaults to fill in the blanks
423426

424-
std::vector<handle> pass_args;
425-
pass_args.reserve(it->nargs);
427+
function_call call(func, parent);
426428

427429
size_t args_to_copy = std::min(pos_args, n_args_in);
428430
size_t args_copied = 0;
@@ -440,7 +442,7 @@ class cpp_function : public function {
440442
std::string(it->args[args_copied].name) + "'");
441443
}
442444

443-
pass_args.push_back(PyTuple_GET_ITEM(args_in, args_copied));
445+
call.args.push_back(PyTuple_GET_ITEM(args_in, args_copied));
444446
}
445447

446448
// We'll need to copy this if we steal some kwargs for defaults
@@ -470,7 +472,7 @@ class cpp_function : public function {
470472
}
471473

472474
if (value)
473-
pass_args.push_back(value);
475+
call.args.push_back(value);
474476
else
475477
break;
476478
}
@@ -502,22 +504,26 @@ class cpp_function : public function {
502504
extra_args[i] = item.inc_ref().ptr();
503505
}
504506
}
505-
pass_args.push_back(extra_args);
507+
call.args.push_back(extra_args);
506508
}
507509

508510
// 4b. If we have a py::kwargs, pass on any remaining kwargs
509511
if (it->has_kwargs) {
510512
if (!kwargs.ptr())
511513
kwargs = dict(); // If we didn't get one, send an empty one
512-
pass_args.push_back(kwargs);
514+
call.args.push_back(kwargs);
513515
}
514516

515-
// 5. Put everything in a big tuple. Not technically step 5, we've been building it
516-
// in `pass_args` all along.
517+
// 5. Put everything in a vector. Not technically step 5, we've been building it
518+
// in `call.args` all along.
519+
#if !defined(NDEBUG)
520+
if (call.args.size() != call.func.nargs)
521+
pybind11_fail("Internal error: function call dispatcher inserted wrong number of arguments!");
522+
#endif
517523

518524
// 6. Call the function.
519525
try {
520-
result = it->impl(it, pass_args, parent);
526+
result = it->impl(call);
521527
} catch (reference_cast_error &) {
522528
result = PYBIND11_TRY_NEXT_OVERLOAD;
523529
}
@@ -541,7 +547,7 @@ class cpp_function : public function {
541547
- delegate translation to the next translator by throwing a new type of exception. */
542548

543549
auto last_exception = std::current_exception();
544-
auto &registered_exception_translators = pybind11::detail::get_internals().registered_exception_translators;
550+
auto &registered_exception_translators = get_internals().registered_exception_translators;
545551
for (auto& translator : registered_exception_translators) {
546552
try {
547553
translator(last_exception);
@@ -564,7 +570,7 @@ class cpp_function : public function {
564570
" arguments. The following argument types are supported:\n";
565571

566572
int ctr = 0;
567-
for (detail::function_record *it2 = overloads; it2 != nullptr; it2 = it2->next) {
573+
for (function_record *it2 = overloads; it2 != nullptr; it2 = it2->next) {
568574
msg += " "+ std::to_string(++ctr) + ". ";
569575

570576
bool wrote_sig = false;
@@ -609,7 +615,7 @@ class cpp_function : public function {
609615
if (overloads->is_constructor) {
610616
/* When a constructor ran successfully, the corresponding
611617
holder type (e.g. std::unique_ptr) must still be initialized. */
612-
auto tinfo = detail::get_type_info(Py_TYPE(parent.ptr()));
618+
auto tinfo = get_type_info(Py_TYPE(parent.ptr()));
613619
tinfo->init_holder(parent.ptr(), nullptr);
614620
}
615621
return result.ptr();
@@ -1477,10 +1483,10 @@ inline void keep_alive_impl(handle nurse, handle patient) {
14771483
(void) wr.release();
14781484
}
14791485

1480-
PYBIND11_NOINLINE inline void keep_alive_impl(size_t Nurse, size_t Patient, function_arguments args, handle ret) {
1486+
PYBIND11_NOINLINE inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret) {
14811487
keep_alive_impl(
1482-
Nurse == 0 ? ret : Nurse <= args.size() ? args[Nurse - 1] : handle(),
1483-
Patient == 0 ? ret : Patient <= args.size() ? args[Patient - 1] : handle()
1488+
Nurse == 0 ? ret : Nurse <= call.args.size() ? call.args[Nurse - 1] : handle(),
1489+
Patient == 0 ? ret : Patient <= call.args.size() ? call.args[Patient - 1] : handle()
14841490
);
14851491
}
14861492

0 commit comments

Comments
 (0)