151c0b2f7Stbbdev /*
2*c21e688aSSergey Zheltov     Copyright (c) 2005-2022 Intel Corporation
351c0b2f7Stbbdev 
451c0b2f7Stbbdev     Licensed under the Apache License, Version 2.0 (the "License");
551c0b2f7Stbbdev     you may not use this file except in compliance with the License.
651c0b2f7Stbbdev     You may obtain a copy of the License at
751c0b2f7Stbbdev 
851c0b2f7Stbbdev         http://www.apache.org/licenses/LICENSE-2.0
951c0b2f7Stbbdev 
1051c0b2f7Stbbdev     Unless required by applicable law or agreed to in writing, software
1151c0b2f7Stbbdev     distributed under the License is distributed on an "AS IS" BASIS,
1251c0b2f7Stbbdev     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1351c0b2f7Stbbdev     See the License for the specific language governing permissions and
1451c0b2f7Stbbdev     limitations under the License.
1551c0b2f7Stbbdev */
1651c0b2f7Stbbdev 
1751c0b2f7Stbbdev // Basic testing of an allocator
1851c0b2f7Stbbdev // Tests against requirements in 20.1.5 of ISO C++ Standard (1998).
1951c0b2f7Stbbdev // Does not check for thread safety or false sharing issues.
2051c0b2f7Stbbdev //
2151c0b2f7Stbbdev // Tests for compatibility with the host's STL are in
2251c0b2f7Stbbdev // test_Allocator_STL.h.  Those tests are in a separate file
2351c0b2f7Stbbdev // because they bring in lots of STL headers, and the tests here
2451c0b2f7Stbbdev // are supposed to work in the abscense of STL.
2551c0b2f7Stbbdev 
264523a761Stbbdev #ifndef __TBB_test_common_allocator_test_common_H_
274523a761Stbbdev #define __TBB_test_common_allocator_test_common_H_
284523a761Stbbdev 
2951c0b2f7Stbbdev #include "common/test.h"
3051c0b2f7Stbbdev #include "common/utils.h"
3151c0b2f7Stbbdev #include <utility> //for std::pair
3251c0b2f7Stbbdev #include <cstring>
3351c0b2f7Stbbdev 
3451c0b2f7Stbbdev //! Compile-time error if x and y have different types
3551c0b2f7Stbbdev template<typename T>
AssertSameType(const T &,const T &)3651c0b2f7Stbbdev void AssertSameType( const T& /*x*/, const T& /*y*/ ) {}
3751c0b2f7Stbbdev 
3851c0b2f7Stbbdev //! The function to zero-initialize arrays; useful to avoid warnings
3951c0b2f7Stbbdev template <typename T>
zero_fill(void * array,size_t n)4051c0b2f7Stbbdev void zero_fill(void* array, size_t n) {
4151c0b2f7Stbbdev     memset(array, 0, sizeof(T)*n);
4251c0b2f7Stbbdev }
4351c0b2f7Stbbdev 
4451c0b2f7Stbbdev template<typename A>
4551c0b2f7Stbbdev struct is_zero_filling {
4651c0b2f7Stbbdev     static const bool value = false;
4751c0b2f7Stbbdev };
4851c0b2f7Stbbdev 
4951c0b2f7Stbbdev int NumberOfFoo;
5051c0b2f7Stbbdev 
5151c0b2f7Stbbdev template<typename T, size_t N>
5251c0b2f7Stbbdev struct Foo {
5351c0b2f7Stbbdev     T foo_array[N];
FooFoo5451c0b2f7Stbbdev     Foo() {
5551c0b2f7Stbbdev         zero_fill<T>(foo_array, N);
5651c0b2f7Stbbdev         ++NumberOfFoo;
5751c0b2f7Stbbdev     }
FooFoo5851c0b2f7Stbbdev     Foo( const Foo& x ) {
5951c0b2f7Stbbdev         *this = x;
6051c0b2f7Stbbdev         //Internal call of assignment
6151c0b2f7Stbbdev     }
6251c0b2f7Stbbdev     Foo& operator=( const Foo& x ) {
6351c0b2f7Stbbdev         for (size_t i = 0; i < N; i++)
6451c0b2f7Stbbdev             foo_array[i] = x.foo_array[i];
6551c0b2f7Stbbdev         ++NumberOfFoo;
6651c0b2f7Stbbdev         return *this;
6751c0b2f7Stbbdev     }
6851c0b2f7Stbbdev 
~FooFoo6951c0b2f7Stbbdev     ~Foo() {
7051c0b2f7Stbbdev         --NumberOfFoo;
7151c0b2f7Stbbdev     }
7251c0b2f7Stbbdev };
7351c0b2f7Stbbdev 
PseudoRandomValue(size_t j,size_t k)7451c0b2f7Stbbdev inline char PseudoRandomValue( size_t j, size_t k ) {
7551c0b2f7Stbbdev     return char(j*3 ^ j>>4 ^ k);
7651c0b2f7Stbbdev }
7751c0b2f7Stbbdev 
7851c0b2f7Stbbdev #if __APPLE__
7951c0b2f7Stbbdev #include <fcntl.h>
8051c0b2f7Stbbdev #include <unistd.h>
8151c0b2f7Stbbdev 
8251c0b2f7Stbbdev // A RAII class to disable stderr in a certain scope. It's not thread-safe.
8351c0b2f7Stbbdev class DisableStderr {
8451c0b2f7Stbbdev     int stderrCopy;
dupToStderrAndClose(int fd)8551c0b2f7Stbbdev     static void dupToStderrAndClose(int fd) {
8651c0b2f7Stbbdev         int ret = dup2(fd, STDERR_FILENO); // close current stderr
8751c0b2f7Stbbdev         REQUIRE(ret != -1);
8851c0b2f7Stbbdev         ret = close(fd);
8951c0b2f7Stbbdev         REQUIRE(ret != -1);
9051c0b2f7Stbbdev     }
9151c0b2f7Stbbdev public:
DisableStderr()9251c0b2f7Stbbdev     DisableStderr() {
9351c0b2f7Stbbdev         int devNull = open("/dev/null", O_WRONLY);
9451c0b2f7Stbbdev         REQUIRE(devNull != -1);
9551c0b2f7Stbbdev         stderrCopy = dup(STDERR_FILENO);
9651c0b2f7Stbbdev         REQUIRE(stderrCopy != -1);
9751c0b2f7Stbbdev         dupToStderrAndClose(devNull);
9851c0b2f7Stbbdev     }
~DisableStderr()9951c0b2f7Stbbdev     ~DisableStderr() {
10051c0b2f7Stbbdev         dupToStderrAndClose(stderrCopy);
10151c0b2f7Stbbdev     }
10251c0b2f7Stbbdev };
10351c0b2f7Stbbdev #endif
10451c0b2f7Stbbdev 
10551c0b2f7Stbbdev //! T is type and A is allocator for that type
10651c0b2f7Stbbdev template<typename T, typename A>
TestBrokenAllocator(A & a)10751c0b2f7Stbbdev void TestBrokenAllocator(A& a) {
10851c0b2f7Stbbdev     T x;
10951c0b2f7Stbbdev     const T cx = T();
11051c0b2f7Stbbdev     // See Table 32 in ISO ++ Standard
11151c0b2f7Stbbdev     typename A::pointer px = &x;
11251c0b2f7Stbbdev     typename A::const_pointer pcx = &cx;
11351c0b2f7Stbbdev 
11451c0b2f7Stbbdev     typename A::reference rx = x;
11551c0b2f7Stbbdev     REQUIRE(&rx == &x);
11651c0b2f7Stbbdev 
11751c0b2f7Stbbdev     typename A::const_reference rcx = cx;
11851c0b2f7Stbbdev     REQUIRE(&rcx==&cx);
11951c0b2f7Stbbdev 
12051c0b2f7Stbbdev     typename A::value_type v = x;
12151c0b2f7Stbbdev 
12251c0b2f7Stbbdev     typename A::size_type size;
12351c0b2f7Stbbdev     size = 0;
12451c0b2f7Stbbdev     --size;
12551c0b2f7Stbbdev     REQUIRE_MESSAGE(size > 0, "not an unsigned integral type?");
12651c0b2f7Stbbdev 
12751c0b2f7Stbbdev     typename A::difference_type difference;
12851c0b2f7Stbbdev     difference = 0;
12951c0b2f7Stbbdev     --difference;
13051c0b2f7Stbbdev     REQUIRE_MESSAGE(difference<0, "not an signed integral type?");
13151c0b2f7Stbbdev 
13251c0b2f7Stbbdev     // "rebind" tested by our caller
13351c0b2f7Stbbdev 
13451c0b2f7Stbbdev     REQUIRE(a.address(rx) == px);
13551c0b2f7Stbbdev 
13651c0b2f7Stbbdev     REQUIRE(a.address(rcx) == pcx);
13751c0b2f7Stbbdev 
13851c0b2f7Stbbdev     // Test "a.max_size()"
13951c0b2f7Stbbdev     AssertSameType(a.max_size(), typename A::size_type(0));
14051c0b2f7Stbbdev     // Following assertion catches case where max_size() is so large that computation of
14151c0b2f7Stbbdev     // number of bytes for such an allocation would overflow size_type.
14251c0b2f7Stbbdev     REQUIRE_MESSAGE((a.max_size() * typename A::size_type(sizeof(T)) >= a.max_size()), "max_size larger than reasonable");
14351c0b2f7Stbbdev 
14451c0b2f7Stbbdev     // Test "a.construct(p,t)"
14551c0b2f7Stbbdev     int n = NumberOfFoo;
14651c0b2f7Stbbdev     typename A::pointer p = a.allocate(1);
14751c0b2f7Stbbdev     a.construct(p, cx);
14851c0b2f7Stbbdev     REQUIRE_MESSAGE(NumberOfFoo == n + 1, "constructor for Foo not called?");
14951c0b2f7Stbbdev 
15051c0b2f7Stbbdev     // Test "a.destroy(p)"
15151c0b2f7Stbbdev     a.destroy(p);
15251c0b2f7Stbbdev     REQUIRE_MESSAGE(NumberOfFoo == n, "destructor for Foo not called?");
15351c0b2f7Stbbdev     a.deallocate(p, 1);
15451c0b2f7Stbbdev 
15551c0b2f7Stbbdev     {
15651c0b2f7Stbbdev         typedef typename A::template rebind<std::pair<typename A::value_type, typename A::value_type> >::other pair_allocator_type;
15751c0b2f7Stbbdev         pair_allocator_type pair_allocator(a);
15851c0b2f7Stbbdev         int NumberOfFooBeforeConstruct = NumberOfFoo;
15951c0b2f7Stbbdev         typename pair_allocator_type::pointer pair_pointer = pair_allocator.allocate(1);
16051c0b2f7Stbbdev         pair_allocator.construct(pair_pointer, cx, cx);
16151c0b2f7Stbbdev         REQUIRE_MESSAGE(NumberOfFoo == NumberOfFooBeforeConstruct+2, "constructor for Foo not called appropriate number of times?");
16251c0b2f7Stbbdev 
16351c0b2f7Stbbdev         pair_allocator.destroy(pair_pointer);
16451c0b2f7Stbbdev         REQUIRE_MESSAGE(NumberOfFoo == NumberOfFooBeforeConstruct, "destructor for Foo not called appropriate number of times?");
16551c0b2f7Stbbdev         pair_allocator.deallocate(pair_pointer, 1);
16651c0b2f7Stbbdev     }
16751c0b2f7Stbbdev }
16851c0b2f7Stbbdev 
16951c0b2f7Stbbdev //! T is type and A is allocator for that type
17051c0b2f7Stbbdev template<typename T, typename A>
TestAllocatorConcept(A & a)17151c0b2f7Stbbdev void TestAllocatorConcept(A& a) {
17251c0b2f7Stbbdev     // Test "a.allocate(p,n)
17351c0b2f7Stbbdev     typename std::allocator_traits<A>::pointer array[100];
17451c0b2f7Stbbdev     std::size_t sizeof_T = sizeof(T);
17551c0b2f7Stbbdev     for(std::size_t k = 0; k < 100; ++k) {
17651c0b2f7Stbbdev         array[k] = a.allocate(k);
17751c0b2f7Stbbdev         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[k]));
17851c0b2f7Stbbdev         for(std::size_t j=0; j < k * sizeof_T; ++j)
17951c0b2f7Stbbdev             s[j] = PseudoRandomValue(j, k);
18051c0b2f7Stbbdev     }
18151c0b2f7Stbbdev 
18251c0b2f7Stbbdev     // Test "a.deallocate(p,n)
18351c0b2f7Stbbdev     for(std::size_t k = 0; k < 100; ++k) {
18451c0b2f7Stbbdev         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[k]));
18551c0b2f7Stbbdev         for(std::size_t j = 0; j < k * sizeof_T; ++j)
18651c0b2f7Stbbdev             REQUIRE(s[j] == PseudoRandomValue(j, k));
18751c0b2f7Stbbdev         a.deallocate(array[k], k);
18851c0b2f7Stbbdev     }
18951c0b2f7Stbbdev }
19051c0b2f7Stbbdev 
19151c0b2f7Stbbdev //! T is type and A is allocator for that type
19251c0b2f7Stbbdev template<typename T, typename A>
TestAllocatorExceptions(A & a)19351c0b2f7Stbbdev void TestAllocatorExceptions(A& a) {
19451c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
19551c0b2f7Stbbdev     volatile size_t too_big = (~std::size_t(0) - 1024 * 1024) / sizeof(T);
19651c0b2f7Stbbdev     bool exception_caught = false;
19751c0b2f7Stbbdev     typename std::allocator_traits<A>::pointer p1 = nullptr;
19851c0b2f7Stbbdev     try {
19951c0b2f7Stbbdev #if __APPLE__
20051c0b2f7Stbbdev         // On macOS*, failure to map memory results in messages to stderr;
20151c0b2f7Stbbdev         // suppress them.
20251c0b2f7Stbbdev         DisableStderr disableStderr;
20351c0b2f7Stbbdev #endif
20451c0b2f7Stbbdev         p1 = a.allocate(too_big);
20551c0b2f7Stbbdev     } catch (std::bad_alloc&) {
20651c0b2f7Stbbdev         exception_caught = true;
20751c0b2f7Stbbdev     }
20851c0b2f7Stbbdev     REQUIRE_MESSAGE(exception_caught, "allocate expected to throw bad_alloc");
20951c0b2f7Stbbdev     a.deallocate(p1, too_big);
21051c0b2f7Stbbdev #endif // TBB_USE_EXCEPTIONS
21151c0b2f7Stbbdev     utils::suppress_unused_warning(a);
21251c0b2f7Stbbdev }
21351c0b2f7Stbbdev 
21451c0b2f7Stbbdev #if _MSC_VER && !defined(__INTEL_COMPILER)
21551c0b2f7Stbbdev     // Workaround for erroneous "conditional expression is constant" warning in method check_allocate.
21651c0b2f7Stbbdev     #pragma warning (disable: 4127)
21751c0b2f7Stbbdev #endif
21851c0b2f7Stbbdev 
21951c0b2f7Stbbdev // A is an allocator for some type
22051c0b2f7Stbbdev template<typename A>
22151c0b2f7Stbbdev struct Body: utils::NoAssign {
22251c0b2f7Stbbdev     using pointer_type = typename std::allocator_traits<A>::pointer;
22351c0b2f7Stbbdev     using value_type = typename std::allocator_traits<A>::value_type;
22451c0b2f7Stbbdev     // For the int types and above this test runs too long
22551c0b2f7Stbbdev     static const std::size_t max_k = sizeof(value_type) < sizeof(int) ? 100000 : 5000;
22651c0b2f7Stbbdev     A &a;
BodyBody22751c0b2f7Stbbdev     Body(A &a_) : a(a_) {}
check_allocateBody22851c0b2f7Stbbdev     void check_allocate(pointer_type array[], std::size_t i, std::size_t t) const
22951c0b2f7Stbbdev     {
23051c0b2f7Stbbdev         REQUIRE(array[i] == nullptr);
23151c0b2f7Stbbdev         std::size_t size = i * (i & 3);
23251c0b2f7Stbbdev         array[i] = a.allocate(size);
23351c0b2f7Stbbdev         REQUIRE_MESSAGE(array[i] != nullptr, "allocator returned null");
23451c0b2f7Stbbdev         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[i]));
23551c0b2f7Stbbdev         for(std::size_t j = 0; j < size * sizeof(value_type); ++j) {
23651c0b2f7Stbbdev             if(is_zero_filling<typename std::allocator_traits<A>::template rebind_alloc<void>>::value)
23751c0b2f7Stbbdev                 REQUIRE(!s[j]);
23851c0b2f7Stbbdev             s[j] = PseudoRandomValue(i, t);
23951c0b2f7Stbbdev         }
24051c0b2f7Stbbdev     }
24151c0b2f7Stbbdev 
check_deallocateBody24251c0b2f7Stbbdev     void check_deallocate(pointer_type array[], std::size_t i, std::size_t t) const
24351c0b2f7Stbbdev     {
24451c0b2f7Stbbdev         REQUIRE(array[i] != nullptr);
24551c0b2f7Stbbdev         size_t size = i * (i & 3);
24651c0b2f7Stbbdev         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[i]));
24751c0b2f7Stbbdev         for(std::size_t j=0; j < size * sizeof(value_type); ++j)
24851c0b2f7Stbbdev             REQUIRE_MESSAGE(s[j] == PseudoRandomValue(i, t), "Thread safety test failed");
24951c0b2f7Stbbdev         a.deallocate(array[i], size);
25057f524caSIlya Isaev         array[i] = nullptr;
25151c0b2f7Stbbdev     }
25251c0b2f7Stbbdev 
operatorBody25351c0b2f7Stbbdev     void operator()(std::size_t thread_id) const {
25451c0b2f7Stbbdev         pointer_type array[256];
25551c0b2f7Stbbdev 
25651c0b2f7Stbbdev         for(std::size_t k = 0; k < 256; ++k)
25757f524caSIlya Isaev             array[k] = nullptr;
25851c0b2f7Stbbdev         for(std::size_t k = 0; k < max_k; ++k) {
25951c0b2f7Stbbdev             std::size_t i = static_cast<unsigned char>(PseudoRandomValue(k, thread_id));
26051c0b2f7Stbbdev             if(!array[i]) check_allocate(array, i, thread_id);
26151c0b2f7Stbbdev             else check_deallocate(array, i, thread_id);
26251c0b2f7Stbbdev         }
26351c0b2f7Stbbdev         for(std::size_t k = 0; k < 256; ++k)
26451c0b2f7Stbbdev             if(array[k])
26551c0b2f7Stbbdev                 check_deallocate(array, k, thread_id);
26651c0b2f7Stbbdev     }
26751c0b2f7Stbbdev };
26851c0b2f7Stbbdev 
26951c0b2f7Stbbdev template<typename A>
TestThreadSafety(A & a)27051c0b2f7Stbbdev void TestThreadSafety(A &a) {
27151c0b2f7Stbbdev     utils::NativeParallelFor(4, Body<A>(a));
27251c0b2f7Stbbdev }
27351c0b2f7Stbbdev 
27451c0b2f7Stbbdev enum TestName { Concept, Broken, Exceptions, ThreadSafety, Comparison };
27551c0b2f7Stbbdev 
27651c0b2f7Stbbdev template<typename Allocator>
27751c0b2f7Stbbdev void TestAllocator(TestName name, const Allocator &a = Allocator()) {
27851c0b2f7Stbbdev 
27951c0b2f7Stbbdev     using FooChar = Foo<char, 1>;
28051c0b2f7Stbbdev     using FooDouble = Foo<double, 1>;
28151c0b2f7Stbbdev     using FooInt = Foo<int, 17>;
28251c0b2f7Stbbdev     using FooFloat = Foo<float, 23>;
28351c0b2f7Stbbdev     #if TBB_ALLOCATOR_TRAITS_BROKEN
28451c0b2f7Stbbdev         using AllocatorFooChar = typename Allocator::template rebind<FooChar>::other;
28551c0b2f7Stbbdev         using AllocatorFooDouble = typename Allocator::template rebind<FooDouble>::other;
28651c0b2f7Stbbdev         using AllocatorFooInt = typename AllocatorFooChar::template rebind<FooInt>::other;
28751c0b2f7Stbbdev         using AllocatorFooFloat = typename AllocatorFooDouble::template rebind<FooFloat>::other;
28851c0b2f7Stbbdev     #else
28951c0b2f7Stbbdev         using AllocatorFooChar = typename std::allocator_traits<Allocator>::template rebind_alloc<FooChar>;
29051c0b2f7Stbbdev         using AllocatorFooDouble = typename std::allocator_traits<Allocator>::template rebind_alloc<FooDouble>;
29151c0b2f7Stbbdev         using AllocatorFooInt = typename std::allocator_traits<AllocatorFooChar>::template rebind_alloc<FooInt>;
29251c0b2f7Stbbdev         using AllocatorFooFloat = typename std::allocator_traits<AllocatorFooDouble>::template rebind_alloc<FooFloat>;
29351c0b2f7Stbbdev     #endif
29451c0b2f7Stbbdev 
29551c0b2f7Stbbdev     NumberOfFoo = 0;
296112076d0SIlya Isaev     Allocator a_cpy(a);
29751c0b2f7Stbbdev     AllocatorFooChar a1(a);
29851c0b2f7Stbbdev     AllocatorFooDouble a2(a);
29951c0b2f7Stbbdev     AllocatorFooInt b1(a1);
30051c0b2f7Stbbdev     AllocatorFooFloat b2(a2);
30151c0b2f7Stbbdev 
30251c0b2f7Stbbdev     switch(name) {
30351c0b2f7Stbbdev     case Comparison:
304112076d0SIlya Isaev         REQUIRE(a_cpy == a);
30551c0b2f7Stbbdev         REQUIRE(a1 == b1);
30651c0b2f7Stbbdev         REQUIRE(!(a2 != b2));
30751c0b2f7Stbbdev         break;
30851c0b2f7Stbbdev     case Concept:
30951c0b2f7Stbbdev         TestAllocatorConcept<FooInt>(b1);
31051c0b2f7Stbbdev         TestAllocatorConcept<typename AllocatorFooChar::value_type>(a1);
31151c0b2f7Stbbdev         TestAllocatorConcept<FooFloat>(b2);
31251c0b2f7Stbbdev         TestAllocatorConcept<typename AllocatorFooDouble::value_type>(a2);
31351c0b2f7Stbbdev         break;
31451c0b2f7Stbbdev     case Broken:
31551c0b2f7Stbbdev     #if TBB_ALLOCATOR_TRAITS_BROKEN
31651c0b2f7Stbbdev         TestBrokenAllocator<FooInt>(b1);
31751c0b2f7Stbbdev         TestBrokenAllocator<typename AllocatorFooChar::value_type>(a1);
31851c0b2f7Stbbdev         TestBrokenAllocator<FooFloat>(b2);
31951c0b2f7Stbbdev         TestBrokenAllocator<typename AllocatorFooDouble::value_type>(a2);
32051c0b2f7Stbbdev     #endif
32151c0b2f7Stbbdev         break;
32251c0b2f7Stbbdev     case Exceptions:
32351c0b2f7Stbbdev         TestAllocatorExceptions<FooInt>(b1);
32451c0b2f7Stbbdev         TestAllocatorExceptions<typename AllocatorFooChar::value_type>(a1);
32551c0b2f7Stbbdev         TestAllocatorExceptions<FooFloat>(b2);
32651c0b2f7Stbbdev         TestAllocatorExceptions<typename AllocatorFooDouble::value_type>(a2);
32751c0b2f7Stbbdev         break;
32851c0b2f7Stbbdev     case ThreadSafety:
32951c0b2f7Stbbdev         TestThreadSafety(a1);
33051c0b2f7Stbbdev         TestThreadSafety(a2);
33151c0b2f7Stbbdev         break;
33251c0b2f7Stbbdev     }
33351c0b2f7Stbbdev     REQUIRE_MESSAGE(NumberOfFoo == 0, "Allocate/deallocate count mismatched");
33451c0b2f7Stbbdev }
3354523a761Stbbdev #endif // __TBB_test_common_allocator_test_common_H_
336