Skip to content

Commit 146f4e1

Browse files
committed
Reduce binary size overhead of new-style constructors
The lookup of the `self` type and value pointer are moved out of template code and into `dispatcher`. This brings down the binary size of constructors back to the level of the old placement-new approach. (It also avoids a second lookup for `init_instance`.) With this implementation, mixing old- and new-style constructors in the same overload set may result in some runtime overhead for temporary allocations/deallocations, but this should be fine as old style constructors are phased out.
1 parent 1fb9df6 commit 146f4e1

File tree

4 files changed

+107
-100
lines changed

4 files changed

+107
-100
lines changed

include/pybind11/attr.h

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,8 @@ struct argument_record {
133133
/// Internal data structure which holds metadata about a bound function (signature, overloads, etc.)
134134
struct function_record {
135135
function_record()
136-
: is_constructor(false), is_stateless(false), is_operator(false),
137-
has_args(false), has_kwargs(false), is_method(false) { }
136+
: is_constructor(false), is_new_style_constructor(false), is_stateless(false),
137+
is_operator(false), has_args(false), has_kwargs(false), is_method(false) { }
138138

139139
/// Function name
140140
char *name = nullptr; /* why no C++ strings? They generate heavier code.. */
@@ -163,6 +163,9 @@ struct function_record {
163163
/// True if name == '__init__'
164164
bool is_constructor : 1;
165165

166+
/// True if this is a new-style `__init__` defined in `detail/init.h`
167+
bool is_new_style_constructor : 1;
168+
166169
/// True if this is a stateless function pointer
167170
bool is_stateless : 1;
168171

@@ -281,6 +284,9 @@ inline function_call::function_call(function_record &f, handle p) :
281284
args_convert.reserve(f.nargs);
282285
}
283286

287+
/// Tag for a new-style `__init__` defined in `detail/init.h`
288+
struct is_new_style_constructor { };
289+
284290
/**
285291
* Partial template specializations to process custom attributes provided to
286292
* cpp_function_ and class_. These are either used to initialize the respective
@@ -339,6 +345,10 @@ template <> struct process_attribute<is_operator> : process_attribute_default<is
339345
static void init(const is_operator &, function_record *r) { r->is_operator = true; }
340346
};
341347

348+
template <> struct process_attribute<is_new_style_constructor> : process_attribute_default<is_new_style_constructor> {
349+
static void init(const is_new_style_constructor &, function_record *r) { r->is_new_style_constructor = true; }
350+
};
351+
342352
/// Process a keyword argument attribute (*without* a default value)
343353
template <> struct process_attribute<arg> : process_attribute_default<arg> {
344354
static void init(const arg &a, function_record *r) {

include/pybind11/detail/init.h

Lines changed: 54 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -13,33 +13,29 @@
1313

1414
NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
1515
NAMESPACE_BEGIN(detail)
16-
NAMESPACE_BEGIN(initimpl)
1716

18-
inline void no_nullptr(void *ptr) {
19-
if (!ptr) throw type_error("pybind11::init(): factory function returned nullptr");
20-
}
17+
template <>
18+
class type_caster<value_and_holder> {
19+
public:
20+
bool load(handle h, bool) {
21+
value = reinterpret_cast<value_and_holder *>(h.ptr());
22+
return true;
23+
}
2124

22-
// Makes sure the `value` for the given value_and_holder is not preallocated (e.g. by a previous
23-
// old-style placement new `__init__` that requires a preallocated, uninitialized value). If
24-
// preallocated, deallocate. Returns the (null) value pointer reference ready for allocation.
25-
inline void *&deallocate(value_and_holder &v_h) {
26-
if (v_h) v_h.type->dealloc(v_h);
27-
return v_h.value_ptr();
28-
}
25+
template <typename> using cast_op_type = value_and_holder &;
26+
operator value_and_holder &() { return *value; }
27+
static PYBIND11_DESCR name() { return type_descr(_<value_and_holder>()); }
2928

30-
PYBIND11_NOINLINE inline value_and_holder load_v_h(handle self_, type_info *tinfo) {
31-
if (!self_ || !tinfo)
32-
throw type_error("__init__(self, ...) called with invalid `self` argument");
29+
private:
30+
value_and_holder *value = nullptr;
31+
};
3332

34-
auto *inst = reinterpret_cast<instance *>(self_.ptr());
35-
auto result = inst->get_value_and_holder(tinfo, false);
36-
if (!result.inst)
37-
throw type_error("__init__(self, ...) called with invalid `self` argument");
33+
NAMESPACE_BEGIN(initimpl)
3834

39-
return result;
35+
inline void no_nullptr(void *ptr) {
36+
if (!ptr) throw type_error("pybind11::init(): factory function returned nullptr");
4037
}
4138

42-
4339
// Implementing functions for all forms of py::init<...> and py::init(...)
4440
template <typename Class> using Cpp = typename Class::type;
4541
template <typename Class> using Alias = typename Class::type_alias;
@@ -64,7 +60,7 @@ constexpr bool is_alias(void *) { return false; }
6460
template <typename Class>
6561
void construct_alias_from_cpp(std::true_type /*is_alias_constructible*/,
6662
value_and_holder &v_h, Cpp<Class> &&base) {
67-
deallocate(v_h) = new Alias<Class>(std::move(base));
63+
v_h.value_ptr() = new Alias<Class>(std::move(base));
6864
}
6965
template <typename Class>
7066
[[noreturn]] void construct_alias_from_cpp(std::false_type /*!is_alias_constructible*/,
@@ -98,19 +94,17 @@ void construct(value_and_holder &v_h, Cpp<Class> *ptr, bool need_alias) {
9894
// it was a normal instance, then steal the holder away into a local variable; thus
9995
// the holder and destruction happens when we leave the C++ scope, and the holder
10096
// class gets to handle the destruction however it likes.
101-
deallocate(v_h) = ptr;
97+
v_h.value_ptr() = ptr;
10298
v_h.set_instance_registered(true); // To prevent init_instance from registering it
10399
v_h.type->init_instance(v_h.inst, nullptr); // Set up the holder
104100
Holder<Class> temp_holder(std::move(v_h.holder<Holder<Class>>())); // Steal the holder
105101
v_h.type->dealloc(v_h); // Destroys the moved-out holder remains, resets value ptr to null
106102
v_h.set_instance_registered(false);
107103

108104
construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(*ptr));
109-
}
110-
else {
111-
// Otherwise the type isn't inherited, so we don't need an Alias and can just store the Cpp
112-
// pointer directory:
113-
deallocate(v_h) = ptr;
105+
} else {
106+
// Otherwise the type isn't inherited, so we don't need an Alias
107+
v_h.value_ptr() = ptr;
114108
}
115109
}
116110

@@ -119,7 +113,7 @@ void construct(value_and_holder &v_h, Cpp<Class> *ptr, bool need_alias) {
119113
template <typename Class, enable_if_t<Class::has_alias, int> = 0>
120114
void construct(value_and_holder &v_h, Alias<Class> *alias_ptr, bool) {
121115
no_nullptr(alias_ptr);
122-
deallocate(v_h) = static_cast<Cpp<Class> *>(alias_ptr);
116+
v_h.value_ptr() = static_cast<Cpp<Class> *>(alias_ptr);
123117
}
124118

125119
// Holder return: copy its pointer, and move or copy the returned holder into the new instance's
@@ -133,7 +127,7 @@ void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
133127
throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance "
134128
"is not an alias instance");
135129

136-
deallocate(v_h) = ptr;
130+
v_h.value_ptr() = ptr;
137131
v_h.type->init_instance(v_h.inst, &holder);
138132
}
139133

@@ -148,7 +142,7 @@ void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
148142
if (Class::has_alias && need_alias)
149143
construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result));
150144
else
151-
deallocate(v_h) = new Cpp<Class>(std::move(result));
145+
v_h.value_ptr() = new Cpp<Class>(std::move(result));
152146
}
153147

154148
// return-by-value version 2: returning a value of the alias type itself. We move-construct an
@@ -158,48 +152,38 @@ template <typename Class>
158152
void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
159153
static_assert(std::is_move_constructible<Alias<Class>>::value,
160154
"pybind11::init() return-by-alias-value factory function requires a movable alias class");
161-
deallocate(v_h) = new Alias<Class>(std::move(result));
155+
v_h.value_ptr() = new Alias<Class>(std::move(result));
162156
}
163157

164158
// Implementing class for py::init<...>()
165-
template <typename... Args> struct constructor {
159+
template <typename... Args>
160+
struct constructor {
166161
template <typename Class, typename... Extra, enable_if_t<!Class::has_alias, int> = 0>
167162
static void execute(Class &cl, const Extra&... extra) {
168-
auto *cl_type = get_type_info(typeid(Cpp<Class>));
169-
cl.def("__init__", [cl_type](handle self_, Args... args) {
170-
auto v_h = load_v_h(self_, cl_type);
171-
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
172-
173-
construct<Class>(v_h, new Cpp<Class>(std::forward<Args>(args)...), false);
174-
}, extra...);
163+
cl.def("__init__", [](value_and_holder &v_h, Args... args) {
164+
v_h.value_ptr() = new Cpp<Class>(std::forward<Args>(args)...);
165+
}, is_new_style_constructor(), extra...);
175166
}
176167

177168
template <typename Class, typename... Extra,
178169
enable_if_t<Class::has_alias &&
179170
std::is_constructible<Cpp<Class>, Args...>::value, int> = 0>
180171
static void execute(Class &cl, const Extra&... extra) {
181-
auto *cl_type = get_type_info(typeid(Cpp<Class>));
182-
cl.def("__init__", [cl_type](handle self_, Args... args) {
183-
auto v_h = load_v_h(self_, cl_type);
184-
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
185-
186-
if (Py_TYPE(v_h.inst) == cl_type->type)
187-
construct<Class>(v_h, new Cpp<Class>(std::forward<Args>(args)...), false);
172+
cl.def("__init__", [](value_and_holder &v_h, Args... args) {
173+
if (Py_TYPE(v_h.inst) == v_h.type->type)
174+
v_h.value_ptr() = new Cpp<Class>(std::forward<Args>(args)...);
188175
else
189-
construct<Class>(v_h, new Alias<Class>(std::forward<Args>(args)...), true);
190-
}, extra...);
176+
v_h.value_ptr() = new Alias<Class>(std::forward<Args>(args)...);
177+
}, is_new_style_constructor(), extra...);
191178
}
192179

193180
template <typename Class, typename... Extra,
194181
enable_if_t<Class::has_alias &&
195182
!std::is_constructible<Cpp<Class>, Args...>::value, int> = 0>
196183
static void execute(Class &cl, const Extra&... extra) {
197-
auto *cl_type = get_type_info(typeid(Cpp<Class>));
198-
cl.def("__init__", [cl_type](handle self_, Args... args) {
199-
auto v_h = load_v_h(self_, cl_type);
200-
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
201-
construct<Class>(v_h, new Alias<Class>(std::forward<Args>(args)...), true);
202-
}, extra...);
184+
cl.def("__init__", [](value_and_holder &v_h, Args... args) {
185+
v_h.value_ptr() = new Alias<Class>(std::forward<Args>(args)...);
186+
}, is_new_style_constructor(), extra...);
203187
}
204188
};
205189

@@ -208,17 +192,15 @@ template <typename... Args> struct alias_constructor {
208192
template <typename Class, typename... Extra,
209193
enable_if_t<Class::has_alias && std::is_constructible<Alias<Class>, Args...>::value, int> = 0>
210194
static void execute(Class &cl, const Extra&... extra) {
211-
auto *cl_type = get_type_info(typeid(Cpp<Class>));
212-
cl.def("__init__", [cl_type](handle self_, Args... args) {
213-
auto v_h = load_v_h(self_, cl_type);
214-
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
215-
construct<Class>(v_h, new Alias<Class>(std::forward<Args>(args)...), true);
216-
}, extra...);
195+
cl.def("__init__", [](value_and_holder &v_h, Args... args) {
196+
v_h.value_ptr() = new Alias<Class>(std::forward<Args>(args)...);
197+
}, is_new_style_constructor(), extra...);
217198
}
218199
};
219200

220201
// Implementation class for py::init(Func) and py::init(Func, AliasFunc)
221-
template <typename CFunc, typename AFuncIn, typename... Args> struct factory {
202+
template <typename CFunc, typename AFuncIn, typename... Args>
203+
struct factory {
222204
private:
223205
using CFuncType = typename std::remove_reference<CFunc>::type;
224206
using AFunc = conditional_t<std::is_void<AFuncIn>::value, void_type, AFuncIn>;
@@ -246,21 +228,16 @@ template <typename CFunc, typename AFuncIn, typename... Args> struct factory {
246228
template <typename Class, typename... Extra,
247229
enable_if_t<!Class::has_alias || std::is_void<AFuncIn>::value, int> = 0>
248230
void execute(Class &cl, const Extra&... extra) && {
249-
auto *cl_type = get_type_info(typeid(Cpp<Class>));
250231
#if defined(PYBIND11_CPP14)
251-
cl.def("__init__", [cl_type, func = std::move(class_factory)]
232+
cl.def("__init__", [func = std::move(class_factory)]
252233
#else
253234
CFuncType &func = class_factory;
254-
cl.def("__init__", [cl_type, func]
235+
cl.def("__init__", [func]
255236
#endif
256-
(handle self_, Args... args) {
257-
auto v_h = load_v_h(self_, cl_type);
258-
// If this value is already registered it must mean __init__ is invoked multiple times;
259-
// we really can't support that in C++, so just ignore the second __init__.
260-
if (v_h.instance_registered()) return;
261-
262-
construct<Class>(v_h, func(std::forward<Args>(args)...), Py_TYPE(v_h.inst) != cl_type->type);
263-
}, extra...);
237+
(value_and_holder &v_h, Args... args) {
238+
construct<Class>(v_h, func(std::forward<Args>(args)...),
239+
Py_TYPE(v_h.inst) != v_h.type->type);
240+
}, is_new_style_constructor(), extra...);
264241
}
265242

266243
// Add __init__ definition for a class with an alias *and* distinct alias factory; the former is
@@ -269,26 +246,21 @@ template <typename CFunc, typename AFuncIn, typename... Args> struct factory {
269246
template <typename Class, typename... Extra,
270247
enable_if_t<Class::has_alias && !std::is_void<AFuncIn>::value, int> = 0>
271248
void execute(Class &cl, const Extra&... extra) && {
272-
auto *cl_type = get_type_info(typeid(Cpp<Class>));
273-
274249
#if defined(PYBIND11_CPP14)
275-
cl.def("__init__", [cl_type, class_func = std::move(class_factory), alias_func = std::move(alias_factory)]
250+
cl.def("__init__", [class_func = std::move(class_factory), alias_func = std::move(alias_factory)]
276251
#else
277252
CFuncType &class_func = class_factory;
278253
AFuncType &alias_func = alias_factory;
279-
cl.def("__init__", [cl_type, class_func, alias_func]
254+
cl.def("__init__", [class_func, alias_func]
280255
#endif
281-
(handle self_, Args... args) {
282-
auto v_h = load_v_h(self_, cl_type);
283-
if (v_h.instance_registered()) return; // (see comment above)
284-
285-
if (Py_TYPE(v_h.inst) == cl_type->type)
256+
(value_and_holder &v_h, Args... args) {
257+
if (Py_TYPE(v_h.inst) == v_h.type->type)
286258
// If the instance type equals the registered type we don't have inheritance, so
287259
// don't need the alias and can construct using the class function:
288260
construct<Class>(v_h, class_func(std::forward<Args>(args)...), false);
289261
else
290262
construct<Class>(v_h, alias_func(std::forward<Args>(args)...), true);
291-
}, extra...);
263+
}, is_new_style_constructor(), extra...);
292264
}
293265
};
294266

include/pybind11/pybind11.h

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,9 @@ class cpp_function : public function {
242242
.cast<std::string>() + ".";
243243
#endif
244244
signature += tinfo->type->tp_name;
245-
} else if (rec->is_constructor && arg_index == 0 && detail::same_type(typeid(handle), *t) && rec->scope) {
246-
// A py::init(...) constructor takes `self` as a `handle`; rewrite it to the type
245+
} else if (rec->is_new_style_constructor && arg_index == 0) {
246+
// A new-style `__init__` takes `self` as `value_and_holder`.
247+
// Rewrite it to the proper class type.
247248
#if defined(PYPY_VERSION)
248249
signature += rec->scope.attr("__module__").cast<std::string>() + ".";
249250
#endif
@@ -425,6 +426,23 @@ class cpp_function : public function {
425426
handle parent = n_args_in > 0 ? PyTuple_GET_ITEM(args_in, 0) : nullptr,
426427
result = PYBIND11_TRY_NEXT_OVERLOAD;
427428

429+
auto self_value_and_holder = value_and_holder();
430+
if (overloads->is_constructor) {
431+
const auto tinfo = get_type_info((PyTypeObject *) overloads->scope.ptr());
432+
const auto pi = reinterpret_cast<instance *>(parent.ptr());
433+
self_value_and_holder = pi->get_value_and_holder(tinfo, false);
434+
435+
if (!self_value_and_holder.type || !self_value_and_holder.inst) {
436+
PyErr_SetString(PyExc_TypeError, "__init__(self, ...) called with invalid `self` argument");
437+
return nullptr;
438+
}
439+
440+
// If this value is already registered it must mean __init__ is invoked multiple times;
441+
// we really can't support that in C++, so just ignore the second __init__.
442+
if (self_value_and_holder.instance_registered())
443+
return none().release().ptr();
444+
}
445+
428446
try {
429447
// We do this in two passes: in the first pass, we load arguments with `convert=false`;
430448
// in the second, we allow conversion (except for arguments with an explicit
@@ -472,6 +490,18 @@ class cpp_function : public function {
472490
size_t args_to_copy = std::min(pos_args, n_args_in);
473491
size_t args_copied = 0;
474492

493+
// 0. Inject new-style `self` argument
494+
if (it->is_new_style_constructor) {
495+
// The `value` may have been preallocated by an old-style `__init__`
496+
// if it was a preceding candidate for overload resolution.
497+
if (self_value_and_holder)
498+
self_value_and_holder.type->dealloc(self_value_and_holder);
499+
500+
call.args.push_back(reinterpret_cast<PyObject *>(&self_value_and_holder));
501+
call.args_convert.push_back(false);
502+
++args_copied;
503+
}
504+
475505
// 1. Copy any position arguments given.
476506
bool bad_arg = false;
477507
for (; args_copied < args_to_copy; ++args_copied) {
@@ -715,13 +745,9 @@ class cpp_function : public function {
715745
PyErr_SetString(PyExc_TypeError, msg.c_str());
716746
return nullptr;
717747
} else {
718-
if (overloads->is_constructor) {
719-
auto tinfo = get_type_info((PyTypeObject *) overloads->scope.ptr());
748+
if (overloads->is_constructor && !self_value_and_holder.holder_constructed()) {
720749
auto *pi = reinterpret_cast<instance *>(parent.ptr());
721-
auto v_h = pi->get_value_and_holder(tinfo);
722-
if (!v_h.holder_constructed()) {
723-
tinfo->init_instance(pi, nullptr);
724-
}
750+
self_value_and_holder.type->init_instance(pi, nullptr);
725751
}
726752
return result.ptr();
727753
}

0 commit comments

Comments
 (0)