151c0b2f7Stbbdev /*
2*b15aabb3Stbbdev     Copyright (c) 2005-2021 Intel Corporation
351c0b2f7Stbbdev 
451c0b2f7Stbbdev     Licensed under the Apache License, Version 2.0 (the "License");
551c0b2f7Stbbdev     you may not use this file except in compliance with the License.
651c0b2f7Stbbdev     You may obtain a copy of the License at
751c0b2f7Stbbdev 
851c0b2f7Stbbdev         http://www.apache.org/licenses/LICENSE-2.0
951c0b2f7Stbbdev 
1051c0b2f7Stbbdev     Unless required by applicable law or agreed to in writing, software
1151c0b2f7Stbbdev     distributed under the License is distributed on an "AS IS" BASIS,
1251c0b2f7Stbbdev     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1351c0b2f7Stbbdev     See the License for the specific language governing permissions and
1451c0b2f7Stbbdev     limitations under the License.
1551c0b2f7Stbbdev */
1651c0b2f7Stbbdev 
1751c0b2f7Stbbdev 
1851c0b2f7Stbbdev //! \file test_scalable_allocator.cpp
1951c0b2f7Stbbdev //! \brief Test for [memory_allocation.scalable_allocator] functionality
2051c0b2f7Stbbdev 
2151c0b2f7Stbbdev // Test whether scalable_allocator complies with the requirements in 20.1.5 of ISO C++ Standard (1998).
2251c0b2f7Stbbdev 
2351c0b2f7Stbbdev #define __TBB_NO_IMPLICIT_LINKAGE 1
2451c0b2f7Stbbdev 
2551c0b2f7Stbbdev #include "common/test.h"
2651c0b2f7Stbbdev #include "common/utils.h"
2751c0b2f7Stbbdev #include "common/utils_assert.h"
2851c0b2f7Stbbdev #include "common/custom_allocators.h"
2951c0b2f7Stbbdev 
3051c0b2f7Stbbdev #define __TBB_EXTRA_DEBUG 1 // enables additional checks
3151c0b2f7Stbbdev #define TBB_PREVIEW_MEMORY_POOL 1
3251c0b2f7Stbbdev 
3351c0b2f7Stbbdev #include "tbb/memory_pool.h"
3451c0b2f7Stbbdev #include "tbb/scalable_allocator.h"
3551c0b2f7Stbbdev 
3651c0b2f7Stbbdev // The actual body of the tests
3751c0b2f7Stbbdev #include "common/allocator_test_common.h"
3851c0b2f7Stbbdev #include "common/allocator_stl_test_common.h"
3951c0b2f7Stbbdev 
4051c0b2f7Stbbdev #define HARNESS_TBBMALLOC_THREAD_SHUTDOWN 1
4151c0b2f7Stbbdev // #include "harness_allocator.h"
4251c0b2f7Stbbdev 
4351c0b2f7Stbbdev #if _MSC_VER
4451c0b2f7Stbbdev #include <windows.h>
4551c0b2f7Stbbdev #endif /* _MSC_VER */
4651c0b2f7Stbbdev 
4751c0b2f7Stbbdev typedef StaticCountingAllocator<tbb::memory_pool_allocator<char>> cnt_alloc_t;
4851c0b2f7Stbbdev typedef LocalCountingAllocator<std::allocator<char> > cnt_provider_t;
4951c0b2f7Stbbdev 
5051c0b2f7Stbbdev class MinimalAllocator : cnt_provider_t {
5151c0b2f7Stbbdev public:
5251c0b2f7Stbbdev     typedef char value_type;
5351c0b2f7Stbbdev     MinimalAllocator() {
5451c0b2f7Stbbdev         // REMARK("%p::ctor\n", this);
5551c0b2f7Stbbdev     }
5651c0b2f7Stbbdev     MinimalAllocator(const MinimalAllocator&s) : cnt_provider_t(s) {
5751c0b2f7Stbbdev         // REMARK("%p::ctor(%p)\n", this, &s);
5851c0b2f7Stbbdev     }
5951c0b2f7Stbbdev     ~MinimalAllocator() {
6051c0b2f7Stbbdev         /* REMARK("%p::dtor: alloc=%u/%u free=%u/%u\n", this, unsigned(items_allocated),unsigned(allocations),
6151c0b2f7Stbbdev             unsigned(items_freed), unsigned(frees) ); */
6251c0b2f7Stbbdev         REQUIRE((allocations==frees && items_allocated==items_freed));
6351c0b2f7Stbbdev         if( allocations ) { // non-temporal copy
6451c0b2f7Stbbdev             // TODO: describe consumption requirements
6551c0b2f7Stbbdev             REQUIRE(items_allocated>cnt_alloc_t::items_allocated);
6651c0b2f7Stbbdev         }
6751c0b2f7Stbbdev     }
6851c0b2f7Stbbdev     void *allocate(size_t sz) {
6951c0b2f7Stbbdev         void *p = cnt_provider_t::allocate(sz);
7051c0b2f7Stbbdev         // REMARK("%p::allocate(%u) = %p\n", this, unsigned(sz), p);
7151c0b2f7Stbbdev         return p;
7251c0b2f7Stbbdev     }
7351c0b2f7Stbbdev     void deallocate(void *p, size_t sz) {
7451c0b2f7Stbbdev         REQUIRE(allocations>frees);
7551c0b2f7Stbbdev         // REMARK("%p::deallocate(%p, %u)\n", this, p, unsigned(sz));
76*b15aabb3Stbbdev         cnt_provider_t::deallocate(std::allocator_traits<cnt_provider_t>::pointer(p), sz);
7751c0b2f7Stbbdev     }
7851c0b2f7Stbbdev };
7951c0b2f7Stbbdev 
8051c0b2f7Stbbdev class NullAllocator {
8151c0b2f7Stbbdev public:
8251c0b2f7Stbbdev     typedef char value_type;
8351c0b2f7Stbbdev     NullAllocator() { }
8451c0b2f7Stbbdev     NullAllocator(const NullAllocator&) { }
8551c0b2f7Stbbdev     ~NullAllocator() { }
8651c0b2f7Stbbdev     void *allocate(size_t) { return NULL; }
8751c0b2f7Stbbdev     void deallocate(void *, size_t) { REQUIRE(false); }
8851c0b2f7Stbbdev };
8951c0b2f7Stbbdev 
9051c0b2f7Stbbdev void TestZeroSpaceMemoryPool()
9151c0b2f7Stbbdev {
9251c0b2f7Stbbdev     tbb::memory_pool<NullAllocator> pool;
9351c0b2f7Stbbdev     bool allocated = pool.malloc(16) || pool.malloc(9*1024);
9451c0b2f7Stbbdev     REQUIRE_MESSAGE(!allocated, "Allocator with no memory must not allocate anything.");
9551c0b2f7Stbbdev }
9651c0b2f7Stbbdev 
9751c0b2f7Stbbdev #if !TBB_USE_EXCEPTIONS
9851c0b2f7Stbbdev struct FixedPool {
9951c0b2f7Stbbdev     void  *buf;
10051c0b2f7Stbbdev     size_t size;
10151c0b2f7Stbbdev     bool   used;
10251c0b2f7Stbbdev     FixedPool(void *a_buf, size_t a_size) : buf(a_buf), size(a_size), used(false) {}
10351c0b2f7Stbbdev };
10451c0b2f7Stbbdev 
10551c0b2f7Stbbdev static void *fixedBufGetMem(intptr_t pool_id, size_t &bytes)
10651c0b2f7Stbbdev {
10751c0b2f7Stbbdev     if (((FixedPool*)pool_id)->used)
10851c0b2f7Stbbdev         return NULL;
10951c0b2f7Stbbdev 
11051c0b2f7Stbbdev     ((FixedPool*)pool_id)->used = true;
11151c0b2f7Stbbdev     bytes = ((FixedPool*)pool_id)->size;
11251c0b2f7Stbbdev     return bytes? ((FixedPool*)pool_id)->buf : NULL;
11351c0b2f7Stbbdev }
11451c0b2f7Stbbdev #endif
11551c0b2f7Stbbdev 
11651c0b2f7Stbbdev /* test that pools in small space are either usable or not created
11751c0b2f7Stbbdev    (i.e., exception raised) */
11851c0b2f7Stbbdev void TestSmallFixedSizePool()
11951c0b2f7Stbbdev {
12051c0b2f7Stbbdev     char *buf;
12151c0b2f7Stbbdev     bool allocated = false;
12251c0b2f7Stbbdev 
12351c0b2f7Stbbdev     for (size_t sz = 0; sz < 64*1024; sz = sz? 3*sz : 3) {
12451c0b2f7Stbbdev         buf = (char*)malloc(sz);
12551c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
12651c0b2f7Stbbdev         try {
12751c0b2f7Stbbdev             tbb::fixed_pool pool(buf, sz);
12851c0b2f7Stbbdev /* Check that pool is usable, i.e. such an allocation exists,
12951c0b2f7Stbbdev    that can be fulfilled from the pool. 16B allocation fits in 16KB slabs,
13051c0b2f7Stbbdev    so it requires at least 16KB. Requirement of 9KB allocation is more modest.
13151c0b2f7Stbbdev */
13251c0b2f7Stbbdev             allocated = pool.malloc( 16 ) || pool.malloc( 9*1024 );
13351c0b2f7Stbbdev         } catch (std::invalid_argument&) {
13451c0b2f7Stbbdev             REQUIRE_MESSAGE(!sz, "expect std::invalid_argument for zero-sized pool only");
13551c0b2f7Stbbdev         } catch (...) {
13651c0b2f7Stbbdev             REQUIRE_MESSAGE(false, "wrong exception type;");
13751c0b2f7Stbbdev         }
13851c0b2f7Stbbdev #else
13951c0b2f7Stbbdev /* Do not test high-level pool interface because pool ctor emit exception
14051c0b2f7Stbbdev    on creation failure. Instead test same functionality via low-level interface.
14151c0b2f7Stbbdev    TODO: add support for configuration with disabled exceptions to pools.
14251c0b2f7Stbbdev */
14351c0b2f7Stbbdev         rml::MemPoolPolicy pol(fixedBufGetMem, NULL, 0, /*fixedSizePool=*/true,
14451c0b2f7Stbbdev                                /*keepMemTillDestroy=*/false);
14551c0b2f7Stbbdev         rml::MemoryPool *pool;
14651c0b2f7Stbbdev         FixedPool fixedPool(buf, sz);
14751c0b2f7Stbbdev 
14851c0b2f7Stbbdev         rml::MemPoolError ret = pool_create_v1((intptr_t)&fixedPool, &pol, &pool);
14951c0b2f7Stbbdev 
15051c0b2f7Stbbdev         if (ret == rml::POOL_OK) {
15151c0b2f7Stbbdev             allocated = pool_malloc(pool, 16) || pool_malloc(pool, 9*1024);
15251c0b2f7Stbbdev             pool_destroy(pool);
15351c0b2f7Stbbdev         } else
15451c0b2f7Stbbdev             REQUIRE_MESSAGE(ret == rml::NO_MEMORY, "Expected that pool either valid or have no memory to be created");
15551c0b2f7Stbbdev #endif
15651c0b2f7Stbbdev         free(buf);
15751c0b2f7Stbbdev     }
15851c0b2f7Stbbdev     REQUIRE_MESSAGE(allocated, "Maximal buf size should be enough to create working fixed_pool");
15951c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
16051c0b2f7Stbbdev     try {
16151c0b2f7Stbbdev         tbb::fixed_pool pool(NULL, 10*1024*1024);
16251c0b2f7Stbbdev         REQUIRE_MESSAGE(false, "Useless allocator with no memory must not be created");
16351c0b2f7Stbbdev     } catch (std::invalid_argument&) {
16451c0b2f7Stbbdev     } catch (...) {
16551c0b2f7Stbbdev         REQUIRE_MESSAGE(false, "wrong exception type; expected invalid_argument");
16651c0b2f7Stbbdev     }
16751c0b2f7Stbbdev #endif
16851c0b2f7Stbbdev }
16951c0b2f7Stbbdev 
17051c0b2f7Stbbdev //! Testing ISO C++ allocator requirements
17151c0b2f7Stbbdev //! \brief \ref interface \ref requirement
17251c0b2f7Stbbdev TEST_CASE("Allocator concept") {
17351c0b2f7Stbbdev #if _MSC_VER && !__TBBMALLOC_NO_IMPLICIT_LINKAGE && !__TBB_WIN8UI_SUPPORT
17451c0b2f7Stbbdev #ifdef _DEBUG
17551c0b2f7Stbbdev     REQUIRE_MESSAGE((!GetModuleHandle("tbbmalloc.dll") && GetModuleHandle("tbbmalloc_debug.dll")),
17651c0b2f7Stbbdev         "test linked with wrong (non-debug) tbbmalloc library");
17751c0b2f7Stbbdev #else
17851c0b2f7Stbbdev     REQUIRE_MESSAGE((!GetModuleHandle("tbbmalloc_debug.dll") && GetModuleHandle("tbbmalloc.dll")),
17951c0b2f7Stbbdev         "test linked with wrong (debug) tbbmalloc library");
18051c0b2f7Stbbdev #endif // _DEBUG
18151c0b2f7Stbbdev #endif // _MSC_VER && !__TBBMALLOC_NO_IMPLICIT_LINKAGE
18251c0b2f7Stbbdev     // allocate/deallocate
18351c0b2f7Stbbdev     TestAllocator<tbb::scalable_allocator<void>>(Concept);
18451c0b2f7Stbbdev     {
18551c0b2f7Stbbdev         tbb::memory_pool<tbb::scalable_allocator<int>> pool;
18651c0b2f7Stbbdev         TestAllocator(Concept, tbb::memory_pool_allocator<void>(pool));
18751c0b2f7Stbbdev     }{
18851c0b2f7Stbbdev         // tbb::memory_pool<MinimalAllocator> pool;
18951c0b2f7Stbbdev         // cnt_alloc_t alloc( tbb::memory_pool_allocator<char>(pool) ); // double parentheses to avoid function declaration
19051c0b2f7Stbbdev         // TestAllocator(Concept, alloc);
19151c0b2f7Stbbdev     }{
19251c0b2f7Stbbdev         static char buf[1024*1024*4];
19351c0b2f7Stbbdev         tbb::fixed_pool pool(buf, sizeof(buf));
19451c0b2f7Stbbdev         const char *text = "this is a test";// 15 bytes
19551c0b2f7Stbbdev         char *p1 = (char*)pool.malloc( 16 );
19651c0b2f7Stbbdev         REQUIRE(p1);
19751c0b2f7Stbbdev         strcpy(p1, text);
19851c0b2f7Stbbdev         char *p2 = (char*)pool.realloc( p1, 15 );
19951c0b2f7Stbbdev         REQUIRE_MESSAGE( (p2 && !strcmp(p2, text)), "realloc broke memory" );
20051c0b2f7Stbbdev 
20151c0b2f7Stbbdev         TestAllocator(Concept, tbb::memory_pool_allocator<void>(pool) );
20251c0b2f7Stbbdev 
20351c0b2f7Stbbdev         // try allocate almost entire buf keeping some reasonable space for internals
20451c0b2f7Stbbdev         char *p3 = (char*)pool.realloc( p2, sizeof(buf)-128*1024 );
20551c0b2f7Stbbdev         REQUIRE_MESSAGE( p3, "defragmentation failed" );
20651c0b2f7Stbbdev         REQUIRE_MESSAGE( !strcmp(p3, text), "realloc broke memory" );
20751c0b2f7Stbbdev         for( size_t sz = 10; sz < sizeof(buf); sz *= 2) {
20851c0b2f7Stbbdev             REQUIRE( pool.malloc( sz ) );
20951c0b2f7Stbbdev             pool.recycle();
21051c0b2f7Stbbdev         }
21151c0b2f7Stbbdev 
21251c0b2f7Stbbdev         TestAllocator(Concept, tbb::memory_pool_allocator<void>(pool) );
21351c0b2f7Stbbdev     }{
21451c0b2f7Stbbdev         // Two nested level allocators case with fixed pool allocator as an underlying layer
21551c0b2f7Stbbdev         // serving allocRawMem requests for the top level scalable allocator
21651c0b2f7Stbbdev         typedef tbb::memory_pool<tbb::memory_pool_allocator<char, tbb::fixed_pool> > NestedPool;
21751c0b2f7Stbbdev 
21851c0b2f7Stbbdev         static char buffer[8*1024*1024];
21951c0b2f7Stbbdev         tbb::fixed_pool fixedPool(buffer, sizeof(buffer));
22051c0b2f7Stbbdev         // Underlying fixed pool allocator
22151c0b2f7Stbbdev         tbb::memory_pool_allocator<char, tbb::fixed_pool> fixedPoolAllocator(fixedPool);
22251c0b2f7Stbbdev         // Memory pool that handles fixed pool allocator
22351c0b2f7Stbbdev         NestedPool nestedPool(fixedPoolAllocator);
22451c0b2f7Stbbdev         // Top level memory pool allocator
22551c0b2f7Stbbdev         tbb::memory_pool_allocator<char, NestedPool> nestedAllocator(nestedPool);
22651c0b2f7Stbbdev 
22751c0b2f7Stbbdev         TestAllocator(Concept, nestedAllocator);
22851c0b2f7Stbbdev     }
22951c0b2f7Stbbdev 
23051c0b2f7Stbbdev     // operator==
23151c0b2f7Stbbdev     TestAllocator<tbb::scalable_allocator<void>>(Comparison);
23251c0b2f7Stbbdev }
23351c0b2f7Stbbdev 
23451c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
23551c0b2f7Stbbdev //! Testing exception guarantees
23651c0b2f7Stbbdev //! \brief \ref requirement
23751c0b2f7Stbbdev TEST_CASE("Exceptions") {
23851c0b2f7Stbbdev     TestAllocator<tbb::scalable_allocator<void>>(Exceptions);
23951c0b2f7Stbbdev }
24051c0b2f7Stbbdev #endif /* TBB_USE_EXCEPTIONS */
24151c0b2f7Stbbdev 
24251c0b2f7Stbbdev //! Testing allocators thread safety (should not introduce data races)
24351c0b2f7Stbbdev //! \brief \ref requirements
24451c0b2f7Stbbdev TEST_CASE("Thread safety") {
24551c0b2f7Stbbdev     TestAllocator<tbb::scalable_allocator<void>>(ThreadSafety);
24651c0b2f7Stbbdev }
24751c0b2f7Stbbdev 
24851c0b2f7Stbbdev //! Test that pools in small space are either usable or not created (i.e., exception raised)
24951c0b2f7Stbbdev //! \brief \ref error_guessing
25051c0b2f7Stbbdev TEST_CASE("Small fixed pool") {
25151c0b2f7Stbbdev     TestSmallFixedSizePool();
25251c0b2f7Stbbdev }
25351c0b2f7Stbbdev 
25451c0b2f7Stbbdev //! Test that allocator with no memory must not allocate anything.
25551c0b2f7Stbbdev //! \brief \ref error_guessing
25651c0b2f7Stbbdev TEST_CASE("Zero space pool") {
25751c0b2f7Stbbdev     TestZeroSpaceMemoryPool();
25851c0b2f7Stbbdev }
25951c0b2f7Stbbdev 
26051c0b2f7Stbbdev //! Testing allocators compatibility with STL containers
26151c0b2f7Stbbdev //! \brief \ref interface
26251c0b2f7Stbbdev TEST_CASE("Integration with STL containers") {
26351c0b2f7Stbbdev     TestAllocatorWithSTL<tbb::scalable_allocator<void> >();
26451c0b2f7Stbbdev     tbb::memory_pool<tbb::scalable_allocator<int> > mpool;
26551c0b2f7Stbbdev     TestAllocatorWithSTL(tbb::memory_pool_allocator<void>(mpool) );
26651c0b2f7Stbbdev     static char buf[1024*1024*4];
26751c0b2f7Stbbdev     tbb::fixed_pool fpool(buf, sizeof(buf));
26851c0b2f7Stbbdev     TestAllocatorWithSTL(tbb::memory_pool_allocator<void>(fpool) );
26951c0b2f7Stbbdev }
27051c0b2f7Stbbdev 
27151c0b2f7Stbbdev #if __TBB_CPP17_MEMORY_RESOURCE_PRESENT
27251c0b2f7Stbbdev //! Testing memory resources compatibility with STL containers through std::pmr::polymorphic_allocator
27351c0b2f7Stbbdev //! \brief \ref interface
27451c0b2f7Stbbdev TEST_CASE("polymorphic_allocator test") {
27551c0b2f7Stbbdev     REQUIRE_MESSAGE(!tbb::scalable_memory_resource()->is_equal(*std::pmr::get_default_resource()),
27651c0b2f7Stbbdev             "Scalable resource shouldn't be equal to standard resource." );
27751c0b2f7Stbbdev     REQUIRE_MESSAGE(tbb::scalable_memory_resource()->is_equal(*tbb::scalable_memory_resource()),
27851c0b2f7Stbbdev             "Memory that was allocated by one scalable resource should be deallocated by any other instance.");
27951c0b2f7Stbbdev 
28051c0b2f7Stbbdev     typedef std::pmr::polymorphic_allocator<void> pmr_alloc_t;
28151c0b2f7Stbbdev     TestAllocatorWithSTL(pmr_alloc_t(tbb::scalable_memory_resource()));
28251c0b2f7Stbbdev }
28351c0b2f7Stbbdev #endif
28451c0b2f7Stbbdev 
285