diff --git a/Makefile b/Makefile index 4921d6b..570b16d 100644 --- a/Makefile +++ b/Makefile @@ -1,71 +1,24 @@ -# fossbench - multi-core CPU benchmark -# -# The assembly kernels are architecture-specific: -# src/fossbench.S AArch64 (ARM64) -# 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 -# The C driver (src/main.c) is portable across architectures and OSes. A -# "binary that runs everywhere" is not possible - each OS/arch pair uses a -# different executable format and instruction set - so output is named per -# platform, e.g. dist/fossbench-linux-arm64, dist/fossbench-linux-amd64. -# -# Common targets: -# make build for the host arch (dist/fossbench--) -# make linux-arm64 build the Linux/ARM64 binary -# make linux-amd64 build the Linux/AMD64 binary -# 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 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/ -# -# Cross-compiling: linux-amd64 on an ARM64 host (or vice versa) needs the -# matching cross toolchain. The compiler for each target defaults to the host -# `cc` when the host arch already matches, and to the conventional GNU cross -# compiler otherwise. Override with CC_ARM64=... / CC_AMD64=... if your -# toolchain is named differently, e.g.: -# make linux-amd64 CC_AMD64=x86_64-linux-gnu-gcc-14 -# make linux-arm64 CC_ARM64="clang --target=aarch64-linux-gnu" -# make linux-ppc64be CC_PPC64BE=powerpc64-linux-gnu-gcc -# -# On macOS, Apple Clang can build both architectures. The macOS compiler may -# 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) uses WinHTTP - a system component -# present on every Windows install - instead of OpenSSL, 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 +# fossbench build file +# Use make for the current computer, or a named target for another one. CC ?= cc CFLAGS ?= -O2 -Wall -Wextra TLS_CFLAGS ?= TLS_LDLIBS ?= -lssl -lcrypto LDLIBS ?= -lm $(TLS_LDLIBS) -# The driver spreads each workload across all cores with pthreads. +# Needed for the worker threads. PTHREAD := -pthread DIST := dist -DRIVER := src/main.c -ASM_ARM64 := src/fossbench.S -ASM_AMD64 := src/fossbench_x86_64.S -ASM_I386 := src/fossbench_i386.S -SRC_PPC32 := src/fossbench_ppc32.c -ASM_PPC32 := src/fossbench_ppc32_ext.S -SRC_PPC64 := src/fossbench_ppc32.c +DRIVER := src/main.c src/app/benchmark.c +ASM_ARM64 := src/kernels/fossbench-arm64.S +ASM_AMD64 := src/kernels/fossbench-amd64.S +ASM_I386 := src/kernels/fossbench-i386.S +SRC_PPC32 := src/kernels/fossbench-powerpc.c +ASM_PPC32 := src/kernels/fossbench-ppc32-ext.S +SRC_PPC64 := src/kernels/fossbench-powerpc.c -# ---- host detection: normalise `uname -m` to our arch names ---- +# Figure out the host CPU. HOST_ARCH := $(shell uname -m) ifneq (,$(filter aarch64 arm64,$(HOST_ARCH))) HOST_ARCHNAME := arm64 @@ -87,14 +40,7 @@ else $(error unsupported host architecture '$(HOST_ARCH)') endif ifeq ($(HOST_ARCHNAME),i386) - # The kernels are hand-written assembly (fossbench_i386.S) using SSE2 - # directly, so -msse2/-mfpmath=sse have nothing left to gate - only - # main.c (the portable driver) is still compiled from C here. - # - # -fno-pie: i386 PIC costs a whole general-purpose register (already the - # scarcest resource in 32-bit mode) for the life of any function that - # touches global data or calls out - a tax amd64/arm64 don't pay the same - # way. Paired with -no-pie at link time below. + # Keep the old i386 target simple and non-PIE. CFLAGS += -march=pentium4 -fno-pie LDFLAGS += -no-pie endif @@ -105,7 +51,7 @@ ifeq ($(HOST_ARCHNAME),arm64) HOST_KERNEL := $(ASM_ARM64) endif -# ---- host OS name for the native binary ---- +# Figure out the host OS. UNAME_S := $(shell uname -s) ifeq ($(UNAME_S),Linux) OSNAME := linux @@ -117,7 +63,7 @@ else OSNAME := $(shell uname -s | tr '[:upper:]' '[:lower:]') endif -# ---- per-target compilers: native cc if the host matches, else a cross gcc ---- +# Pick a compiler for each target. ifeq ($(HOST_ARCHNAME),arm64) CC_ARM64 ?= $(CC) else @@ -146,20 +92,20 @@ 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. +# Windows uses MinGW. 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. +# Plain make builds for this computer. .DEFAULT_GOAL := native .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, plus the (cross-compiled) Windows ones. +# Build the release targets. 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. +# Build for this computer. native: $(NATIVE_BIN) linux-arm64: $(DIST)/fossbench-linux-arm64 @@ -200,13 +146,7 @@ $(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 uses WinHTTP (-lwinhttp) instead of OpenSSL for TLS, so unlike -# every other target here, these don't need $(TLS_CFLAGS)/$(TLS_LDLIBS); -static -# doesn't affect winhttp.dll, which ships with Windows itself. $(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. +# Windows builds are static and use WinHTTP. $(DIST)/fossbench-windows-amd64.exe: $(DRIVER) $(ASM_AMD64) | $(DIST) $(CC_WINDOWS_AMD64) $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_AMD64) -lm -lwinhttp @echo "built $@" @@ -215,9 +155,7 @@ $(DIST)/fossbench-windows-i386.exe: $(DRIVER) $(ASM_I386) | $(DIST) $(CC_WINDOWS_I386) -march=pentium4 $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_I386) -lm -lwinhttp @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. +# Add a native rule if one was not already made above. ifeq ($(OSNAME)-$(HOST_ARCHNAME),linux-arm64) NATIVE_HAS_RULE := yes endif diff --git a/README.md b/README.md index 1806803..9f7c626 100644 --- a/README.md +++ b/README.md @@ -1,255 +1,159 @@ # fossbench -fossbench is an open-source CPU benchmark with nine assembly workloads and a -small C driver. It measures each workload twice: once on a single core and once -across every available core. The final report includes separate single-core and -multicore scores. +fossbench is a CPU benchmarking tool thats fully open-source. The core idea is to build an open-source alternative to Passmark, Geekbench, and the like by providing our entire database for free to the public. Crowdsourcing the data to ensure its accuracy without any hidden strings being pulled behind the scenes. -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, on -Linux, macOS, and Windows. The C driver handles timing, memory, -threads, output, and scoring. Performance-sensitive kernels live in -architecture-specific backend files. +## Supported systems -## Workloads +fossbench builds on Linux, macOS, and Windows for these architectures: -| Test | What it measures | +| Architecture | Baseline | |---|---| -| Integer math | 64-bit multiplication, division, shifts, and bit operations | -| Floating point math | Scalar double-precision multiplication, addition, division, and square roots | -| Prime numbers | A sieve of Eratosthenes up to 2,000,000 | -| Extended instructions | 128-bit SIMD integer and floating point work using NEON or SSE2 | -| Compression | An LZ77 match finder over a 4 MiB generated corpus | -| Encryption | ChaCha20 with 20 rounds over a 1 MiB buffer | -| Physics | Direct-sum gravity for 512 bodies | -| Sorting | In-place heapsort of one million 32-bit integers | -| Memory latency | Dependent pointer chasing through a private cache-exceeding cycle | +| ARM64 | ARMv8-A with NEON | +| x86-64 | baseline x86-64 with SSE2 | +| x86 32-bit | baseline i386 with SSE2 | +| PowerPC 32-bit big-endian | scalar fallback with runtime-selected extended instructions | +| PowerPC 64-bit big-endian | PowerPC 970 with AltiVec | -The benchmark increases each test's iteration count until one run takes at -least two seconds. It then keeps the fastest of three runs. Each kernel returns -a checksum, and fossbench stops if repeated runs produce different results. +If you find something it doesnt run on, please make a PR with patches if you think you can make it! -During the multicore pass, every thread gets its own mutable workspace. This -keeps the kernels free of data races and prevents shared scratch buffers from -distorting the result. +## Building -## Build and run +You need GNU Make, a C compiler, pthreads, and the system math library. Linux +and macOS builds also need OpenSSL headers and libraries. Windows uses WinHTTP +and does not depend on OpenSSL. -You need a C compiler, GNU Make, OpenSSL development headers and libraries, -pthreads, and the system math library. +Build for the current machine: ```sh make +``` + +The binary is written to `dist/fossbench--`. To build it and start a +benchmark immediately, run: + +```sh make bench ``` -`make` builds a binary for the host at -`dist/fossbench--`. `make bench` builds that binary and runs it. +Named targets are available when you want a specific build, these can be found in the Makefile. -Other targets are available for explicit platforms and architectures: - -```sh -make linux-arm64 -make linux-amd64 -make linux-i386 # Pentium 4 / SSE2 baseline -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 five Linux targets plus both Windows targets. -Cross-compilation requires a suitable toolchain. Override the target compiler -when its name differs from the default: +Cross builds use conventional GNU toolchain names by default. Override a +compiler when your toolchain uses a different name: ```sh make linux-arm64 CC_ARM64=aarch64-linux-gnu-gcc -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 -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 build defaults to macOS 10.5 compatibility. Set +`MACOS_AMD64_MIN` to choose another deployment target. -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 -deployment floor when needed with `MACOS_AMD64_MIN`, for example -`make macos-amd64 MACOS_AMD64_MIN=10.8`. +## Running a benchmark -Run the benchmark with extra per-test details by passing `--verbose`: +Run the binary directly. The exact name depends on your build: ```sh -./dist/fossbench-linux-amd64 --verbose +./dist/fossbench-linux-amd64 ``` -The exact filename depends on the host platform and architecture. +Useful options: -At startup, fossbench reports the detected CPU model, physical cores, logical -threads, installed memory, operating system, architecture, and compiler. At the -start of a normal run it also samples whole-system CPU activity for ten seconds, -then reports average and peak background CPU use, available memory, the current -process count, and the OS kernel/build. Use `--no-system-check` to skip this -startup sample (for example, in automated test runs). These summary metrics and -the kernel/build identifier are included with uploaded result diagnostics; no -process names or command lines are collected. +```text +--verbose print details for each workload +--no-system-check skip the startup system activity sample +--upload upload the result without prompting +--noupload do not prompt or upload +``` -At the end it prints the composite scores and total benchmark duration, then asks -whether to upload the result. Uploading is opt-in and anonymous by default; no -account or API token is required. Pass `--upload` to upload without asking, or -`--noupload` to skip the prompt and never upload. +Before a normal run, fossbench samples system activity for ten seconds. It +reports background CPU use, available memory, process count, and the OS kernel +or build. It does not collect process names or command lines. -To associate results with your fossbench.net profile instead of submitting -anonymously, create an API token under Account -> Benchmark client API token -and set it in the environment: +Result uploads are optional and anonymous unless you provide an API token. To +attach a result to your fossbench.net account, create a benchmark client token +on the site and export it before running the benchmark: ```sh export FOSSBENCH_TOKEN=fb_your_token_here ./dist/fossbench-linux-amd64 --upload ``` -The token is never printed or logged by fossbench. - -The API base URL is defined by `FB_API_BASE_URL` in `src/main.c` and defaults to -`https://fossbench.net`. A release build can override it without editing the -source: +You can point a build at another server with a compile-time definition: ```sh make CFLAGS='-O2 -Wall -Wextra -DFB_API_BASE_URL=\"https://bench.example.com\"' ``` -HTTPS uploads use OpenSSL with certificate and hostname verification. +## What it measures -The PPC64 target is big-endian and is compiled for the PowerPC 970 with -AltiVec, matching the CPU used by the iMac G5. It targets 64-bit Linux; use a -PowerPC64 Linux installation or live environment on the machine to run it. - -PPC64 is source-build support only. The commonly available PPC64 cross-build -libc requires POWER6 instructions and produces release binaries that fault on -the iMac G5's PowerPC 970. Build `linux-ppc64be` natively on the G5 so it uses -the compatible Arch POWER ELFv2 runtime. - -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. 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, 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. +| Workload | Measurement | +|---|---| +| Integer math | 64-bit multiplication, division, shifts, and bit operations | +| Floating point | scalar double-precision arithmetic | +| Prime numbers | sieve of Eratosthenes up to 2,000,000 | +| Extended instructions | 128-bit integer and floating point vector work | +| Compression | LZ77 match finding over a generated 4 MiB corpus | +| Encryption | ChaCha20 over a 1 MiB buffer | +| Physics | direct-sum gravity for 512 bodies | +| Sorting | in-place heapsort of one million 32-bit integers | +| Memory latency | dependent pointer chasing through a private cycle larger than cache | ## Scores -Each workload receives a score relative to a reference rate: +A workload score compares its measured rate with a fixed reference rate: ```text test score = 10000 * measured rate / reference rate ``` -The single-core and multicore totals are weighted geometric means of the nine -test scores. Both passes use the same reference rates and weights, so their -ratio gives a direct view of scaling across the machine's available cores. +The single-core and multicore totals are weighted geometric means. Both passes +use the same references and weights. -| Test | Weight | +| Workload | Weight | |---|---:| | Integer math | 20% | | Memory latency | 16% | | Compression | 14% | | Sorting | 12% | | Extended instructions | 11% | -| Floating point math | 9% | +| Floating point | 9% | | Encryption | 8% | | Prime numbers | 6% | | Physics | 4% | -The reference rates, weights, target score, workload sizes, calibration floor, -and repeat count are compile-time constants in `src/main.c`. Changing them -creates a different benchmark profile, so scores from that build should not be -compared with scores from the default build. +The benchmark profile lives in `src/app/benchmark.c`. Changing its reference +rates, weights, workload sizes, calibration time, or repeat count makes scores +incompatible with the default build. -Memory latency is displayed as nanoseconds per access, but its score uses the -underlying pointer-chase throughput. Latency results are sensitive to memory -placement and operating-system activity, so some variation between runs is -normal. - -## Architecture support - -The kernel backends use only baseline instructions for their architecture: - -* `src/fossbench.S` uses ARMv8-A and NEON under AAPCS64. -* `src/fossbench_x86_64.S` uses baseline x86-64 and SSE2 under the System V ABI. -* `src/fossbench_i386.S` uses baseline 32-bit x86 (Pentium 4) and SSE2 under the - i386 System V (cdecl) ABI. With only six general-purpose registers, no - 64-bit integer registers, and half of amd64's SSE2 register file (xmm0-7), - several kernels keep working state on the stack instead of in registers - - a real cost of the architecture, not an oversight. -* `src/fossbench_ppc32.c` is endian-safe and keeps a baseline 32-bit PowerPC - fallback. At runtime, the extended-instruction test uses Paired Singles when - the device-tree `compatible` property begins with `nintendo,`; otherwise it - selects VSX, AltiVec, or the scalar fallback in that order according to - Linux `AT_HWCAP`. -* The same C backend builds for 64-bit big-endian PowerPC. Its PPC64 path uses - the PowerPC 970's AltiVec unit and leaves out the PPC32-only assembly helpers. - -The PPC32 build uses a 2 MiB pointer-chase cycle, which exceeds the 750CL's L2 -cache while keeping peak benchmark memory consumption below 32 MiB. Other -architectures retain the default 16 MiB cycle. - -The assembly kernel files contain no system calls or calls into the C library. -The same ARM64 source can be assembled for Linux, macOS, Windows, and BSD object formats. -The current x86-64 source supports Linux, macOS, and the BSDs that use the -System V calling convention. - -One binary cannot run on every supported target because operating systems and -architectures use different executable formats and instruction sets. Build a -separate binary for each operating system and architecture pair. +Memory latency is printed in nanoseconds per access, though scoring uses the +underlying pointer-chase throughput. Memory placement and background operating +system work can move this result between runs. ## Tests -The correctness suite checks all nine kernels against C reference -implementations, known answers, or invariants. Most checks also run concurrently -on every available core to catch shared-state and reentrancy bugs. +The correctness suite compares the kernels with C implementations, known +answers, or invariants. It covers the RFC 8439 ChaCha20 vector, prime counts, +sorting, physics momentum, pointer chasing, deterministic output, and concurrent +execution. ```sh make test ``` -The suite covers the RFC 8439 ChaCha20 test vector, prime counts, sorting output, -physics momentum, pointer-chase behavior, and deterministic results. It exits -with a nonzero status if any check fails. +The command exits with a nonzero status when a check fails. -## Source layout +## Repository layout ```text -src/main.c portable benchmark driver and scoring -src/fossbench.S ARM64 kernels -src/fossbench_x86_64.S x86-64 kernels -src/fossbench_i386.S i386 (Pentium 4) kernels -src/fossbench_ppc32.c PPC32/PPC64 big-endian kernels -src/fossbench_ppc32_ext.S optional PPC32 PS, VSX, and AltiVec kernels -src/test_kernels.c correctness suite -Makefile native and cross-build targets -dist/ generated binaries +src/main.c command-line parsing and entrypoint +src/app/benchmark.c workload setup, timing, scoring, and run flow +src/app/benchmark.h function used by main.c +src/app/upload.c API payload and network transport +src/kernels/fossbench-arm64.S ARM64 kernels +src/kernels/fossbench-amd64.S x86-64 kernels +src/kernels/fossbench-i386.S i386 kernels +src/kernels/fossbench-powerpc.c PowerPC kernels +src/kernels/fossbench-ppc32-ext.S optional PPC32 extended kernels +src/test_kernels.c kernel correctness suite ``` diff --git a/src/app/benchmark.c b/src/app/benchmark.c new file mode 100644 index 0000000..0044d8f --- /dev/null +++ b/src/app/benchmark.c @@ -0,0 +1,1037 @@ +/* Runs the benchmark stuff that the kernels do not handle. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "benchmark.h" +#if defined(__linux__) +# include +#endif + +#if !defined(_WIN32) +# include +# include +# include +# include +# include +# include +# include "../ca_bundle.h" +#endif +#if defined(__APPLE__) +# include +# include +# include +# include +#endif +#if defined(_WIN32) && (defined(__i386__) || defined(__x86_64__)) +# include +#endif + +/* The server URL can be changed when building. */ +#ifndef FB_API_BASE_URL +# define FB_API_BASE_URL "https://fossbench.net" +#endif +#define FB_VERSION "0.1.6" + +/* Names used in the header. */ + +#if defined(_WIN32) +# define FB_OS "Windows" +#elif defined(__APPLE__) +# define FB_OS "macOS" +#elif defined(__linux__) +# define FB_OS "Linux" +#else +# define FB_OS "POSIX" +#endif + +#if defined(__aarch64__) || defined(_M_ARM64) +# define FB_ARCH "ARM64" +# define D_INT "64-bit ALU: madd, umulh, udiv, bitops" +# define D_FP "double: fmadd, fdiv, fsqrt" +# define D_SIMD "NEON ASIMD: 128-bit integer + float" +#elif defined(__x86_64__) || defined(_M_X64) +# define FB_ARCH "x86-64" +# define D_INT "64-bit ALU: imul, mul, div, bitops" +# define D_FP "double: mulsd/addsd, divsd, sqrtsd" +# define D_SIMD "SSE2: 128-bit integer + float" +#elif defined(__i386__) || defined(_M_IX86) +# define FB_ARCH "x86 32-bit" +# define D_INT "Pentium 4 integer ALU and software 64-bit arithmetic" +# define D_FP "x87 scalar double-precision floating point" +# define D_SIMD "SSE2: 128-bit integer vectors" +#elif defined(__powerpc64__) +# define FB_ARCH "PowerPC 64-bit big-endian" +# define D_INT "64-bit PowerPC integer ALU" +# define D_FP "PowerPC scalar double-precision floating point" +# define D_SIMD "AltiVec: 128-bit integer vectors (PowerPC 970)" +#elif defined(__powerpc__) +# define FB_ARCH "PowerPC 32-bit big-endian" +# define D_INT "PPC32 integer ALU and software 64-bit arithmetic" +# define D_FP "PowerPC scalar double-precision floating point" +# define D_SIMD "runtime-selected PS, VSX, AltiVec, or scalar" +#else +# define FB_ARCH "unknown" +# define D_INT "64-bit integer ALU" +# define D_FP "double-precision FP" +# define D_SIMD "128-bit SIMD: integer + float" +#endif + +/* Get the current time. */ + +#if defined(_WIN32) +# define WIN32_LEAN_AND_MEAN +# include +# include +# include +static double now_seconds(void) +{ + LARGE_INTEGER f, t; + QueryPerformanceFrequency(&f); + QueryPerformanceCounter(&t); + return (double)t.QuadPart / (double)f.QuadPart; +} +#elif defined(__APPLE__) +static double now_seconds(void) +{ + static mach_timebase_info_data_t timebase; + uint64_t ticks; + + if (timebase.denom == 0) + mach_timebase_info(&timebase); + ticks = mach_absolute_time(); + return (double)ticks * (double)timebase.numer / + (double)timebase.denom * 1e-9; +} +#else +# include +static double now_seconds(void) +{ + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; +} +#endif + +/* Functions provided by the kernel files. */ + +extern uint64_t fb_int_math(uint64_t iters); +extern uint64_t fb_fp_math(uint64_t iters); +extern uint64_t fb_primes(uint64_t limit, uint8_t *sieve); +extern uint64_t fb_simd(uint64_t iters, void *buf); +extern uint64_t fb_compress(const uint8_t *src, uint64_t len, uint32_t *ht); +extern uint64_t fb_chacha20(uint8_t *buf, uint64_t len, + const uint8_t key[32], uint64_t rounds); +extern uint64_t fb_physics(double *bodies, uint64_t n, uint64_t steps); +extern uint64_t fb_sort(uint32_t *a, uint64_t n); +extern uint64_t fb_chase(void **ptrs, uint64_t steps); + +/* Test sizes and timing settings. */ + +#define PRIME_LIMIT (2u * 1000u * 1000u) /* Prime test size. */ +#define COMPRESS_LEN (4u * 1024u * 1024u) /* Compression data size. */ +#define HT_ENTRIES (1u << 16) /* Compression table size. */ +#define CIPHER_LEN (1u * 1024u * 1024u) /* Encryption data size. */ +#define SIMD_BUF 256 /* Small SIMD buffer. */ +#define NBODY_N 512 /* Number of physics bodies. */ +#define SORT_N (1u << 20) /* Number of values to sort. */ +/* Use less memory on 32-bit PowerPC. */ +#if defined(__powerpc__) && !defined(__powerpc64__) +# define CHASE_NODES (1u << 19) /* Smaller pointer loop. */ +# define CHASE_DETAIL "dependent-load pointer chase, 2 MiB" +#elif UINTPTR_MAX == UINT32_MAX +# define CHASE_NODES (1u << 21) /* Pointer loop size. */ +# define CHASE_DETAIL "dependent-load pointer chase, 8 MiB" +#else +# define CHASE_NODES (1u << 21) /* Pointer loop size. */ +# define CHASE_DETAIL "dependent-load pointer chase, 16 MiB" +#endif + +#define MIN_SECONDS 2.0 /* Minimum run time. */ +#define REPEATS 3 /* Number of tries. */ + +/* Numbers used to calculate scores. */ + +#define FB_TARGET_SCORE 10000.0 /* reference-machine overall. */ + +/* Rates from the reference machine. */ +#define FB_REF_INT 3086.0 /* Reference rate. */ +#define FB_REF_FP 1682.0 /* Reference rate. */ +#define FB_REF_PRIMES 812.0 /* Reference rate. */ +#define FB_REF_SIMD 6576.0 /* Reference rate. */ +#define FB_REF_COMPRESS 674.0 /* Reference rate. */ +#define FB_REF_CRYPTO 406.0 /* Reference rate. */ +#define FB_REF_PHYSICS 631.0 /* Reference rate. */ +#define FB_REF_SORT 363.0 /* Reference rate. */ +#define FB_REF_CHASE 79.0 /* Memory test reference. */ + +/* How much each test counts. */ +#define FB_WEIGHT_INT 20.0 /* Score weight. */ +#define FB_WEIGHT_CHASE 16.0 /* Score weight. */ +#define FB_WEIGHT_COMPRESS 14.0 /* Score weight. */ +#define FB_WEIGHT_SORT 12.0 /* Score weight. */ +#define FB_WEIGHT_SIMD 11.0 /* Score weight. */ +#define FB_WEIGHT_FP 9.0 /* Score weight. */ +#define FB_WEIGHT_CRYPTO 8.0 /* Score weight. */ +#define FB_WEIGHT_PRIMES 6.0 /* Score weight. */ +#define FB_WEIGHT_PHYSICS 4.0 /* Score weight. */ + +/* Simple repeatable random numbers. */ + +static uint64_t rng_state = 0x853c49e6748fea9bULL; + +static uint64_t rng_next(void) +{ + uint64_t z = (rng_state += 0x9e3779b97f4a7c15ULL); + z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; + z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; + return z ^ (z >> 31); +} + +static void rng_reset(void) { rng_state = 0x853c49e6748fea9bULL; } + +/* Allocate aligned memory for the kernels. */ + +static void *xalloc(size_t n) +{ + void *p = NULL; +#if defined(_WIN32) + p = _aligned_malloc(n, 64); +#else + if (posix_memalign(&p, 64, n) != 0) + p = NULL; +#endif + if (!p) { + fprintf(stderr, "fossbench: out of memory (%zu bytes)\n", n); + exit(1); + } + return p; +} + +static void xfree(void *p) +{ +#if defined(_WIN32) + _aligned_free(p); +#else + free(p); +#endif +} + +struct background_metrics { + double average_cpu_percent, peak_cpu_percent; + long available_memory_mb, process_count; + int samples, available; +}; + +struct cpu_snapshot { uint64_t total, idle; }; + +static int take_cpu_snapshot(struct cpu_snapshot *s) +{ +#if defined(__linux__) + FILE *f = fopen("/proc/stat", "r"); + unsigned long long user=0, nice=0, system=0, idle=0, wait=0, irq=0, softirq=0, steal=0; + int n; + if (!f) return 0; + n = fscanf(f, "cpu %llu %llu %llu %llu %llu %llu %llu %llu", + &user, &nice, &system, &idle, &wait, &irq, &softirq, &steal); + fclose(f); if (n < 4) return 0; + s->idle = idle + wait; + s->total = user + nice + system + idle + wait + irq + softirq + steal; + return 1; +#elif defined(__APPLE__) + host_cpu_load_info_data_t cpu; mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; + if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&cpu, &count) != KERN_SUCCESS) return 0; + s->idle = cpu.cpu_ticks[CPU_STATE_IDLE]; + s->total = cpu.cpu_ticks[CPU_STATE_USER] + cpu.cpu_ticks[CPU_STATE_SYSTEM] + s->idle + cpu.cpu_ticks[CPU_STATE_NICE]; + return 1; +#elif defined(_WIN32) + FILETIME idle, kernel, user; ULARGE_INTEGER i, k, u; + if (!GetSystemTimes(&idle, &kernel, &user)) return 0; + i.LowPart=idle.dwLowDateTime; i.HighPart=idle.dwHighDateTime; + k.LowPart=kernel.dwLowDateTime; k.HighPart=kernel.dwHighDateTime; + u.LowPart=user.dwLowDateTime; u.HighPart=user.dwHighDateTime; + s->idle=i.QuadPart; s->total=k.QuadPart+u.QuadPart; return 1; +#else + (void)s; return 0; +#endif +} + +static void take_resource_snapshot(long *available_mb, long *processes) +{ + *available_mb = -1; *processes = -1; +#if defined(__linux__) + { + FILE *f=fopen("/proc/meminfo","r"); char line[256]; long kb; + if (f) { while (fgets(line,sizeof(line),f)) if (sscanf(line,"MemAvailable: %ld kB",&kb)==1) { *available_mb=kb/1024; break; } fclose(f); } + } + { + DIR *dir=opendir("/proc"); struct dirent *entry; long count=0; + if (dir) { while ((entry=readdir(dir)) != NULL) { const char *p=entry->d_name; if (!*p) continue; while (*p && isdigit((unsigned char)*p)) p++; if (!*p) count++; } closedir(dir); *processes=count; } + } +#elif defined(__APPLE__) + { + vm_statistics_data_t vm; mach_msg_type_number_t count=HOST_VM_INFO_COUNT; vm_size_t page; + if (host_page_size(mach_host_self(),&page)==KERN_SUCCESS && host_statistics(mach_host_self(),HOST_VM_INFO,(host_info_t)&vm,&count)==KERN_SUCCESS) + *available_mb=(long)(((uint64_t)vm.free_count+vm.inactive_count)*page/1024/1024); + } + { + int mib[4]={CTL_KERN,KERN_PROC,KERN_PROC_ALL,0}; size_t bytes=0; + if (sysctl(mib,4,NULL,&bytes,NULL,0)==0) *processes=(long)(bytes/sizeof(struct kinfo_proc)); + } +#elif defined(_WIN32) + { + MEMORYSTATUSEX ms; ms.dwLength=sizeof(ms); if (GlobalMemoryStatusEx(&ms)) *available_mb=(long)(ms.ullAvailPhys/1024/1024); + } + { + HANDLE snap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); PROCESSENTRY32 entry; long count=0; entry.dwSize=sizeof(entry); + if (snap!=INVALID_HANDLE_VALUE) { if (Process32First(snap,&entry)) do { count++; } while (Process32Next(snap,&entry)); CloseHandle(snap); *processes=count; } + } +#endif +} + +static void sample_background_metrics(struct background_metrics *m, int seconds) +{ + struct cpu_snapshot before, after; int i; + memset(m,0,sizeof(*m)); m->available_memory_mb=-1; m->process_count=-1; + printf("\n checking background system activity for %d seconds",seconds); fflush(stdout); + if (!take_cpu_snapshot(&before)) { printf("... unavailable\n"); return; } + for (i=0;ibefore.total) { + uint64_t total=after.total-before.total, idle=after.idle-before.idle; + double busy=100.0*(double)(total > idle ? total-idle : 0)/(double)total; + m->average_cpu_percent+=busy; if (busy>m->peak_cpu_percent) m->peak_cpu_percent=busy; + m->samples++; before=after; + } + printf("."); fflush(stdout); + } + take_resource_snapshot(&m->available_memory_mb,&m->process_count); + if (m->samples) { m->average_cpu_percent/=m->samples; m->available=1; } + printf(" done\n"); +} + +/* Buffers used while tests are running. */ +struct workspace { + uint8_t *sieve; /* Prime test buffer. */ + uint32_t *ht; /* Compression table. */ + uint8_t *cipher_buf; /* ChaCha20 buffer, encrypted in place. */ + uint8_t *simd_buf; /* Small SIMD buffer. */ + double *bodies; /* n-body integration buffer. */ + uint32_t *sort_work; /* the buffer we actually sort. */ + void **chase; /* private 16 MiB pointer-chase cycle. */ +}; + +static long g_ncores = 1; /* active online cores. */ +static struct workspace *g_ws; /* g_ncores per-thread workspaces. */ + +static uint8_t *g_corpus; /* shared, read-only compression input. */ +static uint8_t g_key[32]; /* shared, read-only cipher key. */ +static uint8_t *g_cipher_src; /* pristine plaintext, copied per-core. */ +static uint8_t *g_simd_src; /* pristine NEON seed, copied per-core. */ +static double *g_bodies_src; /* pristine initial conditions. */ +static uint32_t *g_sort_src; /* pristine unsorted data. */ + +struct system_info { + char cpu[256]; + char model[256]; + char operating_system[256]; + char compiler[128]; + char kernel[128]; + long cpu_cores; + long cpu_threads; + long memory_mb; +}; + +static void trim(char *s) +{ + char *p = s; + size_t n; + while (isspace((unsigned char)*p)) p++; + if (p != s) memmove(s, p, strlen(p) + 1); + n = strlen(s); + while (n && isspace((unsigned char)s[n - 1])) s[--n] = '\0'; +} + +/* Read the first device tree value. */ +static int read_first_property(const char *path, char *dst, size_t cap) +{ + FILE *f; + size_t n, i; + + if (cap == 0) + return 0; + f = fopen(path, "rb"); + if (f == NULL) + return 0; + n = fread(dst, 1, cap - 1, f); + fclose(f); + for (i = 0; i < n && dst[i] != '\0' && dst[i] != '\n' && dst[i] != '\r'; i++) + if ((unsigned char)dst[i] < 0x20) + dst[i] = ' '; + dst[i] = '\0'; + trim(dst); + return dst[0] != '\0'; +} + +static void detect_system_info(struct system_info *info) +{ +#if defined(__linux__) + char cpuinfo_hardware[sizeof info->model] = ""; +#endif + memset(info, 0, sizeof(*info)); + info->cpu_threads = g_ncores; + info->cpu_cores = g_ncores; + strncpy(info->cpu, FB_ARCH, sizeof(info->cpu) - 1); + strncpy(info->operating_system, FB_OS, sizeof(info->operating_system) - 1); +#if defined(__clang__) + snprintf(info->compiler, sizeof(info->compiler), "Clang %s", __clang_version__); +#elif defined(__GNUC__) + snprintf(info->compiler, sizeof(info->compiler), "GCC %s", __VERSION__); +#elif defined(_MSC_VER) + snprintf(info->compiler, sizeof(info->compiler), "MSVC %d", _MSC_VER); +#else + strncpy(info->compiler, "Unknown", sizeof(info->compiler) - 1); +#endif + +#if defined(__linux__) + { + struct utsname u; + if (uname(&u) == 0) + snprintf(info->kernel, sizeof(info->kernel), "%s %s", u.sysname, u.release); + } + { + static const char *const model_paths[] = { + "/sys/firmware/devicetree/base/compatible", + "/sys/firmware/devicetree/base/model", + "/proc/device-tree/compatible" + }; + unsigned i; + for (i = 0; i < sizeof model_paths / sizeof model_paths[0]; i++) + if (read_first_property(model_paths[i], info->model, + sizeof info->model)) + break; + } + { + FILE *f = fopen("/proc/cpuinfo", "r"); + char line[512]; + int pairs[1024][2], npairs = 0, physical = -1, core = -1; + if (f) { + while (fgets(line, sizeof(line), f)) { + char *colon = strchr(line, ':'); + if (!colon) continue; + *colon++ = '\0'; trim(line); trim(colon); + if ((!strcmp(line, "model name") || !strcmp(line, "Processor") || + !strcmp(line, "cpu")) && info->cpu[0] && !strcmp(info->cpu, FB_ARCH)) + strncpy(info->cpu, colon, sizeof(info->cpu) - 1); + else if (!strcmp(line, "Hardware") && cpuinfo_hardware[0] == '\0') + strncpy(cpuinfo_hardware, colon, sizeof cpuinfo_hardware - 1); + else if (!strcmp(line, "physical id")) physical = atoi(colon); + else if (!strcmp(line, "core id")) core = atoi(colon); + if (physical >= 0 && core >= 0) { + int i, seen = 0; + for (i = 0; i < npairs; i++) + if (pairs[i][0] == physical && pairs[i][1] == core) seen = 1; + if (!seen && npairs < 1024) { pairs[npairs][0] = physical; pairs[npairs++][1] = core; } + physical = core = -1; + } + } + fclose(f); + if (npairs > 0) info->cpu_cores = npairs; + } + } + /* Try the system files if CPU info is missing. */ +#if defined(__aarch64__) || defined(__arm__) + if (info->model[0] == '\0') + read_first_property("/sys/class/dmi/id/product_name", info->model, + sizeof info->model); +#endif + if (info->model[0] == '\0' && cpuinfo_hardware[0] != '\0') + snprintf(info->model, sizeof info->model, "%s", cpuinfo_hardware); + { + FILE *f = fopen("/proc/meminfo", "r"); + char line[256]; long kb; + if (f) { + while (fgets(line, sizeof(line), f)) { + if (sscanf(line, "MemTotal: %ld kB", &kb) == 1) { + info->memory_mb = kb / 1024; + break; + } + } + fclose(f); + } + } + { + FILE *f = fopen("/etc/os-release", "r"); char line[512]; + if (f) { while (fgets(line, sizeof(line), f)) if (!strncmp(line, "PRETTY_NAME=", 12)) { + char *v = line + 12; trim(v); + if (v[0] == '\"') { memmove(v, v + 1, strlen(v)); if (strlen(v) && v[strlen(v)-1] == '\"') v[strlen(v)-1] = '\0'; } + snprintf(info->operating_system, sizeof(info->operating_system), "%s", v); break; + } fclose(f); } + } +#elif defined(__APPLE__) + { + size_t n = sizeof(info->cpu); uint64_t mem = 0; size_t mn = sizeof(mem); + size_t model_n = sizeof(info->model); + int cores = 0; size_t cn = sizeof(cores); + if (sysctlbyname("machdep.cpu.brand_string", info->cpu, &n, NULL, 0) != 0) + strncpy(info->cpu, FB_ARCH, sizeof info->cpu - 1); + sysctlbyname("hw.model", info->model, &model_n, NULL, 0); + if (sysctlbyname("hw.physicalcpu", &cores, &cn, NULL, 0) == 0) info->cpu_cores = cores; + if (sysctlbyname("hw.memsize", &mem, &mn, NULL, 0) == 0) info->memory_mb = (long)(mem / 1024 / 1024); + } + { + char product[64] = ""; size_t pn = sizeof(product); + struct utsname u; + if (sysctlbyname("kern.osproductversion", product, &pn, NULL, 0) == 0) + snprintf(info->operating_system, sizeof(info->operating_system), "macOS %s", product); + if (uname(&u) == 0) + snprintf(info->kernel, sizeof(info->kernel), "%s %s", u.sysname, u.release); + } +#elif defined(_WIN32) + { + MEMORYSTATUSEX ms; + ms.dwLength = sizeof(ms); + if (GlobalMemoryStatusEx(&ms)) + info->memory_mb = (long)(ms.ullTotalPhys / 1024 / 1024); + } + { + OSVERSIONINFOEXA version; + typedef LONG (WINAPI *rtl_get_version_fn)(OSVERSIONINFOEXA *); + rtl_get_version_fn rtl_get_version = (rtl_get_version_fn)(void *) + GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); + memset(&version, 0, sizeof(version)); version.dwOSVersionInfoSize = sizeof(version); + if (rtl_get_version && rtl_get_version(&version) == 0) { + snprintf(info->operating_system, sizeof(info->operating_system), + "Windows %lu.%lu (build %lu)", (unsigned long)version.dwMajorVersion, + (unsigned long)version.dwMinorVersion, (unsigned long)version.dwBuildNumber); + snprintf(info->kernel, sizeof(info->kernel), "NT %lu.%lu build %lu", + (unsigned long)version.dwMajorVersion, (unsigned long)version.dwMinorVersion, + (unsigned long)version.dwBuildNumber); + } + } +#if defined(__i386__) || defined(__x86_64__) + { + /* Read the CPU name from CPUID. */ + 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 +} + +/* Synthesise a compressible corpus. */ +static void build_corpus(uint8_t *buf, size_t len) +{ + static const char *words[] = { + "the", "quick", "brown", "fox", "jumps", "over", "lazy", + "dog", "benchmark", "processor", "assembly", "vector", + "memory", "cache", "pipeline", "instruction", "compress", + "data", "system", "performance", "register", "kernel" + }; + const size_t nwords = sizeof(words) / sizeof(words[0]); + size_t pos = 0; + + while (pos < len) { + const char *w = words[rng_next() % nwords]; + size_t wl = strlen(w); + + if (pos + wl + 1 > len) + break; + memcpy(buf + pos, w, wl); + pos += wl; + buf[pos++] = (rng_next() % 8 == 0) ? '\n' : ' '; + } + while (pos < len) + buf[pos++] = ' '; +} + +/* Build a random pointer loop. */ +static void build_chase(void **nodes, size_t n) +{ + size_t *perm = xalloc(n * sizeof(size_t)); + size_t i; + + for (i = 0; i < n; i++) + perm[i] = i; + for (i = n - 1; i > 0; i--) { + size_t j = (size_t)(rng_next() % i); /* Do not pick the current item. */ + size_t t = perm[i]; + perm[i] = perm[j]; + perm[j] = t; + } + for (i = 0; i < n; i++) + nodes[perm[i]] = (void *)&nodes[perm[(i + 1) % n]]; + + xfree(perm); +} + +static void setup(void) +{ + size_t i; + long t; + + rng_reset(); + + /* Set up data shared by the workers. */ + g_corpus = xalloc(COMPRESS_LEN); + g_cipher_src = xalloc(CIPHER_LEN); + g_simd_src = xalloc(SIMD_BUF); + g_bodies_src = xalloc(NBODY_N * 8 * sizeof(double)); + g_sort_src = xalloc(SORT_N * sizeof(uint32_t)); + + build_corpus(g_corpus, COMPRESS_LEN); + + for (i = 0; i < CIPHER_LEN; i++) + g_cipher_src[i] = (uint8_t)rng_next(); + for (i = 0; i < 32; i++) + g_key[i] = (uint8_t)rng_next(); + for (i = 0; i < SIMD_BUF; i++) + g_simd_src[i] = (uint8_t)rng_next(); + for (i = 0; i < SORT_N; i++) + g_sort_src[i] = (uint32_t)rng_next(); + + /* Set up the physics bodies. */ + for (i = 0; i < NBODY_N; i++) { + double *b = &g_bodies_src[i * 8]; + b[0] = (double)(rng_next() % 2000) / 1000.0 - 1.0; + b[1] = (double)(rng_next() % 2000) / 1000.0 - 1.0; + b[2] = (double)(rng_next() % 2000) / 1000.0 - 1.0; + b[3] = (double)(rng_next() % 900) / 1000.0 + 0.1; /* Keep mass above zero. */ + b[4] = b[5] = b[6] = 0.0; + b[7] = 0.0; + } + + /* Give every worker its own buffers. */ + g_ws = xalloc((size_t)g_ncores * sizeof *g_ws); + for (t = 0; t < g_ncores; t++) { + struct workspace *w = &g_ws[t]; + + w->sieve = xalloc(PRIME_LIMIT); + w->ht = xalloc(HT_ENTRIES * sizeof(uint32_t)); + w->cipher_buf = xalloc(CIPHER_LEN); + w->simd_buf = xalloc(SIMD_BUF); + w->bodies = xalloc(NBODY_N * 8 * sizeof(double)); + w->sort_work = xalloc(SORT_N * sizeof(uint32_t)); + w->chase = xalloc(CHASE_NODES * sizeof(void *)); + + memcpy(w->cipher_buf, g_cipher_src, CIPHER_LEN); + memcpy(w->simd_buf, g_simd_src, SIMD_BUF); + build_chase(w->chase, CHASE_NODES); + } +} + +static void teardown(void) +{ + long t; + + for (t = 0; t < g_ncores; t++) { + struct workspace *w = &g_ws[t]; + + xfree(w->sieve); xfree(w->ht); xfree(w->cipher_buf); + xfree(w->simd_buf); xfree(w->bodies); xfree(w->sort_work); + xfree(w->chase); + } + xfree(g_ws); + + xfree(g_corpus); xfree(g_cipher_src); xfree(g_simd_src); + xfree(g_bodies_src); xfree(g_sort_src); +} + +/* Code that runs and times each test. */ + +/* Run one kernel with its workspace. */ +typedef uint64_t (*run_fn)(uint64_t n, struct workspace *ws); + +struct test { + const char *name; + const char *detail; + run_fn run; + uint64_t start_n; + double work_per_n; /* Amount of work used for scoring. */ + const char *unit; + double ref_rate; /* Rate used as the score reference. */ + double weight; /* How much this test counts. */ +}; + +static uint64_t run_int(uint64_t n, struct workspace *ws) +{ + (void)ws; + return fb_int_math(n * 100000); +} +static uint64_t run_fp(uint64_t n, struct workspace *ws) +{ + (void)ws; + return fb_fp_math(n * 100000); +} +static uint64_t run_primes(uint64_t n, struct workspace *ws) +{ + uint64_t c = 0; + for (uint64_t i = 0; i < n; i++) + c += fb_primes(PRIME_LIMIT, ws->sieve); + return c; +} +static uint64_t run_simd(uint64_t n, struct workspace *ws) +{ + /* Reset the SIMD buffer before running. */ + memcpy(ws->simd_buf, g_simd_src, SIMD_BUF); + return fb_simd(n * 100000, ws->simd_buf); +} +static uint64_t run_compress(uint64_t n, struct workspace *ws) +{ + uint64_t c = 0; + for (uint64_t i = 0; i < n; i++) + c += fb_compress(g_corpus, COMPRESS_LEN, ws->ht); + return c; +} +static uint64_t run_crypto(uint64_t n, struct workspace *ws) +{ + return fb_chacha20(ws->cipher_buf, CIPHER_LEN, g_key, n); +} +static uint64_t run_physics(uint64_t n, struct workspace *ws) +{ + /* Reset the physics data before running. */ + memcpy(ws->bodies, g_bodies_src, NBODY_N * 8 * sizeof(double)); + return fb_physics(ws->bodies, NBODY_N, n); +} +static uint64_t run_sort(uint64_t n, struct workspace *ws) +{ + uint64_t c = 0; + for (uint64_t i = 0; i < n; i++) { + /* Reset the sort data before running. */ + memcpy(ws->sort_work, g_sort_src, SORT_N * sizeof(uint32_t)); + c ^= fb_sort(ws->sort_work, SORT_N); + } + return c; +} +static uint64_t run_chase(uint64_t n, struct workspace *ws) +{ + return fb_chase(ws->chase, n * 1000000); +} + +static const struct test tests[] = { + { "Integer Math", D_INT, + run_int, 20, 100000.0 * 24, "Mops/s", + FB_REF_INT, FB_WEIGHT_INT }, + { "Floating Point Math", D_FP, + run_fp, 20, 100000.0 * 20, "Mops/s", + FB_REF_FP, FB_WEIGHT_FP }, + { "Prime Numbers", "sieve of Eratosthenes to 2M", + run_primes, 1, (double)PRIME_LIMIT, "Mcand/s", + FB_REF_PRIMES, FB_WEIGHT_PRIMES }, + { "Extended Instructions",D_SIMD, + run_simd, 10, 100000.0 * 32, "Mops/s", + FB_REF_SIMD, FB_WEIGHT_SIMD }, + { "Compression", "LZ77 match finder, 4 MiB corpus", + run_compress, 1, (double)COMPRESS_LEN, "MB/s", + FB_REF_COMPRESS, FB_WEIGHT_COMPRESS }, + { "Encryption", "ChaCha20, 20 rounds, 1 MiB", + run_crypto, 4, (double)CIPHER_LEN, "MB/s", + FB_REF_CRYPTO, FB_WEIGHT_CRYPTO }, + { "Physics", "512-body direct-sum gravity", + run_physics, 4, (double)NBODY_N * NBODY_N, "Mpair/s", + FB_REF_PHYSICS, FB_WEIGHT_PHYSICS }, + { "Sorting", "heapsort, 1M uint32", + run_sort, 1, (double)SORT_N * 20, "Mkey-cmp/s", + FB_REF_SORT, FB_WEIGHT_SORT }, + { "Memory Latency", CHASE_DETAIL, + run_chase, 1, 1000000.0, "ns/access", + FB_REF_CHASE, FB_WEIGHT_CHASE }, +}; + +#define NTESTS (sizeof(tests) / sizeof(tests[0])) + +struct result { + double rate; /* Measured speed. */ + double score; + uint64_t checksum; + double seconds; + uint64_t iters; + int threads; /* Number of worker threads. */ +}; + +/* Data passed to a worker thread. */ +struct job { + run_fn run; + uint64_t n; + struct workspace *ws; + uint64_t result; +}; + +static void *job_entry(void *arg) +{ + struct job *j = arg; + j->result = j->run(j->n, j->ws); + return NULL; +} + +/* Run a kernel on all requested threads. */ +static uint64_t dispatch(run_fn run, uint64_t n, int threads) +{ + struct job *jobs = xalloc((size_t)threads * sizeof *jobs); + pthread_t *tids = threads > 1 + ? xalloc((size_t)(threads - 1) * sizeof *tids) : NULL; + int i, spawned = 0; + uint64_t agg = 0; + + for (i = 0; i < threads; i++) { + jobs[i].run = run; + jobs[i].n = n; + jobs[i].ws = &g_ws[i]; + } + for (i = 1; i < threads; i++) { + if (pthread_create(&tids[spawned], NULL, job_entry, &jobs[i]) == 0) + spawned++; + else + job_entry(&jobs[i]); /* Run it here if the thread fails. */ + } + + job_entry(&jobs[0]); /* The main thread does the first job. */ + + for (i = 0; i < spawned; i++) + pthread_join(tids[i], NULL); + for (i = 0; i < threads; i++) + agg += jobs[i].result; + + xfree(jobs); + xfree(tids); + return agg; +} + +static struct result run_test(const struct test *t, int threads) +{ + struct result r; + uint64_t n = t->start_n; + double elapsed = 0.0, best = 0.0; + uint64_t checksum = 0; + int i; + + /* Increase the work until it runs long enough. */ + for (;;) { + double t0 = now_seconds(); + checksum = dispatch(t->run, n, threads); + elapsed = now_seconds() - t0; + + if (elapsed >= MIN_SECONDS) + break; + if (elapsed < 0.001) { + n *= 8; /* Increase it a lot when timing is too short. */ + } else { + double scale = (MIN_SECONDS * 1.3) / elapsed; + if (scale < 1.5) + scale = 1.5; + if (scale > 8.0) + scale = 8.0; + n = (uint64_t)((double)n * scale) + 1; + } + } + + /* Keep the fastest run. */ + best = elapsed; + for (i = 1; i < REPEATS; i++) { + double t0 = now_seconds(); + uint64_t c = dispatch(t->run, n, threads); + double e = now_seconds() - t0; + + if (c != checksum) { + fprintf(stderr, + "fossbench: %s is non-deterministic " + "(checksum %llu != %llu)\n", t->name, + (unsigned long long)c, + (unsigned long long)checksum); + exit(2); + } + if (e < best) + best = e; + } + + r.seconds = best; + r.iters = n; + r.checksum = checksum; + r.threads = threads; + /* Calculate the total speed. */ + r.rate = ((double)threads * (double)n * t->work_per_n) / best / 1e6; + /* Turn the speed into a score. */ + r.score = FB_TARGET_SCORE * (r.rate / t->ref_rate); + return r; +} + +/* Pick the value shown in the rate column. */ +static double display_metric(const struct test *t, const struct result *r) +{ + if (t->run == run_chase) { + double hops = (double)r->iters * t->work_per_n; /* Per core. */ + return r->seconds / hops * 1e9; /* Convert it to nanoseconds. */ + } + return r->rate; +} + +#include "upload.c" +/* Print the results. */ + +static void print_header(const struct system_info *info) +{ + printf("\n"); + printf(" fossbench %s - multi-core CPU benchmark\n", FB_VERSION); + printf(" ------------------------------------------------------------------\n"); + printf(" CPU: %s\n", info->cpu); + printf(" model: %s\n", info->model[0] ? info->model : "unknown"); + printf(" cores: %ld physical / %ld threads\n", info->cpu_cores, info->cpu_threads); + printf(" memory: %ld MB\n", info->memory_mb); + printf(" OS: %s (%s)\n", info->operating_system, FB_ARCH); + printf(" kernel: %s\n", info->kernel[0] ? info->kernel : "unknown"); + printf(" compiler: %s\n", info->compiler); + printf("\n"); + printf(" %-24s %12s %-11s %8s %9s\n", + "TEST", "RATE", "UNIT", "TIME", "SCORE"); + printf(" --------------------------------------------------------------------------\n"); + fflush(stdout); +} + +int fossbench_run(int verbose, int upload_mode, int system_check) +{ + struct result multi[NTESTS], single[NTESTS]; + struct system_info system_info; + struct background_metrics background; + double multi_log_sum = 0.0, single_log_sum = 0.0; + double weight_sum = 0.0; + double benchmark_started, multicore_score, singlecore_score; + uint64_t duration_ms; + size_t i; + + { +#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); + memset(&background, 0, sizeof(background)); + background.available_memory_mb = background.process_count = -1; + if (system_check) { + sample_background_metrics(&background, 10); + if (background.available) { + printf(" background CPU: %.1f%% average / %.1f%% peak\n", + background.average_cpu_percent, background.peak_cpu_percent); + if (background.available_memory_mb >= 0) + printf(" available memory: %ld MB of %ld MB\n", background.available_memory_mb, system_info.memory_mb); + if (background.process_count >= 0) printf(" processes: %ld\n", background.process_count); + if (background.average_cpu_percent >= 10.0) + printf(" warning: background CPU activity may reduce benchmark scores\n"); + } + } + benchmark_started = now_seconds(); + + printf("\n preparing workloads..."); + fflush(stdout); + setup(); + printf(" done\n"); + + print_header(&system_info); + + for (i = 0; i < NTESTS; i++) { + double sm, ss; + + printf(" %-24s", tests[i].name); + fflush(stdout); + + /* Run every test with all cores and one core. */ + multi[i] = run_test(&tests[i], (int)g_ncores); + single[i] = run_test(&tests[i], 1); + + printf(" %12.1f %-11s %7.2fs %9.0f\n", + display_metric(&tests[i], &multi[i]), tests[i].unit, + multi[i].seconds, multi[i].score); + if (verbose) + printf(" %-24s %s\n" + " %-24s weight=%.0f%% 1-core: %.1f %s / %.0f %ld-core: %.1f %s / %.0f\n", + "", tests[i].detail, "", tests[i].weight, + display_metric(&tests[i], &single[i]), tests[i].unit, + single[i].score, g_ncores, + display_metric(&tests[i], &multi[i]), tests[i].unit, + multi[i].score); + fflush(stdout); + + /* Add this test to both total scores. */ + sm = multi[i].score > 0.0 ? multi[i].score : 1e-9; + ss = single[i].score > 0.0 ? single[i].score : 1e-9; + multi_log_sum += tests[i].weight * log(sm); + single_log_sum += tests[i].weight * log(ss); + weight_sum += tests[i].weight; + } + + printf(" --------------------------------------------------------------------------\n"); + + /* Add this test to both total scores. */ + multicore_score = exp(multi_log_sum / weight_sum); + singlecore_score = exp(single_log_sum / weight_sum); + duration_ms = (uint64_t)((now_seconds() - benchmark_started) * 1000.0); + printf(" %-24s %44.0f\n", "MULTICORE SCORE", multicore_score); + printf(" %-24s %44.0f\n", "SINGLECORE SCORE", singlecore_score); + printf(" %-24s %41.2fs\n", "TOTAL DURATION", (double)duration_ms / 1000.0); + printf("\n"); + + teardown(); + + { + /* Read the token from the environment. */ + const char *token = getenv("FOSSBENCH_TOKEN"); + int do_upload; + + if (token && token[0] == '\0') + token = NULL; + + if (upload_mode == 1) { + do_upload = 1; + } else if (upload_mode == 2) { + do_upload = 0; + printf(" Result was not uploaded.\n"); + } else { + char answer[16]; + if (token) + printf(" Upload this result to %s using your API token? [y/N] ", FB_API_BASE_URL); + else + printf(" Upload this result to %s? [y/N] ", FB_API_BASE_URL); + fflush(stdout); + do_upload = fgets(answer, sizeof(answer), stdin) && + (answer[0] == 'y' || answer[0] == 'Y'); + if (!do_upload) + printf(" Result was not uploaded.\n"); + } + + if (do_upload) + upload_results(&system_info, multicore_score, singlecore_score, + multi, single, duration_ms, &background, token); + } + return 0; +} diff --git a/src/app/benchmark.h b/src/app/benchmark.h new file mode 100644 index 0000000..aae4df1 --- /dev/null +++ b/src/app/benchmark.h @@ -0,0 +1,6 @@ +#ifndef FOSSBENCH_BENCHMARK_H +#define FOSSBENCH_BENCHMARK_H + +int fossbench_run(int verbose, int upload_mode, int system_check); + +#endif diff --git a/src/app/upload.c b/src/app/upload.c new file mode 100644 index 0000000..4b97d01 --- /dev/null +++ b/src/app/upload.c @@ -0,0 +1,316 @@ +/* Upload results if the user wants to. */ + +static void json_escape(const char *src, char *dst, size_t cap) +{ + size_t used = 0; + while (*src && used + 1 < cap) { + unsigned char c = (unsigned char)*src++; + const char *esc = NULL; + if (c == '\"') esc = "\\\""; + else if (c == '\\') esc = "\\\\"; + else if (c == '\n') esc = "\\n"; + else if (c == '\r') esc = "\\r"; + else if (c == '\t') esc = "\\t"; + if (esc) { + size_t n = strlen(esc); if (used + n >= cap) break; + memcpy(dst + used, esc, n); used += n; + } else if (c >= 0x20) dst[used++] = (char)c; + } + dst[used] = '\0'; +} + +#if !defined(_WIN32) +/* Load the certificates included with the program. */ +static int load_embedded_ca_bundle(SSL_CTX *ctx) +{ + X509_STORE *store = SSL_CTX_get_cert_store(ctx); + BIO *bio = BIO_new_mem_buf(fb_ca_bundle_pem, -1); + X509 *cert; + int loaded = 0; + + if (!bio) return 0; + while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) { + if (X509_STORE_add_cert(store, cert)) loaded++; + X509_free(cert); + } + BIO_free(bio); + ERR_clear_error(); /* Reaching the end sets an error, which is fine. */ + return loaded > 0; +} +#endif + +#if defined(_WIN32) +/* Check if WinHTTP found a certificate problem. */ +static int is_winhttp_secure_error(DWORD err) +{ + switch (err) { + case ERROR_WINHTTP_SECURE_CERT_DATE_INVALID: + case ERROR_WINHTTP_SECURE_CERT_CN_INVALID: + case ERROR_WINHTTP_SECURE_INVALID_CA: + case ERROR_WINHTTP_SECURE_CERT_REV_FAILED: + case ERROR_WINHTTP_SECURE_CHANNEL_ERROR: + case ERROR_WINHTTP_SECURE_INVALID_CERT: + case ERROR_WINHTTP_SECURE_CERT_REVOKED: + case ERROR_WINHTTP_SECURE_FAILURE: + case ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE: + case ERROR_WINHTTP_SECURE_FAILURE_PROXY: + return 1; + default: + return 0; + } +} + +/* Send the request with Windows networking. */ +static int winhttp_post(const char *host, const char *port, const char *path, + int use_tls, const char *payload, int payload_len, + const char *auth_header, int *out_status) +{ + wchar_t whost[256], wpath[512], wheaders[700]; + char header_buf[700]; + HINTERNET hsession = NULL, hconnect = NULL, hrequest = NULL; + INTERNET_PORT wport = (INTERNET_PORT)atoi(port); + DWORD status = 0, status_size = sizeof(status); + int ok = 0; + + if (MultiByteToWideChar(CP_UTF8, 0, host, -1, whost, sizeof whost / sizeof whost[0]) == 0 || + MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof wpath / sizeof wpath[0]) == 0) + return 0; + snprintf(header_buf, sizeof(header_buf), "Content-Type: application/json\r\n%s", auth_header); + if (MultiByteToWideChar(CP_UTF8, 0, header_buf, -1, wheaders, sizeof wheaders / sizeof wheaders[0]) == 0) + return 0; + + hsession = WinHttpOpen(L"fossbench", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (!hsession) { + fprintf(stderr, " upload error: cannot initialize WinHTTP\n"); + return 0; + } + hconnect = WinHttpConnect(hsession, whost, wport, 0); + if (!hconnect) { + fprintf(stderr, " upload error: cannot connect to %s:%s\n", host, port); + goto done; + } + hrequest = WinHttpOpenRequest(hconnect, L"POST", wpath, NULL, WINHTTP_NO_REFERER, + WINHTTP_DEFAULT_ACCEPT_TYPES, + use_tls ? WINHTTP_FLAG_SECURE : 0); + if (!hrequest) { + fprintf(stderr, " upload error: cannot create HTTP request\n"); + goto done; + } + if (!WinHttpSendRequest(hrequest, wheaders, (DWORD)-1L, (LPVOID)payload, + (DWORD)payload_len, (DWORD)payload_len, 0)) { + if (is_winhttp_secure_error(GetLastError())) + fprintf(stderr, " upload error: TLS connection or certificate verification failed\n"); + else + fprintf(stderr, " upload error: send failed\n"); + goto done; + } + if (!WinHttpReceiveResponse(hrequest, NULL)) { + if (is_winhttp_secure_error(GetLastError())) + fprintf(stderr, " upload error: TLS connection or certificate verification failed\n"); + else + fprintf(stderr, " upload error: no server response\n"); + goto done; + } + if (!WinHttpQueryHeaders(hrequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, + WINHTTP_HEADER_NAME_BY_INDEX, &status, &status_size, + WINHTTP_NO_HEADER_INDEX)) { + fprintf(stderr, " upload error: no server response\n"); + goto done; + } + *out_status = (int)status; + ok = 1; +done: + if (hrequest) WinHttpCloseHandle(hrequest); + if (hconnect) WinHttpCloseHandle(hconnect); + WinHttpCloseHandle(hsession); + return ok; +} +#endif + +static int upload_results(const struct system_info *info, double score, + double singlecore_score, const struct result *multi, + const struct result *single, uint64_t duration_ms, + const struct background_metrics *background, const char *token) +{ + char host[256], port[16], path[512], payload[16384]; + char auth_header[600]; + char cpu[512], model[512], os[512], compiler[256], kernel[256]; + const char *base = FB_API_BASE_URL, *p, *slash, *colon; + int use_tls, status = 0, payload_len; +#if !defined(_WIN32) + char request[20000], response[512]; + struct addrinfo hints, *addresses = NULL, *a; + SSL_CTX *tls_ctx = NULL; + SSL *tls = NULL; + int fd = -1, request_len; +#endif + + if (!strncmp(base, "https://", 8)) { + use_tls = 1; p = base + 8; strcpy(port, "443"); + } else if (!strncmp(base, "http://", 7)) { + use_tls = 0; p = base + 7; strcpy(port, "80"); + } else { + fprintf(stderr, " upload error: unsupported URL scheme\n"); + return 0; + } + slash = strchr(p, '/'); + if (!slash) slash = p + strlen(p); + colon = memchr(p, ':', (size_t)(slash - p)); + if (colon) { + size_t hn = (size_t)(colon - p), pn = (size_t)(slash - colon - 1); + if (hn >= sizeof(host) || pn == 0 || pn >= sizeof(port)) return 0; + memcpy(host, p, hn); host[hn] = '\0'; memcpy(port, colon + 1, pn); port[pn] = '\0'; + } else { + size_t hn = (size_t)(slash - p); if (hn >= sizeof(host)) return 0; + memcpy(host, p, hn); host[hn] = '\0'; + } + { + int base_path_len = (int)strlen(slash); + while (base_path_len > 0 && slash[base_path_len - 1] == '/') base_path_len--; + snprintf(path, sizeof(path), "%.*s/api/v1/submissions", base_path_len, slash); + } + + json_escape(info->cpu, cpu, sizeof(cpu)); + json_escape(info->model, model, sizeof(model)); + json_escape(info->operating_system, os, sizeof(os)); + json_escape(info->compiler, compiler, sizeof(compiler)); + json_escape(info->kernel, kernel, sizeof(kernel)); + /* The server still calls this field fossmark_version. */ + payload_len = snprintf(payload, sizeof(payload), + "{\"cpu\":\"%s\",\"model\":\"%s\",\"cpu_cores\":%ld,\"cpu_threads\":%ld," + "\"memory_mb\":%ld,\"operating_system\":\"%s\",\"compiler\":\"%s\"," + "\"fossmark_version\":\"%s\",\"score\":%.17g,\"duration_ms\":%llu," + "\"score_details\":{\"scoring_method\":\"weighted_geometric_mean\"," + "\"target_score\":%.17g,\"multicore_score\":%.17g,\"singlecore_score\":%.17g," + "\"minimum_test_seconds\":%.17g,\"repeats\":%d," + "\"system_environment\":{\"kernel\":\"%s\",\"sample_seconds\":%d," + "\"background_cpu_average_percent\":%.17g,\"background_cpu_peak_percent\":%.17g," + "\"available_memory_mb\":%ld,\"process_count\":%ld},\"tests\":[", + cpu, model, info->cpu_cores, info->cpu_threads, info->memory_mb, os, compiler, + FB_VERSION, score, (unsigned long long)duration_ms, FB_TARGET_SCORE, score, + singlecore_score, MIN_SECONDS, REPEATS, kernel, background->samples, + background->average_cpu_percent, background->peak_cpu_percent, + background->available_memory_mb, background->process_count); + if (payload_len < 0 || (size_t)payload_len >= sizeof(payload)) return 0; + { + size_t used = (size_t)payload_len; + size_t i; + for (i = 0; i < NTESTS; i++) { + int n = snprintf(payload + used, sizeof(payload) - used, + "%s{\"name\":\"%s\",\"detail\":\"%s\",\"unit\":\"%s\"," + "\"start_iterations\":%llu,\"work_per_iteration\":%.17g," + "\"reference_rate\":%.17g,\"weight\":%.17g," + "\"multicore\":{\"display_metric\":%.17g,\"rate\":%.17g,\"score\":%.17g," + "\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"}," + "\"singlecore\":{\"display_metric\":%.17g,\"rate\":%.17g,\"score\":%.17g," + "\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"}}", + i ? "," : "", tests[i].name, tests[i].detail, tests[i].unit, + (unsigned long long)tests[i].start_n, tests[i].work_per_n, + tests[i].ref_rate, tests[i].weight, + display_metric(&tests[i], &multi[i]), multi[i].rate, multi[i].score, + multi[i].seconds, (unsigned long long)multi[i].iters, multi[i].threads, + (unsigned long long)multi[i].checksum, + display_metric(&tests[i], &single[i]), single[i].rate, single[i].score, + single[i].seconds, (unsigned long long)single[i].iters, single[i].threads, + (unsigned long long)single[i].checksum); + if (n < 0 || (size_t)n >= sizeof(payload) - used) return 0; + used += (size_t)n; + } + if (used + 3 >= sizeof(payload)) return 0; + memcpy(payload + used, "]}}", 4); + payload_len = (int)(used + 3); + } + + auth_header[0] = '\0'; + if (token && token[0]) { + int n = snprintf(auth_header, sizeof(auth_header), + "Authorization: Bearer %s\r\n", token); + if (n < 0 || (size_t)n >= sizeof(auth_header)) { + fprintf(stderr, " upload error: API token too long\n"); + return 0; + } + } +#if !defined(_WIN32) + request_len = snprintf(request, sizeof(request), + "POST %s HTTP/1.1\r\nHost: %s:%s\r\nContent-Type: application/json\r\n" + "Content-Length: %d\r\nConnection: close\r\n%s\r\n%s", + path, host, port, payload_len, auth_header, payload); + if (request_len < 0 || (size_t)request_len >= sizeof(request)) return 0; + + memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; + if (getaddrinfo(host, port, &hints, &addresses) != 0) { fprintf(stderr, " upload error: cannot resolve %s\n", host); return 0; } + for (a = addresses; a; a = a->ai_next) { + fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol); + if (fd >= 0 && connect(fd, a->ai_addr, a->ai_addrlen) == 0) break; + if (fd >= 0) close(fd); + fd = -1; + } + freeaddrinfo(addresses); + if (fd < 0) { fprintf(stderr, " upload error: cannot connect to %s:%s\n", host, port); return 0; } + if (use_tls) { + tls_ctx = SSL_CTX_new(TLS_client_method()); + if (!tls_ctx) { + fprintf(stderr, " upload error: cannot initialize TLS trust store\n"); + goto upload_failed; + } + SSL_CTX_set_default_verify_paths(tls_ctx); /* Try system certificates too. */ + if (!load_embedded_ca_bundle(tls_ctx)) { + fprintf(stderr, " upload error: cannot initialize TLS trust store\n"); + goto upload_failed; + } + SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_PEER, NULL); + tls = SSL_new(tls_ctx); + if (!tls || !SSL_set_tlsext_host_name(tls, host) || + !SSL_set1_host(tls, host) || !SSL_set_fd(tls, fd) || + SSL_connect(tls) != 1) { + fprintf(stderr, " upload error: TLS connection or certificate verification failed\n"); + goto upload_failed; + } + } + { + size_t sent = 0; + while (sent < (size_t)request_len) { + int n = use_tls ? SSL_write(tls, request + sent, (int)((size_t)request_len - sent)) : + (int)send(fd, request + sent, (size_t)request_len - sent, 0); + if (n <= 0) { fprintf(stderr, " upload error: send failed\n"); goto upload_failed; } + sent += (size_t)n; + } + } + { + int n = use_tls ? SSL_read(tls, response, sizeof(response) - 1) : + (int)recv(fd, response, sizeof(response) - 1, 0); + if (n <= 0) { fprintf(stderr, " upload error: no server response\n"); goto upload_failed; } + response[n] = '\0'; + if (sscanf(response, "HTTP/%*s %d", &status) != 1) status = 0; + } + if (tls) { SSL_shutdown(tls); SSL_free(tls); } + if (tls_ctx) SSL_CTX_free(tls_ctx); + close(fd); +#else + if (!winhttp_post(host, port, path, use_tls, payload, payload_len, auth_header, &status)) + return 0; +#endif + if (status == 401) { + fprintf(stderr, " upload failed: API token was rejected (HTTP 401)\n"); + return 0; + } + if (status == 422) { + fprintf(stderr, " upload failed: server rejected the submission as invalid (HTTP 422)\n"); + return 0; + } + if (status < 200 || status >= 300) { fprintf(stderr, " upload failed: server returned HTTP %d\n", status); return 0; } + if (token) + printf(" Results uploaded and published to your profile (HTTP %d).\n", status); + else + printf(" Results uploaded, pending administrator review (HTTP %d).\n", status); + return 1; + +#if !defined(_WIN32) +upload_failed: + if (tls) SSL_free(tls); + if (tls_ctx) SSL_CTX_free(tls_ctx); + if (fd >= 0) close(fd); + return 0; +#endif +} diff --git a/src/ca_bundle.h b/src/ca_bundle.h index 687fcc6..087861b 100644 --- a/src/ca_bundle.h +++ b/src/ca_bundle.h @@ -1,26 +1,5 @@ -/* - * Embedded CA trust bundle (fallback for upload_results() TLS). - * - * Statically linking against a machine's OpenSSL is not enough to verify - * TLS certificates on an arbitrary target machine: SSL_CTX_set_default_verify_paths() - * only works if OpenSSL's compiled-in default CA directory/file happens to exist on - * the machine running the binary - a path baked in wherever OpenSSL itself was built, - * not the release binary. That almost never matches the end user's machine (macOS has - * no such path at all outside Homebrew; Linux distros disagree on the location), so - * uploads failed with a TLS/certificate error on nearly every machine except the one - * that built the release binaries. - * - * This bundle is carried as a fallback trust source so upload_results() can verify - * fossbench.net's certificate chain unconditionally, without relying on the host having - * a usable system trust store. It is tried in addition to (not instead of) the system's - * own default verify paths, so locally-trusted/corporate CAs still work where present. - * - * Contents: the Mozilla CA root program's included/trusted certificate set, as shipped - * by the ca-certificates-mozilla distro package (/etc/ssl/certs/ca-certificates.crt), - * the same root program curl/Go/Python(certifi) bundle for the identical reason. - * Regenerate by re-running the script that produced this file against a current - * ca-certificates package; do this roughly yearly as roots rotate. - */ +/* Backup certificates for uploads. + * These come from Mozilla's CA list. */ #ifndef FB_CA_BUNDLE_H #define FB_CA_BUNDLE_H diff --git a/src/fossbench_x86_64.S b/src/kernels/fossbench-amd64.S similarity index 99% rename from src/fossbench_x86_64.S rename to src/kernels/fossbench-amd64.S index 341c901..6df9339 100644 --- a/src/fossbench_x86_64.S +++ b/src/kernels/fossbench-amd64.S @@ -1,7 +1,7 @@ /* - * fossbench_x86_64.S - x86-64 (AMD64) CPU benchmark kernels + * fossbench-amd64.S - x86-64 (AMD64) CPU benchmark kernels * - * The AMD64 counterpart to fossbench.S. Same nine routines, same contract: each + * The AMD64 counterpart to the ARM64 backend. Same nine routines and contract: each * is a pure function of its arguments under the System V AMD64 ABI, contains no * syscalls, no libc calls and no external data relocations, so it assembles and * runs on Linux (ELF), macOS (Mach-O) and the BSDs. The portable C driver in diff --git a/src/fossbench.S b/src/kernels/fossbench-arm64.S similarity index 99% rename from src/fossbench.S rename to src/kernels/fossbench-arm64.S index 4cacb96..86a2660 100644 --- a/src/fossbench.S +++ b/src/kernels/fossbench-arm64.S @@ -1,5 +1,5 @@ /* - * fossbench.S - AArch64 CPU benchmark kernels + * fossbench-arm64.S - AArch64 CPU benchmark kernels * * OS-independent: contains no syscalls, no libc calls, no relocations against * external data. Every routine is a pure function of its arguments under the diff --git a/src/fossbench_i386.S b/src/kernels/fossbench-i386.S similarity index 99% rename from src/fossbench_i386.S rename to src/kernels/fossbench-i386.S index 0e4225a..f245b69 100644 --- a/src/fossbench_i386.S +++ b/src/kernels/fossbench-i386.S @@ -1,7 +1,7 @@ /* - * fossbench_i386.S - x86 32-bit (i386) CPU benchmark kernels + * fossbench-i386.S - x86 32-bit (i386) CPU benchmark kernels * - * The i386 counterpart to fossbench_x86_64.S. Same nine routines, same + * The i386 counterpart to the AMD64 backend. Same nine routines, same * contract: each is a pure function of its arguments, contains no syscalls, * no libc calls and no external data relocations, so it assembles and runs * unmodified under the plain i386 SysV (cdecl) ABI on Linux. diff --git a/src/fossbench_ppc32.c b/src/kernels/fossbench-powerpc.c similarity index 90% rename from src/fossbench_ppc32.c rename to src/kernels/fossbench-powerpc.c index aa3af65..e5f2c12 100644 --- a/src/fossbench_ppc32.c +++ b/src/kernels/fossbench-powerpc.c @@ -1,10 +1,4 @@ -/* - * Portable kernel backend for big-endian PowerPC. - * - * Keeping this backend in C lets the compiler implement 64-bit arguments and - * returns according to the platform ABI. All byte-oriented formats - * are decoded explicitly, so the code is correct on big-endian systems. - */ +/* PowerPC kernel code. */ #include #include #include @@ -80,9 +74,7 @@ static uint64_t fb_simd_scalar(uint64_t iters, void *memory) #endif #if defined(__powerpc64__) -/* The PowerPC 970 in every iMac G5 implements AltiVec. Using GCC's vector - * type here lets the compiler handle whichever PPC64 ELF ABI the system uses; - * both PPC64 ABIs differ from the PPC32 assembly convention below. */ +/* The iMac G5 has AltiVec. */ typedef uint32_t fb_vec_u32 __attribute__((vector_size(16))); uint64_t fb_simd(uint64_t iters, void *memory) @@ -110,8 +102,7 @@ uint64_t fb_simd(uint64_t iters, void *memory) return sum; } #else -/* These are kept in fossbench_ppc32_ext.S so this translation unit, and thus - * the executable's default code path, only requires baseline PPC32. */ +/* The optional PowerPC code is in the assembly file. */ extern void fb_simd_ps_kernel(uint64_t iters, void *memory); extern void fb_simd_vsx_kernel(uint64_t iters, void *memory); extern void fb_simd_altivec_kernel(uint64_t iters, void *memory); @@ -147,8 +138,7 @@ static int device_is_nintendo(void) static fb_simd_kernel detect_simd_kernel(void) { - /* Linux exposes these in AT_HWCAP on both 32- and 64-bit PowerPC. - * Spell out the ABI values instead of depending on kernel-only headers. */ + /* Linux tells us which PowerPC features are available. */ #if defined(__linux__) && defined(AT_HWCAP) const unsigned long hwcap = getauxval(AT_HWCAP); const unsigned long has_altivec = 0x10000000UL; diff --git a/src/fossbench_ppc32_ext.S b/src/kernels/fossbench-ppc32-ext.S similarity index 95% rename from src/fossbench_ppc32_ext.S rename to src/kernels/fossbench-ppc32-ext.S index 1b4aaf9..7e84b68 100644 --- a/src/fossbench_ppc32_ext.S +++ b/src/kernels/fossbench-ppc32-ext.S @@ -1,5 +1,5 @@ /* Optional PPC32 extended-instruction kernels. No instruction in this file - * is reached until fossbench_ppc32.c has checked the device tree or AT_HWCAP. + * is reached until fossbench-powerpc.c has checked the device tree or AT_HWCAP. * Arguments use the PPC32 ABI: iters in r3:r4 and memory in r5. */ .text diff --git a/src/main.c b/src/main.c index e3ab810..f2558d2 100644 --- a/src/main.c +++ b/src/main.c @@ -1,1347 +1,17 @@ -/* - * fossbench - a multi-core AArch64 CPU benchmark - * - * This file is the portable driver: it owns everything the assembly kernels - * deliberately do not (timing, memory, I/O, scoring). The kernels in - * fossbench.S are pure computation and identical on every OS; only this file - * knows what an operating system is. - * - * Every workload is run twice: once on a single core, and once on all available - * cores at once - one identical copy of the kernel per core, each with its own - * private buffers, so the machine is driven to 100%% and the rate is whole-machine - * throughput. From these two passes fossbench reports two composite scores, a - * SINGLECORE and a MULTICORE, from the same tests and the same weights. - * - * Build: cc -O2 -pthread main.c fossbench.S -o fossbench -lm - */ - #include -#include #include -#include -#include -#include -#include -#include -#include -#if defined(__linux__) -# include -#endif -#if !defined(_WIN32) -# include -# include -# include -# include -# include -# include -# include "ca_bundle.h" -#endif -#if defined(__APPLE__) -# include -# include -# 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.6" - -/* ---------- platform identification (for the banner only) ---------- */ - -#if defined(_WIN32) -# define FB_OS "Windows" -#elif defined(__APPLE__) -# define FB_OS "macOS" -#elif defined(__linux__) -# define FB_OS "Linux" -#else -# define FB_OS "POSIX" -#endif - -#if defined(__aarch64__) || defined(_M_ARM64) -# define FB_ARCH "ARM64" -# define D_INT "64-bit ALU: madd, umulh, udiv, bitops" -# define D_FP "double: fmadd, fdiv, fsqrt" -# define D_SIMD "NEON ASIMD: 128-bit integer + float" -#elif defined(__x86_64__) || defined(_M_X64) -# define FB_ARCH "x86-64" -# define D_INT "64-bit ALU: imul, mul, div, bitops" -# define D_FP "double: mulsd/addsd, divsd, sqrtsd" -# define D_SIMD "SSE2: 128-bit integer + float" -#elif defined(__i386__) || defined(_M_IX86) -# define FB_ARCH "x86 32-bit" -# define D_INT "Pentium 4 integer ALU and software 64-bit arithmetic" -# define D_FP "x87 scalar double-precision floating point" -# define D_SIMD "SSE2: 128-bit integer vectors" -#elif defined(__powerpc64__) -# define FB_ARCH "PowerPC 64-bit big-endian" -# define D_INT "64-bit PowerPC integer ALU" -# define D_FP "PowerPC scalar double-precision floating point" -# define D_SIMD "AltiVec: 128-bit integer vectors (PowerPC 970)" -#elif defined(__powerpc__) -# define FB_ARCH "PowerPC 32-bit big-endian" -# define D_INT "PPC32 integer ALU and software 64-bit arithmetic" -# define D_FP "PowerPC scalar double-precision floating point" -# define D_SIMD "runtime-selected PS, VSX, AltiVec, or scalar" -#else -# define FB_ARCH "unknown" -# define D_INT "64-bit integer ALU" -# define D_FP "double-precision FP" -# define D_SIMD "128-bit SIMD: integer + float" -#endif - -/* ---------- portable monotonic clock ---------- */ - -#if defined(_WIN32) -# define WIN32_LEAN_AND_MEAN -# include -# include -# include -static double now_seconds(void) -{ - LARGE_INTEGER f, t; - QueryPerformanceFrequency(&f); - QueryPerformanceCounter(&t); - return (double)t.QuadPart / (double)f.QuadPart; -} -#elif defined(__APPLE__) -static double now_seconds(void) -{ - static mach_timebase_info_data_t timebase; - uint64_t ticks; - - if (timebase.denom == 0) - mach_timebase_info(&timebase); - ticks = mach_absolute_time(); - return (double)ticks * (double)timebase.numer / - (double)timebase.denom * 1e-9; -} -#else -# include -static double now_seconds(void) -{ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; -} -#endif - -/* ---------- the assembly kernels ---------- */ - -extern uint64_t fb_int_math(uint64_t iters); -extern uint64_t fb_fp_math(uint64_t iters); -extern uint64_t fb_primes(uint64_t limit, uint8_t *sieve); -extern uint64_t fb_simd(uint64_t iters, void *buf); -extern uint64_t fb_compress(const uint8_t *src, uint64_t len, uint32_t *ht); -extern uint64_t fb_chacha20(uint8_t *buf, uint64_t len, - const uint8_t key[32], uint64_t rounds); -extern uint64_t fb_physics(double *bodies, uint64_t n, uint64_t steps); -extern uint64_t fb_sort(uint32_t *a, uint64_t n); -extern uint64_t fb_chase(void **ptrs, uint64_t steps); - -/* ---------- tuning ---------- */ - -#define PRIME_LIMIT (2u * 1000u * 1000u) /* sieve span */ -#define COMPRESS_LEN (4u * 1024u * 1024u) /* corpus size */ -#define HT_ENTRIES (1u << 16) /* LZ77 hash buckets */ -#define CIPHER_LEN (1u * 1024u * 1024u) /* plaintext size */ -#define SIMD_BUF 256 /* NEON scratch */ -#define NBODY_N 512 /* bodies */ -#define SORT_N (1u << 20) /* elements to sort */ -/* PPC32 Wii Linux systems have less than 32 MiB available to a process. - * A 2 MiB chase remains well beyond the 750CL's 256 KiB L2 while keeping the - * complete benchmark (including setup's temporary permutation) below 32 MiB. */ -#if defined(__powerpc__) && !defined(__powerpc64__) -# define CHASE_NODES (1u << 19) /* 2 MiB with 32-bit pointers */ -# define CHASE_DETAIL "dependent-load pointer chase, 2 MiB" -#elif UINTPTR_MAX == UINT32_MAX -# define CHASE_NODES (1u << 21) /* 8 MiB with 32-bit pointers */ -# define CHASE_DETAIL "dependent-load pointer chase, 8 MiB" -#else -# define CHASE_NODES (1u << 21) /* 16 MiB cycle, > any L2 */ -# define CHASE_DETAIL "dependent-load pointer chase, 16 MiB" -#endif - -#define MIN_SECONDS 2.0 /* per-test measured floor */ -#define REPEATS 3 /* best-of, to reject noise */ - -/* ---------- scoring configuration ---------- - * - * The overall score is a WEIGHTED geometric mean of each test's rate expressed - * relative to a reference machine. Two knobs per test: - * - * FB_REF_* the reference rate (this machine's measured rate). A machine - * matching the reference scores FB_TARGET_SCORE on that test. - * FB_WEIGHT_* how much that test counts toward the overall, by its - * influence on everyday user experience. Weights are relative: - * only their ratios matter, so they need not sum to anything - - * the code normalises by their sum. (They happen to sum to 100 - * here, so each reads as a percent.) - * - * Per-test score: S_i = FB_TARGET_SCORE * (rate_i / FB_REF_i) - * Overall score: Overall = FB_TARGET_SCORE * - * exp( Sum(w_i * ln(rate_i/FB_REF_i)) / Sum(w_i) ) - * - * On the reference machine every ratio is 1, so every S_i and the overall come - * out to exactly FB_TARGET_SCORE, regardless of the weights. Scaling is linear - * in performance, so far slower machines fall well below (half as fast -> half - * the score) and faster future machines rise above. - */ - -#define FB_TARGET_SCORE 10000.0 /* reference-machine overall */ - -/* Reference rates: this machine, in each test's native unit (see tests[]). */ -#define FB_REF_INT 3086.0 /* Mops/s */ -#define FB_REF_FP 1682.0 /* Mops/s */ -#define FB_REF_PRIMES 812.0 /* Mcand/s */ -#define FB_REF_SIMD 6576.0 /* Mops/s */ -#define FB_REF_COMPRESS 674.0 /* MB/s */ -#define FB_REF_CRYPTO 406.0 /* MB/s */ -#define FB_REF_PHYSICS 631.0 /* Mpair/s */ -#define FB_REF_SORT 363.0 /* Mkey-cmp/s*/ -#define FB_REF_CHASE 79.0 /* Mhop/s (scoring); shown as ns/access */ - -/* Weights: influence on day-to-day, common-workload user experience. - * Rationale: integer/general-purpose code and memory-latency-bound - * responsiveness dominate everyday use; specialised FP/physics matter least. - * Roughly an 80/20 integer-vs-FP split, in the spirit of Geekbench 6's - * weighted, integer-dominant methodology. Retune freely. */ -#define FB_WEIGHT_INT 20.0 /* general-purpose ALU: everything */ -#define FB_WEIGHT_CHASE 16.0 /* memory latency: responsiveness */ -#define FB_WEIGHT_COMPRESS 14.0 /* web, storage, RAM compression */ -#define FB_WEIGHT_SORT 12.0 /* general data-structure work */ -#define FB_WEIGHT_SIMD 11.0 /* codecs, mem/string ops, parsing */ -#define FB_WEIGHT_FP 9.0 /* spreadsheets, app/media math */ -#define FB_WEIGHT_CRYPTO 8.0 /* TLS, disk encryption (small frac) */ -#define FB_WEIGHT_PRIMES 6.0 /* synthetic ALU+memory proxy */ -#define FB_WEIGHT_PHYSICS 4.0 /* niche simulation/games */ - -/* ---------- deterministic PRNG (splitmix64) ---------- */ - -static uint64_t rng_state = 0x853c49e6748fea9bULL; - -static uint64_t rng_next(void) -{ - uint64_t z = (rng_state += 0x9e3779b97f4a7c15ULL); - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL; - z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL; - return z ^ (z >> 31); -} - -static void rng_reset(void) { rng_state = 0x853c49e6748fea9bULL; } - -/* ---------- aligned allocation ---------- */ - -static void *xalloc(size_t n) -{ - void *p = NULL; -#if defined(_WIN32) - p = _aligned_malloc(n, 64); -#else - if (posix_memalign(&p, 64, n) != 0) - p = NULL; -#endif - if (!p) { - fprintf(stderr, "fossbench: out of memory (%zu bytes)\n", n); - exit(1); - } - return p; -} - -static void xfree(void *p) -{ -#if defined(_WIN32) - _aligned_free(p); -#else - free(p); -#endif -} - -struct background_metrics { - double average_cpu_percent, peak_cpu_percent; - long available_memory_mb, process_count; - int samples, available; -}; - -struct cpu_snapshot { uint64_t total, idle; }; - -static int take_cpu_snapshot(struct cpu_snapshot *s) -{ -#if defined(__linux__) - FILE *f = fopen("/proc/stat", "r"); - unsigned long long user=0, nice=0, system=0, idle=0, wait=0, irq=0, softirq=0, steal=0; - int n; - if (!f) return 0; - n = fscanf(f, "cpu %llu %llu %llu %llu %llu %llu %llu %llu", - &user, &nice, &system, &idle, &wait, &irq, &softirq, &steal); - fclose(f); if (n < 4) return 0; - s->idle = idle + wait; - s->total = user + nice + system + idle + wait + irq + softirq + steal; - return 1; -#elif defined(__APPLE__) - host_cpu_load_info_data_t cpu; mach_msg_type_number_t count = HOST_CPU_LOAD_INFO_COUNT; - if (host_statistics(mach_host_self(), HOST_CPU_LOAD_INFO, (host_info_t)&cpu, &count) != KERN_SUCCESS) return 0; - s->idle = cpu.cpu_ticks[CPU_STATE_IDLE]; - s->total = cpu.cpu_ticks[CPU_STATE_USER] + cpu.cpu_ticks[CPU_STATE_SYSTEM] + s->idle + cpu.cpu_ticks[CPU_STATE_NICE]; - return 1; -#elif defined(_WIN32) - FILETIME idle, kernel, user; ULARGE_INTEGER i, k, u; - if (!GetSystemTimes(&idle, &kernel, &user)) return 0; - i.LowPart=idle.dwLowDateTime; i.HighPart=idle.dwHighDateTime; - k.LowPart=kernel.dwLowDateTime; k.HighPart=kernel.dwHighDateTime; - u.LowPart=user.dwLowDateTime; u.HighPart=user.dwHighDateTime; - s->idle=i.QuadPart; s->total=k.QuadPart+u.QuadPart; return 1; -#else - (void)s; return 0; -#endif -} - -static void take_resource_snapshot(long *available_mb, long *processes) -{ - *available_mb = -1; *processes = -1; -#if defined(__linux__) - { - FILE *f=fopen("/proc/meminfo","r"); char line[256]; long kb; - if (f) { while (fgets(line,sizeof(line),f)) if (sscanf(line,"MemAvailable: %ld kB",&kb)==1) { *available_mb=kb/1024; break; } fclose(f); } - } - { - DIR *dir=opendir("/proc"); struct dirent *entry; long count=0; - if (dir) { while ((entry=readdir(dir)) != NULL) { const char *p=entry->d_name; if (!*p) continue; while (*p && isdigit((unsigned char)*p)) p++; if (!*p) count++; } closedir(dir); *processes=count; } - } -#elif defined(__APPLE__) - { - vm_statistics_data_t vm; mach_msg_type_number_t count=HOST_VM_INFO_COUNT; vm_size_t page; - if (host_page_size(mach_host_self(),&page)==KERN_SUCCESS && host_statistics(mach_host_self(),HOST_VM_INFO,(host_info_t)&vm,&count)==KERN_SUCCESS) - *available_mb=(long)(((uint64_t)vm.free_count+vm.inactive_count)*page/1024/1024); - } - { - int mib[4]={CTL_KERN,KERN_PROC,KERN_PROC_ALL,0}; size_t bytes=0; - if (sysctl(mib,4,NULL,&bytes,NULL,0)==0) *processes=(long)(bytes/sizeof(struct kinfo_proc)); - } -#elif defined(_WIN32) - { - MEMORYSTATUSEX ms; ms.dwLength=sizeof(ms); if (GlobalMemoryStatusEx(&ms)) *available_mb=(long)(ms.ullAvailPhys/1024/1024); - } - { - HANDLE snap=CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS,0); PROCESSENTRY32 entry; long count=0; entry.dwSize=sizeof(entry); - if (snap!=INVALID_HANDLE_VALUE) { if (Process32First(snap,&entry)) do { count++; } while (Process32Next(snap,&entry)); CloseHandle(snap); *processes=count; } - } -#endif -} - -static void sample_background_metrics(struct background_metrics *m, int seconds) -{ - struct cpu_snapshot before, after; int i; - memset(m,0,sizeof(*m)); m->available_memory_mb=-1; m->process_count=-1; - printf("\n checking background system activity for %d seconds",seconds); fflush(stdout); - if (!take_cpu_snapshot(&before)) { printf("... unavailable\n"); return; } - for (i=0;ibefore.total) { - uint64_t total=after.total-before.total, idle=after.idle-before.idle; - double busy=100.0*(double)(total > idle ? total-idle : 0)/(double)total; - m->average_cpu_percent+=busy; if (busy>m->peak_cpu_percent) m->peak_cpu_percent=busy; - m->samples++; before=after; - } - printf("."); fflush(stdout); - } - take_resource_snapshot(&m->available_memory_mb,&m->process_count); - if (m->samples) { m->average_cpu_percent/=m->samples; m->available=1; } - printf(" done\n"); -} - -/* ---------- workload state ---------- - * - * Because every core runs the same kernel simultaneously, each core needs its - * OWN mutable buffers - sharing them would be a data race and would corrupt - * both the results and the determinism check. Per-core scratch lives in a - * `workspace`, one per thread. Read-only inputs (the corpus, the pristine - * physics/sort seeds, the key, the chase graph) are genuinely shared. - */ -struct workspace { - uint8_t *sieve; /* prime sieve scratch */ - uint32_t *ht; /* LZ77 hash table scratch */ - uint8_t *cipher_buf; /* ChaCha20 buffer, encrypted in place */ - uint8_t *simd_buf; /* NEON scratch */ - double *bodies; /* n-body integration buffer */ - uint32_t *sort_work; /* the buffer we actually sort */ - void **chase; /* private 16 MiB pointer-chase cycle */ -}; - -static long g_ncores = 1; /* active online cores */ -static struct workspace *g_ws; /* g_ncores per-thread workspaces */ - -static uint8_t *g_corpus; /* shared, read-only compression input */ -static uint8_t g_key[32]; /* shared, read-only cipher key */ -static uint8_t *g_cipher_src; /* pristine plaintext, copied per-core */ -static uint8_t *g_simd_src; /* pristine NEON seed, copied per-core */ -static double *g_bodies_src; /* pristine initial conditions */ -static uint32_t *g_sort_src; /* pristine unsorted data */ - -struct system_info { - char cpu[256]; - char model[256]; - char operating_system[256]; - char compiler[128]; - char kernel[128]; - long cpu_cores; - long cpu_threads; - long memory_mb; -}; - -static void trim(char *s) -{ - char *p = s; - size_t n; - while (isspace((unsigned char)*p)) p++; - if (p != s) memmove(s, p, strlen(p) + 1); - n = strlen(s); - while (n && isspace((unsigned char)s[n - 1])) s[--n] = '\0'; -} - -/* Device-tree strings may be NUL-separated lists. The first entry is the - * most-specific compatible identifier, which is the useful model number. */ -static int read_first_property(const char *path, char *dst, size_t cap) -{ - FILE *f; - size_t n, i; - - if (cap == 0) - return 0; - f = fopen(path, "rb"); - if (f == NULL) - return 0; - n = fread(dst, 1, cap - 1, f); - fclose(f); - for (i = 0; i < n && dst[i] != '\0' && dst[i] != '\n' && dst[i] != '\r'; i++) - if ((unsigned char)dst[i] < 0x20) - dst[i] = ' '; - dst[i] = '\0'; - trim(dst); - return dst[0] != '\0'; -} - -static void detect_system_info(struct system_info *info) -{ -#if defined(__linux__) - char cpuinfo_hardware[sizeof info->model] = ""; -#endif - memset(info, 0, sizeof(*info)); - info->cpu_threads = g_ncores; - info->cpu_cores = g_ncores; - strncpy(info->cpu, FB_ARCH, sizeof(info->cpu) - 1); - strncpy(info->operating_system, FB_OS, sizeof(info->operating_system) - 1); -#if defined(__clang__) - snprintf(info->compiler, sizeof(info->compiler), "Clang %s", __clang_version__); -#elif defined(__GNUC__) - snprintf(info->compiler, sizeof(info->compiler), "GCC %s", __VERSION__); -#elif defined(_MSC_VER) - snprintf(info->compiler, sizeof(info->compiler), "MSVC %d", _MSC_VER); -#else - strncpy(info->compiler, "Unknown", sizeof(info->compiler) - 1); -#endif - -#if defined(__linux__) - { - struct utsname u; - if (uname(&u) == 0) - snprintf(info->kernel, sizeof(info->kernel), "%s %s", u.sysname, u.release); - } - { - static const char *const model_paths[] = { - "/sys/firmware/devicetree/base/compatible", - "/sys/firmware/devicetree/base/model", - "/proc/device-tree/compatible" - }; - unsigned i; - for (i = 0; i < sizeof model_paths / sizeof model_paths[0]; i++) - if (read_first_property(model_paths[i], info->model, - sizeof info->model)) - break; - } - { - FILE *f = fopen("/proc/cpuinfo", "r"); - char line[512]; - int pairs[1024][2], npairs = 0, physical = -1, core = -1; - if (f) { - while (fgets(line, sizeof(line), f)) { - char *colon = strchr(line, ':'); - if (!colon) continue; - *colon++ = '\0'; trim(line); trim(colon); - if ((!strcmp(line, "model name") || !strcmp(line, "Processor") || - !strcmp(line, "cpu")) && info->cpu[0] && !strcmp(info->cpu, FB_ARCH)) - strncpy(info->cpu, colon, sizeof(info->cpu) - 1); - else if (!strcmp(line, "Hardware") && cpuinfo_hardware[0] == '\0') - strncpy(cpuinfo_hardware, colon, sizeof cpuinfo_hardware - 1); - else if (!strcmp(line, "physical id")) physical = atoi(colon); - else if (!strcmp(line, "core id")) core = atoi(colon); - if (physical >= 0 && core >= 0) { - int i, seen = 0; - for (i = 0; i < npairs; i++) - if (pairs[i][0] == physical && pairs[i][1] == core) seen = 1; - if (!seen && npairs < 1024) { pairs[npairs][0] = physical; pairs[npairs++][1] = core; } - physical = core = -1; - } - } - fclose(f); - if (npairs > 0) info->cpu_cores = npairs; - } - } - /* ARM servers commonly expose SMBIOS. sysfs contains the same system - * product value as `dmidecode -t system` without requiring root. */ -#if defined(__aarch64__) || defined(__arm__) - if (info->model[0] == '\0') - read_first_property("/sys/class/dmi/id/product_name", info->model, - sizeof info->model); -#endif - if (info->model[0] == '\0' && cpuinfo_hardware[0] != '\0') - snprintf(info->model, sizeof info->model, "%s", cpuinfo_hardware); - { - FILE *f = fopen("/proc/meminfo", "r"); - char line[256]; long kb; - if (f) { - while (fgets(line, sizeof(line), f)) { - if (sscanf(line, "MemTotal: %ld kB", &kb) == 1) { - info->memory_mb = kb / 1024; - break; - } - } - fclose(f); - } - } - { - FILE *f = fopen("/etc/os-release", "r"); char line[512]; - if (f) { while (fgets(line, sizeof(line), f)) if (!strncmp(line, "PRETTY_NAME=", 12)) { - char *v = line + 12; trim(v); - if (v[0] == '\"') { memmove(v, v + 1, strlen(v)); if (strlen(v) && v[strlen(v)-1] == '\"') v[strlen(v)-1] = '\0'; } - snprintf(info->operating_system, sizeof(info->operating_system), "%s", v); break; - } fclose(f); } - } -#elif defined(__APPLE__) - { - size_t n = sizeof(info->cpu); uint64_t mem = 0; size_t mn = sizeof(mem); - size_t model_n = sizeof(info->model); - int cores = 0; size_t cn = sizeof(cores); - if (sysctlbyname("machdep.cpu.brand_string", info->cpu, &n, NULL, 0) != 0) - strncpy(info->cpu, FB_ARCH, sizeof info->cpu - 1); - sysctlbyname("hw.model", info->model, &model_n, NULL, 0); - if (sysctlbyname("hw.physicalcpu", &cores, &cn, NULL, 0) == 0) info->cpu_cores = cores; - if (sysctlbyname("hw.memsize", &mem, &mn, NULL, 0) == 0) info->memory_mb = (long)(mem / 1024 / 1024); - } - { - char product[64] = ""; size_t pn = sizeof(product); - struct utsname u; - if (sysctlbyname("kern.osproductversion", product, &pn, NULL, 0) == 0) - snprintf(info->operating_system, sizeof(info->operating_system), "macOS %s", product); - if (uname(&u) == 0) - snprintf(info->kernel, sizeof(info->kernel), "%s %s", u.sysname, u.release); - } -#elif defined(_WIN32) - { - MEMORYSTATUSEX ms; - ms.dwLength = sizeof(ms); - if (GlobalMemoryStatusEx(&ms)) - info->memory_mb = (long)(ms.ullTotalPhys / 1024 / 1024); - } - { - OSVERSIONINFOEXA version; - typedef LONG (WINAPI *rtl_get_version_fn)(OSVERSIONINFOEXA *); - rtl_get_version_fn rtl_get_version = (rtl_get_version_fn)(void *) - GetProcAddress(GetModuleHandleA("ntdll.dll"), "RtlGetVersion"); - memset(&version, 0, sizeof(version)); version.dwOSVersionInfoSize = sizeof(version); - if (rtl_get_version && rtl_get_version(&version) == 0) { - snprintf(info->operating_system, sizeof(info->operating_system), - "Windows %lu.%lu (build %lu)", (unsigned long)version.dwMajorVersion, - (unsigned long)version.dwMinorVersion, (unsigned long)version.dwBuildNumber); - snprintf(info->kernel, sizeof(info->kernel), "NT %lu.%lu build %lu", - (unsigned long)version.dwMajorVersion, (unsigned long)version.dwMinorVersion, - (unsigned long)version.dwBuildNumber); - } - } -#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 -} - -/* - * Synthesise a compressible corpus. Random bytes would be incompressible and - * would make the match-finder trivially miss every probe, measuring nothing - * interesting. This builds text-like data with realistic repetition instead. - */ -static void build_corpus(uint8_t *buf, size_t len) -{ - static const char *words[] = { - "the", "quick", "brown", "fox", "jumps", "over", "lazy", - "dog", "benchmark", "processor", "assembly", "vector", - "memory", "cache", "pipeline", "instruction", "compress", - "data", "system", "performance", "register", "kernel" - }; - const size_t nwords = sizeof(words) / sizeof(words[0]); - size_t pos = 0; - - while (pos < len) { - const char *w = words[rng_next() % nwords]; - size_t wl = strlen(w); - - if (pos + wl + 1 > len) - break; - memcpy(buf + pos, w, wl); - pos += wl; - buf[pos++] = (rng_next() % 8 == 0) ? '\n' : ' '; - } - while (pos < len) - buf[pos++] = ' '; -} - -/* Build a single random cycle through the node array (Sattolo's algorithm), - * guaranteeing one cycle of exactly CHASE_NODES steps with no early closure. */ -static void build_chase(void **nodes, size_t n) -{ - size_t *perm = xalloc(n * sizeof(size_t)); - size_t i; - - for (i = 0; i < n; i++) - perm[i] = i; - for (i = n - 1; i > 0; i--) { - size_t j = (size_t)(rng_next() % i); /* strictly j < i */ - size_t t = perm[i]; - perm[i] = perm[j]; - perm[j] = t; - } - for (i = 0; i < n; i++) - nodes[perm[i]] = (void *)&nodes[perm[(i + 1) % n]]; - - xfree(perm); -} - -static void setup(void) -{ - size_t i; - long t; - - rng_reset(); - - /* shared read-only inputs and pristine per-core seeds */ - g_corpus = xalloc(COMPRESS_LEN); - g_cipher_src = xalloc(CIPHER_LEN); - g_simd_src = xalloc(SIMD_BUF); - g_bodies_src = xalloc(NBODY_N * 8 * sizeof(double)); - g_sort_src = xalloc(SORT_N * sizeof(uint32_t)); - - build_corpus(g_corpus, COMPRESS_LEN); - - for (i = 0; i < CIPHER_LEN; i++) - g_cipher_src[i] = (uint8_t)rng_next(); - for (i = 0; i < 32; i++) - g_key[i] = (uint8_t)rng_next(); - for (i = 0; i < SIMD_BUF; i++) - g_simd_src[i] = (uint8_t)rng_next(); - for (i = 0; i < SORT_N; i++) - g_sort_src[i] = (uint32_t)rng_next(); - - /* bodies: [x y z mass vx vy vz pad], positions in a unit-ish cube */ - for (i = 0; i < NBODY_N; i++) { - double *b = &g_bodies_src[i * 8]; - b[0] = (double)(rng_next() % 2000) / 1000.0 - 1.0; - b[1] = (double)(rng_next() % 2000) / 1000.0 - 1.0; - b[2] = (double)(rng_next() % 2000) / 1000.0 - 1.0; - b[3] = (double)(rng_next() % 900) / 1000.0 + 0.1; /* mass > 0 */ - b[4] = b[5] = b[6] = 0.0; - b[7] = 0.0; - } - - /* one private workspace per core, every copy seeded identically so all - * cores compute the same deterministic result. Each core also gets its - * own pointer-chase cycle: sharing one would collapse the multi-core - * latency test into a shared-cache test instead of a memory test. */ - g_ws = xalloc((size_t)g_ncores * sizeof *g_ws); - for (t = 0; t < g_ncores; t++) { - struct workspace *w = &g_ws[t]; - - w->sieve = xalloc(PRIME_LIMIT); - w->ht = xalloc(HT_ENTRIES * sizeof(uint32_t)); - w->cipher_buf = xalloc(CIPHER_LEN); - w->simd_buf = xalloc(SIMD_BUF); - w->bodies = xalloc(NBODY_N * 8 * sizeof(double)); - w->sort_work = xalloc(SORT_N * sizeof(uint32_t)); - w->chase = xalloc(CHASE_NODES * sizeof(void *)); - - memcpy(w->cipher_buf, g_cipher_src, CIPHER_LEN); - memcpy(w->simd_buf, g_simd_src, SIMD_BUF); - build_chase(w->chase, CHASE_NODES); - } -} - -static void teardown(void) -{ - long t; - - for (t = 0; t < g_ncores; t++) { - struct workspace *w = &g_ws[t]; - - xfree(w->sieve); xfree(w->ht); xfree(w->cipher_buf); - xfree(w->simd_buf); xfree(w->bodies); xfree(w->sort_work); - xfree(w->chase); - } - xfree(g_ws); - - xfree(g_corpus); xfree(g_cipher_src); xfree(g_simd_src); - xfree(g_bodies_src); xfree(g_sort_src); -} - -/* ---------- the test harness ---------- */ - -/* - * Each test runs a kernel `n` times against a per-core workspace and returns a - * checksum. The harness auto-calibrates `n` upward until the run exceeds - * MIN_SECONDS, so the result is insensitive to clock granularity and to how - * fast the machine is. - */ -typedef uint64_t (*run_fn)(uint64_t n, struct workspace *ws); - -struct test { - const char *name; - const char *detail; - run_fn run; - uint64_t start_n; - double work_per_n; /* abstract work units, for scoring */ - const char *unit; - double ref_rate; /* reference-machine rate, in `unit` */ - double weight; /* relative weight in the overall score */ -}; - -static uint64_t run_int(uint64_t n, struct workspace *ws) -{ - (void)ws; - return fb_int_math(n * 100000); -} -static uint64_t run_fp(uint64_t n, struct workspace *ws) -{ - (void)ws; - return fb_fp_math(n * 100000); -} -static uint64_t run_primes(uint64_t n, struct workspace *ws) -{ - uint64_t c = 0; - for (uint64_t i = 0; i < n; i++) - c += fb_primes(PRIME_LIMIT, ws->sieve); - return c; -} -static uint64_t run_simd(uint64_t n, struct workspace *ws) -{ - /* The kernel is allowed to use its scratch as an accumulator. Restore it - * before every timed run so calibration and repeats see identical input. */ - memcpy(ws->simd_buf, g_simd_src, SIMD_BUF); - return fb_simd(n * 100000, ws->simd_buf); -} -static uint64_t run_compress(uint64_t n, struct workspace *ws) -{ - uint64_t c = 0; - for (uint64_t i = 0; i < n; i++) - c += fb_compress(g_corpus, COMPRESS_LEN, ws->ht); - return c; -} -static uint64_t run_crypto(uint64_t n, struct workspace *ws) -{ - return fb_chacha20(ws->cipher_buf, CIPHER_LEN, g_key, n); -} -static uint64_t run_physics(uint64_t n, struct workspace *ws) -{ - /* restore initial conditions: the integrator mutates the bodies, so - * a re-run must start from the same state to be reproducible */ - memcpy(ws->bodies, g_bodies_src, NBODY_N * 8 * sizeof(double)); - return fb_physics(ws->bodies, NBODY_N, n); -} -static uint64_t run_sort(uint64_t n, struct workspace *ws) -{ - uint64_t c = 0; - for (uint64_t i = 0; i < n; i++) { - /* restore the pristine data: sorting an already-sorted array - * would measure the best case, not the real one */ - memcpy(ws->sort_work, g_sort_src, SORT_N * sizeof(uint32_t)); - c ^= fb_sort(ws->sort_work, SORT_N); - } - return c; -} -static uint64_t run_chase(uint64_t n, struct workspace *ws) -{ - return fb_chase(ws->chase, n * 1000000); -} - -static const struct test tests[] = { - { "Integer Math", D_INT, - run_int, 20, 100000.0 * 24, "Mops/s", - FB_REF_INT, FB_WEIGHT_INT }, - { "Floating Point Math", D_FP, - run_fp, 20, 100000.0 * 20, "Mops/s", - FB_REF_FP, FB_WEIGHT_FP }, - { "Prime Numbers", "sieve of Eratosthenes to 2M", - run_primes, 1, (double)PRIME_LIMIT, "Mcand/s", - FB_REF_PRIMES, FB_WEIGHT_PRIMES }, - { "Extended Instructions",D_SIMD, - run_simd, 10, 100000.0 * 32, "Mops/s", - FB_REF_SIMD, FB_WEIGHT_SIMD }, - { "Compression", "LZ77 match finder, 4 MiB corpus", - run_compress, 1, (double)COMPRESS_LEN, "MB/s", - FB_REF_COMPRESS, FB_WEIGHT_COMPRESS }, - { "Encryption", "ChaCha20, 20 rounds, 1 MiB", - run_crypto, 4, (double)CIPHER_LEN, "MB/s", - FB_REF_CRYPTO, FB_WEIGHT_CRYPTO }, - { "Physics", "512-body direct-sum gravity", - run_physics, 4, (double)NBODY_N * NBODY_N, "Mpair/s", - FB_REF_PHYSICS, FB_WEIGHT_PHYSICS }, - { "Sorting", "heapsort, 1M uint32", - run_sort, 1, (double)SORT_N * 20, "Mkey-cmp/s", - FB_REF_SORT, FB_WEIGHT_SORT }, - { "Memory Latency", CHASE_DETAIL, - run_chase, 1, 1000000.0, "ns/access", - FB_REF_CHASE, FB_WEIGHT_CHASE }, -}; - -#define NTESTS (sizeof(tests) / sizeof(tests[0])) - -struct result { - double rate; /* work units per second */ - double score; - uint64_t checksum; - double seconds; - uint64_t iters; - int threads; /* cores this test was spread across */ -}; - -/* - * One unit of parallel work: run `run(n, ws)` on a private workspace. Every - * core executes the identical kernel on identically-seeded data, so all cores - * return the same checksum; the harness sums them into one aggregate that stays - * deterministic across repeats. - */ -struct job { - run_fn run; - uint64_t n; - struct workspace *ws; - uint64_t result; -}; - -static void *job_entry(void *arg) -{ - struct job *j = arg; - j->result = j->run(j->n, j->ws); - return NULL; -} - -/* - * Run the kernel on `threads` cores at once and return the summed checksum. - * The calling thread runs job 0 itself; threads 1..N-1 run on spawned workers. - * A thread that fails to spawn simply runs inline, so the benchmark still - * completes (with less parallelism) rather than aborting. - */ -static uint64_t dispatch(run_fn run, uint64_t n, int threads) -{ - struct job *jobs = xalloc((size_t)threads * sizeof *jobs); - pthread_t *tids = threads > 1 - ? xalloc((size_t)(threads - 1) * sizeof *tids) : NULL; - int i, spawned = 0; - uint64_t agg = 0; - - for (i = 0; i < threads; i++) { - jobs[i].run = run; - jobs[i].n = n; - jobs[i].ws = &g_ws[i]; - } - for (i = 1; i < threads; i++) { - if (pthread_create(&tids[spawned], NULL, job_entry, &jobs[i]) == 0) - spawned++; - else - job_entry(&jobs[i]); /* fall back to inline */ - } - - job_entry(&jobs[0]); /* this thread runs job 0 */ - - for (i = 0; i < spawned; i++) - pthread_join(tids[i], NULL); - for (i = 0; i < threads; i++) - agg += jobs[i].result; - - xfree(jobs); - xfree(tids); - return agg; -} - -static struct result run_test(const struct test *t, int threads) -{ - struct result r; - uint64_t n = t->start_n; - double elapsed = 0.0, best = 0.0; - uint64_t checksum = 0; - int i; - - /* calibrate: grow n until a single run clears the noise floor */ - for (;;) { - double t0 = now_seconds(); - checksum = dispatch(t->run, n, threads); - elapsed = now_seconds() - t0; - - if (elapsed >= MIN_SECONDS) - break; - if (elapsed < 0.001) { - n *= 8; /* far too fast to measure */ - } else { - double scale = (MIN_SECONDS * 1.3) / elapsed; - if (scale < 1.5) - scale = 1.5; - if (scale > 8.0) - scale = 8.0; - n = (uint64_t)((double)n * scale) + 1; - } - } - - /* best-of: the fastest run is the one least disturbed by the OS */ - best = elapsed; - for (i = 1; i < REPEATS; i++) { - double t0 = now_seconds(); - uint64_t c = dispatch(t->run, n, threads); - double e = now_seconds() - t0; - - if (c != checksum) { - fprintf(stderr, - "fossbench: %s is non-deterministic " - "(checksum %llu != %llu)\n", t->name, - (unsigned long long)c, - (unsigned long long)checksum); - exit(2); - } - if (e < best) - best = e; - } - - r.seconds = best; - r.iters = n; - r.checksum = checksum; - r.threads = threads; - /* aggregate throughput: `threads` cores each did n*work_per_n of work in - * the same wall-clock window, so the machine's rate is their sum */ - r.rate = ((double)threads * (double)n * t->work_per_n) / best / 1e6; - /* normalise against the reference machine: this is the per-test score */ - r.score = FB_TARGET_SCORE * (r.rate / t->ref_rate); - return r; -} - -/* - * The number shown in the RATE column. Most tests report throughput in their - * `unit`. The memory-latency test is different: throughput (hops/s) is not what - * anyone reasons about for memory, so we report the actual per-access latency - * in nanoseconds instead. That is a per-core property -- the time for one - * dependent load in the chain -- so it is derived from a single core's hop - * count and is independent of how many cores ran, unlike the aggregate `rate`. - */ -static double display_metric(const struct test *t, const struct result *r) -{ - if (t->run == run_chase) { - double hops = (double)r->iters * t->work_per_n; /* per core */ - return r->seconds / hops * 1e9; /* ns/access */ - } - return r->rate; -} - -/* ---------- optional result upload ---------- */ - -static void json_escape(const char *src, char *dst, size_t cap) -{ - size_t used = 0; - while (*src && used + 1 < cap) { - unsigned char c = (unsigned char)*src++; - const char *esc = NULL; - if (c == '\"') esc = "\\\""; - else if (c == '\\') esc = "\\\\"; - else if (c == '\n') esc = "\\n"; - else if (c == '\r') esc = "\\r"; - else if (c == '\t') esc = "\\t"; - if (esc) { - size_t n = strlen(esc); if (used + n >= cap) break; - memcpy(dst + used, esc, n); used += n; - } else if (c >= 0x20) dst[used++] = (char)c; - } - dst[used] = '\0'; -} - -#if !defined(_WIN32) -/* Add fb_ca_bundle_pem's roots to ctx's trust store. SSL_CTX_set_default_verify_paths() - * alone isn't enough to verify a server cert on an arbitrary target machine: it only - * works if OpenSSL's compiled-in default CA directory/file happens to exist where this - * binary ends up running, which is essentially never true for a release binary built - * elsewhere (macOS has no such path outside Homebrew; Linux distros disagree on the - * location). This embedded bundle is the trust source upload actually relies on; the - * system default paths are still tried first so a locally-trusted/corporate CA works too. */ -static int load_embedded_ca_bundle(SSL_CTX *ctx) -{ - X509_STORE *store = SSL_CTX_get_cert_store(ctx); - BIO *bio = BIO_new_mem_buf(fb_ca_bundle_pem, -1); - X509 *cert; - int loaded = 0; - - if (!bio) return 0; - while ((cert = PEM_read_bio_X509(bio, NULL, NULL, NULL)) != NULL) { - if (X509_STORE_add_cert(store, cert)) loaded++; - X509_free(cert); - } - BIO_free(bio); - ERR_clear_error(); /* PEM_read_bio_X509's final EOF "failure" is expected */ - return loaded > 0; -} -#endif - -#if defined(_WIN32) -/* WinHTTP reports a TLS handshake/certificate problem as one of several - * specific ERROR_WINHTTP_SECURE_* codes (untrusted root, expired cert, - * hostname mismatch, ...) rather than a single sentinel value. */ -static int is_winhttp_secure_error(DWORD err) -{ - switch (err) { - case ERROR_WINHTTP_SECURE_CERT_DATE_INVALID: - case ERROR_WINHTTP_SECURE_CERT_CN_INVALID: - case ERROR_WINHTTP_SECURE_INVALID_CA: - case ERROR_WINHTTP_SECURE_CERT_REV_FAILED: - case ERROR_WINHTTP_SECURE_CHANNEL_ERROR: - case ERROR_WINHTTP_SECURE_INVALID_CERT: - case ERROR_WINHTTP_SECURE_CERT_REVOKED: - case ERROR_WINHTTP_SECURE_FAILURE: - case ERROR_WINHTTP_SECURE_CERT_WRONG_USAGE: - case ERROR_WINHTTP_SECURE_FAILURE_PROXY: - return 1; - default: - return 0; - } -} - -/* WinHTTP does TLS (and certificate verification, against the system trust - * store) itself, so unlike the POSIX+OpenSSL path below, Windows needs - * neither an embedded CA bundle nor a hand-rolled HTTP/1.1 request. */ -static int winhttp_post(const char *host, const char *port, const char *path, - int use_tls, const char *payload, int payload_len, - const char *auth_header, int *out_status) -{ - wchar_t whost[256], wpath[512], wheaders[700]; - char header_buf[700]; - HINTERNET hsession = NULL, hconnect = NULL, hrequest = NULL; - INTERNET_PORT wport = (INTERNET_PORT)atoi(port); - DWORD status = 0, status_size = sizeof(status); - int ok = 0; - - if (MultiByteToWideChar(CP_UTF8, 0, host, -1, whost, sizeof whost / sizeof whost[0]) == 0 || - MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, sizeof wpath / sizeof wpath[0]) == 0) - return 0; - snprintf(header_buf, sizeof(header_buf), "Content-Type: application/json\r\n%s", auth_header); - if (MultiByteToWideChar(CP_UTF8, 0, header_buf, -1, wheaders, sizeof wheaders / sizeof wheaders[0]) == 0) - return 0; - - hsession = WinHttpOpen(L"fossbench", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); - if (!hsession) { - fprintf(stderr, " upload error: cannot initialize WinHTTP\n"); - return 0; - } - hconnect = WinHttpConnect(hsession, whost, wport, 0); - if (!hconnect) { - fprintf(stderr, " upload error: cannot connect to %s:%s\n", host, port); - goto done; - } - hrequest = WinHttpOpenRequest(hconnect, L"POST", wpath, NULL, WINHTTP_NO_REFERER, - WINHTTP_DEFAULT_ACCEPT_TYPES, - use_tls ? WINHTTP_FLAG_SECURE : 0); - if (!hrequest) { - fprintf(stderr, " upload error: cannot create HTTP request\n"); - goto done; - } - if (!WinHttpSendRequest(hrequest, wheaders, (DWORD)-1L, (LPVOID)payload, - (DWORD)payload_len, (DWORD)payload_len, 0)) { - if (is_winhttp_secure_error(GetLastError())) - fprintf(stderr, " upload error: TLS connection or certificate verification failed\n"); - else - fprintf(stderr, " upload error: send failed\n"); - goto done; - } - if (!WinHttpReceiveResponse(hrequest, NULL)) { - if (is_winhttp_secure_error(GetLastError())) - fprintf(stderr, " upload error: TLS connection or certificate verification failed\n"); - else - fprintf(stderr, " upload error: no server response\n"); - goto done; - } - if (!WinHttpQueryHeaders(hrequest, WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER, - WINHTTP_HEADER_NAME_BY_INDEX, &status, &status_size, - WINHTTP_NO_HEADER_INDEX)) { - fprintf(stderr, " upload error: no server response\n"); - goto done; - } - *out_status = (int)status; - ok = 1; -done: - if (hrequest) WinHttpCloseHandle(hrequest); - if (hconnect) WinHttpCloseHandle(hconnect); - WinHttpCloseHandle(hsession); - return ok; -} -#endif - -static int upload_results(const struct system_info *info, double score, - double singlecore_score, const struct result *multi, - const struct result *single, uint64_t duration_ms, - const struct background_metrics *background, const char *token) -{ - char host[256], port[16], path[512], payload[16384]; - char auth_header[600]; - char cpu[512], model[512], os[512], compiler[256], kernel[256]; - const char *base = FB_API_BASE_URL, *p, *slash, *colon; - int use_tls, status = 0, payload_len; -#if !defined(_WIN32) - char request[20000], response[512]; - struct addrinfo hints, *addresses = NULL, *a; - SSL_CTX *tls_ctx = NULL; - SSL *tls = NULL; - int fd = -1, request_len; -#endif - - if (!strncmp(base, "https://", 8)) { - use_tls = 1; p = base + 8; strcpy(port, "443"); - } else if (!strncmp(base, "http://", 7)) { - use_tls = 0; p = base + 7; strcpy(port, "80"); - } else { - fprintf(stderr, " upload error: unsupported URL scheme\n"); - return 0; - } - slash = strchr(p, '/'); - if (!slash) slash = p + strlen(p); - colon = memchr(p, ':', (size_t)(slash - p)); - if (colon) { - size_t hn = (size_t)(colon - p), pn = (size_t)(slash - colon - 1); - if (hn >= sizeof(host) || pn == 0 || pn >= sizeof(port)) return 0; - memcpy(host, p, hn); host[hn] = '\0'; memcpy(port, colon + 1, pn); port[pn] = '\0'; - } else { - size_t hn = (size_t)(slash - p); if (hn >= sizeof(host)) return 0; - memcpy(host, p, hn); host[hn] = '\0'; - } - { - int base_path_len = (int)strlen(slash); - while (base_path_len > 0 && slash[base_path_len - 1] == '/') base_path_len--; - snprintf(path, sizeof(path), "%.*s/api/v1/submissions", base_path_len, slash); - } - - json_escape(info->cpu, cpu, sizeof(cpu)); - json_escape(info->model, model, sizeof(model)); - json_escape(info->operating_system, os, sizeof(os)); - json_escape(info->compiler, compiler, sizeof(compiler)); - json_escape(info->kernel, kernel, sizeof(kernel)); - /* "fossmark_version" is the API's field name, fixed by the server - * contract; it does not track this client's own product name. */ - payload_len = snprintf(payload, sizeof(payload), - "{\"cpu\":\"%s\",\"model\":\"%s\",\"cpu_cores\":%ld,\"cpu_threads\":%ld," - "\"memory_mb\":%ld,\"operating_system\":\"%s\",\"compiler\":\"%s\"," - "\"fossmark_version\":\"%s\",\"score\":%.17g,\"duration_ms\":%llu," - "\"score_details\":{\"scoring_method\":\"weighted_geometric_mean\"," - "\"target_score\":%.17g,\"multicore_score\":%.17g,\"singlecore_score\":%.17g," - "\"minimum_test_seconds\":%.17g,\"repeats\":%d," - "\"system_environment\":{\"kernel\":\"%s\",\"sample_seconds\":%d," - "\"background_cpu_average_percent\":%.17g,\"background_cpu_peak_percent\":%.17g," - "\"available_memory_mb\":%ld,\"process_count\":%ld},\"tests\":[", - cpu, model, info->cpu_cores, info->cpu_threads, info->memory_mb, os, compiler, - FB_VERSION, score, (unsigned long long)duration_ms, FB_TARGET_SCORE, score, - singlecore_score, MIN_SECONDS, REPEATS, kernel, background->samples, - background->average_cpu_percent, background->peak_cpu_percent, - background->available_memory_mb, background->process_count); - if (payload_len < 0 || (size_t)payload_len >= sizeof(payload)) return 0; - { - size_t used = (size_t)payload_len; - size_t i; - for (i = 0; i < NTESTS; i++) { - int n = snprintf(payload + used, sizeof(payload) - used, - "%s{\"name\":\"%s\",\"detail\":\"%s\",\"unit\":\"%s\"," - "\"start_iterations\":%llu,\"work_per_iteration\":%.17g," - "\"reference_rate\":%.17g,\"weight\":%.17g," - "\"multicore\":{\"display_metric\":%.17g,\"rate\":%.17g,\"score\":%.17g," - "\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"}," - "\"singlecore\":{\"display_metric\":%.17g,\"rate\":%.17g,\"score\":%.17g," - "\"seconds\":%.17g,\"iterations\":%llu,\"threads\":%d,\"checksum\":\"%llu\"}}", - i ? "," : "", tests[i].name, tests[i].detail, tests[i].unit, - (unsigned long long)tests[i].start_n, tests[i].work_per_n, - tests[i].ref_rate, tests[i].weight, - display_metric(&tests[i], &multi[i]), multi[i].rate, multi[i].score, - multi[i].seconds, (unsigned long long)multi[i].iters, multi[i].threads, - (unsigned long long)multi[i].checksum, - display_metric(&tests[i], &single[i]), single[i].rate, single[i].score, - single[i].seconds, (unsigned long long)single[i].iters, single[i].threads, - (unsigned long long)single[i].checksum); - if (n < 0 || (size_t)n >= sizeof(payload) - used) return 0; - used += (size_t)n; - } - if (used + 3 >= sizeof(payload)) return 0; - memcpy(payload + used, "]}}", 4); - payload_len = (int)(used + 3); - } - - auth_header[0] = '\0'; - if (token && token[0]) { - int n = snprintf(auth_header, sizeof(auth_header), - "Authorization: Bearer %s\r\n", token); - if (n < 0 || (size_t)n >= sizeof(auth_header)) { - fprintf(stderr, " upload error: API token too long\n"); - return 0; - } - } -#if !defined(_WIN32) - request_len = snprintf(request, sizeof(request), - "POST %s HTTP/1.1\r\nHost: %s:%s\r\nContent-Type: application/json\r\n" - "Content-Length: %d\r\nConnection: close\r\n%s\r\n%s", - path, host, port, payload_len, auth_header, payload); - if (request_len < 0 || (size_t)request_len >= sizeof(request)) return 0; - - memset(&hints, 0, sizeof(hints)); hints.ai_socktype = SOCK_STREAM; hints.ai_family = AF_UNSPEC; - if (getaddrinfo(host, port, &hints, &addresses) != 0) { fprintf(stderr, " upload error: cannot resolve %s\n", host); return 0; } - for (a = addresses; a; a = a->ai_next) { - fd = socket(a->ai_family, a->ai_socktype, a->ai_protocol); - if (fd >= 0 && connect(fd, a->ai_addr, a->ai_addrlen) == 0) break; - if (fd >= 0) close(fd); - fd = -1; - } - freeaddrinfo(addresses); - if (fd < 0) { fprintf(stderr, " upload error: cannot connect to %s:%s\n", host, port); return 0; } - if (use_tls) { - tls_ctx = SSL_CTX_new(TLS_client_method()); - if (!tls_ctx) { - fprintf(stderr, " upload error: cannot initialize TLS trust store\n"); - goto upload_failed; - } - SSL_CTX_set_default_verify_paths(tls_ctx); /* best-effort; see load_embedded_ca_bundle() */ - if (!load_embedded_ca_bundle(tls_ctx)) { - fprintf(stderr, " upload error: cannot initialize TLS trust store\n"); - goto upload_failed; - } - SSL_CTX_set_verify(tls_ctx, SSL_VERIFY_PEER, NULL); - tls = SSL_new(tls_ctx); - if (!tls || !SSL_set_tlsext_host_name(tls, host) || - !SSL_set1_host(tls, host) || !SSL_set_fd(tls, fd) || - SSL_connect(tls) != 1) { - fprintf(stderr, " upload error: TLS connection or certificate verification failed\n"); - goto upload_failed; - } - } - { - size_t sent = 0; - while (sent < (size_t)request_len) { - int n = use_tls ? SSL_write(tls, request + sent, (int)((size_t)request_len - sent)) : - (int)send(fd, request + sent, (size_t)request_len - sent, 0); - if (n <= 0) { fprintf(stderr, " upload error: send failed\n"); goto upload_failed; } - sent += (size_t)n; - } - } - { - int n = use_tls ? SSL_read(tls, response, sizeof(response) - 1) : - (int)recv(fd, response, sizeof(response) - 1, 0); - if (n <= 0) { fprintf(stderr, " upload error: no server response\n"); goto upload_failed; } - response[n] = '\0'; - if (sscanf(response, "HTTP/%*s %d", &status) != 1) status = 0; - } - if (tls) { SSL_shutdown(tls); SSL_free(tls); } - if (tls_ctx) SSL_CTX_free(tls_ctx); - close(fd); -#else - if (!winhttp_post(host, port, path, use_tls, payload, payload_len, auth_header, &status)) - return 0; -#endif - if (status == 401) { - fprintf(stderr, " upload failed: API token was rejected (HTTP 401)\n"); - return 0; - } - if (status == 422) { - fprintf(stderr, " upload failed: server rejected the submission as invalid (HTTP 422)\n"); - return 0; - } - if (status < 200 || status >= 300) { fprintf(stderr, " upload failed: server returned HTTP %d\n", status); return 0; } - if (token) - printf(" Results uploaded and published to your profile (HTTP %d).\n", status); - else - printf(" Results uploaded, pending administrator review (HTTP %d).\n", status); - return 1; - -#if !defined(_WIN32) -upload_failed: - if (tls) SSL_free(tls); - if (tls_ctx) SSL_CTX_free(tls_ctx); - if (fd >= 0) close(fd); - return 0; -#endif -} - -/* ---------- output ---------- */ - -static void print_header(const struct system_info *info) -{ - printf("\n"); - printf(" fossbench %s - multi-core CPU benchmark\n", FB_VERSION); - printf(" ------------------------------------------------------------------\n"); - printf(" CPU: %s\n", info->cpu); - printf(" model: %s\n", info->model[0] ? info->model : "unknown"); - printf(" cores: %ld physical / %ld threads\n", info->cpu_cores, info->cpu_threads); - printf(" memory: %ld MB\n", info->memory_mb); - printf(" OS: %s (%s)\n", info->operating_system, FB_ARCH); - printf(" kernel: %s\n", info->kernel[0] ? info->kernel : "unknown"); - printf(" compiler: %s\n", info->compiler); - printf("\n"); - printf(" %-24s %12s %-11s %8s %9s\n", - "TEST", "RATE", "UNIT", "TIME", "SCORE"); - printf(" --------------------------------------------------------------------------\n"); - fflush(stdout); -} +#include "app/benchmark.h" int main(int argc, char **argv) { - struct result multi[NTESTS], single[NTESTS]; - struct system_info system_info; - struct background_metrics background; - double multi_log_sum = 0.0, single_log_sum = 0.0; - double weight_sum = 0.0; - double benchmark_started, multicore_score, singlecore_score; - uint64_t duration_ms; int verbose = 0; - int upload_mode = 0; /* 0 = ask, 1 = force upload, 2 = force no upload */ + int upload_mode = 0; int system_check = 1; - size_t i; + int i; - for (i = 1; i < (size_t)argc; i++) { - if (strcmp(argv[i], "-v") == 0 || - strcmp(argv[i], "--verbose") == 0) { + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-v") == 0 || strcmp(argv[i], "--verbose") == 0) { verbose = 1; } else if (strcmp(argv[i], "--upload") == 0) { if (upload_mode == 2) { @@ -1357,136 +27,14 @@ int main(int argc, char **argv) upload_mode = 2; } else if (strcmp(argv[i], "--no-system-check") == 0) { system_check = 0; - } else if (strcmp(argv[i], "-h") == 0 || - strcmp(argv[i], "--help") == 0) { + } else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) { printf("usage: %s [-v|--verbose] [--upload|--noupload] [--no-system-check]\n", argv[0]); return 0; } else { - fprintf(stderr, "fossbench: unknown option '%s'\n", - argv[i]); + fprintf(stderr, "fossbench: unknown option '%s'\n", argv[i]); return 1; } } - { -#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); - memset(&background, 0, sizeof(background)); - background.available_memory_mb = background.process_count = -1; - if (system_check) { - sample_background_metrics(&background, 10); - if (background.available) { - printf(" background CPU: %.1f%% average / %.1f%% peak\n", - background.average_cpu_percent, background.peak_cpu_percent); - if (background.available_memory_mb >= 0) - printf(" available memory: %ld MB of %ld MB\n", background.available_memory_mb, system_info.memory_mb); - if (background.process_count >= 0) printf(" processes: %ld\n", background.process_count); - if (background.average_cpu_percent >= 10.0) - printf(" warning: background CPU activity may reduce benchmark scores\n"); - } - } - benchmark_started = now_seconds(); - - printf("\n preparing workloads..."); - fflush(stdout); - setup(); - printf(" done\n"); - - print_header(&system_info); - - for (i = 0; i < NTESTS; i++) { - double sm, ss; - - printf(" %-24s", tests[i].name); - fflush(stdout); - - /* each test runs twice: the all-core pass (shown) and the - * single-core pass (folded into the SINGLECORE score) */ - multi[i] = run_test(&tests[i], (int)g_ncores); - single[i] = run_test(&tests[i], 1); - - printf(" %12.1f %-11s %7.2fs %9.0f\n", - display_metric(&tests[i], &multi[i]), tests[i].unit, - multi[i].seconds, multi[i].score); - if (verbose) - printf(" %-24s %s\n" - " %-24s weight=%.0f%% 1-core: %.1f %s / %.0f %ld-core: %.1f %s / %.0f\n", - "", tests[i].detail, "", tests[i].weight, - display_metric(&tests[i], &single[i]), tests[i].unit, - single[i].score, g_ncores, - display_metric(&tests[i], &multi[i]), tests[i].unit, - multi[i].score); - fflush(stdout); - - /* accumulate the weighted geometric mean of BOTH passes, same - * weights, so the two composite scores are directly comparable */ - sm = multi[i].score > 0.0 ? multi[i].score : 1e-9; - ss = single[i].score > 0.0 ? single[i].score : 1e-9; - multi_log_sum += tests[i].weight * log(sm); - single_log_sum += tests[i].weight * log(ss); - weight_sum += tests[i].weight; - } - - printf(" --------------------------------------------------------------------------\n"); - - /* - * Two composite scores, each the WEIGHTED geometric mean of the per-test - * scores from one pass. Per-test scores are already normalised so the - * single-thread reference machine reads FB_TARGET_SCORE. Geometric rather - * than arithmetic so no single test dominates; weighted so tests count in - * proportion to their influence on everyday use (the FB_WEIGHT_* config). - * The two passes share tests and weights, so MULTICORE / SINGLECORE is a - * clean read of how much the machine gains from all its cores. - */ - multicore_score = exp(multi_log_sum / weight_sum); - singlecore_score = exp(single_log_sum / weight_sum); - duration_ms = (uint64_t)((now_seconds() - benchmark_started) * 1000.0); - printf(" %-24s %44.0f\n", "MULTICORE SCORE", multicore_score); - printf(" %-24s %44.0f\n", "SINGLECORE SCORE", singlecore_score); - printf(" %-24s %41.2fs\n", "TOTAL DURATION", (double)duration_ms / 1000.0); - printf("\n"); - - teardown(); - - { - /* the token is read from the environment only: it is never echoed - * back, so it never appears in argv, shell history, or process - * listings from a command-line flag */ - const char *token = getenv("FOSSBENCH_TOKEN"); - int do_upload; - - if (token && token[0] == '\0') - token = NULL; - - if (upload_mode == 1) { - do_upload = 1; - } else if (upload_mode == 2) { - do_upload = 0; - printf(" Result was not uploaded.\n"); - } else { - char answer[16]; - if (token) - printf(" Upload this result to %s using your API token? [y/N] ", FB_API_BASE_URL); - else - printf(" Upload this result to %s? [y/N] ", FB_API_BASE_URL); - fflush(stdout); - do_upload = fgets(answer, sizeof(answer), stdin) && - (answer[0] == 'y' || answer[0] == 'Y'); - if (!do_upload) - printf(" Result was not uploaded.\n"); - } - - if (do_upload) - upload_results(&system_info, multicore_score, singlecore_score, - multi, single, duration_ms, &background, token); - } - return 0; + return fossbench_run(verbose, upload_mode, system_check); } diff --git a/src/test_kernels.c b/src/test_kernels.c index 7bf6d25..cc3c01e 100644 --- a/src/test_kernels.c +++ b/src/test_kernels.c @@ -1,21 +1,4 @@ -/* - * test_kernels.c - correctness checks for the fossbench assembly kernels - * - * The benchmark's own best-of-N run guards against non-determinism, but a - * kernel can be perfectly deterministic and still wrong. This file is the - * "single C file to poke at and test with": it validates each kernel against - * an independent reference or an invariant, so a mistake in the assembly is - * caught here rather than silently skewing a score. - * - * Every check (except the single-threaded pointer-chase) is run concurrently - * on all available cores. The kernels take their buffers as arguments and hold - * no shared state, so a correct kernel must give identical, correct results no - * matter how many copies run at once; a hidden global or a reentrancy bug would - * survive a single-threaded run but fail here. - * - * Build: cc -O2 -pthread test_kernels.c fossbench.S -o test_kernels -lm - * Exit status is 0 iff every check passes. - */ +/* The actual tests. */ #include #include @@ -40,14 +23,7 @@ extern uint64_t fb_chase(void **ptrs, uint64_t steps); static int failures = 0; static int checks = 0; -/* - * Concurrency plumbing. Each check runs on every core at once; the counters and - * stdout are shared, so ok()/note() serialise on this lock. `fb_primary` is set - * on exactly one thread per check (the one running on the main thread): it owns - * the human-readable output so the "[ ok ]" lines and diagnostics appear once, - * not once per core. Every thread still evaluates every assertion, so a failure - * on any core - even a silent secondary - is reported and counted. - */ +/* Concurrency plumbing. */ static pthread_mutex_t io_lock = PTHREAD_MUTEX_INITIALIZER; static __thread int fb_primary = 1; static long fb_ncores = 1; @@ -64,14 +40,14 @@ static void ok(const char *what, int cond) failures++; } } else if (!cond) { - /* a secondary core disagrees: surface it explicitly */ + /* Show if another thread failed. */ printf(" [FAIL] %s (concurrent core)\n", what); failures++; } pthread_mutex_unlock(&io_lock); } -/* Diagnostic output that should appear once per check, not once per core. */ +/* Only print this once. */ static void note(const char *fmt, ...) { va_list ap; @@ -85,8 +61,7 @@ static void note(const char *fmt, ...) pthread_mutex_unlock(&io_lock); } -/* Run `check` on every core simultaneously. The main thread is the primary; - * fb_ncores-1 workers run the same check as silent secondaries. */ +/* Run the same check on every core. */ static void *fb_worker(void *arg) { void (*check)(void) = *(void (**)(void))arg; @@ -112,14 +87,14 @@ static void parallel(void (*check)(void)) } } - check(); /* primary runs on this thread */ + check(); /* Run the first check here. */ for (i = 0; i < spawned; i++) pthread_join(th[i], NULL); free(th); } -/* ---------- reference implementations ---------- */ +/* Small C versions used for comparison. */ static uint64_t ref_prime_count(uint64_t limit) { @@ -137,9 +112,7 @@ static uint64_t ref_prime_count(uint64_t limit) return count; } -/* A textbook scalar ChaCha20 block function, used both to anchor against the - * RFC 8439 known-answer vector and to validate the NEON kernel block-for-block. - * `out` receives 64 keystream bytes for the given counter and 12-byte nonce. */ +/* Basic ChaCha20 used to check the kernel. */ #define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n)))) static void ref_chacha_block(uint32_t out_words[16], const uint8_t key[32], @@ -180,12 +153,11 @@ static void ref_chacha_block(uint32_t out_words[16], const uint8_t key[32], out_words[i] = x[i] + s[i]; } -/* ---------- checks ---------- */ +/* The actual tests. */ static void check_int(void) { - /* determinism and non-triviality: the checksum must be stable and - * must actually change with the iteration count */ + /* The actual tests. */ uint64_t a = fb_int_math(1000); uint64_t b = fb_int_math(1000); uint64_t c = fb_int_math(2000); @@ -217,7 +189,7 @@ static void check_primes(void) note(" primes < %d: got %llu, expected %llu\n", LIM, (unsigned long long)got, (unsigned long long)ref); ok("primes matches reference sieve", got == ref); - ok("primes < 10 == 4", fb_primes(10, sieve) == 4); /* 2,3,5,7 */ + ok("primes < 10 == 4", fb_primes(10, sieve) == 4); /* The primes are 2, 3, 5, and 7. */ ok("primes < 2 == 0", fb_primes(2, sieve) == 0); free(sieve); } @@ -244,8 +216,7 @@ static void check_compress(void) uint64_t incompressible, compressible; size_t i; - /* genuinely incompressible data (splitmix64 output): with no matches - * to exploit, an LZ coder's output must be at least the input size */ + /* Random data should not compress much. */ { uint64_t st = 0x1234567890abcdefULL; for (i = 0; i < N; i++) { @@ -257,7 +228,7 @@ static void check_compress(void) } incompressible = fb_compress(src, N, ht); - /* all-zero data is maximally compressible: it must shrink hugely */ + /* Zeros should compress a lot. */ memset(src, 0, N); compressible = fb_compress(src, N, ht); @@ -276,9 +247,7 @@ static void check_crypto(void) uint8_t key[32]; size_t i; - /* (1) anchor the scalar reference to the RFC 8439 s.2.3.2 vector: - * key = 00,01,...,1f; counter = 1; nonce = 00,00,00,09,...,4a,... - * serialised keystream begins 10 f1 e7 e4. */ + /* Check the C version with the RFC example. */ { uint32_t w[16]; uint8_t rnonce[12] = {0,0,0,9, 0,0,0,0x4a, 0,0,0,0}; @@ -296,9 +265,7 @@ static void check_crypto(void) ks0[2] == 0xe7 && ks0[3] == 0xe4); } - /* (2) validate the NEON kernel against that reference. The kernel - * hardwires nonce = 0 and starts the block counter at 0, so we - * compare its keystream to the reference block-for-block. */ + /* Compare the kernel with the C version. */ { uint8_t buf[128]; uint8_t zero_nonce[12] = {0}; @@ -307,7 +274,7 @@ static void check_crypto(void) for (i = 0; i < 32; i++) key[i] = (uint8_t)(i * 5 + 1); - memset(buf, 0, sizeof buf); /* zeros -> raw keystream */ + memset(buf, 0, sizeof buf); /* Zeros give the keystream. */ fb_chacha20(buf, sizeof buf, key, 1); ref_chacha_block(ref0, key, 0, zero_nonce); @@ -327,7 +294,7 @@ static void check_crypto(void) ok("NEON ChaCha20 matches scalar reference (2 blocks)", match); } - /* (3) the cipher is a real XOR stream: applying it twice is identity */ + /* Running it twice should restore the data. */ { uint8_t plain[128], work[128], k2[32]; for (i = 0; i < 128; i++) @@ -345,18 +312,17 @@ static void check_crypto(void) static void check_physics(void) { - /* two equal masses released from rest must accelerate toward each - * other: symmetric, momentum-conserving, and bounded. */ + /* The two bodies should move toward each other. */ double bodies[2 * 8] = {0}; double total_p; - bodies[0] = -1.0; bodies[3] = 1.0; /* body 0 at x=-1, mass 1 */ - bodies[8] = 1.0; bodies[11] = 1.0; /* body 1 at x=+1, mass 1 */ + bodies[0] = -1.0; bodies[3] = 1.0; /* First body. */ + bodies[8] = 1.0; bodies[11] = 1.0; /* Second body. */ fb_physics(bodies, 2, 200); - /* velocities must be equal and opposite (Newton's third law) */ - total_p = bodies[4] + bodies[12]; /* vx0 + vx1 */ + /* The velocities should cancel out. */ + total_p = bodies[4] + bodies[12]; /* Add both x velocities. */ note(" 2-body: vx0=%.6f vx1=%.6f (sum should be ~0)\n", bodies[4], bodies[12]); ok("physics conserves momentum", fabs(total_p) < 1e-9); @@ -396,13 +362,12 @@ static void check_sort(void) s = fb_sort(a, N); ok("sort produces sorted output", is_sorted(a, N)); - /* multiset is preserved: sort the reference with the C library and - * compare element by element */ + /* Compare it with the C library sort. */ qsort(b, N, sizeof(uint32_t), cmp_u32); ok("sort is a permutation of the input", memcmp(a, b, N * sizeof(uint32_t)) == 0); - /* already-sorted input stays sorted and gives the same checksum */ + /* The actual tests. */ { uint64_t s2 = fb_sort(a, N); ok("sort is idempotent on sorted data", @@ -416,8 +381,7 @@ static void check_sort(void) static void check_chase(void) { - /* build a tiny 4-node cycle by hand and confirm the walk returns to - * the start after exactly `n` steps (offset 0 relative to entry) */ + /* Make a small pointer loop. */ void *nodes[4]; nodes[0] = &nodes[1]; @@ -425,11 +389,10 @@ static void check_chase(void) nodes[2] = &nodes[3]; nodes[3] = &nodes[0]; - /* 4 hops from &nodes[0] returns to &nodes[0]; fb_chase returns the - * final pointer minus the starting pointer, so a full loop gives 0 */ + /* Four hops should return to the start. */ ok("chase completes a full cycle", fb_chase(nodes, 4) == 0); ok("chase(0) is zero", fb_chase(nodes, 0) == 0); - /* one hop lands on &nodes[1], i.e. one pointer-width past the start */ + /* One hop should move to the next pointer. */ ok("chase single hop offset", fb_chase(nodes, 1) == (uint64_t)((char *)&nodes[1] - (char *)&nodes[0])); } @@ -453,7 +416,7 @@ int main(void) printf("Encryption:\n"); parallel(check_crypto); printf("Physics:\n"); parallel(check_physics); printf("Sorting:\n"); parallel(check_sort); - /* the pointer chase is the single-threaded test: run it on one core */ + /* Run the pointer test on one core. */ printf("Single-Threaded (chase):\n"); check_chase(); printf("\n=================================\n");