mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-15 23:40:29 +02:00
The libretro-common submodule pinned the standalone repository at 76a3d54. It is replaced by an in-tree copy of RetroArch master (b655a20) holding only what the core uses: the VFS, file, string, encoding, compat and time sources it already built, the headers they and the core include, the threading set - rthreads, retro_atomic, retro_spsc and retro_waitable_spsc, mpsc_stack, retro_triple_buffer, retro_eventcount, retro_asym_eventcount, retro_procbarrier and tpool, with features_cpu and rstrtod, which they need - and the codecs that replace the bundled zlib, zstd, libchdr, libzip and libpng: rinflate and rdeflate, rzstd, rchd with its LZMA and FLAC decoders, rzip_archive, rpng and crc32. stdstring.c, which nothing calls, is no longer built. The threading sources are always built, by both the static Makefile and CMake; the VFS set stays behind HAVE_LIBRETRO_VFS as before. The codecs are built by the change that switches the core over to them.
1787 lines
53 KiB
C
1787 lines
53 KiB
C
/* Copyright (C) 2010-2020 The RetroArch team
|
|
*
|
|
* ---------------------------------------------------------------------------------------
|
|
* The following license statement only applies to this file (features_cpu.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.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#if defined(_WIN32)
|
|
#include <direct.h>
|
|
#else
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
#include <compat/strl.h>
|
|
#include <libretro.h>
|
|
#include <features/features_cpu.h>
|
|
#include "cpu_class.h" /* per-processor class, shared with rthreads */
|
|
#if defined(__linux__)
|
|
#include <sys/syscall.h>
|
|
/* The prototype every Linux libc uses; spelled out so a strict C89
|
|
* build, where glibc hides it, sees the same one. */
|
|
extern long syscall(long number, ...);
|
|
#endif
|
|
#include <retro_atomic.h>
|
|
#include <retro_timers.h>
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
#include <windows.h>
|
|
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
|
#include <intrin.h>
|
|
#endif
|
|
#endif
|
|
|
|
#ifdef __PSL1GHT__
|
|
#include <lv2/systime.h>
|
|
#endif
|
|
|
|
#if defined(_XBOX360)
|
|
#include <PPCIntrinsics.h>
|
|
#elif !defined(__MACH__) && !defined(__FreeBSD__) && (defined(__POWERPC__) || defined(__powerpc__) || defined(__ppc__) || defined(__PPC64__) || defined(__powerpc64__))
|
|
#ifndef _PPU_INTRINSICS_H
|
|
#include <ppu_intrinsics.h>
|
|
#endif
|
|
#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(ANDROID) || defined(__QNX__) || defined(DJGPP)
|
|
/* POSIX_MONOTONIC_CLOCK is not being defined in Android headers despite support being present. */
|
|
#include <time.h>
|
|
#endif
|
|
|
|
#if defined(__QNX__) && !defined(CLOCK_MONOTONIC)
|
|
#define CLOCK_MONOTONIC 2
|
|
#endif
|
|
|
|
#if defined(PSP)
|
|
#include <pspkernel.h>
|
|
#endif
|
|
|
|
#if defined(PSP) || defined(__PSL1GHT__)
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
#if defined(PSP)
|
|
#include <psprtc.h>
|
|
#endif
|
|
|
|
#if defined(VITA)
|
|
#include <psp2/kernel/processmgr.h>
|
|
#include <psp2/rtc.h>
|
|
#endif
|
|
|
|
#if defined(ORBIS)
|
|
#include <orbis/libkernel.h>
|
|
#endif
|
|
|
|
#if defined(PS2)
|
|
#include <ps2sdkapi.h>
|
|
#endif
|
|
|
|
#if !defined(__PSL1GHT__) && defined(__PS3__)
|
|
#include <sys/sys_time.h>
|
|
#endif
|
|
|
|
#if defined(GEKKO_NATIVE)
|
|
#include <gekko/gekko.h>
|
|
#elif defined(GEKKO)
|
|
#include <ogc/lwp_watchdog.h>
|
|
#endif
|
|
|
|
#ifdef WIIU
|
|
#include <wiiu/os/time.h>
|
|
#endif
|
|
|
|
#if defined(HAVE_LIBNX)
|
|
#include <switch.h>
|
|
#elif defined(SWITCH)
|
|
#include <libtransistor/types.h>
|
|
#include <libtransistor/svc.h>
|
|
#endif
|
|
|
|
#if defined(_3DS)
|
|
#include <3ds/svc.h>
|
|
#include <3ds/os.h>
|
|
#include <3ds/services/cfgu.h>
|
|
#endif
|
|
|
|
#if defined(WEBOS)
|
|
#include <sys/stat.h>
|
|
#endif
|
|
|
|
/* iOS/OSX specific. Lacks clock_gettime(), so implement it. */
|
|
#ifdef __MACH__
|
|
#include <sys/time.h>
|
|
#include <AvailabilityMacros.h>
|
|
|
|
#ifndef CLOCK_MONOTONIC
|
|
#define CLOCK_MONOTONIC 0
|
|
#endif
|
|
|
|
#ifndef CLOCK_REALTIME
|
|
#define CLOCK_REALTIME 0
|
|
#endif
|
|
|
|
/* clock_gettime() was added in iOS 10.0 / macOS 10.12 Sierra.
|
|
* On deployment targets below those versions the symbol doesn't
|
|
* exist in libSystem and the link fails, so fall back to a
|
|
* gettimeofday() shim. Gated on MIN_REQUIRED (deployment target)
|
|
* rather than MAX_ALLOWED (SDK) because the binary needs to run on
|
|
* the deployment target, and Apple weak-links clock_gettime when
|
|
* targeting < 10.12 even from a newer SDK. */
|
|
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) \
|
|
&& __IPHONE_OS_VERSION_MIN_REQUIRED < 100000) \
|
|
|| (defined(MAC_OS_X_VERSION_MIN_REQUIRED) \
|
|
&& MAC_OS_X_VERSION_MIN_REQUIRED < 101200)
|
|
#define RA_NEEDS_CLOCK_GETTIME_SHIM 1
|
|
static int ra_clock_gettime(int clk_ik, struct timespec *t)
|
|
{
|
|
struct timeval now;
|
|
int rv = gettimeofday(&now, NULL);
|
|
if (rv)
|
|
return rv;
|
|
t->tv_sec = now.tv_sec;
|
|
t->tv_nsec = now.tv_usec * 1000;
|
|
return 0;
|
|
}
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(__MACH__) && defined(RA_NEEDS_CLOCK_GETTIME_SHIM)
|
|
#else
|
|
#define ra_clock_gettime clock_gettime
|
|
#endif
|
|
|
|
#ifdef __EMSCRIPTEN__
|
|
#include <emscripten.h>
|
|
#endif
|
|
|
|
#if defined(BSD) || defined(__APPLE__)
|
|
#include <sys/sysctl.h>
|
|
#endif
|
|
|
|
#include <string.h>
|
|
|
|
retro_perf_tick_t cpu_features_get_perf_counter(void)
|
|
{
|
|
retro_perf_tick_t time_ticks = 0;
|
|
/* The CPU's own cycle counter wherever there is one: a register
|
|
* read, no clock behind it. Platform clocks follow for the rest. */
|
|
#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
|
|
time_ticks = (retro_perf_tick_t)__rdtsc();
|
|
#elif defined(__GNUC__) && (defined(__i386__) || defined(__x86_64__))
|
|
{
|
|
unsigned a, d;
|
|
__asm__ __volatile__ ("rdtsc" : "=a" (a), "=d" (d));
|
|
time_ticks = (retro_perf_tick_t)a | ((retro_perf_tick_t)d << 32);
|
|
}
|
|
#elif defined(__GNUC__) && defined(__aarch64__)
|
|
__asm__ __volatile__ ("mrs %0, cntvct_el0" : "=r" (time_ticks));
|
|
#elif defined(__GNUC__) && defined(__ARM_ARCH_6__)
|
|
__asm__ __volatile__ ("mrc p15, 0, %0, c9, c13, 0" : "=r" (time_ticks));
|
|
#elif defined(_WIN32)
|
|
{
|
|
LARGE_INTEGER c;
|
|
QueryPerformanceCounter(&c);
|
|
time_ticks = (retro_perf_tick_t)c.QuadPart;
|
|
}
|
|
#elif defined(GEKKO_NATIVE)
|
|
time_ticks = gk_ticks();
|
|
#elif defined(GEKKO)
|
|
time_ticks = gettime();
|
|
#elif !defined(__MACH__) && !defined(__FreeBSD__) && (defined(_XBOX360) || defined(__powerpc__) || defined(__ppc__) || defined(__POWERPC__) || defined(__PSL1GHT__) || defined(__PPC64__) || defined(__powerpc64__))
|
|
time_ticks = __mftb();
|
|
#elif (defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK > 0) || defined(__QNX__) || defined(ANDROID)
|
|
{
|
|
struct timespec tv;
|
|
if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) == 0)
|
|
time_ticks = (retro_perf_tick_t)tv.tv_sec * 1000000000 +
|
|
(retro_perf_tick_t)tv.tv_nsec;
|
|
}
|
|
#elif defined(PSP) || defined(VITA)
|
|
time_ticks = sceKernelGetSystemTimeWide();
|
|
#elif defined(ORBIS)
|
|
sceRtcGetCurrentTick((SceRtcTick*)&time_ticks);
|
|
#elif defined(PS2)
|
|
time_ticks = ps2_clock();
|
|
#elif defined(_3DS)
|
|
time_ticks = svcGetSystemTick();
|
|
#elif defined(WIIU)
|
|
time_ticks = OSGetSystemTime();
|
|
#elif defined(HAVE_LIBNX)
|
|
time_ticks = armGetSystemTick();
|
|
#elif defined(__EMSCRIPTEN__)
|
|
time_ticks = emscripten_get_now() * 1000;
|
|
#endif
|
|
|
|
return time_ticks;
|
|
}
|
|
|
|
retro_time_t cpu_features_get_time_usec(void)
|
|
{
|
|
#if defined(_WIN32)
|
|
static LARGE_INTEGER freq;
|
|
LARGE_INTEGER count;
|
|
|
|
/* Frequency is guaranteed to not change. */
|
|
if (!freq.QuadPart && !QueryPerformanceFrequency(&freq))
|
|
return 0;
|
|
|
|
if (!QueryPerformanceCounter(&count))
|
|
return 0;
|
|
return (count.QuadPart / freq.QuadPart * 1000000) + (count.QuadPart % freq.QuadPart * 1000000 / freq.QuadPart);
|
|
#elif defined(__PSL1GHT__)
|
|
return sysGetSystemTime();
|
|
#elif !defined(__PSL1GHT__) && defined(__PS3__)
|
|
return sys_time_get_system_time();
|
|
#elif defined(GEKKO_NATIVE)
|
|
return gk_ticks_to_us(gk_ticks());
|
|
#elif defined(GEKKO)
|
|
return ticks_to_microsecs(gettime());
|
|
#elif defined(WIIU)
|
|
return ticks_to_us(OSGetSystemTime());
|
|
#elif defined(SWITCH) || defined(HAVE_LIBNX)
|
|
return (svcGetSystemTick() * 10) / 192;
|
|
#elif defined(_3DS)
|
|
return osGetTime() * 1000;
|
|
#elif defined(_POSIX_MONOTONIC_CLOCK) || defined(__QNX__) || defined(ANDROID) || defined(__MACH__)
|
|
struct timespec tv;
|
|
tv.tv_sec = 0;
|
|
tv.tv_nsec = 0;
|
|
if (ra_clock_gettime(CLOCK_MONOTONIC, &tv) < 0)
|
|
return 0;
|
|
return tv.tv_sec * INT64_C(1000000) + (tv.tv_nsec + 500) / 1000;
|
|
#elif defined(__EMSCRIPTEN__)
|
|
return emscripten_get_now() * 1000;
|
|
#elif defined(PS2)
|
|
return ps2_clock() / PS2_CLOCKS_PER_MSEC * 1000;
|
|
#elif defined(VITA) || defined(PSP)
|
|
return sceKernelGetSystemTimeWide();
|
|
#elif defined(DJGPP)
|
|
return uclock() * 1000000LL / UCLOCKS_PER_SEC;
|
|
#elif defined(ORBIS)
|
|
return sceKernelGetProcessTime();
|
|
#else
|
|
#error "Your platform does not have a timer function implemented in cpu_features_get_time_usec(). Cannot continue."
|
|
#endif
|
|
}
|
|
|
|
#if defined(__x86_64__) || defined(__i386__) || defined(__i486__) || defined(__i686__) || (defined(_M_X64) && _MSC_VER > 1310) || (defined(_M_IX86) && _MSC_VER > 1310)
|
|
#define CPU_X86
|
|
#endif
|
|
|
|
#if defined(_MSC_VER) && !defined(_XBOX)
|
|
#if (_MSC_VER > 1310)
|
|
#include <intrin.h>
|
|
#endif
|
|
#endif
|
|
|
|
#if defined(CPU_X86) && !defined(__MACH__)
|
|
#include <limits.h>
|
|
void x86_cpuid(uint32_t func, int32_t flags[4])
|
|
{
|
|
/* On Android, we compile RetroArch with PIC, and we
|
|
* are not allowed to clobber the ebx register. */
|
|
#ifdef __x86_64__
|
|
#define REG_b "rbx"
|
|
#define REG_S "rsi"
|
|
#else
|
|
#define REG_b "ebx"
|
|
#define REG_S "esi"
|
|
#endif
|
|
|
|
#if defined(__GNUC__)
|
|
__asm__ volatile (
|
|
"mov %%" REG_b ", %%" REG_S "\n"
|
|
"cpuid\n"
|
|
"xchg %%" REG_b ", %%" REG_S "\n"
|
|
: "=a"(flags[0]), "=S"(flags[1]), "=c"(flags[2]), "=d"(flags[3])
|
|
: "a"(func));
|
|
#elif defined(_MSC_VER) && INT_MAX == 2147483647
|
|
__cpuid((int*)flags, func);
|
|
#else
|
|
#ifndef NDEBUG
|
|
printf("Unknown compiler. Cannot check CPUID with inline assembly.\n");
|
|
#endif
|
|
memset(flags, 0, 4 * sizeof(int));
|
|
#endif
|
|
}
|
|
|
|
/* Only runs on i686 and above. Needs to be conditionally run. */
|
|
static uint64_t xgetbv_x86(uint32_t idx)
|
|
{
|
|
#if defined(__GNUC__)
|
|
uint32_t eax, edx;
|
|
__asm__ volatile (
|
|
/* Older GCC versions (Apple's GCC for example) do
|
|
* not understand xgetbv instruction.
|
|
* Stamp out the machine code directly.
|
|
*/
|
|
".byte 0x0f, 0x01, 0xd0\n"
|
|
: "=a"(eax), "=d"(edx) : "c"(idx));
|
|
return ((uint64_t)edx << 32) | eax;
|
|
#elif _MSC_FULL_VER >= 160040219
|
|
/* Intrinsic only works on 2010 SP1 and above. */
|
|
return _xgetbv(idx);
|
|
#else
|
|
#ifndef NDEBUG
|
|
printf("Unknown compiler. Cannot check xgetbv bits.\n");
|
|
#endif
|
|
return 0;
|
|
#endif
|
|
}
|
|
#endif
|
|
|
|
/* RunFast mode is a 32-bit VFP control, and this writes FPSCR - so it
|
|
* needs both an ARM32 target and an FPU worth configuring. NEON
|
|
* implies VFP, which is why its presence is the proxy used here; a
|
|
* VFP-less core such as the armv5te arm926ej-s would fault on the
|
|
* write.
|
|
*
|
|
* CPU_ARM_RUNFAST is the single condition for the definition AND
|
|
* every call. They were separate conditions and drifted: the calls
|
|
* ended up reachable where the definition was not, which is a link
|
|
* error on exactly the targets with no NEON - Miyoo armv5te and
|
|
* armv7 builds without an FPU selected. */
|
|
#if (defined(__ARM_NEON) || defined(__ARM_NEON__)) && defined(__arm__)
|
|
#define CPU_ARM_RUNFAST 1
|
|
#endif
|
|
|
|
#ifdef CPU_ARM_RUNFAST
|
|
static void arm_enable_runfast_mode(void)
|
|
{
|
|
/* RunFast mode. Enables flush-to-zero and some
|
|
* floating point optimizations. */
|
|
static const unsigned x = 0x04086060;
|
|
static const unsigned y = 0x03000000;
|
|
int r;
|
|
__asm__ volatile(
|
|
"fmrx %0, fpscr \n\t" /* r0 = FPSCR */
|
|
"and %0, %0, %1 \n\t" /* r0 = r0 & 0x04086060 */
|
|
"orr %0, %0, %2 \n\t" /* r0 = r0 | 0x03000000 */
|
|
"fmxr fpscr, %0 \n\t" /* FPSCR = r0 */
|
|
: "=r"(r)
|
|
: "r"(x), "r"(y)
|
|
);
|
|
}
|
|
#endif /* CPU_ARM_RUNFAST */
|
|
|
|
#if defined(__linux__) && !defined(CPU_X86)
|
|
static unsigned char check_arm_cpu_feature(const char* feature)
|
|
{
|
|
char line[1024];
|
|
unsigned char status = 0;
|
|
FILE *fp = fopen("/proc/cpuinfo", "r");
|
|
|
|
if (!fp)
|
|
return 0;
|
|
|
|
while (fgets(line, sizeof(line), fp))
|
|
{
|
|
const char *list;
|
|
const char *p;
|
|
size_t flen;
|
|
|
|
if (strncmp(line, "Features\t: ", 11))
|
|
continue;
|
|
|
|
/* Feature names are space separated and several are prefixes of
|
|
* others - 'aes' sits inside 'sveaes', 'sha3' inside 'svesha3',
|
|
* 'asimd' inside 'asimddp' - so compare whole tokens. */
|
|
flen = strlen(feature);
|
|
list = line + 11;
|
|
|
|
for (p = list; (p = strstr(p, feature)); p += flen)
|
|
{
|
|
char after = p[flen];
|
|
char before = (p == list) ? ' ' : p[-1];
|
|
if ( (before == ' ')
|
|
&& (after == ' ' || after == '\n' || after == '\0'))
|
|
{
|
|
status = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
return status;
|
|
}
|
|
|
|
#if !defined(_SC_NPROCESSORS_ONLN)
|
|
/**
|
|
* parse_decimal:
|
|
*
|
|
* Parse an decimal integer starting from 'input', but not going further
|
|
* than 'limit'. Return the value into '*result'.
|
|
*
|
|
* NOTE: Does not skip over leading spaces, or deal with sign characters.
|
|
* NOTE: Ignores overflows.
|
|
*
|
|
* The function returns NULL in case of error (bad format), or the new
|
|
* position after the decimal number in case of success (which will always
|
|
* be <= 'limit').
|
|
*
|
|
* Leaf function.
|
|
**/
|
|
static const char *parse_decimal(const char* input,
|
|
const char* limit, int* result)
|
|
{
|
|
const char* p = input;
|
|
int val = 0;
|
|
|
|
while (p < limit)
|
|
{
|
|
int d = (*p - '0');
|
|
if ((unsigned)d >= 10U)
|
|
break;
|
|
val = val*10 + d;
|
|
p++;
|
|
}
|
|
if (p == input)
|
|
return NULL;
|
|
|
|
*result = val;
|
|
return p;
|
|
}
|
|
|
|
/**
|
|
* cpulist_parse:
|
|
* Parse a textual list of cpus and store the result inside a CpuList object.
|
|
* Input format is the following:
|
|
* - comma-separated list of items (no spaces)
|
|
* - each item is either a single decimal number (cpu index), or a range made
|
|
* of two numbers separated by a single dash (-). Ranges are inclusive.
|
|
*
|
|
* Examples: 0
|
|
* 2,4-127,128-143
|
|
* 0-1
|
|
**/
|
|
static void cpulist_parse(CpuList* list, const char *buf, ssize_t len)
|
|
{
|
|
const char* p = buf;
|
|
const char* end = p + len;
|
|
|
|
/* NOTE: the input line coming from sysfs typically contains a
|
|
* trailing newline, so take care of it in the code below
|
|
*/
|
|
while (p < end && *p != '\n')
|
|
{
|
|
int val, start_value, end_value;
|
|
/* Find the end of current item, and put it into 'q' */
|
|
const char *q = (const char*)memchr(p, ',', end-p);
|
|
|
|
if (!q)
|
|
q = end;
|
|
|
|
/* Get first value */
|
|
if (!(p = parse_decimal(p, q, &start_value)))
|
|
return;
|
|
|
|
end_value = start_value;
|
|
|
|
/* If we're not at the end of the item, expect a dash and
|
|
* and integer; extract end value.
|
|
*/
|
|
if (p < q && *p == '-')
|
|
{
|
|
if (!(p = parse_decimal(p+1, q, &end_value)))
|
|
return;
|
|
}
|
|
|
|
/* Set bits CPU list bits */
|
|
for (val = start_value; val <= end_value; val++)
|
|
{
|
|
if ((unsigned)val < 32)
|
|
list->mask |= (uint32_t)(UINT32_C(1) << val);
|
|
}
|
|
|
|
/* Jump to next item */
|
|
p = q;
|
|
if (p < end)
|
|
p++;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* cpulist_read_from:
|
|
*
|
|
* Read a CPU list from one sysfs file
|
|
**/
|
|
static void cpulist_read_from(CpuList* list, const char* filename)
|
|
{
|
|
char buf[512];
|
|
size_t _len;
|
|
FILE *fp = fopen(filename, "r");
|
|
|
|
list->mask = 0;
|
|
|
|
if (!fp)
|
|
return;
|
|
|
|
_len = fread(buf, 1, sizeof(buf) - 1, fp);
|
|
fclose(fp);
|
|
buf[_len] = '\0';
|
|
|
|
cpulist_parse(list, buf, (ssize_t)_len);
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
|
/* GetLogicalProcessorInformation arrived in XP SP3 and Server 2003, so
|
|
* it is resolved rather than linked: on Win9x, NT 4 and 2000 the export
|
|
* is absent, GetProcAddress reports so, and the caller falls back to
|
|
* the plain processor count. The Ex form carrying EfficiencyClass is
|
|
* Windows 7 and later and would rank efficiency cores, which this does
|
|
* not attempt. */
|
|
typedef BOOL (WINAPI *cpu_glpi_t)(
|
|
PSYSTEM_LOGICAL_PROCESSOR_INFORMATION, PDWORD);
|
|
|
|
/* Caller frees. Returns NULL, leaving *count untouched, wherever the
|
|
* export or the query is unavailable. */
|
|
static SYSTEM_LOGICAL_PROCESSOR_INFORMATION *cpu_win32_slpi(DWORD *count)
|
|
{
|
|
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf = NULL;
|
|
DWORD _len = 0;
|
|
HMODULE k32 = GetModuleHandleA("kernel32.dll");
|
|
cpu_glpi_t glpi = k32 ? (cpu_glpi_t)(void (*)(void))
|
|
GetProcAddress(k32, "GetLogicalProcessorInformation") : NULL;
|
|
|
|
if (!glpi)
|
|
return NULL;
|
|
|
|
/* The first call is expected to fail, and reports the size wanted. */
|
|
if (glpi(NULL, &_len))
|
|
return NULL;
|
|
if (GetLastError() != ERROR_INSUFFICIENT_BUFFER || _len == 0)
|
|
return NULL;
|
|
if (_len % sizeof(*buf))
|
|
return NULL;
|
|
|
|
if (!(buf = (SYSTEM_LOGICAL_PROCESSOR_INFORMATION *)malloc(_len)))
|
|
return NULL;
|
|
|
|
if (!glpi(buf, &_len))
|
|
{
|
|
free(buf);
|
|
return NULL;
|
|
}
|
|
|
|
*count = _len / (DWORD)sizeof(*buf);
|
|
return buf;
|
|
}
|
|
|
|
/* Size in KiB of the level-3 cache whose ProcessorMask covers the
|
|
* processor, from the same GetLogicalProcessorInformation records
|
|
* (RelationCache entries carry a level, a size and the mask of
|
|
* processors behind them); 0 where none does. */
|
|
static unsigned cpu_win32_llc_kib(
|
|
const SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf, DWORD count,
|
|
unsigned bit)
|
|
{
|
|
DWORD i;
|
|
for (i = 0; i < count; i++)
|
|
{
|
|
if (buf[i].Relationship != RelationCache)
|
|
continue;
|
|
if (buf[i].Cache.Level != 3)
|
|
continue;
|
|
if (buf[i].ProcessorMask & (((ULONG_PTR)1) << bit))
|
|
return (unsigned)(buf[i].Cache.Size / 1024);
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
/* Number of distinct SMT sibling groups under
|
|
* /sys/devices/system/cpu, which is one per physical core. The list a
|
|
* sibling file holds names every processor sharing that core, so the
|
|
* lowest id in it identifies the group and counting distinct ones
|
|
* counts cores. */
|
|
static unsigned linux_core_amount_physical(unsigned logical)
|
|
{
|
|
char path[512];
|
|
char line[64];
|
|
unsigned seen[128];
|
|
unsigned n_seen = 0;
|
|
unsigned found = 0;
|
|
unsigned i;
|
|
|
|
/* One entry per core, so a machine wider than the table is left to
|
|
* the logical count rather than answered wrongly. */
|
|
if (logical > (unsigned)(sizeof(seen) / sizeof(seen[0])))
|
|
return logical;
|
|
|
|
for (i = 0; i < 256 && found < logical; i++)
|
|
{
|
|
FILE *fp;
|
|
unsigned first;
|
|
unsigned j;
|
|
|
|
snprintf(path, sizeof(path),
|
|
CPU_CLASS_SYSFS "/cpu%u/topology/thread_siblings_list", i);
|
|
|
|
if (!(fp = fopen(path, "r")))
|
|
continue;
|
|
|
|
line[0] = '\0';
|
|
if (!fgets(line, sizeof(line), fp))
|
|
{
|
|
fclose(fp);
|
|
continue;
|
|
}
|
|
fclose(fp);
|
|
|
|
found++;
|
|
|
|
if (sscanf(line, "%u", &first) != 1)
|
|
continue;
|
|
|
|
for (j = 0; j < n_seen; j++)
|
|
if (seen[j] == first)
|
|
break;
|
|
|
|
if (j == n_seen && n_seen < (unsigned)(sizeof(seen) / sizeof(seen[0])))
|
|
seen[n_seen++] = first;
|
|
}
|
|
|
|
/* Nothing readable, so the kernel is not publishing topology here. */
|
|
if (n_seen == 0)
|
|
return logical;
|
|
|
|
return n_seen;
|
|
}
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
/* First unsigned in a one-line sysfs file, or @fallback where the file
|
|
* is missing or unreadable. */
|
|
static unsigned sysfs_read_uint(const char *path, unsigned fallback)
|
|
{
|
|
char line[64];
|
|
unsigned val;
|
|
FILE *fp = fopen(path, "r");
|
|
|
|
if (!fp)
|
|
return fallback;
|
|
|
|
line[0] = '\0';
|
|
if (!fgets(line, sizeof(line), fp))
|
|
{
|
|
fclose(fp);
|
|
return fallback;
|
|
}
|
|
fclose(fp);
|
|
|
|
if (sscanf(line, "%u", &val) != 1)
|
|
return fallback;
|
|
return val;
|
|
}
|
|
|
|
/* Size of the last-level cache the processor sits behind, in KiB,
|
|
* from "<cpu>/cache/index3/size" (a figure with a K or M suffix); 0
|
|
* where the kernel publishes none. What separates the two CCDs of an
|
|
* X3D part, which are one class and near enough one clock. */
|
|
static unsigned sysfs_read_llc_kib(unsigned cpu)
|
|
{
|
|
char path[512];
|
|
char line[64];
|
|
unsigned val = 0;
|
|
char unit = 0;
|
|
FILE *fp;
|
|
|
|
snprintf(path, sizeof(path), CPU_CLASS_SYSFS "/cpu%u/cache/index3/size", cpu);
|
|
if (!(fp = fopen(path, "r")))
|
|
return 0;
|
|
line[0] = '\0';
|
|
if (!fgets(line, sizeof(line), fp))
|
|
line[0] = '\0';
|
|
fclose(fp);
|
|
if (sscanf(line, "%u%c", &val, &unit) < 1)
|
|
return 0;
|
|
if (unit == 'M' || unit == 'm')
|
|
return val * 1024;
|
|
if (unit == 'G' || unit == 'g')
|
|
return val * 1024 * 1024;
|
|
return val;
|
|
}
|
|
#endif
|
|
|
|
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || defined(__linux__)
|
|
struct cpu_proc_rank
|
|
{
|
|
unsigned cls; /* performance class from cpu_class.h, higher is faster */
|
|
unsigned llc; /* last-level cache behind the core, KiB */
|
|
unsigned freq; /* kHz, higher is a stronger core within its class */
|
|
unsigned id; /* OS processor identifier */
|
|
unsigned smt; /* 0 for the first processor on its core, else 1 */
|
|
};
|
|
|
|
/* Fastest class first (the P-cores, the big cluster); within a class
|
|
* the bigger last-level cache first, then the higher clock; a core
|
|
* ahead of its own SMT siblings; and the identifier as the tie-break
|
|
* so the result does not depend on the order the entries were
|
|
* gathered in. Class comes before everything so an E-core that
|
|
* happens to clock above a P-core sibling cannot outrank the fast
|
|
* silicon. Cache comes before clock for the X3D parts: the V-cache
|
|
* CCD boosts a few percent lower than the other and is the die an
|
|
* emulator wants to be on. */
|
|
static int cpu_proc_rank_cmp(const void *a, const void *b)
|
|
{
|
|
const struct cpu_proc_rank *l = (const struct cpu_proc_rank *)a;
|
|
const struct cpu_proc_rank *r = (const struct cpu_proc_rank *)b;
|
|
|
|
if (l->cls != r->cls)
|
|
return (l->cls > r->cls) ? -1 : 1;
|
|
if (l->llc != r->llc)
|
|
return (l->llc > r->llc) ? -1 : 1;
|
|
if (l->freq != r->freq)
|
|
return (l->freq > r->freq) ? -1 : 1;
|
|
if (l->smt != r->smt)
|
|
return (l->smt < r->smt) ? -1 : 1;
|
|
if (l->id != r->id)
|
|
return (l->id < r->id) ? -1 : 1;
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
/* The order, restricted to processors whose bit is set in allowed
|
|
* (NULL: no restriction). Split from the public function so a test
|
|
* can rank a fixture topology under a synthetic affinity mask. */
|
|
static size_t cpu_features_processor_order_masked(
|
|
const unsigned char *allowed, unsigned *s, size_t len)
|
|
{
|
|
size_t n = 0;
|
|
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || defined(__linux__)
|
|
unsigned char klass[CPU_CLASS_MAX_IDS];
|
|
size_t n_class = cpu_class_read(klass, sizeof(klass));
|
|
#define CPU_PROC_CLASS(id) \
|
|
(((size_t)(id) < n_class) ? klass[(id)] : 0)
|
|
#define CPU_PROC_ALLOWED(id) \
|
|
(!allowed || ((size_t)(id) < CPU_CLASS_MAX_IDS && allowed[(id)]))
|
|
#endif
|
|
|
|
if (!s || !len)
|
|
return 0;
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
|
{
|
|
DWORD count = 0;
|
|
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf = cpu_win32_slpi(&count);
|
|
|
|
if (buf)
|
|
{
|
|
struct cpu_proc_rank *rank;
|
|
size_t cap = (size_t)cpu_features_get_core_amount();
|
|
|
|
if (cap < 1)
|
|
cap = 1;
|
|
if (cap > 1024)
|
|
cap = 1024;
|
|
|
|
if ((rank = (struct cpu_proc_rank *)
|
|
malloc(cap * sizeof(struct cpu_proc_rank))))
|
|
{
|
|
DWORD i;
|
|
for (i = 0; i < count && n < cap; i++)
|
|
{
|
|
unsigned bit;
|
|
unsigned seen = 0;
|
|
ULONG_PTR mask = buf[i].ProcessorMask;
|
|
|
|
if (buf[i].Relationship != RelationProcessorCore)
|
|
continue;
|
|
|
|
/* One mask bit per processor on this core, the lowest
|
|
* of them being the one an SMT sibling shares with. */
|
|
for (bit = 0; bit < sizeof(ULONG_PTR) * 8 && n < cap; bit++)
|
|
{
|
|
if (!(mask & (((ULONG_PTR)1) << bit)))
|
|
continue;
|
|
if (!CPU_PROC_ALLOWED(bit))
|
|
{
|
|
seen++;
|
|
continue;
|
|
}
|
|
rank[n].cls = CPU_PROC_CLASS(bit);
|
|
rank[n].llc = cpu_win32_llc_kib(buf, count, bit);
|
|
rank[n].freq = 0;
|
|
rank[n].id = bit;
|
|
rank[n].smt = seen ? 1 : 0;
|
|
seen++;
|
|
n++;
|
|
}
|
|
}
|
|
|
|
if (n)
|
|
{
|
|
qsort(rank, n, sizeof(struct cpu_proc_rank),
|
|
cpu_proc_rank_cmp);
|
|
if (n > len)
|
|
n = len;
|
|
for (i = 0; i < (DWORD)n; i++)
|
|
s[i] = rank[i].id;
|
|
}
|
|
free(rank);
|
|
}
|
|
free(buf);
|
|
|
|
if (n)
|
|
return n;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
#if defined(__linux__)
|
|
{
|
|
struct cpu_proc_rank *rank;
|
|
char path[512];
|
|
unsigned i;
|
|
/* Every processor is ranked before any is handed back: gathering
|
|
* only the first @len of them would sort a set chosen by
|
|
* identifier and hand back the weakest cores on a layout that
|
|
* numbers the little cluster first. */
|
|
size_t cap = 1024; /* every processor the kernel publishes; 16 KiB */
|
|
|
|
if (!(rank = (struct cpu_proc_rank *)
|
|
malloc(cap * sizeof(struct cpu_proc_rank))))
|
|
return 0;
|
|
|
|
for (i = 0; i < 1024 && n < cap; i++)
|
|
{
|
|
unsigned first;
|
|
|
|
snprintf(path, sizeof(path),
|
|
CPU_CLASS_SYSFS "/cpu%u/topology/thread_siblings_list", i);
|
|
/* A processor with no sibling list is one the kernel is not
|
|
* publishing, rather than one that shares no core. */
|
|
first = sysfs_read_uint(path, (unsigned)-1);
|
|
if (first == (unsigned)-1)
|
|
continue;
|
|
if (!CPU_PROC_ALLOWED(i))
|
|
continue;
|
|
|
|
snprintf(path, sizeof(path),
|
|
CPU_CLASS_SYSFS "/cpu%u/cpufreq/cpuinfo_max_freq", i);
|
|
|
|
rank[n].cls = CPU_PROC_CLASS(i);
|
|
rank[n].llc = sysfs_read_llc_kib(i);
|
|
rank[n].freq = sysfs_read_uint(path, 0);
|
|
rank[n].id = i;
|
|
rank[n].smt = (i == first) ? 0 : 1;
|
|
n++;
|
|
}
|
|
|
|
if (n)
|
|
{
|
|
qsort(rank, n, sizeof(struct cpu_proc_rank), cpu_proc_rank_cmp);
|
|
if (n > len)
|
|
n = len;
|
|
for (i = 0; i < (unsigned)n; i++)
|
|
s[i] = rank[i].id;
|
|
}
|
|
|
|
free(rank);
|
|
|
|
if (n)
|
|
return n;
|
|
}
|
|
#endif
|
|
|
|
/* No topology to rank by, so name each processor once in order. */
|
|
{
|
|
unsigned amount = cpu_features_get_core_amount();
|
|
unsigned i;
|
|
for (i = 0; n < len && i < amount; i++)
|
|
{
|
|
if (allowed && (i >= CPU_CLASS_MAX_IDS || !allowed[i]))
|
|
continue;
|
|
s[n++] = i;
|
|
}
|
|
}
|
|
#if (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)) || defined(__linux__)
|
|
#undef CPU_PROC_CLASS
|
|
#undef CPU_PROC_ALLOWED
|
|
#endif
|
|
return n;
|
|
}
|
|
|
|
size_t cpu_features_get_processor_order(unsigned *s, size_t len)
|
|
{
|
|
#if defined(__linux__) || (defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__))
|
|
unsigned char allowed[CPU_CLASS_MAX_IDS];
|
|
#endif
|
|
const unsigned char *mask = NULL;
|
|
#if defined(__linux__)
|
|
{
|
|
/* The processors this thread may run on: a pin from a parent
|
|
* process, a container or the user is a boundary, not something
|
|
* to hand back as a target. */
|
|
unsigned long bits[CPU_CLASS_MAX_IDS / (8 * sizeof(unsigned long))];
|
|
memset(bits, 0, sizeof(bits));
|
|
if (syscall(__NR_sched_getaffinity, 0, sizeof(bits), bits) > 0)
|
|
{
|
|
size_t i;
|
|
for (i = 0; i < CPU_CLASS_MAX_IDS; i++)
|
|
allowed[i] = (unsigned char)((bits[i / (8 * sizeof(unsigned long))]
|
|
>> (i % (8 * sizeof(unsigned long)))) & 1ul);
|
|
mask = allowed;
|
|
}
|
|
}
|
|
#elif defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
|
{
|
|
DWORD_PTR proc = 0, sys = 0;
|
|
if (GetProcessAffinityMask(GetCurrentProcess(), &proc, &sys) && proc)
|
|
{
|
|
size_t i;
|
|
memset(allowed, 0, sizeof(allowed));
|
|
for (i = 0; i < sizeof(DWORD_PTR) * 8; i++)
|
|
allowed[i] = (unsigned char)((proc >> i) & 1);
|
|
mask = allowed;
|
|
}
|
|
}
|
|
#endif
|
|
return cpu_features_processor_order_masked(mask, s, len);
|
|
}
|
|
|
|
unsigned cpu_features_get_core_amount_physical(void)
|
|
{
|
|
unsigned logical = cpu_features_get_core_amount();
|
|
|
|
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
|
|
{
|
|
DWORD count = 0;
|
|
SYSTEM_LOGICAL_PROCESSOR_INFORMATION *buf = cpu_win32_slpi(&count);
|
|
|
|
if (buf)
|
|
{
|
|
DWORD i;
|
|
unsigned cores = 0;
|
|
for (i = 0; i < count; i++)
|
|
if (buf[i].Relationship == RelationProcessorCore)
|
|
cores++;
|
|
free(buf);
|
|
if (cores > 0)
|
|
return cores;
|
|
}
|
|
}
|
|
#elif defined(__APPLE__)
|
|
{
|
|
/* Darwin publishes both counts, so the physical one is a read
|
|
* rather than a derivation. */
|
|
int val = 0;
|
|
size_t _len = sizeof(val);
|
|
if ( sysctlbyname("hw.physicalcpu", &val, &_len, NULL, 0) == 0
|
|
&& val > 0)
|
|
return (unsigned)val;
|
|
}
|
|
#elif defined(__linux__)
|
|
return linux_core_amount_physical(logical);
|
|
#endif
|
|
|
|
/* Every other target either has no SMT to discount or publishes no
|
|
* way to tell, and the thread count is the safe answer in both
|
|
* cases. */
|
|
return logical;
|
|
}
|
|
|
|
unsigned cpu_features_get_core_amount(void)
|
|
{
|
|
#if defined(_WIN32) && !defined(_XBOX)
|
|
/* Win32 */
|
|
SYSTEM_INFO sysinfo;
|
|
#if defined(__WINRT__) || (defined(WINAPI_FAMILY) && defined(WINAPI_FAMILY_PHONE_APP) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP)
|
|
GetNativeSystemInfo(&sysinfo);
|
|
#else
|
|
GetSystemInfo(&sysinfo);
|
|
#endif
|
|
return sysinfo.dwNumberOfProcessors;
|
|
#elif defined(GEKKO)
|
|
return 1;
|
|
#elif defined(PSP) || defined(PS2)
|
|
return 1;
|
|
#elif defined(__PSL1GHT__) || !defined(__PSL1GHT__) && defined(__PS3__)
|
|
return 1; /* Only one PPU, SPUs don't really count */
|
|
#elif defined(VITA)
|
|
return 4;
|
|
#elif defined(HAVE_LIBNX) || defined(SWITCH)
|
|
return 4;
|
|
#elif defined(_3DS)
|
|
u8 device_model = 0xFF;
|
|
CFGU_GetSystemModel(&device_model);/*(0 = O3DS, 1 = O3DSXL, 2 = N3DS, 3 = 2DS, 4 = N3DSXL, 5 = N2DSXL)*/
|
|
switch (device_model)
|
|
{
|
|
case 0:
|
|
case 1:
|
|
case 3:
|
|
/*Old 3/2DS*/
|
|
return 2;
|
|
|
|
case 2:
|
|
case 4:
|
|
case 5:
|
|
/*New 3/2DS*/
|
|
return 4;
|
|
|
|
default:
|
|
/*Unknown Device Or Check Failed*/
|
|
break;
|
|
}
|
|
return 1;
|
|
#elif defined(WIIU)
|
|
return 3;
|
|
#elif defined(_SC_NPROCESSORS_ONLN)
|
|
/* Linux, most UNIX-likes. */
|
|
long ret = sysconf(_SC_NPROCESSORS_ONLN);
|
|
if (ret <= 0)
|
|
return (unsigned)1;
|
|
return (unsigned)ret;
|
|
#elif defined(BSD) || defined(__APPLE__)
|
|
/* BSD */
|
|
/* Copypasta from stackoverflow, dunno if it works. */
|
|
int num_cpu = 0;
|
|
int mib[4];
|
|
size_t _len = sizeof(num_cpu);
|
|
|
|
mib[0] = CTL_HW;
|
|
mib[1] = HW_AVAILCPU;
|
|
sysctl(mib, 2, &num_cpu, &_len, NULL, 0);
|
|
if (num_cpu < 1)
|
|
{
|
|
mib[1] = HW_NCPU;
|
|
sysctl(mib, 2, &num_cpu, &_len, NULL, 0);
|
|
if (num_cpu < 1)
|
|
num_cpu = 1;
|
|
}
|
|
return num_cpu;
|
|
#elif defined(__linux__)
|
|
CpuList cpus_present[1];
|
|
CpuList cpus_possible[1];
|
|
int amount = 0;
|
|
|
|
cpulist_read_from(cpus_present, "/sys/devices/system/cpu/present");
|
|
cpulist_read_from(cpus_possible, "/sys/devices/system/cpu/possible");
|
|
|
|
/* Compute the intersection of both sets to get the actual number of
|
|
* CPU cores that can be used on this device by the kernel.
|
|
*/
|
|
cpus_present->mask &= cpus_possible->mask;
|
|
amount = __builtin_popcount(cpus_present->mask);
|
|
|
|
if (amount == 0)
|
|
return 1;
|
|
return amount;
|
|
#elif defined(_XBOX360)
|
|
return 3;
|
|
#else
|
|
/* No idea, assume single core. */
|
|
return 1;
|
|
#endif
|
|
}
|
|
|
|
/* According to http://en.wikipedia.org/wiki/CPUID */
|
|
#define VENDOR_INTEL_b 0x756e6547
|
|
#define VENDOR_INTEL_c 0x6c65746e
|
|
#define VENDOR_INTEL_d 0x49656e69
|
|
|
|
#if defined(__MACH__) && defined(CPU_X86)
|
|
/* Whole-token search of one of the machdep.cpu.*features sysctls, each
|
|
* of which is a space separated list of CPUID feature names. */
|
|
static bool darwin_cpu_feature_present(const char *key, const char *want)
|
|
{
|
|
char buf[1024];
|
|
size_t len = sizeof(buf);
|
|
size_t wlen = strlen(want);
|
|
const char *p;
|
|
|
|
buf[0] = '\0';
|
|
if (sysctlbyname(key, buf, &len, NULL, 0) != 0)
|
|
return false;
|
|
buf[sizeof(buf) - 1] = '\0';
|
|
|
|
for (p = buf; (p = strstr(p, want)); p += wlen)
|
|
{
|
|
char after = p[wlen];
|
|
char before = (p == buf) ? ' ' : p[-1];
|
|
if ((before == ' ') && (after == ' ' || after == '\0'))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
static uint64_t cpu_features_probe(void)
|
|
{
|
|
uint64_t cpu = 0;
|
|
#if defined(CPU_X86) && !defined(__MACH__)
|
|
int vendor_is_intel = 0;
|
|
/* Set once the OS is known to preserve YMM state, which AVX, FMA3
|
|
* and FMA4 all depend on. */
|
|
int ymm_state = 0;
|
|
const int avx_flags = (1 << 27) | (1 << 28);
|
|
#endif
|
|
#if defined(__MACH__)
|
|
/* sysctlbyname() returns 0 (success) whenever the key exists, regardless
|
|
* of its value. On Intel Macs the hw.optional.* keys are always present
|
|
* but report 0 when the feature is unsupported (e.g. avx512f on pre-Skylake
|
|
* Xeon CPUs). We therefore have to read the value, not just the success
|
|
* code. */
|
|
int _val = 0;
|
|
size_t _len = sizeof(_val);
|
|
if ( sysctlbyname("hw.optional.floatingpoint", &_val, &_len, NULL, 0) == 0
|
|
&& _val)
|
|
cpu |= RETRO_SIMD_CMOV;
|
|
|
|
#if defined(CPU_X86)
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.mmx", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_MMX | RETRO_SIMD_MMXEXT;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.sse", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_SSE;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.sse2", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_SSE2;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.sse3", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_SSE3;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.supplementalsse3", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_SSSE3;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.sse4_1", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_SSE4;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.sse4_2", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_SSE42;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.aes", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_AES;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.avx1_0", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_AVX;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.fma", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_FMA3;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.avx2_0", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_AVX2;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.avx512f", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_AVX512;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.altivec", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_VMX;
|
|
/* Darwin publishes no hw.optional key for PCLMULQDQ, so read the
|
|
* CPUID leaf-1 feature-name list instead. Matching is on whole
|
|
* space-separated tokens: a plain strstr() would also fire on a
|
|
* hypothetical future "PCLMULQDQ2". */
|
|
if (darwin_cpu_feature_present("machdep.cpu.features", "PCLMULQDQ"))
|
|
cpu |= RETRO_SIMD_PCLMUL;
|
|
/* SHA-NI lives in the leaf-7 list rather than the leaf-1 one. */
|
|
if (darwin_cpu_feature_present("machdep.cpu.leaf7_features", "SHA"))
|
|
cpu |= RETRO_SIMD_SHA1 | RETRO_SIMD_SHA256;
|
|
#else
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
/* Older key first; newer systems also carry the FEAT_ spelling.
|
|
* Written out rather than folded into one condition with a comma
|
|
* operator: the second query needs the buffer and its length reset
|
|
* first, and doing that inside a short-circuit || is both a warning
|
|
* and a thing to read twice. */
|
|
if (sysctlbyname("hw.optional.armv8_crc32", &_val, &_len, NULL, 0) == 0
|
|
&& _val)
|
|
cpu |= RETRO_SIMD_CRC32;
|
|
else
|
|
{
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.arm.FEAT_CRC32", &_val, &_len, NULL, 0) == 0
|
|
&& _val)
|
|
cpu |= RETRO_SIMD_CRC32;
|
|
}
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.arm.FEAT_AES", &_val, &_len, NULL, 0) == 0
|
|
&& _val)
|
|
cpu |= RETRO_SIMD_AES;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.arm.FEAT_SHA512", &_val, &_len, NULL, 0) == 0
|
|
&& _val)
|
|
cpu |= RETRO_SIMD_SHA512;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.arm.FEAT_SHA1", &_val, &_len, NULL, 0) == 0
|
|
&& _val)
|
|
cpu |= RETRO_SIMD_SHA1;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.arm.FEAT_SHA256", &_val, &_len, NULL, 0) == 0
|
|
&& _val)
|
|
cpu |= RETRO_SIMD_SHA256;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.neon", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_NEON;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.neon_fp16", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_VFPV3;
|
|
_val = 0;
|
|
_len = sizeof(_val);
|
|
if (sysctlbyname("hw.optional.neon_hpfp", &_val, &_len, NULL, 0) == 0 && _val)
|
|
cpu |= RETRO_SIMD_VFPV4;
|
|
#endif
|
|
#elif defined(_XBOX1)
|
|
cpu |= RETRO_SIMD_MMX | RETRO_SIMD_SSE | RETRO_SIMD_MMXEXT;
|
|
#elif defined(CPU_X86)
|
|
unsigned max_flag = 0;
|
|
int32_t flags[4];
|
|
int vendor_shuffle[3];
|
|
char vendor[13];
|
|
x86_cpuid(0, flags);
|
|
vendor_shuffle[0] = flags[1];
|
|
vendor_shuffle[1] = flags[3];
|
|
vendor_shuffle[2] = flags[2];
|
|
|
|
vendor[0] = '\0';
|
|
memcpy(vendor, vendor_shuffle, sizeof(vendor_shuffle));
|
|
|
|
/* printf("[CPUID]: Vendor: %s\n", vendor); */
|
|
|
|
vendor_is_intel = (
|
|
flags[1] == VENDOR_INTEL_b
|
|
&& flags[2] == VENDOR_INTEL_c
|
|
&& flags[3] == VENDOR_INTEL_d);
|
|
|
|
/* CPUID register contents are unsigned; flags[] is int32_t, so the
|
|
* widening is spelled out rather than left implicit -- leaf
|
|
* 0x80000000 legitimately returns values with the top bit set. */
|
|
max_flag = (uint32_t)flags[0];
|
|
/* Does CPUID not support func = 1? (unlikely ...) */
|
|
if (max_flag < 1)
|
|
return 0;
|
|
|
|
x86_cpuid(1, flags);
|
|
|
|
if (flags[3] & (1 << 15))
|
|
cpu |= RETRO_SIMD_CMOV;
|
|
|
|
if (flags[3] & (1 << 23))
|
|
cpu |= RETRO_SIMD_MMX;
|
|
|
|
/* SSE also implies MMXEXT (according to FFmpeg source). */
|
|
if (flags[3] & (1 << 25))
|
|
cpu |= RETRO_SIMD_SSE | RETRO_SIMD_MMXEXT;
|
|
|
|
if (flags[3] & (1 << 26))
|
|
cpu |= RETRO_SIMD_SSE2;
|
|
|
|
if (flags[2] & (1 << 0))
|
|
cpu |= RETRO_SIMD_SSE3;
|
|
|
|
if (flags[2] & (1 << 9))
|
|
cpu |= RETRO_SIMD_SSSE3;
|
|
|
|
if (flags[2] & (1 << 19))
|
|
cpu |= RETRO_SIMD_SSE4;
|
|
|
|
if (flags[2] & (1 << 20))
|
|
cpu |= RETRO_SIMD_SSE42;
|
|
|
|
if ((flags[2] & (1 << 23)))
|
|
cpu |= RETRO_SIMD_POPCNT;
|
|
|
|
if (vendor_is_intel && (flags[2] & (1 << 22)))
|
|
cpu |= RETRO_SIMD_MOVBE;
|
|
|
|
if (flags[2] & (1 << 25))
|
|
cpu |= RETRO_SIMD_AES;
|
|
|
|
if (flags[2] & (1 << 1))
|
|
cpu |= RETRO_SIMD_PCLMUL;
|
|
|
|
/* Must only perform xgetbv check if we have
|
|
* AVX CPU support (guaranteed to have at least i686). */
|
|
if (((flags[2] & avx_flags) == avx_flags)
|
|
&& ((xgetbv_x86(0) & 0x6) == 0x6))
|
|
{
|
|
ymm_state = 1;
|
|
cpu |= RETRO_SIMD_AVX;
|
|
}
|
|
|
|
/* FMA3 accumulates in YMM, so it answers only where the OS keeps
|
|
* that state. Leaf 1 ECX is still in flags here; the extended leaf
|
|
* carrying FMA4 is read further down. */
|
|
if (ymm_state && (flags[2] & (1 << 12)))
|
|
cpu |= RETRO_SIMD_FMA3;
|
|
|
|
if (max_flag >= 7)
|
|
{
|
|
/* CPUID leaf 7 sub-leaf 0 requires ECX = 0.
|
|
* x86_cpuid() does not set ECX, so call cpuid directly. */
|
|
int32_t flags7[4];
|
|
#if defined(__GNUC__)
|
|
__asm__ volatile (
|
|
"mov %%" REG_b ", %%" REG_S "\n"
|
|
"cpuid\n"
|
|
"xchg %%" REG_b ", %%" REG_S "\n"
|
|
: "=a"(flags7[0]), "=S"(flags7[1]), "=c"(flags7[2]), "=d"(flags7[3])
|
|
: "a"(7), "c"(0));
|
|
#elif defined(_MSC_VER) && INT_MAX == 2147483647
|
|
#if _MSC_VER >= 1600
|
|
__cpuidex((int*)flags7, 7, 0);
|
|
#else
|
|
{
|
|
int *p = (int*)flags7;
|
|
__asm {
|
|
mov eax, 7
|
|
xor ecx, ecx
|
|
cpuid
|
|
mov esi, p
|
|
mov [esi], eax
|
|
mov [esi + 4], ebx
|
|
mov [esi + 8], ecx
|
|
mov [esi + 12], edx
|
|
}
|
|
}
|
|
#endif
|
|
#else
|
|
memset(flags7, 0, sizeof(flags7));
|
|
#endif
|
|
|
|
if (flags7[1] & (1 << 5))
|
|
cpu |= RETRO_SIMD_AVX2;
|
|
|
|
/* SHA-NI is one bit covering both digests. */
|
|
if (flags7[1] & (1 << 29))
|
|
cpu |= RETRO_SIMD_SHA1 | RETRO_SIMD_SHA256;
|
|
|
|
/* AVX-512 Foundation detection.
|
|
* Requires CPUID leaf 7 sub-leaf 0 EBX bit 16 (AVX-512F),
|
|
* and OS support for saving ZMM state:
|
|
* xgetbv XCR0 bits 1,2 (SSE/AVX) and 5,6,7 (opmask, ZMM_Hi256, Hi16_ZMM). */
|
|
if ((flags7[1] & (1 << 16))
|
|
&& ((xgetbv_x86(0) & 0xe6) == 0xe6))
|
|
cpu |= RETRO_SIMD_AVX512;
|
|
|
|
/* Leaf 7 EAX carries the highest sub-leaf the CPU implements, so
|
|
* consult it before asking for sub-leaf 1: an out of range
|
|
* sub-leaf returns zeroes on current parts but is not documented
|
|
* to. */
|
|
if (flags7[0] >= 1)
|
|
{
|
|
int32_t flags71[4];
|
|
#if defined(__GNUC__)
|
|
__asm__ volatile (
|
|
"mov %%" REG_b ", %%" REG_S "\n"
|
|
"cpuid\n"
|
|
"xchg %%" REG_b ", %%" REG_S "\n"
|
|
: "=a"(flags71[0]), "=S"(flags71[1]), "=c"(flags71[2]), "=d"(flags71[3])
|
|
: "a"(7), "c"(1));
|
|
#elif defined(_MSC_VER) && INT_MAX == 2147483647
|
|
#if _MSC_VER >= 1600
|
|
__cpuidex((int*)flags71, 7, 1);
|
|
#else
|
|
{
|
|
int *p = (int*)flags71;
|
|
__asm {
|
|
mov eax, 7
|
|
mov ecx, 1
|
|
cpuid
|
|
mov esi, p
|
|
mov [esi], eax
|
|
mov [esi + 4], ebx
|
|
mov [esi + 8], ecx
|
|
mov [esi + 12], edx
|
|
}
|
|
}
|
|
#endif
|
|
#else
|
|
memset(flags71, 0, sizeof(flags71));
|
|
#endif
|
|
|
|
if (flags71[0] & (1 << 0))
|
|
cpu |= RETRO_SIMD_SHA512;
|
|
}
|
|
}
|
|
|
|
x86_cpuid(0x80000000, flags);
|
|
max_flag = (uint32_t)flags[0];
|
|
if (max_flag >= 0x80000001u)
|
|
{
|
|
x86_cpuid(0x80000001, flags);
|
|
if (flags[3] & (1 << 23))
|
|
cpu |= RETRO_SIMD_MMX;
|
|
if (flags[3] & (1 << 22))
|
|
cpu |= RETRO_SIMD_MMXEXT;
|
|
/* LZCNT / ABM (ECX bit 5). macOS x86 uses the sysctl path above and
|
|
* does not report this bit; consumers there fall back accordingly. */
|
|
if (flags[2] & (1 << 5))
|
|
cpu |= RETRO_SIMD_LZCNT;
|
|
/* FMA4, an AMD encoding that no Zen part implements, under the
|
|
* same OS state as FMA3. */
|
|
if (ymm_state && (flags[2] & (1 << 16)))
|
|
cpu |= RETRO_SIMD_FMA4;
|
|
}
|
|
#elif defined(__linux__)
|
|
if (check_arm_cpu_feature("neon"))
|
|
{
|
|
cpu |= RETRO_SIMD_NEON;
|
|
#ifdef CPU_ARM_RUNFAST
|
|
arm_enable_runfast_mode();
|
|
#endif
|
|
}
|
|
|
|
if (check_arm_cpu_feature("vfpv3"))
|
|
cpu |= RETRO_SIMD_VFPV3;
|
|
|
|
if (check_arm_cpu_feature("vfpv4"))
|
|
cpu |= RETRO_SIMD_VFPV4;
|
|
|
|
/* Part of the optional Cryptographic Extension, which an
|
|
* implementation may leave out or hold in reset, so a 64-bit ARM
|
|
* CPU does not imply it. */
|
|
if (check_arm_cpu_feature("aes"))
|
|
cpu |= RETRO_SIMD_AES;
|
|
|
|
/* aarch64 lists it as "crc32" in the Features: line. */
|
|
if (check_arm_cpu_feature("crc32"))
|
|
cpu |= RETRO_SIMD_CRC32;
|
|
|
|
if (check_arm_cpu_feature("sha512"))
|
|
cpu |= RETRO_SIMD_SHA512;
|
|
|
|
if (check_arm_cpu_feature("sha1"))
|
|
cpu |= RETRO_SIMD_SHA1;
|
|
|
|
if (check_arm_cpu_feature("sha2"))
|
|
cpu |= RETRO_SIMD_SHA256;
|
|
|
|
if (check_arm_cpu_feature("asimd"))
|
|
{
|
|
cpu |= RETRO_SIMD_ASIMD;
|
|
|
|
/* ASIMD *is* NEON: Advanced SIMD is what aarch64 kernels call
|
|
* it in the Features: line, where 32-bit ARM says "neon". It
|
|
* is architecturally mandatory in ARMv8-A, so a CPU reporting
|
|
* asimd has NEON by definition and callers testing
|
|
* RETRO_SIMD_NEON must see it.
|
|
*
|
|
* This used to be gated on __ARM_NEON__, which is the LEGACY
|
|
* 32-bit spelling. aarch64 toolchains define __ARM_NEON (no
|
|
* trailing underscores) instead - verified against the NDK's
|
|
* own compiler, which reports __ARM_NEON 1 and no __ARM_NEON__
|
|
* for aarch64-linux-android - so the guard was false on every
|
|
* 64-bit build and NEON was never reported there. That is why
|
|
* System Information listed ASIMD alone on a device whose CPU
|
|
* has had NEON since it was designed. */
|
|
cpu |= RETRO_SIMD_NEON;
|
|
|
|
#ifdef CPU_ARM_RUNFAST
|
|
arm_enable_runfast_mode();
|
|
#endif
|
|
}
|
|
#elif defined(__ARM_NEON) || defined(__ARM_NEON__)
|
|
cpu |= RETRO_SIMD_NEON;
|
|
#ifdef CPU_ARM_RUNFAST
|
|
arm_enable_runfast_mode();
|
|
#endif
|
|
#elif defined(__ALTIVEC__)
|
|
cpu |= RETRO_SIMD_VMX;
|
|
#elif defined(XBOX360)
|
|
cpu |= RETRO_SIMD_VMX128;
|
|
#elif defined(PSP) || defined(PS2)
|
|
cpu |= RETRO_SIMD_VFPU;
|
|
#elif defined(GEKKO)
|
|
cpu |= RETRO_SIMD_PS;
|
|
#endif
|
|
|
|
return cpu;
|
|
}
|
|
|
|
/* The probe is not cheap: on x86 it issues a chain of serialising CPUID
|
|
* instructions, and on ARM/Linux it opens and line-parses /proc/cpuinfo
|
|
* once per feature queried. The answer cannot change over the lifetime
|
|
* of the process, so probe once and hand out the cached mask thereafter.
|
|
* Callers that sit on a per-frame or per-asset path (the image decoders
|
|
* select their SIMD kernels this way) were paying the full probe on every
|
|
* single call. */
|
|
uint64_t cpu_features_get(void)
|
|
{
|
|
/* The mask is published with a release store and consumed with an
|
|
* acquire load, so a thread that observes the ready flag is
|
|
* guaranteed to see the fully written mask.
|
|
*
|
|
* The probe being idempotent is not on its own enough: if several
|
|
* threads reach it before any has published, they all write
|
|
* cpu_features_cache with plain stores, and those writes are
|
|
* unordered with respect to one another. Same value or not, that
|
|
* is a data race and ThreadSanitizer reports it. Elect exactly one
|
|
* writer with an atomic increment; the threads that lose return the
|
|
* value they computed themselves, which is identical, so nobody
|
|
* spins and nobody writes memory another thread is writing. */
|
|
static uint64_t cpu_features_cache;
|
|
static retro_atomic_int_t cpu_features_claim; /* 0 = unclaimed */
|
|
static retro_atomic_int_t cpu_features_ready; /* 0 = not probed yet */
|
|
uint64_t cpu;
|
|
|
|
if (retro_atomic_load_acquire_int(&cpu_features_ready))
|
|
return cpu_features_cache;
|
|
|
|
cpu = cpu_features_probe();
|
|
|
|
if (retro_atomic_fetch_add_int(&cpu_features_claim, 1) == 0)
|
|
{
|
|
cpu_features_cache = cpu;
|
|
retro_atomic_store_release_int(&cpu_features_ready, 1);
|
|
}
|
|
|
|
return cpu;
|
|
}
|
|
|
|
void cpu_features_get_model_name(char *s, int len)
|
|
{
|
|
#if defined(CPU_X86) && !defined(__MACH__)
|
|
union {
|
|
int32_t i[4];
|
|
uint32_t u[4];
|
|
uint8_t s[16];
|
|
} flags;
|
|
int i, j;
|
|
int pos = 0;
|
|
bool start = false;
|
|
|
|
if (!s)
|
|
return;
|
|
|
|
x86_cpuid(0x80000000, flags.i);
|
|
|
|
/* Check for additional cpuid attributes availability */
|
|
if (flags.u[0] < 0x80000004)
|
|
return;
|
|
|
|
for (i = 0; i < 3; i++)
|
|
{
|
|
memset(flags.i, 0, sizeof(flags.i));
|
|
x86_cpuid(0x80000002 + i, flags.i);
|
|
|
|
for (j = 0; j < (int)sizeof(flags.s); j++)
|
|
{
|
|
if (!start && flags.s[j] == ' ')
|
|
continue;
|
|
|
|
start = true;
|
|
|
|
if (pos == len - 1)
|
|
{
|
|
/* truncate if we ran out of room */
|
|
s[pos] = '\0';
|
|
goto end;
|
|
}
|
|
|
|
s[pos++] = flags.s[j];
|
|
}
|
|
}
|
|
end:
|
|
/* terminate our string */
|
|
if (pos < len)
|
|
s[pos] = '\0';
|
|
#elif defined(__MACH__)
|
|
if (!s)
|
|
return;
|
|
{
|
|
size_t __len = len;
|
|
sysctlbyname("machdep.cpu.brand_string", s, &__len, NULL, 0);
|
|
}
|
|
#elif defined(__linux__)
|
|
if (!s)
|
|
return;
|
|
{
|
|
char *model_name, line[128];
|
|
FILE *fp = fopen("/proc/cpuinfo", "r");
|
|
|
|
if (!fp)
|
|
return;
|
|
|
|
while (fgets(line, sizeof(line), fp))
|
|
{
|
|
if (strncmp(line, "model name", 10))
|
|
continue;
|
|
|
|
if ((model_name = strstr(line + 10, ": ")))
|
|
{
|
|
model_name += 2;
|
|
strlcpy(s, model_name, len);
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
fclose(fp);
|
|
|
|
#if defined(WEBOS)
|
|
struct stat st;
|
|
if (stat("/usr/bin/lscpu", &st) == 0)
|
|
{
|
|
FILE *pipe = popen("/usr/bin/lscpu", "r");
|
|
if (pipe)
|
|
{
|
|
char buf[256];
|
|
|
|
while (fgets(buf, sizeof(buf), pipe))
|
|
{
|
|
if (strncmp(buf, "Model name:", 11) == 0)
|
|
{
|
|
const char *p = strchr(buf, ':');
|
|
if (p)
|
|
{
|
|
size_t len2;
|
|
p++; /* skip ':' */
|
|
while (*p == ' ' || *p == '\t')
|
|
p++;
|
|
len2 = strcspn(p, "\r\n");
|
|
|
|
if (len2 > 0)
|
|
{
|
|
char *tmp = malloc(len2 + 1);
|
|
if (tmp)
|
|
{
|
|
memcpy(tmp, p, len2);
|
|
tmp[len2] = '\0';
|
|
|
|
if (s[0] != '\0')
|
|
{
|
|
size_t oldlen = strlen(s);
|
|
char *combined = (char*)malloc(oldlen + len2 + 4);
|
|
if (combined)
|
|
{
|
|
memcpy(combined, s, oldlen);
|
|
combined[oldlen] = ' ';
|
|
combined[oldlen + 1] = '(';
|
|
memcpy(combined + oldlen + 2, tmp, len2);
|
|
combined[oldlen + 2 + len2] = ')';
|
|
combined[oldlen + 2 + len2 + 1] = '\0';
|
|
|
|
strlcpy(s, combined, len);
|
|
free(combined);
|
|
}
|
|
}
|
|
else
|
|
strlcpy(s, tmp, len);
|
|
free(tmp);
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
pclose(pipe);
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/* Sleep until cpu_features_get_time_usec() reads at least @deadline.
|
|
* It lives here, beside the clock it is measured on, and nowhere a
|
|
* launcher compiles: the salamanders build rtime.c for localtime alone
|
|
* and never link this file.
|
|
*
|
|
* Absolute where the platform offers it, so the time between reading
|
|
* the clock and entering the kernel, and any early or interrupted
|
|
* wake, are not added to when the caller comes back. Never early
|
|
* against that clock on any backend; late as every sleep may be, and
|
|
* a caller that needs the instant itself sleeps short and spins the
|
|
* rest, as the frame limiter does with its measured margin. */
|
|
#if (defined(__linux__) || defined(ANDROID)) && !defined(__MACH__)
|
|
/* The exact tool: the clock above is clock_gettime(CLOCK_MONOTONIC)
|
|
* here, and clock_nanosleep() takes an absolute deadline on the same
|
|
* clock. EINTR re-arms against the unchanged deadline by definition
|
|
* of TIMER_ABSTIME. */
|
|
void retro_sleep_until_us(retro_time_t deadline)
|
|
{
|
|
struct timespec ts;
|
|
ts.tv_sec = (time_t)(deadline / 1000000);
|
|
ts.tv_nsec = (long)((deadline % 1000000) * 1000);
|
|
while (clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &ts, NULL) != 0)
|
|
;
|
|
}
|
|
#elif defined(__APPLE__) && defined(__MACH__)
|
|
#include <mach/mach_time.h>
|
|
/* mach_wait_until() is the absolute wait, on the Mach clock; the
|
|
* public microsecond clock above is CLOCK_MONOTONIC, which Darwin
|
|
* derives from the same hardware ticks. The two are bridged with one
|
|
* paired read - nanoseconds apart - and unlike a relative sleep the
|
|
* arming is still absolute in Mach terms, so an early wake or the
|
|
* kernel's leeway re-arms nothing and accumulates nothing. */
|
|
void retro_sleep_until_us(retro_time_t deadline)
|
|
{
|
|
static mach_timebase_info_data_t tb;
|
|
uint64_t ticks;
|
|
retro_time_t now = cpu_features_get_time_usec();
|
|
uint64_t mach_now = mach_absolute_time();
|
|
if (deadline <= now)
|
|
return;
|
|
/* A constant; a racing first read fills in the same values. */
|
|
if (!tb.denom)
|
|
mach_timebase_info(&tb);
|
|
ticks = (uint64_t)(deadline - now) * 1000 * tb.denom / tb.numer;
|
|
mach_wait_until(mach_now + ticks);
|
|
}
|
|
#else
|
|
/* Windows and everything else: re-arm the platform's best relative
|
|
* wait - retro_sleep_us from retro_timers.h, included above on every
|
|
* platform - against the deadline until the clock agrees. On desktop
|
|
* Windows that wait is rtime.c's per-thread high-resolution timer, so
|
|
* each lap is microsecond-grained; on a platform whose sleep rounds
|
|
* up the loop ends one lap past the deadline, no worse than the
|
|
* relative call was. */
|
|
void retro_sleep_until_us(retro_time_t deadline)
|
|
{
|
|
for (;;)
|
|
{
|
|
retro_time_t now = cpu_features_get_time_usec();
|
|
if (now >= deadline)
|
|
return;
|
|
retro_sleep_us((unsigned)(deadline - now));
|
|
}
|
|
}
|
|
#endif
|