You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1514 lines
59 KiB

/*
Copyright 2005-2013 Intel Corporation. All Rights Reserved.
This file is part of Threading Building Blocks.
Threading Building Blocks is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
Threading Building Blocks is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty
of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Threading Building Blocks; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
As a special exception, you may use this file as part of a free software
library without restriction. Specifically, if other files instantiate
templates or use macros or inline functions from this file, or you compile
this file and link it with other files to produce an executable, this
file does not by itself cause the resulting executable to be covered by
the GNU General Public License. This exception does not however
invalidate any other reasons why the executable file might be covered by
the GNU General Public License.
*/
#include "harness_defs.h"
#if __TBB_TEST_SKIP_PIC_MODE || (__TBB_TEST_SKIP_GCC_BUILTINS_MODE && __TBB_TEST_SKIP_ICC_BUILTINS_MODE)
#include "harness.h"
int TestMain() {
REPORT("Known issue: %s\n",
__TBB_TEST_SKIP_PIC_MODE? "PIC mode is not supported" : "GCC/ICC builtins aren't available");
return Harness::Skipped;
}
#else
// Put tbb/atomic.h first, so if it is missing a prerequisite header, we find out about it.
// The tests here do *not* test for atomicity, just serial correctness. */
#include "tbb/atomic.h"
#include "harness_assert.h"
#include <cstring> // memcmp
#include "tbb/aligned_space.h"
#include <new> //for placement new
using std::memcmp;
#if _MSC_VER && !defined(__INTEL_COMPILER)
// Unary minus operator applied to unsigned type, result still unsigned
// Constant conditional expression
#pragma warning( disable: 4127 4310 )
#endif
enum LoadStoreExpression {
UseOperators,
UseImplicitAcqRel,
UseExplicitFullyFenced,
UseExplicitAcqRel,
UseExplicitRelaxed,
UseGlobalHelperFullyFenced,
UseGlobalHelperAcqRel,
UseGlobalHelperRelaxed
};
//! Structure that holds an atomic<T> and some guard bytes around it.
template<typename T, LoadStoreExpression E = UseOperators>
struct TestStruct {
typedef unsigned char byte_type;
T prefix;
tbb::atomic<T> counter;
T suffix;
TestStruct( T i ) {
ASSERT( sizeof(*this)==3*sizeof(T), NULL );
for (size_t j = 0; j < sizeof(T); ++j) {
reinterpret_cast<byte_type*>(&prefix)[j] = byte_type(0x11*(j+1));
reinterpret_cast<byte_type*>(&suffix)[sizeof(T)-j-1] = byte_type(0x11*(j+1));
}
if ( E == UseOperators )
counter = i;
else if ( E == UseExplicitRelaxed )
counter.template store<tbb::relaxed>(i);
else
tbb::store<tbb::full_fence>( counter, i );
}
~TestStruct() {
// Check for writes outside the counter.
for (size_t j = 0; j < sizeof(T); ++j) {
ASSERT( reinterpret_cast<byte_type*>(&prefix)[j] == byte_type(0x11*(j+1)), NULL );
ASSERT( reinterpret_cast<byte_type*>(&suffix)[sizeof(T)-j-1] == byte_type(0x11*(j+1)), NULL );
}
}
static tbb::atomic<T> gCounter;
};
// A global variable of type tbb::atomic<>
template<typename T, LoadStoreExpression E> tbb::atomic<T> TestStruct<T, E>::gCounter;
//! Test compare_and_swap template members of class atomic<T> for memory_semantics=M
template<typename T,tbb::memory_semantics M>
void TestCompareAndSwapWithExplicitOrdering( T i, T j, T k ) {
ASSERT( i!=k, "values must be distinct" );
// Test compare_and_swap that should fail
TestStruct<T> x(i);
T old = x.counter.template compare_and_swap<M>( j, k );
ASSERT( old==i, NULL );
ASSERT( x.counter==i, "old value not retained" );
// Test compare and swap that should succeed
old = x.counter.template compare_and_swap<M>( j, i );
ASSERT( old==i, NULL );
ASSERT( x.counter==j, "value not updated?" );
}
//! i, j, k must be different values
template<typename T>
void TestCompareAndSwap( T i, T j, T k ) {
ASSERT( i!=k, "values must be distinct" );
// Test compare_and_swap that should fail
TestStruct<T> x(i);
T old = x.counter.compare_and_swap( j, k );
ASSERT( old==i, NULL );
ASSERT( x.counter==i, "old value not retained" );
// Test compare and swap that should succeed
old = x.counter.compare_and_swap( j, i );
ASSERT( old==i, NULL );
if( x.counter==i ) {
ASSERT( x.counter==j, "value not updated?" );
} else {
ASSERT( x.counter==j, "value trashed" );
}
// Check that atomic global variables work
TestStruct<T>::gCounter = i;
old = TestStruct<T>::gCounter.compare_and_swap( j, i );
ASSERT( old==i, NULL );
ASSERT( TestStruct<T>::gCounter==j, "value not updated?" );
TestCompareAndSwapWithExplicitOrdering<T,tbb::full_fence>(i,j,k);
TestCompareAndSwapWithExplicitOrdering<T,tbb::acquire>(i,j,k);
TestCompareAndSwapWithExplicitOrdering<T,tbb::release>(i,j,k);
TestCompareAndSwapWithExplicitOrdering<T,tbb::relaxed>(i,j,k);
}
//! memory_semantics variation on TestFetchAndStore
template<typename T, tbb::memory_semantics M>
void TestFetchAndStoreWithExplicitOrdering( T i, T j ) {
ASSERT( i!=j, "values must be distinct" );
TestStruct<T> x(i);
T old = x.counter.template fetch_and_store<M>( j );
ASSERT( old==i, NULL );
ASSERT( x.counter==j, NULL );
}
//! i and j must be different values
template<typename T>
void TestFetchAndStore( T i, T j ) {
ASSERT( i!=j, "values must be distinct" );
TestStruct<T> x(i);
T old = x.counter.fetch_and_store( j );
ASSERT( old==i, NULL );
ASSERT( x.counter==j, NULL );
// Check that atomic global variables work
TestStruct<T>::gCounter = i;
old = TestStruct<T>::gCounter.fetch_and_store( j );
ASSERT( old==i, NULL );
ASSERT( TestStruct<T>::gCounter==j, "value not updated?" );
TestFetchAndStoreWithExplicitOrdering<T,tbb::full_fence>(i,j);
TestFetchAndStoreWithExplicitOrdering<T,tbb::acquire>(i,j);
TestFetchAndStoreWithExplicitOrdering<T,tbb::release>(i,j);
TestFetchAndStoreWithExplicitOrdering<T,tbb::relaxed>(i,j);
}
#if _MSC_VER && !defined(__INTEL_COMPILER)
// conversion from <bigger integer> to <smaller integer>, possible loss of data
// the warning seems a complete nonsense when issued for e.g. short+=short
#pragma warning( disable: 4244 )
#endif
//! Test fetch_and_add members of class atomic<T> for memory_semantics=M
template<typename T,tbb::memory_semantics M>
void TestFetchAndAddWithExplicitOrdering( T i ) {
TestStruct<T> x(i);
T actual;
T expected = i;
// Test fetch_and_add member template
for( int j=0; j<10; ++j ) {
actual = x.counter.fetch_and_add(j);
ASSERT( actual==expected, NULL );
expected += j;
}
for( int j=0; j<10; ++j ) {
actual = x.counter.fetch_and_add(-j);
ASSERT( actual==expected, NULL );
expected -= j;
}
// Test fetch_and_increment member template
ASSERT( x.counter==i, NULL );
actual = x.counter.template fetch_and_increment<M>();
ASSERT( actual==i, NULL );
ASSERT( x.counter==T(i+1), NULL );
// Test fetch_and_decrement member template
actual = x.counter.template fetch_and_decrement<M>();
ASSERT( actual==T(i+1), NULL );
ASSERT( x.counter==i, NULL );
}
//! Test fetch_and_add and related operators
template<typename T>
void TestFetchAndAdd( T i ) {
TestStruct<T> x(i);
T value;
value = ++x.counter;
ASSERT( value==T(i+1), NULL );
value = x.counter++;
ASSERT( value==T(i+1), NULL );
value = x.counter--;
ASSERT( value==T(i+2), NULL );
value = --x.counter;
ASSERT( value==i, NULL );
T actual;
T expected = i;
for( int j=-100; j<=100; ++j ) {
expected += j;
actual = x.counter += j;
ASSERT( actual==expected, NULL );
}
for( int j=-100; j<=100; ++j ) {
expected -= j;
actual = x.counter -= j;
ASSERT( actual==expected, NULL );
}
// Test fetch_and_increment
ASSERT( x.counter==i, NULL );
actual = x.counter.fetch_and_increment();
ASSERT( actual==i, NULL );
ASSERT( x.counter==T(i+1), NULL );
// Test fetch_and_decrement
actual = x.counter.fetch_and_decrement();
ASSERT( actual==T(i+1), NULL );
ASSERT( x.counter==i, NULL );
x.counter = i;
ASSERT( x.counter==i, NULL );
// Check that atomic global variables work
TestStruct<T>::gCounter = i;
value = TestStruct<T>::gCounter.fetch_and_add( 42 );
expected = i+42;
ASSERT( value==i, NULL );
ASSERT( TestStruct<T>::gCounter==expected, "value not updated?" );
TestFetchAndAddWithExplicitOrdering<T,tbb::full_fence>(i);
TestFetchAndAddWithExplicitOrdering<T,tbb::acquire>(i);
TestFetchAndAddWithExplicitOrdering<T,tbb::release>(i);
TestFetchAndAddWithExplicitOrdering<T,tbb::relaxed>(i);
}
//! A type with unknown size.
class IncompleteType;
void TestFetchAndAdd( IncompleteType* ) {
// There are no fetch-and-add operations on a IncompleteType*.
}
void TestFetchAndAdd( void* ) {
// There are no fetch-and-add operations on a void*.
}
void TestFetchAndAdd( bool ) {
// There are no fetch-and-add operations on a bool.
}
template<typename T>
void TestConst( T i ) {
// Try const
const TestStruct<T> x(i);
ASSERT( memcmp( &i, &x.counter, sizeof(T) )==0, "write to atomic<T> broken?" );
ASSERT( x.counter==i, "read of atomic<T> broken?" );
const TestStruct<T, UseExplicitRelaxed> y(i);
ASSERT( memcmp( &i, &y.counter, sizeof(T) )==0, "relaxed write to atomic<T> broken?" );
ASSERT( tbb::load<tbb::relaxed>(y.counter) == i, "relaxed read of atomic<T> broken?" );
const TestStruct<T, UseGlobalHelperFullyFenced> z(i);
ASSERT( memcmp( &i, &z.counter, sizeof(T) )==0, "sequentially consistent write to atomic<T> broken?" );
ASSERT( z.counter.template load<tbb::full_fence>() == i, "sequentially consistent read of atomic<T> broken?" );
}
#include "harness.h"
#include <sstream>
//TODO: consider moving it to separate file, and unify with one in examples command line interface
template<typename T>
std::string to_string(const T& a){
std::stringstream str; str <<a;
return str.str();
}
namespace initialization_tests {
template<typename T>
struct test_initialization_fixture{
typedef tbb::atomic<T> atomic_t;
tbb::aligned_space<atomic_t,1> non_zeroed_storage;
enum {fill_value = 0xFF };
test_initialization_fixture(){
memset(non_zeroed_storage.begin(),fill_value,sizeof(non_zeroed_storage));
ASSERT( char(fill_value)==*(tbb::internal::punned_cast<char*>(non_zeroed_storage.begin()))
,"failed to fill the storage; memset error?");
}
//TODO: consider move it to destructor, even in a price of UB
void tear_down(){
non_zeroed_storage.begin()->~atomic_t();
}
};
template<typename T>
struct TestValueInitialization : test_initialization_fixture<T>{
void operator()(){
typedef typename test_initialization_fixture<T>::atomic_t atomic_type;
//please note that explicit braces below are needed to get zero initialization.
//in C++11, 8.5 Initializers [dcl.init], see paragraphs 10,7,5
new (this->non_zeroed_storage.begin()) atomic_type();
//TODO: add use of KNOWN_ISSUE macro on SunCC 5.11
#if !__SUNPRO_CC || __SUNPRO_CC > 0x5110
//TODO: add printing of typename to the assertion
ASSERT(char(0)==*(tbb::internal::punned_cast<char*>(this->non_zeroed_storage.begin()))
,("value initialization for tbb::atomic should do zero initialization; "
"actual value:"+to_string(this->non_zeroed_storage.begin()->load())).c_str());
#endif
this->tear_down();
};
};
template<typename T>
struct TestDefaultInitialization : test_initialization_fixture<T>{
void operator ()(){
typedef typename test_initialization_fixture<T>::atomic_t atomic_type;
new (this->non_zeroed_storage.begin()) atomic_type;
ASSERT( char(this->fill_value)==*(tbb::internal::punned_cast<char*>(this->non_zeroed_storage.begin()))
,"default initialization for atomic should do no initialization");
this->tear_down();
}
};
# if __TBB_ATOMIC_CTORS
template<typename T>
struct TestDirectInitialization : test_initialization_fixture<T> {
void operator()(T i){
typedef typename test_initialization_fixture<T>::atomic_t atomic_type;
new (this->non_zeroed_storage.begin()) atomic_type(i);
ASSERT(i == this->non_zeroed_storage.begin()->load()
,("tbb::atomic initialization failed; "
"value:"+to_string(this->non_zeroed_storage.begin()->load())+
"; expected:"+to_string(i)).c_str());
this->tear_down();
}
};
# endif
}
template<typename T>
void TestValueInitialization(){
initialization_tests::TestValueInitialization<T>()();
}
template<typename T>
void TestDefaultInitialization(){
initialization_tests::TestDefaultInitialization<T>()();
}
#if __TBB_ATOMIC_CTORS
template<typename T>
void TestDirectInitialization(T i){
initialization_tests::TestDirectInitialization<T>()(i);
}
//TODO: it would be great to have constructor doing dynamic initialization of local atomic objects implicitly (with zero?),
// but do no dynamic initializations by default for static objects
namespace test_constexpr_initialization_helper {
struct white_box_ad_hoc_type {
int _int;
constexpr white_box_ad_hoc_type(int a =0) : _int(a) {};
constexpr operator int() const { return _int;}
};
}
//some white boxing
namespace tbb { namespace internal {
template<>
struct atomic_impl<test_constexpr_initialization_helper::white_box_ad_hoc_type>: atomic_impl<int> {
atomic_impl() = default;
constexpr atomic_impl(test_constexpr_initialization_helper::white_box_ad_hoc_type value):atomic_impl<int>(value){}
constexpr operator int(){ return this->my_storage.my_value;}
};
}}
//TODO: make this a parameterized macro
void TestConstExprInitializationIsTranslationTime(){
const char* ct_init_failed_msg = "translation time init failed?";
typedef tbb::atomic<int> atomic_t;
constexpr atomic_t a(8);
ASSERT(a == 8,ct_init_failed_msg);
constexpr tbb::atomic<test_constexpr_initialization_helper::white_box_ad_hoc_type> ct_atomic(10);
enum {ct_atomic_value_ten = (int)ct_atomic};
__TBB_STATIC_ASSERT(ct_atomic_value_ten == 10, "translation time init failed?");
ASSERT(ct_atomic_value_ten == 10,ct_init_failed_msg);
int array[ct_atomic_value_ten];
ASSERT(array_length(array) == 10,ct_init_failed_msg);
}
#include <string>
#include <vector>
namespace TestConstExprInitializationOfGlobalObjectsHelper{
struct static_objects_dynamic_init_order_tester {
static int order_hash;
template<int N> struct nth {
nth(){ order_hash = (order_hash<<4)+N; }
};
static nth<2> second;
static nth<3> third;
};
int static_objects_dynamic_init_order_tester::order_hash=1;
static_objects_dynamic_init_order_tester::nth<2> static_objects_dynamic_init_order_tester::second;
static_objects_dynamic_init_order_tester::nth<3> static_objects_dynamic_init_order_tester::third;
void TestStaticsDynamicInitializationOrder(){
ASSERT(static_objects_dynamic_init_order_tester::order_hash==0x123,"Statics dynamic initialization order is broken? ");
}
template<typename T>
void TestStaticInit();
namespace auto_registered_tests_helper {
template<typename T>
struct type_name ;
#define REGISTER_TYPE_NAME(T) \
namespace auto_registered_tests_helper{ \
template<> \
struct type_name<T> { \
static const char* name; \
}; \
const char* type_name<T>::name = #T; \
} \
typedef void (* p_test_function_type)();
static std::vector<p_test_function_type> const_expr_tests;
template <typename T>
struct registration{
registration(){const_expr_tests.push_back(TestStaticInit<T>);}
};
}
//according to ISO C++11 [basic.start.init], static data fields of class template have unordered
//initialization unless it is an explicit specialization
template<typename T>
struct tester;
#define TESTER_SPECIALIZATION(T,ct_value) \
template<> \
struct tester<T> { \
struct static_before; \
static bool result; \
static static_before static_before_; \
static tbb::atomic<T> static_atomic; \
\
static auto_registered_tests_helper::registration<T> registered; \
}; \
bool tester<T>::result = false; \
\
struct tester<T>::static_before { \
static_before(){ result = (static_atomic==ct_value); } \
} ; \
\
typename tester<T>::static_before tester<T>::static_before_; \
tbb::atomic<T> tester<T>::static_atomic(ct_value); \
\
auto_registered_tests_helper::registration<T> tester<T>::registered; \
REGISTER_TYPE_NAME(T) \
template<typename T>
void TestStaticInit(){
//TODO: add printing of values to the assertion
std::string type_name = auto_registered_tests_helper::type_name<T>::name;
ASSERT(tester<T>::result,("Static initialization failed for atomic " + type_name).c_str());
}
void CallExprInitTests(){
using namespace auto_registered_tests_helper;
for (size_t i =0; i<const_expr_tests.size(); ++i){
(*const_expr_tests[i])();
}
REMARK("ran %d consrexpr static init test \n",const_expr_tests.size());
}
//TODO: unify somehow list of tested types with one in TestMain
//TODO: add specializations for:
//T,T(-T(1)
//T,1
# if __TBB_64BIT_ATOMICS
TESTER_SPECIALIZATION(long long,8LL)
TESTER_SPECIALIZATION(unsigned long long,8ULL)
# endif
TESTER_SPECIALIZATION(unsigned long,8UL)
TESTER_SPECIALIZATION(long,8L)
TESTER_SPECIALIZATION(unsigned int,8U)
TESTER_SPECIALIZATION(int,8)
TESTER_SPECIALIZATION(unsigned short,8)
TESTER_SPECIALIZATION(short,8)
TESTER_SPECIALIZATION(unsigned char,8)
TESTER_SPECIALIZATION(signed char,8)
TESTER_SPECIALIZATION(char,8)
TESTER_SPECIALIZATION(wchar_t,8)
int dummy;
TESTER_SPECIALIZATION(void*,&dummy);
TESTER_SPECIALIZATION(bool,false);
//TODO: add test for constexpt initialization of floating types
//for some unknown reasons 0.1 becomes 0.10000001 and equality comparison fails
enum written_number_enum{one=2,two};
TESTER_SPECIALIZATION(written_number_enum,one);
//TODO: add test for ArrayElement<> as in TestMain
}
void TestConstExprInitializationOfGlobalObjects(){
//first assert that assumption the test based on are correct
TestConstExprInitializationOfGlobalObjectsHelper::TestStaticsDynamicInitializationOrder();
TestConstExprInitializationOfGlobalObjectsHelper::CallExprInitTests();
}
#endif //__TBB_ATOMIC_CTORS
template<typename T>
void TestOperations( T i, T j, T k ) {
TestValueInitialization<T>();
TestDefaultInitialization<T>();
# if __TBB_ATOMIC_CTORS
TestConstExprInitializationIsTranslationTime();
TestDirectInitialization<T>(i);
TestDirectInitialization<T>(j);
TestDirectInitialization<T>(k);
# endif
TestConst(i);
TestCompareAndSwap(i,j,k);
TestFetchAndStore(i,k); // Pass i,k instead of i,j, because callee requires two distinct values.
}
template<typename T>
void TestParallel( const char* name );
bool ParallelError;
template<typename T>
struct AlignmentChecker {
char c;
tbb::atomic<T> i;
};
//TODO: candidate for test_compiler?
template<typename T>
void TestAlignment( const char* name ) {
AlignmentChecker<T> ac;
tbb::atomic<T> x;
x = T(0);
bool is_stack_variable_aligned = tbb::internal::is_aligned(&x,sizeof(T));
bool is_member_variable_aligned = tbb::internal::is_aligned(&ac.i,sizeof(T));
bool is_struct_size_correct = (sizeof(AlignmentChecker<T>)==2*sizeof(tbb::atomic<T>));
bool known_issue_condition = __TBB_FORCE_64BIT_ALIGNMENT_BROKEN && ( sizeof(T)==8);
//TODO: replace these ifs with KNOWN_ISSUE macro when it available
if (!is_stack_variable_aligned){
std::string msg = "Compiler failed to properly align local atomic variable?; size:"+to_string(sizeof(T)) + " type: "
+to_string(name) + " location:" + to_string(&x) +"\n";
if (known_issue_condition) {
REPORT(("Known issue: "+ msg).c_str());
}else{
ASSERT(false,msg.c_str());
}
}
if (!is_member_variable_aligned){
std::string msg = "Compiler failed to properly align atomic member variable?; size:"+to_string(sizeof(T)) + " type: "
+to_string(name) + " location:" + to_string(&ac.i) +"\n";
if (known_issue_condition) {
REPORT(("Known issue: "+ msg).c_str());
}else{
ASSERT(false,msg.c_str());
}
}
if (!is_struct_size_correct){
std::string msg = "Compiler failed to properly add padding to structure with atomic member variable?; Structure size:"+to_string(sizeof(AlignmentChecker<T>))
+ " atomic size:"+to_string(sizeof(tbb::atomic<T>)) + " type: " + to_string(name) +"\n";
if (known_issue_condition) {
REPORT(("Known issue: "+ msg).c_str());
}else{
ASSERT(false,msg.c_str());
}
}
AlignmentChecker<T> array[5];
for( int k=0; k<5; ++k ) {
bool is_member_variable_in_array_aligned = tbb::internal::is_aligned(&array[k].i,sizeof(T));
if (!is_member_variable_in_array_aligned) {
std::string msg = "Compiler failed to properly align atomic member variable inside an array?; size:"+to_string(sizeof(T)) + " type:"+to_string(name)
+ " location:" + to_string(&array[k].i) + "\n";
if (known_issue_condition){
REPORT(("Known issue: "+ msg).c_str());
}else{
ASSERT(false,msg.c_str());
}
}
}
}
#if _MSC_VER && !defined(__INTEL_COMPILER)
// unary minus operator applied to unsigned type, result still unsigned
#pragma warning( disable: 4146 )
#endif
/** T is an integral type. */
template<typename T>
void TestAtomicInteger( const char* name ) {
REMARK("testing atomic<%s> (size=%d)\n",name,sizeof(tbb::atomic<T>));
TestAlignment<T>(name);
TestOperations<T>(0L,T(-T(1)),T(1));
for( int k=0; k<int(sizeof(long))*8-1; ++k ) {
TestOperations<T>(T(1L<<k),T(~(1L<<k)),T(1-(1L<<k)));
TestOperations<T>(T(-1L<<k),T(~(-1L<<k)),T(1-(-1L<<k)));
TestFetchAndAdd<T>(T(-1L<<k));
}
TestParallel<T>( name );
}
namespace test_indirection_helpers {
template<typename T>
struct Foo {
//this constructor is needed to workaround ICC intrinsics port (compiler ?)bug, firing assertion below
//TODO: move this under #if
Foo(): x(), y(), z() {}
T x, y, z;
};
}
template<typename T>
void TestIndirection() {
using test_indirection_helpers::Foo;
Foo<T> item;
tbb::atomic<Foo<T>*> pointer;
pointer = &item;
for( int k=-10; k<=10; ++k ) {
// Test various syntaxes for indirection to fields with non-zero offset.
T value1=T(), value2=T();
for( size_t j=0; j<sizeof(T); ++j ) {
((char*)&value1)[j] = char(k^j);
((char*)&value2)[j] = char(k^j*j);
}
pointer->y = value1;
(*pointer).z = value2;
T result1 = (*pointer).y;
T result2 = pointer->z;
//TODO: investigate (fill a bug?)assertion failure bellow for ICC (12.1.2?) intrinsic port for sizes of 4,6,7
//and remove default constructor for test_indirection_helpers::Foo
#if !TBB_USE_ICC_BUILTINS
ASSERT( memcmp(&value1,&result1,sizeof(T))==0, NULL );
ASSERT( memcmp(&value2,&result2,sizeof(T))==0, NULL );
#else
if ( (memcmp(&value1,&result1,sizeof(T))!=0)
|| (memcmp(&value2,&result2,sizeof(T))!=0))
{
REMARK_ONCE("Known Issue: ICC builtins port seems to generate wrong code of atomic::operator* "
"and operator*-> for some types \n");
}
#endif
}
}
//! Test atomic<T*>
template<typename T>
void TestAtomicPointer() {
REMARK("testing atomic pointer (%d)\n",int(sizeof(T)));
T array[1000];
TestOperations<T*>(&array[500],&array[250],&array[750]);
TestFetchAndAdd<T*>(&array[500]);
TestIndirection<T>();
TestParallel<T*>( "pointer" );
}
//! Test atomic<Ptr> where Ptr is a pointer to a type of unknown size
template<typename Ptr>
void TestAtomicPointerToTypeOfUnknownSize( const char* name ) {
REMARK("testing atomic<%s>\n",name);
char array[1000];
TestOperations<Ptr>((Ptr)(void*)&array[500],(Ptr)(void*)&array[250],(Ptr)(void*)&array[750]);
TestParallel<Ptr>( name );
}
void TestAtomicBool() {
REMARK("testing atomic<bool>\n");
TestOperations<bool>(true,true,false);
TestOperations<bool>(false,false,true);
TestParallel<bool>( "bool" );
}
enum Color {Red=0,Green=1,Blue=-1};
void TestAtomicEnum() {
REMARK("testing atomic<Color>\n");
TestOperations<Color>(Red,Green,Blue);
TestParallel<Color>( "Color" );
}
template<typename T>
void TestAtomicFloat( const char* name ) {
REMARK("testing atomic<%s>\n", name );
TestAlignment<T>(name);
TestOperations<T>(0.5,3.25,10.75);
TestParallel<T>( name );
}
#if __TBB_BIG_ENDIAN!=-1
namespace masked_cas_helpers {
const int numMaskedOperations = 100000;
const int testSpaceSize = 8;
int prime[testSpaceSize] = {3,5,7,11,13,17,19,23};
template<typename T>
class TestMaskedCAS_Body: NoAssign {
T* test_space_uncontended;
T* test_space_contended;
public:
TestMaskedCAS_Body( T* _space1, T* _space2 ) : test_space_uncontended(_space1), test_space_contended(_space2) {}
void operator()( int my_idx ) const {
using tbb::internal::__TBB_MaskedCompareAndSwap;
const T my_prime = T(prime[my_idx]);
T* const my_ptr = test_space_uncontended+my_idx;
T old_value=0;
for( int i=0; i<numMaskedOperations; ++i, old_value+=my_prime ){
T result;
// Test uncontended case
T new_value = old_value + my_prime;
// The following CAS should always fail
result = __TBB_MaskedCompareAndSwap<T>(my_ptr,new_value,old_value-1);
ASSERT(result!=old_value-1, "masked CAS succeeded while it should fail");
ASSERT(result==*my_ptr, "masked CAS result mismatch with real value");
// The following one should succeed
result = __TBB_MaskedCompareAndSwap<T>(my_ptr,new_value,old_value);
ASSERT(result==old_value && *my_ptr==new_value, "masked CAS failed while it should succeed");
// The following one should fail again
result = __TBB_MaskedCompareAndSwap<T>(my_ptr,new_value,old_value);
ASSERT(result!=old_value, "masked CAS succeeded while it should fail");
ASSERT(result==*my_ptr, "masked CAS result mismatch with real value");
// Test contended case
for( int j=0; j<testSpaceSize; ++j ){
// try adding my_prime until success
T value;
do {
value = test_space_contended[j];
result = __TBB_MaskedCompareAndSwap<T>(test_space_contended+j,value+my_prime,value);
} while( result!=value );
}
}
}
};
template<typename T>
struct intptr_as_array_of
{
static const int how_many_Ts = sizeof(intptr_t)/sizeof(T);
union {
intptr_t result;
T space[ how_many_Ts ];
};
};
template<typename T>
intptr_t getCorrectUncontendedValue(int slot_idx) {
intptr_as_array_of<T> slot;
slot.result = 0;
for( int i=0; i<slot.how_many_Ts; ++i ) {
const T my_prime = T(prime[slot_idx*slot.how_many_Ts + i]);
for( int j=0; j<numMaskedOperations; ++j )
slot.space[i] += my_prime;
}
return slot.result;
}
template<typename T>
intptr_t getCorrectContendedValue() {
intptr_as_array_of<T> slot;
slot.result = 0;
for( int i=0; i<slot.how_many_Ts; ++i )
for( int primes=0; primes<testSpaceSize; ++primes )
for( int j=0; j<numMaskedOperations; ++j )
slot.space[i] += prime[primes];
return slot.result;
}
} // namespace masked_cas_helpers
template<typename T>
void TestMaskedCAS() {
using namespace masked_cas_helpers;
REMARK("testing masked CAS<%d>\n",int(sizeof(T)));
const int num_slots = sizeof(T)*testSpaceSize/sizeof(intptr_t);
intptr_t arr1[num_slots+2]; // two more "canary" slots at boundaries
intptr_t arr2[num_slots+2];
for(int i=0; i<num_slots+2; ++i)
arr2[i] = arr1[i] = 0;
T* test_space_uncontended = (T*)(arr1+1);
T* test_space_contended = (T*)(arr2+1);
NativeParallelFor( testSpaceSize, TestMaskedCAS_Body<T>(test_space_uncontended, test_space_contended) );
ASSERT( arr1[0]==0 && arr1[num_slots+1]==0 && arr2[0]==0 && arr2[num_slots+1]==0 , "adjacent memory was overwritten" );
const intptr_t correctContendedValue = getCorrectContendedValue<T>();
for(int i=0; i<num_slots; ++i) {
ASSERT( arr1[i+1]==getCorrectUncontendedValue<T>(i), "unexpected value in an uncontended slot" );
ASSERT( arr2[i+1]==correctContendedValue, "unexpected value in a contended slot" );
}
}
#endif
template <typename T>
class TestRelaxedLoadStorePlainBody {
static T s_turn,
s_ready;
public:
static unsigned s_count1,
s_count2;
void operator() ( int id ) const {
using tbb::internal::__TBB_load_relaxed;
using tbb::internal::__TBB_store_relaxed;
if ( id == 0 ) {
while ( !__TBB_load_relaxed(s_turn) ) {
++s_count1;
__TBB_store_relaxed(s_ready, 1);
}
}
else {
while ( !__TBB_load_relaxed(s_ready) ) {
++s_count2;
continue;
}
__TBB_store_relaxed(s_turn, 1);
}
}
}; // class TestRelaxedLoadStorePlainBody<T>
template <typename T> T TestRelaxedLoadStorePlainBody<T>::s_turn = 0;
template <typename T> T TestRelaxedLoadStorePlainBody<T>::s_ready = 0;
template <typename T> unsigned TestRelaxedLoadStorePlainBody<T>::s_count1 = 0;
template <typename T> unsigned TestRelaxedLoadStorePlainBody<T>::s_count2 = 0;
template <typename T>
class TestRelaxedLoadStoreAtomicBody {
static tbb::atomic<T> s_turn,
s_ready;
public:
static unsigned s_count1,
s_count2;
void operator() ( int id ) const {
if ( id == 0 ) {
while ( s_turn.template load<tbb::relaxed>() == 0 ) {
++s_count1;
s_ready.template store<tbb::relaxed>(1);
}
}
else {
while ( s_ready.template load<tbb::relaxed>() == 0 ) {
++s_count2;
continue;
}
s_turn.template store<tbb::relaxed>(1);
}
}
}; // class TestRelaxedLoadStoreAtomicBody<T>
template <typename T> tbb::atomic<T> TestRelaxedLoadStoreAtomicBody<T>::s_turn;
template <typename T> tbb::atomic<T> TestRelaxedLoadStoreAtomicBody<T>::s_ready;
template <typename T> unsigned TestRelaxedLoadStoreAtomicBody<T>::s_count1 = 0;
template <typename T> unsigned TestRelaxedLoadStoreAtomicBody<T>::s_count2 = 0;
template <typename T>
void TestRegisterPromotionSuppression () {
REMARK("testing register promotion suppression (size=%d)\n", (int)sizeof(T));
NativeParallelFor( 2, TestRelaxedLoadStorePlainBody<T>() );
NativeParallelFor( 2, TestRelaxedLoadStoreAtomicBody<T>() );
}
template<unsigned N>
class ArrayElement {
char item[N];
};
#include "harness_barrier.h"
namespace bit_operation_test_suite{
struct fixture : NoAssign{
static const uintptr_t zero = 0;
const uintptr_t random_value ;
const uintptr_t inverted_random_value ;
fixture():
random_value (tbb::internal::select_size_t_constant<0x9E3779B9,0x9E3779B97F4A7C15ULL>::value),
inverted_random_value ( ~random_value)
{}
};
struct TestAtomicORSerially : fixture {
void operator()(){
//these additional variable are needed to get more meaningful expression in the assert
uintptr_t initial_value = zero;
uintptr_t atomic_or_result = initial_value;
uintptr_t atomic_or_operand = random_value;
__TBB_AtomicOR(&atomic_or_result,atomic_or_operand);
ASSERT(atomic_or_result == (initial_value | atomic_or_operand),"AtomicOR should do the OR operation");
}
};
struct TestAtomicANDSerially : fixture {
void operator()(){
//these additional variable are needed to get more meaningful expression in the assert
uintptr_t initial_value = inverted_random_value;
uintptr_t atomic_and_result = initial_value;
uintptr_t atomic_and_operand = random_value;
__TBB_AtomicAND(&atomic_and_result,atomic_and_operand);
ASSERT(atomic_and_result == (initial_value & atomic_and_operand),"AtomicAND should do the AND operation");
}
};
struct TestAtomicORandANDConcurrently : fixture {
static const uintptr_t bit_per_word = sizeof(uintptr_t) * 8;
static const uintptr_t threads_number = bit_per_word;
Harness::SpinBarrier m_barrier;
uintptr_t bitmap;
TestAtomicORandANDConcurrently():bitmap(zero) {}
struct thread_body{
TestAtomicORandANDConcurrently* test;
thread_body(TestAtomicORandANDConcurrently* the_test) : test(the_test) {}
void operator()(int thread_index)const{
const uintptr_t single_bit_mask = ((uintptr_t)1u) << (thread_index % bit_per_word);
test->m_barrier.wait();
static const char* error_msg = "AtomicOR and AtomicAND should be atomic";
for (uintptr_t attempts=0; attempts<1000; attempts++ ){
//Set and clear designated bits in a word.
__TBB_AtomicOR(&test->bitmap,single_bit_mask);
__TBB_Yield();
bool the_bit_is_set_after_set_via_atomic_or = ((__TBB_load_with_acquire(test->bitmap) & single_bit_mask )== single_bit_mask);
ASSERT(the_bit_is_set_after_set_via_atomic_or,error_msg);
__TBB_AtomicAND(&test->bitmap,~single_bit_mask);
__TBB_Yield();
bool the_bit_is_clear_after_clear_via_atomic_and = ((__TBB_load_with_acquire(test->bitmap) & single_bit_mask )== zero);
ASSERT(the_bit_is_clear_after_clear_via_atomic_and,error_msg);
}
}
};
void operator()(){
m_barrier.initialize(threads_number);
NativeParallelFor(threads_number,thread_body(this));
}
};
}
void TestBitOperations(){
using namespace bit_operation_test_suite;
TestAtomicORSerially()();
TestAtomicANDSerially()();
TestAtomicORandANDConcurrently()();
}
int TestMain () {
# if __TBB_ATOMIC_CTORS
TestConstExprInitializationOfGlobalObjects();
# endif //__TBB_ATOMIC_CTORS
# if __TBB_64BIT_ATOMICS
TestAtomicInteger<unsigned long long>("unsigned long long");
TestAtomicInteger<long long>("long long");
# else
REPORT("64-bit atomics not supported\n");
ASSERT(sizeof(long long)==8, "type long long is not 64 bits");
# endif
TestAtomicInteger<unsigned long>("unsigned long");
TestAtomicInteger<long>("long");
TestAtomicInteger<unsigned int>("unsigned int");
TestAtomicInteger<int>("int");
TestAtomicInteger<unsigned short>("unsigned short");
TestAtomicInteger<short>("short");
TestAtomicInteger<signed char>("signed char");
TestAtomicInteger<unsigned char>("unsigned char");
TestAtomicInteger<char>("char");
TestAtomicInteger<wchar_t>("wchar_t");
TestAtomicInteger<size_t>("size_t");
TestAtomicInteger<ptrdiff_t>("ptrdiff_t");
TestAtomicPointer<ArrayElement<1> >();
TestAtomicPointer<ArrayElement<2> >();
TestAtomicPointer<ArrayElement<3> >();
TestAtomicPointer<ArrayElement<4> >();
TestAtomicPointer<ArrayElement<5> >();
TestAtomicPointer<ArrayElement<6> >();
TestAtomicPointer<ArrayElement<7> >();
TestAtomicPointer<ArrayElement<8> >();
TestAtomicPointerToTypeOfUnknownSize<IncompleteType*>( "IncompleteType*" );
TestAtomicPointerToTypeOfUnknownSize<void*>( "void*" );
TestAtomicBool();
TestAtomicEnum();
TestAtomicFloat<float>("float");
# if __TBB_64BIT_ATOMICS
TestAtomicFloat<double>("double");
# else
ASSERT(sizeof(double)==8, "type double is not 64 bits");
# endif
ASSERT( !ParallelError, NULL );
# if __TBB_BIG_ENDIAN!=-1
TestMaskedCAS<unsigned char>();
TestMaskedCAS<unsigned short>();
# else
REPORT("Generic part-word CAS is not available\n");
# endif
# if __TBB_64BIT_ATOMICS
TestRegisterPromotionSuppression<tbb::internal::int64_t>();
# endif
TestRegisterPromotionSuppression<tbb::internal::int32_t>();
TestRegisterPromotionSuppression<tbb::internal::int16_t>();
TestRegisterPromotionSuppression<tbb::internal::int8_t>();
TestBitOperations();
return Harness::Done;
}
template<typename T, bool aligned>
class AlignedAtomic: NoAssign {
//tbb::aligned_space can not be used here, because internally it utilize align pragma/attribute,
//which has bugs on 8byte alignment on ia32 on some compilers( see according ****_BROKEN macro)
// Allocate space big enough to always contain sizeof(T)-byte locations that are aligned and misaligned.
char raw_space[2*sizeof(T) -1];
public:
tbb::atomic<T>& construct_atomic(){
std::memset(&raw_space[0],0, sizeof(raw_space));
uintptr_t delta = aligned ? 0 : sizeof(T)/2;
size_t index=sizeof(T)-1;
tbb::atomic<T>& y = *reinterpret_cast<tbb::atomic<T>*>((reinterpret_cast<uintptr_t>(&raw_space[index+delta])&~index) - delta);
// Assertion checks that y really did end up somewhere inside "raw_space".
ASSERT( raw_space<=reinterpret_cast<char*>(&y), "y starts before raw_space" );
ASSERT( reinterpret_cast<char*>(&y+1) <= raw_space+sizeof(raw_space), "y starts after raw_space" );
ASSERT( !(aligned ^ tbb::internal::is_aligned(&y,sizeof(T))), "y is not aligned as it required" );
new (&y) tbb::atomic<T> ();
return y;
}
};
template<typename T, bool aligned>
struct FlagAndMessage: AlignedAtomic<T,aligned> {
//! 0 if message not set yet, 1 if message is set.
tbb::atomic<T>& flag;
/** Force flag and message to be on distinct cache lines for machines with cache line size <= 4096 bytes */
char pad[4096/sizeof(T)];
//! Non-zero if message is ready
T message;
FlagAndMessage(): flag(FlagAndMessage::construct_atomic()) {
std::memset(pad,0,sizeof(pad));
}
};
// A special template function used for summation.
// Actually it is only necessary because of its specialization for void*
template<typename T>
T special_sum(intptr_t arg1, intptr_t arg2) {
return (T)((T)arg1 + arg2);
}
// The specialization for IncompleteType* is required
// because pointer arithmetic (+) is impossible with IncompleteType*
template<>
IncompleteType* special_sum<IncompleteType*>(intptr_t arg1, intptr_t arg2) {
return (IncompleteType*)(arg1 + arg2);
}
// The specialization for void* is required
// because pointer arithmetic (+) is impossible with void*
template<>
void* special_sum<void*>(intptr_t arg1, intptr_t arg2) {
return (void*)(arg1 + arg2);
}
// The specialization for bool is required to shut up gratuitous compiler warnings,
// because some compilers warn about casting int to bool.
template<>
bool special_sum<bool>(intptr_t arg1, intptr_t arg2) {
return ((arg1!=0) + arg2)!=0;
}
volatile int One = 1;
inline bool IsRelaxed ( LoadStoreExpression e ) {
return e == UseExplicitRelaxed || e == UseGlobalHelperRelaxed;
}
template <typename T, LoadStoreExpression E>
struct LoadStoreTraits;
template <typename T>
struct LoadStoreTraits<T, UseOperators> {
static void load ( T& dst, const tbb::atomic<T>& src ) { dst = src; }
static void store ( tbb::atomic<T>& dst, const T& src ) { dst = src; }
};
template <typename T>
struct LoadStoreTraits<T, UseImplicitAcqRel> {
static void load ( T& dst, const tbb::atomic<T>& src ) { dst = src.load(); }
static void store ( tbb::atomic<T>& dst, const T& src ) { dst.store(src); }
};
template <typename T>
struct LoadStoreTraits<T, UseExplicitFullyFenced> {
static void load ( T& dst, const tbb::atomic<T>& src ) { dst = src.template load<tbb::full_fence>(); }
static void store ( tbb::atomic<T>& dst, const T& src ) { dst.template store<tbb::full_fence>(src); }
};
template <typename T>
struct LoadStoreTraits<T, UseExplicitAcqRel> {
static void load ( T& dst, const tbb::atomic<T>& src ) { dst = src.template load<tbb::acquire>(); }
static void store ( tbb::atomic<T>& dst, const T& src ) { dst.template store<tbb::release>(src); }
};
template <typename T>
struct LoadStoreTraits<T, UseExplicitRelaxed> {
static void load ( T& dst, const tbb::atomic<T>& src ) { dst = src.template load<tbb::relaxed>(); }
static void store ( tbb::atomic<T>& dst, const T& src ) { dst.template store<tbb::relaxed>(src); }
};
template <typename T>
struct LoadStoreTraits<T, UseGlobalHelperFullyFenced> {
static void load ( T& dst, const tbb::atomic<T>& src ) { dst = tbb::load<tbb::full_fence>(src); }
static void store ( tbb::atomic<T>& dst, const T& src ) { tbb::store<tbb::full_fence>(dst, src); }
};
template <typename T>
struct LoadStoreTraits<T, UseGlobalHelperAcqRel> {
static void load ( T& dst, const tbb::atomic<T>& src ) { dst = tbb::load<tbb::acquire>(src); }
static void store ( tbb::atomic<T>& dst, const T& src ) { tbb::store<tbb::release>(dst, src); }
};
template <typename T>
struct LoadStoreTraits<T, UseGlobalHelperRelaxed> {
static void load ( T& dst, const tbb::atomic<T>& src ) { dst = tbb::load<tbb::relaxed>(src); }
static void store ( tbb::atomic<T>& dst, const T& src ) { tbb::store<tbb::relaxed>(dst, src); }
};
template<typename T, bool aligned, LoadStoreExpression E>
struct HammerLoadAndStoreFence: NoAssign {
typedef FlagAndMessage<T,aligned> fam_type;
private:
typedef LoadStoreTraits<T, E> trait;
fam_type* fam;
const int n;
const int p;
const int trial;
const char* name;
mutable T accum;
public:
HammerLoadAndStoreFence( fam_type* fam_, int n_, int p_, const char* name_, int trial_ ) : fam(fam_), n(n_), p(p_), trial(trial_), name(name_) {}
void operator()( int k ) const {
int one = One;
fam_type* s = fam+k;
fam_type* s_next = fam + (k+1)%p;
for( int i=0; i<n; ++i ) {
// The inner for loop is a spin-wait loop, which is normally considered very bad style.
// But we must use it here because we are interested in examining subtle hardware effects.
for(unsigned short cnt=1; ; ++cnt) {
if( !(cnt%1024) ) // to help 1-core or oversubscribed systems complete the test, yield every 2^10 iterations
__TBB_Yield();
// Compilers typically generate non-trivial sequence for division by a constant.
// The expression here is dependent on the loop index i, so it cannot be hoisted.
#define COMPLICATED_ZERO (i*(one-1)/100)
// Read flag and then the message
T flag, message;
if( trial&1 ) {
// COMPLICATED_ZERO here tempts compiler to hoist load of message above reading of flag.
trait::load( flag, (s+COMPLICATED_ZERO)->flag );
message = s->message;
} else {
trait::load( flag, s->flag );
message = s->message;
}
if( flag ) {
if( flag!=(T)-1 ) {
REPORT("ERROR: flag!=(T)-1 k=%d i=%d trial=%x type=%s (atomicity problem?)\n", k, i, trial, name );
ParallelError = true;
}
if( !IsRelaxed(E) && message!=(T)-1 ) {
REPORT("ERROR: message!=(T)-1 k=%d i=%d trial=%x type=%s mode=%d (memory fence problem?)\n", k, i, trial, name, E );
ParallelError = true;
}
s->message = T(0);
trait::store( s->flag, T(0) );
// Prevent deadlock possible in relaxed mode because of store(0)
// to the first thread's flag being reordered after the last
// thread's store(-1) into it.
if ( IsRelaxed(E) ) {
while( s_next->flag.template load<tbb::relaxed>() != 0 )
__TBB_Yield();
}
else
ASSERT( s_next->flag == 0, NULL );
// Set message and then the flag
if( trial&2 ) {
// COMPLICATED_ZERO here tempts compiler to sink store below setting of flag
s_next->message = special_sum<T>(-1, COMPLICATED_ZERO);
trait::store( s_next->flag, (T)-1 );
} else {
s_next->message = (T)-1;
trait::store( s_next->flag, (T)-1 );
}
break;
} else {
// Force compiler to use message anyway, so it cannot sink read of s->message below the if.
accum = message;
}
}
}
}
};
//! Test that atomic<T> has acquire semantics for loads and release semantics for stores.
/** Test performs round-robin passing of message among p processors,
where p goes from MinThread to MaxThread. */
template<typename T, bool aligned, LoadStoreExpression E>
void TestLoadAndStoreFences( const char* name ) {
typedef HammerLoadAndStoreFence<T, aligned, E> hammer_load_store_type;
typedef typename hammer_load_store_type::fam_type fam_type;
for( int p=MinThread<2 ? 2 : MinThread; p<=MaxThread; ++p ) {
fam_type * fam = new fam_type[p];
// Each of four trials exercise slightly different expression pattern within the test.
// See occurrences of COMPLICATED_ZERO for details.
for( int trial=0; trial<4; ++trial ) {
fam->message = (T)-1;
fam->flag = (T)-1;
NativeParallelFor( p, hammer_load_store_type( fam, 100, p, name, trial ) );
if ( !IsRelaxed(E) ) {
for( int k=0; k<p; ++k ) {
ASSERT( fam[k].message==(k==0 ? (T)-1 : 0), "incomplete round-robin?" );
ASSERT( fam[k].flag==(k==0 ? (T)-1 : 0), "incomplete round-robin?" );
}
}
}
delete[] fam;
}
}
//! Sparse set of values of integral type T.
/** Set is designed so that if a value is read or written non-atomically,
the resulting intermediate value is likely to not be a member of the set. */
template<typename T>
class SparseValueSet {
T factor;
public:
SparseValueSet() {
// Compute factor such that:
// 1. It has at least one 1 in most of its bytes.
// 2. The bytes are typically different.
// 3. When multiplied by any value <=127, the product does not overflow.
factor = T(0);
for( unsigned i=0; i<sizeof(T)*8-7; i+=7 )
factor = T(factor | T(1)<<i);
}
//! Get ith member of set
T get( int i ) const {
// Create multiple of factor. The & prevents overflow of the product.
return T((i&0x7F)*factor);
}
//! True if set contains x
bool contains( T x ) const {
// True if
return (x%factor)==0;
}
};
//! Specialization for pointer types. The pointers are random and should not be dereferenced.
template<typename T>
class SparseValueSet<T*> {
SparseValueSet<ptrdiff_t> my_set;
public:
T* get( int i ) const {return reinterpret_cast<T*>(my_set.get(i));}
bool contains( T* x ) const {return my_set.contains(reinterpret_cast<ptrdiff_t>(x));}
};
//! Specialization for bool.
/** Checking bool for atomic read/write is pointless in practice, because
there is no way to *not* atomically read or write a bool value. */
template<>
class SparseValueSet<bool> {
public:
bool get( int i ) const {return i&1;}
bool contains( bool ) const {return true;}
};
#if _MSC_VER==1500 && !defined(__INTEL_COMPILER)
// VS2008/VC9 seems to have an issue; limits pull in math.h
#pragma warning( push )
#pragma warning( disable: 4985 )
#endif
#include <limits> /* Need std::numeric_limits */
#if _MSC_VER==1500 && !defined(__INTEL_COMPILER)
#pragma warning( pop )
#endif
//! Commonality inherited by specializations for floating-point types.
template<typename T>
class SparseFloatSet: NoAssign {
const T epsilon;
public:
SparseFloatSet() : epsilon(std::numeric_limits<T>::epsilon()) {}
T get( int i ) const {
return i==0 ? T(0) : 1/T((i&0x7F)+1);
}
bool contains( T x ) const {
if( x==T(0) ) {
return true;
} else {
int j = int(1/x+T(0.5));
if( 0<j && j<=128 ) {
T error = x*T(j)-T(1);
// In the calculation above, if x was indeed generated by method get, the error should be
// at most epsilon, because x is off by at most 1/2 ulp from its infinitely precise value,
// j is exact, and the multiplication incurs at most another 1/2 ulp of round-off error.
if( -epsilon<=error && error<=epsilon ) {
return true;
} else {
REPORT("Warning: excessive floating-point error encountered j=%d x=%.15g error=%.15g\n",j,x,error);
}
}
return false;
}
};
};
template<>
class SparseValueSet<float>: public SparseFloatSet<float> {};
template<>
class SparseValueSet<double>: public SparseFloatSet<double> {};
template<typename T, bool aligned>
class HammerAssignment: AlignedAtomic<T,aligned> {
tbb::atomic<T>& x;
const char* name;
SparseValueSet<T> set;
public:
HammerAssignment(const char* name_ ) : x(HammerAssignment::construct_atomic()), name(name_) {}
void operator()( int k ) const {
const int n = 1000000;
if( k ) {
tbb::atomic<T> z;
AssertSameType( z=x, z ); // Check that return type from assignment is correct
for( int i=0; i<n; ++i ) {
// Read x atomically into z.
z = x;
if( !set.contains(z) ) {
REPORT("ERROR: assignment of atomic<%s> is not atomic\n", name);
ParallelError = true;
return;
}
}
} else {
tbb::atomic<T> y;
for( int i=0; i<n; ++i ) {
// Get pseudo-random value.
y = set.get(i);
// Write y atomically into x.
x = y;
}
}
}
};
// Compile-time check that a class method has the required signature.
// Intended to check the assignment operator of tbb::atomic.
template<typename T> void TestAssignmentSignature( T& (T::*)(const T&) ) {}
#if _MSC_VER && !defined(__INTEL_COMPILER)
#pragma warning( disable: 4355 4800 )
#endif
template<typename T, bool aligned>
void TestAssignment( const char* name ) {
TestAssignmentSignature( &tbb::atomic<T>::operator= );
NativeParallelFor( 2, HammerAssignment<T,aligned>(name ) );
}
static const unsigned Primes[] = {
0x9e3779b1, 0xffe6cc59, 0x2109f6dd, 0x43977ab5, 0xba5703f5, 0xb495a877, 0xe1626741, 0x79695e6b,
0xbc98c09f, 0xd5bee2b3, 0x287488f9, 0x3af18231, 0x9677cd4d, 0xbe3a6929, 0xadc6a877, 0xdcf0674b,
0xbe4d6fe9, 0x5f15e201, 0x99afc3fd, 0xf3f16801, 0xe222cfff, 0x24ba5fdb, 0x0620452d, 0x79f149e3,
0xc8b93f49, 0x972702cd, 0xb07dd827, 0x6c97d5ed, 0x085a3d61, 0x46eb5ea7, 0x3d9910ed, 0x2e687b5b,
0x29609227, 0x6eb081f1, 0x0954c4e1, 0x9d114db9, 0x542acfa9, 0xb3e6bd7b, 0x0742d917, 0xe9f3ffa7,
0x54581edb, 0xf2480f45, 0x0bb9288f, 0xef1affc7, 0x85fa0ca7, 0x3ccc14db, 0xe6baf34b, 0x343377f7,
0x5ca19031, 0xe6d9293b, 0xf0a9f391, 0x5d2e980b, 0xfc411073, 0xc3749363, 0xb892d829, 0x3549366b,
0x629750ad, 0xb98294e5, 0x892d9483, 0xc235baf3, 0x3d2402a3, 0x6bdef3c9, 0xbec333cd, 0x40c9520f
};
class FastRandom {
unsigned x, a;
public:
unsigned short get() {
unsigned short r = (unsigned short)(x>>16);
x = x*a+1;
return r;
}
FastRandom( unsigned seed ) {
x = seed;
a = Primes[seed % (sizeof(Primes)/sizeof(Primes[0]))];
}
};
template <typename T, bool aligned, LoadStoreExpression E>
class DekkerArbitrationBody : NoAssign, Harness::NoAfterlife {
typedef LoadStoreTraits<T, E> trait;
mutable FastRandom my_rand;
static const unsigned short c_rand_ceil = 10;
mutable AlignedAtomic<T,aligned> s_ready_storage[2];
mutable AlignedAtomic<T,aligned> s_turn_storage;
mutable tbb::atomic<T>* s_ready[2];
tbb::atomic<T>& s_turn;
mutable volatile bool s_inside;
public:
void operator() ( int id ) const {
const int me = id;
const T other = (T)(uintptr_t)(1 - id),
cleared = T(0),
signaled = T(1);
for ( int i = 0; i < 100000; ++i ) {
trait::store( *s_ready[me], signaled );
trait::store( s_turn, other );
T r, t;
for ( int j = 0; ; ++j ) {
trait::load(r, *s_ready[(uintptr_t)other]);
trait::load(t, s_turn);
if ( r != signaled || t != other )
break;
__TBB_Pause(1);
if ( j == 2<<12 ) {
j = 0;
__TBB_Yield();
}
}
// Entered critical section
ASSERT( !s_inside, "Peterson lock is broken - some fences are missing" );
s_inside = true;
unsigned short spin = my_rand.get() % c_rand_ceil;
for ( volatile int j = 0; j < spin; ++j )
continue;
s_inside = false;
ASSERT( !s_inside, "Peterson lock is broken - some fences are missing" );
// leaving critical section
trait::store( *s_ready[me], cleared );
spin = my_rand.get() % c_rand_ceil;
for ( volatile int j = 0; j < spin; ++j )
continue;
}
}
DekkerArbitrationBody ()
: my_rand((unsigned)(uintptr_t)this)
, s_turn(s_turn_storage.construct_atomic())
, s_inside (false)
{
//atomics pointed to by s_ready and s_turn will be zeroed by the
//according construct_atomic() calls
s_ready[0] = &s_ready_storage[0].construct_atomic();
s_ready[1] = &s_ready_storage[1].construct_atomic();
}
};
template <typename T, bool aligned, LoadStoreExpression E>
void TestDekkerArbitration () {
NativeParallelFor( 2, DekkerArbitrationBody<T,aligned, E>() );
}
template<typename T>
void TestParallel( const char* name ) {
//TODO: looks like there are no tests for operations other than load/store ?
#if __TBB_FORCE_64BIT_ALIGNMENT_BROKEN
if (sizeof(T)==8){
TestLoadAndStoreFences<T, false, UseOperators>(name);
TestLoadAndStoreFences<T, false, UseImplicitAcqRel>(name);
TestLoadAndStoreFences<T, false, UseExplicitFullyFenced>(name);
TestLoadAndStoreFences<T, false, UseExplicitAcqRel>(name);
TestLoadAndStoreFences<T, false, UseExplicitRelaxed>(name);
TestLoadAndStoreFences<T, false, UseGlobalHelperFullyFenced>(name);
TestLoadAndStoreFences<T, false, UseGlobalHelperAcqRel>(name);
TestLoadAndStoreFences<T, false, UseGlobalHelperRelaxed>(name);
TestAssignment<T,false>(name);
TestDekkerArbitration<T, false, UseExplicitFullyFenced>();
TestDekkerArbitration<T, false, UseGlobalHelperFullyFenced>();
}
#endif
TestLoadAndStoreFences<T, true, UseOperators>(name);
TestLoadAndStoreFences<T, true, UseImplicitAcqRel>(name);
TestLoadAndStoreFences<T, true, UseExplicitFullyFenced>(name);
TestLoadAndStoreFences<T, true, UseExplicitAcqRel>(name);
TestLoadAndStoreFences<T, true, UseExplicitRelaxed>(name);
TestLoadAndStoreFences<T, true, UseGlobalHelperFullyFenced>(name);
TestLoadAndStoreFences<T, true, UseGlobalHelperAcqRel>(name);
TestLoadAndStoreFences<T, true, UseGlobalHelperRelaxed>(name);
TestAssignment<T,true>(name);
TestDekkerArbitration<T, true, UseExplicitFullyFenced>();
TestDekkerArbitration<T, true, UseGlobalHelperFullyFenced>();
}
#endif // __TBB_TEST_SKIP_PIC_MODE || __TBB_TEST_SKIP_BUILTINS_MODE