From e2eca4f8f86685507764ea25dc96a404026a50f2 Mon Sep 17 00:00:00 2001 From: Wenzel Jakob Date: Fri, 9 Nov 2018 20:14:53 +0100 Subject: [PATCH] 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. --- include/pybind11/attr.h | 5 ++++- include/pybind11/cast.h | 12 +++++++++++- include/pybind11/detail/internals.h | 4 ++-- include/pybind11/pybind11.h | 23 +++++++++++++++++++---- tests/test_class.cpp | 13 +++++++++++++ tests/test_class.py | 6 ++++++ 6 files changed, 55 insertions(+), 8 deletions(-) diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h index 985bc5794..8732cfe10 100644 --- a/include/pybind11/attr.h +++ b/include/pybind11/attr.h @@ -214,11 +214,14 @@ struct type_record { /// How large is the underlying C++ type? size_t type_size = 0; + /// What is the alignment of the underlying C++ type? + size_t type_align = 0; + /// How large is the type's holder? size_t holder_size = 0; /// The global operator new can be overridden with a class-specific variant - void *(*operator_new)(size_t) = ::operator new; + void *(*operator_new)(size_t) = nullptr; /// Function pointer to class_<..>::init_instance void (*init_instance)(instance *, const void *) = nullptr; diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index a21d6bae3..2dec23cb3 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -571,7 +571,17 @@ public: // Lazy allocation for unallocated values: if (vptr == nullptr) { auto *type = v_h.type ? v_h.type : typeinfo; - vptr = type->operator_new(type->type_size); + if (type->operator_new) { + vptr = type->operator_new(type->type_size); + } else { + #if defined(PYBIND11_CPP17) + if (type->type_align > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + vptr = ::operator new(type->type_size, + (std::align_val_t) type->type_align); + else + #endif + vptr = ::operator new(type->type_size); + } } value = vptr; } diff --git a/include/pybind11/detail/internals.h b/include/pybind11/detail/internals.h index 78d4afed0..ad3441576 100644 --- a/include/pybind11/detail/internals.h +++ b/include/pybind11/detail/internals.h @@ -116,7 +116,7 @@ struct internals { struct type_info { PyTypeObject *type; const std::type_info *cpptype; - size_t type_size, holder_size_in_ptrs; + size_t type_size, type_align, holder_size_in_ptrs; void *(*operator_new)(size_t); void (*init_instance)(instance *, const void *); void (*dealloc)(value_and_holder &v_h); @@ -138,7 +138,7 @@ struct type_info { }; /// Tracks the `internals` and `type_info` ABI version independent of the main library version -#define PYBIND11_INTERNALS_VERSION 2 +#define PYBIND11_INTERNALS_VERSION 3 #if defined(WITH_THREAD) # define PYBIND11_INTERNALS_KIND "" diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 99a1e0a78..e45f1a0c6 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -896,6 +896,7 @@ protected: tinfo->type = (PyTypeObject *) m_ptr; tinfo->cpptype = rec.type; tinfo->type_size = rec.type_size; + tinfo->type_align = rec.type_align; tinfo->operator_new = rec.operator_new; tinfo->holder_size_in_ptrs = size_in_ptrs(rec.holder_size); tinfo->init_instance = rec.init_instance; @@ -987,11 +988,21 @@ template struct has_operator_delete_size::value, int> = 0> -void call_operator_delete(T *p, size_t) { T::operator delete(p); } +void call_operator_delete(T *p, size_t, size_t) { T::operator delete(p); } template ::value && has_operator_delete_size::value, int> = 0> -void call_operator_delete(T *p, size_t s) { T::operator delete(p, s); } +void call_operator_delete(T *p, size_t s, size_t) { T::operator delete(p, s); } -inline void call_operator_delete(void *p, size_t) { ::operator delete(p); } +inline void call_operator_delete(void *p, size_t s, size_t a) { + (void)s; (void)a; +#if defined(PYBIND11_CPP17) + if (a > __STDCPP_DEFAULT_NEW_ALIGNMENT__) + ::operator delete(p, s, std::align_val_t(a)); + else + ::operator delete(p, s); +#else + ::operator delete(p); +#endif +} NAMESPACE_END(detail) @@ -1054,6 +1065,7 @@ public: record.name = name; record.type = &typeid(type); record.type_size = sizeof(conditional_t); + record.type_align = alignof(conditional_t&); record.holder_size = sizeof(holder_type); record.init_instance = init_instance; record.dealloc = dealloc; @@ -1329,7 +1341,10 @@ private: v_h.set_holder_constructed(false); } else { - detail::call_operator_delete(v_h.value_ptr(), v_h.type->type_size); + detail::call_operator_delete(v_h.value_ptr(), + v_h.type->type_size, + v_h.type->type_align + ); } v_h.value_ptr() = nullptr; } diff --git a/tests/test_class.cpp b/tests/test_class.cpp index 9ed1f50bb..499d0cc51 100644 --- a/tests/test_class.cpp +++ b/tests/test_class.cpp @@ -12,6 +12,10 @@ #include "local_bindings.h" #include +#if defined(_MSC_VER) +# pragma warning(disable: 4324) // warning C4324: structure was padded due to alignment specifier +#endif + // test_brace_initialization struct NoBraceInitialization { NoBraceInitialization(std::vector v) : vec{std::move(v)} {} @@ -354,6 +358,15 @@ TEST_SUBMODULE(class_, m) { [](StringWrapper) -> NotRegistered { return {}; }); py::class_(m, "StringWrapper").def(py::init()); py::implicitly_convertible(); + + #if defined(PYBIND11_CPP17) + struct alignas(1024) Aligned { + std::uintptr_t ptr() const { return (uintptr_t) this; } + }; + py::class_(m, "Aligned") + .def(py::init<>()) + .def("ptr", &Aligned::ptr); + #endif } template class BreaksBase { public: virtual ~BreaksBase() = default; }; diff --git a/tests/test_class.py b/tests/test_class.py index 4a488ab65..ed63ca853 100644 --- a/tests/test_class.py +++ b/tests/test_class.py @@ -273,3 +273,9 @@ def test_error_after_conversions(): m.test_error_after_conversions("hello") assert str(exc_info.value).startswith( "Unable to convert function return value to a Python type!") + + +def test_aligned(): + if hasattr(m, "Aligned"): + p = m.Aligned().ptr() + assert p % 1024 == 0