/**************************************************************************** ** ** This file is part of GAP, a system for computational discrete algebra. ** ** Copyright of GAP belongs to its developers, whose names are too numerous ** to list here. Please refer to the COPYRIGHT file for details. ** ** SPDX-License-Identifier: GPL-2.0-or-later
*/
// The following code also occurs in configure.ac, and both need to be // kept in sync.
#define OFFS 0x100 #define END (-1)
int cmpOpCode(unsignedchar *code, int *with) { int result = 0; while (*with >= 0) { if (*with == OFFS) {
result = *code;
} else { if (*code != *with) return -1;
}
code++;
with++;
} return result;
}
void FindTLSOffset() { // This is an idea borrowed from Mono. We test if the implementation // of pthread_getspecific() uses the assembly code below. If that is // true, we can replace calls to pthread_getspecific() with the // matching inline assembly, allowing a significant performance boost. // There are two possible implementations. staticint asm_code[] = { // movq %gs:[OFFS](,%rdi,8), %rax // retq
0x65, 0x48, 0x8b, 0x04, 0xfd, OFFS, 0x00, 0x00, 0x00, 0xc3, END
}; staticint asm_code2[] = { // pushq %rbp // movq %rsp, %rbp // movq %gs:[OFFS](,%rdi,8),%rax // popq %rbp // retq
0x55, 0x48, 0x89, 0xe5, 0x65, 0x48, 0x8b, 0x04, 0xfd, OFFS,
0x00, 0x00, 0x00, 0x5d, 0xc3, END
};
TLSOffset = cmpOpCode((unsignedchar *)pthread_getspecific, asm_code); if (TLSOffset >= 0) return;
TLSOffset = cmpOpCode((unsignedchar *)pthread_getspecific, asm_code2); if (TLSOffset >= 0) return;
Panic("Unable to find macOS thread-local storage offset");
} #endif
void FreeTLS(void * address)
{ /* We currently cannot free this memory because of garbage collector
* issues. Instead, it will be reused */ #if 0
munmap(address, TLS_STACK_SIZE); #endif
}
/* In order to safely use thread-local memory on the main stack, we have * to work around an idiosyncrasy in some virtual memory systems. These * VM implementations do not allow fully random access within the stack * segment, but only quasi-linear access: a page can only be accessed if * a nearby page was accessed before. If this pattern is not observed, * but if we access memory within the stack segment randomly -- as in * our TLS implementation, but also with very large stack frames -- a * segmentation fault can arise. To avoid such segmentation faults, we * traverse the stack segment of the main stack, touching each page in * turn. * * Note that this is not necessary for thread stacks, which are * allocated in private memory-mapped storage.
*/
static NOINLINE void GrowStack(void)
{ char * tls = (char *)GetTLS();
size_t pagesize = getpagesize(); /* p needs to be a volatile pointer so that the memory writes are not
* removed by the optimizer */ volatilechar * p = alloca(pagesize); while (p > tls) { // touch memory
*p = '\0';
p = alloca(pagesize);
}
} #endif
void RunThreadedMain(int (*mainFunction)(int, constchar **), int argc, constchar ** argv)
{ #ifndef USE_NATIVE_TLS #ifdef STACK_GROWS_UP #error Upward growing stack not yet supported #else /* We need to ensure that the stack pointer and frame pointer * of the called function begin at the top end of a memory * segment whose beginning and end address are a multiple of * TLS_SIZE (which is a power of 2). To that end, we look at * an approximation of the current stack pointer by taking * the address of a local variable, then mask out the lowest * bits and use alloca() to allocate at least that many bytes * on the stack. We also need to touch the pages in that area; * see the comments on GrowStack() for the reason why.
*/ volatileint dummy[1];
size_t amount;
amount = ((uintptr_t)dummy) & ~TLS_MASK; volatilechar * p = alloca(((uintptr_t)dummy) & ~TLS_MASK); volatilechar * q; for (q = p + amount - 1; (void *)q >= (void *)p; q -= 1024) { // touch memory
*q = '\0';
} #endif #endif
RunThreadedMain2(mainFunction, argc, argv);
}
staticvoid RunThreadedMain2(int (*mainFunction)(int, constchar **), int argc, constchar ** argv)
{ int i; static pthread_mutex_t main_thread_mutex; static pthread_cond_t main_thread_cond;
SetupTLS(); for (i = 1; i < MAX_THREADS - 1; i++)
thread_data[i].next = thread_data + i + 1;
thread_data[0].next = NULL; for (i = 0; i < NUM_LOCKS; i++)
pthread_rwlock_init(&ObjLock[i], 0);
thread_data[MAX_THREADS - 1].next = NULL; for (i = 0; i < MAX_THREADS; i++) {
thread_data[i].tls = 0;
thread_data[i].state = TSTATE_TERMINATED;
thread_data[i].system = 0;
}
thread_free_list = thread_data + 1;
pthread_rwlock_init(&master_lock, 0);
pthread_mutex_init(&main_thread_mutex, 0);
pthread_cond_init(&main_thread_cond, 0);
TLS(threadLock) = &main_thread_mutex;
TLS(threadSignal) = &main_thread_cond;
thread_data[0].lock = TLS(threadLock);
thread_data[0].cond = TLS(threadSignal);
thread_data[0].state = TSTATE_RUNNING;
thread_data[0].tls = GetTLS();
InitSignals(); if (setjmp(TLS(threadExit))) exit(0); exit((*mainFunction)(argc, argv));
}
void RegionWriteLock(Region * region)
{ int result = 0;
assert(region->owner != GetTLS());
if (region->count_active || TLS(CountActive)) {
result = !pthread_rwlock_trywrlock(region->lock);
if (result) { if (region->count_active)
region->locks_acquired++; if (TLS(CountActive))
TLS(LocksAcquired)++;
} else { if (region->count_active)
region->locks_contended++; if (TLS(CountActive))
TLS(LocksContended)++;
pthread_rwlock_wrlock(region->lock);
}
} else {
pthread_rwlock_wrlock(region->lock);
}
region->owner = GetTLS();
}
int RegionTryWriteLock(Region * region)
{ int result;
assert(region->owner != GetTLS());
result = !pthread_rwlock_trywrlock(region->lock);
if (result) { if (region->count_active)
region->locks_acquired++; if (TLS(CountActive))
TLS(LocksAcquired)++;
region->owner = GetTLS();
} else { if (region->count_active)
region->locks_contended++; if (TLS(CountActive))
TLS(LocksContended)++;
} return result;
}
void RegionReadLock(Region * region)
{ int result = 0;
assert(region->owner != GetTLS());
assert(region->readers[TLS(threadID)] == 0);
if (region->count_active || TLS(CountActive)) {
result = !pthread_rwlock_rdlock(region->lock);
if (result) { if (region->count_active)
ATOMIC_INC(®ion->locks_acquired); if (TLS(CountActive))
TLS(LocksAcquired)++;
} else { if (region->count_active)
ATOMIC_INC(®ion->locks_contended); if (TLS(CountActive))
TLS(LocksAcquired)++;
pthread_rwlock_rdlock(region->lock);
}
} else {
pthread_rwlock_rdlock(region->lock);
}
region->readers[TLS(threadID)] = 1;
}
int RegionTryReadLock(Region * region)
{ int result = !pthread_rwlock_rdlock(region->lock);
assert(region->owner != GetTLS());
assert(region->readers[TLS(threadID)] == 0);
if (result) { if (region->count_active)
ATOMIC_INC(®ion->locks_acquired); if (TLS(CountActive))
TLS(LocksAcquired)++;
region->readers[TLS(threadID)] = 1;
} else { if (region->count_active)
ATOMIC_INC(®ion->locks_contended); if (TLS(CountActive))
TLS(LocksContended)++;
} return result;
}
LockStatus IsLocked(Region * region)
{ if (!region) return LOCK_STATUS_UNLOCKED; // public region if (region->owner == GetTLS()) return LOCK_STATUS_READWRITE_LOCKED; if (region->readers[TLS(threadID)]) return LOCK_STATUS_READONLY_LOCKED; return LOCK_STATUS_UNLOCKED;
}
Region * GetRegionOf(Obj obj)
{ if (!IS_BAG_REF(obj)) return NULL; if (TNUM_OBJ(obj) == T_REGION) return *(Region **)(ADDR_OBJ(obj)); return REGION(obj);
}
void SetRegionName(Region * region, Obj name)
{ if (name) { if (!IS_STRING(name)) return; if (IS_MUTABLE_OBJ(name))
name = CopyObj(name, 0);
}
MEMBAR_WRITE();
region->name = name;
}
Obj GetRegionName(Region * region)
{
Obj result; if (region)
result = region->name; else
result = PublicRegionName;
MEMBAR_READ(); return result;
}
void GetLockStatus(int count, Obj * objects, LockStatus * status)
{ int i; for (i = 0; i < count; i++)
status[i] = IsLocked(REGION(objects[i]));
}
void PopRegionLocks(int newSP)
{ while (newSP < TLS(lockStackPointer)) { int p = TLS(lockStackPointer)--;
Obj region_obj = ELM_PLIST(TLS(lockStack), p); if (!region_obj) continue;
Region * region = *(Region **)(ADDR_OBJ(region_obj));
RegionUnlock(region);
SET_ELM_PLIST(TLS(lockStack), p, (Obj)0);
}
}
int RegionLockSP(void)
{ return TLS(lockStackPointer);
}
int GetThreadState(int threadID)
{ return (int)(thread_data[threadID].state);
}
int UpdateThreadState(int threadID, int oldState, int newState)
{ return COMPARE_AND_SWAP(&thread_data[threadID].state,
(AtomicUInt)oldState, (AtomicUInt)newState);
}
staticint LockAndUpdateThreadState(int threadID, int oldState, int newState)
{ if (pthread_mutex_trylock(thread_data[threadID].lock) < 0) { return 0;
} if (!UpdateThreadState(threadID, oldState, newState)) {
pthread_mutex_unlock(thread_data[threadID].lock); return 0;
}
SetInterrupt(threadID);
pthread_cond_signal(thread_data[threadID].cond);
pthread_mutex_unlock(thread_data[threadID].lock); return 1;
}
void PauseThread(int threadID)
{ for (;;) { int state = GetThreadState(threadID); switch (state & TSTATE_MASK) { case TSTATE_RUNNING: if (UpdateThreadState(threadID, TSTATE_RUNNING,
TSTATE_PAUSED |
(TLS(threadID) << TSTATE_SHIFT))) {
SetInterrupt(threadID); return;
} break; case TSTATE_BLOCKED: case TSTATE_SYSCALL: if (LockAndUpdateThreadState(threadID, state,
TSTATE_PAUSED |
(TLS(threadID) << TSTATE_SHIFT))) return; break; case TSTATE_PAUSED: case TSTATE_TERMINATED: case TSTATE_KILLED: case TSTATE_INTERRUPTED: return;
}
}
}
void HandleInterrupts(int locked, Stat stat)
{ switch (GetThreadState(TLS(threadID)) & TSTATE_MASK) { case TSTATE_PAUSED:
PauseCurrentThread(locked); break; case TSTATE_INTERRUPTED:
InterruptCurrentThread(locked, stat); break; case TSTATE_KILLED:
TerminateCurrentThread(locked); break;
}
}
void KillThread(int threadID)
{ for (;;) { int state = GetThreadState(threadID); switch (state & TSTATE_MASK) { case TSTATE_RUNNING: if (UpdateThreadState(threadID, TSTATE_RUNNING, TSTATE_KILLED)) {
SetInterrupt(threadID); return;
} break; case TSTATE_BLOCKED: if (LockAndUpdateThreadState(threadID, state, TSTATE_KILLED)) return; break; case TSTATE_SYSCALL: if (UpdateThreadState(threadID, state, TSTATE_KILLED)) { return;
} break; case TSTATE_INTERRUPTED: if (UpdateThreadState(threadID, state, TSTATE_KILLED)) {
SetInterrupt(threadID); return;
} break; case TSTATE_PAUSED: if (LockAndUpdateThreadState(threadID, state, TSTATE_KILLED)) { return;
} break; case TSTATE_TERMINATED: case TSTATE_KILLED: return;
}
}
}
void InterruptThread(int threadID, int handler)
{ for (;;) { int state = GetThreadState(threadID); switch (state & TSTATE_MASK) { case TSTATE_RUNNING: if (UpdateThreadState(threadID, TSTATE_RUNNING,
TSTATE_INTERRUPTED |
(handler << TSTATE_SHIFT))) {
SetInterrupt(threadID); return;
} break; case TSTATE_BLOCKED: if (LockAndUpdateThreadState(threadID, state,
TSTATE_INTERRUPTED |
(handler << TSTATE_SHIFT))) return; break; case TSTATE_SYSCALL: if (UpdateThreadState(threadID, state,
TSTATE_INTERRUPTED |
(handler << TSTATE_SHIFT))) return; break; case TSTATE_PAUSED: case TSTATE_TERMINATED: case TSTATE_KILLED: case TSTATE_INTERRUPTED: // We do not interrupt threads that are interrupted return;
}
}
}
void ResumeThread(int threadID)
{ int state = GetThreadState(threadID); if ((state & TSTATE_MASK) == TSTATE_PAUSED) {
LockAndUpdateThreadState(threadID, state, TSTATE_RUNNING);
}
}
int PauseAllThreads(void)
{ int i, n;
LockThreadControl(1); if (GlobalPauseInProgress) {
UnlockThreadControl(); return 0;
}
GlobalPauseInProgress = 1; for (i = 0, n = 0; i < MAX_THREADS; i++) { switch (GetThreadState(i) & TSTATE_MASK) { case TSTATE_TERMINATED: case TSTATE_KILLED: break; default: if (!thread_data[i].system)
paused_threads[n++] = thread_data + i; break;
}
}
num_paused_threads = n;
UnlockThreadControl(); for (i = 0; i < n; i++)
PauseThread(i); return 1;
}
void ResumeAllThreads(void)
{ int i, n;
n = num_paused_threads; for (i = 0; i < n; i++) {
ResumeThread(i);
}
}
/** * Deadlock checks * --------------- * * We use a scheme of numerical tiers to implement deadlock checking. * Each region is assigned a numerical precedence, and regions must * be locked in strictly descending numerical order. If this order * is inverted, or if two regions of the same precedence are locked * through separate actions, then this is an error. * * Regions with negative precedence are ignored for these tests. This * is to facilitate more complex precedence schemes that cannot be * embedded in a total ordering.
*/
staticInt CurrentRegionPrecedence(void)
{ Int sp; if (!DeadlockCheck || !TLS(lockStack)) return -1;
sp = TLS(lockStackPointer); while (sp > 0) {
Obj region_obj = ELM_PLIST(TLS(lockStack), sp); if (region_obj) { Int prec = ((Region *)(*ADDR_OBJ(region_obj)))->prec; if (prec >= 0) return prec;
}
sp--;
} return -1;
}
int LockObject(Obj obj, LockMode mode)
{
Region * region = GetRegionOf(obj); int result = TLS(lockStackPointer); if (!region) return result;
LockStatus locked = IsLocked(region); if (locked == LOCK_STATUS_READONLY_LOCKED && mode == LOCK_MODE_READWRITE) return -1; if (locked == LOCK_STATUS_UNLOCKED) { Int prec = CurrentRegionPrecedence(); if (prec >= 0 && region->prec >= prec && region->prec >= 0) return -1; if (region->fixed_owner) return -1; if (mode == LOCK_MODE_READWRITE)
RegionWriteLock(region); else
RegionReadLock(region);
PushRegionLock(region);
} return result;
}
int LockObjects(int count, Obj * objects, const LockMode * mode)
{ int result; int i, p; Int curr_prec;
LockRequest * order; if (count == 1) // fast path return LockObject(objects[0], mode[0]); if (count > MAX_LOCKS) return -1;
order = alloca(sizeof(LockRequest) * count); for (i = 0, p = 0; i < count; i++) {
Region * r = GetRegionOf(objects[i]); if (r) {
order[p].obj = objects[i];
order[p].region = GetRegionOf(objects[i]);
order[p].mode = mode[i];
p++;
}
}
count = p; if (p > 1)
MergeSort(order, count, sizeof(LockRequest), LessThanLockRequest);
result = TLS(lockStackPointer);
curr_prec = CurrentRegionPrecedence(); for (i = 0; i < count; i++) {
Region * region = order[i].region; /* If there are multiple lock requests with different modes, * they have been sorted for writes to occur first, so deadlock * cannot occur from doing readlocks before writelocks.
*/ if (i > 0 && region == order[i - 1].region) continue; // skip duplicates if (!region) continue;
LockStatus locked = IsLocked(region); if (locked == LOCK_STATUS_READONLY_LOCKED &&
order[i].mode == LOCK_MODE_READWRITE) { // trying to upgrade read lock to write lock
PopRegionLocks(result); return -1;
} if (!locked) { if (curr_prec >= 0 && region->prec >= curr_prec &&
region->prec >= 0) {
PopRegionLocks(result); return -1;
} if (region->fixed_owner) {
PopRegionLocks(result); return -1;
} if (order[i].mode == LOCK_MODE_READWRITE)
RegionWriteLock(region); else
RegionReadLock(region);
PushRegionLock(region);
} if (GetRegionOf(order[i].obj) != region) { // Race condition, revert locks and fail
PopRegionLocks(result); return -1;
}
} return result;
}
int TryLockObjects(int count, Obj * objects, const LockMode * mode)
{ int result; int i;
LockRequest * order; if (count > MAX_LOCKS) return -1;
order = alloca(sizeof(LockRequest) * count); for (i = 0; i < count; i++) {
order[i].obj = objects[i];
order[i].region = GetRegionOf(objects[i]);
order[i].mode = mode[i];
}
MergeSort(order, count, sizeof(LockRequest), LessThanLockRequest);
result = TLS(lockStackPointer); for (i = 0; i < count; i++) {
Region * region = order[i].region; /* If there are multiple lock requests with different modes, * they have been sorted for writes to occur first, so deadlock * cannot occur from doing readlocks before writelocks.
*/ if (i > 0 && region == order[i - 1].region) continue; // skip duplicates if (!region ||
region->fixed_owner) { // public or thread-local region
PopRegionLocks(result); return -1;
}
LockStatus locked = IsLocked(region); if (locked == LOCK_STATUS_READONLY_LOCKED &&
order[i].mode == LOCK_MODE_READWRITE) { // trying to upgrade read lock to write lock
PopRegionLocks(result); return -1;
} if (locked == LOCK_STATUS_UNLOCKED) { if (order[i].mode == LOCK_MODE_READWRITE) { if (!RegionTryWriteLock(region)) {
PopRegionLocks(result); return -1;
}
} else { if (!RegionTryReadLock(region)) {
PopRegionLocks(result); return -1;
}
}
PushRegionLock(region);
} if (GetRegionOf(order[i].obj) != region) { // Race condition, revert locks and fail
PopRegionLocks(result); return -1;
}
} return result;
}
Region * CurrentRegion(void)
{ return TLS(currentRegion);
}
#ifdef VERBOSE_GUARDS
staticvoid PrintGuardError(char * buffer, char * mode,
Obj obj, constchar * file, unsigned line, constchar * func, constchar * expr)
{
sprintf(buffer, "No %s access to object %llu of type %s\n" "in %s, line %u, function %s(), accessing %s",
mode, (unsignedlonglong)(UInt)obj, TNAM_OBJ(obj), file, line,
func, expr);
} void WriteGuardError(Obj o, constchar * file, unsigned line, constchar * func, constchar * expr)
{ char * buffer = alloca(strlen(file) + strlen(func) + strlen(expr) + 200);
ImpliedReadGuard(o); if (TLS(DisableGuards)) return;
SetGVar(&LastInaccessibleGVar, o);
PrintGuardError(buffer, "write", o, file, line, func, expr);
ErrorMayQuit("%s", (UInt)buffer, 0);
}
#else void WriteGuardError(Obj o)
{
ImpliedReadGuard(o); if (TLS(DisableGuards)) return;
SetGVar(&LastInaccessibleGVar, o);
ErrorMayQuit( "Attempt to write object %d of type %s without having write access",
(Int)o, (Int)TNAM_OBJ(o));
}
void ReadGuardError(Obj o)
{
ImpliedReadGuard(o); if (TLS(DisableGuards)) return;
SetGVar(&LastInaccessibleGVar, o);
ErrorMayQuit( "Attempt to read object %d of type %s without having read access",
(Int)o, (Int)TNAM_OBJ(o));
} #endif
#endif
¤ Dauer der Verarbeitung: 0.14 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.