Merge pull request #2 from owenrummage/owen-refactor
alright, 5hrs of refactoring and remaking it because I was tired of t…
This commit is contained in:
@@ -1,71 +1,24 @@
|
|||||||
# fossbench - multi-core CPU benchmark
|
# fossbench build file
|
||||||
#
|
# Use make for the current computer, or a named target for another one.
|
||||||
# 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-<os>-<arch>)
|
|
||||||
# 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
|
|
||||||
|
|
||||||
CC ?= cc
|
CC ?= cc
|
||||||
CFLAGS ?= -O2 -Wall -Wextra
|
CFLAGS ?= -O2 -Wall -Wextra
|
||||||
TLS_CFLAGS ?=
|
TLS_CFLAGS ?=
|
||||||
TLS_LDLIBS ?= -lssl -lcrypto
|
TLS_LDLIBS ?= -lssl -lcrypto
|
||||||
LDLIBS ?= -lm $(TLS_LDLIBS)
|
LDLIBS ?= -lm $(TLS_LDLIBS)
|
||||||
# The driver spreads each workload across all cores with pthreads.
|
# Needed for the worker threads.
|
||||||
PTHREAD := -pthread
|
PTHREAD := -pthread
|
||||||
|
|
||||||
DIST := dist
|
DIST := dist
|
||||||
DRIVER := src/main.c
|
DRIVER := src/main.c src/app/benchmark.c
|
||||||
ASM_ARM64 := src/fossbench.S
|
ASM_ARM64 := src/kernels/fossbench-arm64.S
|
||||||
ASM_AMD64 := src/fossbench_x86_64.S
|
ASM_AMD64 := src/kernels/fossbench-amd64.S
|
||||||
ASM_I386 := src/fossbench_i386.S
|
ASM_I386 := src/kernels/fossbench-i386.S
|
||||||
SRC_PPC32 := src/fossbench_ppc32.c
|
SRC_PPC32 := src/kernels/fossbench-powerpc.c
|
||||||
ASM_PPC32 := src/fossbench_ppc32_ext.S
|
ASM_PPC32 := src/kernels/fossbench-ppc32-ext.S
|
||||||
SRC_PPC64 := src/fossbench_ppc32.c
|
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)
|
HOST_ARCH := $(shell uname -m)
|
||||||
ifneq (,$(filter aarch64 arm64,$(HOST_ARCH)))
|
ifneq (,$(filter aarch64 arm64,$(HOST_ARCH)))
|
||||||
HOST_ARCHNAME := arm64
|
HOST_ARCHNAME := arm64
|
||||||
@@ -87,14 +40,7 @@ else
|
|||||||
$(error unsupported host architecture '$(HOST_ARCH)')
|
$(error unsupported host architecture '$(HOST_ARCH)')
|
||||||
endif
|
endif
|
||||||
ifeq ($(HOST_ARCHNAME),i386)
|
ifeq ($(HOST_ARCHNAME),i386)
|
||||||
# The kernels are hand-written assembly (fossbench_i386.S) using SSE2
|
# Keep the old i386 target simple and non-PIE.
|
||||||
# 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.
|
|
||||||
CFLAGS += -march=pentium4 -fno-pie
|
CFLAGS += -march=pentium4 -fno-pie
|
||||||
LDFLAGS += -no-pie
|
LDFLAGS += -no-pie
|
||||||
endif
|
endif
|
||||||
@@ -105,7 +51,7 @@ ifeq ($(HOST_ARCHNAME),arm64)
|
|||||||
HOST_KERNEL := $(ASM_ARM64)
|
HOST_KERNEL := $(ASM_ARM64)
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# ---- host OS name for the native binary ----
|
# Figure out the host OS.
|
||||||
UNAME_S := $(shell uname -s)
|
UNAME_S := $(shell uname -s)
|
||||||
ifeq ($(UNAME_S),Linux)
|
ifeq ($(UNAME_S),Linux)
|
||||||
OSNAME := linux
|
OSNAME := linux
|
||||||
@@ -117,7 +63,7 @@ else
|
|||||||
OSNAME := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
OSNAME := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# ---- per-target compilers: native cc if the host matches, else a cross gcc ----
|
# Pick a compiler for each target.
|
||||||
ifeq ($(HOST_ARCHNAME),arm64)
|
ifeq ($(HOST_ARCHNAME),arm64)
|
||||||
CC_ARM64 ?= $(CC)
|
CC_ARM64 ?= $(CC)
|
||||||
else
|
else
|
||||||
@@ -146,20 +92,20 @@ ifeq ($(HOST_ARCHNAME),ppc64be)
|
|||||||
else
|
else
|
||||||
CC_PPC64BE ?= powerpc64-linux-gnu-gcc
|
CC_PPC64BE ?= powerpc64-linux-gnu-gcc
|
||||||
endif
|
endif
|
||||||
# Windows is always cross-compiled with MinGW-w64, regardless of host OS/arch.
|
# Windows uses MinGW.
|
||||||
CC_WINDOWS_AMD64 ?= x86_64-w64-mingw32-gcc
|
CC_WINDOWS_AMD64 ?= x86_64-w64-mingw32-gcc
|
||||||
CC_WINDOWS_I386 ?= i686-w64-mingw32-gcc
|
CC_WINDOWS_I386 ?= i686-w64-mingw32-gcc
|
||||||
|
|
||||||
NATIVE_BIN := $(DIST)/fossbench-$(OSNAME)-$(HOST_ARCHNAME)
|
NATIVE_BIN := $(DIST)/fossbench-$(OSNAME)-$(HOST_ARCHNAME)
|
||||||
|
|
||||||
# `make` with no target builds the host binary, as before.
|
# Plain make builds for this computer.
|
||||||
.DEFAULT_GOAL := native
|
.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
|
.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
|
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)
|
native: $(NATIVE_BIN)
|
||||||
|
|
||||||
linux-arm64: $(DIST)/fossbench-linux-arm64
|
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)
|
MACOSX_DEPLOYMENT_TARGET=$(MACOS_AMD64_MIN) $(CC_MACOS_AMD64) -arch x86_64 -mmacosx-version-min=$(MACOS_AMD64_MIN) $(CFLAGS) $(TLS_CFLAGS) $(PTHREAD) $(LDFLAGS) -Wl,-no_fixup_chains -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS)
|
||||||
@echo "built $@"
|
@echo "built $@"
|
||||||
|
|
||||||
# Windows binaries are statically linked (-static) so the .exe is
|
# Windows builds are static and use WinHTTP.
|
||||||
# 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.
|
|
||||||
$(DIST)/fossbench-windows-amd64.exe: $(DRIVER) $(ASM_AMD64) | $(DIST)
|
$(DIST)/fossbench-windows-amd64.exe: $(DRIVER) $(ASM_AMD64) | $(DIST)
|
||||||
$(CC_WINDOWS_AMD64) $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_AMD64) -lm -lwinhttp
|
$(CC_WINDOWS_AMD64) $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_AMD64) -lm -lwinhttp
|
||||||
@echo "built $@"
|
@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
|
$(CC_WINDOWS_I386) -march=pentium4 $(CFLAGS) $(PTHREAD) -static -o $@ $(DRIVER) $(ASM_I386) -lm -lwinhttp
|
||||||
@echo "built $@"
|
@echo "built $@"
|
||||||
|
|
||||||
# When the host is Linux/ARM64 or Linux/AMD64, the native binary IS one of the
|
# Add a native rule if one was not already made above.
|
||||||
# linux-* targets above, so no separate recipe is defined (that would be a
|
|
||||||
# duplicate). Otherwise - e.g. macOS/ARM64 - provide the native recipe here.
|
|
||||||
ifeq ($(OSNAME)-$(HOST_ARCHNAME),linux-arm64)
|
ifeq ($(OSNAME)-$(HOST_ARCHNAME),linux-arm64)
|
||||||
NATIVE_HAS_RULE := yes
|
NATIVE_HAS_RULE := yes
|
||||||
endif
|
endif
|
||||||
|
|||||||
@@ -1,255 +1,159 @@
|
|||||||
# fossbench
|
# fossbench
|
||||||
|
|
||||||
fossbench is an open-source CPU benchmark with nine assembly workloads and a
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||
The repository currently builds an executable named `fossbench` for ARM64,
|
## Supported systems
|
||||||
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.
|
|
||||||
|
|
||||||
## 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 |
|
| ARM64 | ARMv8-A with NEON |
|
||||||
| Floating point math | Scalar double-precision multiplication, addition, division, and square roots |
|
| x86-64 | baseline x86-64 with SSE2 |
|
||||||
| Prime numbers | A sieve of Eratosthenes up to 2,000,000 |
|
| x86 32-bit | baseline i386 with SSE2 |
|
||||||
| Extended instructions | 128-bit SIMD integer and floating point work using NEON or SSE2 |
|
| PowerPC 32-bit big-endian | scalar fallback with runtime-selected extended instructions |
|
||||||
| Compression | An LZ77 match finder over a 4 MiB generated corpus |
|
| PowerPC 64-bit big-endian | PowerPC 970 with AltiVec |
|
||||||
| 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 |
|
|
||||||
|
|
||||||
The benchmark increases each test's iteration count until one run takes at
|
If you find something it doesnt run on, please make a PR with patches if you think you can make it!
|
||||||
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.
|
|
||||||
|
|
||||||
During the multicore pass, every thread gets its own mutable workspace. This
|
## Building
|
||||||
keeps the kernels free of data races and prevents shared scratch buffers from
|
|
||||||
distorting the result.
|
|
||||||
|
|
||||||
## 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,
|
Build for the current machine:
|
||||||
pthreads, and the system math library.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make
|
make
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary is written to `dist/fossbench-<os>-<arch>`. To build it and start a
|
||||||
|
benchmark immediately, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
make bench
|
make bench
|
||||||
```
|
```
|
||||||
|
|
||||||
`make` builds a binary for the host at
|
Named targets are available when you want a specific build, these can be found in the Makefile.
|
||||||
`dist/fossbench-<os>-<arch>`. `make bench` builds that binary and runs it.
|
|
||||||
|
|
||||||
Other targets are available for explicit platforms and architectures:
|
Cross builds use conventional GNU toolchain names by default. Override a
|
||||||
|
compiler when your toolchain uses a different name:
|
||||||
```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:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make linux-arm64 CC_ARM64=aarch64-linux-gnu-gcc
|
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-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-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
|
The macOS AMD64 build defaults to macOS 10.5 compatibility. Set
|
||||||
binaries are cross-compiled with the MinGW-w64 toolchain (package
|
`MACOS_AMD64_MIN` to choose another deployment target.
|
||||||
`mingw-w64-gcc` on Arch, `gcc-mingw-w64-x86-64` / `gcc-mingw-w64-i686` on
|
|
||||||
Debian/Ubuntu) and are statically linked, so the `.exe` needs no
|
|
||||||
accompanying DLLs. Windows uses a different AMD64 calling convention than
|
|
||||||
Linux/macOS (integer args in `rcx`/`rdx`/`r8`/`r9` rather than
|
|
||||||
`rdi`/`rsi`/`rdx`/`rcx`, with `rdi`, `rsi`, and `xmm6`-`xmm15` callee-saved);
|
|
||||||
`src/fossbench_x86_64.S` still writes every kernel once to the System V
|
|
||||||
convention and wraps each public entry point in a small ABI-translating
|
|
||||||
thunk (`WIN64_THUNK`) when building for Windows. Result upload (HTTPS/TLS)
|
|
||||||
is not built for Windows, so these targets need no OpenSSL.
|
|
||||||
|
|
||||||
The macOS AMD64 target is linked for macOS 10.5 and disables chained fixups so
|
## Running a benchmark
|
||||||
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`.
|
|
||||||
|
|
||||||
Run the benchmark with extra per-test details by passing `--verbose`:
|
Run the binary directly. The exact name depends on your build:
|
||||||
|
|
||||||
```sh
|
```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
|
```text
|
||||||
threads, installed memory, operating system, architecture, and compiler. At the
|
--verbose print details for each workload
|
||||||
start of a normal run it also samples whole-system CPU activity for ten seconds,
|
--no-system-check skip the startup system activity sample
|
||||||
then reports average and peak background CPU use, available memory, the current
|
--upload upload the result without prompting
|
||||||
process count, and the OS kernel/build. Use `--no-system-check` to skip this
|
--noupload do not prompt or upload
|
||||||
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.
|
|
||||||
|
|
||||||
At the end it prints the composite scores and total benchmark duration, then asks
|
Before a normal run, fossbench samples system activity for ten seconds. It
|
||||||
whether to upload the result. Uploading is opt-in and anonymous by default; no
|
reports background CPU use, available memory, process count, and the OS kernel
|
||||||
account or API token is required. Pass `--upload` to upload without asking, or
|
or build. It does not collect process names or command lines.
|
||||||
`--noupload` to skip the prompt and never upload.
|
|
||||||
|
|
||||||
To associate results with your fossbench.net profile instead of submitting
|
Result uploads are optional and anonymous unless you provide an API token. To
|
||||||
anonymously, create an API token under Account -> Benchmark client API token
|
attach a result to your fossbench.net account, create a benchmark client token
|
||||||
and set it in the environment:
|
on the site and export it before running the benchmark:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
export FOSSBENCH_TOKEN=fb_your_token_here
|
export FOSSBENCH_TOKEN=fb_your_token_here
|
||||||
./dist/fossbench-linux-amd64 --upload
|
./dist/fossbench-linux-amd64 --upload
|
||||||
```
|
```
|
||||||
|
|
||||||
The token is never printed or logged by fossbench.
|
You can point a build at another server with a compile-time definition:
|
||||||
|
|
||||||
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:
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make CFLAGS='-O2 -Wall -Wextra -DFB_API_BASE_URL=\"https://bench.example.com\"'
|
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
|
| Workload | Measurement |
|
||||||
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.
|
| Integer math | 64-bit multiplication, division, shifts, and bit operations |
|
||||||
|
| Floating point | scalar double-precision arithmetic |
|
||||||
PPC64 is source-build support only. The commonly available PPC64 cross-build
|
| Prime numbers | sieve of Eratosthenes up to 2,000,000 |
|
||||||
libc requires POWER6 instructions and produces release binaries that fault on
|
| Extended instructions | 128-bit integer and floating point vector work |
|
||||||
the iMac G5's PowerPC 970. Build `linux-ppc64be` natively on the G5 so it uses
|
| Compression | LZ77 match finding over a generated 4 MiB corpus |
|
||||||
the compatible Arch POWER ELFv2 runtime.
|
| Encryption | ChaCha20 over a 1 MiB buffer |
|
||||||
|
| Physics | direct-sum gravity for 512 bodies |
|
||||||
Release binaries statically include OpenSSL. Linux releases dynamically use
|
| Sorting | in-place heapsort of one million 32-bit integers |
|
||||||
the system C library so DNS resolution can safely load the matching NSS
|
| Memory latency | dependent pointer chasing through a private cycle larger than cache |
|
||||||
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 <tag name>` 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.
|
|
||||||
|
|
||||||
## Scores
|
## Scores
|
||||||
|
|
||||||
Each workload receives a score relative to a reference rate:
|
A workload score compares its measured rate with a fixed reference rate:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
test score = 10000 * measured rate / reference rate
|
test score = 10000 * measured rate / reference rate
|
||||||
```
|
```
|
||||||
|
|
||||||
The single-core and multicore totals are weighted geometric means of the nine
|
The single-core and multicore totals are weighted geometric means. Both passes
|
||||||
test scores. Both passes use the same reference rates and weights, so their
|
use the same references and weights.
|
||||||
ratio gives a direct view of scaling across the machine's available cores.
|
|
||||||
|
|
||||||
| Test | Weight |
|
| Workload | Weight |
|
||||||
|---|---:|
|
|---|---:|
|
||||||
| Integer math | 20% |
|
| Integer math | 20% |
|
||||||
| Memory latency | 16% |
|
| Memory latency | 16% |
|
||||||
| Compression | 14% |
|
| Compression | 14% |
|
||||||
| Sorting | 12% |
|
| Sorting | 12% |
|
||||||
| Extended instructions | 11% |
|
| Extended instructions | 11% |
|
||||||
| Floating point math | 9% |
|
| Floating point | 9% |
|
||||||
| Encryption | 8% |
|
| Encryption | 8% |
|
||||||
| Prime numbers | 6% |
|
| Prime numbers | 6% |
|
||||||
| Physics | 4% |
|
| Physics | 4% |
|
||||||
|
|
||||||
The reference rates, weights, target score, workload sizes, calibration floor,
|
The benchmark profile lives in `src/app/benchmark.c`. Changing its reference
|
||||||
and repeat count are compile-time constants in `src/main.c`. Changing them
|
rates, weights, workload sizes, calibration time, or repeat count makes scores
|
||||||
creates a different benchmark profile, so scores from that build should not be
|
incompatible with the default build.
|
||||||
compared with scores from the default build.
|
|
||||||
|
|
||||||
Memory latency is displayed as nanoseconds per access, but its score uses the
|
Memory latency is printed in nanoseconds per access, though scoring uses the
|
||||||
underlying pointer-chase throughput. Latency results are sensitive to memory
|
underlying pointer-chase throughput. Memory placement and background operating
|
||||||
placement and operating-system activity, so some variation between runs is
|
system work can move this result between runs.
|
||||||
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.
|
|
||||||
|
|
||||||
## Tests
|
## Tests
|
||||||
|
|
||||||
The correctness suite checks all nine kernels against C reference
|
The correctness suite compares the kernels with C implementations, known
|
||||||
implementations, known answers, or invariants. Most checks also run concurrently
|
answers, or invariants. It covers the RFC 8439 ChaCha20 vector, prime counts,
|
||||||
on every available core to catch shared-state and reentrancy bugs.
|
sorting, physics momentum, pointer chasing, deterministic output, and concurrent
|
||||||
|
execution.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
make test
|
make test
|
||||||
```
|
```
|
||||||
|
|
||||||
The suite covers the RFC 8439 ChaCha20 test vector, prime counts, sorting output,
|
The command exits with a nonzero status when a check fails.
|
||||||
physics momentum, pointer-chase behavior, and deterministic results. It exits
|
|
||||||
with a nonzero status if any check fails.
|
|
||||||
|
|
||||||
## Source layout
|
## Repository layout
|
||||||
|
|
||||||
```text
|
```text
|
||||||
src/main.c portable benchmark driver and scoring
|
src/main.c command-line parsing and entrypoint
|
||||||
src/fossbench.S ARM64 kernels
|
src/app/benchmark.c workload setup, timing, scoring, and run flow
|
||||||
src/fossbench_x86_64.S x86-64 kernels
|
src/app/benchmark.h function used by main.c
|
||||||
src/fossbench_i386.S i386 (Pentium 4) kernels
|
src/app/upload.c API payload and network transport
|
||||||
src/fossbench_ppc32.c PPC32/PPC64 big-endian kernels
|
src/kernels/fossbench-arm64.S ARM64 kernels
|
||||||
src/fossbench_ppc32_ext.S optional PPC32 PS, VSX, and AltiVec kernels
|
src/kernels/fossbench-amd64.S x86-64 kernels
|
||||||
src/test_kernels.c correctness suite
|
src/kernels/fossbench-i386.S i386 kernels
|
||||||
Makefile native and cross-build targets
|
src/kernels/fossbench-powerpc.c PowerPC kernels
|
||||||
dist/ generated binaries
|
src/kernels/fossbench-ppc32-ext.S optional PPC32 extended kernels
|
||||||
|
src/test_kernels.c kernel correctness suite
|
||||||
```
|
```
|
||||||
|
|||||||
+1037
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,6 @@
|
|||||||
|
#ifndef FOSSBENCH_BENCHMARK_H
|
||||||
|
#define FOSSBENCH_BENCHMARK_H
|
||||||
|
|
||||||
|
int fossbench_run(int verbose, int upload_mode, int system_check);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -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
|
||||||
|
}
|
||||||
+2
-23
@@ -1,26 +1,5 @@
|
|||||||
/*
|
/* Backup certificates for uploads.
|
||||||
* Embedded CA trust bundle (fallback for upload_results() TLS).
|
* These come from Mozilla's CA list. */
|
||||||
*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#ifndef FB_CA_BUNDLE_H
|
#ifndef FB_CA_BUNDLE_H
|
||||||
#define FB_CA_BUNDLE_H
|
#define FB_CA_BUNDLE_H
|
||||||
|
|||||||
@@ -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
|
* 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
|
* 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
|
* runs on Linux (ELF), macOS (Mach-O) and the BSDs. The portable C driver in
|
||||||
@@ -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
|
* OS-independent: contains no syscalls, no libc calls, no relocations against
|
||||||
* external data. Every routine is a pure function of its arguments under the
|
* external data. Every routine is a pure function of its arguments under the
|
||||||
@@ -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,
|
* 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
|
* no libc calls and no external data relocations, so it assembles and runs
|
||||||
* unmodified under the plain i386 SysV (cdecl) ABI on Linux.
|
* unmodified under the plain i386 SysV (cdecl) ABI on Linux.
|
||||||
@@ -1,10 +1,4 @@
|
|||||||
/*
|
/* PowerPC kernel code. */
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
#include <stddef.h>
|
#include <stddef.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
@@ -80,9 +74,7 @@ static uint64_t fb_simd_scalar(uint64_t iters, void *memory)
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__powerpc64__)
|
#if defined(__powerpc64__)
|
||||||
/* The PowerPC 970 in every iMac G5 implements AltiVec. Using GCC's vector
|
/* The iMac G5 has AltiVec. */
|
||||||
* type here lets the compiler handle whichever PPC64 ELF ABI the system uses;
|
|
||||||
* both PPC64 ABIs differ from the PPC32 assembly convention below. */
|
|
||||||
typedef uint32_t fb_vec_u32 __attribute__((vector_size(16)));
|
typedef uint32_t fb_vec_u32 __attribute__((vector_size(16)));
|
||||||
|
|
||||||
uint64_t fb_simd(uint64_t iters, void *memory)
|
uint64_t fb_simd(uint64_t iters, void *memory)
|
||||||
@@ -110,8 +102,7 @@ uint64_t fb_simd(uint64_t iters, void *memory)
|
|||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
/* These are kept in fossbench_ppc32_ext.S so this translation unit, and thus
|
/* The optional PowerPC code is in the assembly file. */
|
||||||
* the executable's default code path, only requires baseline PPC32. */
|
|
||||||
extern void fb_simd_ps_kernel(uint64_t iters, void *memory);
|
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_vsx_kernel(uint64_t iters, void *memory);
|
||||||
extern void fb_simd_altivec_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)
|
static fb_simd_kernel detect_simd_kernel(void)
|
||||||
{
|
{
|
||||||
/* Linux exposes these in AT_HWCAP on both 32- and 64-bit PowerPC.
|
/* Linux tells us which PowerPC features are available. */
|
||||||
* Spell out the ABI values instead of depending on kernel-only headers. */
|
|
||||||
#if defined(__linux__) && defined(AT_HWCAP)
|
#if defined(__linux__) && defined(AT_HWCAP)
|
||||||
const unsigned long hwcap = getauxval(AT_HWCAP);
|
const unsigned long hwcap = getauxval(AT_HWCAP);
|
||||||
const unsigned long has_altivec = 0x10000000UL;
|
const unsigned long has_altivec = 0x10000000UL;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/* Optional PPC32 extended-instruction kernels. No instruction in this file
|
/* 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. */
|
* Arguments use the PPC32 ABI: iters in r3:r4 and memory in r5. */
|
||||||
|
|
||||||
.text
|
.text
|
||||||
+8
-1460
File diff suppressed because it is too large
Load Diff
+28
-65
@@ -1,21 +1,4 @@
|
|||||||
/*
|
/* The actual tests. */
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -40,14 +23,7 @@ extern uint64_t fb_chase(void **ptrs, uint64_t steps);
|
|||||||
static int failures = 0;
|
static int failures = 0;
|
||||||
static int checks = 0;
|
static int checks = 0;
|
||||||
|
|
||||||
/*
|
/* Concurrency plumbing. */
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
static pthread_mutex_t io_lock = PTHREAD_MUTEX_INITIALIZER;
|
static pthread_mutex_t io_lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
static __thread int fb_primary = 1;
|
static __thread int fb_primary = 1;
|
||||||
static long fb_ncores = 1;
|
static long fb_ncores = 1;
|
||||||
@@ -64,14 +40,14 @@ static void ok(const char *what, int cond)
|
|||||||
failures++;
|
failures++;
|
||||||
}
|
}
|
||||||
} else if (!cond) {
|
} else if (!cond) {
|
||||||
/* a secondary core disagrees: surface it explicitly */
|
/* Show if another thread failed. */
|
||||||
printf(" [FAIL] %s (concurrent core)\n", what);
|
printf(" [FAIL] %s (concurrent core)\n", what);
|
||||||
failures++;
|
failures++;
|
||||||
}
|
}
|
||||||
pthread_mutex_unlock(&io_lock);
|
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, ...)
|
static void note(const char *fmt, ...)
|
||||||
{
|
{
|
||||||
va_list ap;
|
va_list ap;
|
||||||
@@ -85,8 +61,7 @@ static void note(const char *fmt, ...)
|
|||||||
pthread_mutex_unlock(&io_lock);
|
pthread_mutex_unlock(&io_lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Run `check` on every core simultaneously. The main thread is the primary;
|
/* Run the same check on every core. */
|
||||||
* fb_ncores-1 workers run the same check as silent secondaries. */
|
|
||||||
static void *fb_worker(void *arg)
|
static void *fb_worker(void *arg)
|
||||||
{
|
{
|
||||||
void (*check)(void) = *(void (**)(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++)
|
for (i = 0; i < spawned; i++)
|
||||||
pthread_join(th[i], NULL);
|
pthread_join(th[i], NULL);
|
||||||
free(th);
|
free(th);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- reference implementations ---------- */
|
/* Small C versions used for comparison. */
|
||||||
|
|
||||||
static uint64_t ref_prime_count(uint64_t limit)
|
static uint64_t ref_prime_count(uint64_t limit)
|
||||||
{
|
{
|
||||||
@@ -137,9 +112,7 @@ static uint64_t ref_prime_count(uint64_t limit)
|
|||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* A textbook scalar ChaCha20 block function, used both to anchor against the
|
/* Basic ChaCha20 used to check the kernel. */
|
||||||
* 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. */
|
|
||||||
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
||||||
|
|
||||||
static void ref_chacha_block(uint32_t out_words[16], const uint8_t key[32],
|
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];
|
out_words[i] = x[i] + s[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------- checks ---------- */
|
/* The actual tests. */
|
||||||
|
|
||||||
static void check_int(void)
|
static void check_int(void)
|
||||||
{
|
{
|
||||||
/* determinism and non-triviality: the checksum must be stable and
|
/* The actual tests. */
|
||||||
* must actually change with the iteration count */
|
|
||||||
uint64_t a = fb_int_math(1000);
|
uint64_t a = fb_int_math(1000);
|
||||||
uint64_t b = fb_int_math(1000);
|
uint64_t b = fb_int_math(1000);
|
||||||
uint64_t c = fb_int_math(2000);
|
uint64_t c = fb_int_math(2000);
|
||||||
@@ -217,7 +189,7 @@ static void check_primes(void)
|
|||||||
note(" primes < %d: got %llu, expected %llu\n",
|
note(" primes < %d: got %llu, expected %llu\n",
|
||||||
LIM, (unsigned long long)got, (unsigned long long)ref);
|
LIM, (unsigned long long)got, (unsigned long long)ref);
|
||||||
ok("primes matches reference sieve", got == 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);
|
ok("primes < 2 == 0", fb_primes(2, sieve) == 0);
|
||||||
free(sieve);
|
free(sieve);
|
||||||
}
|
}
|
||||||
@@ -244,8 +216,7 @@ static void check_compress(void)
|
|||||||
uint64_t incompressible, compressible;
|
uint64_t incompressible, compressible;
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
/* genuinely incompressible data (splitmix64 output): with no matches
|
/* Random data should not compress much. */
|
||||||
* to exploit, an LZ coder's output must be at least the input size */
|
|
||||||
{
|
{
|
||||||
uint64_t st = 0x1234567890abcdefULL;
|
uint64_t st = 0x1234567890abcdefULL;
|
||||||
for (i = 0; i < N; i++) {
|
for (i = 0; i < N; i++) {
|
||||||
@@ -257,7 +228,7 @@ static void check_compress(void)
|
|||||||
}
|
}
|
||||||
incompressible = fb_compress(src, N, ht);
|
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);
|
memset(src, 0, N);
|
||||||
compressible = fb_compress(src, N, ht);
|
compressible = fb_compress(src, N, ht);
|
||||||
|
|
||||||
@@ -276,9 +247,7 @@ static void check_crypto(void)
|
|||||||
uint8_t key[32];
|
uint8_t key[32];
|
||||||
size_t i;
|
size_t i;
|
||||||
|
|
||||||
/* (1) anchor the scalar reference to the RFC 8439 s.2.3.2 vector:
|
/* Check the C version with the RFC example. */
|
||||||
* key = 00,01,...,1f; counter = 1; nonce = 00,00,00,09,...,4a,...
|
|
||||||
* serialised keystream begins 10 f1 e7 e4. */
|
|
||||||
{
|
{
|
||||||
uint32_t w[16];
|
uint32_t w[16];
|
||||||
uint8_t rnonce[12] = {0,0,0,9, 0,0,0,0x4a, 0,0,0,0};
|
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);
|
ks0[2] == 0xe7 && ks0[3] == 0xe4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (2) validate the NEON kernel against that reference. The kernel
|
/* Compare the kernel with the C version. */
|
||||||
* hardwires nonce = 0 and starts the block counter at 0, so we
|
|
||||||
* compare its keystream to the reference block-for-block. */
|
|
||||||
{
|
{
|
||||||
uint8_t buf[128];
|
uint8_t buf[128];
|
||||||
uint8_t zero_nonce[12] = {0};
|
uint8_t zero_nonce[12] = {0};
|
||||||
@@ -307,7 +274,7 @@ static void check_crypto(void)
|
|||||||
|
|
||||||
for (i = 0; i < 32; i++)
|
for (i = 0; i < 32; i++)
|
||||||
key[i] = (uint8_t)(i * 5 + 1);
|
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);
|
fb_chacha20(buf, sizeof buf, key, 1);
|
||||||
|
|
||||||
ref_chacha_block(ref0, key, 0, zero_nonce);
|
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);
|
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];
|
uint8_t plain[128], work[128], k2[32];
|
||||||
for (i = 0; i < 128; i++)
|
for (i = 0; i < 128; i++)
|
||||||
@@ -345,18 +312,17 @@ static void check_crypto(void)
|
|||||||
|
|
||||||
static void check_physics(void)
|
static void check_physics(void)
|
||||||
{
|
{
|
||||||
/* two equal masses released from rest must accelerate toward each
|
/* The two bodies should move toward each other. */
|
||||||
* other: symmetric, momentum-conserving, and bounded. */
|
|
||||||
double bodies[2 * 8] = {0};
|
double bodies[2 * 8] = {0};
|
||||||
double total_p;
|
double total_p;
|
||||||
|
|
||||||
bodies[0] = -1.0; bodies[3] = 1.0; /* body 0 at x=-1, mass 1 */
|
bodies[0] = -1.0; bodies[3] = 1.0; /* First body. */
|
||||||
bodies[8] = 1.0; bodies[11] = 1.0; /* body 1 at x=+1, mass 1 */
|
bodies[8] = 1.0; bodies[11] = 1.0; /* Second body. */
|
||||||
|
|
||||||
fb_physics(bodies, 2, 200);
|
fb_physics(bodies, 2, 200);
|
||||||
|
|
||||||
/* velocities must be equal and opposite (Newton's third law) */
|
/* The velocities should cancel out. */
|
||||||
total_p = bodies[4] + bodies[12]; /* vx0 + vx1 */
|
total_p = bodies[4] + bodies[12]; /* Add both x velocities. */
|
||||||
note(" 2-body: vx0=%.6f vx1=%.6f (sum should be ~0)\n",
|
note(" 2-body: vx0=%.6f vx1=%.6f (sum should be ~0)\n",
|
||||||
bodies[4], bodies[12]);
|
bodies[4], bodies[12]);
|
||||||
ok("physics conserves momentum", fabs(total_p) < 1e-9);
|
ok("physics conserves momentum", fabs(total_p) < 1e-9);
|
||||||
@@ -396,13 +362,12 @@ static void check_sort(void)
|
|||||||
s = fb_sort(a, N);
|
s = fb_sort(a, N);
|
||||||
ok("sort produces sorted output", is_sorted(a, N));
|
ok("sort produces sorted output", is_sorted(a, N));
|
||||||
|
|
||||||
/* multiset is preserved: sort the reference with the C library and
|
/* Compare it with the C library sort. */
|
||||||
* compare element by element */
|
|
||||||
qsort(b, N, sizeof(uint32_t), cmp_u32);
|
qsort(b, N, sizeof(uint32_t), cmp_u32);
|
||||||
ok("sort is a permutation of the input",
|
ok("sort is a permutation of the input",
|
||||||
memcmp(a, b, N * sizeof(uint32_t)) == 0);
|
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);
|
uint64_t s2 = fb_sort(a, N);
|
||||||
ok("sort is idempotent on sorted data",
|
ok("sort is idempotent on sorted data",
|
||||||
@@ -416,8 +381,7 @@ static void check_sort(void)
|
|||||||
|
|
||||||
static void check_chase(void)
|
static void check_chase(void)
|
||||||
{
|
{
|
||||||
/* build a tiny 4-node cycle by hand and confirm the walk returns to
|
/* Make a small pointer loop. */
|
||||||
* the start after exactly `n` steps (offset 0 relative to entry) */
|
|
||||||
void *nodes[4];
|
void *nodes[4];
|
||||||
|
|
||||||
nodes[0] = &nodes[1];
|
nodes[0] = &nodes[1];
|
||||||
@@ -425,11 +389,10 @@ static void check_chase(void)
|
|||||||
nodes[2] = &nodes[3];
|
nodes[2] = &nodes[3];
|
||||||
nodes[3] = &nodes[0];
|
nodes[3] = &nodes[0];
|
||||||
|
|
||||||
/* 4 hops from &nodes[0] returns to &nodes[0]; fb_chase returns the
|
/* Four hops should return to the start. */
|
||||||
* final pointer minus the starting pointer, so a full loop gives 0 */
|
|
||||||
ok("chase completes a full cycle", fb_chase(nodes, 4) == 0);
|
ok("chase completes a full cycle", fb_chase(nodes, 4) == 0);
|
||||||
ok("chase(0) is zero", fb_chase(nodes, 0) == 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",
|
ok("chase single hop offset",
|
||||||
fb_chase(nodes, 1) == (uint64_t)((char *)&nodes[1] - (char *)&nodes[0]));
|
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("Encryption:\n"); parallel(check_crypto);
|
||||||
printf("Physics:\n"); parallel(check_physics);
|
printf("Physics:\n"); parallel(check_physics);
|
||||||
printf("Sorting:\n"); parallel(check_sort);
|
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("Single-Threaded (chase):\n"); check_chase();
|
||||||
|
|
||||||
printf("\n=================================\n");
|
printf("\n=================================\n");
|
||||||
|
|||||||
Reference in New Issue
Block a user