1 /*
2     Copyright (c) 2005-2022 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 
18 //! \file test_scalable_allocator.cpp
19 //! \brief Test for [memory_allocation.scalable_allocator] functionality
20 
21 // Test whether scalable_allocator complies with the requirements in 20.1.5 of ISO C++ Standard (1998).
22 
23 #define __TBB_NO_IMPLICIT_LINKAGE 1
24 
25 #if _WIN32 || _WIN64
26 #define _CRT_SECURE_NO_WARNINGS
27 #endif
28 
29 #define __TBB_EXTRA_DEBUG 1 // enables additional checks
30 #define TBB_PREVIEW_MEMORY_POOL 1
31 #define HARNESS_TBBMALLOC_THREAD_SHUTDOWN 1
32 
33 #include "common/test.h"
34 #include "common/utils.h"
35 #include "common/utils_assert.h"
36 #include "common/custom_allocators.h"
37 
38 
39 #include "tbb/memory_pool.h"
40 #include "tbb/scalable_allocator.h"
41 
42 // The actual body of the tests
43 #include "common/allocator_test_common.h"
44 #include "common/allocator_stl_test_common.h"
45 
46 // #include "harness_allocator.h"
47 
48 #if _MSC_VER
49 #include <windows.h>
50 #endif /* _MSC_VER */
51 
52 typedef StaticCountingAllocator<tbb::memory_pool_allocator<char>> cnt_alloc_t;
53 typedef LocalCountingAllocator<std::allocator<char> > cnt_provider_t;
54 
55 class MinimalAllocator : cnt_provider_t {
56 public:
57     typedef char value_type;
MinimalAllocator()58     MinimalAllocator() {
59         // REMARK("%p::ctor\n", this);
60     }
MinimalAllocator(const MinimalAllocator & s)61     MinimalAllocator(const MinimalAllocator&s) : cnt_provider_t(s) {
62         // REMARK("%p::ctor(%p)\n", this, &s);
63     }
~MinimalAllocator()64     ~MinimalAllocator() {
65         /* REMARK("%p::dtor: alloc=%u/%u free=%u/%u\n", this, unsigned(items_allocated),unsigned(allocations),
66             unsigned(items_freed), unsigned(frees) ); */
67         REQUIRE((allocations==frees && items_allocated==items_freed));
68         if( allocations ) { // non-temporal copy
69             // TODO: describe consumption requirements
70             REQUIRE(items_allocated>cnt_alloc_t::items_allocated);
71         }
72     }
allocate(size_t sz)73     void *allocate(size_t sz) {
74         void *p = cnt_provider_t::allocate(sz);
75         // REMARK("%p::allocate(%u) = %p\n", this, unsigned(sz), p);
76         return p;
77     }
deallocate(void * p,size_t sz)78     void deallocate(void *p, size_t sz) {
79         REQUIRE(allocations>frees);
80         // REMARK("%p::deallocate(%p, %u)\n", this, p, unsigned(sz));
81         cnt_provider_t::deallocate(std::allocator_traits<cnt_provider_t>::pointer(p), sz);
82     }
83 };
84 
85 class NullAllocator {
86 public:
87     typedef char value_type;
NullAllocator()88     NullAllocator() { }
NullAllocator(const NullAllocator &)89     NullAllocator(const NullAllocator&) { }
~NullAllocator()90     ~NullAllocator() { }
allocate(size_t)91     void *allocate(size_t) { return nullptr; }
deallocate(void *,size_t)92     void deallocate(void *, size_t) { REQUIRE(false); }
93 };
94 
TestZeroSpaceMemoryPool()95 void TestZeroSpaceMemoryPool()
96 {
97     tbb::memory_pool<NullAllocator> pool;
98     bool allocated = pool.malloc(16) || pool.malloc(9*1024);
99     REQUIRE_MESSAGE(!allocated, "Allocator with no memory must not allocate anything.");
100 }
101 
102 #if !TBB_USE_EXCEPTIONS
103 struct FixedPool {
104     void  *buf;
105     size_t size;
106     bool   used;
FixedPoolFixedPool107     FixedPool(void *a_buf, size_t a_size) : buf(a_buf), size(a_size), used(false) {}
108 };
109 
fixedBufGetMem(intptr_t pool_id,size_t & bytes)110 static void *fixedBufGetMem(intptr_t pool_id, size_t &bytes)
111 {
112     if (((FixedPool*)pool_id)->used)
113         return nullptr;
114 
115     ((FixedPool*)pool_id)->used = true;
116     bytes = ((FixedPool*)pool_id)->size;
117     return bytes? ((FixedPool*)pool_id)->buf : nullptr;
118 }
119 #endif
120 
121 /* test that pools in small space are either usable or not created
122    (i.e., exception raised) */
TestSmallFixedSizePool()123 void TestSmallFixedSizePool()
124 {
125     char *buf;
126     bool allocated = false;
127 
128     for (size_t sz = 0; sz < 64*1024; sz = sz? 3*sz : 3) {
129         buf = (char*)malloc(sz);
130 #if TBB_USE_EXCEPTIONS
131         try {
132             tbb::fixed_pool pool(buf, sz);
133 /* Check that pool is usable, i.e. such an allocation exists,
134    that can be fulfilled from the pool. 16B allocation fits in 16KB slabs,
135    so it requires at least 16KB. Requirement of 9KB allocation is more modest.
136 */
137             allocated = pool.malloc( 16 ) || pool.malloc( 9*1024 );
138         } catch (std::invalid_argument&) {
139             REQUIRE_MESSAGE(!sz, "expect std::invalid_argument for zero-sized pool only");
140         } catch (...) {
141             REQUIRE_MESSAGE(false, "wrong exception type;");
142         }
143 #else
144 /* Do not test high-level pool interface because pool ctor emit exception
145    on creation failure. Instead test same functionality via low-level interface.
146    TODO: add support for configuration with disabled exceptions to pools.
147 */
148         rml::MemPoolPolicy pol(fixedBufGetMem, nullptr, 0, /*fixedSizePool=*/true,
149                                /*keepMemTillDestroy=*/false);
150         rml::MemoryPool *pool;
151         FixedPool fixedPool(buf, sz);
152 
153         rml::MemPoolError ret = pool_create_v1((intptr_t)&fixedPool, &pol, &pool);
154 
155         if (ret == rml::POOL_OK) {
156             allocated = pool_malloc(pool, 16) || pool_malloc(pool, 9*1024);
157             pool_destroy(pool);
158         } else
159             REQUIRE_MESSAGE(ret == rml::NO_MEMORY, "Expected that pool either valid or have no memory to be created");
160 #endif
161         free(buf);
162     }
163     REQUIRE_MESSAGE(allocated, "Maximal buf size should be enough to create working fixed_pool");
164 #if TBB_USE_EXCEPTIONS
165     try {
166         tbb::fixed_pool pool(nullptr, 10*1024*1024);
167         REQUIRE_MESSAGE(false, "Useless allocator with no memory must not be created");
168     } catch (std::invalid_argument&) {
169     } catch (...) {
170         REQUIRE_MESSAGE(false, "wrong exception type; expected invalid_argument");
171     }
172 #endif
173 }
174 
175 //! Testing ISO C++ allocator requirements
176 //! \brief \ref interface \ref requirement
177 TEST_CASE("Allocator concept") {
178 #if _MSC_VER && !__TBBMALLOC_NO_IMPLICIT_LINKAGE && !__TBB_WIN8UI_SUPPORT
179 #ifdef _DEBUG
180     REQUIRE_MESSAGE((!GetModuleHandle("tbbmalloc.dll") && GetModuleHandle("tbbmalloc_debug.dll")),
181         "test linked with wrong (non-debug) tbbmalloc library");
182 #else
183     REQUIRE_MESSAGE((!GetModuleHandle("tbbmalloc_debug.dll") && GetModuleHandle("tbbmalloc.dll")),
184         "test linked with wrong (debug) tbbmalloc library");
185 #endif // _DEBUG
186 #endif // _MSC_VER && !__TBBMALLOC_NO_IMPLICIT_LINKAGE
187     // allocate/deallocate
188     TestAllocator<tbb::scalable_allocator<void>>(Concept);
189     {
190         tbb::memory_pool<tbb::scalable_allocator<int>> pool;
191         TestAllocator(Concept, tbb::memory_pool_allocator<void>(pool));
192     }{
193         // tbb::memory_pool<MinimalAllocator> pool;
194         // cnt_alloc_t alloc( tbb::memory_pool_allocator<char>(pool) ); // double parentheses to avoid function declaration
195         // TestAllocator(Concept, alloc);
196     }{
197         static char buf[1024*1024*4];
198         tbb::fixed_pool pool(buf, sizeof(buf));
199         const char *text = "this is a test";// 15 bytes
200         char *p1 = (char*)pool.malloc( 16 );
201         REQUIRE(p1);
202         strcpy(p1, text);
203         char *p2 = (char*)pool.realloc( p1, 15 );
204         REQUIRE_MESSAGE( (p2 && !strcmp(p2, text)), "realloc broke memory" );
205 
206         TestAllocator(Concept, tbb::memory_pool_allocator<void>(pool) );
207 
208         // try allocate almost entire buf keeping some reasonable space for internals
209         char *p3 = (char*)pool.realloc( p2, sizeof(buf)-128*1024 );
210         REQUIRE_MESSAGE( p3, "defragmentation failed" );
211         REQUIRE_MESSAGE( !strcmp(p3, text), "realloc broke memory" );
212         for( size_t sz = 10; sz < sizeof(buf); sz *= 2) {
213             REQUIRE( pool.malloc( sz ) );
214             pool.recycle();
215         }
216 
217         TestAllocator(Concept, tbb::memory_pool_allocator<void>(pool) );
218     }{
219         // Two nested level allocators case with fixed pool allocator as an underlying layer
220         // serving allocRawMem requests for the top level scalable allocator
221         typedef tbb::memory_pool<tbb::memory_pool_allocator<char, tbb::fixed_pool> > NestedPool;
222 
223         static char buffer[8*1024*1024];
224         tbb::fixed_pool fixedPool(buffer, sizeof(buffer));
225         // Underlying fixed pool allocator
226         tbb::memory_pool_allocator<char, tbb::fixed_pool> fixedPoolAllocator(fixedPool);
227         // Memory pool that handles fixed pool allocator
228         NestedPool nestedPool(fixedPoolAllocator);
229         // Top level memory pool allocator
230         tbb::memory_pool_allocator<char, NestedPool> nestedAllocator(nestedPool);
231 
232         TestAllocator(Concept, nestedAllocator);
233     }
234     tbb::memory_pool<tbb::scalable_allocator<int>> mpool;
235 
236     tbb::memory_pool_allocator<int> mpalloc(mpool);
237 
238     TestAllocator<tbb::memory_pool_allocator<int>>(Concept, mpalloc);
239     TestAllocator<tbb::memory_pool_allocator<void>>(Concept, mpalloc);
240 
241     // operator==
242     TestAllocator<tbb::scalable_allocator<void>>(Comparison);
243     TestAllocator<tbb::memory_pool_allocator<void>>(Comparison, tbb::memory_pool_allocator<void>(mpool));
244     TestAllocator<tbb::memory_pool_allocator<int>>(Comparison, mpalloc);
245     TestAllocator<tbb::memory_pool_allocator<void>>(Comparison, mpalloc);
246 }
247 
248 #if TBB_USE_EXCEPTIONS
249 //! Testing exception guarantees
250 //! \brief \ref requirement
251 TEST_CASE("Exceptions") {
252     TestAllocator<tbb::scalable_allocator<void>>(Exceptions);
253 }
254 #endif /* TBB_USE_EXCEPTIONS */
255 
256 //! Testing allocators thread safety (should not introduce data races)
257 //! \brief \ref requirements
258 TEST_CASE("Thread safety") {
259     TestAllocator<tbb::scalable_allocator<void>>(ThreadSafety);
260 }
261 
262 //! Test that pools in small space are either usable or not created (i.e., exception raised)
263 //! \brief \ref error_guessing
264 TEST_CASE("Small fixed pool") {
265     TestSmallFixedSizePool();
266 }
267 
268 //! Test that allocator with no memory must not allocate anything.
269 //! \brief \ref error_guessing
270 TEST_CASE("Zero space pool") {
271     TestZeroSpaceMemoryPool();
272 }
273 
274 #if TBB_ALLOCATOR_TRAITS_BROKEN
275 //! Testing allocator traits is broken
276 //! \brief \ref error_guessing
277 TEST_CASE("Broken allocator concept") {
278     TestAllocator<tbb::scalable_allocator<void>>(Broken);
279 
280     tbb::memory_pool<tbb::scalable_allocator<int>> mpool;
281     TestAllocator<tbb::memory_pool_allocator<void>>(Broken, tbb::memory_pool_allocator<void>(mpool));
282 }
283 #endif
284 
285 
286 //! Testing allocators compatibility with STL containers
287 //! \brief \ref interface
288 TEST_CASE("Integration with STL containers") {
289     TestAllocatorWithSTL<tbb::scalable_allocator<void> >();
290     tbb::memory_pool<tbb::scalable_allocator<int> > mpool;
291     TestAllocatorWithSTL(tbb::memory_pool_allocator<void>(mpool) );
292     static char buf[1024*1024*4];
293     tbb::fixed_pool fpool(buf, sizeof(buf));
294     TestAllocatorWithSTL(tbb::memory_pool_allocator<void>(fpool) );
295 }
296 
297 #if __TBB_CPP17_MEMORY_RESOURCE_PRESENT
298 //! Testing memory resources compatibility with STL containers through std::pmr::polymorphic_allocator
299 //! \brief \ref interface
300 TEST_CASE("polymorphic_allocator test") {
301     REQUIRE_MESSAGE(!tbb::scalable_memory_resource()->is_equal(*std::pmr::get_default_resource()),
302             "Scalable resource shouldn't be equal to standard resource." );
303     REQUIRE_MESSAGE(tbb::scalable_memory_resource()->is_equal(*tbb::scalable_memory_resource()),
304             "Memory that was allocated by one scalable resource should be deallocated by any other instance.");
305 
306     typedef std::pmr::polymorphic_allocator<void> pmr_alloc_t;
307     TestAllocatorWithSTL(pmr_alloc_t(tbb::scalable_memory_resource()));
308 }
309 #endif
310 
311