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
+33 -1
View File
@@ -54,6 +54,14 @@ jobs:
os: macos-14 os: macos-14
openssl_target: darwin64-arm64-cc openssl_target: darwin64-arm64-cc
cross_prefix: '' cross_prefix: ''
- target: windows-amd64
os: ubuntu-24.04
openssl_target: ''
cross_prefix: ''
- target: windows-i386
os: ubuntu-24.04
openssl_target: ''
cross_prefix: ''
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- name: Check out source - name: Check out source
@@ -77,7 +85,20 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install --yes gcc-powerpc-linux-gnu sudo apt-get install --yes gcc-powerpc-linux-gnu
- name: Install the Windows/AMD64 cross-compiler
if: matrix.target == 'windows-amd64'
run: |
sudo apt-get update
sudo apt-get install --yes gcc-mingw-w64-x86-64
- name: Install the Windows/i386 cross-compiler
if: matrix.target == 'windows-i386'
run: |
sudo apt-get update
sudo apt-get install --yes gcc-mingw-w64-i686
- name: Build static OpenSSL - name: Build static OpenSSL
if: matrix.openssl_target != ''
env: env:
OPENSSL_VERSION: 3.5.7 OPENSSL_VERSION: 3.5.7
OPENSSL_TARGET: ${{ matrix.openssl_target }} OPENSSL_TARGET: ${{ matrix.openssl_target }}
@@ -127,8 +148,19 @@ jobs:
exit 1 exit 1
fi fi
- name: Build Windows executable
if: startsWith(matrix.target, 'windows-')
run: |
make "${{ matrix.target }}"
file "dist/fossbench-${{ matrix.target }}.exe"
- name: Package artifact - name: Package artifact
run: tar -czf fossbench-${{ matrix.target }}.tar.gz -C dist fossbench-${{ matrix.target }} run: |
bin="fossbench-${{ matrix.target }}"
if [[ "${{ matrix.target }}" == windows-* ]]; then
bin="$bin.exe"
fi
tar -czf "fossbench-${{ matrix.target }}.tar.gz" -C dist "$bin"
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v4
+34 -5
View File
@@ -2,7 +2,8 @@
# #
# The assembly kernels are architecture-specific: # The assembly kernels are architecture-specific:
# src/fossbench.S AArch64 (ARM64) # src/fossbench.S AArch64 (ARM64)
# src/fossbench_x86_64.S x86-64 (AMD64) # src/fossbench_x86_64.S x86-64 (AMD64) - SysV kernels, Windows callers go
# through a WIN64_THUNK ABI shim (see the file header)
# src/fossbench_i386.S x86 32-bit (i386, Pentium 4 baseline) # src/fossbench_i386.S x86 32-bit (i386, Pentium 4 baseline)
# src/fossbench_ppc32.c PowerPC 32-bit, including big-endian systems # src/fossbench_ppc32.c PowerPC 32-bit, including big-endian systems
# and the portable PPC64 kernel implementations # and the portable PPC64 kernel implementations
@@ -18,7 +19,9 @@
# make linux-ppc64be build Linux/PPC64 big-endian for an iMac G5 # make linux-ppc64be build Linux/PPC64 big-endian for an iMac G5
# make macos-arm64 build the macOS/ARM64 binary # make macos-arm64 build the macOS/ARM64 binary
# make macos-amd64 build the macOS/AMD64 binary # make macos-amd64 build the macOS/AMD64 binary
# make all build both Linux binaries # make windows-amd64 build the Windows/AMD64 binary (.exe, statically linked)
# make windows-i386 build the Windows/i386 binary (.exe, statically linked)
# make all build every release binary (Linux, macOS, Windows)
# make bench build for the host and run it # make bench build for the host and run it
# make test build and run the kernel correctness tests (host arch) # make test build and run the kernel correctness tests (host arch)
# make clean remove dist/ # make clean remove dist/
@@ -36,6 +39,13 @@
# be overridden for an osxcross or other cross toolchain: # be overridden for an osxcross or other cross toolchain:
# make macos-arm64 CC_MACOS_ARM64=clang # make macos-arm64 CC_MACOS_ARM64=clang
# make macos-amd64 CC_MACOS_AMD64=clang # make macos-amd64 CC_MACOS_AMD64=clang
#
# Windows binaries are built with the MinGW-w64 cross toolchain (package
# mingw-w64-gcc on Arch/Debian/Fedora), statically linked so the .exe needs no
# accompanying DLLs. Result upload (TLS) is not built for Windows - main.c
# stubs it out - so no OpenSSL dependency is needed for these targets.
# make windows-amd64 CC_WINDOWS_AMD64=x86_64-w64-mingw32-gcc-12
# make windows-i386 CC_WINDOWS_I386=i686-w64-mingw32-gcc-12
CC ?= cc CC ?= cc
CFLAGS ?= -O2 -Wall -Wextra CFLAGS ?= -O2 -Wall -Wextra
@@ -135,15 +145,18 @@ ifeq ($(HOST_ARCHNAME),ppc64be)
else else
CC_PPC64BE ?= powerpc64-linux-gnu-gcc CC_PPC64BE ?= powerpc64-linux-gnu-gcc
endif endif
# Windows is always cross-compiled with MinGW-w64, regardless of host OS/arch.
CC_WINDOWS_AMD64 ?= x86_64-w64-mingw32-gcc
CC_WINDOWS_I386 ?= i686-w64-mingw32-gcc
NATIVE_BIN := $(DIST)/fossbench-$(OSNAME)-$(HOST_ARCHNAME) NATIVE_BIN := $(DIST)/fossbench-$(OSNAME)-$(HOST_ARCHNAME)
# `make` with no target builds the host binary, as before. # `make` with no target builds the host binary, as before.
.DEFAULT_GOAL := native .DEFAULT_GOAL := native
.PHONY: all native linux-arm64 linux-amd64 linux-i386 linux-ppc32be linux-ppc64be macos-arm64 macos-amd64 bench test clean .PHONY: all native linux-arm64 linux-amd64 linux-i386 linux-ppc32be linux-ppc64be macos-arm64 macos-amd64 windows-amd64 windows-i386 bench test clean
# `make all` builds all Linux binaries. # `make all` builds all Linux binaries, plus the (cross-compiled) Windows ones.
all: linux-arm64 linux-amd64 linux-i386 linux-ppc32be linux-ppc64be all: linux-arm64 linux-amd64 linux-i386 linux-ppc32be linux-ppc64be windows-amd64 windows-i386
# `make native` (and bare `make`) build for whatever host you are on. # `make native` (and bare `make`) build for whatever host you are on.
native: $(NATIVE_BIN) native: $(NATIVE_BIN)
@@ -155,6 +168,8 @@ linux-ppc32be: $(DIST)/fossbench-linux-ppc32be
linux-ppc64be: $(DIST)/fossbench-linux-ppc64be linux-ppc64be: $(DIST)/fossbench-linux-ppc64be
macos-arm64: $(DIST)/fossbench-macos-arm64 macos-arm64: $(DIST)/fossbench-macos-arm64
macos-amd64: $(DIST)/fossbench-macos-amd64 macos-amd64: $(DIST)/fossbench-macos-amd64
windows-amd64: $(DIST)/fossbench-windows-amd64.exe
windows-i386: $(DIST)/fossbench-windows-i386.exe
$(DIST)/fossbench-linux-arm64: $(DRIVER) $(ASM_ARM64) | $(DIST) $(DIST)/fossbench-linux-arm64: $(DRIVER) $(ASM_ARM64) | $(DIST)
$(CC_ARM64) $(CFLAGS) $(TLS_CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(ASM_ARM64) $(LDLIBS) $(CC_ARM64) $(CFLAGS) $(TLS_CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(ASM_ARM64) $(LDLIBS)
@@ -184,6 +199,20 @@ $(DIST)/fossbench-macos-amd64: $(DRIVER) $(ASM_AMD64) | $(DIST)
MACOSX_DEPLOYMENT_TARGET=$(MACOS_AMD64_MIN) $(CC_MACOS_AMD64) -arch x86_64 -mmacosx-version-min=$(MACOS_AMD64_MIN) $(CFLAGS) $(TLS_CFLAGS) $(PTHREAD) $(LDFLAGS) -Wl,-no_fixup_chains -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS) MACOSX_DEPLOYMENT_TARGET=$(MACOS_AMD64_MIN) $(CC_MACOS_AMD64) -arch x86_64 -mmacosx-version-min=$(MACOS_AMD64_MIN) $(CFLAGS) $(TLS_CFLAGS) $(PTHREAD) $(LDFLAGS) -Wl,-no_fixup_chains -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS)
@echo "built $@" @echo "built $@"
# Windows binaries are statically linked (-static) so the .exe is
# self-contained: no libwinpthread/libgcc DLLs need to ship alongside it.
# Result upload (TLS) is stubbed out for Windows in main.c, so unlike every
# other target here, these don't need $(TLS_CFLAGS)/$(TLS_LDLIBS)/OpenSSL, and
# $(LDFLAGS) is deliberately not used since it may carry a host-specific
# -no-pie meant for a native i386 Linux build, not this cross target.
$(DIST)/fossbench-windows-amd64.exe: $(DRIVER) $(ASM_AMD64) | $(DIST)
$(CC_WINDOWS_AMD64) $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_AMD64) -lm
@echo "built $@"
$(DIST)/fossbench-windows-i386.exe: $(DRIVER) $(ASM_I386) | $(DIST)
$(CC_WINDOWS_I386) -march=pentium4 $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_I386) -lm
@echo "built $@"
# When the host is Linux/ARM64 or Linux/AMD64, the native binary IS one of the # When the host is Linux/ARM64 or Linux/AMD64, the native binary IS one of the
# linux-* targets above, so no separate recipe is defined (that would be a # linux-* targets above, so no separate recipe is defined (that would be a
# duplicate). Otherwise - e.g. macOS/ARM64 - provide the native recipe here. # duplicate). Otherwise - e.g. macOS/ARM64 - provide the native recipe here.
+26 -9
View File
@@ -6,7 +6,8 @@ across every available core. The final report includes separate single-core and
multicore scores. multicore scores.
The repository currently builds an executable named `fossbench` for ARM64, The repository currently builds an executable named `fossbench` for ARM64,
x86 (Pentium 4 or newer), x86-64, and 32- or 64-bit big-endian PowerPC. The C driver handles timing, memory, x86 (Pentium 4 or newer), x86-64, and 32- or 64-bit big-endian PowerPC, on
Linux, macOS, and Windows. The C driver handles timing, memory,
threads, output, and scoring. Performance-sensitive kernels live in threads, output, and scoring. Performance-sensitive kernels live in
architecture-specific backend files. architecture-specific backend files.
@@ -55,11 +56,14 @@ make linux-ppc32be
make linux-ppc64be # PowerPC 970 / iMac G5 make linux-ppc64be # PowerPC 970 / iMac G5
make macos-arm64 make macos-arm64
make macos-amd64 make macos-amd64
make windows-amd64
make windows-i386 # Pentium 4 / SSE2 baseline
make all make all
``` ```
`make all` builds all four Linux targets. Cross-compilation requires a suitable `make all` builds all five Linux targets plus both Windows targets.
toolchain. Override the target compiler when its name differs from the default: Cross-compilation requires a suitable toolchain. Override the target compiler
when its name differs from the default:
```sh ```sh
make linux-arm64 CC_ARM64=aarch64-linux-gnu-gcc make linux-arm64 CC_ARM64=aarch64-linux-gnu-gcc
@@ -67,11 +71,21 @@ make linux-amd64 CC_AMD64=x86_64-linux-gnu-gcc
make linux-i386 CC_I386=gcc make linux-i386 CC_I386=gcc
make linux-ppc32be CC_PPC32BE=powerpc-linux-gnu-gcc make linux-ppc32be CC_PPC32BE=powerpc-linux-gnu-gcc
make linux-ppc64be CC_PPC64BE=powerpc64-linux-gnu-gcc make linux-ppc64be CC_PPC64BE=powerpc64-linux-gnu-gcc
make windows-amd64 CC_WINDOWS_AMD64=x86_64-w64-mingw32-gcc
make windows-i386 CC_WINDOWS_I386=i686-w64-mingw32-gcc
``` ```
Apple Clang can build either macOS architecture with `-arch`. Windows timing Apple Clang can build either macOS architecture with `-arch`. Windows
and allocation code exists in the driver, but the Makefile does not include a binaries are cross-compiled with the MinGW-w64 toolchain (package
Windows target and the x86-64 assembly currently follows the System V ABI. `mingw-w64-gcc` on Arch, `gcc-mingw-w64-x86-64` / `gcc-mingw-w64-i686` on
Debian/Ubuntu) and are statically linked, so the `.exe` needs no
accompanying DLLs. Windows uses a different AMD64 calling convention than
Linux/macOS (integer args in `rcx`/`rdx`/`r8`/`r9` rather than
`rdi`/`rsi`/`rdx`/`rcx`, with `rdi`, `rsi`, and `xmm6`-`xmm15` callee-saved);
`src/fossbench_x86_64.S` still writes every kernel once to the System V
convention and wraps each public entry point in a small ABI-translating
thunk (`WIN64_THUNK`) when building for Windows. Result upload (HTTPS/TLS)
is not built for Windows, so these targets need no OpenSSL.
The macOS AMD64 target is linked for macOS 10.5 and disables chained fixups so The macOS AMD64 target is linked for macOS 10.5 and disables chained fixups so
its Mach-O load commands are understood by legacy Intel Macs. Override the its Mach-O load commands are understood by legacy Intel Macs. Override the
@@ -127,14 +141,17 @@ Release binaries statically include OpenSSL. Linux releases dynamically use
the system C library so DNS resolution can safely load the matching NSS the system C library so DNS resolution can safely load the matching NSS
modules; they do not require system OpenSSL libraries. macOS releases retain modules; they do not require system OpenSSL libraries. macOS releases retain
only Apple's required system-library linkage because the macOS toolchain does only Apple's required system-library linkage because the macOS toolchain does
not support fully static executables. not support fully static executables. Windows releases are fully static,
including pthreads (winpthreads); result upload is not available on Windows,
so `--upload`/`FOSSBENCH_TOKEN` have no effect there.
## Continuous integration and releases ## Continuous integration and releases
Pushing a Git tag runs the GitHub Actions build and correctness tests. If they Pushing a Git tag runs the GitHub Actions build and correctness tests. If they
succeed, the workflow creates a GitHub Release named `Release <tag name>` with succeed, the workflow creates a GitHub Release named `Release <tag name>` with
Linux archives for AMD64, ARM64, and PPC32 big-endian; macOS archives for AMD64 Linux archives for AMD64, i386, ARM64, and PPC32 big-endian; macOS archives for
and ARM64; and a `SHA256SUMS` file. PPC64 remains available as a source build. AMD64 and ARM64; Windows archives for AMD64 and i386; and a `SHA256SUMS` file.
PPC64 remains available as a source build.
## Scores ## Scores
+1 -1
View File
@@ -32,7 +32,7 @@
.intel_syntax noprefix .intel_syntax noprefix
#if defined(__APPLE__) #if defined(__APPLE__) || defined(_WIN32)
# define SYM(name) _##name # define SYM(name) _##name
#else #else
# define SYM(name) name # define SYM(name) name
+74 -2
View File
@@ -20,6 +20,14 @@
* integer args rdi, rsi, rdx, rcx, r8, r9 (return in rax) * integer args rdi, rsi, rdx, rcx, r8, r9 (return in rax)
* callee-saved rbx, rbp, r12, r13, r14, r15 (saved when used) * 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. * 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 .intel_syntax noprefix
@@ -30,8 +38,14 @@
# define SYM(name) name # define SYM(name) name
#endif #endif
#define FN_BEGIN(name) FN_BEGIN_ASM SYM(name) #if defined(_WIN32)
#define FN_END(name) FN_END_ASM SYM(name) # 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 .macro FN_BEGIN_ASM name
.p2align 4 .p2align 4
@@ -48,6 +62,52 @@
#endif #endif
.endm .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 .text
/* =================================================================== /* ===================================================================
@@ -991,6 +1051,18 @@ FN_BEGIN(fb_chase)
ret ret
FN_END(fb_chase) 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__) #if defined(__ELF__)
.section .note.GNU-stack, "", @progbits .section .note.GNU-stack, "", @progbits
+41 -1
View File
@@ -37,12 +37,15 @@
# include <sys/sysctl.h> # include <sys/sysctl.h>
# include <mach/mach_time.h> # include <mach/mach_time.h>
#endif #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\". */ /* Change this at build time with -DFB_API_BASE_URL=\"https://host\". */
#ifndef FB_API_BASE_URL #ifndef FB_API_BASE_URL
# define FB_API_BASE_URL "https://fossbench.net" # define FB_API_BASE_URL "https://fossbench.net"
#endif #endif
#define FB_VERSION "0.1.5" #define FB_VERSION "0.1.5-hotfix1"
/* ---------- platform identification (for the banner only) ---------- */ /* ---------- 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) struct utsname u; if (uname(&u) == 0)
snprintf(info->operating_system, sizeof(info->operating_system), "macOS %s", u.release); 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 #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); long n = sysconf(_SC_NPROCESSORS_ONLN);
#endif
g_ncores = n > 0 ? n : 1; g_ncores = n > 0 ? n : 1;
} }
detect_system_info(&system_info); detect_system_info(&system_info);