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.
 
 
 
 

246 lines
9.1 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.
*/
/** This test checks the automatic propagation of master thread FPU settings
into the worker threads. **/
#define TBB_PREVIEW_LOCAL_OBSERVER 1
#include "harness.h"
#include "tbb/parallel_for.h"
#include "tbb/task_scheduler_init.h"
#include "tbb/tbb_machine.h"
#if __TBB_SCHEDULER_OBSERVER
#include "tbb/task_scheduler_observer.h"
#endif
const int N = 500000;
#if ( __TBB_x86_32 || __TBB_x86_64 ) && __TBB_CPU_CTL_ENV_PRESENT
const int FE_TONEAREST = 0x0000,
FE_DOWNWARD = 0x0400,
FE_UPWARD = 0x0800,
FE_TOWARDZERO = 0x0c00,
FE_RND_MODE_MASK = FE_TOWARDZERO,
SSE_RND_MODE_MASK = FE_RND_MODE_MASK << 3,
SSE_DAZ = 0x0040,
SSE_FTZ = 0x8000,
SSE_MODE_MASK = SSE_DAZ | SSE_FTZ;
const int NumSseModes = 4;
const int SseModes[NumSseModes] = { 0, SSE_DAZ, SSE_FTZ, SSE_DAZ | SSE_FTZ };
#if _WIN64 && !__TBB_X86_MSVC_INLINE_ASM_AVAILABLE && !__MINGW64__
// MinGW uses inline implementation from tbb/machine/linux_intel64.h
#include <float.h>
inline void __TBB_get_cpu_ctl_env ( __TBB_cpu_ctl_env_t* fe ) {
fe->x87cw = short(_control87(0, 0) & _MCW_RC) << 2;
fe->mxcsr = _mm_getcsr();
}
inline void __TBB_set_cpu_ctl_env ( const __TBB_cpu_ctl_env_t* fe ) {
ASSERT( (fe->x87cw & FE_RND_MODE_MASK) == ((fe->x87cw & FE_RND_MODE_MASK) >> 2 & _MCW_RC) << 2, "Check float.h constants" );
_control87( (fe->x87cw & FE_RND_MODE_MASK) >> 6, _MCW_RC );
_mm_setcsr( fe->mxcsr );
}
#endif /* _WIN64 && !__TBB_X86_MSVC_INLINE_ASM_AVAILABLE && !__MINGW64__ */
inline int GetRoundingMode ( bool checkConsistency = true ) {
__TBB_cpu_ctl_env_t ctl = { 0, 0 };
__TBB_get_cpu_ctl_env(&ctl);
ASSERT( !checkConsistency || (ctl.mxcsr & SSE_RND_MODE_MASK) >> 3 == (ctl.x87cw & FE_RND_MODE_MASK), NULL );
return ctl.x87cw & FE_RND_MODE_MASK;
}
inline void SetRoundingMode ( int mode ) {
__TBB_cpu_ctl_env_t ctl = { 0, 0 };
__TBB_get_cpu_ctl_env(&ctl);
ctl.mxcsr = (ctl.mxcsr & ~SSE_RND_MODE_MASK) | (mode & FE_RND_MODE_MASK) << 3;
ctl.x87cw = short((ctl.x87cw & ~FE_RND_MODE_MASK) | (mode & FE_RND_MODE_MASK));
__TBB_set_cpu_ctl_env(&ctl);
}
inline int GetSseMode () {
__TBB_cpu_ctl_env_t ctl = { 0, 0 };
__TBB_get_cpu_ctl_env(&ctl);
return ctl.mxcsr & SSE_MODE_MASK;
}
inline void SetSseMode ( int mode ) {
__TBB_cpu_ctl_env_t ctl = { 0, 0 };
__TBB_get_cpu_ctl_env(&ctl);
ctl.mxcsr = (ctl.mxcsr & ~SSE_MODE_MASK) | (mode & SSE_MODE_MASK);
__TBB_set_cpu_ctl_env(&ctl);
}
#else /* Other archs */
#include <fenv.h>
const int RND_MODE_MASK = FE_TONEAREST | FE_DOWNWARD | FE_UPWARD | FE_TOWARDZERO;
const int NumSseModes = 1;
const int SseModes[NumSseModes] = { 0 };
inline int GetRoundingMode ( bool = true ) { return fegetround(); }
inline void SetRoundingMode ( int rnd ) { fesetround(rnd); }
inline int GetSseMode () { return 0; }
inline void SetSseMode ( int ) {}
#endif /* Other archs */
const int NumRoundingModes = 4;
const int RoundingModes[NumRoundingModes] = { FE_TONEAREST, FE_DOWNWARD, FE_UPWARD, FE_TOWARDZERO };
class RoundingModeCheckBody {
Harness::tid_t m_tidMaster;
int m_masterMode;
int m_workerMode;
int m_masterSseMode;
int m_workerSseMode;
public:
void operator() ( int /*iter*/ ) const {
if ( Harness::CurrentTid() == m_tidMaster ) {
ASSERT( GetRoundingMode() == m_masterMode, "Master's FPU control state was corrupted" );
ASSERT( GetSseMode() == m_masterSseMode, "Master's SSE control state was corrupted" );
}
else {
ASSERT( GetRoundingMode() == m_workerMode, "FPU control state has not been propagated to a worker" );
ASSERT( GetSseMode() == m_workerSseMode, "SSE control state has not been propagated to a worker" );
}
}
RoundingModeCheckBody ( Harness::tid_t tidMaster, int masterMode, int workerMode, int masterSseMode, int workerSseMode )
: m_tidMaster(tidMaster)
, m_masterMode(masterMode)
, m_workerMode(workerMode)
, m_masterSseMode(masterSseMode)
, m_workerSseMode(workerSseMode)
{}
};
#if __TBB_SCHEDULER_OBSERVER
class LocalObserver : public tbb::task_scheduler_observer {
int m_sse, m_rounding;
/*override*/
void on_scheduler_entry( bool is_worker ) {
if(is_worker) {
SetSseMode( m_sse );
SetRoundingMode( m_rounding );
}
}
public:
LocalObserver(int fpu, int sse) : tbb::task_scheduler_observer(true), m_sse(sse), m_rounding(fpu) {
observe(true);
}
};
#endif
class LauncherBody {
public:
void operator() ( int id ) const {
Harness::tid_t tid = Harness::CurrentTid();
// TBB scheduler instance in a master thread captures the FPU control state
// at the moment of its initialization and passes it to the workers toiling
// on its behalf.
for( int k = 0; k < NumSseModes; ++k ) {
int sse_mode = SseModes[(k + id) % NumSseModes];
SetSseMode( sse_mode );
for( int i = 0; i < NumRoundingModes; ++i ) {
int mode = RoundingModes[(i + id) % NumRoundingModes];
SetRoundingMode( mode );
// New mode must be set before TBB scheduler is initialized
tbb::task_scheduler_init init;
tbb::parallel_for( 0, N, 1, RoundingModeCheckBody(tid, mode, mode, sse_mode, sse_mode) );
ASSERT( GetRoundingMode() == mode, NULL );
}
}
// Since the following loop uses auto-initialization, the scheduler instance
// implicitly created by the first parallel_for invocation will persist
// until the thread ends, and thus workers will use the mode set by the
// first iteration.
int captured_mode = RoundingModes[id % NumRoundingModes];
int captured_sse_mode = SseModes[id % NumSseModes];
for( int k = 0; k < NumSseModes; ++k ) {
int sse_mode = SseModes[(k + id) % NumSseModes];
SetSseMode( sse_mode );
for( int i = 0; i < NumRoundingModes; ++i ) {
int mode = RoundingModes[(i + id) % NumRoundingModes];
SetRoundingMode( mode );
tbb::parallel_for( 0, N, 1, RoundingModeCheckBody(tid, mode, captured_mode, sse_mode, captured_sse_mode) );
ASSERT( GetRoundingMode() == mode, NULL );
}
}
#if __TBB_SCHEDULER_OBSERVER
// but using observers, it is possible to redefine the mode again
for( int k = 0; k < NumSseModes; ++k ) {
int sse_mode = SseModes[(k + id) % NumSseModes];
SetSseMode( sse_mode );
for( int i = 0; i < NumRoundingModes; ++i ) {
int mode = RoundingModes[(i + id) % NumRoundingModes];
SetRoundingMode( mode );
// New mode must be set before TBB scheduler is initialized
LocalObserver restorer(mode, sse_mode);
tbb::parallel_for( 0, N, 1, RoundingModeCheckBody(tid, mode, mode, sse_mode, sse_mode) );
ASSERT( GetRoundingMode() == mode, NULL );
}
}
#endif
}
};
void TestFpuEnvPropagation () {
NativeParallelFor ( tbb::task_scheduler_init::default_num_threads() * NumRoundingModes, LauncherBody() );
}
void TestCpuCtlEnvApi () {
for( int k = 0; k < NumSseModes; ++k ) {
SetSseMode( SseModes[k] );
for( int i = 0; i < NumRoundingModes; ++i ) {
SetRoundingMode( RoundingModes[i] );
ASSERT( GetRoundingMode() == RoundingModes[i], NULL );
ASSERT( GetSseMode() == SseModes[k], NULL );
}
}
}
int TestMain () {
#if defined(__TBB_CPU_CTL_ENV_PRESENT) && !__TBB_CPU_CTL_ENV_PRESENT
return Harness::Skipped;
#else
TestCpuCtlEnvApi();
TestFpuEnvPropagation();
return Harness::Done;
#endif
}