Add Windows support and windows-amd64/windows-i386 release binaries

The x86-64 kernels were written to the System V ABI only; on Windows's
different calling convention they silently read garbage args and ran in
near-constant time regardless of iteration count. Each public kernel now
gets a small ABI-translating thunk (WIN64_THUNK) on Windows so the kernel
bodies stay single-source. i386 also needed underscore-prefixed symbol
names to match Windows's cdecl convention. Verified end-to-end under Wine,
including determinism across repeated runs.

Also fixes CPU-core detection (sysconf isn't available under MinGW) and
adds memory/CPU-brand detection for the Windows system-info banner.
This commit is contained in:
2026-07-18 06:48:40 -05:00
parent 0c31675c52
commit 72043eb956
6 changed files with 209 additions and 19 deletions
+1 -1
View File
@@ -32,7 +32,7 @@
.intel_syntax noprefix
#if defined(__APPLE__)
#if defined(__APPLE__) || defined(_WIN32)
# define SYM(name) _##name
#else
# define SYM(name) name
+74 -2
View File
@@ -20,6 +20,14 @@
* integer args rdi, rsi, rdx, rcx, r8, r9 (return in rax)
* callee-saved rbx, rbp, r12, r13, r14, r15 (saved when used)
* all of xmm0-15 are caller-saved, so no vector register need be preserved.
*
* Windows uses a different AMD64 ABI (integer args in rcx, rdx, r8, r9; rdi,
* rsi and xmm6-15 callee-saved instead of caller-saved). Every kernel body
* below is written once, unchanged, to the System V convention documented
* above. On Windows each public name becomes a thin trampoline (WIN64_THUNK)
* around a same-named `_sysv` implementation: it saves the registers that
* Windows callers expect preserved, remaps args into System V registers, and
* calls straight through - so the hot kernel bodies never need two versions.
*/
.intel_syntax noprefix
@@ -30,8 +38,14 @@
# define SYM(name) name
#endif
#define FN_BEGIN(name) FN_BEGIN_ASM SYM(name)
#define FN_END(name) FN_END_ASM SYM(name)
#if defined(_WIN32)
# define ENTRY(name) SYM(name##_sysv)
#else
# define ENTRY(name) SYM(name)
#endif
#define FN_BEGIN(name) FN_BEGIN_ASM ENTRY(name)
#define FN_END(name) FN_END_ASM ENTRY(name)
.macro FN_BEGIN_ASM name
.p2align 4
@@ -48,6 +62,52 @@
#endif
.endm
#if defined(_WIN32)
/* Windows x64 ABI trampoline: save the callee-saved registers System V would
* leave to the caller (rdi, rsi, xmm6-15), remap up to 4 integer/pointer
* args from the Windows positions (rcx, rdx, r8, r9) into System V (rdi,
* rsi, rdx, rcx), call the `_sysv` implementation, then restore. Every
* kernel here takes at most 4 integer/pointer args and returns a uint64_t in
* rax (identical in both ABIs), so one thunk shape covers all nine. */
.macro WIN64_THUNK pubname, implname
.p2align 4
.globl \pubname
\pubname:
push rdi
push rsi
sub rsp, 0xA8 /* 10 xmm slots + 8 pad, keeps rsp 16-aligned for the call */
movdqu [rsp + 0x00], xmm6
movdqu [rsp + 0x10], xmm7
movdqu [rsp + 0x20], xmm8
movdqu [rsp + 0x30], xmm9
movdqu [rsp + 0x40], xmm10
movdqu [rsp + 0x50], xmm11
movdqu [rsp + 0x60], xmm12
movdqu [rsp + 0x70], xmm13
movdqu [rsp + 0x80], xmm14
movdqu [rsp + 0x90], xmm15
mov rdi, rcx
mov rsi, rdx
mov rdx, r8
mov rcx, r9
call \implname
movdqu xmm6, [rsp + 0x00]
movdqu xmm7, [rsp + 0x10]
movdqu xmm8, [rsp + 0x20]
movdqu xmm9, [rsp + 0x30]
movdqu xmm10, [rsp + 0x40]
movdqu xmm11, [rsp + 0x50]
movdqu xmm12, [rsp + 0x60]
movdqu xmm13, [rsp + 0x70]
movdqu xmm14, [rsp + 0x80]
movdqu xmm15, [rsp + 0x90]
add rsp, 0xA8
pop rsi
pop rdi
ret
.endm
#endif
.text
/* ===================================================================
@@ -991,6 +1051,18 @@ FN_BEGIN(fb_chase)
ret
FN_END(fb_chase)
#if defined(_WIN32)
.text
WIN64_THUNK SYM(fb_int_math), SYM(fb_int_math_sysv)
WIN64_THUNK SYM(fb_fp_math), SYM(fb_fp_math_sysv)
WIN64_THUNK SYM(fb_primes), SYM(fb_primes_sysv)
WIN64_THUNK SYM(fb_simd), SYM(fb_simd_sysv)
WIN64_THUNK SYM(fb_compress), SYM(fb_compress_sysv)
WIN64_THUNK SYM(fb_chacha20), SYM(fb_chacha20_sysv)
WIN64_THUNK SYM(fb_physics), SYM(fb_physics_sysv)
WIN64_THUNK SYM(fb_sort), SYM(fb_sort_sysv)
WIN64_THUNK SYM(fb_chase), SYM(fb_chase_sysv)
#endif
#if defined(__ELF__)
.section .note.GNU-stack, "", @progbits
+41 -1
View File
@@ -37,12 +37,15 @@
# include <sys/sysctl.h>
# include <mach/mach_time.h>
#endif
#if defined(_WIN32) && (defined(__i386__) || defined(__x86_64__))
# include <cpuid.h>
#endif
/* Change this at build time with -DFB_API_BASE_URL=\"https://host\". */
#ifndef FB_API_BASE_URL
# define FB_API_BASE_URL "https://fossbench.net"
#endif
#define FB_VERSION "0.1.5"
#define FB_VERSION "0.1.5-hotfix1"
/* ---------- platform identification (for the banner only) ---------- */
@@ -429,6 +432,37 @@ static void detect_system_info(struct system_info *info)
struct utsname u; if (uname(&u) == 0)
snprintf(info->operating_system, sizeof(info->operating_system), "macOS %s", u.release);
}
#elif defined(_WIN32)
{
MEMORYSTATUSEX ms;
ms.dwLength = sizeof(ms);
if (GlobalMemoryStatusEx(&ms))
info->memory_mb = (long)(ms.ullTotalPhys / 1024 / 1024);
}
#if defined(__i386__) || defined(__x86_64__)
{
/* CPUID leaves 0x80000002-0x80000004 return the 48-byte brand
* string in eax:ebx:ecx:edx, twelve bytes per leaf. */
unsigned eax, ebx, ecx, edx, max_ext;
char brand[49];
int i;
__cpuid(0x80000000, eax, ebx, ecx, edx);
max_ext = eax;
if (max_ext >= 0x80000004) {
for (i = 0; i < 3; i++) {
__cpuid(0x80000002u + (unsigned)i, eax, ebx, ecx, edx);
memcpy(brand + i * 16 + 0, &eax, 4);
memcpy(brand + i * 16 + 4, &ebx, 4);
memcpy(brand + i * 16 + 8, &ecx, 4);
memcpy(brand + i * 16 + 12, &edx, 4);
}
brand[48] = '\0';
trim(brand);
if (brand[0])
snprintf(info->cpu, sizeof(info->cpu), "%s", brand);
}
}
#endif
#endif
}
@@ -1027,7 +1061,13 @@ int main(int argc, char **argv)
}
{
#if defined(_WIN32)
SYSTEM_INFO si;
GetSystemInfo(&si);
long n = (long)si.dwNumberOfProcessors;
#else
long n = sysconf(_SC_NPROCESSORS_ONLN);
#endif
g_ncores = n > 0 ? n : 1;
}
detect_system_info(&system_info);