Files
jpolo1224 8caacc8231 Android: correct persisted off-spec settings, file:// launches, and RSS reporting
Accurate SPU Reservations was persisted false in the global config, left over from
earlier debugging, where upstream and our own defaults are both true. Turning the
default back on reached nobody who had already run the app, so this migrates the
stored value -- correcting the curated field and forgetting the raw override at
global scope only, since a per-title exception exists on purpose and
forgetEverywhere() would take it with it. Save LLVM logs had the same problem and
needed the value recorded, not just the override un-pinned.

A file:// launch never booted: the intent path was passed through as a URI string
and the loader wants a filesystem path, so only content:// ever worked.

get_memory_usage() reports system-wide totals -- MemTotal minus MemAvailable, every
process on the machine plus page cache -- and was being read as if it were ours.
Add get_process_memory_usage() for this process's resident set, which is the number
Android's low-memory killer actually decides on, and report that instead.
2026-08-17 20:50:43 -04:00

1275 lines
29 KiB
C++
Executable File

#include "util/sysinfo.hpp"
#include <cstdio>
#include "Utilities/StrFmt.h"
#include "Utilities/File.h"
#include "Emu/vfs_config.h"
#include "Utilities/Thread.h"
#include "rpcs3_version.h"
#if defined(ARCH_ARM64)
#include "Emu/CPU/Backends/AArch64/AArch64Common.h"
#include <arm_sve.h>
#endif
#ifdef _WIN32
#include "windows.h"
#include "sysinfoapi.h"
#include "subauth.h"
#include "stringapiset.h"
#include "util/dyn_lib.hpp"
DYNAMIC_IMPORT("ntdll.dll", RtlGetVersion, NTSTATUS(OSVERSIONINFOW* lpVersionInformation));
#else
#include <unistd.h>
#include <sys/resource.h>
#ifdef __APPLE__
#include <sys/sysctl.h>
#else
#include <sys/utsname.h>
#include <errno.h>
#if defined(ARCH_ARM64) && defined(__linux__)
#include <sys/auxv.h>
#include <asm/hwcap.h>
#endif
#endif
#endif
#include <thread>
#include <fstream>
#include "util/asm.hpp"
#include "util/fence.hpp"
#if defined(_M_X64) && defined(_MSC_VER)
extern "C" u64 _xgetbv(u32);
#endif
#if defined(ARCH_X64)
static inline std::array<u32, 4> get_cpuid(u32 func, u32 subfunc)
{
int regs[4];
#ifdef _MSC_VER
__cpuidex(regs, func, subfunc);
#else
__asm__ volatile("cpuid" : "=a" (regs[0]), "=b" (regs[1]), "=c" (regs[2]), "=d" (regs[3]) : "a" (func), "c" (subfunc));
#endif
return {0u+regs[0], 0u+regs[1], 0u+regs[2], 0u+regs[3]};
}
static inline u64 get_xgetbv(u32 xcr)
{
#ifdef _MSC_VER
return _xgetbv(xcr);
#else
u32 eax, edx;
__asm__ volatile("xgetbv" : "=a"(eax), "=d"(edx) : "c"(xcr));
return eax | (u64(edx) << 32);
#endif
}
#endif
#ifdef __APPLE__
// sysinfo_darwin.mm
namespace Darwin_Version
{
extern int getNSmajorVersion();
extern int getNSminorVersion();
extern int getNSpatchVersion();
}
namespace Darwin_ProcessInfo
{
extern bool getLowPowerModeEnabled();
}
#endif
bool utils::has_ssse3()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x1 && get_cpuid(1, 0)[2] & 0x200;
return g_value;
#else
return false;
#endif
}
bool utils::has_sse41()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x1 && get_cpuid(1, 0)[2] & 0x80000;
return g_value;
#else
return false;
#endif
}
bool utils::has_avx()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x1 && get_cpuid(1, 0)[2] & 0x10000000 && (get_cpuid(1, 0)[2] & 0x0C000000) == 0x0C000000 && (get_xgetbv(0) & 0x6) == 0x6;
return g_value;
#else
return false;
#endif
}
bool utils::has_avx2()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && get_cpuid(7, 0)[1] & 0x20 && (get_cpuid(1, 0)[2] & 0x0C000000) == 0x0C000000 && (get_xgetbv(0) & 0x6) == 0x6;
return g_value;
#else
return false;
#endif
}
bool utils::has_rtm()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(7, 0)[1] & 0x800) == 0x800;
return g_value;
#elif defined(ARCH_ARM64)
return false;
#endif
}
bool utils::has_tsx_force_abort()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(7, 0)[3] & 0x2000) == 0x2000;
return g_value;
#else
return false;
#endif
}
bool utils::has_rtm_always_abort()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(7, 0)[3] & 0x800) == 0x800;
return g_value;
#else
return false;
#endif
}
bool utils::has_mpx()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(7, 0)[1] & 0x4000) == 0x4000;
return g_value;
#else
return false;
#endif
}
bool utils::has_avx512()
{
#if defined(ARCH_X64)
// Check AVX512F, AVX512CD, AVX512DQ, AVX512BW, AVX512VL extensions (Skylake-X level support)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(7, 0)[1] & 0xd0030000) == 0xd0030000 && (get_cpuid(1, 0)[2] & 0x0C000000) == 0x0C000000 && (get_xgetbv(0) & 0xe6) == 0xe6;
return g_value;
#else
return false;
#endif
}
bool utils::has_avx512_icl()
{
#if defined(ARCH_X64)
// Check AVX512IFMA, AVX512VBMI, AVX512VBMI2, AVX512VPOPCNTDQ, AVX512BITALG, AVX512VNNI, AVX512VPCLMULQDQ, AVX512GFNI, AVX512VAES (Icelake-client level support)
static const bool g_value = has_avx512() && (get_cpuid(7, 0)[1] & 0x00200000) == 0x00200000 && (get_cpuid(7, 0)[2] & 0x00005f42) == 0x00005f42;
return g_value;
#else
return false;
#endif
}
bool utils::has_avx512_vnni()
{
#if defined(ARCH_X64)
// Check AVX512VNNI
static const bool g_value = has_avx512() && get_cpuid(7, 0)[2] & 0x00000800;
return g_value;
#else
return false;
#endif
}
bool utils::has_avx10()
{
#if defined(ARCH_X64)
// Implies support for most AVX-512 instructions
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && get_cpuid(7, 1)[3] & 0x80000;
return g_value;
#else
return false;
#endif
}
u32 utils::avx10_isa_version()
{
#if defined(ARCH_X64)
// 8bit value
static const u32 g_value = []()
{
u32 isa_version = 0;
if (has_avx10())
{
isa_version = get_cpuid(24, 0)[2] & 0x000ff;
}
return isa_version;
}();
return g_value;
#else
return 0;
#endif
}
bool utils::has_xop()
{
#if defined(ARCH_X64)
static const bool g_value = has_avx() && get_cpuid(0x80000001, 0)[2] & 0x800;
return g_value;
#else
return false;
#endif
}
bool utils::has_clwb()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(7, 0)[1] & 0x1000000) == 0x1000000;
return g_value;
#else
return false;
#endif
}
bool utils::has_invariant_tsc()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(0x80000007, 0)[3] & 0x100) == 0x100;
return g_value;
#elif defined(ARCH_ARM64)
return true;
#endif
}
bool utils::has_fma3()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x1 && get_cpuid(1, 0)[2] & 0x1000;
return g_value;
#elif defined(ARCH_ARM64)
return true;
#endif
}
bool utils::has_fma4()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(0x80000001, 0)[2] & 0x10000) == 0x10000;
return g_value;
#else
return false;
#endif
}
// The Zen4 based CPUs support VPERMI2B/VPERMT2B in a single uop.
// Current Intel cpus (as of 2022) need 3 uops to execute these instructions.
// Check for SSE4A (which intel doesn't doesn't support) as well as VBMI.
bool utils::has_fast_vperm2b()
{
#if defined(ARCH_X64)
static const bool g_value = has_avx512() && (get_cpuid(7, 0)[2] & 0x2) == 0x2 && get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(0x80000001, 0)[2] & 0x40) == 0x40;
return g_value;
#else
return false;
#endif
}
bool utils::has_erms()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(7, 0)[1] & 0x200) == 0x200;
return g_value;
#else
return false;
#endif
}
bool utils::has_fsrm()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(7, 0)[3] & 0x10) == 0x10;
return g_value;
#else
return false;
#endif
}
bool utils::has_waitx()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(0x80000001, 0)[2] & 0x20000000) == 0x20000000;
return g_value;
#else
return false;
#endif
}
bool utils::has_waitpkg()
{
#if defined(ARCH_X64)
static const bool g_value = get_cpuid(0, 0)[0] >= 0x7 && (get_cpuid(7, 0)[2] & 0x20) == 0x20;
return g_value;
#else
return false;
#endif
}
// User mode waits may be unfriendly to low thread CPUs
// Filter out systems with less than 8 threads for linux and less than 12 threads for other platforms
bool utils::has_appropriate_um_wait()
{
#ifdef __linux__
static const bool g_value = (has_waitx() || has_waitpkg()) && (get_thread_count() >= 8) && get_tsc_freq();
return g_value;
#else
static const bool g_value = (has_waitx() || has_waitpkg()) && (get_thread_count() >= 12) && get_tsc_freq();
return g_value;
#endif
}
// Similar to the above function but allow execution if alternatives such as yield are not wanted
bool utils::has_um_wait()
{
static const bool g_value = (has_waitx() || has_waitpkg()) && get_tsc_freq();
return g_value;
}
u32 utils::get_rep_movsb_threshold()
{
static const u32 g_value = []()
{
u32 thresh_value = umax;
if (has_fsrm())
{
thresh_value = 2047;
}
else if (has_erms())
{
thresh_value = 4095;
}
return thresh_value;
}();
return g_value;
}
#ifdef ARCH_ARM64
bool utils::has_neon()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP) & HWCAP_ASIMD) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.AdvSIMD", &val, &len, nullptr, 0);
int val_legacy = 0;
size_t len_legacy = sizeof(val_legacy);
sysctlbyname("hw.optional.neon", &val_legacy, &len_legacy, nullptr, 0);
return val != 0 || val_legacy != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_VFP_32_REGISTERS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
bool utils::has_wfe_event_stream()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
// HWCAP_EVTSTRM: the kernel has enabled the architected timer event
// stream (CNTKCTL_EL1.EVNTEN), which is what bounds a bare WFE's wake
// latency. Waits that rely on WFE without an armed exclusive monitor
// must check this; with the stream off, such a WFE parks until the
// next unrelated interrupt.
return (getauxval(AT_HWCAP) & HWCAP_EVTSTRM) != 0;
#else
// Non-Linux ARM64 (Apple, Windows-on-ARM): no HWCAP equivalent exists.
// Report true so callers keep the same wait shapes they used before this
// probe existed; a platform where the stream-paced wait misbehaves needs
// a measured probe here, not a capability bit.
return true;
#endif
}();
return g_value;
}
bool utils::has_sha3()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP) & HWCAP_SHA3) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_SHA3", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_SHA3_INSTRUCTIONS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
bool utils::has_dotprod()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP) & HWCAP_ASIMDDP) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_DotProd", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_V82_DP_INSTRUCTIONS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
bool utils::has_i8mm()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP2) & HWCAP2_I8MM) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_I8MM", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_V82_I8MM_INSTRUCTIONS_AVAILABLE) != 0;
#else
return false;
#endif
}();
return g_value;
}
bool utils::has_sve()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP) & HWCAP_SVE) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_SVE", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_SVE_INSTRUCTIONS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
bool utils::has_sve2()
{
static const bool g_value = []() -> bool
{
#if defined(__linux__)
return (getauxval(AT_HWCAP2) & HWCAP2_SVE2) != 0;
#elif defined(__APPLE__)
int val = 0;
size_t len = sizeof(val);
sysctlbyname("hw.optional.arm.FEAT_SVE2", &val, &len, nullptr, 0);
return val != 0;
#elif defined(_WIN32)
return IsProcessorFeaturePresent(PF_ARM_SVE2_INSTRUCTIONS_AVAILABLE) != 0;
#endif
}();
return g_value;
}
#if defined(_MSC_VER)
#define sve_func
#else
#define sve_func __attribute__((__target__("+sve")))
#endif
// svcntb returns sve length in bytes, our function retuns length in bits
sve_func int utils::sve_length()
{
static const int g_value = static_cast<int>(svcntb() * 8);
return g_value;
}
#endif
std::string utils::get_cpu_brand()
{
#if defined(ARCH_X64)
std::string brand;
if (get_cpuid(0x80000000, 0)[0] >= 0x80000004)
{
for (u32 i = 0; i < 3; i++)
{
brand.append(reinterpret_cast<const char*>(get_cpuid(0x80000002 + i, 0).data()), 16);
}
}
else
{
brand = "Unknown CPU";
}
brand.erase(brand.find_last_not_of('\0') + 1);
brand.erase(brand.find_last_not_of(' ') + 1);
brand.erase(0, brand.find_first_not_of(' '));
while (auto found = brand.find(" ") + 1)
{
brand.erase(brand.begin() + found);
}
return brand;
#elif defined(ARCH_ARM64)
static const auto g_cpu_brand = aarch64::get_cpu_brand();
return g_cpu_brand;
#else
return "Unidentified CPU";
#endif
}
std::string_view utils::get_architecture()
{
#if defined(ARCH_X64)
return "x64"sv;
#elif defined(ARCH_ARM64)
return "arm64"sv;
#else
return "unknown"sv;
#endif
}
std::string utils::get_system_info()
{
std::string result;
const std::string brand = get_cpu_brand();
const u64 mem_total = get_total_memory();
const u32 num_proc = get_thread_count();
fmt::append(result, "%s | %d Threads | %.2f GiB RAM", brand, num_proc, mem_total / (1024.0f * 1024 * 1024));
if (const ullong tsc_freq = get_tsc_freq())
{
fmt::append(result, " | TSC: %.03fGHz", tsc_freq / 1000000000.);
}
else
{
fmt::append(result, " | TSC: Disabled");
}
#ifdef ARCH_ARM64
if (!has_neon())
{
fmt::throw_exception("Neon support not present");
}
if (has_sve())
{
fmt::append(result, " | SVE%s-%d", has_sve2() ? "2" : "", sve_length());
}
else
{
result += " | Neon";
}
// Surfaced so every log records whether monitor-less WFE waits have a
// bounded wake on this kernel (drives the RSX wait-shape selection).
fmt::append(result, " | EVTSTRM-%s", has_wfe_event_stream() ? "on" : "off");
#else
if (has_avx())
{
result += " | AVX";
if (has_avx10())
{
const u32 avx10_version = avx10_isa_version();
fmt::append(result, "10.%d", avx10_version);
}
else if (has_avx512())
{
result += "-512";
if (has_avx512_icl())
{
result += '+';
}
}
else if (has_avx2())
{
result += '+';
}
if (has_xop())
{
result += 'x';
}
}
if (has_fma3() || has_fma4())
{
result += " | FMA";
if (has_fma3() && has_fma4())
{
result += "3+4";
}
else if (has_fma3())
{
result += "3";
}
else if (has_fma4())
{
result += "4";
}
}
if (has_rtm())
{
result += " | TSX";
if (has_tsx_force_abort())
{
result += "-FA";
}
if (!has_mpx() || has_tsx_force_abort())
{
result += " disabled by default";
}
}
else if (has_rtm_always_abort())
{
result += " | TSX disabled via microcode";
}
#endif
return result;
}
std::string utils::get_firmware_version()
{
const std::string file_path = g_cfg_vfs.get_dev_flash() + "vsh/etc/version.txt";
if (fs::file version_file{file_path})
{
const std::string version_str = version_file.to_string();
std::string_view version = version_str;
// Extract version
const usz start = version.find_first_of(':') + 1;
const usz end = version.find_first_of(':', start);
if (!start || end == umax)
{
return {};
}
version = version.substr(start, end - start);
// Trim version (e.g. '04.8900' becomes '4.89')
usz trim_start = version.find_first_not_of('0');
if (trim_start == umax)
{
return {};
}
// Keep at least one preceding 0 (e.g. '00.3100' becomes '0.31' instead of '.31')
if (version[trim_start] == '.')
{
if (trim_start == 0)
{
// Version starts with '.' for some reason
return {};
}
trim_start--;
}
const usz dot_pos = version.find_first_of('.', trim_start);
if (dot_pos == umax)
{
return {};
}
// Try to keep the second 0 in the minor version (e.g. '04.9000' becomes '4.90' instead of '4.9')
const usz trim_end = std::max(version.find_last_not_of('0', dot_pos + 1), std::min(dot_pos + 2, version.size()));
return std::string(version.substr(trim_start, trim_end));
}
return {};
}
std::pair<u64, u64> utils::get_memory_usage()
{
#ifdef _WIN32
::MEMORYSTATUSEX status{};
status.dwLength = sizeof(status);
::GlobalMemoryStatusEx(&status);
return { status.ullTotalPhys, status.ullTotalPhys - status.ullAvailPhys };
#elif __linux__
std::ifstream proc("/proc/meminfo");
std::string line;
uint64_t mem_total = get_total_memory();
uint64_t mem_available = 0;
while (std::getline(proc, line))
{
if (line.rfind("MemTotal:", 0) == 0 && line.find("kB") != std::string::npos)
{
mem_total = std::stoull(line.substr(line.find_first_of("0123456789"))) * 1024;
}
else if (line.rfind("MemAvailable:", 0) == 0 && line.find("kB") != std::string::npos)
{
mem_available = std::stoull(line.substr(line.find_first_of("0123456789"))) * 1024;
break;
}
}
return { mem_total, mem_total - mem_available };
#else
// TODO
return { get_total_memory(), 0 };
#endif
}
u64 utils::get_process_memory_usage()
{
#ifdef _WIN32
::PROCESS_MEMORY_COUNTERS pmc{};
if (::GetProcessMemoryInfo(::GetCurrentProcess(), &pmc, sizeof(pmc)))
{
return pmc.WorkingSetSize;
}
return 0;
#elif defined(__linux__)
// statm, not status: the second field is the resident page count and needs no parsing beyond
// two integers, where VmRSS in /proc/self/status means scanning a few dozen lines of text
// for something read once a second.
std::ifstream statm("/proc/self/statm");
u64 total_pages = 0;
u64 resident_pages = 0;
if (statm >> total_pages >> resident_pages)
{
return resident_pages * static_cast<u64>(::sysconf(_SC_PAGESIZE));
}
return 0;
#else
return 0;
#endif
}
utils::OS_version utils::get_OS_version()
{
OS_version res {};
#if _WIN32
res.type = "windows";
#elif __linux__
res.type = "linux";
#elif __APPLE__
res.type = "macos";
#elif __FreeBSD__
res.type = "freebsd";
#else
res.type = "unknown";
#endif
#if defined(ARCH_X64)
res.arch = "x64";
#elif defined(ARCH_ARM64)
res.arch = "arm64";
#else
res.arch = "unknown";
#endif
#ifdef _WIN32
if (RtlGetVersion)
{
OSVERSIONINFOW osvi{};
osvi.dwOSVersionInfoSize = sizeof(osvi);
RtlGetVersion(&osvi);
res.version_major = osvi.dwMajorVersion;
res.version_minor = osvi.dwMinorVersion;
res.version_patch = osvi.dwBuildNumber;
}
#elif defined (__APPLE__)
res.version_major = Darwin_Version::getNSmajorVersion();
res.version_minor = Darwin_Version::getNSminorVersion();
res.version_patch = Darwin_Version::getNSpatchVersion();
#else
if (struct utsname details = {}; !uname(&details))
{
const std::string_view release = details.release;
const std::vector<std::string_view> version_list = fmt::split_sv(release, { "." });
const auto get_version_part = [&version_list](usz i) -> usz
{
if (version_list.size() <= i) return 0;
if (const auto [success, version_part] = string_to_number(version_list[i]); success)
{
return version_part;
}
return 0;
};
res.version_major = get_version_part(0);
res.version_minor = get_version_part(1);
res.version_patch = get_version_part(2);
}
#endif
return res;
}
std::string utils::get_OS_version_string(bool simple)
{
#ifdef _WIN32
OSVERSIONINFOW osvi{};
osvi.dwOSVersionInfoSize = sizeof(osvi);
RtlGetVersion(&osvi);
if (simple)
{
return fmt::format("Windows %lu.%lu.%lu", osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber);
}
const bool has_sp = osvi.szCSDVersion[0] != L'\0';
std::vector<char> holder;
if (has_sp)
{
const int len = WideCharToMultiByte(CP_UTF8, 0, osvi.szCSDVersion, -1,
nullptr, 0, nullptr, nullptr);
holder.resize(len);
WideCharToMultiByte(CP_UTF8, 0, osvi.szCSDVersion, -1,
holder.data(), len, nullptr, nullptr);
}
return fmt::format("Operating system: Windows, Major: %lu, Minor: %lu, Build: %lu, Service Pack: %s",
osvi.dwMajorVersion, osvi.dwMinorVersion, osvi.dwBuildNumber,
has_sp ? holder.data() : "none");
#elif defined (__APPLE__)
const int major_version = Darwin_Version::getNSmajorVersion();
const int minor_version = Darwin_Version::getNSminorVersion();
const int patch_version = Darwin_Version::getNSpatchVersion();
if (simple)
{
return fmt::format("macOS %d.%d.%d", major_version, minor_version, patch_version);
}
return fmt::format("Operating system: macOS, Version: %d.%d.%d", major_version, minor_version, patch_version);
#else
struct utsname details = {};
if (!uname(&details))
{
if (simple)
{
return fmt::format("%s %s", details.sysname, details.release);
}
return fmt::format("Operating system: POSIX, Name: %s, Release: %s, Version: %s", details.sysname, details.release, details.version);
}
if (simple)
{
return "POSIX";
}
return fmt::format("Operating system: POSIX, Unknown version! (Error: %d)", errno);
#endif
}
std::string utils::get_user_agent()
{
const std::string user_agent = fmt::format("RPCS3/%s (%s; %s)",
rpcs3::get_version().to_string(true),
utils::get_OS_version_string(true),
utils::get_architecture());
return user_agent;
}
int utils::get_maxfiles()
{
#ifdef _WIN32
// Virtually unlimited on Windows
return INT_MAX;
#else
struct rlimit limits;
ensure(getrlimit(RLIMIT_NOFILE, &limits) == 0);
return limits.rlim_cur;
#endif
}
bool utils::get_low_power_mode()
{
#ifdef __APPLE__
return Darwin_ProcessInfo::getLowPowerModeEnabled();
#else
return false;
#endif
}
static constexpr ullong round_tsc(ullong val, ullong known_error)
{
if (known_error >= 500'000)
{
// Do not accept large errors
return 0;
}
ullong by = 1000;
known_error /= 1000;
while (known_error && by < 100'000)
{
by *= 10;
known_error /= 10;
}
return utils::rounded_div(val, by) * by;
}
namespace utils
{
u64 s_tsc_freq = 0;
}
static const bool s_tsc_freq_evaluated = []() -> bool
{
static const ullong cal_tsc = []() -> ullong
{
#ifdef ARCH_ARM64
u64 r = 0;
__asm__ volatile("mrs %0, cntfrq_el0" : "=r" (r));
return r;
#endif
if (!utils::has_invariant_tsc())
{
return 0;
}
#ifdef _WIN32
LARGE_INTEGER freq{};
if (!QueryPerformanceFrequency(&freq))
{
return 0;
}
if (!freq.QuadPart)
{
return 0;
}
// Theoretical constraint for the function itself to operate properly
// Unlikely to be unmet
constexpr LONGLONG min_supported_QPC_frequency = 50'000;
if (freq.QuadPart <= min_supported_QPC_frequency)
{
return 0;
}
const ullong timer_freq = freq.QuadPart;
#else
#ifdef __linux__
// Check if system clocksource is TSC. If the kernel trusts the TSC, we should too.
// Some Ryzen laptops have broken firmware when running linux (requires a kernel patch). This is also a problem on some older intel CPUs.
const char* clocksource_file = "/sys/devices/system/clocksource/clocksource0/available_clocksource";
if (!fs::is_file(clocksource_file))
{
// OS doesn't support sysfs?
printf("[TSC calibration] Could not determine available clock sources. Disabling TSC.\n");
return 0;
}
std::string clock_sources;
std::ifstream file(clocksource_file);
std::getline(file, clock_sources);
if (file.fail())
{
printf("[TSC calibration] Could not read the available clock sources on this system. Disabling TSC.\n");
return 0;
}
printf("[TSC calibration] Available clock sources: '%s'\n", clock_sources.c_str());
// Check if the Kernel has blacklisted the TSC
const auto available_clocks = fmt::split_sv(clock_sources, { " " });
const bool tsc_reliable = std::find(available_clocks.begin(), available_clocks.end(), "tsc") != available_clocks.end();
if (!tsc_reliable)
{
printf("[TSC calibration] TSC is not a supported clock source on this system.\n");
return 0;
}
printf("[TSC calibration] Kernel reports the TSC is reliable.\n");
#else
if (utils::get_cpu_brand().find("Ryzen") != umax)
{
// MacOS is arm-native these days and I don't know much about BSD to fix this if it's an issue. (kd-11)
// Having this check only for Ryzen is broken behavior - other CPUs can also have this problem.
return 0;
}
#endif
constexpr ullong timer_freq = 1'000'000'000;
#endif
constexpr u64 retry_count = 1024;
// First is entry is for the onset measurements, last is for the end measurements
constexpr usz sample_count = 2;
std::array<u64, sample_count> rdtsc_data{};
std::array<u64, sample_count> rdtsc_diff{};
std::array<u64, sample_count> timer_data{};
#ifdef _WIN32
LARGE_INTEGER ctr0;
QueryPerformanceCounter(&ctr0);
const ullong time_base = ctr0.QuadPart;
#else
struct timespec ts0;
clock_gettime(CLOCK_MONOTONIC, &ts0);
const ullong sec_base = ts0.tv_sec;
#endif
const usz sleep_time_ms = timer_freq <= 300'000 ? (300'000 * 50) / timer_freq : 50;
for (usz sample = 0; sample < sample_count; sample++)
{
for (usz i = 0; i < retry_count; i++)
{
const u64 rdtsc_read = (utils::lfence(), utils::get_tsc());
#ifdef _WIN32
LARGE_INTEGER ctr;
QueryPerformanceCounter(&ctr);
#else
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
#endif
const u64 rdtsc_read2 = (utils::lfence(), utils::get_tsc());
#ifdef _WIN32
const u64 timer_read = ctr.QuadPart - time_base;
#else
const u64 timer_read = ts.tv_nsec + (ts.tv_sec - sec_base) * 1'000'000'000;
#endif
if (i == 0 || (rdtsc_read2 >= rdtsc_read && rdtsc_read2 - rdtsc_read < rdtsc_diff[sample]))
{
rdtsc_data[sample] = rdtsc_read; // Note: rdtsc_read2 can also be written here because of the assumption of accuracy
timer_data[sample] = timer_read;
rdtsc_diff[sample] = rdtsc_read2 >= rdtsc_read ? rdtsc_read2 - rdtsc_read : u64{umax};
}
// 80 results in an error range of 4000 hertz (0.00025% of 4GHz CPU, quite acceptable)
// Error of 2.5 seconds per month
if (rdtsc_read2 - rdtsc_read < 80 && rdtsc_read2 >= rdtsc_read)
{
break;
}
// 8 yields seems to reduce significantly thread contention, improving accuracy
// Even 3 seem to do the job though, but just in case
if (i % 128 == 64)
{
std::this_thread::yield();
}
// Take 50% more yields with the last sample because it helps accuracy additionally the more time that passes
if (sample == sample_count - 1 && i % 256 == 128)
{
std::this_thread::yield();
}
}
if (sample < sample_count - 1)
{
// Sleep between first and last sample
#ifdef _WIN32
Sleep(static_cast<DWORD>(sleep_time_ms));
#else
usleep(sleep_time_ms * 1000);
#endif
}
}
if (timer_data[1] == timer_data[0])
{
// Division by zero
return 0;
}
const u128 data = u128_from_mul(rdtsc_data[1] - rdtsc_data[0], timer_freq);
const u64 res = utils::udiv128(static_cast<u64>(data >> 64), static_cast<u64>(data), (timer_data[1] - timer_data[0]));
// Rounding
return round_tsc(res, utils::mul_saturate<u64>(utils::add_saturate<u64>(rdtsc_diff[0], rdtsc_diff[1]), utils::aligned_div(timer_freq, timer_data[1] - timer_data[0])));
}();
atomic_storage<u64>::store(utils::s_tsc_freq, cal_tsc);
return true;
}();
u64 utils::get_total_memory()
{
#ifdef _WIN32
::MEMORYSTATUSEX memInfo{};
memInfo.dwLength = sizeof(memInfo);
::GlobalMemoryStatusEx(&memInfo);
return memInfo.ullTotalPhys;
#else
return ::sysconf(_SC_PHYS_PAGES) * ::sysconf(_SC_PAGE_SIZE);
#endif
}
u64 utils::get_avail_memory()
{
#ifdef __linux__
// MemAvailable is the kernel's own estimate of what can be handed out without
// swapping, which is the number that matters here; MemFree alone understates it
// badly because it ignores reclaimable page cache.
if (fs::file meminfo{"/proc/meminfo"})
{
const std::string data = meminfo.to_string();
if (const usz pos = data.find("MemAvailable:"); pos != umax)
{
u64 kb = 0;
if (std::sscanf(data.c_str() + pos, "MemAvailable: %llu kB", reinterpret_cast<unsigned long long*>(&kb)) == 1)
{
return kb * 1024;
}
}
}
#endif
return 0;
}
u32 utils::get_thread_count()
{
static const u32 g_count = []()
{
#ifdef _WIN32
::SYSTEM_INFO sysInfo;
::GetNativeSystemInfo(&sysInfo);
return sysInfo.dwNumberOfProcessors;
#else
return ::sysconf(_SC_NPROCESSORS_ONLN);
#endif
}();
return g_count;
}
u32 utils::get_cpu_family()
{
#if defined(ARCH_X64)
static const u32 g_value = []()
{
const u32 reg_value = get_cpuid(0x00000001, 0)[0]; // Processor feature info
const u32 base_value = (reg_value >> 8) & 0xF;
if (base_value == 0xF) [[likely]]
{
const u32 extended_value = (reg_value >> 20) & 0xFF;
return base_value + extended_value;
}
else
{
return base_value;
}
}();
return g_value;
#elif defined(ARCH_ARM64)
return 0;
#endif
}
u32 utils::get_cpu_model()
{
#if defined(ARCH_X64)
static const u32 g_value = []()
{
const u32 reg_value = get_cpuid(0x00000001, 0)[0]; // Processor feature info
const u32 base_value = (reg_value >> 4) & 0xF;
if (const auto base_family_id = (reg_value >> 8) & 0xF;
base_family_id == 0x6 || base_family_id == 0xF) [[likely]]
{
const u32 extended_value = (reg_value >> 16) & 0xF;
return base_value + (extended_value << 4);
}
else
{
return base_value;
}
}();
return g_value;
#elif defined(ARCH_ARM64)
return 0;
#endif
}
u64 utils::_get_main_tid()
{
return thread_ctrl::get_tid();
}
std::pair<bool, usz> utils::string_to_number(std::string_view str)
{
std::add_pointer_t<char> eval;
const usz number = std::strtol(str.data(), &eval, 10);
if (str.data() + str.size() == eval)
{
return { true, number };
}
return { false, 0 };
}