151c0b2f7Stbbdev /*
2*b15aabb3Stbbdev     Copyright (c) 2005-2021 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 
2651c0b2f7Stbbdev #include "common/test.h"
2751c0b2f7Stbbdev #include "common/utils.h"
2851c0b2f7Stbbdev #include <utility> //for std::pair
2951c0b2f7Stbbdev #include <cstring>
3051c0b2f7Stbbdev 
3151c0b2f7Stbbdev //! Compile-time error if x and y have different types
3251c0b2f7Stbbdev template<typename T>
3351c0b2f7Stbbdev void AssertSameType( const T& /*x*/, const T& /*y*/ ) {}
3451c0b2f7Stbbdev 
3551c0b2f7Stbbdev //! The function to zero-initialize arrays; useful to avoid warnings
3651c0b2f7Stbbdev template <typename T>
3751c0b2f7Stbbdev void zero_fill(void* array, size_t n) {
3851c0b2f7Stbbdev     memset(array, 0, sizeof(T)*n);
3951c0b2f7Stbbdev }
4051c0b2f7Stbbdev 
4151c0b2f7Stbbdev template<typename A>
4251c0b2f7Stbbdev struct is_zero_filling {
4351c0b2f7Stbbdev     static const bool value = false;
4451c0b2f7Stbbdev };
4551c0b2f7Stbbdev 
4651c0b2f7Stbbdev int NumberOfFoo;
4751c0b2f7Stbbdev 
4851c0b2f7Stbbdev template<typename T, size_t N>
4951c0b2f7Stbbdev struct Foo {
5051c0b2f7Stbbdev     T foo_array[N];
5151c0b2f7Stbbdev     Foo() {
5251c0b2f7Stbbdev         zero_fill<T>(foo_array, N);
5351c0b2f7Stbbdev         ++NumberOfFoo;
5451c0b2f7Stbbdev     }
5551c0b2f7Stbbdev     Foo( const Foo& x ) {
5651c0b2f7Stbbdev         *this = x;
5751c0b2f7Stbbdev         //Internal call of assignment
5851c0b2f7Stbbdev     }
5951c0b2f7Stbbdev     Foo& operator=( const Foo& x ) {
6051c0b2f7Stbbdev         for (size_t i = 0; i < N; i++)
6151c0b2f7Stbbdev             foo_array[i] = x.foo_array[i];
6251c0b2f7Stbbdev         ++NumberOfFoo;
6351c0b2f7Stbbdev         return *this;
6451c0b2f7Stbbdev     }
6551c0b2f7Stbbdev 
6651c0b2f7Stbbdev     ~Foo() {
6751c0b2f7Stbbdev         --NumberOfFoo;
6851c0b2f7Stbbdev     }
6951c0b2f7Stbbdev };
7051c0b2f7Stbbdev 
7151c0b2f7Stbbdev inline char PseudoRandomValue( size_t j, size_t k ) {
7251c0b2f7Stbbdev     return char(j*3 ^ j>>4 ^ k);
7351c0b2f7Stbbdev }
7451c0b2f7Stbbdev 
7551c0b2f7Stbbdev #if __APPLE__
7651c0b2f7Stbbdev #include <fcntl.h>
7751c0b2f7Stbbdev #include <unistd.h>
7851c0b2f7Stbbdev 
7951c0b2f7Stbbdev // A RAII class to disable stderr in a certain scope. It's not thread-safe.
8051c0b2f7Stbbdev class DisableStderr {
8151c0b2f7Stbbdev     int stderrCopy;
8251c0b2f7Stbbdev     static void dupToStderrAndClose(int fd) {
8351c0b2f7Stbbdev         int ret = dup2(fd, STDERR_FILENO); // close current stderr
8451c0b2f7Stbbdev         REQUIRE(ret != -1);
8551c0b2f7Stbbdev         ret = close(fd);
8651c0b2f7Stbbdev         REQUIRE(ret != -1);
8751c0b2f7Stbbdev     }
8851c0b2f7Stbbdev public:
8951c0b2f7Stbbdev     DisableStderr() {
9051c0b2f7Stbbdev         int devNull = open("/dev/null", O_WRONLY);
9151c0b2f7Stbbdev         REQUIRE(devNull != -1);
9251c0b2f7Stbbdev         stderrCopy = dup(STDERR_FILENO);
9351c0b2f7Stbbdev         REQUIRE(stderrCopy != -1);
9451c0b2f7Stbbdev         dupToStderrAndClose(devNull);
9551c0b2f7Stbbdev     }
9651c0b2f7Stbbdev     ~DisableStderr() {
9751c0b2f7Stbbdev         dupToStderrAndClose(stderrCopy);
9851c0b2f7Stbbdev     }
9951c0b2f7Stbbdev };
10051c0b2f7Stbbdev #endif
10151c0b2f7Stbbdev 
10251c0b2f7Stbbdev //! T is type and A is allocator for that type
10351c0b2f7Stbbdev template<typename T, typename A>
10451c0b2f7Stbbdev void TestBrokenAllocator(A& a) {
10551c0b2f7Stbbdev     T x;
10651c0b2f7Stbbdev     const T cx = T();
10751c0b2f7Stbbdev     // See Table 32 in ISO ++ Standard
10851c0b2f7Stbbdev     typename A::pointer px = &x;
10951c0b2f7Stbbdev     typename A::const_pointer pcx = &cx;
11051c0b2f7Stbbdev 
11151c0b2f7Stbbdev     typename A::reference rx = x;
11251c0b2f7Stbbdev     REQUIRE(&rx == &x);
11351c0b2f7Stbbdev 
11451c0b2f7Stbbdev     typename A::const_reference rcx = cx;
11551c0b2f7Stbbdev     REQUIRE(&rcx==&cx);
11651c0b2f7Stbbdev 
11751c0b2f7Stbbdev     typename A::value_type v = x;
11851c0b2f7Stbbdev 
11951c0b2f7Stbbdev     typename A::size_type size;
12051c0b2f7Stbbdev     size = 0;
12151c0b2f7Stbbdev     --size;
12251c0b2f7Stbbdev     REQUIRE_MESSAGE(size > 0, "not an unsigned integral type?");
12351c0b2f7Stbbdev 
12451c0b2f7Stbbdev     typename A::difference_type difference;
12551c0b2f7Stbbdev     difference = 0;
12651c0b2f7Stbbdev     --difference;
12751c0b2f7Stbbdev     REQUIRE_MESSAGE(difference<0, "not an signed integral type?");
12851c0b2f7Stbbdev 
12951c0b2f7Stbbdev     // "rebind" tested by our caller
13051c0b2f7Stbbdev 
13151c0b2f7Stbbdev     REQUIRE(a.address(rx) == px);
13251c0b2f7Stbbdev 
13351c0b2f7Stbbdev     REQUIRE(a.address(rcx) == pcx);
13451c0b2f7Stbbdev 
13551c0b2f7Stbbdev     // Test "a.max_size()"
13651c0b2f7Stbbdev     AssertSameType(a.max_size(), typename A::size_type(0));
13751c0b2f7Stbbdev     // Following assertion catches case where max_size() is so large that computation of
13851c0b2f7Stbbdev     // number of bytes for such an allocation would overflow size_type.
13951c0b2f7Stbbdev     REQUIRE_MESSAGE((a.max_size() * typename A::size_type(sizeof(T)) >= a.max_size()), "max_size larger than reasonable");
14051c0b2f7Stbbdev 
14151c0b2f7Stbbdev     // Test "a.construct(p,t)"
14251c0b2f7Stbbdev     int n = NumberOfFoo;
14351c0b2f7Stbbdev     typename A::pointer p = a.allocate(1);
14451c0b2f7Stbbdev     a.construct(p, cx);
14551c0b2f7Stbbdev     REQUIRE_MESSAGE(NumberOfFoo == n + 1, "constructor for Foo not called?");
14651c0b2f7Stbbdev 
14751c0b2f7Stbbdev     // Test "a.destroy(p)"
14851c0b2f7Stbbdev     a.destroy(p);
14951c0b2f7Stbbdev     REQUIRE_MESSAGE(NumberOfFoo == n, "destructor for Foo not called?");
15051c0b2f7Stbbdev     a.deallocate(p, 1);
15151c0b2f7Stbbdev 
15251c0b2f7Stbbdev     {
15351c0b2f7Stbbdev         typedef typename A::template rebind<std::pair<typename A::value_type, typename A::value_type> >::other pair_allocator_type;
15451c0b2f7Stbbdev         pair_allocator_type pair_allocator(a);
15551c0b2f7Stbbdev         int NumberOfFooBeforeConstruct = NumberOfFoo;
15651c0b2f7Stbbdev         typename pair_allocator_type::pointer pair_pointer = pair_allocator.allocate(1);
15751c0b2f7Stbbdev         pair_allocator.construct(pair_pointer, cx, cx);
15851c0b2f7Stbbdev         REQUIRE_MESSAGE(NumberOfFoo == NumberOfFooBeforeConstruct+2, "constructor for Foo not called appropriate number of times?");
15951c0b2f7Stbbdev 
16051c0b2f7Stbbdev         pair_allocator.destroy(pair_pointer);
16151c0b2f7Stbbdev         REQUIRE_MESSAGE(NumberOfFoo == NumberOfFooBeforeConstruct, "destructor for Foo not called appropriate number of times?");
16251c0b2f7Stbbdev         pair_allocator.deallocate(pair_pointer, 1);
16351c0b2f7Stbbdev     }
16451c0b2f7Stbbdev }
16551c0b2f7Stbbdev 
16651c0b2f7Stbbdev //! T is type and A is allocator for that type
16751c0b2f7Stbbdev template<typename T, typename A>
16851c0b2f7Stbbdev void TestAllocatorConcept(A& a) {
16951c0b2f7Stbbdev     // Test "a.allocate(p,n)
17051c0b2f7Stbbdev     typename std::allocator_traits<A>::pointer array[100];
17151c0b2f7Stbbdev     std::size_t sizeof_T = sizeof(T);
17251c0b2f7Stbbdev     for(std::size_t k = 0; k < 100; ++k) {
17351c0b2f7Stbbdev         array[k] = a.allocate(k);
17451c0b2f7Stbbdev         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[k]));
17551c0b2f7Stbbdev         for(std::size_t j=0; j < k * sizeof_T; ++j)
17651c0b2f7Stbbdev             s[j] = PseudoRandomValue(j, k);
17751c0b2f7Stbbdev     }
17851c0b2f7Stbbdev 
17951c0b2f7Stbbdev     // Test "a.deallocate(p,n)
18051c0b2f7Stbbdev     for(std::size_t k = 0; k < 100; ++k) {
18151c0b2f7Stbbdev         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[k]));
18251c0b2f7Stbbdev         for(std::size_t j = 0; j < k * sizeof_T; ++j)
18351c0b2f7Stbbdev             REQUIRE(s[j] == PseudoRandomValue(j, k));
18451c0b2f7Stbbdev         a.deallocate(array[k], k);
18551c0b2f7Stbbdev     }
18651c0b2f7Stbbdev }
18751c0b2f7Stbbdev 
18851c0b2f7Stbbdev //! T is type and A is allocator for that type
18951c0b2f7Stbbdev template<typename T, typename A>
19051c0b2f7Stbbdev void TestAllocatorExceptions(A& a) {
19151c0b2f7Stbbdev #if TBB_USE_EXCEPTIONS
19251c0b2f7Stbbdev     volatile size_t too_big = (~std::size_t(0) - 1024 * 1024) / sizeof(T);
19351c0b2f7Stbbdev     bool exception_caught = false;
19451c0b2f7Stbbdev     typename std::allocator_traits<A>::pointer p1 = nullptr;
19551c0b2f7Stbbdev     try {
19651c0b2f7Stbbdev #if __APPLE__
19751c0b2f7Stbbdev         // On macOS*, failure to map memory results in messages to stderr;
19851c0b2f7Stbbdev         // suppress them.
19951c0b2f7Stbbdev         DisableStderr disableStderr;
20051c0b2f7Stbbdev #endif
20151c0b2f7Stbbdev         p1 = a.allocate(too_big);
20251c0b2f7Stbbdev     } catch (std::bad_alloc&) {
20351c0b2f7Stbbdev         exception_caught = true;
20451c0b2f7Stbbdev     }
20551c0b2f7Stbbdev     REQUIRE_MESSAGE(exception_caught, "allocate expected to throw bad_alloc");
20651c0b2f7Stbbdev     a.deallocate(p1, too_big);
20751c0b2f7Stbbdev #endif // TBB_USE_EXCEPTIONS
20851c0b2f7Stbbdev     utils::suppress_unused_warning(a);
20951c0b2f7Stbbdev }
21051c0b2f7Stbbdev 
21151c0b2f7Stbbdev #if _MSC_VER && !defined(__INTEL_COMPILER)
21251c0b2f7Stbbdev     // Workaround for erroneous "conditional expression is constant" warning in method check_allocate.
21351c0b2f7Stbbdev     #pragma warning (disable: 4127)
21451c0b2f7Stbbdev #endif
21551c0b2f7Stbbdev 
21651c0b2f7Stbbdev // A is an allocator for some type
21751c0b2f7Stbbdev template<typename A>
21851c0b2f7Stbbdev struct Body: utils::NoAssign {
21951c0b2f7Stbbdev     using pointer_type = typename std::allocator_traits<A>::pointer;
22051c0b2f7Stbbdev     using value_type = typename std::allocator_traits<A>::value_type;
22151c0b2f7Stbbdev     // For the int types and above this test runs too long
22251c0b2f7Stbbdev     static const std::size_t max_k = sizeof(value_type) < sizeof(int) ? 100000 : 5000;
22351c0b2f7Stbbdev     A &a;
22451c0b2f7Stbbdev     Body(A &a_) : a(a_) {}
22551c0b2f7Stbbdev     void check_allocate(pointer_type array[], std::size_t i, std::size_t t) const
22651c0b2f7Stbbdev     {
22751c0b2f7Stbbdev         REQUIRE(array[i] == nullptr);
22851c0b2f7Stbbdev         std::size_t size = i * (i & 3);
22951c0b2f7Stbbdev         array[i] = a.allocate(size);
23051c0b2f7Stbbdev         REQUIRE_MESSAGE(array[i] != nullptr, "allocator returned null");
23151c0b2f7Stbbdev         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[i]));
23251c0b2f7Stbbdev         for(std::size_t j = 0; j < size * sizeof(value_type); ++j) {
23351c0b2f7Stbbdev             if(is_zero_filling<typename std::allocator_traits<A>::template rebind_alloc<void>>::value)
23451c0b2f7Stbbdev                 REQUIRE(!s[j]);
23551c0b2f7Stbbdev             s[j] = PseudoRandomValue(i, t);
23651c0b2f7Stbbdev         }
23751c0b2f7Stbbdev     }
23851c0b2f7Stbbdev 
23951c0b2f7Stbbdev     void check_deallocate(pointer_type array[], std::size_t i, std::size_t t) const
24051c0b2f7Stbbdev     {
24151c0b2f7Stbbdev         REQUIRE(array[i] != nullptr);
24251c0b2f7Stbbdev         size_t size = i * (i & 3);
24351c0b2f7Stbbdev         char* s = reinterpret_cast<char*>(reinterpret_cast<void*>(array[i]));
24451c0b2f7Stbbdev         for(std::size_t j=0; j < size * sizeof(value_type); ++j)
24551c0b2f7Stbbdev             REQUIRE_MESSAGE(s[j] == PseudoRandomValue(i, t), "Thread safety test failed");
24651c0b2f7Stbbdev         a.deallocate(array[i], size);
24751c0b2f7Stbbdev         array[i] = 0;
24851c0b2f7Stbbdev     }
24951c0b2f7Stbbdev 
25051c0b2f7Stbbdev     void operator()(std::size_t thread_id) const {
25151c0b2f7Stbbdev         pointer_type array[256];
25251c0b2f7Stbbdev 
25351c0b2f7Stbbdev         for(std::size_t k = 0; k < 256; ++k)
25451c0b2f7Stbbdev             array[k] = 0;
25551c0b2f7Stbbdev         for(std::size_t k = 0; k < max_k; ++k) {
25651c0b2f7Stbbdev             std::size_t i = static_cast<unsigned char>(PseudoRandomValue(k, thread_id));
25751c0b2f7Stbbdev             if(!array[i]) check_allocate(array, i, thread_id);
25851c0b2f7Stbbdev             else check_deallocate(array, i, thread_id);
25951c0b2f7Stbbdev         }
26051c0b2f7Stbbdev         for(std::size_t k = 0; k < 256; ++k)
26151c0b2f7Stbbdev             if(array[k])
26251c0b2f7Stbbdev                 check_deallocate(array, k, thread_id);
26351c0b2f7Stbbdev     }
26451c0b2f7Stbbdev };
26551c0b2f7Stbbdev 
26651c0b2f7Stbbdev template<typename A>
26751c0b2f7Stbbdev void TestThreadSafety(A &a) {
26851c0b2f7Stbbdev     utils::NativeParallelFor(4, Body<A>(a));
26951c0b2f7Stbbdev }
27051c0b2f7Stbbdev 
27151c0b2f7Stbbdev enum TestName { Concept, Broken, Exceptions, ThreadSafety, Comparison };
27251c0b2f7Stbbdev 
27351c0b2f7Stbbdev template<typename Allocator>
27451c0b2f7Stbbdev void TestAllocator(TestName name, const Allocator &a = Allocator()) {
27551c0b2f7Stbbdev 
27651c0b2f7Stbbdev     using FooChar = Foo<char, 1>;
27751c0b2f7Stbbdev     using FooDouble = Foo<double, 1>;
27851c0b2f7Stbbdev     using FooInt = Foo<int, 17>;
27951c0b2f7Stbbdev     using FooFloat = Foo<float, 23>;
28051c0b2f7Stbbdev     #if TBB_ALLOCATOR_TRAITS_BROKEN
28151c0b2f7Stbbdev         using AllocatorFooChar = typename Allocator::template rebind<FooChar>::other;
28251c0b2f7Stbbdev         using AllocatorFooDouble = typename Allocator::template rebind<FooDouble>::other;
28351c0b2f7Stbbdev         using AllocatorFooInt = typename AllocatorFooChar::template rebind<FooInt>::other;
28451c0b2f7Stbbdev         using AllocatorFooFloat = typename AllocatorFooDouble::template rebind<FooFloat>::other;
28551c0b2f7Stbbdev     #else
28651c0b2f7Stbbdev         using AllocatorFooChar = typename std::allocator_traits<Allocator>::template rebind_alloc<FooChar>;
28751c0b2f7Stbbdev         using AllocatorFooDouble = typename std::allocator_traits<Allocator>::template rebind_alloc<FooDouble>;
28851c0b2f7Stbbdev         using AllocatorFooInt = typename std::allocator_traits<AllocatorFooChar>::template rebind_alloc<FooInt>;
28951c0b2f7Stbbdev         using AllocatorFooFloat = typename std::allocator_traits<AllocatorFooDouble>::template rebind_alloc<FooFloat>;
29051c0b2f7Stbbdev     #endif
29151c0b2f7Stbbdev 
29251c0b2f7Stbbdev     NumberOfFoo = 0;
29351c0b2f7Stbbdev     AllocatorFooChar a1(a);
29451c0b2f7Stbbdev     AllocatorFooDouble a2(a);
29551c0b2f7Stbbdev     AllocatorFooInt b1(a1);
29651c0b2f7Stbbdev     AllocatorFooFloat b2(a2);
29751c0b2f7Stbbdev 
29851c0b2f7Stbbdev     switch(name) {
29951c0b2f7Stbbdev     case Comparison:
30051c0b2f7Stbbdev         REQUIRE(a1 == b1);
30151c0b2f7Stbbdev         REQUIRE(!(a2 != b2));
30251c0b2f7Stbbdev         break;
30351c0b2f7Stbbdev     case Concept:
30451c0b2f7Stbbdev         TestAllocatorConcept<FooInt>(b1);
30551c0b2f7Stbbdev         TestAllocatorConcept<typename AllocatorFooChar::value_type>(a1);
30651c0b2f7Stbbdev         TestAllocatorConcept<FooFloat>(b2);
30751c0b2f7Stbbdev         TestAllocatorConcept<typename AllocatorFooDouble::value_type>(a2);
30851c0b2f7Stbbdev         break;
30951c0b2f7Stbbdev     case Broken:
31051c0b2f7Stbbdev     #if TBB_ALLOCATOR_TRAITS_BROKEN
31151c0b2f7Stbbdev         TestBrokenAllocator<FooInt>(b1);
31251c0b2f7Stbbdev         TestBrokenAllocator<typename AllocatorFooChar::value_type>(a1);
31351c0b2f7Stbbdev         TestBrokenAllocator<FooFloat>(b2);
31451c0b2f7Stbbdev         TestBrokenAllocator<typename AllocatorFooDouble::value_type>(a2);
31551c0b2f7Stbbdev     #endif
31651c0b2f7Stbbdev         break;
31751c0b2f7Stbbdev     case Exceptions:
31851c0b2f7Stbbdev         TestAllocatorExceptions<FooInt>(b1);
31951c0b2f7Stbbdev         TestAllocatorExceptions<typename AllocatorFooChar::value_type>(a1);
32051c0b2f7Stbbdev         TestAllocatorExceptions<FooFloat>(b2);
32151c0b2f7Stbbdev         TestAllocatorExceptions<typename AllocatorFooDouble::value_type>(a2);
32251c0b2f7Stbbdev         break;
32351c0b2f7Stbbdev     case ThreadSafety:
32451c0b2f7Stbbdev         TestThreadSafety(a1);
32551c0b2f7Stbbdev         TestThreadSafety(a2);
32651c0b2f7Stbbdev         break;
32751c0b2f7Stbbdev     }
32851c0b2f7Stbbdev     REQUIRE_MESSAGE(NumberOfFoo == 0, "Allocate/deallocate count mismatched");
32951c0b2f7Stbbdev }
330