Rewrite i386 backend as hand-written assembly
Replaces the C fallback (borrowed from PowerPC) with dedicated i386 assembly for all nine kernels, matching the amd64/arm64 convention. Fixes two real bugs found and verified on physical Pentium 4 hardware: scalar FP silently running on x87 instead of SSE2, and fm_int_math calling __udivdi3 on every iteration of the highest-weighted test because the C compiler didn't know its 0xdeadbeef divisor fits in 32 bits. Real-hardware SINGLECORE/MULTICORE scores go from 425/448 to 539/581.
This commit is contained in:
@@ -170,4 +170,5 @@ jobs:
|
||||
release/SHA256SUMS \
|
||||
--repo "$GITHUB_REPOSITORY" \
|
||||
--title "Release $RELEASE_TAG" \
|
||||
--notes "⚠️ **i386 (Pentium 4) support is new in this release and still being validated on real hardware.** i386 benchmark scores should not be considered accurate yet." \
|
||||
--generate-notes
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
# The assembly kernels are architecture-specific:
|
||||
# src/fossmark.S AArch64 (ARM64)
|
||||
# src/fossmark_x86_64.S x86-64 (AMD64)
|
||||
# src/fossmark_i386.S x86 32-bit (i386, Pentium 4 baseline)
|
||||
# src/fossmark_ppc32.c PowerPC 32-bit, including big-endian systems
|
||||
# and the portable PPC64 kernel implementations
|
||||
# The C driver (src/main.c) is portable across architectures and OSes. A
|
||||
@@ -48,7 +49,7 @@ DIST := dist
|
||||
DRIVER := src/main.c
|
||||
ASM_ARM64 := src/fossmark.S
|
||||
ASM_AMD64 := src/fossmark_x86_64.S
|
||||
SRC_I386 := src/fossmark_ppc32.c
|
||||
ASM_I386 := src/fossmark_i386.S
|
||||
SRC_PPC32 := src/fossmark_ppc32.c
|
||||
ASM_PPC32 := src/fossmark_ppc32_ext.S
|
||||
SRC_PPC64 := src/fossmark_ppc32.c
|
||||
@@ -63,7 +64,7 @@ else ifneq (,$(filter x86_64 amd64,$(HOST_ARCH)))
|
||||
HOST_KERNEL := $(ASM_AMD64)
|
||||
else ifneq (,$(filter i386 i486 i586 i686 x86,$(HOST_ARCH)))
|
||||
HOST_ARCHNAME := i386
|
||||
HOST_KERNEL := $(SRC_I386)
|
||||
HOST_KERNEL := $(ASM_I386)
|
||||
else ifneq (,$(filter ppc powerpc ppc32 powerpc32,$(HOST_ARCH)))
|
||||
HOST_ARCHNAME := ppc32be
|
||||
HOST_KERNEL := $(SRC_PPC32) $(ASM_PPC32)
|
||||
@@ -75,7 +76,16 @@ else
|
||||
$(error unsupported host architecture '$(HOST_ARCH)')
|
||||
endif
|
||||
ifeq ($(HOST_ARCHNAME),i386)
|
||||
CFLAGS += -march=pentium4 -msse2
|
||||
# The kernels are hand-written assembly (fossmark_i386.S) using SSE2
|
||||
# directly, so -msse2/-mfpmath=sse have nothing left to gate - only
|
||||
# main.c (the portable driver) is still compiled from C here.
|
||||
#
|
||||
# -fno-pie: i386 PIC costs a whole general-purpose register (already the
|
||||
# scarcest resource in 32-bit mode) for the life of any function that
|
||||
# touches global data or calls out - a tax amd64/arm64 don't pay the same
|
||||
# way. Paired with -no-pie at link time below.
|
||||
CFLAGS += -march=pentium4 -fno-pie
|
||||
LDFLAGS += -no-pie
|
||||
endif
|
||||
ifeq ($(HOST_ARCHNAME),ppc64be)
|
||||
CFLAGS += -mcpu=970 -maltivec
|
||||
@@ -154,8 +164,8 @@ $(DIST)/fossmark-linux-amd64: $(DRIVER) $(ASM_AMD64) | $(DIST)
|
||||
$(CC_AMD64) $(CFLAGS) $(TLS_CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(ASM_AMD64) $(LDLIBS)
|
||||
@echo "built $@"
|
||||
|
||||
$(DIST)/fossmark-linux-i386: $(DRIVER) $(SRC_I386) | $(DIST)
|
||||
$(CC_I386) -m32 -march=pentium4 -msse2 $(CFLAGS) $(TLS_CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(SRC_I386) $(LDLIBS)
|
||||
$(DIST)/fossmark-linux-i386: $(DRIVER) $(ASM_I386) | $(DIST)
|
||||
$(CC_I386) -m32 -march=pentium4 -fno-pie -no-pie $(CFLAGS) $(TLS_CFLAGS) $(PTHREAD) $(LDFLAGS) -o $@ $(DRIVER) $(ASM_I386) $(LDLIBS)
|
||||
@echo "built $@"
|
||||
|
||||
$(DIST)/fossmark-linux-ppc32be: $(DRIVER) $(SRC_PPC32) $(ASM_PPC32) | $(DIST)
|
||||
@@ -213,7 +223,7 @@ bench: $(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_KERNEL) -lm
|
||||
$(CC) $(CFLAGS) $(PTHREAD) $(LDFLAGS) -o $(DIST)/test_kernels src/test_kernels.c $(HOST_KERNEL) -lm
|
||||
./$(DIST)/test_kernels
|
||||
|
||||
clean:
|
||||
|
||||
@@ -164,6 +164,11 @@ The kernel backends use only baseline instructions for their architecture:
|
||||
|
||||
* `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_i386.S` uses baseline 32-bit x86 (Pentium 4) and SSE2 under the
|
||||
i386 System V (cdecl) ABI. With only six general-purpose registers, no
|
||||
64-bit integer registers, and half of amd64's SSE2 register file (xmm0-7),
|
||||
several kernels keep working state on the stack instead of in registers -
|
||||
a real cost of the architecture, not an oversight.
|
||||
* `src/fossmark_ppc32.c` is endian-safe and keeps a baseline 32-bit PowerPC
|
||||
fallback. At runtime, the extended-instruction test uses Paired Singles when
|
||||
the device-tree `compatible` property begins with `nintendo,`; otherwise it
|
||||
@@ -205,6 +210,7 @@ with a nonzero status if any check fails.
|
||||
src/main.c portable benchmark driver and scoring
|
||||
src/fossmark.S ARM64 kernels
|
||||
src/fossmark_x86_64.S x86-64 kernels
|
||||
src/fossmark_i386.S i386 (Pentium 4) kernels
|
||||
src/fossmark_ppc32.c PPC32/PPC64 big-endian kernels
|
||||
src/fossmark_ppc32_ext.S optional PPC32 PS, VSX, and AltiVec kernels
|
||||
src/test_kernels.c correctness suite
|
||||
|
||||
+1344
File diff suppressed because it is too large
Load Diff
+2
-31
@@ -11,10 +11,6 @@
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64)
|
||||
#include <emmintrin.h>
|
||||
#endif
|
||||
|
||||
#if defined(__linux__)
|
||||
#include <sys/auxv.h>
|
||||
#endif
|
||||
@@ -68,7 +64,7 @@ uint64_t fm_primes(uint64_t limit, uint8_t *sieve)
|
||||
return count;
|
||||
}
|
||||
|
||||
#if defined(__powerpc__) && !defined(__powerpc64__)
|
||||
#if !defined(__powerpc64__)
|
||||
static uint64_t fm_simd_scalar(uint64_t iters, void *memory)
|
||||
{
|
||||
uint32_t *v = (uint32_t *)memory;
|
||||
@@ -113,7 +109,7 @@ uint64_t fm_simd(uint64_t iters, void *memory)
|
||||
sum ^= v[j];
|
||||
return sum;
|
||||
}
|
||||
#elif defined(__powerpc__)
|
||||
#else
|
||||
/* These are kept in fossmark_ppc32_ext.S so this translation unit, and thus
|
||||
* the executable's default code path, only requires baseline PPC32. */
|
||||
extern void fm_simd_ps_kernel(uint64_t iters, void *memory);
|
||||
@@ -196,31 +192,6 @@ uint64_t fm_simd(uint64_t iters, void *memory)
|
||||
sum ^= v[j];
|
||||
return sum;
|
||||
}
|
||||
#else
|
||||
uint64_t fm_simd(uint64_t iters, void *memory)
|
||||
{
|
||||
__m128i a, b;
|
||||
uint32_t *v = (uint32_t *)memory;
|
||||
uint32_t sum = 0;
|
||||
uint64_t i;
|
||||
unsigned j;
|
||||
|
||||
if (!iters)
|
||||
return 0;
|
||||
a = _mm_loadu_si128((const __m128i *)v);
|
||||
b = _mm_loadu_si128((const __m128i *)(v + 4));
|
||||
for (i = 0; i < iters; i++) {
|
||||
a = _mm_add_epi32(a, b);
|
||||
b = _mm_xor_si128(b, a);
|
||||
a = _mm_add_epi32(a, b);
|
||||
b = _mm_xor_si128(b, a);
|
||||
}
|
||||
_mm_storeu_si128((__m128i *)v, a);
|
||||
_mm_storeu_si128((__m128i *)(v + 4), b);
|
||||
for (j = 0; j < 8; j++)
|
||||
sum ^= v[j];
|
||||
return sum;
|
||||
}
|
||||
#endif
|
||||
|
||||
static uint32_t load32_native(const uint8_t *p)
|
||||
|
||||
+1
-1
@@ -42,7 +42,7 @@
|
||||
#ifndef FM_API_BASE_URL
|
||||
# define FM_API_BASE_URL "https://fossbench.net"
|
||||
#endif
|
||||
#define FM_VERSION "0.1.3-hotfix4"
|
||||
#define FM_VERSION "0.1.4"
|
||||
|
||||
/* ---------- platform identification (for the banner only) ---------- */
|
||||
|
||||
|
||||
Reference in New Issue
Block a user