add ppc32be, and other fixes

This commit is contained in:
2026-07-17 15:16:30 -05:00
parent 31ba00f1b5
commit ec7e7c3a27
6 changed files with 206 additions and 22 deletions
+8
View File
@@ -34,6 +34,8 @@ jobs:
os: ubuntu-24.04 os: ubuntu-24.04
- target: linux-arm64 - target: linux-arm64
os: ubuntu-24.04 os: ubuntu-24.04
- target: linux-ppc32be
os: ubuntu-24.04
- target: macos-amd64 - target: macos-amd64
os: macos-14 os: macos-14
- target: macos-arm64 - target: macos-arm64
@@ -49,6 +51,12 @@ jobs:
sudo apt-get update sudo apt-get update
sudo apt-get install --yes gcc-aarch64-linux-gnu sudo apt-get install --yes gcc-aarch64-linux-gnu
- name: Install the Linux PPC32 big-endian cross-compiler
if: matrix.target == 'linux-ppc32be'
run: |
sudo apt-get update
sudo apt-get install --yes gcc-powerpc-linux-gnu
- name: Build - name: Build
run: make ${{ matrix.target }} run: make ${{ matrix.target }}
+29 -8
View File
@@ -3,6 +3,7 @@
# The assembly kernels are architecture-specific: # The assembly kernels are architecture-specific:
# src/fossmark.S AArch64 (ARM64) # src/fossmark.S AArch64 (ARM64)
# src/fossmark_x86_64.S x86-64 (AMD64) # src/fossmark_x86_64.S x86-64 (AMD64)
# src/fossmark_ppc32.c PowerPC 32-bit, including big-endian systems
# The C driver (src/main.c) is portable across architectures and OSes. A # 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 # "binary that runs everywhere" is not possible - each OS/arch pair uses a
# different executable format and instruction set - so output is named per # different executable format and instruction set - so output is named per
@@ -42,6 +43,7 @@ DIST := dist
DRIVER := src/main.c DRIVER := src/main.c
ASM_ARM64 := src/fossmark.S ASM_ARM64 := src/fossmark.S
ASM_AMD64 := src/fossmark_x86_64.S ASM_AMD64 := src/fossmark_x86_64.S
SRC_PPC32 := src/fossmark_ppc32.c
# ---- host detection: normalise `uname -m` to our arch names ---- # ---- host detection: normalise `uname -m` to our arch names ----
HOST_ARCH := $(shell uname -m) HOST_ARCH := $(shell uname -m)
@@ -50,10 +52,16 @@ ifneq (,$(filter aarch64 arm64,$(HOST_ARCH)))
HOST_ASM := $(ASM_ARM64) HOST_ASM := $(ASM_ARM64)
else ifneq (,$(filter x86_64 amd64,$(HOST_ARCH))) else ifneq (,$(filter x86_64 amd64,$(HOST_ARCH)))
HOST_ARCHNAME := amd64 HOST_ARCHNAME := amd64
HOST_ASM := $(ASM_AMD64) HOST_KERNEL := $(ASM_AMD64)
else ifneq (,$(filter ppc powerpc ppc32 powerpc32,$(HOST_ARCH)))
HOST_ARCHNAME := ppc32be
HOST_KERNEL := $(SRC_PPC32)
else else
HOST_ARCHNAME := $(HOST_ARCH) HOST_ARCHNAME := $(HOST_ARCH)
HOST_ASM := $(ASM_ARM64) $(error unsupported host architecture '$(HOST_ARCH)')
endif
ifeq ($(HOST_ARCHNAME),arm64)
HOST_KERNEL := $(ASM_ARM64)
endif endif
# ---- host OS name for the native binary ---- # ---- host OS name for the native binary ----
@@ -81,21 +89,27 @@ else
endif endif
CC_MACOS_ARM64 ?= $(CC) CC_MACOS_ARM64 ?= $(CC)
CC_MACOS_AMD64 ?= $(CC) CC_MACOS_AMD64 ?= $(CC)
ifeq ($(HOST_ARCHNAME),ppc32be)
CC_PPC32BE ?= $(CC)
else
CC_PPC32BE ?= powerpc-linux-gnu-gcc
endif
NATIVE_BIN := $(DIST)/fossmark-$(OSNAME)-$(HOST_ARCHNAME) NATIVE_BIN := $(DIST)/fossmark-$(OSNAME)-$(HOST_ARCHNAME)
# `make` with no target builds the host binary, as before. # `make` with no target builds the host binary, as before.
.DEFAULT_GOAL := native .DEFAULT_GOAL := native
.PHONY: all native linux-arm64 linux-amd64 macos-arm64 macos-amd64 bench test clean .PHONY: all native linux-arm64 linux-amd64 linux-ppc32be macos-arm64 macos-amd64 bench test clean
# `make all` builds both Linux binaries. # `make all` builds all Linux binaries.
all: linux-arm64 linux-amd64 all: linux-arm64 linux-amd64 linux-ppc32be
# `make native` (and bare `make`) build for whatever host you are on. # `make native` (and bare `make`) build for whatever host you are on.
native: $(NATIVE_BIN) native: $(NATIVE_BIN)
linux-arm64: $(DIST)/fossmark-linux-arm64 linux-arm64: $(DIST)/fossmark-linux-arm64
linux-amd64: $(DIST)/fossmark-linux-amd64 linux-amd64: $(DIST)/fossmark-linux-amd64
linux-ppc32be: $(DIST)/fossmark-linux-ppc32be
macos-arm64: $(DIST)/fossmark-macos-arm64 macos-arm64: $(DIST)/fossmark-macos-arm64
macos-amd64: $(DIST)/fossmark-macos-amd64 macos-amd64: $(DIST)/fossmark-macos-amd64
@@ -107,6 +121,10 @@ $(DIST)/fossmark-linux-amd64: $(DRIVER) $(ASM_AMD64) | $(DIST)
$(CC_AMD64) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS) $(CC_AMD64) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS)
@echo "built $@" @echo "built $@"
$(DIST)/fossmark-linux-ppc32be: $(DRIVER) $(SRC_PPC32) | $(DIST)
$(CC_PPC32BE) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(SRC_PPC32) $(LDLIBS)
@echo "built $@"
$(DIST)/fossmark-macos-arm64: $(DRIVER) $(ASM_ARM64) | $(DIST) $(DIST)/fossmark-macos-arm64: $(DRIVER) $(ASM_ARM64) | $(DIST)
$(CC_MACOS_ARM64) -arch arm64 $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(ASM_ARM64) $(LDLIBS) $(CC_MACOS_ARM64) -arch arm64 $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(ASM_ARM64) $(LDLIBS)
@echo "built $@" @echo "built $@"
@@ -124,6 +142,9 @@ endif
ifeq ($(OSNAME)-$(HOST_ARCHNAME),linux-amd64) ifeq ($(OSNAME)-$(HOST_ARCHNAME),linux-amd64)
NATIVE_HAS_RULE := yes NATIVE_HAS_RULE := yes
endif endif
ifeq ($(OSNAME)-$(HOST_ARCHNAME),linux-ppc32be)
NATIVE_HAS_RULE := yes
endif
ifeq ($(OSNAME)-$(HOST_ARCHNAME),macos-arm64) ifeq ($(OSNAME)-$(HOST_ARCHNAME),macos-arm64)
NATIVE_HAS_RULE := yes NATIVE_HAS_RULE := yes
endif endif
@@ -131,8 +152,8 @@ ifeq ($(OSNAME)-$(HOST_ARCHNAME),macos-amd64)
NATIVE_HAS_RULE := yes NATIVE_HAS_RULE := yes
endif endif
ifneq ($(NATIVE_HAS_RULE),yes) ifneq ($(NATIVE_HAS_RULE),yes)
$(NATIVE_BIN): $(DRIVER) $(HOST_ASM) | $(DIST) $(NATIVE_BIN): $(DRIVER) $(HOST_KERNEL) | $(DIST)
$(CC) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(HOST_ASM) $(LDLIBS) $(CC) $(CFLAGS) $(PTHREAD) -o $@ $(DRIVER) $(HOST_KERNEL) $(LDLIBS)
@echo "built $@" @echo "built $@"
endif endif
@@ -145,7 +166,7 @@ bench: $(NATIVE_BIN)
# Build and run the kernel correctness tests for the host arch. # Build and run the kernel correctness tests for the host arch.
test: | $(DIST) test: | $(DIST)
$(CC) $(CFLAGS) $(PTHREAD) -o $(DIST)/test_kernels src/test_kernels.c $(HOST_ASM) $(LDLIBS) $(CC) $(CFLAGS) $(PTHREAD) -o $(DIST)/test_kernels src/test_kernels.c $(HOST_KERNEL) $(LDLIBS)
./$(DIST)/test_kernels ./$(DIST)/test_kernels
clean: clean:
+13 -8
View File
@@ -5,9 +5,10 @@ small C driver. It measures each workload twice: once on a single core and once
across every available core. The final report includes separate single-core and across every available core. The final report includes separate single-core and
multicore scores. multicore scores.
The repository currently builds an executable named `fossmark` for ARM64 and The repository currently builds an executable named `fossmark` for ARM64,
x86-64. The C driver handles timing, memory, threads, output, and scoring. The x86-64, and 32-bit big-endian PowerPC. The C driver handles timing, memory,
performance-sensitive kernels live in architecture-specific assembly files. threads, output, and scoring. Performance-sensitive kernels live in
architecture-specific backend files.
## Workloads ## Workloads
@@ -48,6 +49,7 @@ Other targets are available for explicit platforms and architectures:
```sh ```sh
make linux-arm64 make linux-arm64
make linux-amd64 make linux-amd64
make linux-ppc32be
make macos-arm64 make macos-arm64
make macos-amd64 make macos-amd64
make all make all
@@ -59,6 +61,7 @@ toolchain. Override the target compiler when its name differs from the default:
```sh ```sh
make linux-arm64 CC_ARM64=aarch64-linux-gnu-gcc make linux-arm64 CC_ARM64=aarch64-linux-gnu-gcc
make linux-amd64 CC_AMD64=x86_64-linux-gnu-gcc make linux-amd64 CC_AMD64=x86_64-linux-gnu-gcc
make linux-ppc32be CC_PPC32BE=powerpc-linux-gnu-gcc
``` ```
Apple Clang can build either macOS architecture with `-arch`. Windows timing Apple Clang can build either macOS architecture with `-arch`. Windows timing
@@ -77,7 +80,8 @@ The exact filename depends on the host platform and architecture.
Pushing a Git tag runs the GitHub Actions build and correctness tests. If they Pushing a Git tag runs the GitHub Actions build and correctness tests. If they
succeed, the workflow creates a GitHub Release named `Release <tag name>` with succeed, the workflow creates a GitHub Release named `Release <tag name>` with
Linux and macOS archives for AMD64 and ARM64, plus a `SHA256SUMS` file. Linux archives for AMD64, ARM64, and PPC32 big-endian, macOS archives for AMD64
and ARM64, and a `SHA256SUMS` file.
## Scores ## Scores
@@ -115,13 +119,14 @@ normal.
## Architecture support ## Architecture support
The assembly kernels use only baseline instructions for their architecture: The kernel backends use only baseline instructions for their architecture:
* `src/fossmark.S` uses ARMv8-A and NEON under AAPCS64. * `src/fossmark.S` uses ARMv8-A and NEON under AAPCS64.
* `src/fossmark_x86_64.S` uses baseline x86-64 and SSE2 under the System V ABI. * `src/fossmark_x86_64.S` uses baseline x86-64 and SSE2 under the System V ABI.
* `src/fossmark_ppc32.c` is endian-safe and uses baseline 32-bit PowerPC operations. It avoids AltiVec so it runs on the Wii's PowerPC 750CL-class CPU.
The kernel files contain no system calls or calls into the C library. The same The assembly kernel files contain no system calls or calls into the C library.
ARM64 source can be assembled for Linux, macOS, Windows, and BSD object formats. The same ARM64 source can be assembled for Linux, macOS, Windows, and BSD object formats.
The current x86-64 source supports Linux, macOS, and the BSDs that use the The current x86-64 source supports Linux, macOS, and the BSDs that use the
System V calling convention. System V calling convention.
@@ -149,8 +154,8 @@ with a nonzero status if any check fails.
src/main.c portable benchmark driver and scoring src/main.c portable benchmark driver and scoring
src/fossmark.S ARM64 kernels src/fossmark.S ARM64 kernels
src/fossmark_x86_64.S x86-64 kernels src/fossmark_x86_64.S x86-64 kernels
src/fossmark_ppc32.c PPC32 big-endian kernels
src/test_kernels.c correctness suite src/test_kernels.c correctness suite
Makefile native and cross-build targets Makefile native and cross-build targets
dist/ generated binaries dist/ generated binaries
``` ```
BIN
View File
Binary file not shown.
+149
View File
@@ -0,0 +1,149 @@
/*
* Portable kernel backend for 32-bit PowerPC.
*
* Keeping this backend in C lets the compiler implement 64-bit arguments and
* returns according to the platform's PPC32 ABI. All byte-oriented formats
* are decoded explicitly, so the code is correct on big-endian systems.
*/
#include <math.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
static uint32_t rotl32(uint32_t x, unsigned n)
{
return (x << n) | (x >> (32 - n));
}
uint64_t fm_int_math(uint64_t iters)
{
uint64_t a = 0x9e3779b97f4a7c15ULL, b = 0xbf58476d1ce4e5b9ULL;
uint64_t c = 0x94d049bb133111ebULL, d = 0x2545f4914f6cdd1dULL;
uint64_t i;
if (!iters) return 0;
for (i = 0; i < iters; i++) {
a = a * 0xdeadbeefU + b; b = b * 0xdeadbeefU + c;
c = c * 0xdeadbeefU + d; d = d * 0xdeadbeefU + a;
a ^= c >> 29; b ^= d << 17; c ^= (a >> 31) | (a << 33);
d ^= b >> 7; a += c / 0xdeadbeefU; b += d / 0xdeadbeefU;
}
return a ^ b ^ c ^ d;
}
uint64_t fm_fp_math(uint64_t iters)
{
double a = 1.5, b = 2.5, c = 3.5, d = .5, out;
uint64_t bits, i;
if (!iters) return 0;
for (i = 0; i < iters; i++) {
a = fmin(a * 1.0625 + .0009765625, 2.0);
b = fmin(b * 1.0625 + .0009765625, 2.0);
c = fmin(c * 1.0625 + .0009765625, 2.0) + sqrt(a);
d = fmax(fabs(fmin(d * 1.0625 + .0009765625, 2.0) + sqrt(b)), 1.0);
a += 1.0 / (c + 1.0); b += 1.0 / (d + 1.0);
}
out = a + b + c + d;
memcpy(&bits, &out, sizeof bits);
return bits;
}
uint64_t fm_primes(uint64_t limit, uint8_t *sieve)
{
uint64_t i, j, count = 0;
if (limit < 2) return 0;
memset(sieve, 0, (size_t)limit);
sieve[0] = sieve[1] = 1;
for (i = 2; i <= (limit - 1) / i; i++)
if (!sieve[i]) for (j = i * i; j < limit; j += i) sieve[j] = 1;
for (i = 2; i < limit; i++) count += !sieve[i];
return count;
}
uint64_t fm_simd(uint64_t iters, void *memory)
{
uint32_t *v = (uint32_t *)memory;
uint32_t a[8]; uint64_t i; unsigned j; uint32_t sum = 0;
if (!iters) return 0;
memcpy(a, v, sizeof a);
for (i = 0; i < iters; i++)
for (j = 0; j < 8; j++) a[j] = rotl32(a[j] + a[(j + 1) & 7] * (j + 3), (j + 5) & 31);
for (j = 0; j < 8; j++) sum ^= a[j];
memcpy(v, a, sizeof a);
return sum;
}
static uint32_t load32_native(const uint8_t *p)
{
uint32_t v; memcpy(&v, p, sizeof v); return v;
}
uint64_t fm_compress(const uint8_t *src, uint64_t len, uint32_t *ht)
{
uint64_t ip = 0, anchor = 0, out = 0, ref, ml, lit;
memset(ht, 0, (size_t)(1U << 16) * sizeof *ht);
if (len < 16) return len + 1;
while (ip < len - 12) {
uint32_t seq = load32_native(src + ip);
uint32_t h = (uint32_t)(seq * 2654435761U) >> 16;
ref = ht[h]; ht[h] = (uint32_t)ip;
if (ref >= ip || ip - ref >= 65536 || load32_native(src + ref) != seq) { ip++; continue; }
for (ml = 4; ip + ml < len && src[ip + ml] == src[ref + ml]; ml++) {}
lit = ip - anchor; out += lit + 3 + (lit >= 15) + (ml >= 19);
ip += ml; anchor = ip;
}
return out + (len - anchor) + 1;
}
static uint32_t load32le(const uint8_t *p)
{
return (uint32_t)p[0] | (uint32_t)p[1] << 8 | (uint32_t)p[2] << 16 | (uint32_t)p[3] << 24;
}
static void store32le(uint8_t *p, uint32_t v)
{
p[0] = (uint8_t)v; p[1] = (uint8_t)(v >> 8); p[2] = (uint8_t)(v >> 16); p[3] = (uint8_t)(v >> 24);
}
#define QR(a,b,c,d) do { a+=b; d=rotl32(d^a,16); c+=d; b=rotl32(b^c,12); a+=b; d=rotl32(d^a,8); c+=d; b=rotl32(b^c,7); } while (0)
uint64_t fm_chacha20(uint8_t *buf, uint64_t len, const uint8_t key[32], uint64_t passes)
{
static const uint32_t sigma[4] = {0x61707865,0x3320646e,0x79622d32,0x6b206574};
uint32_t base[16], x[16], counter = 0, checksum = 0; uint64_t pass, off; int i, r;
len &= ~(uint64_t)63; if (!len || !passes) return 0;
memcpy(base, sigma, 16); for (i=0;i<8;i++) base[4+i]=load32le(key+4*i);
base[13]=base[14]=base[15]=0;
for (pass=0;pass<passes;pass++) for (off=0;off<len;off+=64) {
base[12]=counter++; memcpy(x,base,sizeof x);
for(r=0;r<10;r++) { QR(x[0],x[4],x[8],x[12]); QR(x[1],x[5],x[9],x[13]); QR(x[2],x[6],x[10],x[14]); QR(x[3],x[7],x[11],x[15]); QR(x[0],x[5],x[10],x[15]); QR(x[1],x[6],x[11],x[12]); QR(x[2],x[7],x[8],x[13]); QR(x[3],x[4],x[9],x[14]); }
for(i=0;i<16;i++) { uint32_t k=x[i]+base[i]; uint8_t t[4]; store32le(t,k); buf[off+4*i]^=t[0]; buf[off+4*i+1]^=t[1]; buf[off+4*i+2]^=t[2]; buf[off+4*i+3]^=t[3]; checksum^=k; }
}
return checksum;
}
#undef QR
uint64_t fm_physics(double *b, uint64_t n, uint64_t steps)
{
uint64_t s,i,j,bits; double sum=0;
if (!n || !steps) return 0;
for(s=0;s<steps;s++) { for(i=0;i<n;i++) { double ax=0,ay=0,az=0; for(j=0;j<n;j++) { double dx=b[8*j]-b[8*i],dy=b[8*j+1]-b[8*i+1],dz=b[8*j+2]-b[8*i+2]; double q=1.0/sqrt(dx*dx+dy*dy+dz*dz+.0625); q=q*q*q*b[8*j+3]; ax+=dx*q; ay+=dy*q; az+=dz*q; } b[8*i+4]+=ax*.0078125; b[8*i+5]+=ay*.0078125; b[8*i+6]+=az*.0078125; } for(i=0;i<n;i++) { b[8*i]+=b[8*i+4]*.0078125; b[8*i+1]+=b[8*i+5]*.0078125; b[8*i+2]+=b[8*i+6]*.0078125; } }
for (i = 0; i < n; i++)
sum += b[8*i+4] + b[8*i+5] + b[8*i+6];
memcpy(&bits, &sum, sizeof bits);
return bits;
}
static void sift(uint32_t *a, uint64_t root, uint64_t end) { for (;;) { uint64_t c=root*2+1; uint32_t t; if(c>=end)return; if(c+1<end&&a[c+1]>a[c])c++; if(a[root]>=a[c])return; t=a[root];a[root]=a[c];a[c]=t;root=c; } }
uint64_t fm_sort(uint32_t *a, uint64_t n)
{
uint64_t i,end,sum=0; uint32_t t; if(n<2)return n?a[0]:0;
for (i = n / 2; i; i--)
sift(a, i - 1, n);
for (end = n - 1; end; end--) {
t = a[0]; a[0] = a[end]; a[end] = t;
sift(a, 0, end);
}
for(i=0;i<n;i++){sum=(sum>>7)|(sum<<57);sum^=a[i];sum+=a[i];} return sum;
}
uint64_t fm_chase(void **ptrs, uint64_t steps)
{
void **p=ptrs; uint64_t i; if(!steps)return 0; for(i=0;i<steps;i++)p=(void **)*p; return (uint64_t)((uintptr_t)p-(uintptr_t)ptrs);
}
+7 -6
View File
@@ -45,6 +45,11 @@
# define D_INT "64-bit ALU: imul, mul, div, bitops" # define D_INT "64-bit ALU: imul, mul, div, bitops"
# define D_FP "double: mulsd/addsd, divsd, sqrtsd" # define D_FP "double: mulsd/addsd, divsd, sqrtsd"
# define D_SIMD "SSE2: 128-bit integer + float" # define D_SIMD "SSE2: 128-bit integer + float"
#elif defined(__powerpc__) && !defined(__powerpc64__)
# define FM_ARCH "PowerPC 32-bit big-endian"
# define D_INT "PPC32 integer ALU and software 64-bit arithmetic"
# define D_FP "PowerPC scalar double-precision floating point"
# define D_SIMD "PPC32 parallel integer workload"
#else #else
# define FM_ARCH "unknown" # define FM_ARCH "unknown"
# define D_INT "64-bit integer ALU" # define D_INT "64-bit integer ALU"
@@ -603,14 +608,12 @@ static double display_metric(const struct test *t, const struct result *r)
static void print_header(void) static void print_header(void)
{ {
printf("\n"); printf("\n");
printf(" fossmark 1.0 - multi-core CPU benchmark\n"); printf(" fossbench1.0 - multi-core CPU benchmark\n");
printf(" ------------------------------------------------------------------\n"); printf(" ------------------------------------------------------------------\n");
printf(" platform: %s/%s\n", FM_OS, FM_ARCH); 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", printf(" cores: %ld\n",
g_ncores, g_ncores); g_ncores, g_ncores);
printf("\n"); printf("\n");
printf(" RATE/TIME/SCORE below are the all-core (multi-core) pass.\n");
printf("\n");
printf(" %-24s %12s %-11s %8s %9s\n", printf(" %-24s %12s %-11s %8s %9s\n",
"TEST", "RATE", "UNIT", "TIME", "SCORE"); "TEST", "RATE", "UNIT", "TIME", "SCORE");
printf(" --------------------------------------------------------------------------\n"); printf(" --------------------------------------------------------------------------\n");
@@ -700,8 +703,6 @@ int main(int argc, char **argv)
exp(multi_log_sum / weight_sum)); exp(multi_log_sum / weight_sum));
printf(" %-24s %44.0f\n", "SINGLECORE SCORE", printf(" %-24s %44.0f\n", "SINGLECORE SCORE",
exp(single_log_sum / weight_sum)); exp(single_log_sum / weight_sum));
printf(" %-24s (weighted geometric means, reference machine = %.0f)\n",
"", FM_TARGET_SCORE);
printf("\n"); printf("\n");
teardown(); teardown();