mirror of
https://github.com/autc04/Retro68.git
synced 2024-11-24 23:32:06 +00:00
205 lines
4.7 KiB
C
205 lines
4.7 KiB
C
// Copyright 2011 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
// +build dragonfly freebsd linux
|
|
|
|
#include "runtime.h"
|
|
|
|
// This implementation depends on OS-specific implementations of
|
|
//
|
|
// runtime_futexsleep(uint32 *addr, uint32 val, int64 ns)
|
|
// Atomically,
|
|
// if(*addr == val) sleep
|
|
// Might be woken up spuriously; that's allowed.
|
|
// Don't sleep longer than ns; ns < 0 means forever.
|
|
//
|
|
// runtime_futexwakeup(uint32 *addr, uint32 cnt)
|
|
// If any procs are sleeping on addr, wake up at most cnt.
|
|
|
|
enum
|
|
{
|
|
MUTEX_UNLOCKED = 0,
|
|
MUTEX_LOCKED = 1,
|
|
MUTEX_SLEEPING = 2,
|
|
|
|
ACTIVE_SPIN = 4,
|
|
ACTIVE_SPIN_CNT = 30,
|
|
PASSIVE_SPIN = 1,
|
|
};
|
|
|
|
// Possible lock states are MUTEX_UNLOCKED, MUTEX_LOCKED and MUTEX_SLEEPING.
|
|
// MUTEX_SLEEPING means that there is presumably at least one sleeping thread.
|
|
// Note that there can be spinning threads during all states - they do not
|
|
// affect mutex's state.
|
|
void
|
|
runtime_lock(Lock *l)
|
|
{
|
|
uint32 i, v, wait, spin;
|
|
|
|
if(runtime_m()->locks++ < 0)
|
|
runtime_throw("runtime_lock: lock count");
|
|
|
|
// Speculative grab for lock.
|
|
v = runtime_xchg((uint32*)&l->key, MUTEX_LOCKED);
|
|
if(v == MUTEX_UNLOCKED)
|
|
return;
|
|
|
|
// wait is either MUTEX_LOCKED or MUTEX_SLEEPING
|
|
// depending on whether there is a thread sleeping
|
|
// on this mutex. If we ever change l->key from
|
|
// MUTEX_SLEEPING to some other value, we must be
|
|
// careful to change it back to MUTEX_SLEEPING before
|
|
// returning, to ensure that the sleeping thread gets
|
|
// its wakeup call.
|
|
wait = v;
|
|
|
|
// On uniprocessor's, no point spinning.
|
|
// On multiprocessors, spin for ACTIVE_SPIN attempts.
|
|
spin = 0;
|
|
if(runtime_ncpu > 1)
|
|
spin = ACTIVE_SPIN;
|
|
|
|
for(;;) {
|
|
// Try for lock, spinning.
|
|
for(i = 0; i < spin; i++) {
|
|
while(l->key == MUTEX_UNLOCKED)
|
|
if(runtime_cas((uint32*)&l->key, MUTEX_UNLOCKED, wait))
|
|
return;
|
|
runtime_procyield(ACTIVE_SPIN_CNT);
|
|
}
|
|
|
|
// Try for lock, rescheduling.
|
|
for(i=0; i < PASSIVE_SPIN; i++) {
|
|
while(l->key == MUTEX_UNLOCKED)
|
|
if(runtime_cas((uint32*)&l->key, MUTEX_UNLOCKED, wait))
|
|
return;
|
|
runtime_osyield();
|
|
}
|
|
|
|
// Sleep.
|
|
v = runtime_xchg((uint32*)&l->key, MUTEX_SLEEPING);
|
|
if(v == MUTEX_UNLOCKED)
|
|
return;
|
|
wait = MUTEX_SLEEPING;
|
|
runtime_futexsleep((uint32*)&l->key, MUTEX_SLEEPING, -1);
|
|
}
|
|
}
|
|
|
|
void
|
|
runtime_unlock(Lock *l)
|
|
{
|
|
uint32 v;
|
|
|
|
v = runtime_xchg((uint32*)&l->key, MUTEX_UNLOCKED);
|
|
if(v == MUTEX_UNLOCKED)
|
|
runtime_throw("unlock of unlocked lock");
|
|
if(v == MUTEX_SLEEPING)
|
|
runtime_futexwakeup((uint32*)&l->key, 1);
|
|
|
|
if(--runtime_m()->locks < 0)
|
|
runtime_throw("runtime_unlock: lock count");
|
|
}
|
|
|
|
// One-time notifications.
|
|
void
|
|
runtime_noteclear(Note *n)
|
|
{
|
|
n->key = 0;
|
|
}
|
|
|
|
void
|
|
runtime_notewakeup(Note *n)
|
|
{
|
|
uint32 old;
|
|
|
|
old = runtime_xchg((uint32*)&n->key, 1);
|
|
if(old != 0) {
|
|
runtime_printf("notewakeup - double wakeup (%d)\n", old);
|
|
runtime_throw("notewakeup - double wakeup");
|
|
}
|
|
runtime_futexwakeup((uint32*)&n->key, 1);
|
|
}
|
|
|
|
void
|
|
runtime_notesleep(Note *n)
|
|
{
|
|
M *m = runtime_m();
|
|
|
|
/* For gccgo it's OK to sleep in non-g0, and it happens in
|
|
stoptheworld because we have not implemented preemption.
|
|
|
|
if(runtime_g() != runtime_m()->g0)
|
|
runtime_throw("notesleep not on g0");
|
|
*/
|
|
while(runtime_atomicload((uint32*)&n->key) == 0) {
|
|
m->blocked = true;
|
|
runtime_futexsleep((uint32*)&n->key, 0, -1);
|
|
m->blocked = false;
|
|
}
|
|
}
|
|
|
|
static bool
|
|
notetsleep(Note *n, int64 ns, int64 deadline, int64 now)
|
|
{
|
|
M *m = runtime_m();
|
|
|
|
// Conceptually, deadline and now are local variables.
|
|
// They are passed as arguments so that the space for them
|
|
// does not count against our nosplit stack sequence.
|
|
|
|
if(ns < 0) {
|
|
while(runtime_atomicload((uint32*)&n->key) == 0) {
|
|
m->blocked = true;
|
|
runtime_futexsleep((uint32*)&n->key, 0, -1);
|
|
m->blocked = false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if(runtime_atomicload((uint32*)&n->key) != 0)
|
|
return true;
|
|
|
|
deadline = runtime_nanotime() + ns;
|
|
for(;;) {
|
|
m->blocked = true;
|
|
runtime_futexsleep((uint32*)&n->key, 0, ns);
|
|
m->blocked = false;
|
|
if(runtime_atomicload((uint32*)&n->key) != 0)
|
|
break;
|
|
now = runtime_nanotime();
|
|
if(now >= deadline)
|
|
break;
|
|
ns = deadline - now;
|
|
}
|
|
return runtime_atomicload((uint32*)&n->key) != 0;
|
|
}
|
|
|
|
bool
|
|
runtime_notetsleep(Note *n, int64 ns)
|
|
{
|
|
bool res;
|
|
|
|
if(runtime_g() != runtime_m()->g0 && !runtime_m()->gcing)
|
|
runtime_throw("notetsleep not on g0");
|
|
|
|
res = notetsleep(n, ns, 0, 0);
|
|
return res;
|
|
}
|
|
|
|
// same as runtime_notetsleep, but called on user g (not g0)
|
|
// calls only nosplit functions between entersyscallblock/exitsyscall
|
|
bool
|
|
runtime_notetsleepg(Note *n, int64 ns)
|
|
{
|
|
bool res;
|
|
|
|
if(runtime_g() == runtime_m()->g0)
|
|
runtime_throw("notetsleepg on g0");
|
|
|
|
runtime_entersyscallblock();
|
|
res = notetsleep(n, ns, 0, 0);
|
|
runtime_exitsyscall();
|
|
return res;
|
|
}
|