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 // Basic testing of an allocator
18 // Tests against requirements in 20.1.5 of ISO C++ Standard (1998).
19 // Does not check for thread safety or false sharing issues.
20 //
21 // Tests for compatibility with the host's STL are in
22 // test_Allocator_STL.h. Those tests are in a separate file
23 // because they bring in lots of STL headers, and the tests here
24 // are supposed to work in the abscense of STL.
25
26 #ifndef __TBB_test_common_allocator_test_common_H_
27 #define __TBB_test_common_allocator_test_common_H_
28
29 #include "common/test.h"
30 #include "common/utils.h"
31 #include <utility> //for std::pair
32 #include <cstring>
33
34 //! Compile-time error if x and y have different types
35 template<typename T>
AssertSameType(const T &,const T &)36 void AssertSameType( const T& /*x*/, const T& /*y*/ ) {}
37
38 //! The function to zero-initialize arrays; useful to avoid warnings
39 template <typename T>
zero_fill(void * array,size_t n)40 void zero_fill(void* array, size_t n) {
41 memset(array, 0, sizeof(T)*n);
42 }
43
44 template<typename A>
45 struct is_zero_filling {
46 static const bool value = false;
47 };
48
49 int NumberOfFoo;
50
51 template<typename T, size_t N>
52 struct Foo {
53 T foo_array[N];
FooFoo54 Foo() {
55 zero_fill<T>(foo_array, N);
56 ++NumberOfFoo;
57 }
FooFoo58 Foo( const Foo& x ) {
59 *this = x;
60 //Internal call of assignment
61 }
62 Foo& operator=( const Foo& x ) {
63 for (size_t i = 0; i < N; i++)
64 foo_array[i] = x.foo_array[i];
65 ++NumberOfFoo;
66 return *this;
67 }
68
~FooFoo69 ~Foo() {
70 --NumberOfFoo;
71 }
72 };
73
PseudoRandomValue(size_t j,size_t k)74 inline char PseudoRandomValue( size_t j, size_t k ) {
75 return char(j*3 ^ j>>4 ^ k);
76 }
77
78 #if __APPLE__
79 #include <fcntl.h>
80 #include <unistd.h>
81
82 // A RAII class to disable stderr in a certain scope. It's not thread-safe.
83 class DisableStderr {
84 int stderrCopy;
dupToStderrAndClose(int fd)85 static void dupToStderrAndClose(int fd) {
86 int ret = dup2(fd, STDERR_FILENO); // close current stderr
87 REQUIRE(ret != -1);
88 ret = close(fd);
89 REQUIRE(ret != -1);
90 }
91 public:
DisableStderr()92 DisableStderr() {
93 int devNull = open("/dev/null", O_WRONLY);
94 REQUIRE(devNull != -1);
95 stderrCopy = dup(STDERR_FILENO);
96 REQUIRE(stderrCopy != -1);
97 dupToStderrAndClose(devNull);
98 }
~DisableStderr()99 ~DisableStderr() {
100 dupToStderrAndClose(stderrCopy);
101 }
102 };
103 #endif
104
105 //! T is type and A is allocator for that type
106 template<typename T, typename A>
TestBrokenAllocator(A & a)107 void TestBrokenAllocator(A& a) {
108 T x;
109 const T cx = T();
110 // See Table 32 in ISO ++ Standard
111 typename A::pointer px = &x;
112 typename A::const_pointer pcx = &cx;
113
114 typename A::reference rx = x;
115 REQUIRE(&rx == &x);
116
117 typename A::const_reference rcx = cx;
118 REQUIRE(&rcx==&cx);
119
120 typename A::value_type v = x;
121
122 typename A::size_type size;
123 size = 0;
124 --size;
125 REQUIRE_MESSAGE(size > 0, "not an unsigned integral type?");
126
127 typename A::difference_type difference;
128 difference = 0;
129 --difference;
130 REQUIRE_MESSAGE(difference<0, "not an signed integral type?");
131
132 // "rebind" tested by our caller
133
134 REQUIRE(a.address(rx) == px);
135
136 REQUIRE(a.address(rcx) == pcx);
137
138 // Test "a.max_size()"
139 AssertSameType(a.max_size(), typename A::size_type(0));
140 // Following assertion catches case where max_size() is so large that computation of
141 // number of bytes for such an allocation would overflow size_type.
142 REQUIRE_MESSAGE((a.max_size() * typename A::size_type(sizeof(T)) >= a.max_size()), "max_size larger than reasonable");
143
144 // Test "a.construct(p,t)"
145 int n = NumberOfFoo;
146 typename A::pointer p = a.allocate(1);
147 a.construct(p, cx);
148 REQUIRE_MESSAGE(NumberOfFoo == n + 1, "constructor for Foo not called?");
149
150 // Test "a.destroy(p)"
151 a.destroy(p);
152 REQUIRE_MESSAGE(NumberOfFoo == n, "destructor for Foo not called?");
153 a.deallocate(p, 1);
154
155 {
156 typedef typename A::template rebind<std::pair<typename A::value_type, typename A::value_type> >::other pair_allocator_type;
157 pair_allocator_type pair_allocator(a);
158 int NumberOfFooBeforeConstruct = NumberOfFoo;
159 typename pair_allocator_type::pointer pair_pointer = pair_allocator.allocate(1);
160 pair_allocator.construct(pair_pointer, cx, cx);
161 REQUIRE_MESSAGE(NumberOfFoo == NumberOfFooBeforeConstruct+2, "constructor for Foo not called appropriate number of times?");
162
163 pair_allocator.destroy(pair_pointer);
164 REQUIRE_MESSAGE(NumberOfFoo == NumberOfFooBeforeConstruct, "destructor for Foo not called appropriate number of times?");
165 pair_allocator.deallocate(pair_pointer, 1);
166 }
167 }
168
169 //! T is type and A is allocator for that type
170 template<typename T, typename A>
TestAllocatorConcept(A & a)171 void TestAllocatorConcept(A& a) {
172 // Test "a.allocate(p,n)
173 typename std::allocator_traits<A>::pointer array[100];
174 std::size_t sizeof_T = sizeof(T);
175 for(std::size_t k = 0; k < 100; ++k) {
176 array[k] = a.allocate(k);
177 char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[k]));
178 for(std::size_t j=0; j < k * sizeof_T; ++j)
179 s[j] = PseudoRandomValue(j, k);
180 }
181
182 // Test "a.deallocate(p,n)
183 for(std::size_t k = 0; k < 100; ++k) {
184 char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[k]));
185 for(std::size_t j = 0; j < k * sizeof_T; ++j)
186 REQUIRE(s[j] == PseudoRandomValue(j, k));
187 a.deallocate(array[k], k);
188 }
189 }
190
191 //! T is type and A is allocator for that type
192 template<typename T, typename A>
TestAllocatorExceptions(A & a)193 void TestAllocatorExceptions(A& a) {
194 #if TBB_USE_EXCEPTIONS
195 volatile size_t too_big = (~std::size_t(0) - 1024 * 1024) / sizeof(T);
196 bool exception_caught = false;
197 typename std::allocator_traits<A>::pointer p1 = nullptr;
198 try {
199 #if __APPLE__
200 // On macOS*, failure to map memory results in messages to stderr;
201 // suppress them.
202 DisableStderr disableStderr;
203 #endif
204 p1 = a.allocate(too_big);
205 } catch (std::bad_alloc&) {
206 exception_caught = true;
207 }
208 REQUIRE_MESSAGE(exception_caught, "allocate expected to throw bad_alloc");
209 a.deallocate(p1, too_big);
210 #endif // TBB_USE_EXCEPTIONS
211 utils::suppress_unused_warning(a);
212 }
213
214 #if _MSC_VER && !defined(__INTEL_COMPILER)
215 // Workaround for erroneous "conditional expression is constant" warning in method check_allocate.
216 #pragma warning (disable: 4127)
217 #endif
218
219 // A is an allocator for some type
220 template<typename A>
221 struct Body: utils::NoAssign {
222 using pointer_type = typename std::allocator_traits<A>::pointer;
223 using value_type = typename std::allocator_traits<A>::value_type;
224 // For the int types and above this test runs too long
225 static const std::size_t max_k = sizeof(value_type) < sizeof(int) ? 100000 : 5000;
226 A &a;
BodyBody227 Body(A &a_) : a(a_) {}
check_allocateBody228 void check_allocate(pointer_type array[], std::size_t i, std::size_t t) const
229 {
230 REQUIRE(array[i] == nullptr);
231 std::size_t size = i * (i & 3);
232 array[i] = a.allocate(size);
233 REQUIRE_MESSAGE(array[i] != nullptr, "allocator returned null");
234 char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[i]));
235 for(std::size_t j = 0; j < size * sizeof(value_type); ++j) {
236 if(is_zero_filling<typename std::allocator_traits<A>::template rebind_alloc<void>>::value)
237 REQUIRE(!s[j]);
238 s[j] = PseudoRandomValue(i, t);
239 }
240 }
241
check_deallocateBody242 void check_deallocate(pointer_type array[], std::size_t i, std::size_t t) const
243 {
244 REQUIRE(array[i] != nullptr);
245 size_t size = i * (i & 3);
246 char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[i]));
247 for(std::size_t j=0; j < size * sizeof(value_type); ++j)
248 REQUIRE_MESSAGE(s[j] == PseudoRandomValue(i, t), "Thread safety test failed");
249 a.deallocate(array[i], size);
250 array[i] = nullptr;
251 }
252
operatorBody253 void operator()(std::size_t thread_id) const {
254 pointer_type array[256];
255
256 for(std::size_t k = 0; k < 256; ++k)
257 array[k] = nullptr;
258 for(std::size_t k = 0; k < max_k; ++k) {
259 std::size_t i = static_cast<unsigned char>(PseudoRandomValue(k, thread_id));
260 if(!array[i]) check_allocate(array, i, thread_id);
261 else check_deallocate(array, i, thread_id);
262 }
263 for(std::size_t k = 0; k < 256; ++k)
264 if(array[k])
265 check_deallocate(array, k, thread_id);
266 }
267 };
268
269 template<typename A>
TestThreadSafety(A & a)270 void TestThreadSafety(A &a) {
271 utils::NativeParallelFor(4, Body<A>(a));
272 }
273
274 enum TestName { Concept, Broken, Exceptions, ThreadSafety, Comparison };
275
276 template<typename Allocator>
277 void TestAllocator(TestName name, const Allocator &a = Allocator()) {
278
279 using FooChar = Foo<char, 1>;
280 using FooDouble = Foo<double, 1>;
281 using FooInt = Foo<int, 17>;
282 using FooFloat = Foo<float, 23>;
283 #if TBB_ALLOCATOR_TRAITS_BROKEN
284 using AllocatorFooChar = typename Allocator::template rebind<FooChar>::other;
285 using AllocatorFooDouble = typename Allocator::template rebind<FooDouble>::other;
286 using AllocatorFooInt = typename AllocatorFooChar::template rebind<FooInt>::other;
287 using AllocatorFooFloat = typename AllocatorFooDouble::template rebind<FooFloat>::other;
288 #else
289 using AllocatorFooChar = typename std::allocator_traits<Allocator>::template rebind_alloc<FooChar>;
290 using AllocatorFooDouble = typename std::allocator_traits<Allocator>::template rebind_alloc<FooDouble>;
291 using AllocatorFooInt = typename std::allocator_traits<AllocatorFooChar>::template rebind_alloc<FooInt>;
292 using AllocatorFooFloat = typename std::allocator_traits<AllocatorFooDouble>::template rebind_alloc<FooFloat>;
293 #endif
294
295 NumberOfFoo = 0;
296 Allocator a_cpy(a);
297 AllocatorFooChar a1(a);
298 AllocatorFooDouble a2(a);
299 AllocatorFooInt b1(a1);
300 AllocatorFooFloat b2(a2);
301
302 switch(name) {
303 case Comparison:
304 REQUIRE(a_cpy == a);
305 REQUIRE(a1 == b1);
306 REQUIRE(!(a2 != b2));
307 break;
308 case Concept:
309 TestAllocatorConcept<FooInt>(b1);
310 TestAllocatorConcept<typename AllocatorFooChar::value_type>(a1);
311 TestAllocatorConcept<FooFloat>(b2);
312 TestAllocatorConcept<typename AllocatorFooDouble::value_type>(a2);
313 break;
314 case Broken:
315 #if TBB_ALLOCATOR_TRAITS_BROKEN
316 TestBrokenAllocator<FooInt>(b1);
317 TestBrokenAllocator<typename AllocatorFooChar::value_type>(a1);
318 TestBrokenAllocator<FooFloat>(b2);
319 TestBrokenAllocator<typename AllocatorFooDouble::value_type>(a2);
320 #endif
321 break;
322 case Exceptions:
323 TestAllocatorExceptions<FooInt>(b1);
324 TestAllocatorExceptions<typename AllocatorFooChar::value_type>(a1);
325 TestAllocatorExceptions<FooFloat>(b2);
326 TestAllocatorExceptions<typename AllocatorFooDouble::value_type>(a2);
327 break;
328 case ThreadSafety:
329 TestThreadSafety(a1);
330 TestThreadSafety(a2);
331 break;
332 }
333 REQUIRE_MESSAGE(NumberOfFoo == 0, "Allocate/deallocate count mismatched");
334 }
335 #endif // __TBB_test_common_allocator_test_common_H_
336