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