Skip to content

Commit e2eca4f

Browse files
authored
Support C++17 aligned new statement (#1582)
* Support C++17 aligned new statement This patch makes pybind11 aware of nonstandard alignment requirements in bound types and passes on this information to C++17 aligned 'new' operator. Pre-C++17, the behavior is unchanged.
1 parent adc2cdd commit e2eca4f

File tree

6 files changed

+55
-8
lines changed

6 files changed

+55
-8
lines changed

include/pybind11/attr.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,11 +214,14 @@ struct type_record {
214214
/// How large is the underlying C++ type?
215215
size_t type_size = 0;
216216

217+
/// What is the alignment of the underlying C++ type?
218+
size_t type_align = 0;
219+
217220
/// How large is the type's holder?
218221
size_t holder_size = 0;
219222

220223
/// The global operator new can be overridden with a class-specific variant
221-
void *(*operator_new)(size_t) = ::operator new;
224+
void *(*operator_new)(size_t) = nullptr;
222225

223226
/// Function pointer to class_<..>::init_instance
224227
void (*init_instance)(instance *, const void *) = nullptr;

include/pybind11/cast.h

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,17 @@ class type_caster_generic {
571571
// Lazy allocation for unallocated values:
572572
if (vptr == nullptr) {
573573
auto *type = v_h.type ? v_h.type : typeinfo;
574-
vptr = type->operator_new(type->type_size);
574+
if (type->operator_new) {
575+
vptr = type->operator_new(type->type_size);
576+
} else {
577+
#if defined(PYBIND11_CPP17)
578+
if (type->type_align > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
579+
vptr = ::operator new(type->type_size,
580+
(std::align_val_t) type->type_align);
581+
else
582+
#endif
583+
vptr = ::operator new(type->type_size);
584+
}
575585
}
576586
value = vptr;
577587
}

include/pybind11/detail/internals.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ struct internals {
116116
struct type_info {
117117
PyTypeObject *type;
118118
const std::type_info *cpptype;
119-
size_t type_size, holder_size_in_ptrs;
119+
size_t type_size, type_align, holder_size_in_ptrs;
120120
void *(*operator_new)(size_t);
121121
void (*init_instance)(instance *, const void *);
122122
void (*dealloc)(value_and_holder &v_h);
@@ -138,7 +138,7 @@ struct type_info {
138138
};
139139

140140
/// Tracks the `internals` and `type_info` ABI version independent of the main library version
141-
#define PYBIND11_INTERNALS_VERSION 2
141+
#define PYBIND11_INTERNALS_VERSION 3
142142

143143
#if defined(WITH_THREAD)
144144
# define PYBIND11_INTERNALS_KIND ""

include/pybind11/pybind11.h

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -896,6 +896,7 @@ class generic_type : public object {
896896
tinfo->type = (PyTypeObject *) m_ptr;
897897
tinfo->cpptype = rec.type;
898898
tinfo->type_size = rec.type_size;
899+
tinfo->type_align = rec.type_align;
899900
tinfo->operator_new = rec.operator_new;
900901
tinfo->holder_size_in_ptrs = size_in_ptrs(rec.holder_size);
901902
tinfo->init_instance = rec.init_instance;
@@ -987,11 +988,21 @@ template <typename T> struct has_operator_delete_size<T, void_t<decltype(static_
987988
: std::true_type { };
988989
/// Call class-specific delete if it exists or global otherwise. Can also be an overload set.
989990
template <typename T, enable_if_t<has_operator_delete<T>::value, int> = 0>
990-
void call_operator_delete(T *p, size_t) { T::operator delete(p); }
991+
void call_operator_delete(T *p, size_t, size_t) { T::operator delete(p); }
991992
template <typename T, enable_if_t<!has_operator_delete<T>::value && has_operator_delete_size<T>::value, int> = 0>
992-
void call_operator_delete(T *p, size_t s) { T::operator delete(p, s); }
993+
void call_operator_delete(T *p, size_t s, size_t) { T::operator delete(p, s); }
993994

994-
inline void call_operator_delete(void *p, size_t) { ::operator delete(p); }
995+
inline void call_operator_delete(void *p, size_t s, size_t a) {
996+
(void)s; (void)a;
997+
#if defined(PYBIND11_CPP17)
998+
if (a > __STDCPP_DEFAULT_NEW_ALIGNMENT__)
999+
::operator delete(p, s, std::align_val_t(a));
1000+
else
1001+
::operator delete(p, s);
1002+
#else
1003+
::operator delete(p);
1004+
#endif
1005+
}
9951006

9961007
NAMESPACE_END(detail)
9971008

@@ -1054,6 +1065,7 @@ class class_ : public detail::generic_type {
10541065
record.name = name;
10551066
record.type = &typeid(type);
10561067
record.type_size = sizeof(conditional_t<has_alias, type_alias, type>);
1068+
record.type_align = alignof(conditional_t<has_alias, type_alias, type>&);
10571069
record.holder_size = sizeof(holder_type);
10581070
record.init_instance = init_instance;
10591071
record.dealloc = dealloc;
@@ -1329,7 +1341,10 @@ class class_ : public detail::generic_type {
13291341
v_h.set_holder_constructed(false);
13301342
}
13311343
else {
1332-
detail::call_operator_delete(v_h.value_ptr<type>(), v_h.type->type_size);
1344+
detail::call_operator_delete(v_h.value_ptr<type>(),
1345+
v_h.type->type_size,
1346+
v_h.type->type_align
1347+
);
13331348
}
13341349
v_h.value_ptr() = nullptr;
13351350
}

tests/test_class.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@
1212
#include "local_bindings.h"
1313
#include <pybind11/stl.h>
1414

15+
#if defined(_MSC_VER)
16+
# pragma warning(disable: 4324) // warning C4324: structure was padded due to alignment specifier
17+
#endif
18+
1519
// test_brace_initialization
1620
struct NoBraceInitialization {
1721
NoBraceInitialization(std::vector<int> v) : vec{std::move(v)} {}
@@ -354,6 +358,15 @@ TEST_SUBMODULE(class_, m) {
354358
[](StringWrapper) -> NotRegistered { return {}; });
355359
py::class_<StringWrapper>(m, "StringWrapper").def(py::init<std::string>());
356360
py::implicitly_convertible<std::string, StringWrapper>();
361+
362+
#if defined(PYBIND11_CPP17)
363+
struct alignas(1024) Aligned {
364+
std::uintptr_t ptr() const { return (uintptr_t) this; }
365+
};
366+
py::class_<Aligned>(m, "Aligned")
367+
.def(py::init<>())
368+
.def("ptr", &Aligned::ptr);
369+
#endif
357370
}
358371

359372
template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; };

tests/test_class.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,3 +273,9 @@ def test_error_after_conversions():
273273
m.test_error_after_conversions("hello")
274274
assert str(exc_info.value).startswith(
275275
"Unable to convert function return value to a Python type!")
276+
277+
278+
def test_aligned():
279+
if hasattr(m, "Aligned"):
280+
p = m.Aligned().ptr()
281+
assert p % 1024 == 0

0 commit comments

Comments
 (0)