mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 08:10:30 -10:00
rthreads: add retro_procbarrier, a process-wide memory barrier
A process-wide barrier does what a fence cannot: when it returns, every
other thread of the process that was running has passed through a full
fence too. That is what lets a handshake be asymmetric -- the side that
runs constantly uses a plain store and a plain load, no locked
instruction, and the side that runs rarely pays for the barrier once
before it sleeps. On a core thread pushing frames to a render thread,
that is a locked RMW on the producer replaced by three ordinary moves,
measured at 6.2 ns against 2.6 ns per publish.
Nothing in user mode can make another core execute a fence, so every
implementation is a request to the kernel to interrupt the others. This
file asks in whichever way the running system supports, resolved once:
membarrier Linux 4.14+, FreeBSD 14.1+. PRIVATE_EXPEDITED
IPIs exactly the CPUs currently running a
thread of this process.
FlushProcessWriteBuffers Windows Vista+. Disassembled from ntoskrnl
10.0.26100: raise IRQL, KiIpiSendRequestEx to
the affinity, spin for acks. Already the bare
IPI; there is nothing to beat on the fast path.
page flip x86 only: change the protection of a locked
page, and the TLB shootdown that forces is an
IPI whose handler is a serialising event.
Reaches back to Windows 2000, Linux 2.6 and
macOS on Intel. Refused on ARM and PowerPC,
where the invalidate is broadcast without an
IPI and the trick fences nothing.
signal Any Linux, any Android, any macOS including
Apple Silicon and PowerPC: signal every thread
currently on another CPU and wait for its
handler to acknowledge. The kernel delivers a
signal to a running thread by IPI on every one
of those -- kick_process on Linux,
cause_ast_check on XNU -- and each was checked
in source before this was written.
uniprocessor Nothing to fence against; a compiler fence.
none The multi-core consoles. retro_procbarrier()
returns 0 and the caller uses a symmetric
primitive instead.
Every tier's syscalls are on Android's seccomp allowlist -- membarrier is
there for ART's JIT, so it cannot be blocked for apps -- and the kernel
version cannot be inferred from the Android version, which is why this is
resolved at runtime rather than by ifdef.
The test resolves a tier, issues barriers with busy and sleeping threads
alive, and then runs the asymmetric protocol for 5000 rounds with the
consumer parked on a real condvar, failing on the first lost wake. That
last check is only load-bearing on SMP: on one core the race cannot
happen, and replacing the barrier with a no-op still passes -- checked,
and the test prints the CPU count so a green run on one core is not
mistaken for a result. RETRO_PROCBARRIER=<tier> forces a tier, so every
tier a platform has can be exercised on one machine; all three Linux
tiers complete here and are clean under ASan and UBSan.
Builds C89 -pedantic on Linux, and on Windows for i686 at
_WIN32_WINNT=0x0500 and x86_64 at 0x0600, warning-free. Darwin is written
from XNU's source -- osfmk/x86_64/pmap.c for the page flip, and
cause_ast_check on x86, arm64 and the xnu-1228 ppc tree for the signal
tier -- and has not been compiled or run here.
This commit is contained in:
1 parent
356f854cd2
commit
fde356648e
5 files changed
+1027
No files matched your search
@@ -928,6 +928,7 @@ FIFO BUFFER
|
||||
#if defined(HAVE_THREADS)
|
||||
#include "../libretro-common/queues/retro_waitable_spsc.c"
|
||||
#include "../libretro-common/rthreads/retro_eventcount.c"
|
||||
#include "../libretro-common/rthreads/retro_procbarrier.c"
|
||||
#endif
|
||||
|
||||
/*============================================================
|
||||
|
||||
@@ -0,0 +1,134 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (retro_procbarrier.h).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
#ifndef __LIBRETRO_SDK_PROCBARRIER_H
|
||||
#define __LIBRETRO_SDK_PROCBARRIER_H
|
||||
|
||||
#include <retro_common_api.h>
|
||||
|
||||
RETRO_BEGIN_DECLS
|
||||
|
||||
/*
|
||||
* retro_procbarrier: a process-wide memory barrier, resolved at runtime.
|
||||
*
|
||||
* WHAT IT IS FOR
|
||||
*
|
||||
* A normal fence orders the calling thread's own memory operations. A
|
||||
* process-wide barrier does more: when it returns, every other thread of
|
||||
* this process that was running has passed through a full fence too, so
|
||||
* any store one of them made before this call is visible to the caller
|
||||
* afterwards. That lets a handshake be asymmetric. The side that runs
|
||||
* constantly -- a producer publishing work -- can use a plain store and
|
||||
* a plain load, with no locked instruction and no fence, while the side
|
||||
* that runs rarely -- a consumer about to park -- pays for this call
|
||||
* once before it commits to sleeping. The producer's store cannot slip
|
||||
* past the consumer's decision, because the barrier drains it first.
|
||||
*
|
||||
* On a producer/consumer pair that is exactly the shape of a core thread
|
||||
* pushing frames to a render thread, and the difference on the producer
|
||||
* side is a locked RMW against three ordinary moves.
|
||||
*
|
||||
* WHAT IT COSTS
|
||||
*
|
||||
* Nothing in user mode can make another core execute a fence, so every
|
||||
* implementation of this is a request to the kernel to interrupt the
|
||||
* other cores. It is a syscall at best and a signal per thread at worst,
|
||||
* so it belongs on the cold side of a protocol only. Never call it on a
|
||||
* hot path.
|
||||
*
|
||||
* HOW IT IS IMPLEMENTED
|
||||
*
|
||||
* By whichever of these the running system supports, chosen once at
|
||||
* retro_procbarrier_init():
|
||||
*
|
||||
* RETRO_PROCBARRIER_UNIPROCESSOR
|
||||
* One CPU: a store is globally visible with nothing to reorder
|
||||
* against, so the barrier is a compiler fence and nothing else.
|
||||
*
|
||||
* RETRO_PROCBARRIER_MEMBARRIER
|
||||
* Linux 4.14+, FreeBSD 14.1+: membarrier(MEMBARRIER_CMD_PRIVATE_
|
||||
* EXPEDITED). The kernel sends an IPI to exactly the CPUs currently
|
||||
* running a thread of this process. The best tier where it exists.
|
||||
*
|
||||
* RETRO_PROCBARRIER_FLUSHWRITEBUFFERS
|
||||
* Windows Vista+: FlushProcessWriteBuffers(). One syscall; the
|
||||
* kernel IPIs the process's affinity. Equivalent to membarrier.
|
||||
*
|
||||
* RETRO_PROCBARRIER_PAGEFLIP
|
||||
* x86 only, any Linux, any macOS on Intel, Windows back to 2000:
|
||||
* change the protection of a locked dummy page. The kernel must
|
||||
* invalidate its TLB entry on every CPU the address space is active
|
||||
* on, and on x86 that invalidation travels as an IPI whose handler
|
||||
* is a serialising event. The barrier is a side effect and is not
|
||||
* documented anywhere; it is nonetheless what .NET and the JVM used
|
||||
* before the kernels grew a real call. It is REFUSED on non-x86: ARM
|
||||
* and PowerPC broadcast TLB invalidation without an IPI, and the
|
||||
* trick fences nothing there.
|
||||
*
|
||||
* RETRO_PROCBARRIER_SIGNAL
|
||||
* Any Linux, any macOS including Apple Silicon, any Android: send a
|
||||
* signal to every thread currently running on another CPU and wait
|
||||
* for each to acknowledge from its handler. The kernel delivers a
|
||||
* signal to a running thread by IPI, and entering the kernel is a
|
||||
* fence on every architecture. Threads that are not on a CPU are
|
||||
* skipped -- a context switch already drained them. Cost is a
|
||||
* syscall per running thread; correct everywhere, fastest nowhere.
|
||||
*
|
||||
* RETRO_PROCBARRIER_NONE
|
||||
* Nothing available. retro_procbarrier() returns 0 and the caller
|
||||
* must use a symmetric primitive instead. This is where the
|
||||
* multi-core consoles land.
|
||||
*
|
||||
* The signal tier needs a signal number. The default is SIGRTMAX-1 where
|
||||
* real-time signals exist and SIGUSR2 otherwise; pass another to init if
|
||||
* the application uses those.
|
||||
*/
|
||||
|
||||
enum retro_procbarrier_tier
|
||||
{
|
||||
RETRO_PROCBARRIER_NONE = 0,
|
||||
RETRO_PROCBARRIER_UNIPROCESSOR,
|
||||
RETRO_PROCBARRIER_MEMBARRIER,
|
||||
RETRO_PROCBARRIER_FLUSHWRITEBUFFERS,
|
||||
RETRO_PROCBARRIER_PAGEFLIP,
|
||||
RETRO_PROCBARRIER_SIGNAL
|
||||
};
|
||||
|
||||
/* Resolve the tier for this process. Safe to call more than once; the
|
||||
* first call wins. signum is only used by the signal tier; pass 0 for
|
||||
* the default. Returns the tier selected. */
|
||||
enum retro_procbarrier_tier retro_procbarrier_init(int signum);
|
||||
|
||||
/* The tier in use, without initialising. RETRO_PROCBARRIER_NONE before
|
||||
* init has run. */
|
||||
enum retro_procbarrier_tier retro_procbarrier_tier(void);
|
||||
|
||||
/* A human-readable name for a tier, for logs. */
|
||||
const char *retro_procbarrier_tier_name(enum retro_procbarrier_tier tier);
|
||||
|
||||
/* Issue a process-wide barrier. Returns 1 if one was issued (or was
|
||||
* unnecessary), 0 if this system cannot issue one -- in which case the
|
||||
* caller must not rely on asymmetric ordering. Initialises on first use
|
||||
* with the default signal if init was not called. */
|
||||
int retro_procbarrier(void);
|
||||
|
||||
RETRO_END_DECLS
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,591 @@
|
||||
/* Copyright (C) 2010-2026 The RetroArch team
|
||||
*
|
||||
* ---------------------------------------------------------------------------------------
|
||||
* The following license statement only applies to this file (retro_procbarrier.c).
|
||||
* ---------------------------------------------------------------------------------------
|
||||
*
|
||||
* Permission is hereby granted, free of charge,
|
||||
* to any person obtaining a copy of this software and associated documentation files (the "Software"),
|
||||
* to deal in the Software without restriction, including without limitation the rights to
|
||||
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
|
||||
* and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
|
||||
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
|
||||
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
|
||||
/* See the header for what this is and why each tier is shaped the way it
|
||||
* is. This file is the platform plumbing.
|
||||
*
|
||||
* Every tier below was checked against the kernel that provides it, by
|
||||
* reading its source or disassembling it, before being written down here:
|
||||
*
|
||||
* Linux membarrier kernel/sched/membarrier.c: PRIVATE_EXPEDITED IPIs
|
||||
* each online CPU whose current task shares our mm,
|
||||
* with ipi_mb(), which is smp_mb(). 4.14 and later.
|
||||
* 4.3-4.13 only have SHARED, an RCU grace period that
|
||||
* waits for every CPU on the system; that is not
|
||||
* used here, since it can take milliseconds.
|
||||
* Linux page flip arch/x86/mm/tlb.c: flush_tlb_mm_range sends an IPI
|
||||
* to mm_cpumask(mm), back to at least 2.6.32.
|
||||
* Windows FPWB ntoskrnl 10.0.26100 KeFlushProcessWriteBuffers:
|
||||
* raise IRQL, skip if one CPU, else KiIpiSendRequestEx
|
||||
* to the affinity and spin for acks.
|
||||
* Windows page flip The same IPI, reached through the TLB shootdown a
|
||||
* protection change forces. All APIs it needs exist
|
||||
* on Windows 2000.
|
||||
* Darwin page flip osfmk/x86_64/pmap.c pmap_flush_tlbs:
|
||||
* i386_signal_cpu(cpu, MP_TLB_FLUSH) per active CPU,
|
||||
* then a cpus_to_respond wait.
|
||||
* Darwin signal cause_ast_check: i386_signal_cpu(MP_AST) on x86,
|
||||
* cpu_signal(SIGPast) on arm64 and on ppc (xnu-1228),
|
||||
* the ppc path bracketed by sync; isync.
|
||||
* Linux signal Signal delivery to a running thread goes through
|
||||
* kick_process(), which is an IPI.
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include <retro_atomic.h>
|
||||
#include <rthreads/retro_procbarrier.h>
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Platform selection */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#if defined(_WIN32) || defined(USE_WIN32_THREADS)
|
||||
#define PB_WINDOWS 1
|
||||
#elif defined(__APPLE__)
|
||||
#define PB_DARWIN 1
|
||||
#elif defined(__linux__) || defined(__ANDROID__)
|
||||
#define PB_LINUX 1
|
||||
#elif defined(__FreeBSD__)
|
||||
#define PB_FREEBSD 1
|
||||
#endif
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64)
|
||||
#define PB_X86 1
|
||||
#endif
|
||||
|
||||
#if defined(PB_WINDOWS)
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
#if defined(PB_LINUX) || defined(PB_FREEBSD) || defined(PB_DARWIN)
|
||||
#include <unistd.h>
|
||||
#include <signal.h>
|
||||
#include <errno.h>
|
||||
#include <sys/mman.h>
|
||||
#include <pthread.h>
|
||||
#endif
|
||||
|
||||
#if defined(PB_LINUX)
|
||||
#include <sys/syscall.h>
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#if defined(PB_FREEBSD)
|
||||
#include <sys/membarrier.h>
|
||||
#endif
|
||||
|
||||
#if defined(PB_DARWIN)
|
||||
#include <mach/mach.h>
|
||||
#include <mach/thread_info.h>
|
||||
#include <mach/thread_act.h>
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* State */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static retro_atomic_int_t s_tier; /* enum retro_procbarrier_tier */
|
||||
static int s_signum;
|
||||
|
||||
#if defined(PB_WINDOWS)
|
||||
typedef VOID (WINAPI *pb_fpwb_t)(VOID);
|
||||
static pb_fpwb_t s_fpwb;
|
||||
static void *s_page;
|
||||
static SIZE_T s_page_size;
|
||||
#else
|
||||
static void *s_page;
|
||||
static size_t s_page_size;
|
||||
#endif
|
||||
|
||||
#if defined(PB_LINUX) || defined(PB_DARWIN)
|
||||
static retro_atomic_int_t s_acks;
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Helpers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static unsigned pb_num_cpus(void)
|
||||
{
|
||||
#if defined(PB_WINDOWS)
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return (unsigned)si.dwNumberOfProcessors;
|
||||
#elif defined(_SC_NPROCESSORS_ONLN)
|
||||
long n = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
return (n > 0) ? (unsigned)n : 1u;
|
||||
#else
|
||||
return 2u; /* unknown: assume SMP, which is the safe direction */
|
||||
#endif
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Tier: membarrier */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#if defined(PB_LINUX)
|
||||
/* ABI constants, spelled out rather than taken from the header: the
|
||||
* values are fixed since 4.14 but the header may be older than the
|
||||
* kernel that is actually running. */
|
||||
#ifndef __NR_membarrier
|
||||
#define PB_NO_MEMBARRIER 1
|
||||
#endif
|
||||
#define PB_MEMBARRIER_CMD_QUERY 0
|
||||
#define PB_MEMBARRIER_CMD_PRIVATE_EXPEDITED (1 << 3)
|
||||
#define PB_MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED (1 << 4)
|
||||
|
||||
static int pb_membarrier_try(void)
|
||||
{
|
||||
#if defined(PB_NO_MEMBARRIER)
|
||||
return 0;
|
||||
#else
|
||||
long q = syscall(__NR_membarrier, PB_MEMBARRIER_CMD_QUERY, 0, 0);
|
||||
if (q < 0)
|
||||
return 0; /* ENOSYS before 4.3, or filtered */
|
||||
if (!(q & PB_MEMBARRIER_CMD_PRIVATE_EXPEDITED))
|
||||
return 0; /* 4.3-4.13: only the slow SHARED form; not used */
|
||||
if (syscall(__NR_membarrier,
|
||||
PB_MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0, 0) != 0)
|
||||
return 0;
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void pb_membarrier(void)
|
||||
{
|
||||
#if !defined(PB_NO_MEMBARRIER)
|
||||
syscall(__NR_membarrier, PB_MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 0);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(PB_FREEBSD)
|
||||
static int pb_membarrier_try(void)
|
||||
{
|
||||
int q = membarrier(MEMBARRIER_CMD_QUERY, 0, 0);
|
||||
if (q < 0 || !(q & MEMBARRIER_CMD_PRIVATE_EXPEDITED))
|
||||
return 0;
|
||||
if (membarrier(MEMBARRIER_CMD_REGISTER_PRIVATE_EXPEDITED, 0, 0) != 0)
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void pb_membarrier(void)
|
||||
{
|
||||
membarrier(MEMBARRIER_CMD_PRIVATE_EXPEDITED, 0, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Tier: FlushProcessWriteBuffers */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#if defined(PB_WINDOWS)
|
||||
static int pb_fpwb_try(void)
|
||||
{
|
||||
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
||||
if (!k32)
|
||||
return 0;
|
||||
/* FARPROC to pb_fpwb_t directly: both are function pointers, and
|
||||
* routing the cast through void* is what ISO C objects to. */
|
||||
s_fpwb = (pb_fpwb_t)GetProcAddress(k32, "FlushProcessWriteBuffers");
|
||||
return s_fpwb != NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Tier: page flip */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#if defined(PB_X86)
|
||||
static int pb_pageflip_try(void)
|
||||
{
|
||||
#if defined(PB_WINDOWS)
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
s_page_size = si.dwPageSize;
|
||||
s_page = VirtualAlloc(NULL, s_page_size, MEM_COMMIT | MEM_RESERVE,
|
||||
PAGE_READWRITE);
|
||||
if (!s_page)
|
||||
return 0;
|
||||
/* A page that is paged out has no TLB entry to shoot down. */
|
||||
VirtualLock(s_page, s_page_size);
|
||||
return 1;
|
||||
#else
|
||||
long ps = sysconf(_SC_PAGESIZE);
|
||||
s_page_size = (ps > 0) ? (size_t)ps : 4096u;
|
||||
s_page = mmap(NULL, s_page_size, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
if (s_page == MAP_FAILED)
|
||||
{
|
||||
s_page = NULL;
|
||||
return 0;
|
||||
}
|
||||
mlock(s_page, s_page_size);
|
||||
return 1;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void pb_pageflip(void)
|
||||
{
|
||||
#if defined(PB_WINDOWS)
|
||||
DWORD old;
|
||||
VirtualProtect(s_page, s_page_size, PAGE_READWRITE, &old);
|
||||
*(volatile char*)s_page = 0;
|
||||
VirtualProtect(s_page, s_page_size, PAGE_NOACCESS, &old);
|
||||
#else
|
||||
mprotect(s_page, s_page_size, PROT_READ | PROT_WRITE);
|
||||
*(volatile char*)s_page = 0;
|
||||
mprotect(s_page, s_page_size, PROT_NONE);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Tier: signal */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
#if defined(PB_LINUX) || defined(PB_DARWIN)
|
||||
|
||||
static void pb_ack_handler(int sig)
|
||||
{
|
||||
(void)sig;
|
||||
/* Async-signal-safe: one atomic increment. The release pairs with the
|
||||
* acquire in the waiter, so the interrupted thread's earlier stores
|
||||
* are ordered before its acknowledgement. */
|
||||
retro_atomic_fetch_add_int(&s_acks, 1);
|
||||
}
|
||||
|
||||
static int pb_signal_try(int signum)
|
||||
{
|
||||
struct sigaction sa;
|
||||
memset(&sa, 0, sizeof(sa));
|
||||
sa.sa_handler = pb_ack_handler;
|
||||
sa.sa_flags = SA_RESTART;
|
||||
sigemptyset(&sa.sa_mask);
|
||||
if (sigaction(signum, &sa, NULL) != 0)
|
||||
return 0;
|
||||
#if defined(PB_DARWIN)
|
||||
{
|
||||
/* Prove task_threads is permitted here; sandboxes can deny it. */
|
||||
thread_act_array_t list;
|
||||
mach_msg_type_number_t n;
|
||||
if (task_threads(mach_task_self(), &list, &n) != KERN_SUCCESS)
|
||||
return 0;
|
||||
{
|
||||
mach_msg_type_number_t i;
|
||||
for (i = 0; i < n; i++)
|
||||
mach_port_deallocate(mach_task_self(), list[i]);
|
||||
}
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)list,
|
||||
n * sizeof(thread_act_t));
|
||||
}
|
||||
#else
|
||||
{
|
||||
/* And that /proc/self/task is readable; hidepid exempts self but
|
||||
* a hardened container may not. */
|
||||
DIR *d = opendir("/proc/self/task");
|
||||
if (!d)
|
||||
return 0;
|
||||
closedir(d);
|
||||
}
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
#if defined(PB_LINUX)
|
||||
static int pb_thread_running(pid_t tid)
|
||||
{
|
||||
char path[64], buf[256], *p, *q;
|
||||
int fd;
|
||||
ssize_t n;
|
||||
unsigned v;
|
||||
/* /proc/self/task/<tid>/stat: field 3 is the state, after the comm
|
||||
* in parentheses; the comm may itself contain spaces or parentheses,
|
||||
* so scan back from the last ')' rather than forward. The path is
|
||||
* built by hand: snprintf is not C89, and a tid is a small decimal. */
|
||||
strcpy(path, "/proc/self/task/");
|
||||
q = path + strlen(path);
|
||||
{
|
||||
char digits[16];
|
||||
int nd = 0;
|
||||
v = (unsigned)tid;
|
||||
do { digits[nd++] = (char)('0' + v % 10u); v /= 10u; } while (v);
|
||||
while (nd) *q++ = digits[--nd];
|
||||
}
|
||||
strcpy(q, "/stat");
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return 0;
|
||||
n = read(fd, buf, sizeof(buf) - 1);
|
||||
close(fd);
|
||||
if (n <= 0)
|
||||
return 0;
|
||||
buf[n] = '\0';
|
||||
p = strrchr(buf, ')');
|
||||
return p && p[1] == ' ' && p[2] == 'R';
|
||||
}
|
||||
|
||||
static void pb_signal_barrier(void)
|
||||
{
|
||||
DIR *d;
|
||||
struct dirent *e;
|
||||
pid_t self = (pid_t)syscall(SYS_gettid);
|
||||
pid_t pid = getpid();
|
||||
int sent = 0;
|
||||
|
||||
retro_atomic_store_relaxed_int(&s_acks, 0);
|
||||
d = opendir("/proc/self/task");
|
||||
if (!d)
|
||||
return;
|
||||
while ((e = readdir(d)) != NULL)
|
||||
{
|
||||
pid_t tid = (pid_t)atoi(e->d_name);
|
||||
if (tid <= 0 || tid == self)
|
||||
continue;
|
||||
/* Only threads on a CPU need interrupting; a thread that was
|
||||
* switched out was drained by the switch. 'R' is runnable rather
|
||||
* than strictly running, so under overcommit this can also wait
|
||||
* for a preempted thread to be rescheduled -- bounded by a
|
||||
* scheduler quantum, and acceptable on a path about to sleep. */
|
||||
if (!pb_thread_running(tid))
|
||||
continue;
|
||||
if (syscall(SYS_tgkill, pid, tid, s_signum) == 0)
|
||||
sent++;
|
||||
}
|
||||
closedir(d);
|
||||
while (retro_atomic_load_acquire_int(&s_acks) < sent)
|
||||
;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(PB_DARWIN)
|
||||
static void pb_signal_barrier(void)
|
||||
{
|
||||
thread_act_array_t list;
|
||||
mach_msg_type_number_t n, i;
|
||||
thread_t self = mach_thread_self();
|
||||
int sent = 0;
|
||||
|
||||
retro_atomic_store_relaxed_int(&s_acks, 0);
|
||||
if (task_threads(mach_task_self(), &list, &n) != KERN_SUCCESS)
|
||||
{
|
||||
mach_port_deallocate(mach_task_self(), self);
|
||||
return;
|
||||
}
|
||||
for (i = 0; i < n; i++)
|
||||
{
|
||||
thread_basic_info_data_t info;
|
||||
mach_msg_type_number_t count = THREAD_BASIC_INFO_COUNT;
|
||||
pthread_t pt;
|
||||
|
||||
if (list[i] == self)
|
||||
continue;
|
||||
if (thread_info(list[i], THREAD_BASIC_INFO,
|
||||
(thread_info_t)&info, &count) != KERN_SUCCESS)
|
||||
continue;
|
||||
if (info.run_state != TH_STATE_RUNNING)
|
||||
continue;
|
||||
pt = pthread_from_mach_thread_np(list[i]);
|
||||
if (pt && pthread_kill(pt, s_signum) == 0)
|
||||
sent++;
|
||||
}
|
||||
for (i = 0; i < n; i++)
|
||||
mach_port_deallocate(mach_task_self(), list[i]);
|
||||
vm_deallocate(mach_task_self(), (vm_address_t)list,
|
||||
n * sizeof(thread_act_t));
|
||||
mach_port_deallocate(mach_task_self(), self);
|
||||
|
||||
while (retro_atomic_load_acquire_int(&s_acks) < sent)
|
||||
;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* PB_LINUX || PB_DARWIN */
|
||||
|
||||
/* ------------------------------------------------------------------ */
|
||||
/* Resolution */
|
||||
/* ------------------------------------------------------------------ */
|
||||
|
||||
static int pb_default_signum(void)
|
||||
{
|
||||
#if defined(SIGRTMAX)
|
||||
return SIGRTMAX - 1;
|
||||
#elif defined(SIGUSR2)
|
||||
return SIGUSR2;
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
enum retro_procbarrier_tier retro_procbarrier_init(int signum)
|
||||
{
|
||||
enum retro_procbarrier_tier t;
|
||||
|
||||
t = (enum retro_procbarrier_tier)retro_atomic_load_acquire_int(&s_tier);
|
||||
if (t != RETRO_PROCBARRIER_NONE)
|
||||
return t;
|
||||
|
||||
s_signum = signum ? signum : pb_default_signum();
|
||||
|
||||
/* RETRO_PROCBARRIER=<tier name> forces a tier for testing, the way
|
||||
* RTHREADS_SCOND does for the condvar. A forced tier still has to be
|
||||
* available: asking for membarrier on a 4.4 kernel gets the normal
|
||||
* resolution, not a broken barrier. */
|
||||
{
|
||||
const char *force = getenv("RETRO_PROCBARRIER");
|
||||
if (force && *force)
|
||||
{
|
||||
#if defined(PB_LINUX) || defined(PB_FREEBSD)
|
||||
if (!strcmp(force, "membarrier") && pb_membarrier_try())
|
||||
{ t = RETRO_PROCBARRIER_MEMBARRIER; goto done; }
|
||||
#endif
|
||||
#if defined(PB_WINDOWS)
|
||||
if (!strcmp(force, "fpwb") && pb_fpwb_try())
|
||||
{ t = RETRO_PROCBARRIER_FLUSHWRITEBUFFERS; goto done; }
|
||||
#endif
|
||||
#if defined(PB_X86)
|
||||
if (!strcmp(force, "pageflip") && pb_pageflip_try())
|
||||
{ t = RETRO_PROCBARRIER_PAGEFLIP; goto done; }
|
||||
#endif
|
||||
#if defined(PB_LINUX) || defined(PB_DARWIN)
|
||||
if (!strcmp(force, "signal") && s_signum && pb_signal_try(s_signum))
|
||||
{ t = RETRO_PROCBARRIER_SIGNAL; goto done; }
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/* One CPU: nothing to fence against. Every tier below already
|
||||
* short-circuits this in the kernel, but they pay a ring transition
|
||||
* to find out. */
|
||||
if (pb_num_cpus() <= 1)
|
||||
{
|
||||
t = RETRO_PROCBARRIER_UNIPROCESSOR;
|
||||
goto done;
|
||||
}
|
||||
|
||||
#if defined(PB_LINUX) || defined(PB_FREEBSD)
|
||||
if (pb_membarrier_try())
|
||||
{
|
||||
t = RETRO_PROCBARRIER_MEMBARRIER;
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(PB_WINDOWS)
|
||||
if (pb_fpwb_try())
|
||||
{
|
||||
t = RETRO_PROCBARRIER_FLUSHWRITEBUFFERS;
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(PB_X86)
|
||||
/* x86 only, by the #if: the shootdown is not an IPI elsewhere. On
|
||||
* Windows this is the whole pre-Vista story. */
|
||||
if (pb_pageflip_try())
|
||||
{
|
||||
t = RETRO_PROCBARRIER_PAGEFLIP;
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if (defined(PB_LINUX) || defined(PB_DARWIN))
|
||||
if (s_signum && pb_signal_try(s_signum))
|
||||
{
|
||||
t = RETRO_PROCBARRIER_SIGNAL;
|
||||
goto done;
|
||||
}
|
||||
#endif
|
||||
|
||||
t = RETRO_PROCBARRIER_NONE;
|
||||
|
||||
done:
|
||||
/* NONE is also "not initialised", so a failed resolution is retried
|
||||
* on the next call rather than cached. */
|
||||
if (t != RETRO_PROCBARRIER_NONE)
|
||||
retro_atomic_store_release_int(&s_tier, (int)t);
|
||||
return t;
|
||||
}
|
||||
|
||||
enum retro_procbarrier_tier retro_procbarrier_tier(void)
|
||||
{
|
||||
return (enum retro_procbarrier_tier)retro_atomic_load_acquire_int(&s_tier);
|
||||
}
|
||||
|
||||
const char *retro_procbarrier_tier_name(enum retro_procbarrier_tier tier)
|
||||
{
|
||||
switch (tier)
|
||||
{
|
||||
case RETRO_PROCBARRIER_UNIPROCESSOR: return "uniprocessor";
|
||||
case RETRO_PROCBARRIER_MEMBARRIER: return "membarrier";
|
||||
case RETRO_PROCBARRIER_FLUSHWRITEBUFFERS: return "FlushProcessWriteBuffers";
|
||||
case RETRO_PROCBARRIER_PAGEFLIP: return "page-flip";
|
||||
case RETRO_PROCBARRIER_SIGNAL: return "signal";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
int retro_procbarrier(void)
|
||||
{
|
||||
enum retro_procbarrier_tier t;
|
||||
|
||||
t = (enum retro_procbarrier_tier)retro_atomic_load_acquire_int(&s_tier);
|
||||
if (t == RETRO_PROCBARRIER_NONE)
|
||||
t = retro_procbarrier_init(0);
|
||||
|
||||
switch (t)
|
||||
{
|
||||
case RETRO_PROCBARRIER_UNIPROCESSOR:
|
||||
retro_atomic_thread_fence_seq_cst();
|
||||
return 1;
|
||||
|
||||
#if defined(PB_LINUX) || defined(PB_FREEBSD)
|
||||
case RETRO_PROCBARRIER_MEMBARRIER:
|
||||
pb_membarrier();
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
#if defined(PB_WINDOWS)
|
||||
case RETRO_PROCBARRIER_FLUSHWRITEBUFFERS:
|
||||
s_fpwb();
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
#if defined(PB_X86)
|
||||
case RETRO_PROCBARRIER_PAGEFLIP:
|
||||
pb_pageflip();
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
#if defined(PB_LINUX) || defined(PB_DARWIN)
|
||||
case RETRO_PROCBARRIER_SIGNAL:
|
||||
pb_signal_barrier();
|
||||
return 1;
|
||||
#endif
|
||||
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
TARGET := retro_procbarrier_test
|
||||
|
||||
LIBRETRO_COMM_DIR := ../../..
|
||||
|
||||
# The real primitive out of the tree, plus rthreads for the threads and
|
||||
# condvar the test parks on.
|
||||
SOURCES := \
|
||||
retro_procbarrier_test.c \
|
||||
$(LIBRETRO_COMM_DIR)/rthreads/retro_procbarrier.c \
|
||||
$(LIBRETRO_COMM_DIR)/rthreads/rthreads.c
|
||||
|
||||
CFLAGS += -Wall -W -std=gnu99 -g -O1 \
|
||||
-DHAVE_THREADS -I$(LIBRETRO_COMM_DIR)/include
|
||||
LDFLAGS += -lpthread -lrt
|
||||
|
||||
OBJS := $(SOURCES:.c=.o)
|
||||
|
||||
all: $(TARGET)
|
||||
|
||||
$(TARGET): $(OBJS)
|
||||
$(CC) -o $@ $(OBJS) $(LDFLAGS)
|
||||
|
||||
%.o: %.c
|
||||
$(CC) $(CFLAGS) -c -o $@ $<
|
||||
|
||||
clean:
|
||||
rm -f $(OBJS) $(TARGET)
|
||||
|
||||
.PHONY: all clean
|
||||
@@ -0,0 +1,272 @@
|
||||
/* retro_procbarrier_test.c - does the process-wide barrier fence?
|
||||
*
|
||||
* Three things are checked.
|
||||
*
|
||||
* 1. Resolution. init() picks a tier, and it is a tier this platform
|
||||
* can have: no membarrier on Windows, no FlushProcessWriteBuffers on
|
||||
* Linux, no page flip on ARM.
|
||||
*
|
||||
* 2. It returns. The chosen tier issues a barrier and comes back, with
|
||||
* other threads busy, sleeping and parked. A signal-tier bug that
|
||||
* waited for a sleeping thread would hang here.
|
||||
*
|
||||
* 3. It fences. This is the one that matters and the one most tests of
|
||||
* this kind skip. The asymmetric protocol the barrier enables is:
|
||||
*
|
||||
* producer: seq = seq + 1 (release) consumer: parked = 1 (relaxed)
|
||||
* if (parked) wake() procbarrier()
|
||||
* if (seq unchanged) sleep()
|
||||
*
|
||||
* Without the barrier the consumer's store to parked can sit in its
|
||||
* store buffer while it loads seq, and the producer's store to seq
|
||||
* can sit in the producer's while it loads parked: each sees the
|
||||
* other's old value, the producer does not wake, the consumer sleeps
|
||||
* forever. The barrier drains the producer's buffer before the
|
||||
* consumer decides, so the consumer either sees the new seq or the
|
||||
* producer sees parked.
|
||||
*
|
||||
* The test runs that protocol hard: a producer publishing as fast as
|
||||
* it can and a consumer parking on a real semaphore each round, with
|
||||
* the barrier in place. If the barrier is a no-op on this platform
|
||||
* the consumer eventually sleeps through a publish and the round times
|
||||
* out. On a uniprocessor the race cannot happen and the test passes
|
||||
* trivially, which is correct: no barrier is needed there.
|
||||
*
|
||||
* A timeout, not a hang: a lost wake is reported as a failure with
|
||||
* the round number, so a broken tier fails fast.
|
||||
*
|
||||
* ONE CORE IS NOT ENOUGH. The race needs two threads genuinely on two
|
||||
* CPUs; on a single core the scheduler serialises them and the
|
||||
* barrier is never load-bearing. Checked by replacing the barrier
|
||||
* with a no-op on a one-core box: all 5000 rounds still pass. So a
|
||||
* green fence check on a uniprocessor says nothing, and the test
|
||||
* prints the CPU count so nobody reads it as more than it is. Run it
|
||||
* on real SMP to mean anything.
|
||||
*
|
||||
* RETRO_PROCBARRIER=membarrier|fpwb|pageflip|signal forces a tier, so
|
||||
* every tier a platform has can be exercised on one machine.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <retro_atomic.h>
|
||||
#include <rthreads/rthreads.h>
|
||||
#include <rthreads/retro_procbarrier.h>
|
||||
#include <retro_timers.h>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <windows.h>
|
||||
static void sleep_ms(unsigned ms) { Sleep(ms); }
|
||||
#else
|
||||
#include <unistd.h>
|
||||
static void sleep_ms(unsigned ms) { usleep(ms * 1000u); }
|
||||
#endif
|
||||
|
||||
/* ---- the protocol under test --------------------------------------- */
|
||||
|
||||
static retro_atomic_int_t g_seq;
|
||||
static retro_atomic_int_t g_parked;
|
||||
static retro_atomic_int_t g_stop;
|
||||
static retro_atomic_int_t g_wakes;
|
||||
static slock_t *g_lock;
|
||||
static scond_t *g_cond;
|
||||
static int g_posted; /* under g_lock */
|
||||
|
||||
static void producer(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (!retro_atomic_load_relaxed_int(&g_stop))
|
||||
{
|
||||
int s = retro_atomic_load_relaxed_int(&g_seq);
|
||||
/* Plain release store, plain relaxed load: no locked instruction.
|
||||
* This is the whole point of the asymmetric design. The sequence
|
||||
* wraps through the unsigned domain: it publishes many millions of
|
||||
* times a round, and only "changed" matters, not the value. */
|
||||
retro_atomic_store_release_int(&g_seq, (int)((unsigned)s + 1u));
|
||||
if (retro_atomic_load_relaxed_int(&g_parked))
|
||||
{
|
||||
/* Claim the wake so there is one post per park. */
|
||||
if (retro_atomic_fetch_add_int(&g_parked, -1) == 1)
|
||||
{
|
||||
retro_atomic_fetch_add_int(&g_wakes, 1);
|
||||
slock_lock(g_lock);
|
||||
g_posted = 1;
|
||||
scond_signal(g_cond);
|
||||
slock_unlock(g_lock);
|
||||
}
|
||||
else
|
||||
retro_atomic_fetch_add_int(&g_parked, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* One consumer round: announce parking, barrier, re-check, sleep if
|
||||
* nothing arrived. Returns 1 if woken, 0 if the wake was lost. */
|
||||
static int consumer_round(int last_seen, int *seen_out)
|
||||
{
|
||||
int s;
|
||||
retro_atomic_store_relaxed_int(&g_parked, 1);
|
||||
retro_procbarrier();
|
||||
s = retro_atomic_load_acquire_int(&g_seq);
|
||||
if (s != last_seen)
|
||||
{
|
||||
/* Something arrived during the announce: withdraw. If the
|
||||
* producer already claimed the park it will post; take it. */
|
||||
if (retro_atomic_fetch_add_int(&g_parked, -1) != 1)
|
||||
{
|
||||
retro_atomic_fetch_add_int(&g_parked, 1);
|
||||
slock_lock(g_lock);
|
||||
while (!g_posted)
|
||||
scond_wait(g_cond, g_lock);
|
||||
g_posted = 0;
|
||||
slock_unlock(g_lock);
|
||||
}
|
||||
*seen_out = s;
|
||||
return 1;
|
||||
}
|
||||
/* Nothing new: really sleep on the condvar, with a 2 s timeout so a
|
||||
* lost wake is a failure rather than a hang. The producer publishes
|
||||
* continuously, so a correct barrier means a wake within microseconds. */
|
||||
slock_lock(g_lock);
|
||||
while (!g_posted)
|
||||
{
|
||||
if (!scond_wait_timeout(g_cond, g_lock, 2000000))
|
||||
break;
|
||||
}
|
||||
if (g_posted)
|
||||
{
|
||||
g_posted = 0;
|
||||
slock_unlock(g_lock);
|
||||
*seen_out = retro_atomic_load_acquire_int(&g_seq);
|
||||
return 1;
|
||||
}
|
||||
slock_unlock(g_lock);
|
||||
*seen_out = last_seen;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ---- checks ---------------------------------------------------------- */
|
||||
|
||||
static int check_resolution(void)
|
||||
{
|
||||
enum retro_procbarrier_tier t = retro_procbarrier_init(0);
|
||||
printf(" tier: %s\n", retro_procbarrier_tier_name(t));
|
||||
|
||||
#if defined(_WIN32)
|
||||
if (t == RETRO_PROCBARRIER_MEMBARRIER || t == RETRO_PROCBARRIER_SIGNAL)
|
||||
{
|
||||
printf(" FAIL: Windows resolved a POSIX tier\n");
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
if (t == RETRO_PROCBARRIER_FLUSHWRITEBUFFERS)
|
||||
{
|
||||
printf(" FAIL: POSIX resolved a Windows tier\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
#if !(defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64))
|
||||
if (t == RETRO_PROCBARRIER_PAGEFLIP)
|
||||
{
|
||||
printf(" FAIL: page flip chosen on non-x86, where it fences nothing\n");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
if (t == RETRO_PROCBARRIER_NONE)
|
||||
printf(" note: no tier on this platform; the fence check is skipped\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void busy_thread(void *arg)
|
||||
{
|
||||
volatile unsigned x = 0;
|
||||
(void)arg;
|
||||
while (!retro_atomic_load_relaxed_int(&g_stop))
|
||||
x++;
|
||||
}
|
||||
|
||||
static void sleeping_thread(void *arg)
|
||||
{
|
||||
(void)arg;
|
||||
while (!retro_atomic_load_relaxed_int(&g_stop))
|
||||
sleep_ms(5);
|
||||
}
|
||||
|
||||
static int check_returns(void)
|
||||
{
|
||||
sthread_t *busy[2], *slp;
|
||||
int i;
|
||||
retro_atomic_store_relaxed_int(&g_stop, 0);
|
||||
for (i = 0; i < 2; i++)
|
||||
busy[i] = sthread_create(busy_thread, NULL);
|
||||
slp = sthread_create(sleeping_thread, NULL);
|
||||
sleep_ms(20);
|
||||
for (i = 0; i < 200; i++)
|
||||
retro_procbarrier();
|
||||
retro_atomic_store_relaxed_int(&g_stop, 1);
|
||||
for (i = 0; i < 2; i++)
|
||||
sthread_join(busy[i]);
|
||||
sthread_join(slp);
|
||||
printf(" returns: 200 barriers with busy and sleeping threads alive\n");
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int check_fences(void)
|
||||
{
|
||||
sthread_t *p;
|
||||
int rounds = 5000, r, last = 0, seen, lost = 0;
|
||||
|
||||
if (retro_procbarrier_tier() == RETRO_PROCBARRIER_NONE)
|
||||
return 1;
|
||||
|
||||
g_lock = slock_new();
|
||||
g_cond = scond_new();
|
||||
g_posted = 0;
|
||||
retro_atomic_store_relaxed_int(&g_seq, 0);
|
||||
retro_atomic_store_relaxed_int(&g_parked, 0);
|
||||
retro_atomic_store_relaxed_int(&g_stop, 0);
|
||||
retro_atomic_store_relaxed_int(&g_wakes, 0);
|
||||
p = sthread_create(producer, NULL);
|
||||
|
||||
for (r = 0; r < rounds; r++)
|
||||
{
|
||||
if (!consumer_round(last, &seen))
|
||||
{
|
||||
lost++;
|
||||
if (lost <= 3)
|
||||
printf(" lost wake at round %d (seq %d)\n", r, last);
|
||||
seen = retro_atomic_load_acquire_int(&g_seq);
|
||||
}
|
||||
last = seen;
|
||||
}
|
||||
retro_atomic_store_relaxed_int(&g_stop, 1);
|
||||
sthread_join(p);
|
||||
scond_free(g_cond);
|
||||
slock_free(g_lock);
|
||||
|
||||
printf(" fences: %d rounds, %d lost wakes, %d posts\n",
|
||||
rounds, lost, retro_atomic_load_relaxed_int(&g_wakes));
|
||||
return lost == 0;
|
||||
}
|
||||
|
||||
int main(void)
|
||||
{
|
||||
int ok = 1;
|
||||
printf("retro_procbarrier\n");
|
||||
#if defined(_WIN32)
|
||||
{ SYSTEM_INFO si; GetSystemInfo(&si);
|
||||
printf(" cpus: %u%s\n", (unsigned)si.dwNumberOfProcessors,
|
||||
si.dwNumberOfProcessors > 1 ? "" : " (fence check is not load-bearing on one core)"); }
|
||||
#else
|
||||
{ long n = sysconf(_SC_NPROCESSORS_ONLN);
|
||||
printf(" cpus: %ld%s\n", n,
|
||||
n > 1 ? "" : " (fence check is not load-bearing on one core)"); }
|
||||
#endif
|
||||
ok &= check_resolution();
|
||||
ok &= check_returns();
|
||||
ok &= check_fences();
|
||||
printf(ok ? "procbarrier: ok\n" : "procbarrier: FAILED\n");
|
||||
return ok ? 0 : 1;
|
||||
}
|
||||
Reference in new issue
Block a user