blob: a76afbe87a2ee1ae86c67890bafade13df293c44 [file] [log] [blame]
// Copyright (C) 2018-2021 Free Software Foundation, Inc.
//
// This file is part of the GNU ISO C++ Library. This library is free
// software; you can redistribute it and/or modify it under the
// terms of the GNU General Public License as published by the
// Free Software Foundation; either version 3, or (at your option)
// any later version.
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License along
// with this library; see the file COPYING3. If not see
// <http://www.gnu.org/licenses/>.
// { dg-do run { target c++17 } }
#include <memory_resource>
#include <cstring>
#include <testsuite_allocator.h>
#include <testsuite_hooks.h>
void
test01()
{
__gnu_test::memory_resource test_mr;
{
std::pmr::unsynchronized_pool_resource r(&test_mr);
void* p1 = r.allocate(1, 1);
VERIFY( p1 != nullptr );
auto n = test_mr.number_of_active_allocations();
VERIFY( n > 0 );
// Ensure memory region can be written to (without corrupting heap!)
std::memset(p1, 0xff, 1);
void* p2 = r.allocate(1, 1);
VERIFY( p2 != nullptr );
VERIFY( p2 != p1 );
VERIFY( test_mr.number_of_active_allocations() == n );
std::memset(p1, 0xff, 1);
r.deallocate(p1, 1, 1);
// Returning single blocks to the pool doesn't return them upstream:
VERIFY( test_mr.number_of_active_allocations() == n );
r.deallocate(p2, 1, 1);
VERIFY( test_mr.number_of_active_allocations() == n );
}
VERIFY( test_mr.number_of_active_allocations() == 0 );
}
void
test02()
{
struct nullable_memory_resource : public std::pmr::memory_resource
{
void*
do_allocate(std::size_t bytes, std::size_t alignment) override
{ return upstream->allocate(bytes, alignment); }
void
do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override
{ upstream->deallocate(p, bytes, alignment); }
bool
do_is_equal(const memory_resource& r) const noexcept override
{ return &r == this; }
std::pmr::memory_resource* upstream = std::pmr::get_default_resource();
};
nullable_memory_resource test_mr;
std::pmr::unsynchronized_pool_resource r(&test_mr);
void* p1 = r.allocate(8, 1);
VERIFY( p1 != nullptr );
std::memset(p1, 0xff, 8);
test_mr.upstream = nullptr;
void* p2 = r.allocate(8, 1); //should not need to replenish
VERIFY( p2 != nullptr );
VERIFY( p2 != p1 );
std::memset(p1, 0xff, 8);
r.deallocate(p1, 8, 1); // should not use upstream
r.deallocate(p2, 8, 1); // should not use upstream
// Destructor will return memory upstream, so restore the upstream resource:
test_mr.upstream = std::pmr::get_default_resource();
}
void
test03()
{
__gnu_test::memory_resource test_mr;
{
std::pmr::unsynchronized_pool_resource r({10, 16}, &test_mr);
std::size_t largest_pool = r.options().largest_required_pool_block;
void* p1 = r.allocate(2 * largest_pool);
VERIFY( p1 != nullptr );
const std::size_t n = test_mr.number_of_active_allocations();
// Allocation of pools + allocation of pmr::vector + oversize allocation:
VERIFY( n >= 1 );
std::memset(p1, 0xff, 2 * largest_pool);
void* p2 = r.allocate(3 * largest_pool);
VERIFY( p2 != nullptr );
VERIFY( p2 != p1 );
VERIFY( test_mr.number_of_active_allocations() == n + 1 );
std::memset(p2, 0xff, 3 * largest_pool);
r.deallocate(p1, 2 * largest_pool);
VERIFY( test_mr.number_of_active_allocations() == n );
r.deallocate(p2, 3 * largest_pool);
VERIFY( test_mr.number_of_active_allocations() == n - 1 );
}
VERIFY( test_mr.number_of_active_allocations() == 0 );
{
std::pmr::unsynchronized_pool_resource r({16, 16}, &test_mr);
(void) r.allocate(2);
(void) r.allocate(8);
(void) r.allocate(16);
(void) r.allocate(2);
(void) r.allocate(8);
(void) r.allocate(16);
(void) r.allocate(2 * r.options().largest_required_pool_block);
VERIFY( test_mr.number_of_active_allocations() != 0 );
// Destructor calls release()
}
VERIFY( test_mr.number_of_active_allocations() == 0 );
}
void
test04()
{
__gnu_test::memory_resource test_mr;
std::pmr::unsynchronized_pool_resource r({256, 256}, &test_mr);
// Check alignment
void* p1 = r.allocate(2, 64);
VERIFY( (std::uintptr_t)p1 % 64 == 0 );
void* p2 = r.allocate(2, 128);
VERIFY( (std::uintptr_t)p2 % 128 == 0 );
void* p3 = r.allocate(2, 256);
VERIFY( (std::uintptr_t)p3 % 256 == 0 );
const std::size_t largest_pool = r.options().largest_required_pool_block;
void* p4 = r.allocate(2 * largest_pool, 1024);
VERIFY( (std::uintptr_t)p4 % 1024 == 0 );
r.deallocate(p1, 2, 64);
r.deallocate(p2, 2, 128);
r.deallocate(p3, 2, 256);
r.deallocate(p4, 2 * largest_pool, 1024);
}
void
test05()
{
__gnu_test::memory_resource test_mr;
std::pmr::pool_options opts{};
opts.max_blocks_per_chunk = 1;
opts.largest_required_pool_block = 1;
std::pmr::unsynchronized_pool_resource r(opts, &test_mr);
opts = r.options();
// Test unpooled allocations
void** p = new void*[opts.largest_required_pool_block];
for (unsigned a : {64, 128, 256, 512})
{
for (unsigned i = 0; i < opts.largest_required_pool_block; ++i)
p[i] = r.allocate(i, a);
for (unsigned i = 0; i < opts.largest_required_pool_block; ++i)
r.deallocate(p[i], i, a);
}
delete[] p;
}
void
test06()
{
struct checking_mr : std::pmr::memory_resource
{
size_t expected_size = 0;
size_t expected_alignment = 0;
struct bad_size { };
struct bad_alignment { };
void* do_allocate(std::size_t bytes, std::size_t align)
{
// Internal data structures in unsynchronized_pool_resource need to
// allocate memory, so handle those normally:
if (align <= alignof(std::max_align_t))
return std::pmr::new_delete_resource()->allocate(bytes, align);
// This is a large, unpooled allocation. Check the arguments:
if (bytes < expected_size)
throw bad_size();
else if (align != expected_alignment)
{
if (bytes == std::numeric_limits<std::size_t>::max()
&& align == (1 + std::numeric_limits<std::size_t>::max() / 2))
{
// Pool resources request bytes=SIZE_MAX && align=bit_floor(SIZE_MAX)
// when they are unable to meet an allocation request.
}
else
throw bad_alignment();
}
// Else just throw, don't really try to allocate:
throw std::bad_alloc();
}
void do_deallocate(void* p, std::size_t bytes, std::size_t align)
{ std::pmr::new_delete_resource()->deallocate(p, bytes, align); }
bool do_is_equal(const memory_resource&) const noexcept
{ return false; }
};
checking_mr c;
std::pmr::unsynchronized_pool_resource r({1, 1}, &c);
std::pmr::pool_options opts = r.options();
const std::size_t largest_pool = opts.largest_required_pool_block;
const std::size_t large_alignment = 1024;
// Ensure allocations won't fit in pools:
VERIFY( largest_pool < large_alignment );
// Ensure the vector of large allocations has some capacity
// and won't need to reallocate:
r.deallocate(r.allocate(largest_pool + 1, 1), largest_pool + 1, 1);
// Try allocating various very large sizes and ensure the size requested
// from the upstream allocator is at least as large as needed.
for (int i = 0; i < std::numeric_limits<std::size_t>::digits; ++i)
{
for (auto b : { -63, -5, -1, 0, 1, 3, std::numeric_limits<int>::max() })
{
std::size_t bytes = std::size_t(1) << i;
bytes += b; // For negative b this can wrap to a large positive value.
c.expected_size = bytes;
c.expected_alignment = large_alignment;
bool caught_bad_alloc = false;
try {
(void) r.allocate(bytes, large_alignment);
} catch (const std::bad_alloc&) {
// expect to catch bad_alloc
caught_bad_alloc = true;
} catch (checking_mr::bad_size) {
VERIFY( ! "allocation from upstream resource had expected size" );
} catch (checking_mr::bad_alignment) {
VERIFY( ! "allocation from upstream resource had expected alignment" );
}
VERIFY( caught_bad_alloc );
}
}
}
void
test07()
{
// Custom exception thrown on expected allocation failure.
struct very_bad_alloc : std::bad_alloc { };
struct careful_resource : __gnu_test::memory_resource
{
void* do_allocate(std::size_t bytes, std::size_t alignment)
{
// Need to allow normal allocations for the pool resource's internal
// data structures:
if (alignment < 1024)
return __gnu_test::memory_resource::do_allocate(bytes, alignment);
// pmr::unsynchronized_pool_resource::do_allocate is not allowed to
// throw an exception when asked for an allocation it can't satisfy.
// The libstdc++ implementation will ask upstream to allocate
// bytes=SIZE_MAX and alignment=bit_floor(SIZE_MAX) instead of throwing.
// Verify that we got those values:
if (bytes != std::numeric_limits<size_t>::max())
VERIFY( !"upstream allocation should request SIZE_MAX bytes" );
if (alignment != (1 + std::numeric_limits<size_t>::max() / 2))
VERIFY( !"upstream allocation should request SIZE_MAX/2 alignment" );
// A successful failure:
throw very_bad_alloc();
}
};
careful_resource cr;
std::pmr::unsynchronized_pool_resource upr(&cr);
try
{
// Try to allocate a ridiculous size (and use a large extended alignment
// so that careful_resource::do_allocate can distinguish this allocation
// from any required for the pool resource's internal data structures):
void* p = upr.allocate(std::size_t(-2), 1024);
// Should not reach here!
VERIFY( !"attempt to allocate SIZE_MAX-1 should not have succeeded" );
throw p;
}
catch (const very_bad_alloc&)
{
// Should catch this exception from careful_resource::do_allocate
}
catch (const std::bad_alloc&)
{
VERIFY( !"unsynchronized_pool_resource::do_allocate is not allowed to throw" );
}
}
void
test08()
{
std::pmr::pool_options opts;
opts.largest_required_pool_block = 64;
// PR libstdc++/94160
// max_blocks_per_chunk=1 causes pool resources to return null pointers
for (int i = 0; i < 8; ++i)
{
opts.max_blocks_per_chunk = i;
std::pmr::unsynchronized_pool_resource upr(opts);
auto* p = (int*)upr.allocate(4);
VERIFY( p != nullptr );
*p = i;
upr.deallocate(p, 4);
}
}
int
main()
{
test01();
test02();
test03();
test04();
test05();
test06();
test07();
test08();
}