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