Fix required memory size calculations, do only one pass.

This commit is contained in:
Smertig 2021-05-10 13:36:26 +03:00 committed by The Phantom Derpstorm
parent 1a7c9fc74b
commit 05235646ef
2 changed files with 57 additions and 89 deletions

View File

@ -94,16 +94,35 @@ namespace sol {
return reinterpret_cast<void*>(align(alignment, reinterpret_cast<std::uintptr_t>(ptr), space)); return reinterpret_cast<void*>(align(alignment, reinterpret_cast<std::uintptr_t>(ptr), space));
} }
constexpr std::uintptr_t align_one(std::size_t alignment, std::size_t size, std::uintptr_t target_alignment) { constexpr std::uintptr_t align_one(std::size_t alignment, std::size_t size, std::uintptr_t ptr) {
std::size_t space = (std::numeric_limits<std::size_t>::max)(); std::size_t space = (std::numeric_limits<std::size_t>::max)();
return align(alignment, target_alignment, space) + size; return align(alignment, ptr, space) + size;
} }
template <typename... Args> template <typename... Args>
constexpr std::size_t aligned_space_for(std::uintptr_t alignment) { constexpr std::size_t aligned_space_for(std::uintptr_t ptr) {
std::uintptr_t start = alignment; std::uintptr_t end = ptr;
(void)detail::swallow { int {}, (alignment = align_one(std::alignment_of_v<Args>, sizeof(Args), alignment), int {})... }; ((end = align_one(alignof(Args), sizeof(Args), end)), ...);
return static_cast<std::size_t>(alignment - start); return static_cast<std::size_t>(end - ptr);
}
template <typename... Args>
constexpr std::size_t aligned_space_for() {
static_assert(sizeof...(Args) > 0);
constexpr std::size_t max_arg_alignment = (std::max)({ alignof(Args)... });
if constexpr (max_arg_alignment <= alignof(std::max_align_t)) {
// If all types are `good enough`, simply calculate alignment in case of the worst allocator
std::size_t worst_required_size = 0;
for (std::size_t ptr = 0; ptr < max_arg_alignment; ptr++) {
worst_required_size = std::max(worst_required_size, aligned_space_for<Args...>(ptr));
}
return worst_required_size;
}
else {
// For over-aligned types let's assume that every Arg in Args starts at the worst aligned address
return (aligned_space_for<Args>(0x1) + ...);
}
} }
inline void* align_usertype_pointer(void* ptr) { inline void* align_usertype_pointer(void* ptr) {
@ -219,19 +238,11 @@ namespace sol {
T** pointerpointer = static_cast<T**>(alloc_newuserdata(L, sizeof(T*))); T** pointerpointer = static_cast<T**>(alloc_newuserdata(L, sizeof(T*)));
return pointerpointer; return pointerpointer;
} }
constexpr std::size_t initial_size = aligned_space_for<T*>(0x0); constexpr std::size_t initial_size = aligned_space_for<T*>();
constexpr std::size_t misaligned_size = aligned_space_for<T*>(0x1);
std::size_t allocated_size = initial_size; std::size_t allocated_size = initial_size;
void* unadjusted = alloc_newuserdata(L, initial_size); void* unadjusted = alloc_newuserdata(L, initial_size);
void* adjusted = align(std::alignment_of<T*>::value, unadjusted, allocated_size); void* adjusted = align(std::alignment_of<T*>::value, unadjusted, allocated_size);
if (adjusted == nullptr) {
lua_pop(L, 1);
// what kind of absolute garbage trash allocator are we dealing with?
// whatever, add some padding in the case of MAXIMAL alignment waste...
allocated_size = misaligned_size;
unadjusted = alloc_newuserdata(L, allocated_size);
adjusted = align(std::alignment_of<T*>::value, unadjusted, allocated_size);
if (adjusted == nullptr) { if (adjusted == nullptr) {
// trash allocator can burn in hell // trash allocator can burn in hell
lua_pop(L, 1); lua_pop(L, 1);
@ -239,7 +250,6 @@ namespace sol {
// worse job than malloc/realloc and should go read some books, yeah?"); // worse job than malloc/realloc and should go read some books, yeah?");
luaL_error(L, "cannot properly align memory for '%s'", detail::demangle<T*>().data()); luaL_error(L, "cannot properly align memory for '%s'", detail::demangle<T*>().data());
} }
}
return static_cast<T**>(adjusted); return static_cast<T**>(adjusted);
} }
@ -316,35 +326,12 @@ namespace sol {
return allocationtarget; return allocationtarget;
} }
/* the assumption is that `alloc_newuserdata` -- unless someone constexpr std::size_t initial_size = aligned_space_for<T*, T>();
passes a specific lua_Alloc that gives us bogus, un-aligned pointers
-- uses malloc, which tends to hand out more or less aligned pointers to memory
(most of the time, anyhow)
but it's not guaranteed, so we have to do a post-adjustment check and increase padding
we do this preliminarily with compile-time stuff, to see
if we strike lucky with the allocator and alignment values
otherwise, we have to re-allocate the userdata and
over-allocate some space for additional padding because
compilers are optimized for aligned reads/writes
(and clang will barf UBsan errors on us for not being aligned)
*/
constexpr std::size_t initial_size = aligned_space_for<T*, T>(0x0);
constexpr std::size_t misaligned_size = aligned_space_for<T*, T>(0x1);
void* pointer_adjusted; void* pointer_adjusted;
void* data_adjusted; void* data_adjusted;
bool result bool result
= attempt_alloc(L, std::alignment_of_v<T*>, sizeof(T*), std::alignment_of_v<T>, initial_size, pointer_adjusted, data_adjusted); = attempt_alloc(L, std::alignment_of_v<T*>, sizeof(T*), std::alignment_of_v<T>, initial_size, pointer_adjusted, data_adjusted);
if (!result) {
// we're likely to get something that fails to perform the proper allocation a second time,
// so we use the suggested_new_size bump to help us out here
pointer_adjusted = nullptr;
data_adjusted = nullptr;
result = attempt_alloc(
L, std::alignment_of_v<T*>, sizeof(T*), std::alignment_of_v<T>, misaligned_size, pointer_adjusted, data_adjusted);
if (!result) { if (!result) {
if (pointer_adjusted == nullptr) { if (pointer_adjusted == nullptr) {
luaL_error(L, "aligned allocation of userdata block (pointer section) for '%s' failed", detail::demangle<T>().c_str()); luaL_error(L, "aligned allocation of userdata block (pointer section) for '%s' failed", detail::demangle<T>().c_str());
@ -354,7 +341,6 @@ namespace sol {
} }
return nullptr; return nullptr;
} }
}
T** pointerpointer = reinterpret_cast<T**>(pointer_adjusted); T** pointerpointer = reinterpret_cast<T**>(pointer_adjusted);
T*& pointerreference = *pointerpointer; T*& pointerreference = *pointerpointer;
@ -382,8 +368,7 @@ namespace sol {
return mem; return mem;
} }
constexpr std::size_t initial_size = aligned_space_for<T*, unique_destructor, unique_tag, Real>(0x0); constexpr std::size_t initial_size = aligned_space_for<T*, unique_destructor, unique_tag, Real>();
constexpr std::size_t misaligned_size = aligned_space_for<T*, unique_destructor, unique_tag, Real>(0x1);
void* pointer_adjusted; void* pointer_adjusted;
void* dx_adjusted; void* dx_adjusted;
@ -398,22 +383,6 @@ namespace sol {
dx_adjusted, dx_adjusted,
id_adjusted, id_adjusted,
data_adjusted); data_adjusted);
if (!result) {
// we're likely to get something that fails to perform the proper allocation a second time,
// so we use the suggested_new_size bump to help us out here
pointer_adjusted = nullptr;
dx_adjusted = nullptr;
id_adjusted = nullptr;
data_adjusted = nullptr;
result = attempt_alloc_unique(L,
std::alignment_of_v<T*>,
sizeof(T*),
std::alignment_of_v<Real>,
misaligned_size,
pointer_adjusted,
dx_adjusted,
id_adjusted,
data_adjusted);
if (!result) { if (!result) {
if (pointer_adjusted == nullptr) { if (pointer_adjusted == nullptr) {
luaL_error(L, "aligned allocation of userdata block (pointer section) for '%s' failed", detail::demangle<T>().c_str()); luaL_error(L, "aligned allocation of userdata block (pointer section) for '%s' failed", detail::demangle<T>().c_str());
@ -426,7 +395,6 @@ namespace sol {
} }
return nullptr; return nullptr;
} }
}
pref = static_cast<T**>(pointer_adjusted); pref = static_cast<T**>(pointer_adjusted);
dx = static_cast<detail::unique_destructor*>(dx_adjusted); dx = static_cast<detail::unique_destructor*>(dx_adjusted);
@ -450,23 +418,15 @@ namespace sol {
return pointer; return pointer;
} }
constexpr std::size_t initial_size = aligned_space_for<T>(0x0); constexpr std::size_t initial_size = aligned_space_for<T>();
constexpr std::size_t misaligned_size = aligned_space_for<T>(0x1);
std::size_t allocated_size = initial_size; std::size_t allocated_size = initial_size;
void* unadjusted = alloc_newuserdata(L, allocated_size); void* unadjusted = alloc_newuserdata(L, allocated_size);
void* adjusted = align(std::alignment_of_v<T>, unadjusted, allocated_size); void* adjusted = align(std::alignment_of_v<T>, unadjusted, allocated_size);
if (adjusted == nullptr) {
lua_pop(L, 1);
// try again, add extra space for alignment padding
allocated_size = misaligned_size;
unadjusted = alloc_newuserdata(L, allocated_size);
adjusted = align(std::alignment_of_v<T>, unadjusted, allocated_size);
if (adjusted == nullptr) { if (adjusted == nullptr) {
lua_pop(L, 1); lua_pop(L, 1);
luaL_error(L, "cannot properly align memory for '%s'", detail::demangle<T>().data()); luaL_error(L, "cannot properly align memory for '%s'", detail::demangle<T>().data());
} }
}
return static_cast<T*>(adjusted); return static_cast<T*>(adjusted);
} }

View File

@ -6,6 +6,10 @@ inline namespace sol2_regression_test_XXXX {
struct Test { struct Test {
std::uint64_t dummy; std::uint64_t dummy;
}; };
struct alignas(1024) Test2 {
char dummy[1024];
};
} // namespace sol2_regression_test_XXXX } // namespace sol2_regression_test_XXXX
unsigned int regression_XXXX() { unsigned int regression_XXXX() {
@ -20,6 +24,10 @@ unsigned int regression_XXXX() {
/// Note: may not panic depending on alignment of local variable `alignment_shim` in sol::detail::aligned_space_for /// Note: may not panic depending on alignment of local variable `alignment_shim` in sol::detail::aligned_space_for
lua["test"] = Test {}; lua["test"] = Test {};
// Test also unique and over-aligned userdata
lua["test"] = std::make_unique<Test>();
lua["test"] = Test2 {};
return 0; return 0;
} }