diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8c2e90..648a247 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -54,6 +54,14 @@ jobs: os: macos-14 openssl_target: darwin64-arm64-cc 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 }} steps: - name: Check out source @@ -77,7 +85,20 @@ jobs: sudo apt-get update 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 + if: matrix.openssl_target != '' env: OPENSSL_VERSION: 3.5.7 OPENSSL_TARGET: ${{ matrix.openssl_target }} @@ -127,8 +148,19 @@ jobs: exit 1 fi + - name: Build Windows executable + if: startsWith(matrix.target, 'windows-') + run: | + make "${{ matrix.target }}" + file "dist/fossbench-${{ matrix.target }}.exe" + - 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 uses: actions/upload-artifact@v4 diff --git a/Makefile b/Makefile index 6c70e6f..652ced2 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,8 @@ # # The assembly kernels are architecture-specific: # 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_ppc32.c PowerPC 32-bit, including big-endian systems # and the portable PPC64 kernel implementations @@ -18,7 +19,9 @@ # make linux-ppc64be build Linux/PPC64 big-endian for an iMac G5 # make macos-arm64 build the macOS/ARM64 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 test build and run the kernel correctness tests (host arch) # make clean remove dist/ @@ -36,6 +39,13 @@ # be overridden for an osxcross or other cross toolchain: # make macos-arm64 CC_MACOS_ARM64=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 CFLAGS ?= -O2 -Wall -Wextra @@ -135,15 +145,18 @@ ifeq ($(HOST_ARCHNAME),ppc64be) else CC_PPC64BE ?= powerpc64-linux-gnu-gcc 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) # `make` with no target builds the host binary, as before. .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. -all: linux-arm64 linux-amd64 linux-i386 linux-ppc32be linux-ppc64be +# `make all` builds all Linux binaries, plus the (cross-compiled) Windows ones. +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. native: $(NATIVE_BIN) @@ -155,6 +168,8 @@ linux-ppc32be: $(DIST)/fossbench-linux-ppc32be linux-ppc64be: $(DIST)/fossbench-linux-ppc64be macos-arm64: $(DIST)/fossbench-macos-arm64 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) $(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) @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 # linux-* targets above, so no separate recipe is defined (that would be a # duplicate). Otherwise - e.g. macOS/ARM64 - provide the native recipe here. diff --git a/README.md b/README.md index a7b81ba..026970d 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,8 @@ across every available core. The final report includes separate single-core and multicore scores. 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 architecture-specific backend files. @@ -55,11 +56,14 @@ make linux-ppc32be make linux-ppc64be # PowerPC 970 / iMac G5 make macos-arm64 make macos-amd64 +make windows-amd64 +make windows-i386 # Pentium 4 / SSE2 baseline make all ``` -`make all` builds all four Linux targets. Cross-compilation requires a suitable -toolchain. Override the target compiler when its name differs from the default: +`make all` builds all five Linux targets plus both Windows targets. +Cross-compilation requires a suitable toolchain. Override the target compiler +when its name differs from the default: ```sh 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-ppc32be CC_PPC32BE=powerpc-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 -and allocation code exists in the driver, but the Makefile does not include a -Windows target and the x86-64 assembly currently follows the System V ABI. +Apple Clang can build either macOS architecture with `-arch`. Windows +binaries are cross-compiled with the MinGW-w64 toolchain (package +`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 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 modules; they do not require system OpenSSL libraries. macOS releases retain 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 Pushing a Git tag runs the GitHub Actions build and correctness tests. If they succeed, the workflow creates a GitHub Release named `Release ` with -Linux archives for AMD64, ARM64, and PPC32 big-endian; macOS archives for AMD64 -and ARM64; and a `SHA256SUMS` file. PPC64 remains available as a source build. +Linux archives for AMD64, i386, ARM64, and PPC32 big-endian; macOS archives for +AMD64 and ARM64; Windows archives for AMD64 and i386; and a `SHA256SUMS` file. +PPC64 remains available as a source build. ## Scores diff --git a/src/fossbench_i386.S b/src/fossbench_i386.S index b733592..0e4225a 100644 --- a/src/fossbench_i386.S +++ b/src/fossbench_i386.S @@ -32,7 +32,7 @@ .intel_syntax noprefix -#if defined(__APPLE__) +#if defined(__APPLE__) || defined(_WIN32) # define SYM(name) _##name #else # define SYM(name) name diff --git a/src/fossbench_x86_64.S b/src/fossbench_x86_64.S index 288889d..341c901 100644 --- a/src/fossbench_x86_64.S +++ b/src/fossbench_x86_64.S @@ -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 diff --git a/src/main.c b/src/main.c index 21a5508..7bfb155 100644 --- a/src/main.c +++ b/src/main.c @@ -37,12 +37,15 @@ # include # include #endif +#if defined(_WIN32) && (defined(__i386__) || defined(__x86_64__)) +# include +#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);