initial commit for alpha release and testing on MacOS
This commit is contained in:
@@ -0,0 +1,152 @@
|
|||||||
|
# fossmark - multi-core CPU benchmark
|
||||||
|
#
|
||||||
|
# The assembly kernels are architecture-specific:
|
||||||
|
# src/fossmark.S AArch64 (ARM64)
|
||||||
|
# src/fossmark_x86_64.S x86-64 (AMD64)
|
||||||
|
# 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/fossmark-linux-arm64, dist/fossmark-linux-amd64.
|
||||||
|
#
|
||||||
|
# Common targets:
|
||||||
|
# make build for the host arch (dist/fossmark-<os>-<arch>)
|
||||||
|
# make linux-arm64 build the Linux/ARM64 binary
|
||||||
|
# make linux-amd64 build the Linux/AMD64 binary
|
||||||
|
# make macos-arm64 build the macOS/ARM64 binary
|
||||||
|
# make macos-amd64 build the macOS/AMD64 binary
|
||||||
|
# make all build both Linux binaries
|
||||||
|
# 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"
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
|
||||||
|
CC ?= cc
|
||||||
|
CFLAGS ?= -O2 -Wall -Wextra
|
||||||
|
LDLIBS ?= -lm
|
||||||
|
# The driver spreads each workload across all cores with pthreads.
|
||||||
|
PTHREAD := -pthread
|
||||||
|
|
||||||
|
DIST := dist
|
||||||
|
DRIVER := src/main.c
|
||||||
|
ASM_ARM64 := src/fossmark.S
|
||||||
|
ASM_AMD64 := src/fossmark_x86_64.S
|
||||||
|
|
||||||
|
# ---- host detection: normalise `uname -m` to our arch names ----
|
||||||
|
HOST_ARCH := $(shell uname -m)
|
||||||
|
ifneq (,$(filter aarch64 arm64,$(HOST_ARCH)))
|
||||||
|
HOST_ARCHNAME := arm64
|
||||||
|
HOST_ASM := $(ASM_ARM64)
|
||||||
|
else ifneq (,$(filter x86_64 amd64,$(HOST_ARCH)))
|
||||||
|
HOST_ARCHNAME := amd64
|
||||||
|
HOST_ASM := $(ASM_AMD64)
|
||||||
|
else
|
||||||
|
HOST_ARCHNAME := $(HOST_ARCH)
|
||||||
|
HOST_ASM := $(ASM_ARM64)
|
||||||
|
endif
|
||||||
|
|
||||||
|
# ---- host OS name for the native binary ----
|
||||||
|
UNAME_S := $(shell uname -s)
|
||||||
|
ifeq ($(UNAME_S),Linux)
|
||||||
|
OSNAME := linux
|
||||||
|
else ifeq ($(UNAME_S),Darwin)
|
||||||
|
OSNAME := macos
|
||||||
|
else ifeq ($(OS),Windows_NT)
|
||||||
|
OSNAME := windows
|
||||||
|
else
|
||||||
|
OSNAME := $(shell uname -s | tr '[:upper:]' '[:lower:]')
|
||||||
|
endif
|
||||||
|
|
||||||
|
# ---- per-target compilers: native cc if the host matches, else a cross gcc ----
|
||||||
|
ifeq ($(HOST_ARCHNAME),arm64)
|
||||||
|
CC_ARM64 ?= $(CC)
|
||||||
|
else
|
||||||
|
CC_ARM64 ?= aarch64-linux-gnu-gcc
|
||||||
|
endif
|
||||||
|
ifeq ($(HOST_ARCHNAME),amd64)
|
||||||
|
CC_AMD64 ?= $(CC)
|
||||||
|
else
|
||||||
|
CC_AMD64 ?= x86_64-linux-gnu-gcc
|
||||||
|
endif
|
||||||
|
CC_MACOS_ARM64 ?= $(CC)
|
||||||
|
CC_MACOS_AMD64 ?= $(CC)
|
||||||
|
|
||||||
|
NATIVE_BIN := $(DIST)/fossmark-$(OSNAME)-$(HOST_ARCHNAME)
|
||||||
|
|
||||||
|
# `make` with no target builds the host binary, as before.
|
||||||
|
.DEFAULT_GOAL := native
|
||||||
|
.PHONY: all native linux-arm64 linux-amd64 macos-arm64 macos-amd64 bench test clean
|
||||||
|
|
||||||
|
# `make all` builds both Linux binaries.
|
||||||
|
all: linux-arm64 linux-amd64
|
||||||
|
|
||||||
|
# `make native` (and bare `make`) build for whatever host you are on.
|
||||||
|
native: $(NATIVE_BIN)
|
||||||
|
|
||||||
|
linux-arm64: $(DIST)/fossmark-linux-arm64
|
||||||
|
linux-amd64: $(DIST)/fossmark-linux-amd64
|
||||||
|
macos-arm64: $(DIST)/fossmark-macos-arm64
|
||||||
|
macos-amd64: $(DIST)/fossmark-macos-amd64
|
||||||
|
|
||||||
|
$(DIST)/fossmark-linux-arm64: $(DRIVER) $(ASM_ARM64) | $(DIST)
|
||||||
|
$(CC_ARM64) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(ASM_ARM64) $(LDLIBS)
|
||||||
|
@echo "built $@"
|
||||||
|
|
||||||
|
$(DIST)/fossmark-linux-amd64: $(DRIVER) $(ASM_AMD64) | $(DIST)
|
||||||
|
$(CC_AMD64) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS)
|
||||||
|
@echo "built $@"
|
||||||
|
|
||||||
|
$(DIST)/fossmark-macos-arm64: $(DRIVER) $(ASM_ARM64) | $(DIST)
|
||||||
|
$(CC_MACOS_ARM64) -arch arm64 $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(ASM_ARM64) $(LDLIBS)
|
||||||
|
@echo "built $@"
|
||||||
|
|
||||||
|
$(DIST)/fossmark-macos-amd64: $(DRIVER) $(ASM_AMD64) | $(DIST)
|
||||||
|
$(CC_MACOS_AMD64) -arch x86_64 $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS)
|
||||||
|
@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.
|
||||||
|
ifeq ($(OSNAME)-$(HOST_ARCHNAME),linux-arm64)
|
||||||
|
NATIVE_HAS_RULE := yes
|
||||||
|
endif
|
||||||
|
ifeq ($(OSNAME)-$(HOST_ARCHNAME),linux-amd64)
|
||||||
|
NATIVE_HAS_RULE := yes
|
||||||
|
endif
|
||||||
|
ifeq ($(OSNAME)-$(HOST_ARCHNAME),macos-arm64)
|
||||||
|
NATIVE_HAS_RULE := yes
|
||||||
|
endif
|
||||||
|
ifeq ($(OSNAME)-$(HOST_ARCHNAME),macos-amd64)
|
||||||
|
NATIVE_HAS_RULE := yes
|
||||||
|
endif
|
||||||
|
ifneq ($(NATIVE_HAS_RULE),yes)
|
||||||
|
$(NATIVE_BIN): $(DRIVER) $(HOST_ASM) | $(DIST)
|
||||||
|
$(CC) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(HOST_ASM) $(LDLIBS)
|
||||||
|
@echo "built $@"
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(DIST):
|
||||||
|
mkdir -p $(DIST)
|
||||||
|
|
||||||
|
# Build for the host and run the benchmark.
|
||||||
|
bench: $(NATIVE_BIN)
|
||||||
|
./$(NATIVE_BIN)
|
||||||
|
|
||||||
|
# Build and run the kernel correctness tests for the host arch.
|
||||||
|
test: | $(DIST)
|
||||||
|
$(CC) $(CFLAGS) $(PTHREAD) -o $(DIST)/test_kernels src/test_kernels.c $(HOST_ASM) $(LDLIBS)
|
||||||
|
./$(DIST)/test_kernels
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -rf $(DIST)
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# Fossmark
|
||||||
|
|
||||||
|
A single-threaded CPU benchmark for ARM64 (AArch64), with the numeric kernels
|
||||||
|
hand-written in assembly and a small portable C driver to run and score them.
|
||||||
|
|
||||||
|
## What it measures
|
||||||
|
|
||||||
|
Nine workloads, each a tight assembly kernel:
|
||||||
|
|
||||||
|
| # | Test | What it exercises |
|
||||||
|
|---|-------------------------|--------------------------------------------------------------|
|
||||||
|
| 1 | Integer Math | 64-bit ALU: `madd`, `umulh`/`smulh`, `udiv`/`sdiv`, bit ops |
|
||||||
|
| 2 | Floating Point Math | scalar double: `fmadd`, `fdiv`, `fsqrt` |
|
||||||
|
| 3 | Prime Numbers | sieve of Eratosthenes to 2,000,000 (strided memory + ALU) |
|
||||||
|
| 4 | Extended Instructions | NEON/ASIMD: 128-bit integer, widening, table, float vectors |
|
||||||
|
| 5 | Compression | LZ77 match-finder over a 4 MiB corpus (branchy, cache probe) |
|
||||||
|
| 6 | Encryption | ChaCha20, 20 rounds, NEON, over 1 MiB |
|
||||||
|
| 7 | Physics | 512-body direct-summation gravity, double precision |
|
||||||
|
| 8 | Sorting | in-place heapsort of 1M `uint32` (branch + cache stress) |
|
||||||
|
| 9 | Single-Threaded | dependent-load pointer chase over 16 MiB (memory latency) |
|
||||||
|
|
||||||
|
Each test auto-calibrates its iteration count until it runs long enough to be
|
||||||
|
timed reliably, then reports the best of several runs (the run least disturbed
|
||||||
|
by the OS scheduler). Every kernel returns a checksum that the driver verifies
|
||||||
|
across runs, so a miscompiled or non-deterministic kernel is caught rather than
|
||||||
|
silently mis-scored.
|
||||||
|
|
||||||
|
## Scoring
|
||||||
|
|
||||||
|
Each test's raw rate is normalised against a **reference machine** into a
|
||||||
|
unitless score, and the overall is a **weighted geometric mean** of those
|
||||||
|
scores:
|
||||||
|
|
||||||
|
```
|
||||||
|
S_i = TARGET * (rate_i / REF_i) (per-test score)
|
||||||
|
Overall = TARGET * exp( Σ w_i·ln(rate_i/REF_i) / Σ w_i ) (weighted geo. mean)
|
||||||
|
```
|
||||||
|
|
||||||
|
The reference rates are the tuning machine's own rates, and `TARGET` is 20000,
|
||||||
|
so that machine scores ~20000 on every test and overall. Scaling is linear in
|
||||||
|
performance: a machine half as fast scores ~10000, one 10× slower ~2000, and a
|
||||||
|
future machine twice as fast ~40000 — so there is unbounded room both below and
|
||||||
|
above the reference.
|
||||||
|
|
||||||
|
The weights reflect each test's influence on **everyday, common-workload user
|
||||||
|
experience** — integer/general-purpose throughput and memory-latency-bound
|
||||||
|
responsiveness matter most; specialised floating-point and physics matter
|
||||||
|
least. This mirrors the weighted, integer-dominant approach of mainstream
|
||||||
|
suites such as Geekbench 6 (which splits integer/FP roughly 65/35 and combines
|
||||||
|
real-world workloads with a weighted mean).
|
||||||
|
|
||||||
|
| Test | Weight |
|
||||||
|
|---|---:|
|
||||||
|
| Integer Math | 20% |
|
||||||
|
| Single-Threaded | 16% |
|
||||||
|
| Compression | 14% |
|
||||||
|
| Sorting | 12% |
|
||||||
|
| Extended Instructions | 11% |
|
||||||
|
| Floating Point | 9% |
|
||||||
|
| Encryption | 8% |
|
||||||
|
| Prime Numbers | 6% |
|
||||||
|
| Physics | 4% |
|
||||||
|
|
||||||
|
Everything above is configurable via `#define`s at the top of `src/main.c`:
|
||||||
|
`FM_TARGET_SCORE`, the nine `FM_REF_*` reference rates, and the nine
|
||||||
|
`FM_WEIGHT_*` weights. Weights are relative — the code normalises by their sum,
|
||||||
|
so you can change one without rebalancing the rest. To re-baseline for a
|
||||||
|
different reference machine, set each `FM_REF_*` to that machine's measured
|
||||||
|
rate.
|
||||||
|
|
||||||
|
Note: the pointer-chase (Single-Threaded) test measures raw memory latency and
|
||||||
|
is the noisiest to sample, so the overall typically varies ~1–2% run to run.
|
||||||
|
|
||||||
|
## "Runs on all operating systems"
|
||||||
|
|
||||||
|
The **assembly is** OS-independent: `src/fossmark.S` contains no system calls,
|
||||||
|
no libc calls, and no external relocations. Every routine is a pure function of
|
||||||
|
its arguments under the AAPCS64 calling convention, so the same source
|
||||||
|
assembles and runs correctly on Linux (ELF), macOS (Mach-O), Windows (COFF) and
|
||||||
|
the BSDs. It avoids `x18` (reserved on Darwin/Windows) and the `v8`–`v15`
|
||||||
|
callee-saved vector bank.
|
||||||
|
|
||||||
|
A single *binary* that runs everywhere is not possible — Linux, macOS and
|
||||||
|
Windows use incompatible executable formats and system-call ABIs. So the
|
||||||
|
portable C driver (`src/main.c`) supplies the per-OS parts (timing, memory,
|
||||||
|
I/O), and you build one binary per platform. The Linux build is named
|
||||||
|
`fossmark-linux-arm64`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make # builds dist/fossmark-<os>-<arch> for the host
|
||||||
|
make linux-arm64
|
||||||
|
make linux-amd64
|
||||||
|
make macos-arm64
|
||||||
|
make macos-amd64
|
||||||
|
make bench # build and run the benchmark
|
||||||
|
make test # build and run the kernel correctness tests
|
||||||
|
```
|
||||||
|
|
||||||
|
Both macOS targets can be built on either Apple Silicon or Intel Macs; Apple
|
||||||
|
Clang selects the requested architecture with `-arch`. They produce
|
||||||
|
`dist/fossmark-macos-arm64` and `dist/fossmark-macos-amd64`, respectively.
|
||||||
|
|
||||||
|
Or by hand:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cc -O2 src/main.c src/fossmark.S -o dist/fossmark-linux-arm64 -lm
|
||||||
|
```
|
||||||
|
|
||||||
|
On macOS the same command produces a native binary (name it
|
||||||
|
`fossmark-macos-arm64`); on Windows use `clang` from the LLVM/MSVC toolchain.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
`src/test_kernels.c` is a standalone harness that validates each kernel against
|
||||||
|
an independent reference or invariant — the sieve against a C reference sieve,
|
||||||
|
the NEON ChaCha20 against a scalar reference anchored to the RFC 8439
|
||||||
|
known-answer vector, the sort against `qsort`, the N-body step against
|
||||||
|
conservation of momentum, and so on. It exits non-zero if any check fails.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
make test
|
||||||
|
```
|
||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
+942
@@ -0,0 +1,942 @@
|
|||||||
|
/*
|
||||||
|
* fossmark.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
|
||||||
|
* AAPCS64 procedure call standard, so this source assembles and runs correctly
|
||||||
|
* on Linux (ELF), macOS (Mach-O), Windows (COFF), and the BSDs.
|
||||||
|
*
|
||||||
|
* x18 is never used: it is the platform register on Darwin and Windows.
|
||||||
|
* v8-v15 are never used: only their low 64 bits are callee-saved, which makes
|
||||||
|
* them a trap for 128-bit vector code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.arch armv8-a
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
# define SYM(name) _##name
|
||||||
|
#else
|
||||||
|
# define SYM(name) name
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__ELF__)
|
||||||
|
# define FN_BEGIN(name) .p2align 4 ; .globl SYM(name) ; .type SYM(name), %function ; SYM(name):
|
||||||
|
# define FN_END(name) .size SYM(name), . - SYM(name)
|
||||||
|
#else
|
||||||
|
# define FN_BEGIN(name) .p2align 4 ; .globl SYM(name) ; SYM(name):
|
||||||
|
# define FN_END(name)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* Materialise a 64-bit constant without a literal pool, so no relocations and
|
||||||
|
* no .ltorg placement worries across object formats. */
|
||||||
|
#define MOV64(reg, val) \
|
||||||
|
movz reg, #((val) & 0xffff) ;\
|
||||||
|
movk reg, #(((val) >> 16) & 0xffff), lsl #16 ;\
|
||||||
|
movk reg, #(((val) >> 32) & 0xffff), lsl #32 ;\
|
||||||
|
movk reg, #(((val) >> 48) & 0xffff), lsl #48
|
||||||
|
|
||||||
|
.text
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_int_math(uint64_t iters)
|
||||||
|
*
|
||||||
|
* Four largely independent accumulator chains to expose instruction-level
|
||||||
|
* parallelism, mixed with high-latency serialising ops (udiv/sdiv) and the
|
||||||
|
* bit-manipulation instructions. Returns a checksum so the compiler and the
|
||||||
|
* driver cannot elide the work.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_int_math)
|
||||||
|
cbz x0, .Lim_zero
|
||||||
|
|
||||||
|
MOV64(x1, 0x9E3779B97F4A7C15) /* a */
|
||||||
|
MOV64(x2, 0xBF58476D1CE4E5B9) /* b */
|
||||||
|
MOV64(x3, 0x94D049BB133111EB) /* c */
|
||||||
|
MOV64(x4, 0x2545F4914F6CDD1D) /* d */
|
||||||
|
MOV64(x5, 0x00000000DEADBEEF) /* odd multiplier, never zero */
|
||||||
|
mov x6, x0 /* trip count */
|
||||||
|
|
||||||
|
.Lim_loop:
|
||||||
|
/* four independent multiply-accumulate chains */
|
||||||
|
madd x1, x1, x5, x2
|
||||||
|
madd x2, x2, x5, x3
|
||||||
|
madd x3, x3, x5, x4
|
||||||
|
madd x4, x4, x5, x1
|
||||||
|
|
||||||
|
/* cross-mix with shifts and logic ops (free shifter operands) */
|
||||||
|
eor x1, x1, x3, lsr #29
|
||||||
|
eor x2, x2, x4, lsl #17
|
||||||
|
eor x3, x3, x1, ror #31
|
||||||
|
bic x4, x4, x2, asr #7
|
||||||
|
|
||||||
|
/* wide multiplies: umulh/smulh are the long-latency multiplier path */
|
||||||
|
umulh x9, x1, x3
|
||||||
|
smulh x10, x2, x4
|
||||||
|
add x1, x1, x9
|
||||||
|
add x2, x2, x10
|
||||||
|
|
||||||
|
/* bit manipulation */
|
||||||
|
rbit x11, x1
|
||||||
|
clz x12, x2
|
||||||
|
rev x13, x3
|
||||||
|
eor x4, x4, x11
|
||||||
|
add x4, x4, x12
|
||||||
|
eor x1, x1, x13
|
||||||
|
|
||||||
|
/* division: fully serialising, ~10-20 cycle latency, not pipelined */
|
||||||
|
orr x14, x5, #1 /* guarantee a non-zero divisor */
|
||||||
|
udiv x15, x1, x14
|
||||||
|
sdiv x16, x2, x14
|
||||||
|
msub x3, x15, x14, x3
|
||||||
|
add x4, x4, x16
|
||||||
|
|
||||||
|
/* bitfield ops */
|
||||||
|
ror x2, x2, #11
|
||||||
|
extr x1, x1, x2, #23
|
||||||
|
|
||||||
|
subs x6, x6, #1
|
||||||
|
b.ne .Lim_loop
|
||||||
|
|
||||||
|
eor x0, x1, x2
|
||||||
|
eor x0, x0, x3
|
||||||
|
eor x0, x0, x4
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lim_zero:
|
||||||
|
mov x0, xzr
|
||||||
|
ret
|
||||||
|
FN_END(fm_int_math)
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_fp_math(uint64_t iters)
|
||||||
|
*
|
||||||
|
* Double-precision scalar FP. Four fmadd chains cover the pipelined
|
||||||
|
* multiply-add path; fdiv and fsqrt cover the non-pipelined divide/sqrt unit,
|
||||||
|
* which is usually the real differentiator between cores.
|
||||||
|
* Returns the result bit-cast to u64.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_fp_math)
|
||||||
|
cbz x0, .Lfp_zero
|
||||||
|
mov x6, x0
|
||||||
|
|
||||||
|
/* Constants come from a table rather than fmov immediates: the AArch64
|
||||||
|
* 8-bit FP immediate can only encode a narrow set of values, and
|
||||||
|
* several of the ones we want fall outside it. */
|
||||||
|
adr x7, .Lfp_consts
|
||||||
|
ldp d0, d1, [x7] /* a = 1.5, b = 2.5 */
|
||||||
|
ldp d2, d3, [x7, #16] /* c = 3.5, d = 0.5 */
|
||||||
|
ldp d4, d5, [x7, #32] /* mul, small addend */
|
||||||
|
ldp d6, d7, [x7, #48] /* 2.0, 1.0 */
|
||||||
|
|
||||||
|
.Lfp_loop:
|
||||||
|
/* four independent fused multiply-add chains */
|
||||||
|
fmadd d0, d0, d4, d5
|
||||||
|
fmadd d1, d1, d4, d5
|
||||||
|
fmadd d2, d2, d4, d5
|
||||||
|
fmadd d3, d3, d4, d5
|
||||||
|
|
||||||
|
/* keep the accumulators bounded so they never reach inf/NaN */
|
||||||
|
fmin d0, d0, d6
|
||||||
|
fmin d1, d1, d6
|
||||||
|
fmin d2, d2, d6
|
||||||
|
fmin d3, d3, d6
|
||||||
|
|
||||||
|
/* square root: long latency, low throughput */
|
||||||
|
fsqrt d16, d0
|
||||||
|
fsqrt d17, d1
|
||||||
|
fadd d2, d2, d16
|
||||||
|
fadd d3, d3, d17
|
||||||
|
|
||||||
|
/* divide: the other long-latency unit */
|
||||||
|
fadd d18, d2, d7 /* divisor >= 1, never zero */
|
||||||
|
fdiv d19, d7, d18
|
||||||
|
fadd d0, d0, d19
|
||||||
|
|
||||||
|
fadd d20, d3, d7
|
||||||
|
fdiv d21, d7, d20
|
||||||
|
fadd d1, d1, d21
|
||||||
|
|
||||||
|
/* abs/neg/compare-select: cheap ops to balance the mix */
|
||||||
|
fabs d2, d2
|
||||||
|
fneg d22, d3
|
||||||
|
fabs d3, d22
|
||||||
|
fmax d3, d3, d7
|
||||||
|
|
||||||
|
subs x6, x6, #1
|
||||||
|
b.ne .Lfp_loop
|
||||||
|
|
||||||
|
fadd d0, d0, d1
|
||||||
|
fadd d2, d2, d3
|
||||||
|
fadd d0, d0, d2
|
||||||
|
fmov x0, d0
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lfp_zero:
|
||||||
|
mov x0, xzr
|
||||||
|
ret
|
||||||
|
FN_END(fm_fp_math)
|
||||||
|
|
||||||
|
.p2align 4
|
||||||
|
.Lfp_consts:
|
||||||
|
.double 1.5, 2.5
|
||||||
|
.double 3.5, 0.5
|
||||||
|
.double 1.0625, 0.0009765625
|
||||||
|
.double 2.0, 1.0
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_primes(uint64_t limit, uint8_t *sieve)
|
||||||
|
*
|
||||||
|
* Sieve of Eratosthenes over [0, limit). The caller supplies `limit` bytes of
|
||||||
|
* scratch; this routine clears it itself, so the clearing pass is part of the
|
||||||
|
* measured work (as it would be in any real use). Returns the prime count.
|
||||||
|
*
|
||||||
|
* Strided stores over a buffer larger than L1 make this a memory-hierarchy
|
||||||
|
* test as much as an arithmetic one.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_primes)
|
||||||
|
cmp x0, #2
|
||||||
|
b.lo .Lpr_none
|
||||||
|
|
||||||
|
mov x2, x1 /* sieve base */
|
||||||
|
mov x3, x0 /* limit */
|
||||||
|
|
||||||
|
/* zero the sieve, 32 bytes per iteration */
|
||||||
|
movi v0.16b, #0
|
||||||
|
mov x4, xzr
|
||||||
|
and x5, x3, #~31 /* bulk portion */
|
||||||
|
.Lpr_clear32:
|
||||||
|
cmp x4, x5
|
||||||
|
b.hs .Lpr_clear1
|
||||||
|
add x6, x2, x4
|
||||||
|
stp q0, q0, [x6]
|
||||||
|
add x4, x4, #32
|
||||||
|
b .Lpr_clear32
|
||||||
|
.Lpr_clear1:
|
||||||
|
cmp x4, x3
|
||||||
|
b.hs .Lpr_clear_done
|
||||||
|
strb wzr, [x2, x4]
|
||||||
|
add x4, x4, #1
|
||||||
|
b .Lpr_clear1
|
||||||
|
.Lpr_clear_done:
|
||||||
|
|
||||||
|
/* mark 0 and 1 as composite */
|
||||||
|
mov w6, #1
|
||||||
|
strb w6, [x2]
|
||||||
|
strb w6, [x2, #1]
|
||||||
|
|
||||||
|
/* outer loop: i = 2; i*i < limit; i++ */
|
||||||
|
mov x7, #2
|
||||||
|
.Lpr_outer:
|
||||||
|
mul x9, x7, x7
|
||||||
|
cmp x9, x3
|
||||||
|
b.hs .Lpr_count
|
||||||
|
|
||||||
|
ldrb w10, [x2, x7]
|
||||||
|
cbnz w10, .Lpr_outer_next /* already composite, skip */
|
||||||
|
|
||||||
|
/* inner loop: mark multiples starting at i*i, stride i */
|
||||||
|
mov x11, x9
|
||||||
|
.Lpr_inner:
|
||||||
|
cmp x11, x3
|
||||||
|
b.hs .Lpr_outer_next
|
||||||
|
strb w6, [x2, x11]
|
||||||
|
add x11, x11, x7
|
||||||
|
b .Lpr_inner
|
||||||
|
|
||||||
|
.Lpr_outer_next:
|
||||||
|
add x7, x7, #1
|
||||||
|
b .Lpr_outer
|
||||||
|
|
||||||
|
/* count the survivors */
|
||||||
|
.Lpr_count:
|
||||||
|
mov x0, xzr /* count */
|
||||||
|
mov x4, #2
|
||||||
|
.Lpr_count_loop:
|
||||||
|
cmp x4, x3
|
||||||
|
b.hs .Lpr_done
|
||||||
|
ldrb w10, [x2, x4]
|
||||||
|
cmp w10, #0
|
||||||
|
cinc x0, x0, eq
|
||||||
|
add x4, x4, #1
|
||||||
|
b .Lpr_count_loop
|
||||||
|
|
||||||
|
.Lpr_done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lpr_none:
|
||||||
|
mov x0, xzr
|
||||||
|
ret
|
||||||
|
FN_END(fm_primes)
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_simd(uint64_t iters, void *buf)
|
||||||
|
*
|
||||||
|
* "Extended instructions": the ASIMD/NEON unit, which is architecturally
|
||||||
|
* mandatory on AArch64 and therefore safe to use without runtime feature
|
||||||
|
* detection. (AES, SHA and DotProd are all *optional* extensions; using them
|
||||||
|
* unguarded would fault on cores that lack them, so they are deliberately
|
||||||
|
* avoided here.)
|
||||||
|
*
|
||||||
|
* buf must be at least 128 bytes and 16-byte aligned. Returns a checksum.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_simd)
|
||||||
|
cbz x0, .Lsd_zero
|
||||||
|
mov x6, x0
|
||||||
|
|
||||||
|
/* seed eight vectors from the scratch buffer */
|
||||||
|
ldp q0, q1, [x1]
|
||||||
|
ldp q2, q3, [x1, #32]
|
||||||
|
ldp q4, q5, [x1, #64]
|
||||||
|
ldp q6, q7, [x1, #96]
|
||||||
|
|
||||||
|
movi v28.4s, #3
|
||||||
|
movi v29.4s, #7
|
||||||
|
movi v30.16b, #0x5A
|
||||||
|
|
||||||
|
/* byte-permute table for tbl: reverses the 16 byte lanes */
|
||||||
|
adr x9, .Lsd_perm
|
||||||
|
ldr q31, [x9]
|
||||||
|
|
||||||
|
/* float accumulator and operands: derived from the integer seeds by
|
||||||
|
* conversion, so the FP pipeline sees real finite values rather than
|
||||||
|
* reinterpreted integer bit patterns (which would be NaNs/denormals
|
||||||
|
* and would measure the slow path, not the common one). */
|
||||||
|
movi v22.4s, #0
|
||||||
|
scvtf v26.4s, v0.4s
|
||||||
|
scvtf v27.4s, v1.4s
|
||||||
|
|
||||||
|
.Lsd_loop:
|
||||||
|
/* integer SIMD: multiply-accumulate across four independent vectors */
|
||||||
|
mla v0.4s, v1.4s, v28.4s
|
||||||
|
mla v1.4s, v2.4s, v29.4s
|
||||||
|
mla v2.4s, v3.4s, v28.4s
|
||||||
|
mla v3.4s, v0.4s, v29.4s
|
||||||
|
|
||||||
|
/* saturating and halving arithmetic */
|
||||||
|
sqadd v4.4s, v4.4s, v0.4s
|
||||||
|
uhadd v5.4s, v5.4s, v1.4s
|
||||||
|
srhadd v6.4s, v6.4s, v2.4s
|
||||||
|
sqsub v7.4s, v7.4s, v3.4s
|
||||||
|
|
||||||
|
/* widening ops: 16->32 bit lane expansion */
|
||||||
|
umull v16.4s, v0.4h, v1.4h
|
||||||
|
umull2 v17.4s, v0.8h, v1.8h
|
||||||
|
saddw v2.4s, v2.4s, v16.4h
|
||||||
|
ssubw2 v3.4s, v3.4s, v17.8h
|
||||||
|
|
||||||
|
/* pairwise reduction */
|
||||||
|
uaddlp v18.2d, v4.4s
|
||||||
|
addp v19.4s, v5.4s, v6.4s
|
||||||
|
add v4.4s, v4.4s, v19.4s
|
||||||
|
|
||||||
|
/* table lookup: the byte-permute network */
|
||||||
|
tbl v20.16b, {v0.16b}, v31.16b
|
||||||
|
eor v1.16b, v1.16b, v20.16b
|
||||||
|
|
||||||
|
/* shifts, logic, min/max, reverse */
|
||||||
|
shl v21.4s, v2.4s, #3
|
||||||
|
usra v21.4s, v2.4s, #29
|
||||||
|
orr v2.16b, v2.16b, v21.16b
|
||||||
|
bic v3.16b, v3.16b, v30.16b
|
||||||
|
umax v5.4s, v5.4s, v0.4s
|
||||||
|
umin v6.4s, v6.4s, v1.4s
|
||||||
|
rev32 v7.16b, v7.16b
|
||||||
|
|
||||||
|
/* single-precision float SIMD: 4-wide fmla, plus the reciprocal and
|
||||||
|
* rsqrt estimate instructions that shader-style code leans on */
|
||||||
|
/* v16/v17 are dead after the widening ops above, so they are reused
|
||||||
|
* here as scratch rather than touching the callee-saved v8-v15 bank. */
|
||||||
|
fmla v22.4s, v26.4s, v27.4s
|
||||||
|
frecpe v23.4s, v26.4s
|
||||||
|
frsqrte v16.4s, v22.4s
|
||||||
|
fadd v22.4s, v22.4s, v23.4s
|
||||||
|
fmul v26.4s, v26.4s, v16.4s
|
||||||
|
|
||||||
|
/* population count and leading-zero count */
|
||||||
|
cnt v24.16b, v0.16b
|
||||||
|
uaddlv h25, v24.8b
|
||||||
|
clz v17.4s, v1.4s
|
||||||
|
add v0.4s, v0.4s, v17.4s
|
||||||
|
|
||||||
|
/* fold the reduction results back in so nothing is dead code */
|
||||||
|
add v4.2d, v4.2d, v18.2d
|
||||||
|
|
||||||
|
subs x6, x6, #1
|
||||||
|
b.ne .Lsd_loop
|
||||||
|
|
||||||
|
/* horizontal fold to a single 64-bit checksum */
|
||||||
|
eor v0.16b, v0.16b, v1.16b
|
||||||
|
eor v2.16b, v2.16b, v3.16b
|
||||||
|
eor v4.16b, v4.16b, v5.16b
|
||||||
|
eor v6.16b, v6.16b, v7.16b
|
||||||
|
eor v0.16b, v0.16b, v2.16b
|
||||||
|
eor v4.16b, v4.16b, v6.16b
|
||||||
|
eor v0.16b, v0.16b, v4.16b
|
||||||
|
addv s0, v0.4s
|
||||||
|
fmov w0, s0
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lsd_zero:
|
||||||
|
mov x0, xzr
|
||||||
|
ret
|
||||||
|
FN_END(fm_simd)
|
||||||
|
|
||||||
|
.p2align 4
|
||||||
|
.Lsd_perm:
|
||||||
|
.byte 15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_compress(const uint8_t *src, uint64_t len, uint32_t *ht)
|
||||||
|
*
|
||||||
|
* The match-finding inner loop of an LZ77 compressor (the LZ4 fast strategy):
|
||||||
|
* hash the next 4 bytes, probe a single-entry-per-bucket table, verify, then
|
||||||
|
* extend the match. This is where real compressors spend their time - it is
|
||||||
|
* branch-heavy with a data-dependent, cache-missing table probe.
|
||||||
|
*
|
||||||
|
* ht must hold 1<<16 uint32_t (256 KiB); this routine clears it itself.
|
||||||
|
* Returns the encoded size in bytes.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_compress)
|
||||||
|
stp x29, x30, [sp, #-96]!
|
||||||
|
mov x29, sp
|
||||||
|
stp x19, x20, [sp, #16]
|
||||||
|
stp x21, x22, [sp, #32]
|
||||||
|
stp x23, x24, [sp, #48]
|
||||||
|
stp x25, x26, [sp, #64]
|
||||||
|
stp x27, x28, [sp, #80]
|
||||||
|
|
||||||
|
mov x19, x0 /* src */
|
||||||
|
mov x20, x1 /* len */
|
||||||
|
mov x21, x2 /* ht */
|
||||||
|
|
||||||
|
/* clear the hash table: 1<<16 entries * 4 bytes = 262144 bytes */
|
||||||
|
movi v0.16b, #0
|
||||||
|
mov x9, xzr
|
||||||
|
MOV64(x10, 262144)
|
||||||
|
.Lcm_clear:
|
||||||
|
add x11, x21, x9
|
||||||
|
stp q0, q0, [x11]
|
||||||
|
stp q0, q0, [x11, #32]
|
||||||
|
add x9, x9, #64
|
||||||
|
cmp x9, x10
|
||||||
|
b.lo .Lcm_clear
|
||||||
|
|
||||||
|
cmp x20, #16
|
||||||
|
b.lo .Lcm_tiny
|
||||||
|
|
||||||
|
mov x22, x19 /* ip */
|
||||||
|
mov x23, x19 /* anchor */
|
||||||
|
add x24, x19, x20 /* end */
|
||||||
|
sub x25, x24, #12 /* mflimit */
|
||||||
|
mov x26, xzr /* outsize */
|
||||||
|
|
||||||
|
MOV64(x27, 2654435761) /* Knuth multiplicative hash */
|
||||||
|
|
||||||
|
.Lcm_loop:
|
||||||
|
cmp x22, x25
|
||||||
|
b.hs .Lcm_flush
|
||||||
|
|
||||||
|
ldr w9, [x22] /* seq = load32(ip) */
|
||||||
|
mul w10, w9, w27
|
||||||
|
lsr w10, w10, #16 /* h = (seq * prime) >> 16 */
|
||||||
|
|
||||||
|
ldr w11, [x21, x10, lsl #2] /* ref_off = ht[h] */
|
||||||
|
sub x12, x22, x19 /* cur_off = ip - src */
|
||||||
|
str w12, [x21, x10, lsl #2] /* ht[h] = cur_off */
|
||||||
|
|
||||||
|
add x13, x19, x11 /* ref = src + ref_off */
|
||||||
|
cmp x13, x22
|
||||||
|
b.hs .Lcm_no_match /* ref must be strictly behind ip */
|
||||||
|
|
||||||
|
sub x14, x22, x13 /* distance */
|
||||||
|
MOV64(x15, 65536)
|
||||||
|
cmp x14, x15
|
||||||
|
b.hs .Lcm_no_match /* 16-bit offset window */
|
||||||
|
|
||||||
|
ldr w16, [x13]
|
||||||
|
cmp w16, w9
|
||||||
|
b.ne .Lcm_no_match
|
||||||
|
|
||||||
|
/* match confirmed: extend it byte by byte */
|
||||||
|
mov x28, #4 /* ml */
|
||||||
|
.Lcm_extend:
|
||||||
|
add x9, x22, x28
|
||||||
|
cmp x9, x24
|
||||||
|
b.hs .Lcm_emit
|
||||||
|
ldrb w10, [x22, x28]
|
||||||
|
ldrb w11, [x13, x28]
|
||||||
|
cmp w10, w11
|
||||||
|
b.ne .Lcm_emit
|
||||||
|
add x28, x28, #1
|
||||||
|
b .Lcm_extend
|
||||||
|
|
||||||
|
.Lcm_emit:
|
||||||
|
/* token(1) + offset(2) + literals + varint extensions */
|
||||||
|
sub x9, x22, x23 /* literal run length */
|
||||||
|
add x26, x26, x9
|
||||||
|
add x26, x26, #3
|
||||||
|
cmp x9, #15
|
||||||
|
cinc x26, x26, hs /* literal-length extension byte */
|
||||||
|
cmp x28, #19
|
||||||
|
cinc x26, x26, hs /* match-length extension byte */
|
||||||
|
|
||||||
|
add x22, x22, x28
|
||||||
|
mov x23, x22
|
||||||
|
b .Lcm_loop
|
||||||
|
|
||||||
|
.Lcm_no_match:
|
||||||
|
add x22, x22, #1
|
||||||
|
b .Lcm_loop
|
||||||
|
|
||||||
|
.Lcm_flush:
|
||||||
|
/* trailing literals */
|
||||||
|
sub x9, x24, x23
|
||||||
|
add x26, x26, x9
|
||||||
|
add x26, x26, #1
|
||||||
|
mov x0, x26
|
||||||
|
b .Lcm_ret
|
||||||
|
|
||||||
|
.Lcm_tiny:
|
||||||
|
add x0, x20, #1
|
||||||
|
|
||||||
|
.Lcm_ret:
|
||||||
|
ldp x27, x28, [sp, #80]
|
||||||
|
ldp x25, x26, [sp, #64]
|
||||||
|
ldp x23, x24, [sp, #48]
|
||||||
|
ldp x21, x22, [sp, #32]
|
||||||
|
ldp x19, x20, [sp, #16]
|
||||||
|
ldp x29, x30, [sp], #96
|
||||||
|
ret
|
||||||
|
FN_END(fm_compress)
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_chacha20(uint8_t *buf, uint64_t len, const uint8_t key[32],
|
||||||
|
* uint64_t rounds)
|
||||||
|
*
|
||||||
|
* ChaCha20 stream cipher, NEON, four 128-bit state rows. Chosen over AES
|
||||||
|
* deliberately: the ARMv8 AES extension is optional, so an AES-instruction
|
||||||
|
* benchmark would fault on cores without it. ChaCha20 needs only baseline
|
||||||
|
* ASIMD and is a real, widely deployed cipher (TLS, WireGuard, SSH).
|
||||||
|
*
|
||||||
|
* len is rounded down to a multiple of 64. `rounds` = number of passes over
|
||||||
|
* the buffer. Returns a checksum of the keystream output.
|
||||||
|
* =================================================================== */
|
||||||
|
|
||||||
|
/* rotate each 32-bit lane left by n, via shl + shift-right-and-insert */
|
||||||
|
#define VROTL(vd, vs, vt, n) \
|
||||||
|
shl vt##.4s, vs##.4s, #(n) ;\
|
||||||
|
sri vt##.4s, vs##.4s, #(32 - (n)) ;\
|
||||||
|
mov vd##.16b, vt##.16b
|
||||||
|
|
||||||
|
/* one ChaCha quarter-round over rows a,b,c,d using v24 as scratch */
|
||||||
|
#define QROUND(a, b, c, d) \
|
||||||
|
add a##.4s, a##.4s, b##.4s ;\
|
||||||
|
eor d##.16b, d##.16b, a##.16b ;\
|
||||||
|
rev32 d##.8h, d##.8h ;\
|
||||||
|
add c##.4s, c##.4s, d##.4s ;\
|
||||||
|
eor b##.16b, b##.16b, c##.16b ;\
|
||||||
|
VROTL(b, b, v24, 12) ;\
|
||||||
|
add a##.4s, a##.4s, b##.4s ;\
|
||||||
|
eor d##.16b, d##.16b, a##.16b ;\
|
||||||
|
VROTL(d, d, v24, 8) ;\
|
||||||
|
add c##.4s, c##.4s, d##.4s ;\
|
||||||
|
eor b##.16b, b##.16b, c##.16b ;\
|
||||||
|
VROTL(b, b, v24, 7)
|
||||||
|
|
||||||
|
FN_BEGIN(fm_chacha20)
|
||||||
|
and x1, x1, #~63 /* whole 64-byte blocks only */
|
||||||
|
cbz x1, .Lcc_zero
|
||||||
|
cbz x3, .Lcc_zero
|
||||||
|
|
||||||
|
stp x29, x30, [sp, #-32]!
|
||||||
|
mov x29, sp
|
||||||
|
stp x19, x20, [sp, #16]
|
||||||
|
|
||||||
|
mov x19, x0 /* buf */
|
||||||
|
mov x20, x1 /* len */
|
||||||
|
|
||||||
|
/* v4..v7 hold the base state */
|
||||||
|
adr x9, .Lcc_sigma
|
||||||
|
ldr q4, [x9] /* "expand 32-byte k" */
|
||||||
|
ldp q5, q6, [x2] /* key[0..31] */
|
||||||
|
movi v7.4s, #0 /* counter || nonce */
|
||||||
|
|
||||||
|
movi v25.16b, #0 /* running checksum */
|
||||||
|
mov x10, xzr /* counter value */
|
||||||
|
|
||||||
|
.Lcc_pass:
|
||||||
|
mov x11, xzr /* byte offset into buf */
|
||||||
|
|
||||||
|
.Lcc_block:
|
||||||
|
/* working state = base state, with the block counter in lane 0 of v7 */
|
||||||
|
mov v0.16b, v4.16b
|
||||||
|
mov v1.16b, v5.16b
|
||||||
|
mov v2.16b, v6.16b
|
||||||
|
mov v3.16b, v7.16b
|
||||||
|
mov v3.s[0], w10
|
||||||
|
|
||||||
|
/* keep originals for the final feed-forward add */
|
||||||
|
mov v16.16b, v0.16b
|
||||||
|
mov v17.16b, v1.16b
|
||||||
|
mov v18.16b, v2.16b
|
||||||
|
mov v19.16b, v3.16b
|
||||||
|
|
||||||
|
mov w12, #10 /* 10 double rounds = 20 rounds */
|
||||||
|
.Lcc_rounds:
|
||||||
|
/* column round */
|
||||||
|
QROUND(v0, v1, v2, v3)
|
||||||
|
|
||||||
|
/* rotate lanes to form the diagonals */
|
||||||
|
ext v1.16b, v1.16b, v1.16b, #4
|
||||||
|
ext v2.16b, v2.16b, v2.16b, #8
|
||||||
|
ext v3.16b, v3.16b, v3.16b, #12
|
||||||
|
|
||||||
|
/* diagonal round */
|
||||||
|
QROUND(v0, v1, v2, v3)
|
||||||
|
|
||||||
|
/* undo the lane rotation */
|
||||||
|
ext v1.16b, v1.16b, v1.16b, #12
|
||||||
|
ext v2.16b, v2.16b, v2.16b, #8
|
||||||
|
ext v3.16b, v3.16b, v3.16b, #4
|
||||||
|
|
||||||
|
subs w12, w12, #1
|
||||||
|
b.ne .Lcc_rounds
|
||||||
|
|
||||||
|
/* feed-forward: keystream = working + original */
|
||||||
|
add v0.4s, v0.4s, v16.4s
|
||||||
|
add v1.4s, v1.4s, v17.4s
|
||||||
|
add v2.4s, v2.4s, v18.4s
|
||||||
|
add v3.4s, v3.4s, v19.4s
|
||||||
|
|
||||||
|
/* XOR the keystream into the buffer */
|
||||||
|
add x13, x19, x11
|
||||||
|
ldp q20, q21, [x13]
|
||||||
|
ldp q22, q23, [x13, #32]
|
||||||
|
eor v20.16b, v20.16b, v0.16b
|
||||||
|
eor v21.16b, v21.16b, v1.16b
|
||||||
|
eor v22.16b, v22.16b, v2.16b
|
||||||
|
eor v23.16b, v23.16b, v3.16b
|
||||||
|
stp q20, q21, [x13]
|
||||||
|
stp q22, q23, [x13, #32]
|
||||||
|
|
||||||
|
/* accumulate a checksum of the keystream */
|
||||||
|
eor v25.16b, v25.16b, v0.16b
|
||||||
|
eor v25.16b, v25.16b, v3.16b
|
||||||
|
|
||||||
|
add x10, x10, #1
|
||||||
|
add x11, x11, #64
|
||||||
|
cmp x11, x20
|
||||||
|
b.lo .Lcc_block
|
||||||
|
|
||||||
|
subs x3, x3, #1
|
||||||
|
b.ne .Lcc_pass
|
||||||
|
|
||||||
|
addv s25, v25.4s
|
||||||
|
fmov w0, s25
|
||||||
|
|
||||||
|
ldp x19, x20, [sp, #16]
|
||||||
|
ldp x29, x30, [sp], #32
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lcc_zero:
|
||||||
|
mov x0, xzr
|
||||||
|
ret
|
||||||
|
FN_END(fm_chacha20)
|
||||||
|
|
||||||
|
.p2align 4
|
||||||
|
.Lcc_sigma:
|
||||||
|
.word 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_physics(double *bodies, uint64_t n, uint64_t steps)
|
||||||
|
*
|
||||||
|
* Direct-summation N-body gravity, O(n^2) per step, double precision.
|
||||||
|
* Layout per body, 8 doubles (64 bytes, one cache line):
|
||||||
|
* [0]=x [1]=y [2]=z [3]=mass [4]=vx [5]=vy [6]=vz [7]=pad
|
||||||
|
*
|
||||||
|
* The 1/sqrt is done with a real fsqrt+fdiv rather than the frsqrte estimate,
|
||||||
|
* so this exercises the divide/sqrt unit the way physics code actually does.
|
||||||
|
* Returns a checksum bit-cast from the final velocity sum.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_physics)
|
||||||
|
cbz x1, .Lph_zero
|
||||||
|
cbz x2, .Lph_zero
|
||||||
|
|
||||||
|
stp x29, x30, [sp, #-64]!
|
||||||
|
mov x29, sp
|
||||||
|
stp x19, x20, [sp, #16]
|
||||||
|
stp x21, x22, [sp, #32]
|
||||||
|
stp x23, x24, [sp, #48]
|
||||||
|
|
||||||
|
mov x19, x0 /* bodies */
|
||||||
|
mov x20, x1 /* n */
|
||||||
|
mov x21, x2 /* steps */
|
||||||
|
|
||||||
|
adr x9, .Lph_consts
|
||||||
|
ldp d28, d29, [x9] /* dt, eps^2 */
|
||||||
|
ldr d30, [x9, #16] /* 1.0 */
|
||||||
|
|
||||||
|
.Lph_step:
|
||||||
|
mov x22, xzr /* i */
|
||||||
|
|
||||||
|
.Lph_body_i:
|
||||||
|
lsl x9, x22, #6 /* i * 64 */
|
||||||
|
add x23, x19, x9 /* &bodies[i] */
|
||||||
|
|
||||||
|
ldp d0, d1, [x23] /* xi, yi */
|
||||||
|
ldr d2, [x23, #16] /* zi */
|
||||||
|
|
||||||
|
movi d16, #0 /* ax */
|
||||||
|
movi d17, #0 /* ay */
|
||||||
|
movi d18, #0 /* az */
|
||||||
|
|
||||||
|
mov x24, xzr /* j */
|
||||||
|
mov x10, x19 /* &bodies[j] */
|
||||||
|
|
||||||
|
.Lph_body_j:
|
||||||
|
ldp d3, d4, [x10] /* xj, yj */
|
||||||
|
ldp d5, d6, [x10, #16] /* zj, mj */
|
||||||
|
|
||||||
|
fsub d3, d3, d0 /* dx */
|
||||||
|
fsub d4, d4, d1 /* dy */
|
||||||
|
fsub d5, d5, d2 /* dz */
|
||||||
|
|
||||||
|
/* d2 = dx*dx + dy*dy + dz*dz + eps^2 (always >= eps^2, never zero,
|
||||||
|
* so the i==j self-term is finite and contributes exactly 0 below) */
|
||||||
|
fmul d7, d3, d3
|
||||||
|
fmadd d7, d4, d4, d7
|
||||||
|
fmadd d7, d5, d5, d7
|
||||||
|
fadd d7, d7, d29
|
||||||
|
|
||||||
|
fsqrt d19, d7 /* r */
|
||||||
|
fdiv d20, d30, d19 /* 1/r */
|
||||||
|
fmul d21, d20, d20 /* 1/r^2 */
|
||||||
|
fmul d21, d21, d20 /* 1/r^3 */
|
||||||
|
fmul d21, d21, d6 /* m/r^3 */
|
||||||
|
|
||||||
|
fmadd d16, d3, d21, d16 /* ax += dx * m/r^3 */
|
||||||
|
fmadd d17, d4, d21, d17
|
||||||
|
fmadd d18, d5, d21, d18
|
||||||
|
|
||||||
|
add x10, x10, #64
|
||||||
|
add x24, x24, #1
|
||||||
|
cmp x24, x20
|
||||||
|
b.lo .Lph_body_j
|
||||||
|
|
||||||
|
/* v += a * dt */
|
||||||
|
ldp d22, d23, [x23, #32]
|
||||||
|
ldr d24, [x23, #48]
|
||||||
|
fmadd d22, d16, d28, d22
|
||||||
|
fmadd d23, d17, d28, d23
|
||||||
|
fmadd d24, d18, d28, d24
|
||||||
|
stp d22, d23, [x23, #32]
|
||||||
|
str d24, [x23, #48]
|
||||||
|
|
||||||
|
add x22, x22, #1
|
||||||
|
cmp x22, x20
|
||||||
|
b.lo .Lph_body_i
|
||||||
|
|
||||||
|
/* second pass: x += v * dt (positions updated only after all forces) */
|
||||||
|
mov x22, xzr
|
||||||
|
mov x10, x19
|
||||||
|
.Lph_integrate:
|
||||||
|
ldp d0, d1, [x10]
|
||||||
|
ldr d2, [x10, #16]
|
||||||
|
ldp d22, d23, [x10, #32]
|
||||||
|
ldr d24, [x10, #48]
|
||||||
|
fmadd d0, d22, d28, d0
|
||||||
|
fmadd d1, d23, d28, d1
|
||||||
|
fmadd d2, d24, d28, d2
|
||||||
|
stp d0, d1, [x10]
|
||||||
|
str d2, [x10, #16]
|
||||||
|
add x10, x10, #64
|
||||||
|
add x22, x22, #1
|
||||||
|
cmp x22, x20
|
||||||
|
b.lo .Lph_integrate
|
||||||
|
|
||||||
|
subs x21, x21, #1
|
||||||
|
b.ne .Lph_step
|
||||||
|
|
||||||
|
/* checksum: sum of all velocity components */
|
||||||
|
movi d0, #0
|
||||||
|
mov x22, xzr
|
||||||
|
mov x10, x19
|
||||||
|
.Lph_sum:
|
||||||
|
ldp d22, d23, [x10, #32]
|
||||||
|
ldr d24, [x10, #48]
|
||||||
|
fadd d0, d0, d22
|
||||||
|
fadd d0, d0, d23
|
||||||
|
fadd d0, d0, d24
|
||||||
|
add x10, x10, #64
|
||||||
|
add x22, x22, #1
|
||||||
|
cmp x22, x20
|
||||||
|
b.lo .Lph_sum
|
||||||
|
|
||||||
|
fmov x0, d0
|
||||||
|
|
||||||
|
ldp x23, x24, [sp, #48]
|
||||||
|
ldp x21, x22, [sp, #32]
|
||||||
|
ldp x19, x20, [sp, #16]
|
||||||
|
ldp x29, x30, [sp], #64
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lph_zero:
|
||||||
|
mov x0, xzr
|
||||||
|
ret
|
||||||
|
FN_END(fm_physics)
|
||||||
|
|
||||||
|
.p2align 4
|
||||||
|
.Lph_consts:
|
||||||
|
.double 0.0078125 /* dt */
|
||||||
|
.double 0.0625 /* eps^2 */
|
||||||
|
.double 1.0
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_sort(uint32_t *a, uint64_t n)
|
||||||
|
*
|
||||||
|
* In-place heapsort. Chosen over quicksort because it needs no recursion or
|
||||||
|
* explicit stack, yet is aggressively branch-unpredictable and touches memory
|
||||||
|
* in a scattered pattern - it stresses the branch predictor and the cache
|
||||||
|
* hierarchy, which is what a sort benchmark should measure.
|
||||||
|
*
|
||||||
|
* Returns an order-sensitive checksum, which also verifies the sort.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_sort)
|
||||||
|
cmp x1, #2
|
||||||
|
b.lo .Lst_trivial
|
||||||
|
|
||||||
|
stp x29, x30, [sp, #-48]!
|
||||||
|
mov x29, sp
|
||||||
|
stp x19, x20, [sp, #16]
|
||||||
|
stp x21, x22, [sp, #32]
|
||||||
|
|
||||||
|
mov x19, x0 /* a */
|
||||||
|
mov x20, x1 /* n */
|
||||||
|
|
||||||
|
/* ---- build the max-heap: for i = n/2 - 1 down to 0 ---- */
|
||||||
|
lsr x21, x20, #1 /* i = n/2 */
|
||||||
|
.Lst_build:
|
||||||
|
cbz x21, .Lst_extract
|
||||||
|
sub x21, x21, #1 /* i-- */
|
||||||
|
mov x0, x21 /* root */
|
||||||
|
mov x1, x20 /* end */
|
||||||
|
bl .Lst_siftdown
|
||||||
|
cbnz x21, .Lst_build
|
||||||
|
|
||||||
|
/* ---- extract: for end = n-1 down to 1 ---- */
|
||||||
|
.Lst_extract:
|
||||||
|
sub x22, x20, #1 /* end = n-1 */
|
||||||
|
.Lst_extract_loop:
|
||||||
|
cbz x22, .Lst_checksum
|
||||||
|
|
||||||
|
/* swap a[0] and a[end] */
|
||||||
|
ldr w9, [x19]
|
||||||
|
ldr w10, [x19, x22, lsl #2]
|
||||||
|
str w10, [x19]
|
||||||
|
str w9, [x19, x22, lsl #2]
|
||||||
|
|
||||||
|
mov x0, xzr /* root = 0 */
|
||||||
|
mov x1, x22 /* end = end */
|
||||||
|
bl .Lst_siftdown
|
||||||
|
|
||||||
|
sub x22, x22, #1
|
||||||
|
b .Lst_extract_loop
|
||||||
|
|
||||||
|
/* ---- order-sensitive checksum ---- */
|
||||||
|
.Lst_checksum:
|
||||||
|
mov x0, xzr
|
||||||
|
mov x9, xzr
|
||||||
|
.Lst_cksum_loop:
|
||||||
|
ldr w10, [x19, x9, lsl #2]
|
||||||
|
eor x0, x0, x10
|
||||||
|
ror x0, x0, #7
|
||||||
|
add x0, x0, x10
|
||||||
|
add x9, x9, #1
|
||||||
|
cmp x9, x20
|
||||||
|
b.lo .Lst_cksum_loop
|
||||||
|
|
||||||
|
ldp x21, x22, [sp, #32]
|
||||||
|
ldp x19, x20, [sp, #16]
|
||||||
|
ldp x29, x30, [sp], #48
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lst_trivial:
|
||||||
|
mov x0, xzr
|
||||||
|
cbz x1, .Lst_trivial_ret
|
||||||
|
ldr w0, [x0]
|
||||||
|
.Lst_trivial_ret:
|
||||||
|
ret
|
||||||
|
|
||||||
|
/* ---- local helper: siftdown(root = x0, end = x1)
|
||||||
|
* clobbers x9-x15 only; x19 (base) is live across the call. ---- */
|
||||||
|
.Lst_siftdown:
|
||||||
|
mov x11, x0 /* root */
|
||||||
|
.Lst_sift_loop:
|
||||||
|
lsl x12, x11, #1
|
||||||
|
add x12, x12, #1 /* child = 2*root + 1 */
|
||||||
|
cmp x12, x1
|
||||||
|
b.hs .Lst_sift_done /* no children */
|
||||||
|
|
||||||
|
/* pick the larger of the two children */
|
||||||
|
add x13, x12, #1 /* child + 1 */
|
||||||
|
cmp x13, x1
|
||||||
|
b.hs .Lst_sift_have_child
|
||||||
|
ldr w14, [x19, x12, lsl #2]
|
||||||
|
ldr w15, [x19, x13, lsl #2]
|
||||||
|
cmp w15, w14
|
||||||
|
csel x12, x13, x12, hi
|
||||||
|
|
||||||
|
.Lst_sift_have_child:
|
||||||
|
ldr w14, [x19, x11, lsl #2] /* a[root] */
|
||||||
|
ldr w15, [x19, x12, lsl #2] /* a[child] */
|
||||||
|
cmp w14, w15
|
||||||
|
b.hs .Lst_sift_done /* heap property holds */
|
||||||
|
|
||||||
|
/* swap and descend */
|
||||||
|
str w15, [x19, x11, lsl #2]
|
||||||
|
str w14, [x19, x12, lsl #2]
|
||||||
|
mov x11, x12
|
||||||
|
b .Lst_sift_loop
|
||||||
|
|
||||||
|
.Lst_sift_done:
|
||||||
|
ret
|
||||||
|
FN_END(fm_sort)
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_chase(void **ptrs, uint64_t steps)
|
||||||
|
*
|
||||||
|
* Pointer chase around a randomised cycle. Every load depends on the previous
|
||||||
|
* one, so nothing can be prefetched, overlapped or reordered - this measures
|
||||||
|
* the pure serial latency of the memory hierarchy, which is the single
|
||||||
|
* hardest thing for a wide out-of-order core to hide. It is the truest
|
||||||
|
* "single-threaded" test in the suite.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_chase)
|
||||||
|
cbz x1, .Lch_zero
|
||||||
|
mov x2, x0 /* p = ptrs */
|
||||||
|
mov x3, x1
|
||||||
|
|
||||||
|
.Lch_loop:
|
||||||
|
ldr x2, [x2]
|
||||||
|
subs x3, x3, #1
|
||||||
|
b.ne .Lch_loop
|
||||||
|
|
||||||
|
sub x0, x2, x0 /* final offset, keeps p live */
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lch_zero:
|
||||||
|
mov x0, xzr
|
||||||
|
ret
|
||||||
|
FN_END(fm_chase)
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__ELF__)
|
||||||
|
.section .note.GNU-stack, "", %progbits
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,987 @@
|
|||||||
|
/*
|
||||||
|
* fossmark_x86_64.S - x86-64 (AMD64) CPU benchmark kernels
|
||||||
|
*
|
||||||
|
* The AMD64 counterpart to fossmark.S. Same nine routines, same 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
|
||||||
|
* main.c is shared unchanged between this file and the AArch64 one.
|
||||||
|
*
|
||||||
|
* Only baseline instructions are used: general-purpose AMD64 plus SSE2, which
|
||||||
|
* is architecturally mandatory on x86-64. The optional extensions (SSE4, AVX,
|
||||||
|
* FMA, AES-NI, POPCNT/BMI) are deliberately avoided - using them unguarded
|
||||||
|
* would fault (#UD) on cores that lack them - so, exactly as the NEON file
|
||||||
|
* sticks to mandatory ASIMD and shuns the optional AES/SHA/DotProd, this file
|
||||||
|
* sticks to mandatory SSE2 and shuns everything above it. FMA in particular is
|
||||||
|
* optional here, so every fused multiply-add is written as a separate multiply
|
||||||
|
* and add.
|
||||||
|
*
|
||||||
|
* Register conventions (System V AMD64):
|
||||||
|
* integer args rdi, rsi, rdx, rcx, r8, r9 (return in rax)
|
||||||
|
* callee-saved rbx, rbp, r12, r13, r14, r15 (saved when used)
|
||||||
|
* all of xmm0-15 are caller-saved, so no vector register need be preserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
.intel_syntax noprefix
|
||||||
|
|
||||||
|
#if defined(__APPLE__)
|
||||||
|
# define SYM(name) _##name
|
||||||
|
#else
|
||||||
|
# define SYM(name) name
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__ELF__)
|
||||||
|
# define FN_BEGIN(name) .p2align 4 ; .globl SYM(name) ; .type SYM(name), @function ; SYM(name):
|
||||||
|
# define FN_END(name) .size SYM(name), . - SYM(name)
|
||||||
|
#else
|
||||||
|
# define FN_BEGIN(name) .p2align 4 ; .globl SYM(name) ; SYM(name):
|
||||||
|
# define FN_END(name)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
.text
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_int_math(uint64_t iters) [rdi = iters]
|
||||||
|
*
|
||||||
|
* Four independent multiply-accumulate chains for instruction-level
|
||||||
|
* parallelism, mixed with the long-latency serialising ops (mul/div) and
|
||||||
|
* bit-manipulation. Returns a checksum so nothing can be elided.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_int_math)
|
||||||
|
test rdi, rdi
|
||||||
|
jz .Lim_zero
|
||||||
|
|
||||||
|
mov r8, 0x9E3779B97F4A7C15 /* a */
|
||||||
|
mov r9, 0xBF58476D1CE4E5B9 /* b */
|
||||||
|
mov r10, 0x94D049BB133111EB /* c */
|
||||||
|
mov r11, 0x2545F4914F6CDD1D /* d */
|
||||||
|
mov rsi, 0x00000000DEADBEEF /* odd multiplier, never zero */
|
||||||
|
|
||||||
|
.Lim_loop:
|
||||||
|
/* four independent multiply-accumulate chains */
|
||||||
|
imul r8, rsi
|
||||||
|
add r8, r9
|
||||||
|
imul r9, rsi
|
||||||
|
add r9, r10
|
||||||
|
imul r10, rsi
|
||||||
|
add r10, r11
|
||||||
|
imul r11, rsi
|
||||||
|
add r11, r8
|
||||||
|
|
||||||
|
/* cross-mix with shifts and logic ops */
|
||||||
|
mov rax, r10
|
||||||
|
shr rax, 29
|
||||||
|
xor r8, rax
|
||||||
|
mov rax, r11
|
||||||
|
shl rax, 17
|
||||||
|
xor r9, rax
|
||||||
|
mov rax, r8
|
||||||
|
ror rax, 31
|
||||||
|
xor r10, rax
|
||||||
|
mov rax, r9
|
||||||
|
sar rax, 7
|
||||||
|
not rax
|
||||||
|
and r11, rax /* r11 &= ~(r9 >> 7 arith) */
|
||||||
|
|
||||||
|
/* wide multiplies: the long-latency 128-bit multiplier path */
|
||||||
|
mov rax, r8
|
||||||
|
mul r10 /* rdx:rax = a*c, high in rdx */
|
||||||
|
add r8, rdx
|
||||||
|
mov rax, r9
|
||||||
|
imul r11 /* rdx:rax = b*d signed, high rdx */
|
||||||
|
add r9, rdx
|
||||||
|
|
||||||
|
/* bit manipulation: byte reverse */
|
||||||
|
mov rax, r10
|
||||||
|
bswap rax
|
||||||
|
xor r8, rax
|
||||||
|
mov rax, r11
|
||||||
|
bswap rax
|
||||||
|
xor r9, rax
|
||||||
|
|
||||||
|
/* division: fully serialising, not pipelined */
|
||||||
|
mov rcx, rsi
|
||||||
|
or rcx, 1 /* guarantee a non-zero divisor */
|
||||||
|
mov rax, r8
|
||||||
|
xor edx, edx
|
||||||
|
div rcx /* rax = a / rcx (unsigned) */
|
||||||
|
imul rax, rcx
|
||||||
|
sub r10, rax /* c -= (a/div)*div */
|
||||||
|
mov rax, r9
|
||||||
|
cqo
|
||||||
|
idiv rcx /* rax = b / rcx (signed) */
|
||||||
|
add r11, rax
|
||||||
|
|
||||||
|
/* bitfield ops */
|
||||||
|
ror r9, 11
|
||||||
|
shld r8, r9, 23
|
||||||
|
|
||||||
|
dec rdi
|
||||||
|
jnz .Lim_loop
|
||||||
|
|
||||||
|
mov rax, r8
|
||||||
|
xor rax, r9
|
||||||
|
xor rax, r10
|
||||||
|
xor rax, r11
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lim_zero:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
FN_END(fm_int_math)
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_fp_math(uint64_t iters) [rdi = iters]
|
||||||
|
*
|
||||||
|
* Double-precision scalar FP. Four multiply-add chains for the pipelined
|
||||||
|
* path; sqrtsd and divsd for the non-pipelined divide/sqrt unit that usually
|
||||||
|
* separates cores. Returns the result bit-cast to u64.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_fp_math)
|
||||||
|
test rdi, rdi
|
||||||
|
jz .Lfp_zero
|
||||||
|
|
||||||
|
movsd xmm0, [rip + .Lfp_consts + 0] /* a = 1.5 */
|
||||||
|
movsd xmm1, [rip + .Lfp_consts + 8] /* b = 2.5 */
|
||||||
|
movsd xmm2, [rip + .Lfp_consts + 16] /* c = 3.5 */
|
||||||
|
movsd xmm3, [rip + .Lfp_consts + 24] /* d = 0.5 */
|
||||||
|
movsd xmm4, [rip + .Lfp_consts + 32] /* mul */
|
||||||
|
movsd xmm5, [rip + .Lfp_consts + 40] /* addend */
|
||||||
|
movsd xmm6, [rip + .Lfp_consts + 48] /* 2.0 */
|
||||||
|
movsd xmm7, [rip + .Lfp_consts + 56] /* 1.0 */
|
||||||
|
|
||||||
|
.Lfp_loop:
|
||||||
|
/* four independent multiply-add chains (no baseline FMA) */
|
||||||
|
mulsd xmm0, xmm4
|
||||||
|
addsd xmm0, xmm5
|
||||||
|
mulsd xmm1, xmm4
|
||||||
|
addsd xmm1, xmm5
|
||||||
|
mulsd xmm2, xmm4
|
||||||
|
addsd xmm2, xmm5
|
||||||
|
mulsd xmm3, xmm4
|
||||||
|
addsd xmm3, xmm5
|
||||||
|
|
||||||
|
/* keep the accumulators bounded so they never reach inf/NaN */
|
||||||
|
minsd xmm0, xmm6
|
||||||
|
minsd xmm1, xmm6
|
||||||
|
minsd xmm2, xmm6
|
||||||
|
minsd xmm3, xmm6
|
||||||
|
|
||||||
|
/* square root: long latency, low throughput */
|
||||||
|
sqrtsd xmm8, xmm0
|
||||||
|
sqrtsd xmm9, xmm1
|
||||||
|
addsd xmm2, xmm8
|
||||||
|
addsd xmm3, xmm9
|
||||||
|
|
||||||
|
/* divide: 1/(c+1), divisor >= 1 so never zero */
|
||||||
|
movapd xmm10, xmm2
|
||||||
|
addsd xmm10, xmm7
|
||||||
|
movapd xmm11, xmm7
|
||||||
|
divsd xmm11, xmm10
|
||||||
|
addsd xmm0, xmm11
|
||||||
|
|
||||||
|
movapd xmm10, xmm3
|
||||||
|
addsd xmm10, xmm7
|
||||||
|
movapd xmm11, xmm7
|
||||||
|
divsd xmm11, xmm10
|
||||||
|
addsd xmm1, xmm11
|
||||||
|
|
||||||
|
/* abs/neg/max: cheap ops to balance the mix */
|
||||||
|
andpd xmm2, [rip + .Lfp_absmask] /* fabs(c) */
|
||||||
|
xorpd xmm3, [rip + .Lfp_signmask] /* fneg(d) */
|
||||||
|
andpd xmm3, [rip + .Lfp_absmask] /* fabs() */
|
||||||
|
maxsd xmm3, xmm7
|
||||||
|
|
||||||
|
dec rdi
|
||||||
|
jnz .Lfp_loop
|
||||||
|
|
||||||
|
addsd xmm0, xmm1
|
||||||
|
addsd xmm2, xmm3
|
||||||
|
addsd xmm0, xmm2
|
||||||
|
movq rax, xmm0
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lfp_zero:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
FN_END(fm_fp_math)
|
||||||
|
|
||||||
|
.p2align 4
|
||||||
|
.Lfp_consts:
|
||||||
|
.double 1.5, 2.5
|
||||||
|
.double 3.5, 0.5
|
||||||
|
.double 1.0625, 0.0009765625
|
||||||
|
.double 2.0, 1.0
|
||||||
|
.p2align 4
|
||||||
|
.Lfp_absmask:
|
||||||
|
.quad 0x7fffffffffffffff, 0x7fffffffffffffff
|
||||||
|
.Lfp_signmask:
|
||||||
|
.quad 0x8000000000000000, 0x8000000000000000
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_primes(uint64_t limit, uint8_t *sieve) [rdi, rsi]
|
||||||
|
*
|
||||||
|
* Sieve of Eratosthenes over [0, limit). The routine clears the caller's
|
||||||
|
* scratch itself, so the clearing pass counts as measured work. Strided stores
|
||||||
|
* over a buffer larger than L1 make this a memory-hierarchy test too. Returns
|
||||||
|
* the prime count.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_primes)
|
||||||
|
cmp rdi, 2
|
||||||
|
jb .Lpr_none
|
||||||
|
|
||||||
|
/* zero the sieve, 32 bytes per iteration */
|
||||||
|
pxor xmm0, xmm0
|
||||||
|
xor rax, rax /* index */
|
||||||
|
mov rcx, rdi
|
||||||
|
and rcx, -32 /* bulk portion */
|
||||||
|
.Lpr_clear32:
|
||||||
|
cmp rax, rcx
|
||||||
|
jae .Lpr_clear1
|
||||||
|
movdqu [rsi + rax], xmm0
|
||||||
|
movdqu [rsi + rax + 16], xmm0
|
||||||
|
add rax, 32
|
||||||
|
jmp .Lpr_clear32
|
||||||
|
.Lpr_clear1:
|
||||||
|
cmp rax, rdi
|
||||||
|
jae .Lpr_clear_done
|
||||||
|
mov byte ptr [rsi + rax], 0
|
||||||
|
inc rax
|
||||||
|
jmp .Lpr_clear1
|
||||||
|
.Lpr_clear_done:
|
||||||
|
|
||||||
|
/* mark 0 and 1 as composite */
|
||||||
|
mov byte ptr [rsi], 1
|
||||||
|
mov byte ptr [rsi + 1], 1
|
||||||
|
|
||||||
|
/* outer loop: i = 2; i*i < limit; i++ */
|
||||||
|
mov r8, 2
|
||||||
|
.Lpr_outer:
|
||||||
|
mov rax, r8
|
||||||
|
imul rax, r8 /* i*i */
|
||||||
|
cmp rax, rdi
|
||||||
|
jae .Lpr_count
|
||||||
|
|
||||||
|
movzx edx, byte ptr [rsi + r8]
|
||||||
|
test dl, dl
|
||||||
|
jnz .Lpr_outer_next /* already composite, skip */
|
||||||
|
|
||||||
|
/* inner loop: mark multiples starting at i*i, stride i */
|
||||||
|
mov r9, rax /* j = i*i */
|
||||||
|
.Lpr_inner:
|
||||||
|
cmp r9, rdi
|
||||||
|
jae .Lpr_outer_next
|
||||||
|
mov byte ptr [rsi + r9], 1
|
||||||
|
add r9, r8
|
||||||
|
jmp .Lpr_inner
|
||||||
|
|
||||||
|
.Lpr_outer_next:
|
||||||
|
inc r8
|
||||||
|
jmp .Lpr_outer
|
||||||
|
|
||||||
|
/* count the survivors */
|
||||||
|
.Lpr_count:
|
||||||
|
xor eax, eax /* count */
|
||||||
|
mov r9, 2
|
||||||
|
.Lpr_count_loop:
|
||||||
|
cmp r9, rdi
|
||||||
|
jae .Lpr_done
|
||||||
|
cmp byte ptr [rsi + r9], 0
|
||||||
|
jne .Lpr_count_next
|
||||||
|
inc rax
|
||||||
|
.Lpr_count_next:
|
||||||
|
inc r9
|
||||||
|
jmp .Lpr_count_loop
|
||||||
|
|
||||||
|
.Lpr_done:
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lpr_none:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
FN_END(fm_primes)
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_simd(uint64_t iters, void *buf) [rdi = iters, rsi = buf]
|
||||||
|
*
|
||||||
|
* "Extended instructions": the SSE2 unit, which is architecturally mandatory
|
||||||
|
* on x86-64 and therefore safe without runtime feature detection. Packed
|
||||||
|
* 16-bit integer arithmetic is used (SSE2's widest integer multiply is 16-bit;
|
||||||
|
* 32-bit packed multiply, pmulld, is an SSE4.1 extension and is avoided), plus
|
||||||
|
* saturating/averaging ops, widening multiply-add, shuffles and the packed
|
||||||
|
* single-precision float path including the reciprocal/rsqrt estimates.
|
||||||
|
*
|
||||||
|
* buf must be at least 128 bytes. Returns a checksum.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_simd)
|
||||||
|
test rdi, rdi
|
||||||
|
jz .Lsd_zero
|
||||||
|
|
||||||
|
/* seed eight vectors from the scratch buffer */
|
||||||
|
movdqu xmm0, [rsi]
|
||||||
|
movdqu xmm1, [rsi + 16]
|
||||||
|
movdqu xmm2, [rsi + 32]
|
||||||
|
movdqu xmm3, [rsi + 48]
|
||||||
|
movdqu xmm4, [rsi + 64]
|
||||||
|
movdqu xmm5, [rsi + 80]
|
||||||
|
movdqu xmm6, [rsi + 96]
|
||||||
|
movdqu xmm7, [rsi + 112]
|
||||||
|
|
||||||
|
/* float operands: convert the integer seeds to finite floats rather
|
||||||
|
* than reinterpreting bit patterns (which would be NaNs/denormals) */
|
||||||
|
cvtdq2ps xmm12, xmm0
|
||||||
|
cvtdq2ps xmm13, xmm1
|
||||||
|
pxor xmm14, xmm14 /* float accumulator */
|
||||||
|
|
||||||
|
.Lsd_loop:
|
||||||
|
/* 16-bit integer multiply-accumulate across independent vectors */
|
||||||
|
pmullw xmm0, xmm1
|
||||||
|
paddw xmm0, xmm2
|
||||||
|
pmullw xmm1, xmm2
|
||||||
|
paddw xmm1, xmm3
|
||||||
|
pmullw xmm2, xmm3
|
||||||
|
paddw xmm2, xmm0
|
||||||
|
|
||||||
|
/* saturating and averaging arithmetic */
|
||||||
|
paddsw xmm4, xmm0
|
||||||
|
paddusw xmm5, xmm1
|
||||||
|
psubsw xmm6, xmm2
|
||||||
|
psubusw xmm7, xmm3
|
||||||
|
|
||||||
|
/* widening multiply-add: 16->32 bit lanes */
|
||||||
|
movdqa xmm8, xmm0
|
||||||
|
pmaddwd xmm8, xmm1
|
||||||
|
paddd xmm3, xmm8
|
||||||
|
|
||||||
|
/* high-half multiply */
|
||||||
|
movdqa xmm9, xmm0
|
||||||
|
pmulhw xmm9, xmm1
|
||||||
|
pxor xmm2, xmm9
|
||||||
|
|
||||||
|
/* shifts and logic */
|
||||||
|
movdqa xmm10, xmm2
|
||||||
|
pslld xmm10, 3
|
||||||
|
psrld xmm2, 29
|
||||||
|
por xmm2, xmm10
|
||||||
|
pand xmm3, xmm4
|
||||||
|
|
||||||
|
/* min/max */
|
||||||
|
pmaxsw xmm5, xmm0
|
||||||
|
pminsw xmm6, xmm1
|
||||||
|
|
||||||
|
/* byte average and sum-of-absolute-differences */
|
||||||
|
pavgb xmm7, xmm4
|
||||||
|
movdqa xmm11, xmm0
|
||||||
|
psadbw xmm11, xmm5
|
||||||
|
paddw xmm4, xmm11
|
||||||
|
|
||||||
|
/* lane shuffle: reverse the four 32-bit lanes */
|
||||||
|
pshufd xmm0, xmm0, 0x1B
|
||||||
|
pxor xmm1, xmm0
|
||||||
|
|
||||||
|
/* single-precision float SIMD: multiply-add plus the reciprocal and
|
||||||
|
* rsqrt estimates that shader-style code leans on */
|
||||||
|
movaps xmm15, xmm12
|
||||||
|
mulps xmm15, xmm13
|
||||||
|
addps xmm14, xmm15
|
||||||
|
rcpps xmm8, xmm12
|
||||||
|
rsqrtps xmm9, xmm14
|
||||||
|
addps xmm14, xmm8
|
||||||
|
mulps xmm12, xmm9
|
||||||
|
|
||||||
|
dec rdi
|
||||||
|
jnz .Lsd_loop
|
||||||
|
|
||||||
|
/* fold the eight integer vectors together */
|
||||||
|
pxor xmm0, xmm1
|
||||||
|
pxor xmm2, xmm3
|
||||||
|
pxor xmm4, xmm5
|
||||||
|
pxor xmm6, xmm7
|
||||||
|
pxor xmm0, xmm2
|
||||||
|
pxor xmm4, xmm6
|
||||||
|
pxor xmm0, xmm4
|
||||||
|
|
||||||
|
/* fold in the float accumulator (truncate to int lanes) */
|
||||||
|
cvttps2dq xmm14, xmm14
|
||||||
|
pxor xmm0, xmm14
|
||||||
|
|
||||||
|
/* horizontal add of the four 32-bit lanes -> single checksum */
|
||||||
|
pshufd xmm1, xmm0, 0x4E
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
pshufd xmm1, xmm0, 0xB1
|
||||||
|
paddd xmm0, xmm1
|
||||||
|
movd eax, xmm0
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lsd_zero:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
FN_END(fm_simd)
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_compress(const uint8_t *src, uint64_t len, uint32_t *ht)
|
||||||
|
* [rdi, rsi, rdx]
|
||||||
|
*
|
||||||
|
* The match-finding inner loop of an LZ77 compressor (the LZ4 fast strategy):
|
||||||
|
* hash the next 4 bytes, probe a single-entry-per-bucket table, verify, then
|
||||||
|
* extend. Branch-heavy with a data-dependent, cache-missing table probe.
|
||||||
|
*
|
||||||
|
* ht must hold 1<<16 uint32_t (256 KiB); this routine clears it itself.
|
||||||
|
* Returns the encoded size in bytes.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_compress)
|
||||||
|
push rbp
|
||||||
|
push rbx
|
||||||
|
push r12
|
||||||
|
push r13
|
||||||
|
push r14
|
||||||
|
push r15
|
||||||
|
|
||||||
|
mov r12, rdi /* src */
|
||||||
|
mov r13, rdx /* ht */
|
||||||
|
|
||||||
|
/* clear the hash table: 1<<16 entries * 4 bytes = 262144 bytes */
|
||||||
|
pxor xmm0, xmm0
|
||||||
|
xor rax, rax
|
||||||
|
mov ecx, 262144
|
||||||
|
.Lcm_clear:
|
||||||
|
movdqu [r13 + rax], xmm0
|
||||||
|
movdqu [r13 + rax + 16], xmm0
|
||||||
|
movdqu [r13 + rax + 32], xmm0
|
||||||
|
movdqu [r13 + rax + 48], xmm0
|
||||||
|
add rax, 64
|
||||||
|
cmp rax, rcx
|
||||||
|
jb .Lcm_clear
|
||||||
|
|
||||||
|
cmp rsi, 16
|
||||||
|
jb .Lcm_tiny
|
||||||
|
|
||||||
|
mov r14, r12 /* ip */
|
||||||
|
mov r15, r12 /* anchor */
|
||||||
|
lea rbx, [r12 + rsi] /* end */
|
||||||
|
lea r10, [rbx - 12] /* mflimit = end - 12 */
|
||||||
|
xor ebp, ebp /* outsize */
|
||||||
|
|
||||||
|
.Lcm_loop:
|
||||||
|
cmp r14, r10
|
||||||
|
jae .Lcm_flush
|
||||||
|
|
||||||
|
mov eax, [r14] /* seq = load32(ip) */
|
||||||
|
imul eax, eax, 0x9E3779B1 /* * Knuth prime 2654435761 */
|
||||||
|
shr eax, 16 /* h = (seq*prime) >> 16 */
|
||||||
|
|
||||||
|
mov ecx, [r13 + rax*4] /* ref_off = ht[h] */
|
||||||
|
mov rdx, r14
|
||||||
|
sub rdx, r12 /* cur_off = ip - src */
|
||||||
|
mov [r13 + rax*4], edx /* ht[h] = cur_off */
|
||||||
|
|
||||||
|
lea rsi, [r12 + rcx] /* ref = src + ref_off */
|
||||||
|
cmp rsi, r14
|
||||||
|
jae .Lcm_no_match /* ref must be strictly behind ip */
|
||||||
|
|
||||||
|
mov rax, r14
|
||||||
|
sub rax, rsi /* distance */
|
||||||
|
cmp rax, 65536
|
||||||
|
jae .Lcm_no_match /* 16-bit offset window */
|
||||||
|
|
||||||
|
mov eax, [rsi]
|
||||||
|
cmp eax, [r14] /* verify the 4-byte match */
|
||||||
|
jne .Lcm_no_match
|
||||||
|
|
||||||
|
/* match confirmed: extend it byte by byte */
|
||||||
|
mov r9, 4 /* ml */
|
||||||
|
.Lcm_extend:
|
||||||
|
lea rax, [r14 + r9]
|
||||||
|
cmp rax, rbx /* ip + ml vs end */
|
||||||
|
jae .Lcm_emit
|
||||||
|
mov cl, [r14 + r9]
|
||||||
|
cmp cl, [rsi + r9]
|
||||||
|
jne .Lcm_emit
|
||||||
|
inc r9
|
||||||
|
jmp .Lcm_extend
|
||||||
|
|
||||||
|
.Lcm_emit:
|
||||||
|
/* token(1) + offset(2) + literals + varint extensions */
|
||||||
|
mov rax, r14
|
||||||
|
sub rax, r15 /* literal run length */
|
||||||
|
add rbp, rax
|
||||||
|
add rbp, 3
|
||||||
|
cmp rax, 15
|
||||||
|
jb .Lcm_no_lit_ext
|
||||||
|
inc rbp /* literal-length extension byte */
|
||||||
|
.Lcm_no_lit_ext:
|
||||||
|
cmp r9, 19
|
||||||
|
jb .Lcm_no_ml_ext
|
||||||
|
inc rbp /* match-length extension byte */
|
||||||
|
.Lcm_no_ml_ext:
|
||||||
|
add r14, r9 /* ip += ml */
|
||||||
|
mov r15, r14 /* anchor = ip */
|
||||||
|
jmp .Lcm_loop
|
||||||
|
|
||||||
|
.Lcm_no_match:
|
||||||
|
inc r14
|
||||||
|
jmp .Lcm_loop
|
||||||
|
|
||||||
|
.Lcm_flush:
|
||||||
|
/* trailing literals */
|
||||||
|
mov rax, rbx
|
||||||
|
sub rax, r15 /* end - anchor */
|
||||||
|
add rbp, rax
|
||||||
|
add rbp, 1
|
||||||
|
mov rax, rbp
|
||||||
|
jmp .Lcm_ret
|
||||||
|
|
||||||
|
.Lcm_tiny:
|
||||||
|
lea rax, [rsi + 1] /* len + 1 */
|
||||||
|
|
||||||
|
.Lcm_ret:
|
||||||
|
pop r15
|
||||||
|
pop r14
|
||||||
|
pop r13
|
||||||
|
pop r12
|
||||||
|
pop rbx
|
||||||
|
pop rbp
|
||||||
|
ret
|
||||||
|
FN_END(fm_compress)
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_chacha20(uint8_t *buf, uint64_t len,
|
||||||
|
* const uint8_t key[32], uint64_t rounds)
|
||||||
|
* [rdi, rsi, rdx, rcx]
|
||||||
|
*
|
||||||
|
* ChaCha20 stream cipher, SSE2, four 128-bit state rows. Chosen over AES for
|
||||||
|
* the same reason the NEON file chose it: the AES-NI extension is optional, so
|
||||||
|
* an AES-instruction benchmark would fault (#UD) on cores without it. ChaCha20
|
||||||
|
* needs only baseline SSE2 and is a real, widely deployed cipher.
|
||||||
|
*
|
||||||
|
* len is rounded down to a multiple of 64. `rounds` = passes over the buffer.
|
||||||
|
* Returns a checksum of the keystream output.
|
||||||
|
* =================================================================== */
|
||||||
|
|
||||||
|
/* rotate each 32-bit lane left by n, via shift-left + shift-right + or */
|
||||||
|
#define ROL32(v, n) \
|
||||||
|
movdqa xmm14, v ;\
|
||||||
|
pslld v, n ;\
|
||||||
|
psrld xmm14, (32 - (n)) ;\
|
||||||
|
por v, xmm14
|
||||||
|
|
||||||
|
/* one ChaCha quarter-round over rows a,b,c,d (xmm14 is scratch, via ROL32) */
|
||||||
|
#define QROUND(a, b, c, d) \
|
||||||
|
paddd a, b ;\
|
||||||
|
pxor d, a ;\
|
||||||
|
ROL32(d, 16) ;\
|
||||||
|
paddd c, d ;\
|
||||||
|
pxor b, c ;\
|
||||||
|
ROL32(b, 12) ;\
|
||||||
|
paddd a, b ;\
|
||||||
|
pxor d, a ;\
|
||||||
|
ROL32(d, 8) ;\
|
||||||
|
paddd c, d ;\
|
||||||
|
pxor b, c ;\
|
||||||
|
ROL32(b, 7)
|
||||||
|
|
||||||
|
FN_BEGIN(fm_chacha20)
|
||||||
|
and rsi, -64 /* whole 64-byte blocks only */
|
||||||
|
jz .Lcc_zero
|
||||||
|
test rcx, rcx
|
||||||
|
jz .Lcc_zero
|
||||||
|
|
||||||
|
/* xmm4..7 hold the base state */
|
||||||
|
movdqa xmm4, [rip + .Lcc_sigma] /* "expand 32-byte k" */
|
||||||
|
movdqu xmm5, [rdx] /* key[0..15] */
|
||||||
|
movdqu xmm6, [rdx + 16] /* key[16..31] */
|
||||||
|
pxor xmm7, xmm7 /* counter || nonce = 0 */
|
||||||
|
|
||||||
|
pxor xmm13, xmm13 /* running checksum */
|
||||||
|
xor r8, r8 /* block counter value */
|
||||||
|
|
||||||
|
.Lcc_pass:
|
||||||
|
xor r9, r9 /* byte offset into buf */
|
||||||
|
|
||||||
|
.Lcc_block:
|
||||||
|
/* working state = base state, block counter in lane 0 of row 3.
|
||||||
|
* Row 3 is all-zero (nonce and counter), so a plain movd both sets
|
||||||
|
* the counter lane and clears the nonce lanes. */
|
||||||
|
movdqa xmm0, xmm4
|
||||||
|
movdqa xmm1, xmm5
|
||||||
|
movdqa xmm2, xmm6
|
||||||
|
movd xmm3, r8d
|
||||||
|
|
||||||
|
/* keep originals for the final feed-forward add */
|
||||||
|
movdqa xmm8, xmm0
|
||||||
|
movdqa xmm9, xmm1
|
||||||
|
movdqa xmm10, xmm2
|
||||||
|
movdqa xmm11, xmm3
|
||||||
|
|
||||||
|
mov r10d, 10 /* 10 double rounds = 20 rounds */
|
||||||
|
.Lcc_rounds:
|
||||||
|
/* column round */
|
||||||
|
QROUND(xmm0, xmm1, xmm2, xmm3)
|
||||||
|
|
||||||
|
/* rotate lanes to form the diagonals */
|
||||||
|
pshufd xmm1, xmm1, 0x39 /* <<< 1 lane */
|
||||||
|
pshufd xmm2, xmm2, 0x4E /* <<< 2 lanes */
|
||||||
|
pshufd xmm3, xmm3, 0x93 /* <<< 3 lanes */
|
||||||
|
|
||||||
|
/* diagonal round */
|
||||||
|
QROUND(xmm0, xmm1, xmm2, xmm3)
|
||||||
|
|
||||||
|
/* undo the lane rotation */
|
||||||
|
pshufd xmm1, xmm1, 0x93
|
||||||
|
pshufd xmm2, xmm2, 0x4E
|
||||||
|
pshufd xmm3, xmm3, 0x39
|
||||||
|
|
||||||
|
dec r10d
|
||||||
|
jnz .Lcc_rounds
|
||||||
|
|
||||||
|
/* feed-forward: keystream = working + original */
|
||||||
|
paddd xmm0, xmm8
|
||||||
|
paddd xmm1, xmm9
|
||||||
|
paddd xmm2, xmm10
|
||||||
|
paddd xmm3, xmm11
|
||||||
|
|
||||||
|
/* XOR the keystream into the buffer */
|
||||||
|
lea rax, [rdi + r9]
|
||||||
|
movdqu xmm12, [rax]
|
||||||
|
pxor xmm12, xmm0
|
||||||
|
movdqu [rax], xmm12
|
||||||
|
movdqu xmm12, [rax + 16]
|
||||||
|
pxor xmm12, xmm1
|
||||||
|
movdqu [rax + 16], xmm12
|
||||||
|
movdqu xmm12, [rax + 32]
|
||||||
|
pxor xmm12, xmm2
|
||||||
|
movdqu [rax + 32], xmm12
|
||||||
|
movdqu xmm12, [rax + 48]
|
||||||
|
pxor xmm12, xmm3
|
||||||
|
movdqu [rax + 48], xmm12
|
||||||
|
|
||||||
|
/* accumulate a checksum of the keystream */
|
||||||
|
pxor xmm13, xmm0
|
||||||
|
pxor xmm13, xmm3
|
||||||
|
|
||||||
|
inc r8 /* counter++ */
|
||||||
|
add r9, 64
|
||||||
|
cmp r9, rsi
|
||||||
|
jb .Lcc_block
|
||||||
|
|
||||||
|
dec rcx
|
||||||
|
jnz .Lcc_pass
|
||||||
|
|
||||||
|
/* horizontal add of the checksum lanes */
|
||||||
|
pshufd xmm0, xmm13, 0x4E
|
||||||
|
paddd xmm13, xmm0
|
||||||
|
pshufd xmm0, xmm13, 0xB1
|
||||||
|
paddd xmm13, xmm0
|
||||||
|
movd eax, xmm13
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lcc_zero:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
FN_END(fm_chacha20)
|
||||||
|
|
||||||
|
.p2align 4
|
||||||
|
.Lcc_sigma:
|
||||||
|
.long 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_physics(double *bodies, uint64_t n, uint64_t steps)
|
||||||
|
* [rdi, rsi, rdx]
|
||||||
|
*
|
||||||
|
* Direct-summation N-body gravity, O(n^2) per step, double precision.
|
||||||
|
* Layout per body, 8 doubles (64 bytes): [x y z mass vx vy vz pad].
|
||||||
|
* The 1/sqrt is a real sqrtsd+divsd (not the rsqrt estimate), exercising the
|
||||||
|
* divide/sqrt unit the way physics code does. Returns a velocity checksum.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_physics)
|
||||||
|
test rsi, rsi
|
||||||
|
jz .Lph_zero
|
||||||
|
test rdx, rdx
|
||||||
|
jz .Lph_zero
|
||||||
|
|
||||||
|
movsd xmm13, [rip + .Lph_dt] /* dt */
|
||||||
|
movsd xmm14, [rip + .Lph_eps2] /* eps^2 */
|
||||||
|
movsd xmm15, [rip + .Lph_one] /* 1.0 */
|
||||||
|
|
||||||
|
.Lph_step:
|
||||||
|
xor r8, r8 /* i */
|
||||||
|
|
||||||
|
.Lph_body_i:
|
||||||
|
mov rax, r8
|
||||||
|
shl rax, 6 /* i * 64 */
|
||||||
|
lea r9, [rdi + rax] /* &bodies[i] */
|
||||||
|
|
||||||
|
movsd xmm0, [r9] /* xi */
|
||||||
|
movsd xmm1, [r9 + 8] /* yi */
|
||||||
|
movsd xmm2, [r9 + 16] /* zi */
|
||||||
|
|
||||||
|
xorpd xmm3, xmm3 /* ax */
|
||||||
|
xorpd xmm4, xmm4 /* ay */
|
||||||
|
xorpd xmm5, xmm5 /* az */
|
||||||
|
|
||||||
|
xor r10, r10 /* j */
|
||||||
|
mov r11, rdi /* &bodies[j] */
|
||||||
|
|
||||||
|
.Lph_body_j:
|
||||||
|
movsd xmm6, [r11] /* xj */
|
||||||
|
movsd xmm7, [r11 + 8] /* yj */
|
||||||
|
movsd xmm8, [r11 + 16] /* zj */
|
||||||
|
movsd xmm9, [r11 + 24] /* mj */
|
||||||
|
|
||||||
|
subsd xmm6, xmm0 /* dx */
|
||||||
|
subsd xmm7, xmm1 /* dy */
|
||||||
|
subsd xmm8, xmm2 /* dz */
|
||||||
|
|
||||||
|
/* r2 = dx*dx + dy*dy + dz*dz + eps^2 (>= eps^2, so the i==j self-term
|
||||||
|
* is finite and contributes exactly 0 below) */
|
||||||
|
movsd xmm10, xmm6
|
||||||
|
mulsd xmm10, xmm6
|
||||||
|
movsd xmm11, xmm7
|
||||||
|
mulsd xmm11, xmm7
|
||||||
|
addsd xmm10, xmm11
|
||||||
|
movsd xmm11, xmm8
|
||||||
|
mulsd xmm11, xmm8
|
||||||
|
addsd xmm10, xmm11
|
||||||
|
addsd xmm10, xmm14 /* + eps^2 */
|
||||||
|
|
||||||
|
sqrtsd xmm10, xmm10 /* r */
|
||||||
|
movsd xmm11, xmm15
|
||||||
|
divsd xmm11, xmm10 /* 1/r */
|
||||||
|
movsd xmm12, xmm11
|
||||||
|
mulsd xmm12, xmm11 /* 1/r^2 */
|
||||||
|
mulsd xmm12, xmm11 /* 1/r^3 */
|
||||||
|
mulsd xmm12, xmm9 /* m/r^3 */
|
||||||
|
|
||||||
|
mulsd xmm6, xmm12 /* dx * m/r^3 */
|
||||||
|
addsd xmm3, xmm6
|
||||||
|
mulsd xmm7, xmm12
|
||||||
|
addsd xmm4, xmm7
|
||||||
|
mulsd xmm8, xmm12
|
||||||
|
addsd xmm5, xmm8
|
||||||
|
|
||||||
|
add r11, 64
|
||||||
|
inc r10
|
||||||
|
cmp r10, rsi
|
||||||
|
jb .Lph_body_j
|
||||||
|
|
||||||
|
/* v += a * dt */
|
||||||
|
movsd xmm6, [r9 + 32] /* vx */
|
||||||
|
movsd xmm7, [r9 + 40] /* vy */
|
||||||
|
movsd xmm8, [r9 + 48] /* vz */
|
||||||
|
mulsd xmm3, xmm13
|
||||||
|
addsd xmm6, xmm3
|
||||||
|
mulsd xmm4, xmm13
|
||||||
|
addsd xmm7, xmm4
|
||||||
|
mulsd xmm5, xmm13
|
||||||
|
addsd xmm8, xmm5
|
||||||
|
movsd [r9 + 32], xmm6
|
||||||
|
movsd [r9 + 40], xmm7
|
||||||
|
movsd [r9 + 48], xmm8
|
||||||
|
|
||||||
|
inc r8
|
||||||
|
cmp r8, rsi
|
||||||
|
jb .Lph_body_i
|
||||||
|
|
||||||
|
/* second pass: x += v * dt (positions move only after all forces) */
|
||||||
|
xor r8, r8
|
||||||
|
mov r11, rdi
|
||||||
|
.Lph_integrate:
|
||||||
|
movsd xmm0, [r11]
|
||||||
|
movsd xmm1, [r11 + 8]
|
||||||
|
movsd xmm2, [r11 + 16]
|
||||||
|
movsd xmm6, [r11 + 32]
|
||||||
|
movsd xmm7, [r11 + 40]
|
||||||
|
movsd xmm8, [r11 + 48]
|
||||||
|
mulsd xmm6, xmm13
|
||||||
|
addsd xmm0, xmm6
|
||||||
|
mulsd xmm7, xmm13
|
||||||
|
addsd xmm1, xmm7
|
||||||
|
mulsd xmm8, xmm13
|
||||||
|
addsd xmm2, xmm8
|
||||||
|
movsd [r11], xmm0
|
||||||
|
movsd [r11 + 8], xmm1
|
||||||
|
movsd [r11 + 16], xmm2
|
||||||
|
add r11, 64
|
||||||
|
inc r8
|
||||||
|
cmp r8, rsi
|
||||||
|
jb .Lph_integrate
|
||||||
|
|
||||||
|
dec rdx
|
||||||
|
jnz .Lph_step
|
||||||
|
|
||||||
|
/* checksum: sum of all velocity components */
|
||||||
|
xorpd xmm0, xmm0
|
||||||
|
xor r8, r8
|
||||||
|
mov r11, rdi
|
||||||
|
.Lph_sum:
|
||||||
|
movsd xmm6, [r11 + 32]
|
||||||
|
movsd xmm7, [r11 + 40]
|
||||||
|
movsd xmm8, [r11 + 48]
|
||||||
|
addsd xmm0, xmm6
|
||||||
|
addsd xmm0, xmm7
|
||||||
|
addsd xmm0, xmm8
|
||||||
|
add r11, 64
|
||||||
|
inc r8
|
||||||
|
cmp r8, rsi
|
||||||
|
jb .Lph_sum
|
||||||
|
|
||||||
|
movq rax, xmm0
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lph_zero:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
FN_END(fm_physics)
|
||||||
|
|
||||||
|
.p2align 4
|
||||||
|
.Lph_dt:
|
||||||
|
.double 0.0078125 /* dt */
|
||||||
|
.Lph_eps2:
|
||||||
|
.double 0.0625 /* eps^2 */
|
||||||
|
.Lph_one:
|
||||||
|
.double 1.0
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_sort(uint32_t *a, uint64_t n) [rdi = a, rsi = n]
|
||||||
|
*
|
||||||
|
* In-place heapsort: no recursion or explicit stack, aggressively
|
||||||
|
* branch-unpredictable, with scattered memory access - it stresses the branch
|
||||||
|
* predictor and the cache hierarchy. Returns an order-sensitive checksum,
|
||||||
|
* which also verifies the sort.
|
||||||
|
*
|
||||||
|
* Uses only caller-saved registers, so no prologue is needed; the internal
|
||||||
|
* siftdown is reached with `call` (contract below).
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_sort)
|
||||||
|
cmp rsi, 2
|
||||||
|
jb .Lst_trivial
|
||||||
|
|
||||||
|
/* ---- build the max-heap: for i = n/2 - 1 down to 0 ---- */
|
||||||
|
mov r8, rsi
|
||||||
|
shr r8, 1 /* i = n/2 */
|
||||||
|
.Lst_build:
|
||||||
|
test r8, r8
|
||||||
|
jz .Lst_extract
|
||||||
|
dec r8 /* i-- */
|
||||||
|
mov rcx, r8 /* root */
|
||||||
|
mov rdx, rsi /* end */
|
||||||
|
call .Lst_siftdown
|
||||||
|
test r8, r8
|
||||||
|
jnz .Lst_build
|
||||||
|
|
||||||
|
/* ---- extract: for end = n-1 down to 1 ---- */
|
||||||
|
.Lst_extract:
|
||||||
|
mov r9, rsi
|
||||||
|
dec r9 /* end = n-1 */
|
||||||
|
.Lst_extract_loop:
|
||||||
|
test r9, r9
|
||||||
|
jz .Lst_checksum
|
||||||
|
|
||||||
|
/* swap a[0] and a[end] */
|
||||||
|
mov eax, [rdi]
|
||||||
|
mov r10d, [rdi + r9*4]
|
||||||
|
mov [rdi], r10d
|
||||||
|
mov [rdi + r9*4], eax
|
||||||
|
|
||||||
|
xor ecx, ecx /* root = 0 */
|
||||||
|
mov rdx, r9 /* end = end */
|
||||||
|
call .Lst_siftdown
|
||||||
|
|
||||||
|
dec r9
|
||||||
|
jmp .Lst_extract_loop
|
||||||
|
|
||||||
|
/* ---- order-sensitive checksum ---- */
|
||||||
|
.Lst_checksum:
|
||||||
|
xor eax, eax
|
||||||
|
xor rcx, rcx /* index */
|
||||||
|
.Lst_cksum_loop:
|
||||||
|
mov r10d, [rdi + rcx*4]
|
||||||
|
xor rax, r10
|
||||||
|
ror rax, 7
|
||||||
|
add rax, r10
|
||||||
|
inc rcx
|
||||||
|
cmp rcx, rsi
|
||||||
|
jb .Lst_cksum_loop
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lst_trivial:
|
||||||
|
xor eax, eax
|
||||||
|
test rsi, rsi
|
||||||
|
jz .Lst_trivial_ret
|
||||||
|
mov eax, [rdi]
|
||||||
|
.Lst_trivial_ret:
|
||||||
|
ret
|
||||||
|
|
||||||
|
/* ---- local helper: siftdown(root = rcx, end = rdx)
|
||||||
|
* base a = rdi; clobbers rax, rcx, r10, r11 only. r8 (build i),
|
||||||
|
* r9 (extract end), rsi (n), rdx (end) all survive. ---- */
|
||||||
|
.Lst_siftdown:
|
||||||
|
mov r11, rcx /* root */
|
||||||
|
.Lst_sift_loop:
|
||||||
|
lea r10, [r11 + r11 + 1] /* child = 2*root + 1 */
|
||||||
|
cmp r10, rdx
|
||||||
|
jae .Lst_sift_done /* no children */
|
||||||
|
|
||||||
|
lea rax, [r10 + 1] /* right = child + 1 */
|
||||||
|
cmp rax, rdx
|
||||||
|
jae .Lst_sift_have_child /* no right child */
|
||||||
|
mov ecx, [rdi + rax*4] /* a[right] */
|
||||||
|
cmp ecx, [rdi + r10*4] /* a[right] vs a[child] */
|
||||||
|
jbe .Lst_sift_have_child /* keep child if a[right] <= it */
|
||||||
|
mov r10, rax /* else child = right */
|
||||||
|
|
||||||
|
.Lst_sift_have_child:
|
||||||
|
mov eax, [rdi + r11*4] /* a[root] */
|
||||||
|
mov ecx, [rdi + r10*4] /* a[child] */
|
||||||
|
cmp eax, ecx
|
||||||
|
jae .Lst_sift_done /* heap property holds */
|
||||||
|
|
||||||
|
/* swap and descend */
|
||||||
|
mov [rdi + r11*4], ecx
|
||||||
|
mov [rdi + r10*4], eax
|
||||||
|
mov r11, r10
|
||||||
|
jmp .Lst_sift_loop
|
||||||
|
|
||||||
|
.Lst_sift_done:
|
||||||
|
ret
|
||||||
|
FN_END(fm_sort)
|
||||||
|
|
||||||
|
|
||||||
|
/* ===================================================================
|
||||||
|
* uint64_t fm_chase(void **ptrs, uint64_t steps) [rdi = ptrs, rsi = steps]
|
||||||
|
*
|
||||||
|
* Pointer chase around a randomised cycle. Every load depends on the previous
|
||||||
|
* one, so nothing can be prefetched, overlapped or reordered - this measures
|
||||||
|
* the pure serial latency of the memory hierarchy. The truest single-threaded
|
||||||
|
* test in the suite.
|
||||||
|
* =================================================================== */
|
||||||
|
FN_BEGIN(fm_chase)
|
||||||
|
test rsi, rsi
|
||||||
|
jz .Lch_zero
|
||||||
|
mov rax, rdi /* p = ptrs */
|
||||||
|
mov rcx, rsi
|
||||||
|
|
||||||
|
.Lch_loop:
|
||||||
|
mov rax, [rax]
|
||||||
|
dec rcx
|
||||||
|
jnz .Lch_loop
|
||||||
|
|
||||||
|
sub rax, rdi /* final offset, keeps p live */
|
||||||
|
ret
|
||||||
|
|
||||||
|
.Lch_zero:
|
||||||
|
xor eax, eax
|
||||||
|
ret
|
||||||
|
FN_END(fm_chase)
|
||||||
|
|
||||||
|
|
||||||
|
#if defined(__ELF__)
|
||||||
|
.section .note.GNU-stack, "", @progbits
|
||||||
|
#endif
|
||||||
+709
@@ -0,0 +1,709 @@
|
|||||||
|
/*
|
||||||
|
* fossmark - 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
|
||||||
|
* fossmark.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 fossmark reports two composite scores, a
|
||||||
|
* SINGLECORE and a MULTICORE, from the same tests and the same weights.
|
||||||
|
*
|
||||||
|
* Build: cc -O2 -pthread main.c fossmark.S -o fossmark -lm
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
/* ---------- platform identification (for the banner only) ---------- */
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
# define FM_OS "Windows"
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
# define FM_OS "macOS"
|
||||||
|
#elif defined(__linux__)
|
||||||
|
# define FM_OS "Linux"
|
||||||
|
#else
|
||||||
|
# define FM_OS "POSIX"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__aarch64__) || defined(_M_ARM64)
|
||||||
|
# define FM_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 FM_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"
|
||||||
|
#else
|
||||||
|
# define FM_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 <windows.h>
|
||||||
|
static double now_seconds(void)
|
||||||
|
{
|
||||||
|
LARGE_INTEGER f, t;
|
||||||
|
QueryPerformanceFrequency(&f);
|
||||||
|
QueryPerformanceCounter(&t);
|
||||||
|
return (double)t.QuadPart / (double)f.QuadPart;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
# include <time.h>
|
||||||
|
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 fm_int_math(uint64_t iters);
|
||||||
|
extern uint64_t fm_fp_math(uint64_t iters);
|
||||||
|
extern uint64_t fm_primes(uint64_t limit, uint8_t *sieve);
|
||||||
|
extern uint64_t fm_simd(uint64_t iters, void *buf);
|
||||||
|
extern uint64_t fm_compress(const uint8_t *src, uint64_t len, uint32_t *ht);
|
||||||
|
extern uint64_t fm_chacha20(uint8_t *buf, uint64_t len,
|
||||||
|
const uint8_t key[32], uint64_t rounds);
|
||||||
|
extern uint64_t fm_physics(double *bodies, uint64_t n, uint64_t steps);
|
||||||
|
extern uint64_t fm_sort(uint32_t *a, uint64_t n);
|
||||||
|
extern uint64_t fm_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 */
|
||||||
|
#define CHASE_NODES (1u << 21) /* 16 MiB cycle, > any L2 */
|
||||||
|
|
||||||
|
#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:
|
||||||
|
*
|
||||||
|
* FM_REF_* the reference rate (this machine's measured rate). A machine
|
||||||
|
* matching the reference scores FM_TARGET_SCORE on that test.
|
||||||
|
* FM_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 = FM_TARGET_SCORE * (rate_i / FM_REF_i)
|
||||||
|
* Overall score: Overall = FM_TARGET_SCORE *
|
||||||
|
* exp( Sum(w_i * ln(rate_i/FM_REF_i)) / Sum(w_i) )
|
||||||
|
*
|
||||||
|
* On the reference machine every ratio is 1, so every S_i and the overall come
|
||||||
|
* out to exactly FM_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 FM_TARGET_SCORE 10000.0 /* reference-machine overall */
|
||||||
|
|
||||||
|
/* Reference rates: this machine, in each test's native unit (see tests[]). */
|
||||||
|
#define FM_REF_INT 3086.0 /* Mops/s */
|
||||||
|
#define FM_REF_FP 1682.0 /* Mops/s */
|
||||||
|
#define FM_REF_PRIMES 812.0 /* Mcand/s */
|
||||||
|
#define FM_REF_SIMD 6576.0 /* Mops/s */
|
||||||
|
#define FM_REF_COMPRESS 674.0 /* MB/s */
|
||||||
|
#define FM_REF_CRYPTO 406.0 /* MB/s */
|
||||||
|
#define FM_REF_PHYSICS 631.0 /* Mpair/s */
|
||||||
|
#define FM_REF_SORT 363.0 /* Mkey-cmp/s*/
|
||||||
|
#define FM_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 FM_WEIGHT_INT 20.0 /* general-purpose ALU: everything */
|
||||||
|
#define FM_WEIGHT_CHASE 16.0 /* memory latency: responsiveness */
|
||||||
|
#define FM_WEIGHT_COMPRESS 14.0 /* web, storage, RAM compression */
|
||||||
|
#define FM_WEIGHT_SORT 12.0 /* general data-structure work */
|
||||||
|
#define FM_WEIGHT_SIMD 11.0 /* codecs, mem/string ops, parsing */
|
||||||
|
#define FM_WEIGHT_FP 9.0 /* spreadsheets, app/media math */
|
||||||
|
#define FM_WEIGHT_CRYPTO 8.0 /* TLS, disk encryption (small frac) */
|
||||||
|
#define FM_WEIGHT_PRIMES 6.0 /* synthetic ALU+memory proxy */
|
||||||
|
#define FM_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, "fossmark: 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- 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 */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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 fm_int_math(n * 100000);
|
||||||
|
}
|
||||||
|
static uint64_t run_fp(uint64_t n, struct workspace *ws)
|
||||||
|
{
|
||||||
|
(void)ws;
|
||||||
|
return fm_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 += fm_primes(PRIME_LIMIT, ws->sieve);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
static uint64_t run_simd(uint64_t n, struct workspace *ws)
|
||||||
|
{
|
||||||
|
return fm_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 += fm_compress(g_corpus, COMPRESS_LEN, ws->ht);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
static uint64_t run_crypto(uint64_t n, struct workspace *ws)
|
||||||
|
{
|
||||||
|
return fm_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 fm_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 ^= fm_sort(ws->sort_work, SORT_N);
|
||||||
|
}
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
static uint64_t run_chase(uint64_t n, struct workspace *ws)
|
||||||
|
{
|
||||||
|
return fm_chase(ws->chase, n * 1000000);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct test tests[] = {
|
||||||
|
{ "Integer Math", D_INT,
|
||||||
|
run_int, 20, 100000.0 * 24, "Mops/s",
|
||||||
|
FM_REF_INT, FM_WEIGHT_INT },
|
||||||
|
{ "Floating Point Math", D_FP,
|
||||||
|
run_fp, 20, 100000.0 * 20, "Mops/s",
|
||||||
|
FM_REF_FP, FM_WEIGHT_FP },
|
||||||
|
{ "Prime Numbers", "sieve of Eratosthenes to 2M",
|
||||||
|
run_primes, 1, (double)PRIME_LIMIT, "Mcand/s",
|
||||||
|
FM_REF_PRIMES, FM_WEIGHT_PRIMES },
|
||||||
|
{ "Extended Instructions",D_SIMD,
|
||||||
|
run_simd, 10, 100000.0 * 32, "Mops/s",
|
||||||
|
FM_REF_SIMD, FM_WEIGHT_SIMD },
|
||||||
|
{ "Compression", "LZ77 match finder, 4 MiB corpus",
|
||||||
|
run_compress, 1, (double)COMPRESS_LEN, "MB/s",
|
||||||
|
FM_REF_COMPRESS, FM_WEIGHT_COMPRESS },
|
||||||
|
{ "Encryption", "ChaCha20, 20 rounds, 1 MiB",
|
||||||
|
run_crypto, 4, (double)CIPHER_LEN, "MB/s",
|
||||||
|
FM_REF_CRYPTO, FM_WEIGHT_CRYPTO },
|
||||||
|
{ "Physics", "512-body direct-sum gravity",
|
||||||
|
run_physics, 4, (double)NBODY_N * NBODY_N, "Mpair/s",
|
||||||
|
FM_REF_PHYSICS, FM_WEIGHT_PHYSICS },
|
||||||
|
{ "Sorting", "heapsort, 1M uint32",
|
||||||
|
run_sort, 1, (double)SORT_N * 20, "Mkey-cmp/s",
|
||||||
|
FM_REF_SORT, FM_WEIGHT_SORT },
|
||||||
|
{ "Memory Latency", "dependent-load pointer chase, 16 MiB",
|
||||||
|
run_chase, 1, 1000000.0, "ns/access",
|
||||||
|
FM_REF_CHASE, FM_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,
|
||||||
|
"fossmark: %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 = FM_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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- output ---------- */
|
||||||
|
|
||||||
|
static void print_header(void)
|
||||||
|
{
|
||||||
|
printf("\n");
|
||||||
|
printf(" fossmark 1.0 - multi-core CPU benchmark\n");
|
||||||
|
printf(" ------------------------------------------------------------------\n");
|
||||||
|
printf(" platform: %s/%s\n", FM_OS, FM_ARCH);
|
||||||
|
printf(" cores: %ld (each test is run once on 1 core, once on all %ld)\n",
|
||||||
|
g_ncores, g_ncores);
|
||||||
|
printf("\n");
|
||||||
|
printf(" RATE/TIME/SCORE below are the all-core (multi-core) pass.\n");
|
||||||
|
printf("\n");
|
||||||
|
printf(" %-24s %12s %-11s %8s %9s\n",
|
||||||
|
"TEST", "RATE", "UNIT", "TIME", "SCORE");
|
||||||
|
printf(" --------------------------------------------------------------------------\n");
|
||||||
|
fflush(stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
struct result multi[NTESTS], single[NTESTS];
|
||||||
|
double multi_log_sum = 0.0, single_log_sum = 0.0;
|
||||||
|
double weight_sum = 0.0;
|
||||||
|
int verbose = 0;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for (i = 1; i < (size_t)argc; i++) {
|
||||||
|
if (strcmp(argv[i], "-v") == 0 ||
|
||||||
|
strcmp(argv[i], "--verbose") == 0) {
|
||||||
|
verbose = 1;
|
||||||
|
} else if (strcmp(argv[i], "-h") == 0 ||
|
||||||
|
strcmp(argv[i], "--help") == 0) {
|
||||||
|
printf("usage: %s [-v|--verbose]\n", argv[0]);
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
fprintf(stderr, "fossmark: unknown option '%s'\n",
|
||||||
|
argv[i]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
long n = sysconf(_SC_NPROCESSORS_ONLN);
|
||||||
|
g_ncores = n > 0 ? n : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n preparing workloads...");
|
||||||
|
fflush(stdout);
|
||||||
|
setup();
|
||||||
|
printf(" done\n");
|
||||||
|
|
||||||
|
print_header();
|
||||||
|
|
||||||
|
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 FM_TARGET_SCORE. Geometric rather
|
||||||
|
* than arithmetic so no single test dominates; weighted so tests count in
|
||||||
|
* proportion to their influence on everyday use (the FM_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.
|
||||||
|
*/
|
||||||
|
printf(" %-24s %44.0f\n", "MULTICORE SCORE",
|
||||||
|
exp(multi_log_sum / weight_sum));
|
||||||
|
printf(" %-24s %44.0f\n", "SINGLECORE SCORE",
|
||||||
|
exp(single_log_sum / weight_sum));
|
||||||
|
printf(" %-24s (weighted geometric means, reference machine = %.0f)\n",
|
||||||
|
"", FM_TARGET_SCORE);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
teardown();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,462 @@
|
|||||||
|
/*
|
||||||
|
* test_kernels.c - correctness checks for the fossmark 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 fossmark.S -o test_kernels -lm
|
||||||
|
* Exit status is 0 iff every check passes.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
extern uint64_t fm_int_math(uint64_t iters);
|
||||||
|
extern uint64_t fm_fp_math(uint64_t iters);
|
||||||
|
extern uint64_t fm_primes(uint64_t limit, uint8_t *sieve);
|
||||||
|
extern uint64_t fm_simd(uint64_t iters, void *buf);
|
||||||
|
extern uint64_t fm_compress(const uint8_t *src, uint64_t len, uint32_t *ht);
|
||||||
|
extern uint64_t fm_chacha20(uint8_t *buf, uint64_t len,
|
||||||
|
const uint8_t key[32], uint64_t rounds);
|
||||||
|
extern uint64_t fm_physics(double *bodies, uint64_t n, uint64_t steps);
|
||||||
|
extern uint64_t fm_sort(uint32_t *a, uint64_t n);
|
||||||
|
extern uint64_t fm_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. `fm_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 __thread int fm_primary = 1;
|
||||||
|
static long fm_ncores = 1;
|
||||||
|
|
||||||
|
static void ok(const char *what, int cond)
|
||||||
|
{
|
||||||
|
pthread_mutex_lock(&io_lock);
|
||||||
|
if (fm_primary) {
|
||||||
|
checks++;
|
||||||
|
if (cond) {
|
||||||
|
printf(" [ ok ] %s\n", what);
|
||||||
|
} else {
|
||||||
|
printf(" [FAIL] %s\n", what);
|
||||||
|
failures++;
|
||||||
|
}
|
||||||
|
} else if (!cond) {
|
||||||
|
/* a secondary core disagrees: surface it explicitly */
|
||||||
|
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. */
|
||||||
|
static void note(const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list ap;
|
||||||
|
|
||||||
|
if (!fm_primary)
|
||||||
|
return;
|
||||||
|
pthread_mutex_lock(&io_lock);
|
||||||
|
va_start(ap, fmt);
|
||||||
|
vprintf(fmt, ap);
|
||||||
|
va_end(ap);
|
||||||
|
pthread_mutex_unlock(&io_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Run `check` on every core simultaneously. The main thread is the primary;
|
||||||
|
* fm_ncores-1 workers run the same check as silent secondaries. */
|
||||||
|
static void *fm_worker(void *arg)
|
||||||
|
{
|
||||||
|
void (*check)(void) = *(void (**)(void))arg;
|
||||||
|
|
||||||
|
fm_primary = 0;
|
||||||
|
check();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parallel(void (*check)(void))
|
||||||
|
{
|
||||||
|
long extra = fm_ncores - 1;
|
||||||
|
pthread_t *th = NULL;
|
||||||
|
long i, spawned = 0;
|
||||||
|
|
||||||
|
if (extra > 0) {
|
||||||
|
th = calloc((size_t)extra, sizeof *th);
|
||||||
|
if (th) {
|
||||||
|
for (i = 0; i < extra; i++)
|
||||||
|
if (pthread_create(&th[spawned], NULL,
|
||||||
|
fm_worker, &check) == 0)
|
||||||
|
spawned++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
check(); /* primary runs on this thread */
|
||||||
|
|
||||||
|
for (i = 0; i < spawned; i++)
|
||||||
|
pthread_join(th[i], NULL);
|
||||||
|
free(th);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- reference implementations ---------- */
|
||||||
|
|
||||||
|
static uint64_t ref_prime_count(uint64_t limit)
|
||||||
|
{
|
||||||
|
uint8_t *s = calloc(limit, 1);
|
||||||
|
uint64_t count = 0, i, j;
|
||||||
|
|
||||||
|
for (i = 2; i * i < limit; i++)
|
||||||
|
if (!s[i])
|
||||||
|
for (j = i * i; j < limit; j += i)
|
||||||
|
s[j] = 1;
|
||||||
|
for (i = 2; i < limit; i++)
|
||||||
|
if (!s[i])
|
||||||
|
count++;
|
||||||
|
free(s);
|
||||||
|
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. */
|
||||||
|
#define ROTL32(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
|
||||||
|
|
||||||
|
static void ref_chacha_block(uint32_t out_words[16], const uint8_t key[32],
|
||||||
|
uint32_t counter, const uint8_t nonce[12])
|
||||||
|
{
|
||||||
|
static const uint32_t c[4] = {
|
||||||
|
0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
|
||||||
|
};
|
||||||
|
uint32_t s[16], x[16];
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
s[i] = c[i];
|
||||||
|
for (i = 0; i < 8; i++)
|
||||||
|
s[4 + i] = (uint32_t)key[4 * i] | (uint32_t)key[4 * i + 1] << 8 |
|
||||||
|
(uint32_t)key[4 * i + 2] << 16 |
|
||||||
|
(uint32_t)key[4 * i + 3] << 24;
|
||||||
|
s[12] = counter;
|
||||||
|
for (i = 0; i < 3; i++)
|
||||||
|
s[13 + i] = (uint32_t)nonce[4 * i] | (uint32_t)nonce[4 * i + 1] << 8 |
|
||||||
|
(uint32_t)nonce[4 * i + 2] << 16 |
|
||||||
|
(uint32_t)nonce[4 * i + 3] << 24;
|
||||||
|
|
||||||
|
memcpy(x, s, sizeof x);
|
||||||
|
#define QR(a, b, cc, d) \
|
||||||
|
x[a] += x[b]; x[d] ^= x[a]; x[d] = ROTL32(x[d], 16); \
|
||||||
|
x[cc] += x[d]; x[b] ^= x[cc]; x[b] = ROTL32(x[b], 12); \
|
||||||
|
x[a] += x[b]; x[d] ^= x[a]; x[d] = ROTL32(x[d], 8); \
|
||||||
|
x[cc] += x[d]; x[b] ^= x[cc]; x[b] = ROTL32(x[b], 7)
|
||||||
|
for (i = 0; i < 10; i++) {
|
||||||
|
QR(0, 4, 8, 12); QR(1, 5, 9, 13);
|
||||||
|
QR(2, 6, 10, 14); QR(3, 7, 11, 15);
|
||||||
|
QR(0, 5, 10, 15); QR(1, 6, 11, 12);
|
||||||
|
QR(2, 7, 8, 13); QR(3, 4, 9, 14);
|
||||||
|
}
|
||||||
|
#undef QR
|
||||||
|
for (i = 0; i < 16; i++)
|
||||||
|
out_words[i] = x[i] + s[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- checks ---------- */
|
||||||
|
|
||||||
|
static void check_int(void)
|
||||||
|
{
|
||||||
|
/* determinism and non-triviality: the checksum must be stable and
|
||||||
|
* must actually change with the iteration count */
|
||||||
|
uint64_t a = fm_int_math(1000);
|
||||||
|
uint64_t b = fm_int_math(1000);
|
||||||
|
uint64_t c = fm_int_math(2000);
|
||||||
|
|
||||||
|
ok("int_math is deterministic", a == b);
|
||||||
|
ok("int_math depends on iters", a != c);
|
||||||
|
ok("int_math(0) is zero", fm_int_math(0) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_fp(void)
|
||||||
|
{
|
||||||
|
uint64_t a = fm_fp_math(1000);
|
||||||
|
uint64_t b = fm_fp_math(1000);
|
||||||
|
double da;
|
||||||
|
|
||||||
|
memcpy(&da, &a, sizeof da);
|
||||||
|
ok("fp_math is deterministic", a == b);
|
||||||
|
ok("fp_math result is finite", isfinite(da));
|
||||||
|
ok("fp_math(0) is zero", fm_fp_math(0) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_primes(void)
|
||||||
|
{
|
||||||
|
enum { LIM = 1000000 };
|
||||||
|
uint8_t *sieve = malloc(LIM);
|
||||||
|
uint64_t got = fm_primes(LIM, sieve);
|
||||||
|
uint64_t ref = ref_prime_count(LIM);
|
||||||
|
|
||||||
|
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", fm_primes(10, sieve) == 4); /* 2,3,5,7 */
|
||||||
|
ok("primes < 2 == 0", fm_primes(2, sieve) == 0);
|
||||||
|
free(sieve);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_simd(void)
|
||||||
|
{
|
||||||
|
uint8_t *buf = aligned_alloc(16, 256);
|
||||||
|
uint64_t a, b;
|
||||||
|
|
||||||
|
memset(buf, 0xA5, 256);
|
||||||
|
a = fm_simd(500, buf);
|
||||||
|
memset(buf, 0xA5, 256);
|
||||||
|
b = fm_simd(500, buf);
|
||||||
|
ok("simd is deterministic", a == b);
|
||||||
|
ok("simd(0) is zero", fm_simd(0, buf) == 0);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_compress(void)
|
||||||
|
{
|
||||||
|
enum { N = 65536 };
|
||||||
|
uint8_t *src = malloc(N);
|
||||||
|
uint32_t *ht = malloc((1 << 16) * sizeof(uint32_t));
|
||||||
|
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 */
|
||||||
|
{
|
||||||
|
uint64_t st = 0x1234567890abcdefULL;
|
||||||
|
for (i = 0; i < N; i++) {
|
||||||
|
uint64_t z = (st += 0x9e3779b97f4a7c15ULL);
|
||||||
|
z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9ULL;
|
||||||
|
z = (z ^ (z >> 27)) * 0x94d049bb133111ebULL;
|
||||||
|
src[i] = (uint8_t)(z ^ (z >> 31));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
incompressible = fm_compress(src, N, ht);
|
||||||
|
|
||||||
|
/* all-zero data is maximally compressible: it must shrink hugely */
|
||||||
|
memset(src, 0, N);
|
||||||
|
compressible = fm_compress(src, N, ht);
|
||||||
|
|
||||||
|
note(" 64KiB random -> %llu bytes, 64KiB zeros -> %llu bytes\n",
|
||||||
|
(unsigned long long)incompressible,
|
||||||
|
(unsigned long long)compressible);
|
||||||
|
ok("compress expands random data", incompressible >= N);
|
||||||
|
ok("compress shrinks constant data", compressible < N / 10);
|
||||||
|
ok("compress is deterministic", fm_compress(src, N, ht) == compressible);
|
||||||
|
free(src);
|
||||||
|
free(ht);
|
||||||
|
}
|
||||||
|
|
||||||
|
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. */
|
||||||
|
{
|
||||||
|
uint32_t w[16];
|
||||||
|
uint8_t rnonce[12] = {0,0,0,9, 0,0,0,0x4a, 0,0,0,0};
|
||||||
|
uint8_t ks0[4];
|
||||||
|
for (i = 0; i < 32; i++)
|
||||||
|
key[i] = (uint8_t)i;
|
||||||
|
ref_chacha_block(w, key, 1, rnonce);
|
||||||
|
for (i = 0; i < 4; i++)
|
||||||
|
ks0[i] = (uint8_t)(w[0] >> (8 * i));
|
||||||
|
note(" ref keystream[0..3] = %02x %02x %02x %02x "
|
||||||
|
"(RFC 8439 expects 10 f1 e7 e4)\n",
|
||||||
|
ks0[0], ks0[1], ks0[2], ks0[3]);
|
||||||
|
ok("scalar ChaCha20 matches RFC 8439 vector",
|
||||||
|
ks0[0] == 0x10 && ks0[1] == 0xf1 &&
|
||||||
|
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. */
|
||||||
|
{
|
||||||
|
uint8_t buf[128];
|
||||||
|
uint8_t zero_nonce[12] = {0};
|
||||||
|
uint32_t ref0[16], ref1[16];
|
||||||
|
int match = 1;
|
||||||
|
|
||||||
|
for (i = 0; i < 32; i++)
|
||||||
|
key[i] = (uint8_t)(i * 5 + 1);
|
||||||
|
memset(buf, 0, sizeof buf); /* zeros -> raw keystream */
|
||||||
|
fm_chacha20(buf, sizeof buf, key, 1);
|
||||||
|
|
||||||
|
ref_chacha_block(ref0, key, 0, zero_nonce);
|
||||||
|
ref_chacha_block(ref1, key, 1, zero_nonce);
|
||||||
|
for (i = 0; i < 16; i++) {
|
||||||
|
uint32_t k0 = (uint32_t)buf[4 * i] |
|
||||||
|
(uint32_t)buf[4 * i + 1] << 8 |
|
||||||
|
(uint32_t)buf[4 * i + 2] << 16 |
|
||||||
|
(uint32_t)buf[4 * i + 3] << 24;
|
||||||
|
uint32_t k1 = (uint32_t)buf[64 + 4 * i] |
|
||||||
|
(uint32_t)buf[64 + 4 * i + 1] << 8 |
|
||||||
|
(uint32_t)buf[64 + 4 * i + 2] << 16 |
|
||||||
|
(uint32_t)buf[64 + 4 * i + 3] << 24;
|
||||||
|
if (k0 != ref0[i] || k1 != ref1[i])
|
||||||
|
match = 0;
|
||||||
|
}
|
||||||
|
ok("NEON ChaCha20 matches scalar reference (2 blocks)", match);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* (3) the cipher is a real XOR stream: applying it twice is identity */
|
||||||
|
{
|
||||||
|
uint8_t plain[128], work[128], k2[32];
|
||||||
|
for (i = 0; i < 128; i++)
|
||||||
|
plain[i] = (uint8_t)(i * 7 + 1);
|
||||||
|
for (i = 0; i < 32; i++)
|
||||||
|
k2[i] = (uint8_t)(i * 3);
|
||||||
|
memcpy(work, plain, 128);
|
||||||
|
fm_chacha20(work, 128, k2, 1);
|
||||||
|
ok("chacha20 actually changes data", memcmp(work, plain, 128) != 0);
|
||||||
|
fm_chacha20(work, 128, k2, 1);
|
||||||
|
ok("chacha20 round-trips (XOR is involutive)",
|
||||||
|
memcmp(work, plain, 128) == 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_physics(void)
|
||||||
|
{
|
||||||
|
/* two equal masses released from rest must accelerate toward each
|
||||||
|
* other: symmetric, momentum-conserving, and bounded. */
|
||||||
|
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 */
|
||||||
|
|
||||||
|
fm_physics(bodies, 2, 200);
|
||||||
|
|
||||||
|
/* velocities must be equal and opposite (Newton's third law) */
|
||||||
|
total_p = bodies[4] + bodies[12]; /* vx0 + vx1 */
|
||||||
|
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);
|
||||||
|
ok("physics: bodies attract", bodies[4] > 0.0 && bodies[12] < 0.0);
|
||||||
|
ok("physics values stay finite", isfinite(bodies[0]) && isfinite(bodies[8]));
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmp_u32(const void *p, const void *q)
|
||||||
|
{
|
||||||
|
uint32_t x = *(const uint32_t *)p, y = *(const uint32_t *)q;
|
||||||
|
return (x > y) - (x < y);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_sorted(const uint32_t *a, size_t n)
|
||||||
|
{
|
||||||
|
for (size_t i = 1; i < n; i++)
|
||||||
|
if (a[i - 1] > a[i])
|
||||||
|
return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_sort(void)
|
||||||
|
{
|
||||||
|
enum { N = 10000 };
|
||||||
|
uint32_t *a = malloc(N * sizeof(uint32_t));
|
||||||
|
uint32_t *b = malloc(N * sizeof(uint32_t));
|
||||||
|
uint64_t s;
|
||||||
|
size_t i;
|
||||||
|
uint32_t r = 12345;
|
||||||
|
|
||||||
|
for (i = 0; i < N; i++) {
|
||||||
|
r = r * 1103515245u + 12345u;
|
||||||
|
a[i] = r;
|
||||||
|
}
|
||||||
|
memcpy(b, a, N * sizeof(uint32_t));
|
||||||
|
|
||||||
|
s = fm_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 */
|
||||||
|
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 */
|
||||||
|
{
|
||||||
|
uint64_t s2 = fm_sort(a, N);
|
||||||
|
ok("sort is idempotent on sorted data",
|
||||||
|
is_sorted(a, N) && s2 == s);
|
||||||
|
}
|
||||||
|
|
||||||
|
ok("sort of empty array is zero", fm_sort(a, 0) == 0);
|
||||||
|
free(a);
|
||||||
|
free(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) */
|
||||||
|
void *nodes[4];
|
||||||
|
|
||||||
|
nodes[0] = &nodes[1];
|
||||||
|
nodes[1] = &nodes[2];
|
||||||
|
nodes[2] = &nodes[3];
|
||||||
|
nodes[3] = &nodes[0];
|
||||||
|
|
||||||
|
/* 4 hops from &nodes[0] returns to &nodes[0]; fm_chase returns the
|
||||||
|
* final pointer minus the starting pointer, so a full loop gives 0 */
|
||||||
|
ok("chase completes a full cycle", fm_chase(nodes, 4) == 0);
|
||||||
|
ok("chase(0) is zero", fm_chase(nodes, 0) == 0);
|
||||||
|
/* one hop lands on &nodes[1], i.e. one pointer-width past the start */
|
||||||
|
ok("chase single hop offset",
|
||||||
|
fm_chase(nodes, 1) == (uint64_t)((char *)&nodes[1] - (char *)&nodes[0]));
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
long n = sysconf(_SC_NPROCESSORS_ONLN);
|
||||||
|
|
||||||
|
fm_ncores = n > 0 ? n : 1;
|
||||||
|
|
||||||
|
printf("\nfossmark kernel correctness tests\n");
|
||||||
|
printf("=================================\n");
|
||||||
|
printf("running each check on %ld core%s in parallel\n\n",
|
||||||
|
fm_ncores, fm_ncores == 1 ? "" : "s");
|
||||||
|
|
||||||
|
printf("Integer Math:\n"); parallel(check_int);
|
||||||
|
printf("Floating Point Math:\n"); parallel(check_fp);
|
||||||
|
printf("Prime Numbers:\n"); parallel(check_primes);
|
||||||
|
printf("Extended Instructions:\n"); parallel(check_simd);
|
||||||
|
printf("Compression:\n"); parallel(check_compress);
|
||||||
|
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 */
|
||||||
|
printf("Single-Threaded (chase):\n"); check_chase();
|
||||||
|
|
||||||
|
printf("\n=================================\n");
|
||||||
|
printf("%d checks, %d failures\n\n", checks, failures);
|
||||||
|
return failures ? 1 : 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user