2022-09-11 18:02:07 +00:00
|
|
|
/*-*- mode:c;indent-tabs-mode:t;c-basic-offset:8;tab-width:8;coding:utf-8 -*-│
|
|
|
|
│ vi: set noet ft=c ts=8 sw=8 fenc=utf-8 :vi │
|
|
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
|
|
│ Copyright 2016 Google Inc. │
|
|
|
|
│ │
|
|
|
|
│ Licensed under the Apache License, Version 2.0 (the "License"); │
|
|
|
|
│ you may not use this file except in compliance with the License. │
|
|
|
|
│ You may obtain a copy of the License at │
|
|
|
|
│ │
|
|
|
|
│ http://www.apache.org/licenses/LICENSE-2.0 │
|
|
|
|
│ │
|
|
|
|
│ Unless required by applicable law or agreed to in writing, software │
|
|
|
|
│ distributed under the License is distributed on an "AS IS" BASIS, │
|
|
|
|
│ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. │
|
|
|
|
│ See the License for the specific language governing permissions and │
|
|
|
|
│ limitations under the License. │
|
|
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
2022-11-05 01:19:05 +00:00
|
|
|
#include "libc/calls/blockcancel.internal.h"
|
2023-07-06 13:57:28 +00:00
|
|
|
#include "libc/intrin/dll.h"
|
2022-09-11 18:02:07 +00:00
|
|
|
#include "libc/str/str.h"
|
|
|
|
#include "third_party/nsync/atomic.h"
|
|
|
|
#include "third_party/nsync/common.internal.h"
|
|
|
|
#include "third_party/nsync/mu_semaphore.h"
|
2022-09-12 01:53:52 +00:00
|
|
|
#include "third_party/nsync/races.internal.h"
|
2024-07-06 06:13:20 +00:00
|
|
|
#include "libc/thread/thread.h"
|
2022-09-11 18:02:07 +00:00
|
|
|
#include "third_party/nsync/wait_s.internal.h"
|
Release Cosmopolitan v3.3
This change upgrades to GCC 12.3 and GNU binutils 2.42. The GNU linker
appears to have changed things so that only a single de-duplicated str
table is present in the binary, and it gets placed wherever the linker
wants, regardless of what the linker script says. To cope with that we
need to stop using .ident to embed licenses. As such, this change does
significant work to revamp how third party licenses are defined in the
codebase, using `.section .notice,"aR",@progbits`.
This new GCC 12.3 toolchain has support for GNU indirect functions. It
lets us support __target_clones__ for the first time. This is used for
optimizing the performance of libc string functions such as strlen and
friends so far on x86, by ensuring AVX systems favor a second codepath
that uses VEX encoding. It shaves some latency off certain operations.
It's a useful feature to have for scientific computing for the reasons
explained by the test/libcxx/openmp_test.cc example which compiles for
fifteen different microarchitectures. Thanks to the upgrades, it's now
also possible to use newer instruction sets, such as AVX512FP16, VNNI.
Cosmo now uses the %gs register on x86 by default for TLS. Doing it is
helpful for any program that links `cosmo_dlopen()`. Such programs had
to recompile their binaries at startup to change the TLS instructions.
That's not great, since it means every page in the executable needs to
be faulted. The work of rewriting TLS-related x86 opcodes, is moved to
fixupobj.com instead. This is great news for MacOS x86 users, since we
previously needed to morph the binary every time for that platform but
now that's no longer necessary. The only platforms where we need fixup
of TLS x86 opcodes at runtime are now Windows, OpenBSD, and NetBSD. On
Windows we morph TLS to point deeper into the TIB, based on a TlsAlloc
assignment, and on OpenBSD/NetBSD we morph %gs back into %fs since the
kernels do not allow us to specify a value for the %gs register.
OpenBSD users are now required to use APE Loader to run Cosmo binaries
and assimilation is no longer possible. OpenBSD kernel needs to change
to allow programs to specify a value for the %gs register, or it needs
to stop marking executable pages loaded by the kernel as mimmutable().
This release fixes __constructor__, .ctor, .init_array, and lastly the
.preinit_array so they behave the exact same way as glibc.
We no longer use hex constants to define math.h symbols like M_PI.
2024-02-20 19:12:09 +00:00
|
|
|
__static_yoink("nsync_notice");
|
2022-09-10 23:11:26 +00:00
|
|
|
|
|
|
|
/* Initialize *mu. */
|
|
|
|
void nsync_mu_init (nsync_mu *mu) {
|
2023-07-06 13:57:28 +00:00
|
|
|
bzero ((void *) mu, sizeof (*mu));
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Release the mutex spinlock. */
|
|
|
|
static void mu_release_spinlock (nsync_mu *mu) {
|
|
|
|
uint32_t old_word = ATM_LOAD (&mu->word);
|
|
|
|
while (!ATM_CAS_REL (&mu->word, old_word, old_word & ~MU_SPINLOCK)) {
|
|
|
|
old_word = ATM_LOAD (&mu->word);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Lock *mu using the specified lock_type, waiting on *w if necessary.
|
|
|
|
"clear" should be zero if the thread has not previously slept on *mu, and
|
|
|
|
MU_DESIG_WAKER if it has; this represents bits that nsync_mu_lock_slow_() must clear when
|
|
|
|
it either acquires or sleeps on *mu. The caller owns *w on return; it is in a valid
|
|
|
|
state to be returned to the free pool. */
|
|
|
|
void nsync_mu_lock_slow_ (nsync_mu *mu, waiter *w, uint32_t clear, lock_type *l_type) {
|
|
|
|
uint32_t zero_to_acquire;
|
|
|
|
uint32_t wait_count;
|
|
|
|
uint32_t long_wait;
|
|
|
|
unsigned attempts = 0; /* attempt count; used for spinloop backoff */
|
Make improvements
- We now serialize the file descriptor table when spawning / executing
processes on Windows. This means you can now inherit more stuff than
just standard i/o. It's needed by bash, which duplicates the console
to file descriptor #255. We also now do a better job serializing the
environment variables, so you're less likely to encounter E2BIG when
using your bash shell. We also no longer coerce environ to uppercase
- execve() on Windows now remotely controls its parent process to make
them spawn a replacement for itself. Then it'll be able to terminate
immediately once the spawn succeeds, without having to linger around
for the lifetime as a shell process for proxying the exit code. When
process worker thread running in the parent sees the child die, it's
given a handle to the new child, to replace it in the process table.
- execve() and posix_spawn() on Windows will now provide CreateProcess
an explicit handle list. This allows us to remove handle locks which
enables better fork/spawn concurrency, with seriously correct thread
safety. Other codebases like Go use the same technique. On the other
hand fork() still favors the conventional WIN32 inheritence approach
which can be a little bit messy, but is *controlled* by guaranteeing
perfectly clean slates at both the spawning and execution boundaries
- sigset_t is now 64 bits. Having it be 128 bits was a mistake because
there's no reason to use that and it's only supported by FreeBSD. By
using the system word size, signal mask manipulation on Windows goes
very fast. Furthermore @asyncsignalsafe funcs have been rewritten on
Windows to take advantage of signal masking, now that it's much more
pleasant to use.
- All the overlapped i/o code on Windows has been rewritten for pretty
good signal and cancelation safety. We're now able to ensure overlap
data structures are cleaned up so long as you don't longjmp() out of
out of a signal handler that interrupted an i/o operation. Latencies
are also improved thanks to the removal of lots of "busy wait" code.
Waits should be optimal for everything except poll(), which shall be
the last and final demon we slay in the win32 i/o horror show.
- getrusage() on Windows is now able to report RUSAGE_CHILDREN as well
as RUSAGE_SELF, thanks to aggregation in the process manager thread.
2023-10-08 12:36:18 +00:00
|
|
|
BLOCK_CANCELATION;
|
2022-09-10 23:11:26 +00:00
|
|
|
w->cv_mu = NULL; /* not a cv wait */
|
|
|
|
w->cond.f = NULL; /* Not using a conditional critical section. */
|
|
|
|
w->cond.v = NULL;
|
|
|
|
w->cond.eq = NULL;
|
|
|
|
w->l_type = l_type;
|
|
|
|
zero_to_acquire = l_type->zero_to_acquire;
|
|
|
|
if (clear != 0) {
|
|
|
|
/* Only the constraints of mutual exclusion should stop a designated waker. */
|
|
|
|
zero_to_acquire &= ~(MU_WRITER_WAITING | MU_LONG_WAIT);
|
|
|
|
}
|
|
|
|
wait_count = 0; /* number of times we waited, and were woken. */
|
|
|
|
long_wait = 0; /* set to MU_LONG_WAIT when wait_count gets large */
|
|
|
|
for (;;) {
|
|
|
|
uint32_t old_word = ATM_LOAD (&mu->word);
|
|
|
|
if ((old_word & zero_to_acquire) == 0) {
|
|
|
|
/* lock can be acquired; try to acquire, possibly
|
|
|
|
clearing MU_DESIG_WAKER and MU_LONG_WAIT. */
|
|
|
|
if (ATM_CAS_ACQ (&mu->word, old_word,
|
|
|
|
(old_word+l_type->add_to_acquire) &
|
|
|
|
~(clear|long_wait|l_type->clear_on_acquire))) {
|
2022-11-05 01:19:05 +00:00
|
|
|
break;
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
} else if ((old_word&MU_SPINLOCK) == 0 &&
|
|
|
|
ATM_CAS_ACQ (&mu->word, old_word,
|
|
|
|
(old_word|MU_SPINLOCK|long_wait|
|
|
|
|
l_type->set_when_waiting) & ~(clear | MU_ALL_FALSE))) {
|
|
|
|
|
|
|
|
/* Spinlock is now held, and lock is held by someone
|
|
|
|
else; MU_WAITING has also been set; queue ourselves.
|
|
|
|
There's no need to adjust same_condition here,
|
|
|
|
because w.condition==NULL. */
|
|
|
|
ATM_STORE (&w->nw.waiting, 1);
|
|
|
|
if (wait_count == 0) {
|
|
|
|
/* first wait goes to end of queue */
|
2023-07-06 13:57:28 +00:00
|
|
|
dll_make_last (&mu->waiters, &w->nw.q);
|
2022-09-10 23:11:26 +00:00
|
|
|
} else {
|
|
|
|
/* subsequent waits go to front of queue */
|
2023-07-06 13:57:28 +00:00
|
|
|
dll_make_first (&mu->waiters, &w->nw.q);
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Release spinlock. Cannot use a store here, because
|
|
|
|
the current thread does not hold the mutex. If
|
|
|
|
another thread were a designated waker, the mutex
|
|
|
|
holder could be concurrently unlocking, even though
|
|
|
|
we hold the spinlock. */
|
|
|
|
mu_release_spinlock (mu);
|
|
|
|
|
|
|
|
/* wait until awoken. */
|
|
|
|
while (ATM_LOAD_ACQ (&w->nw.waiting) != 0) { /* acquire load */
|
2023-09-12 04:34:53 +00:00
|
|
|
/* This can only return 0 or ECANCELED. */
|
|
|
|
ASSERT (nsync_mu_semaphore_p (&w->sem) == 0);
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
wait_count++;
|
|
|
|
/* If the thread has been woken more than this many
|
|
|
|
times, and still not acquired, it sets the
|
|
|
|
MU_LONG_WAIT bit to prevent thread that have not
|
|
|
|
waited from acquiring. This is the starvation
|
|
|
|
avoidance mechanism. The number is fairly high so
|
|
|
|
that we continue to benefit from the throughput of
|
|
|
|
not having running threads wait unless absolutely
|
|
|
|
necessary. */
|
|
|
|
if (wait_count == LONG_WAIT_THRESHOLD) { /* repeatedly woken */
|
|
|
|
long_wait = MU_LONG_WAIT; /* force others to wait at least once */
|
|
|
|
}
|
|
|
|
|
|
|
|
attempts = 0;
|
|
|
|
clear = MU_DESIG_WAKER;
|
|
|
|
/* Threads that have been woken at least once don't care
|
|
|
|
about waiting writers or long waiters. */
|
|
|
|
zero_to_acquire &= ~(MU_WRITER_WAITING | MU_LONG_WAIT);
|
|
|
|
}
|
2024-07-06 06:13:20 +00:00
|
|
|
attempts = pthread_delay_np (mu, attempts);
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
Make improvements
- We now serialize the file descriptor table when spawning / executing
processes on Windows. This means you can now inherit more stuff than
just standard i/o. It's needed by bash, which duplicates the console
to file descriptor #255. We also now do a better job serializing the
environment variables, so you're less likely to encounter E2BIG when
using your bash shell. We also no longer coerce environ to uppercase
- execve() on Windows now remotely controls its parent process to make
them spawn a replacement for itself. Then it'll be able to terminate
immediately once the spawn succeeds, without having to linger around
for the lifetime as a shell process for proxying the exit code. When
process worker thread running in the parent sees the child die, it's
given a handle to the new child, to replace it in the process table.
- execve() and posix_spawn() on Windows will now provide CreateProcess
an explicit handle list. This allows us to remove handle locks which
enables better fork/spawn concurrency, with seriously correct thread
safety. Other codebases like Go use the same technique. On the other
hand fork() still favors the conventional WIN32 inheritence approach
which can be a little bit messy, but is *controlled* by guaranteeing
perfectly clean slates at both the spawning and execution boundaries
- sigset_t is now 64 bits. Having it be 128 bits was a mistake because
there's no reason to use that and it's only supported by FreeBSD. By
using the system word size, signal mask manipulation on Windows goes
very fast. Furthermore @asyncsignalsafe funcs have been rewritten on
Windows to take advantage of signal masking, now that it's much more
pleasant to use.
- All the overlapped i/o code on Windows has been rewritten for pretty
good signal and cancelation safety. We're now able to ensure overlap
data structures are cleaned up so long as you don't longjmp() out of
out of a signal handler that interrupted an i/o operation. Latencies
are also improved thanks to the removal of lots of "busy wait" code.
Waits should be optimal for everything except poll(), which shall be
the last and final demon we slay in the win32 i/o horror show.
- getrusage() on Windows is now able to report RUSAGE_CHILDREN as well
as RUSAGE_SELF, thanks to aggregation in the process manager thread.
2023-10-08 12:36:18 +00:00
|
|
|
ALLOW_CANCELATION;
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Attempt to acquire *mu in writer mode without blocking, and return non-zero
|
|
|
|
iff successful. Return non-zero with high probability if *mu was free on
|
|
|
|
entry. */
|
|
|
|
int nsync_mu_trylock (nsync_mu *mu) {
|
|
|
|
int result;
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
if (ATM_CAS_ACQ (&mu->word, 0, MU_WADD_TO_ACQUIRE)) { /* acquire CAS */
|
|
|
|
result = 1;
|
|
|
|
} else {
|
|
|
|
uint32_t old_word = ATM_LOAD (&mu->word);
|
|
|
|
result = ((old_word & MU_WZERO_TO_ACQUIRE) == 0 &&
|
|
|
|
ATM_CAS_ACQ (&mu->word, old_word,
|
|
|
|
(old_word + MU_WADD_TO_ACQUIRE) & ~MU_WCLEAR_ON_ACQUIRE));
|
|
|
|
}
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
return (result);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Block until *mu is free and then acquire it in writer mode. */
|
|
|
|
void nsync_mu_lock (nsync_mu *mu) {
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
if (!ATM_CAS_ACQ (&mu->word, 0, MU_WADD_TO_ACQUIRE)) { /* acquire CAS */
|
|
|
|
uint32_t old_word = ATM_LOAD (&mu->word);
|
|
|
|
if ((old_word&MU_WZERO_TO_ACQUIRE) != 0 ||
|
|
|
|
!ATM_CAS_ACQ (&mu->word, old_word,
|
|
|
|
(old_word+MU_WADD_TO_ACQUIRE) & ~MU_WCLEAR_ON_ACQUIRE)) {
|
2023-11-10 09:42:06 +00:00
|
|
|
waiter *w = nsync_waiter_new_ ();
|
|
|
|
nsync_mu_lock_slow_ (mu, w, 0, nsync_writer_type_);
|
|
|
|
nsync_waiter_free_ (w);
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Attempt to acquire *mu in reader mode without blocking, and return non-zero
|
|
|
|
iff successful. Returns non-zero with high probability if *mu was free on
|
|
|
|
entry. It may fail to acquire if a writer is waiting, to avoid starvation.
|
|
|
|
*/
|
|
|
|
int nsync_mu_rtrylock (nsync_mu *mu) {
|
|
|
|
int result;
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
if (ATM_CAS_ACQ (&mu->word, 0, MU_RADD_TO_ACQUIRE)) { /* acquire CAS */
|
|
|
|
result = 1;
|
|
|
|
} else {
|
|
|
|
uint32_t old_word = ATM_LOAD (&mu->word);
|
|
|
|
result = ((old_word&MU_RZERO_TO_ACQUIRE) == 0 &&
|
|
|
|
ATM_CAS_ACQ (&mu->word, old_word,
|
|
|
|
(old_word+MU_RADD_TO_ACQUIRE) & ~MU_RCLEAR_ON_ACQUIRE));
|
|
|
|
}
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
return (result);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Block until *mu can be acquired in reader mode and then acquire it. */
|
|
|
|
void nsync_mu_rlock (nsync_mu *mu) {
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
if (!ATM_CAS_ACQ (&mu->word, 0, MU_RADD_TO_ACQUIRE)) { /* acquire CAS */
|
|
|
|
uint32_t old_word = ATM_LOAD (&mu->word);
|
|
|
|
if ((old_word&MU_RZERO_TO_ACQUIRE) != 0 ||
|
|
|
|
!ATM_CAS_ACQ (&mu->word, old_word,
|
|
|
|
(old_word+MU_RADD_TO_ACQUIRE) & ~MU_RCLEAR_ON_ACQUIRE)) {
|
2023-11-10 09:42:06 +00:00
|
|
|
waiter *w = nsync_waiter_new_ ();
|
|
|
|
nsync_mu_lock_slow_ (mu, w, 0, nsync_reader_type_);
|
|
|
|
nsync_waiter_free_ (w);
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Invoke the condition associated with *p, which is an element of
|
|
|
|
a "waiter" list. */
|
2023-07-06 13:57:28 +00:00
|
|
|
static int condition_true (struct Dll *p) {
|
2022-09-10 23:11:26 +00:00
|
|
|
return ((*DLL_WAITER (p)->cond.f) (DLL_WAITER (p)->cond.v));
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If *p is an element of waiter_list (a list of "waiter" structs(, return a
|
|
|
|
pointer to the next element of the list that has a different condition. */
|
2023-07-06 13:57:28 +00:00
|
|
|
static struct Dll *skip_past_same_condition (
|
|
|
|
struct Dll *waiter_list, struct Dll *p) {
|
|
|
|
struct Dll *next;
|
|
|
|
struct Dll *last_with_same_condition =
|
2022-09-10 23:11:26 +00:00
|
|
|
&DLL_WAITER_SAMECOND (DLL_WAITER (p)->same_condition.prev)->nw.q;
|
|
|
|
if (last_with_same_condition != p && last_with_same_condition != p->prev) {
|
|
|
|
/* First in set with same condition, so skip to end. */
|
2023-07-06 13:57:28 +00:00
|
|
|
next = dll_next (waiter_list, last_with_same_condition);
|
2022-09-10 23:11:26 +00:00
|
|
|
} else {
|
2023-07-06 13:57:28 +00:00
|
|
|
next = dll_next (waiter_list, p);
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
return (next);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Merge the same_condition lists of *p and *n if they have the same non-NULL
|
|
|
|
condition. */
|
2023-07-06 13:57:28 +00:00
|
|
|
void nsync_maybe_merge_conditions_ (struct Dll *p, struct Dll *n) {
|
2022-09-10 23:11:26 +00:00
|
|
|
if (p != NULL && n != NULL &&
|
|
|
|
WAIT_CONDITION_EQ (&DLL_WAITER (p)->cond, &DLL_WAITER (n)->cond)) {
|
2023-07-06 13:57:28 +00:00
|
|
|
dll_splice_after (&DLL_WAITER (p)->same_condition,
|
2022-09-10 23:11:26 +00:00
|
|
|
&DLL_WAITER (n)->same_condition);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Remove element *e from nsync_mu waiter queue mu_queue, fixing
|
|
|
|
up the same_condition list by merging the lists on either side if possible.
|
|
|
|
Also increment the waiter's remove_count. */
|
2023-07-06 13:57:28 +00:00
|
|
|
struct Dll *nsync_remove_from_mu_queue_ (struct Dll *mu_queue, struct Dll *e) {
|
2022-09-10 23:11:26 +00:00
|
|
|
/* Record previous and next elements in the original queue. */
|
2023-07-06 13:57:28 +00:00
|
|
|
struct Dll *prev = e->prev;
|
|
|
|
struct Dll *next = e->next;
|
2022-09-10 23:11:26 +00:00
|
|
|
uint32_t old_value;
|
|
|
|
/* Remove. */
|
2023-07-06 13:57:28 +00:00
|
|
|
dll_remove (&mu_queue, e);
|
2022-09-10 23:11:26 +00:00
|
|
|
do {
|
|
|
|
old_value = ATM_LOAD (&DLL_WAITER (e)->remove_count);
|
|
|
|
} while (!ATM_CAS (&DLL_WAITER (e)->remove_count, old_value, old_value+1));
|
2023-07-06 13:57:28 +00:00
|
|
|
if (!dll_is_empty (mu_queue)) {
|
2022-09-10 23:11:26 +00:00
|
|
|
/* Fix up same_condition. */
|
2023-07-06 13:57:28 +00:00
|
|
|
struct Dll *e_same_condition = &DLL_WAITER (e)->same_condition;
|
2022-09-10 23:11:26 +00:00
|
|
|
|
|
|
|
if (e_same_condition->next != e_same_condition) {
|
|
|
|
/* *e is linked to a same_condition neighbour---just remove it. */
|
|
|
|
e_same_condition->next->prev = e_same_condition->prev;
|
|
|
|
e_same_condition->prev->next = e_same_condition->next;
|
|
|
|
e_same_condition->next = e_same_condition;
|
|
|
|
e_same_condition->prev = e_same_condition;
|
2023-07-06 13:57:28 +00:00
|
|
|
} else if (prev != dll_last (mu_queue)) {
|
2022-09-10 23:11:26 +00:00
|
|
|
/* Merge the new neighbours together if we can. */
|
|
|
|
nsync_maybe_merge_conditions_ (prev, next);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return (mu_queue);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Unlock *mu and wake one or more waiters as appropriate after an unlock.
|
|
|
|
It is called with *mu held in mode l_type. */
|
|
|
|
void nsync_mu_unlock_slow_ (nsync_mu *mu, lock_type *l_type) {
|
|
|
|
unsigned attempts = 0; /* attempt count; used for backoff */
|
|
|
|
for (;;) {
|
|
|
|
uint32_t old_word = ATM_LOAD (&mu->word);
|
|
|
|
int testing_conditions = ((old_word & MU_CONDITION) != 0);
|
|
|
|
uint32_t early_release_mu = l_type->add_to_acquire;
|
|
|
|
uint32_t late_release_mu = 0;
|
|
|
|
if (testing_conditions) {
|
|
|
|
/* Convert to a writer lock, and release later.
|
|
|
|
- A writer lock is currently needed to test conditions
|
|
|
|
because exclusive access is needed to the list to
|
|
|
|
allow modification. The spinlock cannot be used
|
|
|
|
to achieve that, because an internal lock should not
|
|
|
|
be held when calling the external predicates.
|
|
|
|
- We must test conditions even though a reader region
|
|
|
|
cannot have made any new ones true because some
|
|
|
|
might have been true before the reader region started.
|
|
|
|
The MU_ALL_FALSE test below shortcuts the case where
|
|
|
|
the conditions are known all to be false. */
|
|
|
|
early_release_mu = l_type->add_to_acquire - MU_WLOCK;
|
|
|
|
late_release_mu = MU_WLOCK;
|
|
|
|
}
|
|
|
|
if ((old_word&MU_WAITING) == 0 || (old_word&MU_DESIG_WAKER) != 0 ||
|
|
|
|
(old_word & MU_RLOCK_FIELD) > MU_RLOCK ||
|
|
|
|
(old_word & (MU_RLOCK|MU_ALL_FALSE)) == (MU_RLOCK|MU_ALL_FALSE)) {
|
|
|
|
/* no one to wake, there's a designated waker waking
|
|
|
|
up, there are still readers, or it's a reader and all waiters
|
|
|
|
have false conditions */
|
|
|
|
if (ATM_CAS_REL (&mu->word, old_word,
|
|
|
|
(old_word - l_type->add_to_acquire) &
|
|
|
|
~l_type->clear_on_uncontended_release)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if ((old_word&MU_SPINLOCK) == 0 &&
|
2023-11-13 18:57:02 +00:00
|
|
|
ATM_CAS_SEQCST (&mu->word, old_word, /* [jart] fixes issues on apple silicon */
|
|
|
|
(old_word-early_release_mu)|MU_SPINLOCK|MU_DESIG_WAKER)) {
|
2023-07-06 13:57:28 +00:00
|
|
|
struct Dll *wake;
|
2022-09-10 23:11:26 +00:00
|
|
|
lock_type *wake_type;
|
|
|
|
uint32_t clear_on_release;
|
|
|
|
uint32_t set_on_release;
|
|
|
|
/* The spinlock is now held, and we've set the
|
|
|
|
designated wake flag, since we're likely to wake a
|
|
|
|
thread that will become that designated waker. If
|
|
|
|
there are conditions to check, the mutex itself is
|
|
|
|
still held. */
|
|
|
|
|
2023-07-06 13:57:28 +00:00
|
|
|
struct Dll *p = NULL;
|
|
|
|
struct Dll *next = NULL;
|
2022-09-10 23:11:26 +00:00
|
|
|
|
|
|
|
/* Swap the entire mu->waiters list into the local
|
|
|
|
"new_waiters" list. This gives us exclusive access
|
|
|
|
to the list, even if we unlock the spinlock, which
|
|
|
|
we may do if checking conditions. The loop below
|
|
|
|
will grab more new waiters that arrived while we
|
|
|
|
were checking conditions, and terminates only if no
|
|
|
|
new waiters arrive in one loop iteration. */
|
2023-07-06 13:57:28 +00:00
|
|
|
struct Dll *waiters = NULL;
|
|
|
|
struct Dll *new_waiters = mu->waiters;
|
2022-09-10 23:11:26 +00:00
|
|
|
mu->waiters = NULL;
|
|
|
|
|
|
|
|
/* Remove a waiter from the queue, if possible. */
|
|
|
|
wake = NULL; /* waiters to wake. */
|
|
|
|
wake_type = NULL; /* type of waiter(s) on wake, or NULL if wake is empty. */
|
|
|
|
clear_on_release = MU_SPINLOCK;
|
|
|
|
set_on_release = MU_ALL_FALSE;
|
2023-07-06 13:57:28 +00:00
|
|
|
while (!dll_is_empty (new_waiters)) { /* some new waiters to consider */
|
|
|
|
p = dll_first (new_waiters);
|
2022-09-10 23:11:26 +00:00
|
|
|
if (testing_conditions) {
|
|
|
|
/* Should we continue to test conditions? */
|
|
|
|
if (wake_type == nsync_writer_type_) {
|
|
|
|
/* No, because we're already waking a writer,
|
|
|
|
and need wake no others.*/
|
|
|
|
testing_conditions = 0;
|
|
|
|
} else if (wake_type == NULL &&
|
|
|
|
DLL_WAITER (p)->l_type != nsync_reader_type_ &&
|
|
|
|
DLL_WAITER (p)->cond.f == NULL) {
|
|
|
|
/* No, because we've woken no one, but the
|
|
|
|
first waiter is a writer with no condition,
|
|
|
|
so we will certainly wake it, and need wake
|
|
|
|
no others. */
|
|
|
|
testing_conditions = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* If testing waiters' conditions, release the
|
|
|
|
spinlock while still holding the write lock.
|
|
|
|
This is so that the spinlock is not held
|
|
|
|
while the conditions are evaluated. */
|
|
|
|
if (testing_conditions) {
|
|
|
|
mu_release_spinlock (mu);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Process the new waiters picked up in this iteration of the
|
2023-07-06 13:57:28 +00:00
|
|
|
"while (!dll_is_empty (new_waiters))" loop,
|
2022-09-10 23:11:26 +00:00
|
|
|
and stop looking when we run out of waiters, or we find
|
|
|
|
a writer to wake up. */
|
|
|
|
while (p != NULL && wake_type != nsync_writer_type_) {
|
|
|
|
int p_has_condition;
|
2023-07-06 13:57:28 +00:00
|
|
|
next = dll_next (new_waiters, p);
|
2022-09-10 23:11:26 +00:00
|
|
|
p_has_condition = (DLL_WAITER (p)->cond.f != NULL);
|
|
|
|
if (p_has_condition && !testing_conditions) {
|
|
|
|
nsync_panic_ ("checking a waiter condition "
|
|
|
|
"while unlocked\n");
|
|
|
|
}
|
|
|
|
if (p_has_condition && !condition_true (p)) {
|
|
|
|
/* condition is false */
|
|
|
|
/* skip to the end of the same_condition group. */
|
|
|
|
next = skip_past_same_condition (new_waiters, p);
|
|
|
|
} else if (wake_type == NULL ||
|
|
|
|
DLL_WAITER (p)->l_type == nsync_reader_type_) {
|
|
|
|
/* Wake this thread. */
|
|
|
|
new_waiters = nsync_remove_from_mu_queue_ (
|
|
|
|
new_waiters, p);
|
2023-07-06 13:57:28 +00:00
|
|
|
dll_make_last (&wake, p);
|
2022-09-10 23:11:26 +00:00
|
|
|
wake_type = DLL_WAITER (p)->l_type;
|
|
|
|
} else {
|
|
|
|
/* Failing to wake a writer
|
|
|
|
that could acquire if it
|
|
|
|
were first. */
|
|
|
|
set_on_release |= MU_WRITER_WAITING;
|
|
|
|
set_on_release &= ~MU_ALL_FALSE;
|
|
|
|
}
|
|
|
|
p = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (p != NULL) {
|
|
|
|
/* Didn't search to end of list, so can't be sure
|
|
|
|
all conditions are false. */
|
|
|
|
set_on_release &= ~MU_ALL_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* If testing waiters' conditions, reacquire the spinlock
|
|
|
|
released above. */
|
|
|
|
if (testing_conditions) {
|
|
|
|
nsync_spin_test_and_set_ (&mu->word, MU_SPINLOCK,
|
2024-07-06 06:13:20 +00:00
|
|
|
MU_SPINLOCK, 0, mu);
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* add the new_waiters to the last of the waiters. */
|
2023-07-06 13:57:28 +00:00
|
|
|
nsync_maybe_merge_conditions_ (dll_last (waiters),
|
|
|
|
dll_first (new_waiters));
|
|
|
|
dll_make_last (&waiters, dll_last (new_waiters));
|
2022-09-10 23:11:26 +00:00
|
|
|
/* Pick up the next set of new waiters. */
|
|
|
|
new_waiters = mu->waiters;
|
|
|
|
mu->waiters = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return the local waiter list to *mu. */
|
|
|
|
mu->waiters = waiters;
|
|
|
|
|
2023-07-06 13:57:28 +00:00
|
|
|
if (dll_is_empty (wake)) {
|
2022-09-10 23:11:26 +00:00
|
|
|
/* not waking a waiter => no designated waker */
|
|
|
|
clear_on_release |= MU_DESIG_WAKER;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((set_on_release & MU_ALL_FALSE) == 0) {
|
|
|
|
/* If not explicitly setting MU_ALL_FALSE, clear it. */
|
|
|
|
clear_on_release |= MU_ALL_FALSE;
|
|
|
|
}
|
|
|
|
|
2023-07-06 13:57:28 +00:00
|
|
|
if (dll_is_empty (mu->waiters)) {
|
2022-09-10 23:11:26 +00:00
|
|
|
/* no waiters left */
|
|
|
|
clear_on_release |= MU_WAITING | MU_WRITER_WAITING |
|
|
|
|
MU_CONDITION | MU_ALL_FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Release the spinlock, and possibly the lock if
|
|
|
|
late_release_mu is non-zero. Other bits are set or
|
|
|
|
cleared according to whether we woke any threads,
|
|
|
|
whether any waiters remain, and whether any of them
|
|
|
|
are writers. */
|
|
|
|
old_word = ATM_LOAD (&mu->word);
|
|
|
|
while (!ATM_CAS_REL (&mu->word, old_word,
|
|
|
|
((old_word-late_release_mu)|set_on_release) &
|
|
|
|
~clear_on_release)) { /* release CAS */
|
|
|
|
old_word = ATM_LOAD (&mu->word);
|
|
|
|
}
|
|
|
|
/* Wake the waiters. */
|
2023-07-06 13:57:28 +00:00
|
|
|
for (p = dll_first (wake); p != NULL; p = next) {
|
|
|
|
next = dll_next (wake, p);
|
|
|
|
dll_remove (&wake, p);
|
2022-09-10 23:11:26 +00:00
|
|
|
ATM_STORE_REL (&DLL_NSYNC_WAITER (p)->waiting, 0);
|
|
|
|
nsync_mu_semaphore_v (&DLL_WAITER (p)->sem);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2024-07-06 06:13:20 +00:00
|
|
|
attempts = pthread_delay_np (mu, attempts);
|
2022-09-10 23:11:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Unlock *mu, which must be held in write mode, and wake waiters, if appropriate. */
|
|
|
|
void nsync_mu_unlock (nsync_mu *mu) {
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
/* C is not a garbage-collected language, so we cannot release until we
|
|
|
|
can be sure that we will not have to touch the mutex again to wake a
|
|
|
|
waiter. Another thread could acquire, decrement a reference count
|
|
|
|
and deallocate the mutex before the current thread touched the mutex
|
|
|
|
word again. */
|
|
|
|
if (!ATM_CAS_REL (&mu->word, MU_WLOCK, 0)) {
|
|
|
|
uint32_t old_word = ATM_LOAD (&mu->word);
|
|
|
|
/* Clear MU_ALL_FALSE because the critical section we're just
|
|
|
|
leaving may have made some conditions true. */
|
|
|
|
uint32_t new_word = (old_word - MU_WLOCK) & ~MU_ALL_FALSE;
|
|
|
|
/* Sanity check: mutex must be held in write mode, and there
|
|
|
|
must be no readers. */
|
|
|
|
if ((new_word & (MU_RLOCK_FIELD | MU_WLOCK)) != 0) {
|
|
|
|
if ((old_word & MU_RLOCK_FIELD) != 0) {
|
|
|
|
nsync_panic_ ("attempt to nsync_mu_unlock() an nsync_mu "
|
|
|
|
"held in read mode\n");
|
|
|
|
} else {
|
|
|
|
nsync_panic_ ("attempt to nsync_mu_unlock() an nsync_mu "
|
|
|
|
"not held in write mode\n");
|
|
|
|
}
|
|
|
|
} else if ((old_word & (MU_WAITING|MU_DESIG_WAKER)) == MU_WAITING ||
|
|
|
|
!ATM_CAS_REL (&mu->word, old_word, new_word)) {
|
|
|
|
/* There are waiters and no designated waker, or
|
|
|
|
our initial CAS attempt failed, to use slow path. */
|
|
|
|
nsync_mu_unlock_slow_ (mu, nsync_writer_type_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Unlock *mu, which must be held in read mode, and wake waiters, if appropriate. */
|
|
|
|
void nsync_mu_runlock (nsync_mu *mu) {
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
/* See comment in nsync_mu_unlock(). */
|
|
|
|
if (!ATM_CAS_REL (&mu->word, MU_RLOCK, 0)) {
|
|
|
|
uint32_t old_word = ATM_LOAD (&mu->word);
|
|
|
|
/* Sanity check: mutex must not be held in write mode and
|
|
|
|
reader count must not be 0. */
|
|
|
|
if (((old_word ^ MU_WLOCK) & (MU_WLOCK | MU_RLOCK_FIELD)) == 0) {
|
|
|
|
if ((old_word & MU_WLOCK) != 0) {
|
|
|
|
nsync_panic_ ("attempt to nsync_mu_runlock() an nsync_mu "
|
|
|
|
"held in write mode\n");
|
|
|
|
} else {
|
|
|
|
nsync_panic_ ("attempt to nsync_mu_runlock() an nsync_mu "
|
|
|
|
"not held in read mode\n");
|
|
|
|
}
|
|
|
|
} else if ((old_word & (MU_WAITING | MU_DESIG_WAKER)) == MU_WAITING &&
|
|
|
|
(old_word & (MU_RLOCK_FIELD|MU_ALL_FALSE)) == MU_RLOCK) {
|
|
|
|
/* There are waiters and no designated waker, the last
|
|
|
|
reader is unlocking, and not all waiters have a
|
|
|
|
false condition. So we must take the slow path to
|
|
|
|
attempt to wake a waiter. */
|
|
|
|
nsync_mu_unlock_slow_ (mu, nsync_reader_type_);
|
|
|
|
} else if (!ATM_CAS_REL (&mu->word, old_word, old_word - MU_RLOCK)) {
|
|
|
|
/* CAS attempt failed, so take slow path. */
|
|
|
|
nsync_mu_unlock_slow_ (mu, nsync_reader_type_);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Abort if *mu is not held in write mode. */
|
|
|
|
void nsync_mu_assert_held (const nsync_mu *mu) {
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
if ((ATM_LOAD (&mu->word) & MU_WHELD_IF_NON_ZERO) == 0) {
|
|
|
|
nsync_panic_ ("nsync_mu not held in write mode\n");
|
|
|
|
}
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Abort if *mu is not held in read or write mode. */
|
|
|
|
void nsync_mu_rassert_held (const nsync_mu *mu) {
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
if ((ATM_LOAD (&mu->word) & MU_ANY_LOCK) == 0) {
|
|
|
|
nsync_panic_ ("nsync_mu not held in some mode\n");
|
|
|
|
}
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Return whether *mu is held in read mode.
|
|
|
|
Requires that *mu is held in some mode. */
|
|
|
|
int nsync_mu_is_reader (const nsync_mu *mu) {
|
|
|
|
uint32_t word;
|
|
|
|
IGNORE_RACES_START ();
|
|
|
|
word = ATM_LOAD (&mu->word);
|
|
|
|
if ((word & MU_ANY_LOCK) == 0) {
|
|
|
|
nsync_panic_ ("nsync_mu not held in some mode\n");
|
|
|
|
}
|
|
|
|
IGNORE_RACES_END ();
|
|
|
|
return ((word & MU_WLOCK) == 0);
|
|
|
|
}
|