From 1b15b6ba0a78e0fe87716cc1df07f092c64dfefb Mon Sep 17 00:00:00 2001 From: Andrew Opalach Date: Thu, 30 Oct 2025 11:56:05 -0400 Subject: random: Try to ensure rand() state is thread-local Signed-off-by: Andrew Opalach --- include/al/random.h | 34 ++++++++++++++++++++++++++++++++-- meson.build | 10 ++++++++++ src/random.c | 6 +++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/include/al/random.h b/include/al/random.h index f92a77b..3df3140 100644 --- a/include/al/random.h +++ b/include/al/random.h @@ -5,23 +5,53 @@ #include "macros.h" #define AL_RAND_MAX RAND_MAX +#define AL_RAND_INIT_SEED 1738 extern u32 al_rand_seed; + extern __thread bool al_rand_set; +#ifndef AL_SRAND_THREAD_LOCAL +extern __thread u32 al_rand_state; +#endif static inline void al_rand_init(u32 seed) { al_rand_seed = seed; } +// @TODO: Replace this with an implementation of any PRNG algorithm where +// we can control the state and make it thread-local. Trying to predict the +// state in libc is pretty dumb. +// al_rand() is equivalent to libc rand(), as such there is no expectation +// of it being an actually good random number generator. +#ifdef AL_SRAND_THREAD_LOCAL +#define rrand() rand() +#else +#define rrand() rand_r(&al_rand_state) +#endif static inline s32 al_rand(void) { if (UNLIKELY(!al_rand_set)) { - // srand() is thread-local in Windows CRT. +#ifdef AL_SRAND_THREAD_LOCAL srand(al_rand_seed); +#else + al_rand_state = al_rand_seed; +#endif + // Call rand() once after setting the seed incase it returns the seed + // on the first call, like on Windows. Having the first result potentially + // be so similar run-to-run would no longer even be pseudo-random. + // https://stackoverflow.com/a/76676396 + rrand(); al_rand_set = true; } - return rand(); + return rrand(); +} +#undef rrand + +// https://c-faq.com/lib/randrange.html +static inline s32 al_random_int(s32 min, s32 max) // [MIN, MAX] +{ + return min + al_rand() / (AL_RAND_MAX / (max - min + 1) + 1); } #endif // _AL_RANDOM_H diff --git a/meson.build b/meson.build index c948887..0dffee3 100644 --- a/meson.build +++ b/meson.build @@ -8,6 +8,7 @@ compiler = meson.get_compiler('c') is_debug = get_option('debug') is_msvc = compiler.get_id() == 'msvc' or compiler.get_id() == 'clang-cl' is_msvc_specifically = compiler.cmd_array()[0] == 'cl' +is_windows = host_machine.system() == 'windows' supports_needed_gnu_extensions = not is_msvc_specifically @@ -57,6 +58,15 @@ if is_msvc_specifically alabaster_args += ['/arch:AVX'] endif +if compiler.has_function('rand_r', prefix: '#define _POSIX_C_SOURCE 200112L\n#include ') +elif is_windows # This is supposed to be a check for if libc = Microsoft CRT. + # https://learn.microsoft.com/en-us/previous-versions/f0d4wb4t(v=vs.140)?redirectedfrom=MSDN + # "The srand function sets ... in the current thread." + alabaster_args += ['-DAL_SRAND_THREAD_LOCAL'] +else + error('No thread-local rand().') +endif + if supports_needed_gnu_extensions alabaster_args += ['-DAL_HAVE_GNU_EXTENSIONS'] endif diff --git a/src/random.c b/src/random.c index 1bbf152..fb059a0 100644 --- a/src/random.c +++ b/src/random.c @@ -1,4 +1,8 @@ #include "../include/al/random.h" -u32 al_rand_seed = 1738; +u32 al_rand_seed = AL_RAND_INIT_SEED; + __thread bool al_rand_set = false; +#ifndef AL_SRAND_THREAD_LOCAL +__thread u32 al_rand_state = AL_RAND_INIT_SEED; +#endif -- cgit v1.2.3-101-g0448