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 #include "common/test.h"
18 #include "common/utils.h"
19 #include "common/utils_report.h"
20 #include "common/checktype.h"
21
22 #include "oneapi/tbb/detail/_utils.h"
23
24 #include "tbb/enumerable_thread_specific.h"
25 #include "tbb/parallel_for.h"
26 #include "tbb/blocked_range.h"
27 #include "tbb/tbb_allocator.h"
28 #include "tbb/global_control.h"
29 #include "tbb/cache_aligned_allocator.h"
30
31 #include <cstring>
32 #include <cstdio>
33 #include <vector>
34 #include <deque>
35 #include <list>
36 #include <map>
37 #include <utility>
38 #include <atomic>
39
40 //! \file test_enumerable_thread_specific.cpp
41 //! \brief Test for [tls.enumerable_thread_specific] specification
42
43 //! Minimum number of threads
44 static int MinThread = 1;
45
46 //! Maximum number of threads
47 static int MaxThread = 4;
48
49 static std::atomic<int> construction_counter;
50 static std::atomic<int> destruction_counter;
51
52 const int VALID_NUMBER_OF_KEYS = 100;
53
54 //! A minimal class that occupies N bytes.
55 /** Defines default and copy constructor, and allows implicit operator&. Hides operator=. */
56 template<size_t N = tbb::detail::max_nfs_size>
57 class minimalN: utils::NoAssign {
58 private:
59 int my_value;
60 bool is_constructed;
61 char pad[N-sizeof(int) - sizeof(bool)];
62 public:
minimalN()63 minimalN() : utils::NoAssign(), my_value(0) { ++construction_counter; is_constructed = true; }
minimalN(const minimalN & m)64 minimalN( const minimalN&m ) : utils::NoAssign(), my_value(m.my_value) { ++construction_counter; is_constructed = true; }
~minimalN()65 ~minimalN() { ++destruction_counter; REQUIRE(is_constructed); is_constructed = false; }
set_value(const int i)66 void set_value( const int i ) { REQUIRE(is_constructed); my_value = i; }
value() const67 int value( ) const { REQUIRE(is_constructed); return my_value; }
68 };
69
70 static size_t AlignMask = 0; // set to cache-line-size - 1
71
72 template<typename T>
check_alignment(T & t,const char * aname)73 T& check_alignment(T& t, const char *aname) {
74 if( !tbb::detail::is_aligned(&t, AlignMask)) {
75 // TBB_REVAMP_TODO: previously was REPORT_ONCE
76 REPORT("alignment error with %s allocator (%x)\n", aname, (int)size_t(&t) & (AlignMask-1));
77 }
78 return t;
79 }
80
81 template<typename T>
check_alignment(const T & t,const char * aname)82 const T& check_alignment(const T& t, const char *aname) {
83 if( !tbb::detail::is_aligned(&t, AlignMask)) {
84 // TBB_REVAMP_TODO: previously was REPORT_ONCE
85 REPORT("alignment error with %s allocator (%x)\n", aname, (int)size_t(&t) & (AlignMask-1));
86 }
87 return t;
88 }
89
90 //
91 // A helper class that simplifies writing the tests since minimalN does not
92 // define = or + operators.
93 //
94
95 const size_t line_size = tbb::detail::max_nfs_size;
96
97 typedef tbb::enumerable_thread_specific<minimalN<line_size> > flogged_ets;
98
99 class set_body {
100 flogged_ets *a;
101
102 public:
set_body(flogged_ets * _a)103 set_body( flogged_ets*_a ) : a(_a) { }
104
operator ()() const105 void operator() ( ) const {
106 for (int i = 0; i < VALID_NUMBER_OF_KEYS; ++i) {
107 check_alignment(a[i].local(), "default").set_value(i + 1);
108 }
109 }
110
111 };
112
do_std_threads(int max_threads,flogged_ets a[])113 void do_std_threads( int max_threads, flogged_ets a[] ) {
114 std::vector< std::thread * > threads;
115
116 for (int p = 0; p < max_threads; ++p) {
117 threads.push_back( new std::thread ( set_body( a ) ) );
118 }
119
120 for (int p = 0; p < max_threads; ++p) {
121 threads[p]->join();
122 }
123
124 for(int p = 0; p < max_threads; ++p) {
125 delete threads[p];
126 }
127 }
128
flog_key_creation_and_deletion()129 void flog_key_creation_and_deletion() {
130 const int FLOG_REPETITIONS = 100;
131
132 for (int p = MinThread; p <= MaxThread; ++p) {
133 for (int j = 0; j < FLOG_REPETITIONS; ++j) {
134 construction_counter = 0;
135 destruction_counter = 0;
136 // causes VALID_NUMBER_OF_KEYS exemplar instances to be constructed
137 flogged_ets* a = new flogged_ets[VALID_NUMBER_OF_KEYS];
138 REQUIRE(int(construction_counter) == 0); // no exemplars or actual locals have been constructed
139 REQUIRE(int(destruction_counter) == 0); // and none have been destroyed
140 // causes p * VALID_NUMBER_OF_KEYS minimals to be created
141 do_std_threads(p, a);
142 for (int i = 0; i < VALID_NUMBER_OF_KEYS; ++i) {
143 int pcnt = 0;
144 for ( flogged_ets::iterator tli = a[i].begin(); tli != a[i].end(); ++tli ) {
145 REQUIRE( (*tli).value() == i+1 );
146 ++pcnt;
147 }
148 REQUIRE( pcnt == p); // should be one local per thread.
149 }
150 delete[] a;
151 }
152 REQUIRE( int(construction_counter) == (p)*VALID_NUMBER_OF_KEYS );
153 REQUIRE( int(destruction_counter) == (p)*VALID_NUMBER_OF_KEYS );
154
155 construction_counter = 0;
156 destruction_counter = 0;
157
158 // causes VALID_NUMBER_OF_KEYS exemplar instances to be constructed
159 flogged_ets* a = new flogged_ets[VALID_NUMBER_OF_KEYS];
160
161 for (int j = 0; j < FLOG_REPETITIONS; ++j) {
162 // causes p * VALID_NUMBER_OF_KEYS minimals to be created
163 do_std_threads(p, a);
164
165 for (int i = 0; i < VALID_NUMBER_OF_KEYS; ++i) {
166 for ( flogged_ets::iterator tli = a[i].begin(); tli != a[i].end(); ++tli ) {
167 REQUIRE( (*tli).value() == i+1 );
168 }
169 a[i].clear();
170 REQUIRE( static_cast<int>(a[i].end() - a[i].begin()) == 0 );
171 }
172 }
173 delete[] a;
174 REQUIRE( int(construction_counter) == (FLOG_REPETITIONS*p)*VALID_NUMBER_OF_KEYS );
175 REQUIRE( int(destruction_counter) == (FLOG_REPETITIONS*p)*VALID_NUMBER_OF_KEYS );
176 }
177
178 }
179
180 template <typename inner_container>
flog_segmented_interator()181 void flog_segmented_interator() {
182
183 bool found_error = false;
184 typedef typename inner_container::value_type T;
185 typedef std::vector< inner_container > nested_vec;
186 inner_container my_inner_container;
187 my_inner_container.clear();
188 nested_vec my_vec;
189
190 // simple nested vector (neither level empty)
191 const int maxval = 10;
192 for(int i=0; i < maxval; i++) {
193 my_vec.push_back(my_inner_container);
194 for(int j = 0; j < maxval; j++) {
195 my_vec.at(i).push_back((T)(maxval * i + j));
196 }
197 }
198
199 tbb::detail::d1::segmented_iterator<nested_vec, T> my_si(my_vec);
200
201 T ii;
202 for(my_si=my_vec.begin(), ii=0; my_si != my_vec.end(); ++my_si, ++ii) {
203 if((*my_si) != ii) {
204 found_error = true;
205 }
206 }
207
208 // outer level empty
209 my_vec.clear();
210 for(my_si=my_vec.begin(); my_si != my_vec.end(); ++my_si) {
211 found_error = true;
212 }
213
214 // inner levels empty
215 my_vec.clear();
216 for(int i =0; i < maxval; ++i) {
217 my_vec.push_back(my_inner_container);
218 }
219 for(my_si = my_vec.begin(); my_si != my_vec.end(); ++my_si) {
220 found_error = true;
221 }
222
223 // every other inner container is empty
224 my_vec.clear();
225 for(int i=0; i < maxval; ++i) {
226 my_vec.push_back(my_inner_container);
227 if(i%2) {
228 for(int j = 0; j < maxval; ++j) {
229 my_vec.at(i).push_back((T)(maxval * (i/2) + j));
230 }
231 }
232 }
233 for(my_si = my_vec.begin(), ii=0; my_si != my_vec.end(); ++my_si, ++ii) {
234 if((*my_si) != ii) {
235 found_error = true;
236 }
237 }
238
239 tbb::detail::d1::segmented_iterator<nested_vec, const T> my_csi(my_vec);
240 for(my_csi=my_vec.begin(), ii=0; my_csi != my_vec.end(); ++my_csi, ++ii) {
241 if((*my_csi) != ii) {
242 found_error = true;
243 }
244 }
245
246 // outer level empty
247 my_vec.clear();
248 for(my_csi=my_vec.begin(); my_csi != my_vec.end(); ++my_csi) {
249 found_error = true;
250 }
251
252 // inner levels empty
253 my_vec.clear();
254 for(int i =0; i < maxval; ++i) {
255 my_vec.push_back(my_inner_container);
256 }
257 for(my_csi = my_vec.begin(); my_csi != my_vec.end(); ++my_csi) {
258 found_error = true;
259 }
260
261 // every other inner container is empty
262 my_vec.clear();
263 for(int i=0; i < maxval; ++i) {
264 my_vec.push_back(my_inner_container);
265 if(i%2) {
266 for(int j = 0; j < maxval; ++j) {
267 my_vec.at(i).push_back((T)(maxval * (i/2) + j));
268 }
269 }
270 }
271 for(my_csi = my_vec.begin(), ii=0; my_csi != my_vec.end(); ++my_csi, ++ii) {
272 if((*my_csi) != ii) {
273 found_error = true;
274 }
275 }
276
277
278 if(found_error) REPORT("segmented_iterator failed\n");
279 }
280
281 template <typename Key, typename Val>
flog_segmented_iterator_map()282 void flog_segmented_iterator_map() {
283 typedef typename std::map<Key, Val> my_map;
284 typedef std::vector< my_map > nested_vec;
285 my_map my_inner_container;
286 my_inner_container.clear();
287 nested_vec my_vec;
288 my_vec.clear();
289 bool found_error = false;
290
291 // simple nested vector (neither level empty)
292 const int maxval = 4;
293 for(int i=0; i < maxval; i++) {
294 my_vec.push_back(my_inner_container);
295 for(int j = 0; j < maxval; j++) {
296 my_vec.at(i).insert(std::make_pair<Key,Val>(maxval * i + j, 2*(maxval*i + j)));
297 }
298 }
299
300 tbb::detail::d1::segmented_iterator<nested_vec, std::pair<const Key, Val> > my_si(my_vec);
301 Key ii;
302 for(my_si=my_vec.begin(), ii=0; my_si != my_vec.end(); ++my_si, ++ii) {
303 if(((*my_si).first != ii) || ((*my_si).second != 2*ii)) {
304 found_error = true;
305 }
306 }
307
308 tbb::detail::d1::segmented_iterator<nested_vec, const std::pair<const Key, Val> > my_csi(my_vec);
309 for(my_csi=my_vec.begin(), ii=0; my_csi != my_vec.end(); ++my_csi, ++ii) {
310 if(((*my_csi).first != ii) || ((*my_csi).second != 2*ii)) {
311 found_error = true;
312 // INFO( "ii=%d, (*my_csi).first=%d, second=%d\n",ii, int((*my_csi).first), int((*my_csi).second));
313 }
314 }
315 if(found_error) REPORT("segmented_iterator_map failed\n");
316 }
317
run_segmented_iterator_tests()318 void run_segmented_iterator_tests() {
319 // only the following containers can be used with the segmented iterator.
320 flog_segmented_interator<std::vector< int > >();
321 flog_segmented_interator<std::vector< double > >();
322 flog_segmented_interator<std::deque< int > >();
323 flog_segmented_interator<std::deque< double > >();
324 flog_segmented_interator<std::list< int > >();
325 flog_segmented_interator<std::list< double > >();
326
327 flog_segmented_iterator_map<int, int>();
328 flog_segmented_iterator_map<int, double>();
329 }
330
align_val(void * const p)331 int align_val(void * const p) {
332 size_t tmp = (size_t)p;
333 int a = 1;
334 while((tmp&0x1) == 0) { a <<=1; tmp >>= 1; }
335 return a;
336 }
337
is_between(void * lowp,void * highp,void * testp)338 bool is_between(void* lowp, void *highp, void *testp) {
339 if((size_t)lowp < (size_t)testp && (size_t)testp < (size_t)highp) return true;
340 return (size_t)lowp > (size_t)testp && (size_t)testp > (size_t)highp;
341 }
342
343 template<class U> struct alignment_of {
344 typedef struct { char t; U padded; } test_alignment;
345 static const size_t value = sizeof(test_alignment) - sizeof(U);
346 };
347 using tbb::detail::d1::ets_element;
348 template<typename T, typename OtherType>
allocate_ets_element_on_stack(const char *)349 void allocate_ets_element_on_stack(const char* /* name */) {
350 typedef T aligning_element_type;
351 const size_t my_align = alignment_of<aligning_element_type>::value;
352 OtherType c1;
353 ets_element<aligning_element_type> my_stack_element;
354 OtherType c2;
355 ets_element<aligning_element_type> my_stack_element2;
356 struct {
357 OtherType cxx;
358 ets_element<aligning_element_type> my_struct_element;
359 } mystruct1;
360 tbb::detail::suppress_unused_warning(c1,c2);
361 REQUIRE_MESSAGE(tbb::detail::is_aligned(my_stack_element.value(), my_align), "Error in first stack alignment" );
362 REQUIRE_MESSAGE(tbb::detail::is_aligned(my_stack_element2.value(), my_align), "Error in second stack alignment" );
363 REQUIRE_MESSAGE(tbb::detail::is_aligned(mystruct1.my_struct_element.value(), my_align), "Error in struct element alignment" );
364 }
365
366 class BigType {
367 public:
BigType()368 BigType() { /* avoid cl warning C4345 about default initialization of POD types */ }
369 char my_data[12 * 1024 * 1024];
370 };
371
372 template<template<class> class Allocator>
TestConstructorWithBigType(const char * allocator_name)373 void TestConstructorWithBigType(const char* allocator_name) {
374 typedef tbb::enumerable_thread_specific<BigType, Allocator<BigType> > CounterBigType;
375 // Test default constructor
376 CounterBigType MyCounters;
377 // Create a local instance.
378 typename CounterBigType::reference my_local = MyCounters.local();
379 my_local.my_data[0] = 'a';
380 // Test copy constructor
381 CounterBigType MyCounters2(MyCounters);
382 REQUIRE(check_alignment(MyCounters2.local(), allocator_name).my_data[0]=='a');
383 }
384
init_tbb_alloc_mask()385 size_t init_tbb_alloc_mask() {
386 // TODO: use __TBB_alignof(T) to check for local() results instead of using internal knowledges of ets element padding
387 if(tbb::tbb_allocator<int>::allocator_type() == tbb::tbb_allocator<int>::standard) {
388 // scalable allocator is not available.
389 // INFO("tbb::tbb_allocator is not available\n");
390 return 1;
391 }
392 else {
393 // this value is for large objects, but will be correct for small.
394 return 64; // TBB_REVAMP_TODO: enable as estimatedCacheLineSize when tbbmalloc is available;
395 }
396 }
397
398 static const size_t cache_allocator_mask = tbb::detail::r1::cache_line_size();
399 static const size_t tbb_allocator_mask = init_tbb_alloc_mask();
400
401 //! Test for internal segmented_iterator type, used inside flattened2d class
402 //! \brief \ref error_guessing
403 TEST_CASE("Segmented iterator") {
404 AlignMask = tbb_allocator_mask;
405 run_segmented_iterator_tests();
406 }
407
408 //! Test ETS keys creation/deletion
409 //! \brief \ref error_guessing \ref boundary
410 TEST_CASE("Key creation and deletion") {
411 AlignMask = tbb_allocator_mask;
412 flog_key_creation_and_deletion();
413 }
414
415 //! Test construction with big ETS types
416 //! \brief \ref error_guessing
417 TEST_CASE("Constructor with big type") {
418 AlignMask = cache_allocator_mask;
419 TestConstructorWithBigType<tbb::cache_aligned_allocator>("tbb::cache_aligned_allocator");
420 AlignMask = tbb_allocator_mask;
421 TestConstructorWithBigType<tbb::tbb_allocator>("tbb::tbb_allocator");
422 }
423
424 //! Test allocation of ETS elements on the stack (internal types)
425 //! \brief \ref error_guessing
426 TEST_CASE("Allocate ETS on stack") {
427 AlignMask = tbb_allocator_mask;
428 allocate_ets_element_on_stack<int,char>("int vs. char");
429 allocate_ets_element_on_stack<int,short>("int vs. short");
430 allocate_ets_element_on_stack<int,char[3]>("int vs. char[3]");
431 allocate_ets_element_on_stack<float,char>("float vs. char");
432 allocate_ets_element_on_stack<float,short>("float vs. short");
433 allocate_ets_element_on_stack<float,char[3]>("float vs. char[3]");
434 }
435
436