/* Copyright (c) 2008-2010, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// This file is part of ThreadSanitizer, a dynamic data race detector.
// Author: Konstantin Serebryany.
// Author: Timur Iskhodzhanov.
//
// See ts_util.h for mode details.
#include "common_util.h"
#include "thread_sanitizer.h"
#include "ts_stats.h"
#include "ts_lock.h"
#include <stdarg.h>
FLAGS *G_flags = NULL;
#if defined(_MSC_VER)
#pragma comment(lib, "winmm.lib")
# ifdef TS_PIN
# include "pin.H"
# endif
namespace WINDOWS
{
// This is the way of including winows.h recommended by PIN docs.
#include<Windows.h>
}
int getpid() { return WINDOWS::GetCurrentProcessId(); }
#endif
#if defined(TS_VALGRIND)
size_t TimeInMilliSeconds() {
return VG_(read_millisecond_timer)();
}
#else
// TODO(kcc): implement this.
size_t TimeInMilliSeconds() {
#ifdef __GNUC__
return time(0) * 1000;
#else
return WINDOWS::timeGetTime();
#endif
}
#endif
Stats *G_stats;
#ifndef TS_LLVM
bool GetNameAndOffsetOfGlobalObject(uintptr_t addr,
string *name, uintptr_t *offset) {
# ifdef TS_VALGRIND
const int kBufLen = 1023;
char buff[kBufLen+1];
PtrdiffT off;
if (VG_(get_datasym_and_offset)(addr, reinterpret_cast<Char*>(buff),
kBufLen, &off)) {
*name = buff;
*offset = off;
return true;
}
return false;
# else
return false;
# endif // TS_VALGRIND
}
#endif // TS_LLVM
#ifndef TS_VALGRIND
void GetThreadStack(int tid, uintptr_t *min_addr, uintptr_t *max_addr) {
*min_addr = 0xfffa;
*max_addr = 0xfffb;
}
#endif
static int n_errs_found;
void SetNumberOfFoundErrors(int n_errs) {
n_errs_found = n_errs;
}
int GetNumberOfFoundErrors() {
return n_errs_found;
}
#if !defined(TS_VALGRIND) && !defined(TS_LLVM)
FILE *G_out = stderr;
#endif
#ifdef TS_LLVM
FILE *G_out;
#endif
static string RemoveUnsupportedFormat(const char *str) {
#ifdef _MSC_VER
// replace "%'" with "%"
string res;
size_t n = strlen(str);
if (n == 0) {
return "";
}
res.reserve(n);
res.push_back(str[0]);
for (size_t i = 1; i < n; i++) {
if (str[i] == '\'' && *res.rbegin() == '%') continue;
res.push_back(str[i]);
}
return res;
#else
return str;
#endif
}
void Printf(const char *format, ...) {
#ifdef TS_VALGRIND
va_list args;
va_start(args, format);
VG_(vprintf)(format, args);
va_end(args);
#else
va_list args;
va_start(args, format);
vfprintf(G_out, RemoveUnsupportedFormat(format).c_str(), args);
fflush(G_out);
va_end(args);
#endif
}
// Like Print(), but prepend each line with ==XXXXX==,
// where XXXXX is the pid.
void Report(const char *format, ...) {
int buff_size = 1024*16;
char *buff = new char[buff_size];
CHECK(buff);
DCHECK(G_flags);
va_list args;
while (1) {
va_start(args, format);
int ret = vsnprintf(buff, buff_size,
RemoveUnsupportedFormat(format).c_str(), args);
va_end(args);
if (ret < buff_size) break;
delete [] buff;
buff_size *= 2;
buff = new char[buff_size];
CHECK(buff);
// Printf("Resized buff: %d\n", buff_size);
}
char pid_buff[100];
snprintf(pid_buff, sizeof(pid_buff), "==%d== ", getpid());
string res;
#ifndef TS_LLVM
int len = strlen(buff);
#else
int len = __real_strlen(buff);
#endif
bool last_was_new_line = true;
for (int i = 0; i < len; i++) {
if (G_flags->show_pid && last_was_new_line)
res += pid_buff;
last_was_new_line = (buff[i] == '\n');
res += buff[i];
}
delete [] buff;
Printf("%s", res.c_str());
}
long my_strtol(const char *str, char **end, int base) {
#ifdef TS_VALGRIND
if (base == 16 || (base == 0 && str && str[0] == '0' && str[1] == 'x')) {
return VG_(strtoll16)((Char*)str, (Char**)end);
}
return VG_(strtoll10)((Char*)str, (Char**)end);
#else
return strtoll(str, end, base);
#endif
}
// Not thread-safe. Need to make it thread-local if we allow
// malloc to be called concurrently.
MallocCostCenterStack g_malloc_stack;
size_t GetVmSizeInMb() {
#ifdef VGO_linux
const char *path ="/proc/self/statm"; // see 'man proc'
uintptr_t counter = G_stats->read_proc_self_stats++;
if (counter >= 1024 && ((counter & (counter - 1)) == 0))
Report("INFO: reading %s for %ld'th time\n", path, counter);
int fd = OpenFileReadOnly(path, false);
if (fd < 0) return 0;
char buff[128];
int n_read = read(fd, buff, sizeof(buff) - 1);
buff[n_read] = 0;
close(fd);
char *end;
size_t vm_size_in_pages = my_strtol(buff, &end, 10);
return vm_size_in_pages >> 8;
#else
return 0;
#endif
}
static string StripTemplatesFromFunctionName(const string &fname) {
// Returns "" in case of error.
string ret;
size_t read_pointer = 0, braces_depth = 0;
while (read_pointer < fname.size()) {
size_t next_brace = fname.find_first_of("<>", read_pointer);
if (next_brace == fname.npos) {
if (braces_depth > 0) {
// This can happen on Visual Studio if we reach the ~2000 char limit.
CHECK(fname.size() > 256);
return "";
}
ret += (fname.c_str() + read_pointer);
break;
}
if (braces_depth == 0) {
ret.append(fname, read_pointer, next_brace - read_pointer);
}
if (next_brace > 0) {
// We could have found one of the following operators.
const char *OP[] = {">>=", "<<=",
">>", "<<",
">=", "<=",
"->", "->*",
"<", ">"};
bool operator_name = false;
for (size_t i = 0; i < TS_ARRAY_SIZE(OP); i++) {
size_t op_offset = ((string)OP[i]).find(fname[next_brace]);
if (op_offset == string::npos)
continue;
if (next_brace >= 8 + op_offset && // 8 == strlen("operator");
"operator" == fname.substr(next_brace - (8 + op_offset), 8) &&
OP[i] == fname.substr(next_brace - op_offset, strlen(OP[i]))) {
operator_name = true;
ret += OP[i] + op_offset;
next_brace += strlen(OP[i] + op_offset);
read_pointer = next_brace;
break;
}
}
if (operator_name)
continue;
}
if (fname[next_brace] == '<') {
braces_depth++;
read_pointer = next_brace + 1;
} else if (fname[next_brace] == '>') {
if (braces_depth == 0) {
// Going to `braces_depth == -1` IS possible at least for this function on Windows:
// "std::operator<<char,std::char_traits<char>,std::allocator<char> >".
// Oh, well... Return an empty string and let the caller decide.
return "";
}
braces_depth--;
read_pointer = next_brace + 1;
} else
CHECK(0);
}
if (braces_depth != 0) {
CHECK(fname.size() > 256);
return "";
}
return ret;
}
static string StripParametersFromFunctionName(const string &demangled) {
// Returns "" in case of error.
string fname = demangled;
// Strip stuff like "(***)" and "(anonymous namespace)" -> they are tricky.
size_t found = fname.npos;
while ((found = fname.find(", ")) != fname.npos)
fname.erase(found+1, 1);
while ((found = fname.find("(**")) != fname.npos)
fname.erase(found+2, 1);
while ((found = fname.find("(*)")) != fname.npos)
fname.erase(found, 3);
while ((found = fname.find("const()")) != fname.npos)
fname.erase(found+5, 2);
while ((found = fname.find("const volatile")) != fname.npos &&
found > 1 && found + 14 == fname.size())
fname.erase(found-1);
while ((found = fname.find("(anonymous namespace)")) != fname.npos)
fname.erase(found, 21);
if (fname.find_first_of("(") == fname.npos)
return fname;
DCHECK(count(fname.begin(), fname.end(), '(') ==
count(fname.begin(), fname.end(), ')'));
string ret;
bool returns_fun_ptr = false;
size_t braces_depth = 0, read_pointer = 0;
size_t first_parenthesis = fname.find("(");
if (first_parenthesis != fname.npos) {
DCHECK(fname.find_first_of(")") != fname.npos);
DCHECK(fname.find_first_of(")") > first_parenthesis);
DCHECK(fname[first_parenthesis] == '(');
if (first_parenthesis + 2 < fname.size() &&
fname[first_parenthesis - 1] == ' ' &&
fname[first_parenthesis + 1] == '*' &&
fname[first_parenthesis + 2] != ' ') {
// Return value type is a function pointer
read_pointer = first_parenthesis + 2;
while (fname[read_pointer] == '*' || fname[read_pointer] == '&')
read_pointer++;
braces_depth = 1;
returns_fun_ptr = true;
}
}
while (read_pointer < fname.size()) {
size_t next_brace = fname.find_first_of("()", read_pointer);
if (next_brace == fname.npos) {
if (braces_depth != 0) {
// Overflow?
return "";
}
size_t _const = fname.find(" const", read_pointer);
if (_const == fname.npos) {
ret += (fname.c_str() + read_pointer);
} else {
CHECK(_const + 6 == fname.size());
ret.append(fname, read_pointer, _const - read_pointer);
}
break;
}
if (braces_depth == (returns_fun_ptr ? 1 : 0)) {
ret.append(fname, read_pointer, next_brace - read_pointer);
returns_fun_ptr = false;
}
if (fname[next_brace] == '(') {
if (next_brace >= 8 && fname[next_brace+1] == ')' &&
"operator" == fname.substr(next_brace - 8, 8)) {
ret += "()";
read_pointer = next_brace + 2;
} else {
braces_depth++;
read_pointer = next_brace + 1;
}
} else if (fname[next_brace] == ')') {
CHECK(braces_depth > 0);
braces_depth--;
read_pointer = next_brace + 1;
} else
CHECK(0);
}
if (braces_depth != 0)
return "";
// Special case: on Linux, Valgrind prepends the return type for template
// functions. And on Windows we may see `scalar deleting destructor'.
// And we may see "operaror new" etc.
// And some STL code inserts const& between the return type and the function
// name.
// Oh, well...
size_t space_or_tick;
while (ret != "") {
space_or_tick = ret.find_first_of("` ");
if (space_or_tick != ret.npos && ret[space_or_tick] == ' ' &&
ret.substr(0, space_or_tick).find("operator") == string::npos) {
ret = ret.substr(space_or_tick + 1);
} else if (space_or_tick != ret.npos && space_or_tick + 1 == ret.size()) {
ret = ret.substr(0, space_or_tick);
} else {
break;
}
}
return ret;
}
string NormalizeFunctionName(const string &demangled) {
if (demangled[1] == '[' && strchr("+-=", demangled[0]) != NULL) {
// Objective-C function
return demangled;
}
if (demangled.find_first_of("<>()") == demangled.npos) {
// C function or a well-formatted function name.
return demangled;
}
if (demangled == "(below main)" || demangled == "(no symbols)")
return demangled;
const char* const MALFORMED = "(malformed frame)";
string fname = StripTemplatesFromFunctionName(demangled);
if (fname.size() == 0) {
if (DEBUG_MODE)
Printf("PANIC: `%s`\n", demangled.c_str());
return MALFORMED;
}
fname = StripParametersFromFunctionName(fname);
if (fname.size() == 0) {
CHECK(demangled.size() >= 256);
if (DEBUG_MODE)
Printf("PANIC: `%s`\n", demangled.c_str());
return MALFORMED;
}
return fname;
}
void OpenFileWriteStringAndClose(const string &file_name, const string &str) {
#ifdef TS_VALGRIND
SysRes sres = VG_(open)((const Char*)file_name.c_str(),
VKI_O_WRONLY|VKI_O_CREAT|VKI_O_TRUNC,
VKI_S_IRUSR|VKI_S_IWUSR);
if (sr_isError(sres)) {
Report("WARNING: can not open file %s\n", file_name.c_str());
exit(1);
}
int fd = sr_Res(sres);
write(fd, str.c_str(), str.size());
close(fd);
#else
CHECK(0);
#endif
}
//--------- Sockets ------------------ {{{1
#if defined(TS_PIN) && defined(__GNUC__)
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
FILE *OpenSocketForWriting(const string &host_and_port) {
size_t col = host_and_port.find(":");
if (col == string::npos) return NULL;
string host = host_and_port.substr(0, col);
string port_str = host_and_port.substr(col + 1);
int sockfd;
struct sockaddr_in serv_addr;
struct hostent *server;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) return NULL;
server = gethostbyname(host.c_str());
if (server == 0) return NULL;
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
memcpy((char *)&serv_addr.sin_addr.s_addr,
(char *)server->h_addr,
server->h_length);
serv_addr.sin_port = htons(atoi(port_str.c_str()));
if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) < 0)
return NULL;
return fdopen(sockfd, "w");
}
#else
FILE *OpenSocketForWriting(const string &host_and_port) {
return NULL; // unimplemented.
}
#endif
//--------- TSLock ------------------ {{{1
#ifdef _MSC_VER
//# define TS_LOCK_PIPE
# define TS_LOCK_PIN
#else
# define TS_LOCK_FUTEX
#endif
#if defined(TS_LOCK_PIPE) && defined(TS_PIN)
#ifdef __GNUC__
#include <unistd.h>
// Lock based on pipe's send/receive. The idea (but not the code)
// is shamelessly stolen from valgrind's /coregrind/m_scheduler/sema.c
struct TSLock::Rep {
bool held;
char pipe_char;
int pipe_fd[2];
void Write() {
char buf[2];
buf[0] = pipe_char;
buf[1] = 0;
int res = write(pipe_fd[1], buf, 1);
CHECK(res == 1);
}
bool Read() {
char buf[2];
buf[0] = 0;
buf[1] = 0;
int res = read(pipe_fd[0], buf, 1);
if (res != 1)
return false;
//Printf("rep::Read: %c\n", buf[0]);
pipe_char++;
if (pipe_char == 'Z' + 1) pipe_char = 'A';
return true;
}
void Open() {
CHECK(0 == pipe(pipe_fd));
CHECK(pipe_fd[0] != pipe_fd[1]);
pipe_char = 'A';
}
void Close() {
close(pipe_fd[0]);
close(pipe_fd[1]);
}
};
#elif defined(_MSC_VER)
struct TSLock::Rep {
bool held;
char pipe_char;
WINDOWS::HANDLE pipe_fd[2];
void Write() {
char buf[2];
buf[0] = pipe_char;
buf[1] = 0;
WINDOWS::DWORD n_written = 0;
int res = WINDOWS::WriteFile(pipe_fd[1], buf, 1, &n_written, NULL);
CHECK(res != 0 && n_written == 1);
}
bool Read() {
char buf[2];
buf[0] = 0;
buf[1] = 0;
WINDOWS::DWORD n_read = 0;
int res = WINDOWS::ReadFile(pipe_fd[0], buf, 1, &n_read, NULL);
if (res == 0 && n_read == 0)
return false;
//Printf("rep::Read: %c\n", buf[0]);
pipe_char++;
if (pipe_char == 'Z' + 1) pipe_char = 'A';
return true;
}
void Open() {
CHECK(WINDOWS::CreatePipe(&pipe_fd[0], &pipe_fd[1], NULL, 0));
CHECK(pipe_fd[0] != pipe_fd[1]);
pipe_char = 'A';
}
void Close() {
WINDOWS::CloseHandle(pipe_fd[0]);
WINDOWS::CloseHandle(pipe_fd[1]);
}
};
#endif
TSLock::TSLock() {
rep_ = new Rep;
rep_->held = false;
rep_->Open();
rep_->Write();
}
TSLock::~TSLock() {
rep_->Close();
}
void TSLock::Lock() {
while(rep_->Read() == false)
;
rep_->held = true;
}
void TSLock::Unlock() {
rep_->held = false;
rep_->Write();
}
void TSLock::AssertHeld() {
DCHECK(rep_->held);
}
#endif // __GNUC__ & TS_LOCK_PIPE
#if defined(TS_LOCK_PIN) && defined(TS_PIN)
#include "pin.H"
struct TSLock::Rep {
PIN_LOCK lock;
bool held;
};
TSLock::TSLock() {
rep_ = new Rep();
rep_->held = false;
InitLock(&rep_->lock);
}
TSLock::~TSLock() {
delete rep_;
}
void TSLock::Lock() {
GetLock(&rep_->lock, __LINE__);
rep_->held = true;
}
void TSLock::Unlock() {
rep_->held = false;
ReleaseLock(&rep_->lock);
}
void TSLock::AssertHeld() {
DCHECK(rep_->held);
}
#endif // TS_LOCK_PIN
#if defined(TS_WRAP_PTHREAD_LOCK)
#include "tsan_rtl_wrap.h"
struct TSLock::Rep {
pthread_mutex_t lock;
bool held;
};
TSLock::TSLock() {
rep_ = new Rep();
rep_->held = false;
__real_pthread_mutex_init(&rep_->lock, NULL);
}
TSLock::~TSLock() {
__real_pthread_mutex_destroy(&rep_->lock);
delete rep_;
}
void TSLock::Lock() {
__real_pthread_mutex_lock(&rep_->lock);
rep_->held = true;
}
void TSLock::Unlock() {
rep_->held = false;
__real_pthread_mutex_unlock(&rep_->lock);
}
void TSLock::AssertHeld() {
DCHECK(rep_->held);
}
#endif // TS_LLVM
#if defined(TS_LOCK_FUTEX) && defined(__GNUC__) && \
(defined (TS_PIN) || defined (TS_LLVM))
#include <linux/futex.h>
#include <sys/time.h>
#include <syscall.h>
// Simple futex-based lock.
// The idea is taken from "Futexes Are Tricky" by Ulrich Drepper
TSLock::TSLock() {
rep_ = 0;
ANNOTATE_BENIGN_RACE(&rep_, "Benign race on TSLock::rep_");
ANNOTATE_RWLOCK_CREATE(this);
}
TSLock::~TSLock() {
ANNOTATE_RWLOCK_DESTROY(this);
DCHECK(rep_ == 0);
}
void TSLock::Lock() {
int *p = (int*)&rep_;
const int kSpinCount = 100;
DCHECK(kSpinCount > 0);
int c;
for (int i = 0; i < kSpinCount; i++) {
c = __sync_val_compare_and_swap(p, 0, 1);
if (c == 0) break;
}
if (c == 0) {
// The mutex was unlocked. Now it's ours. Done.
ANNOTATE_RWLOCK_ACQUIRED(this, /*is_w*/true);
return;
}
DCHECK(c == 1 || c == 2);
// We are going to block on this lock. Make sure others know that.
if (c != 2) {
c = __sync_lock_test_and_set(p, 2);
}
// Block.
int n_waits = 0;
while (c != 0) {
syscall(SYS_futex, p, FUTEX_WAIT, 2, 0, 0, 0);
n_waits++;
c = __sync_lock_test_and_set(p, 2);
}
ANNOTATE_RWLOCK_ACQUIRED(this, /*is_w*/true);
G_stats->futex_wait += n_waits;
}
void TSLock::Unlock() {
ANNOTATE_RWLOCK_RELEASED(this, /*is_w*/true);
int *p = (int*)&rep_;
DCHECK(*p == 1 || *p == 2);
int c = __sync_sub_and_fetch(p, 1);
DCHECK(c == 0 || c == 1);
if (c == 1) {
*p = 0;
syscall(SYS_futex, p, FUTEX_WAKE, 1, 0, 0, 0);
}
}
void TSLock::AssertHeld() {
DCHECK(rep_);
}
#endif
// Same as above to compile Go's rtl
// No annotations in this version: it should be simple as possible.
#if defined(TS_LOCK_FUTEX) && defined(__GNUC__) && \
(defined (TS_GO))
#include <linux/futex.h> // TODO(mpimenov): portability?
#include <sys/time.h>
#include <syscall.h>
// Simple futex-based lock.
// The idea is taken from "Futexes Are Tricky" by Ulrich Drepper
TSLock::TSLock() {
rep_ = 0;
}
TSLock::~TSLock() {
DCHECK(rep_ == 0);
}
void TSLock::Lock() {
int *p = (int*)&rep_;
const int kSpinCount = 100;
DCHECK(kSpinCount > 0);
int c;
for (int i = 0; i < kSpinCount; i++) {
c = __sync_val_compare_and_swap(p, 0, 1);
if (c == 0) break;
}
if (c == 0) {
// The mutex was unlocked. Now it's ours. Done.
return;
}
DCHECK(c == 1 || c == 2);
// We are going to block on this lock. Make sure others know that.
if (c != 2) {
c = __sync_lock_test_and_set(p, 2);
}
// Block.
int n_waits = 0;
while (c != 0) {
syscall(SYS_futex, p, FUTEX_WAIT, 2, 0, 0, 0);
n_waits++;
c = __sync_lock_test_and_set(p, 2);
}
G_stats->futex_wait += n_waits;
}
void TSLock::Unlock() {
int *p = (int*)&rep_;
DCHECK(*p == 1 || *p == 2);
int c = __sync_sub_and_fetch(p, 1);
DCHECK(c == 0 || c == 1);
if (c == 1) {
*p = 0;
syscall(SYS_futex, p, FUTEX_WAKE, 1, 0, 0, 0);
}
}
void TSLock::AssertHeld() {
DCHECK(rep_);
}
#endif // (TS_LOCK_FUTEX) (__GNUC__) && (TS_GO)
//--------------- Atomics ----------------- {{{1
#if defined (_MSC_VER) && TS_SERIALIZED == 0
uintptr_t AtomicExchange(uintptr_t *ptr, uintptr_t new_value) {
return _InterlockedExchange((volatile WINDOWS::LONG*)ptr, new_value);
}
void ReleaseStore(uintptr_t *ptr, uintptr_t value) {
*(volatile uintptr_t*)ptr = value;
// TODO(kcc): anything to add here?
}
int32_t NoBarrier_AtomicIncrement(int32_t* ptr) {
return _InterlockedIncrement((volatile WINDOWS::LONG *)ptr);
}
int32_t NoBarrier_AtomicDecrement(int32_t* ptr) {
return _InterlockedDecrement((volatile WINDOWS::LONG *)ptr);
}
#endif // _MSC_VER && TS_SERIALIZED
//--------------- YIELD ----------------- {{{1
#if defined (_MSC_VER)
#include <intrin.h>
void YIELD() {
WINDOWS::Sleep(0);
}
void PROCESSOR_YIELD() {
_mm_pause();
}
#elif defined(TS_VALGRIND)
void YIELD() {
}
void PROCESSOR_YIELD() {
}
#elif defined(__GNUC__)
void YIELD() {
sched_yield();
}
void PROCESSOR_YIELD() {
__asm__ __volatile__ ("pause");
}
#else
#error "Unknown config"
#endif
// end. {{{1
// vim:shiftwidth=2:softtabstop=2:expandtab:tw=80