Skip to content

Commit

Permalink
work around mingw-w64 init crash
Browse files Browse the repository at this point in the history
commit https://sourceforge.net/p/mingw-w64/mingw-w64/ci/7b33798917a50cf023f1622eee8eba6d7874b796/
introduces a static initialization order fiasco if a static object is
calling clock_gettime() in its constructor.
  • Loading branch information
flyinghead committed May 7, 2024
1 parent 13302b8 commit c53a1c4
Show file tree
Hide file tree
Showing 2 changed files with 288 additions and 0 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -1780,6 +1780,7 @@ if(NOT LIBRETRO)
elseif(WIN32)
if(NOT BUILD_TESTING)
target_sources(${PROJECT_NAME} PRIVATE
core/windows/clock.c
core/windows/rawinput.cpp
core/windows/rawinput.h
core/windows/winmain.cpp)
Expand Down
287 changes: 287 additions & 0 deletions core/windows/clock.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
// FIXME mingw-w64 commit https://sourceforge.net/p/mingw-w64/mingw-w64/ci/7b33798917a50cf023f1622eee8eba6d7874b796/
// A constructor is now run to determine which win32 api is used for the CLOCK_REALTIME clock.
// But if a static variable calls clock_gettime in its constructor, it may call into a null function pointer.
// This is what happens with the discord-rpc library so until this is fixed, we stick to the working version.
/**
* This file has no copyright assigned and is placed in the Public Domain.
* This file is part of the w64 mingw-runtime package.
* No warranty is given; refer to the file DISCLAIMER.PD within this package.
*/
#ifdef __GNUC__
#include <errno.h>
#include <stdint.h>
#include <time.h>
#include <windows.h>
#ifndef IN_WINPTHREAD
#define IN_WINPTHREAD 1
#endif
#include "pthread.h"
#include "pthread_time.h"

#define POW10_7 10000000
#define POW10_9 1000000000

/* Number of 100ns-seconds between the beginning of the Windows epoch
* (Jan. 1, 1601) and the Unix epoch (Jan. 1, 1970)
*/
#define DELTA_EPOCH_IN_100NS INT64_C(116444736000000000)

static WINPTHREADS_INLINE int lc_set_errno(int result)
{
if (result != 0) {
errno = result;
return -1;
}
return 0;
}

typedef void (WINAPI * GetSystemTimeAsFileTime_t)(LPFILETIME);
static GetSystemTimeAsFileTime_t GetSystemTimeAsFileTime_p /* = 0 */;

static GetSystemTimeAsFileTime_t try_load_GetSystemPreciseTimeAsFileTime(void)
{
/* Use GetSystemTimePreciseAsFileTime() if available (Windows 8 or later) */
HMODULE mod = GetModuleHandle("kernel32.dll");
GetSystemTimeAsFileTime_t get_time = NULL;
if (mod)
get_time = (GetSystemTimeAsFileTime_t)(intptr_t)GetProcAddress(mod,
"GetSystemTimePreciseAsFileTime"); /* <1us precision on Windows 10 */
if (get_time == NULL)
get_time = GetSystemTimeAsFileTime; /* >15ms precision on Windows 10 */
__atomic_store_n(&GetSystemTimeAsFileTime_p, get_time, __ATOMIC_RELAXED);
return get_time;
}

static WINPTHREADS_INLINE GetSystemTimeAsFileTime_t load_GetSystemTimeBestAsFileTime(void)
{
GetSystemTimeAsFileTime_t get_time =
__atomic_load_n(&GetSystemTimeAsFileTime_p, __ATOMIC_RELAXED);
if (get_time == NULL)
get_time = try_load_GetSystemPreciseTimeAsFileTime();
return get_time;
}

/**
* Get the resolution of the specified clock clock_id and
* stores it in the struct timespec pointed to by res.
* @param clock_id The clock_id argument is the identifier of the particular
* clock on which to act. The following clocks are supported:
* <pre>
* CLOCK_REALTIME System-wide real-time clock. Setting this clock
* requires appropriate privileges.
* CLOCK_MONOTONIC Clock that cannot be set and represents monotonic
* time since some unspecified starting point.
* CLOCK_PROCESS_CPUTIME_ID High-resolution per-process timer from the CPU.
* CLOCK_THREAD_CPUTIME_ID Thread-specific CPU-time clock.
* </pre>
* @param res The pointer to a timespec structure to receive the time
* resolution.
* @return If the function succeeds, the return value is 0.
* If the function fails, the return value is -1,
* with errno set to indicate the error.
*/
int clock_getres(clockid_t clock_id, struct timespec *res)
{
clockid_t id = clock_id;

if (id == CLOCK_REALTIME && load_GetSystemTimeBestAsFileTime() == GetSystemTimeAsFileTime)
id = CLOCK_REALTIME_COARSE; /* GetSystemTimePreciseAsFileTime() not available */

switch(id) {
case CLOCK_REALTIME:
case CLOCK_MONOTONIC:
{
LARGE_INTEGER pf;

if (QueryPerformanceFrequency(&pf) == 0)
return lc_set_errno(EINVAL);

res->tv_sec = 0;
res->tv_nsec = (int) ((POW10_9 + (pf.QuadPart >> 1)) / pf.QuadPart);
if (res->tv_nsec < 1)
res->tv_nsec = 1;

return 0;
}

case CLOCK_REALTIME_COARSE:
case CLOCK_PROCESS_CPUTIME_ID:
case CLOCK_THREAD_CPUTIME_ID:
{
DWORD timeAdjustment, timeIncrement;
BOOL isTimeAdjustmentDisabled;

(void) GetSystemTimeAdjustment(&timeAdjustment, &timeIncrement, &isTimeAdjustmentDisabled);
res->tv_sec = 0;
res->tv_nsec = timeIncrement * 100;

return 0;
}
default:
break;
}

return lc_set_errno(EINVAL);
}

/**
* Get the time of the specified clock clock_id and stores it in the struct
* timespec pointed to by tp.
* @param clock_id The clock_id argument is the identifier of the particular
* clock on which to act. The following clocks are supported:
* <pre>
* CLOCK_REALTIME System-wide real-time clock. Setting this clock
* requires appropriate privileges.
* CLOCK_MONOTONIC Clock that cannot be set and represents monotonic
* time since some unspecified starting point.
* CLOCK_PROCESS_CPUTIME_ID High-resolution per-process timer from the CPU.
* CLOCK_THREAD_CPUTIME_ID Thread-specific CPU-time clock.
* </pre>
* @param tp The pointer to a timespec structure to receive the time.
* @return If the function succeeds, the return value is 0.
* If the function fails, the return value is -1,
* with errno set to indicate the error.
*/
int clock_gettime(clockid_t clock_id, struct timespec *tp)
{
unsigned __int64 t;
LARGE_INTEGER pf, pc;
union {
unsigned __int64 u64;
FILETIME ft;
} ct, et, kt, ut;

switch(clock_id) {
case CLOCK_REALTIME:
{
load_GetSystemTimeBestAsFileTime()(&ct.ft);
t = ct.u64 - DELTA_EPOCH_IN_100NS;
tp->tv_sec = t / POW10_7;
tp->tv_nsec = ((int) (t % POW10_7)) * 100;

return 0;
}

case CLOCK_REALTIME_COARSE:
{
GetSystemTimeAsFileTime(&ct.ft);
t = ct.u64 - DELTA_EPOCH_IN_100NS;
tp->tv_sec = t / POW10_7;
tp->tv_nsec = ((int) (t % POW10_7)) * 100;

return 0;
}

case CLOCK_MONOTONIC:
{
if (QueryPerformanceFrequency(&pf) == 0)
return lc_set_errno(EINVAL);

if (QueryPerformanceCounter(&pc) == 0)
return lc_set_errno(EINVAL);

tp->tv_sec = pc.QuadPart / pf.QuadPart;
tp->tv_nsec = (int) (((pc.QuadPart % pf.QuadPart) * POW10_9 + (pf.QuadPart >> 1)) / pf.QuadPart);
if (tp->tv_nsec >= POW10_9) {
tp->tv_sec ++;
tp->tv_nsec -= POW10_9;
}

return 0;
}

case CLOCK_PROCESS_CPUTIME_ID:
{
if(0 == GetProcessTimes(GetCurrentProcess(), &ct.ft, &et.ft, &kt.ft, &ut.ft))
return lc_set_errno(EINVAL);
t = kt.u64 + ut.u64;
tp->tv_sec = t / POW10_7;
tp->tv_nsec = ((int) (t % POW10_7)) * 100;

return 0;
}

case CLOCK_THREAD_CPUTIME_ID:
{
if(0 == GetThreadTimes(GetCurrentThread(), &ct.ft, &et.ft, &kt.ft, &ut.ft))
return lc_set_errno(EINVAL);
t = kt.u64 + ut.u64;
tp->tv_sec = t / POW10_7;
tp->tv_nsec = ((int) (t % POW10_7)) * 100;

return 0;
}

default:
break;
}

return lc_set_errno(EINVAL);
}

/**
* Sleep for the specified time.
* @param clock_id This argument should always be CLOCK_REALTIME (0).
* @param flags 0 for relative sleep interval, others for absolute waking up.
* @param request The desired sleep interval or absolute waking up time.
* @param remain The remain amount of time to sleep.
* The current implemention just ignore it.
* @return If the function succeeds, the return value is 0.
* If the function fails, the return value is -1,
* with errno set to indicate the error.
*/
int clock_nanosleep(clockid_t clock_id, int flags,
const struct timespec *request,
struct timespec *remain)
{
struct timespec tp;

if (clock_id != CLOCK_REALTIME)
return lc_set_errno(EINVAL);

if (flags == 0)
return nanosleep(request, remain);

/* TIMER_ABSTIME = 1 */
clock_gettime(CLOCK_REALTIME, &tp);

tp.tv_sec = request->tv_sec - tp.tv_sec;
tp.tv_nsec = request->tv_nsec - tp.tv_nsec;
if (tp.tv_nsec < 0) {
tp.tv_nsec += POW10_9;
tp.tv_sec --;
}

return nanosleep(&tp, remain);
}

/**
* Set the time of the specified clock clock_id.
* @param clock_id This argument should always be CLOCK_REALTIME (0).
* @param tp The requested time.
* @return If the function succeeds, the return value is 0.
* If the function fails, the return value is -1,
* with errno set to indicate the error.
*/
int clock_settime(clockid_t clock_id, const struct timespec *tp)
{
SYSTEMTIME st;

union {
unsigned __int64 u64;
FILETIME ft;
} t;

if (clock_id != CLOCK_REALTIME)
return lc_set_errno(EINVAL);

t.u64 = tp->tv_sec * (__int64) POW10_7 + tp->tv_nsec / 100 + DELTA_EPOCH_IN_100NS;
if (FileTimeToSystemTime(&t.ft, &st) == 0)
return lc_set_errno(EINVAL);

if (SetSystemTime(&st) == 0)
return lc_set_errno(EPERM);

return 0;
}
#endif

0 comments on commit c53a1c4

Please sign in to comment.