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