On Tue, Jul 8, 2025 at 3:24 PM Jonathan Wakely <jwak...@redhat.com> wrote:
> On Tue, 8 Jul 2025 at 14:12, Tomasz Kaminski <tkami...@redhat.com> wrote: > > > > > > > > On Tue, Jul 8, 2025 at 2:48 PM Jonathan Wakely <jwak...@redhat.com> > wrote: > >> > >> On Tue, 8 Jul 2025 at 13:31, Tomasz Kaminski <tkami...@redhat.com> > wrote: > >> > > >> > > >> > > >> > On Sat, Jul 5, 2025 at 1:12 AM Jonathan Wakely <jwak...@redhat.com> > wrote: > >> >> > >> >> For allocations with size > alignment and size % alignment != 0 we > were > >> >> sometimes returning pointers that did not meet the requested > aligment. > >> >> For example, allocate(24, 16) would select the pool for 24-byte > objects > >> >> and the second allocation from that pool (at offset 24 bytes into the > >> >> pool) is only 8-byte aligned not 16-byte aligned. > >> >> > >> >> The pool resources need to round up the requested allocation size to > a > >> >> multiple of the alignment, so that the selected pool will always > return > >> >> allocations that meet the alignment requirement. > >> >> > >> >> libstdc++-v3/ChangeLog: > >> >> > >> >> PR libstdc++/118681 > >> >> * src/c++17/memory_resource.cc (choose_block_size): New > >> >> function. > >> >> (synchronized_pool_resource::do_allocate): Use > choose_block_size > >> >> to determine appropriate block size. > >> >> (synchronized_pool_resource::do_deallocate): Likewise > >> >> (unsynchronized_pool_resource::do_allocate): Likewise. > >> >> (unsynchronized_pool_resource::do_deallocate): Likewise > >> >> * testsuite/20_util/synchronized_pool_resource/118681.cc: New > >> >> test. > >> >> * testsuite/20_util/unsynchronized_pool_resource/118681.cc: > New > >> >> test. > >> >> --- > >> >> > >> >> Tested x86_64-linux. > >> > > >> > I have expressed my preference of doing this check inside the > memory_resouce::allocate. > >> > >> We can still do that separately. The point of this patch is to fix the > >> misaligned allocations from the pool resources, not improve the EB vs > >> UB position for the memory_resource::allocate API. > > > > Because do_allocate is not publicly accessible, and can be called only > via allocate, changing > > memory_resource::allocate API will also fix the misaligned allocations > from pool resources. > > No it won't, the bug happens with res.allocate(24,16) and 16 is a > valid alignment. > > The bug is in the logic that finds the appropriate object pool to > allocate from, and happens for valid alignments. > > The reason I added a call to bit_ceil in the patch is because the fix > to round up to a multiple of the alignment assumes that the alignment > is valid (previously we didn't really care if it was valid, we just > used it). > Thanks for the explanation, I missed the most important part of the patch, and focused on the bit_ceil call instead. > > > > > And I believe, moving the check between specific pool do_allocate to > memory_resource::allocate, > > would be ABI break, as we may end up with binary that contains: > > * memory_resource::allocate without check (check is in > Derived::do_allocate) > > * Derived::do_allocate without check (check is > memory_resource::allocate) > > (Let me know if I misunderstood how this kind of ABI breaks work). > >> > >> > >> > I know that this throws any potential detection of misaligned > resources under the bus, > >> > however I think there is more benefit of not exposing custom > resources to strange alignment values, > >> > and using the wrong pool. > >> > > >> > On the standard level, I think we should make behavior erroneous > here, with defined behavior being bit_ceil. > >> > >> Yeah, I think that's a good idea. > >> > >> > >> >> > >> >> > >> >> libstdc++-v3/src/c++17/memory_resource.cc | 26 +++++++-- > >> >> .../synchronized_pool_resource/118681.cc | 5 ++ > >> >> .../unsynchronized_pool_resource/118681.cc | 58 > +++++++++++++++++++ > >> >> 3 files changed, 85 insertions(+), 4 deletions(-) > >> >> create mode 100644 > libstdc++-v3/testsuite/20_util/synchronized_pool_resource/118681.cc > >> >> create mode 100644 > libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/118681.cc > >> >> > >> >> diff --git a/libstdc++-v3/src/c++17/memory_resource.cc > b/libstdc++-v3/src/c++17/memory_resource.cc > >> >> index fac4c782c5f7..fddfe2c7dd98 100644 > >> >> --- a/libstdc++-v3/src/c++17/memory_resource.cc > >> >> +++ b/libstdc++-v3/src/c++17/memory_resource.cc > >> >> @@ -1242,12 +1242,30 @@ namespace pmr > >> >> return pools; > >> >> } > >> >> > >> >> + static inline size_t > >> >> + choose_block_size(size_t bytes, size_t alignment) > >> >> + { > >> >> + if (bytes == 0) [[unlikely]] > >> >> + return alignment; > >> >> + > >> >> + // Use bit_ceil in case alignment is invalid (i.e. not a power > of two). > >> >> + size_t mask = std::__bit_ceil(alignment) - 1; > >> >> + // Round up to a multiple of alignment. > >> >> + size_t block_size = (bytes + mask) & ~mask; > >> >> + > >> >> + if (block_size >= bytes) [[likely]] > >> >> + return block_size; > >> >> + > >> >> + // Wrapped around to zero, bytes must have been impossibly > large. > >> >> + return numeric_limits<size_t>::max(); > >> >> + } > >> >> + > >> >> // Override for memory_resource::do_allocate > >> >> void* > >> >> synchronized_pool_resource:: > >> >> do_allocate(size_t bytes, size_t alignment) > >> >> { > >> >> - const auto block_size = std::max(bytes, alignment); > >> >> + const auto block_size = choose_block_size(bytes, alignment); > >> >> const pool_options opts = _M_impl._M_opts; > >> >> if (block_size <= opts.largest_required_pool_block) > >> >> { > >> >> @@ -1294,7 +1312,7 @@ namespace pmr > >> >> synchronized_pool_resource:: > >> >> do_deallocate(void* p, size_t bytes, size_t alignment) > >> >> { > >> >> - size_t block_size = std::max(bytes, alignment); > >> >> + size_t block_size = choose_block_size(bytes, alignment); > >> >> if (block_size <= _M_impl._M_opts.largest_required_pool_block) > >> >> { > >> >> const ptrdiff_t index = pool_index(block_size, > _M_impl._M_npools); > >> >> @@ -1453,7 +1471,7 @@ namespace pmr > >> >> void* > >> >> unsynchronized_pool_resource::do_allocate(size_t bytes, size_t > alignment) > >> >> { > >> >> - const auto block_size = std::max(bytes, alignment); > >> >> + const auto block_size = choose_block_size(bytes, alignment); > >> >> if (block_size <= _M_impl._M_opts.largest_required_pool_block) > >> >> { > >> >> // Recreate pools if release() has been called: > >> >> @@ -1470,7 +1488,7 @@ namespace pmr > >> >> unsynchronized_pool_resource:: > >> >> do_deallocate(void* p, size_t bytes, size_t alignment) > >> >> { > >> >> - size_t block_size = std::max(bytes, alignment); > >> >> + size_t block_size = choose_block_size(bytes, alignment); > >> >> if (block_size <= _M_impl._M_opts.largest_required_pool_block) > >> >> { > >> >> if (auto pool = _M_find_pool(block_size)) > >> >> diff --git > a/libstdc++-v3/testsuite/20_util/synchronized_pool_resource/118681.cc > b/libstdc++-v3/testsuite/20_util/synchronized_pool_resource/118681.cc > >> >> new file mode 100644 > >> >> index 000000000000..6d7434ff9106 > >> >> --- /dev/null > >> >> +++ > b/libstdc++-v3/testsuite/20_util/synchronized_pool_resource/118681.cc > >> >> @@ -0,0 +1,5 @@ > >> >> +// { dg-do run { target c++17 } } > >> >> +// Bug 118681 - unsynchronized_pool_resource may fail to respect > alignment > >> >> + > >> >> +#define RESOURCE std::pmr::synchronized_pool_resource > >> >> +#include "../unsynchronized_pool_resource/118681.cc" > >> >> diff --git > a/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/118681.cc > b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/118681.cc > >> >> new file mode 100644 > >> >> index 000000000000..87e1b1d94043 > >> >> --- /dev/null > >> >> +++ > b/libstdc++-v3/testsuite/20_util/unsynchronized_pool_resource/118681.cc > >> >> @@ -0,0 +1,58 @@ > >> >> +// { dg-do run { target c++17 } } > >> >> +// Bug 118681 - unsynchronized_pool_resource may fail to respect > alignment > >> >> + > >> >> +#include <memory_resource> > >> >> +#include <cstdio> > >> >> +#include <testsuite_hooks.h> > >> >> + > >> >> +#ifndef RESOURCE > >> >> +# define RESOURCE std::pmr::unsynchronized_pool_resource > >> >> +#endif > >> >> + > >> >> +bool any_misaligned = false; > >> >> + > >> >> +bool > >> >> +is_aligned(void* p, [[maybe_unused]] std::size_t size, std::size_t > alignment) > >> >> +{ > >> >> + const bool misaligned = reinterpret_cast<std::uintptr_t>(p) % > alignment; > >> >> +#ifdef DEBUG > >> >> + std::printf("allocate(%2zu, %2zu): %p is aligned %scorrectly\n", > >> >> + size, alignment, p, misaligned ? "in" : ""); > >> >> + any_misaligned |= misaligned; > >> >> + return true; > >> >> +#endif > >> >> + return ! misaligned; > >> >> +} > >> >> + > >> >> +void > >> >> +test_alignment(std::pmr::memory_resource& res, bool dealloc) > >> >> +{ > >> >> + for (std::size_t alignment : { 8, 16, 32, 64 }) > >> >> + { > >> >> + for (std::size_t size : { 9, 12, 24, 40, 48, 56, 72 }) > >> >> + { > >> >> + void* p1 = res.allocate(size, alignment); > >> >> + void* p2 = res.allocate(size, alignment); > >> >> + > >> >> + VERIFY( is_aligned(p1, size, alignment) ); > >> >> + VERIFY( is_aligned(p2, size, alignment) ); > >> >> + > >> >> + if (dealloc) > >> >> + { > >> >> + res.deallocate(p2, size, alignment); > >> >> + res.deallocate(p2, size, alignment); > >> >> + } > >> >> + } > >> >> + } > >> >> +} > >> >> + > >> >> +int main() > >> >> +{ > >> >> + RESOURCE res; > >> >> + test_alignment(res, true); > >> >> + res.release(); > >> >> + test_alignment(res, false); > >> >> + res.release(); > >> >> + > >> >> + VERIFY( ! any_misaligned ); > >> >> +} > >> >> -- > >> >> 2.50.0 > >> >> > >> > >