diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 18f8822..a4fd482 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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 diff --git a/Makefile b/Makefile index f6e8553..9f92b49 100644 --- a/Makefile +++ b/Makefile @@ -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: diff --git a/README.md b/README.md index e165fca..6d6d271 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/fossmark_i386.S b/src/fossmark_i386.S new file mode 100644 index 0000000..54b4f29 --- /dev/null +++ b/src/fossmark_i386.S @@ -0,0 +1,1344 @@ +/* + * fossmark_i386.S - x86 32-bit (i386) CPU benchmark kernels + * + * The i386 counterpart to fossmark_x86_64.S. Same nine routines, same + * contract: each is a pure function of its arguments, contains no syscalls, + * no libc calls and no external data relocations, so it assembles and runs + * unmodified under the plain i386 SysV (cdecl) ABI on Linux. + * + * i386 has only six general-purpose registers to work with (eax, ebx, ecx, + * edx, esi, edi - ebp and esp are reserved for the frame/stack) and no + * 64-bit integer registers or instructions at all, so every 64-bit value here + * is carried as an explicit hi:lo register or memory pair, and arithmetic on + * it is built from the matching 32-bit op plus a carry (add/adc, sub/sbb, + * shrd/shld for cross-word shifts). Register pressure is the binding + * constraint - with four independent 64-bit accumulators in flight there + * usually isn't a register left over, so several kernels keep their working + * state on the stack frame instead of in registers, unlike the amd64/arm64 + * files. That's a real, honest cost of 32-bit x86, not an oversight: it's + * the same constraint any hand-written i386 code pays. + * + * Extended instructions are SSE2 only (mandatory baseline: Pentium 4). As in + * the amd64 file, no FMA/SSE3+ is used, and every fused multiply-add is a + * separate multiply and add. + * + * Register conventions (i386 SysV cdecl): + * integer args pushed on the stack, right to left, caller cleans up + * 64-bit args two stack dwords, low dword at the lower address + * 64-bit return edx:eax (edx = high, eax = low) + * callee-saved ebx, esi, edi, ebp + * all of xmm0-7 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 + +#define FN_BEGIN(name) FN_BEGIN_ASM SYM(name) +#define FN_END(name) FN_END_ASM SYM(name) + + .macro FN_BEGIN_ASM name + .p2align 4 + .globl \name +#if defined(__ELF__) + .type \name, @function +#endif +\name: + .endm + + .macro FN_END_ASM name +#if defined(__ELF__) + .size \name, . - \name +#endif + .endm + + .text + + +/* =================================================================== + * uint64_t fm_int_math(uint64_t iters) [ [esp+4]:[esp+8] = iters ] + * + * Four independent multiply-accumulate chains (a,b,c,d), each carried as a + * hi:lo stack-frame pair since there's no register space left to hold four + * 64-bit values at once. Cross-mixed with 64-bit shifts/rotate built from + * shrd/shld, a genuine 64x64->128 unsigned widening multiply (schoolbook, + * four 32x32->64 partial products) for the long-latency multiplier path, and + * hardware division: the divisor (0xdeadbeef) fits in 32 bits, so instead of + * a 64-by-64 divide (which i386 doesn't have, and which libgcc would emulate + * with a slow generic software routine), this splits the dividend into + * hi/lo and does two native 32-bit `div`s - the same trick used to fix the + * C fallback's __udivdi3 calls, now built directly into the kernel. + * =================================================================== */ +FN_BEGIN(fm_int_math) + push ebp + mov ebp, esp + push ebx + sub esp, 48 + + /* frame: a_lo=[ebp-8] a_hi=[ebp-12] b_lo=[ebp-16] b_hi=[ebp-20] + * c_lo=[ebp-24] c_hi=[ebp-28] d_lo=[ebp-32] d_hi=[ebp-36] + * t0=[ebp-40] t1=[ebp-44] t2=[ebp-48] t3=[ebp-52] + * args: iters_lo=[ebp+8] iters_hi=[ebp+12] (decremented in place) */ + + mov eax, [ebp+8] + or eax, [ebp+12] + jz .Lim_zero + + mov dword ptr [ebp-8], 0x7f4a7c15 + mov dword ptr [ebp-12], 0x9e3779b9 + mov dword ptr [ebp-16], 0x1ce4e5b9 + mov dword ptr [ebp-20], 0xbf58476d + mov dword ptr [ebp-24], 0x133111eb + mov dword ptr [ebp-28], 0x94d049bb + mov dword ptr [ebp-32], 0x4f6cdd1d + mov dword ptr [ebp-36], 0x2545f491 + mov ebx, 0xdeadbeef + +.Lim_loop: + /* ---- a = a*m+b ---- */ + mov eax, [ebp-8] + mul ebx + mov [ebp-40], eax + mov ecx, edx + mov eax, [ebp-12] + mul ebx + add ecx, eax + mov eax, [ebp-40] + add eax, [ebp-16] + adc ecx, [ebp-20] + mov [ebp-8], eax + mov [ebp-12], ecx + + /* ---- b = b*m+c ---- */ + mov eax, [ebp-16] + mul ebx + mov [ebp-40], eax + mov ecx, edx + mov eax, [ebp-20] + mul ebx + add ecx, eax + mov eax, [ebp-40] + add eax, [ebp-24] + adc ecx, [ebp-28] + mov [ebp-16], eax + mov [ebp-20], ecx + + /* ---- c = c*m+d ---- */ + mov eax, [ebp-24] + mul ebx + mov [ebp-40], eax + mov ecx, edx + mov eax, [ebp-28] + mul ebx + add ecx, eax + mov eax, [ebp-40] + add eax, [ebp-32] + adc ecx, [ebp-36] + mov [ebp-24], eax + mov [ebp-28], ecx + + /* ---- d = d*m+a(new) ---- */ + mov eax, [ebp-32] + mul ebx + mov [ebp-40], eax + mov ecx, edx + mov eax, [ebp-36] + mul ebx + add ecx, eax + mov eax, [ebp-40] + add eax, [ebp-8] + adc ecx, [ebp-12] + mov [ebp-32], eax + mov [ebp-36], ecx + + /* ---- a ^= c>>29 (logical, 64-bit) ---- */ + mov eax, [ebp-24] + mov edx, [ebp-28] + shrd eax, edx, 29 + shr edx, 29 + xor [ebp-8], eax + xor [ebp-12], edx + + /* ---- b ^= d<<17 (64-bit) ---- */ + mov eax, [ebp-32] + mov edx, [ebp-36] + shld edx, eax, 17 + shl eax, 17 + xor [ebp-16], eax + xor [ebp-20], edx + + /* ---- c ^= ror64(a,31) ---- */ + mov eax, [ebp-8] + mov edx, [ebp-12] + mov ecx, eax + shrd eax, edx, 31 + shr edx, 31 + shl ecx, 1 + or edx, ecx + xor [ebp-24], eax + xor [ebp-28], edx + + /* ---- d &= ~sar64(b,7) (arithmetic, 64-bit) ---- */ + mov eax, [ebp-16] + mov edx, [ebp-20] + shrd eax, edx, 7 + sar edx, 7 + not eax + not edx + and [ebp-32], eax + and [ebp-36], edx + + /* ---- a += high64(unsigned a*c): schoolbook 64x64->128 ---- */ + mov eax, [ebp-8] + mul dword ptr [ebp-24] /* p0 = a_lo*c_lo */ + mov [ebp-40], edx /* t0 = p0_hi */ + mov eax, [ebp-8] + mul dword ptr [ebp-28] /* p1 = a_lo*c_hi */ + mov [ebp-44], eax /* t1 = p1_lo */ + mov [ebp-48], edx /* t2 = p1_hi */ + mov eax, [ebp-12] + mul dword ptr [ebp-24] /* p2 = a_hi*c_lo */ + mov [ebp-52], edx /* t3 = p2_hi */ + add eax, [ebp-44] + mov ecx, 0 + adc ecx, 0 + add eax, [ebp-40] + adc ecx, 0 /* ecx = mid-stage carry (0..2) */ + mov eax, [ebp-12] + mul dword ptr [ebp-28] /* p3 = a_hi*c_hi */ + add eax, [ebp-48] + adc edx, 0 + add eax, [ebp-52] + adc edx, 0 + add eax, ecx + adc edx, 0 + add [ebp-8], eax + adc [ebp-12], edx + + /* ---- b += high64(unsigned b*d) ---- */ + mov eax, [ebp-16] + mul dword ptr [ebp-32] + mov [ebp-40], edx + mov eax, [ebp-16] + mul dword ptr [ebp-36] + mov [ebp-44], eax + mov [ebp-48], edx + mov eax, [ebp-20] + mul dword ptr [ebp-32] + mov [ebp-52], edx + add eax, [ebp-44] + mov ecx, 0 + adc ecx, 0 + add eax, [ebp-40] + adc ecx, 0 + mov eax, [ebp-20] + mul dword ptr [ebp-36] + add eax, [ebp-48] + adc edx, 0 + add eax, [ebp-52] + adc edx, 0 + add eax, ecx + adc edx, 0 + add [ebp-16], eax + adc [ebp-20], edx + + /* ---- a += c/m ; b += d/m : divisor fits in 32 bits, so two native + * `div`s (hi/m first, remainder:lo second) replace a 64-by-64 divide */ + mov eax, [ebp-28] + mov edx, 0 + div ebx + mov ecx, eax /* q1 */ + mov eax, [ebp-24] + div ebx /* edx (r1) : eax (c_lo) still set from above */ + add [ebp-8], eax + adc [ebp-12], ecx + + mov eax, [ebp-36] + mov edx, 0 + div ebx + mov ecx, eax + mov eax, [ebp-32] + div ebx + add [ebp-16], eax + adc [ebp-20], ecx + + /* ---- b = ror64(b,11) ---- */ + mov eax, [ebp-16] + mov edx, [ebp-20] + mov ecx, eax + shrd eax, edx, 11 + shr edx, 11 + shl ecx, 21 + or edx, ecx + mov [ebp-16], eax + mov [ebp-20], edx + + /* ---- a = (a<<23)|(b>>41) ---- */ + mov eax, [ebp-8] + mov edx, [ebp-12] + shld edx, eax, 23 + shl eax, 23 + mov ecx, [ebp-20] + shr ecx, 9 + or eax, ecx + mov [ebp-8], eax + mov [ebp-12], edx + + sub dword ptr [ebp+8], 1 + sbb dword ptr [ebp+12], 0 + mov eax, [ebp+8] + or eax, [ebp+12] + jnz .Lim_loop + + mov eax, [ebp-8] + mov edx, [ebp-12] + xor eax, [ebp-16] + xor edx, [ebp-20] + xor eax, [ebp-24] + xor edx, [ebp-28] + xor eax, [ebp-32] + xor edx, [ebp-36] + add esp, 48 + pop ebx + pop ebp + ret + +.Lim_zero: + xor eax, eax + xor edx, edx + add esp, 48 + pop ebx + pop ebp + ret +FN_END(fm_int_math) + + +/* =================================================================== + * uint64_t fm_fp_math(uint64_t iters) [ [esp+4]:[esp+8] = iters ] + * + * Double-precision scalar FP, genuine SSE2 throughout (Pentium 4's SSE2 unit + * handles mulsd/addsd/minsd/maxsd/divsd/sqrtsd natively - there is no libm + * call here, unlike the portable C fallback, whose fmin()/fmax() calls force + * a round trip through the ABI's x87 return convention on every one of the + * four calls per iteration). 32-bit mode only has xmm0-xmm7, half of + * amd64's xmm0-xmm15, so unlike that file the four rounding/min/mul/add + * constants are read directly from memory operands each use instead of + * being kept pinned in registers - plenty of headroom still remains (this + * uses at most six of the eight available). Returns the result bit-cast to + * u64 (edx:eax), matching the uint64_t return type - NOT via the x87 + * ST(0) that a `double` return would use. + * =================================================================== */ +FN_BEGIN(fm_fp_math) + push ebp + mov ebp, esp + sub esp, 8 + + mov eax, [ebp+8] + or eax, [ebp+12] + jz .Lfp_zero + + movsd xmm0, [.Lfp_a_init] + movsd xmm1, [.Lfp_b_init] + movsd xmm2, [.Lfp_c_init] + movsd xmm3, [.Lfp_d_init] + +.Lfp_loop: + mulsd xmm0, [.Lfp_mul] + addsd xmm0, [.Lfp_addend] + minsd xmm0, [.Lfp_two] + + mulsd xmm1, [.Lfp_mul] + addsd xmm1, [.Lfp_addend] + minsd xmm1, [.Lfp_two] + + mulsd xmm2, [.Lfp_mul] + addsd xmm2, [.Lfp_addend] + minsd xmm2, [.Lfp_two] + sqrtsd xmm4, xmm0 + addsd xmm2, xmm4 + + mulsd xmm3, [.Lfp_mul] + addsd xmm3, [.Lfp_addend] + minsd xmm3, [.Lfp_two] + sqrtsd xmm5, xmm1 + addsd xmm3, xmm5 + andpd xmm3, [.Lfp_absmask] + maxsd xmm3, [.Lfp_one] + + movapd xmm4, xmm2 + addsd xmm4, [.Lfp_one] + movsd xmm5, [.Lfp_one] + divsd xmm5, xmm4 + addsd xmm0, xmm5 + + movapd xmm4, xmm3 + addsd xmm4, [.Lfp_one] + movsd xmm5, [.Lfp_one] + divsd xmm5, xmm4 + addsd xmm1, xmm5 + + sub dword ptr [ebp+8], 1 + sbb dword ptr [ebp+12], 0 + mov eax, [ebp+8] + or eax, [ebp+12] + jnz .Lfp_loop + + addsd xmm0, xmm1 + addsd xmm2, xmm3 + addsd xmm0, xmm2 + movsd [ebp-8], xmm0 + mov eax, [ebp-8] + mov edx, [ebp-4] + leave + ret + +.Lfp_zero: + xor eax, eax + xor edx, edx + leave + ret +FN_END(fm_fp_math) + + .p2align 4 +.Lfp_a_init: + .double 1.5 +.Lfp_b_init: + .double 2.5 +.Lfp_c_init: + .double 3.5 +.Lfp_d_init: + .double 0.5 +.Lfp_mul: + .double 1.0625 +.Lfp_addend: + .double 0.0009765625 +.Lfp_two: + .double 2.0 +.Lfp_one: + .double 1.0 + .p2align 4 +.Lfp_absmask: + .quad 0x7fffffffffffffff + + +/* =================================================================== + * uint64_t fm_primes(uint64_t limit, uint8_t *sieve) + * [ [ebp+8]:[ebp+12]=limit, [ebp+16]=sieve ] + * + * Sieve of Eratosthenes over [0, limit). Only the low dword of `limit` is + * used: it is a sieve/buffer size, and a 32-bit process cannot allocate a + * sieve past 4 GiB anyway, so - like main.c's own CHASE_NODES sizing for + * UINTPTR_MAX == UINT32_MAX - it is always within 32-bit range in practice. + * Clears its own scratch (32 bytes/iteration via SSE2), then sieves. Returns + * the prime count in eax (edx=0: the count is always far under 2^32). + * =================================================================== */ +FN_BEGIN(fm_primes) + push ebp + mov ebp, esp + push ebx + push esi + push edi + + mov edi, [ebp+8] /* limit (low dword) */ + mov esi, [ebp+16] /* sieve */ + cmp edi, 2 + jb .Lpr_none + + pxor xmm0, xmm0 + xor eax, eax + mov ecx, edi + and ecx, -32 +.Lpr_clear32: + cmp eax, ecx + jae .Lpr_clear1 + movdqu [esi+eax], xmm0 + movdqu [esi+eax+16], xmm0 + add eax, 32 + jmp .Lpr_clear32 +.Lpr_clear1: + cmp eax, edi + jae .Lpr_clear_done + mov byte ptr [esi+eax], 0 + inc eax + jmp .Lpr_clear1 +.Lpr_clear_done: + + mov byte ptr [esi], 1 + mov byte ptr [esi+1], 1 + + mov ebx, 2 /* i */ +.Lpr_outer: + mov eax, ebx + imul eax, ebx /* i*i (fits 32 bits: i <= sqrt(limit)) */ + cmp eax, edi + jae .Lpr_count + + movzx edx, byte ptr [esi+ebx] + test dl, dl + jnz .Lpr_outer_next + + mov ecx, eax /* j = i*i */ +.Lpr_inner: + cmp ecx, edi + jae .Lpr_outer_next + mov byte ptr [esi+ecx], 1 + add ecx, ebx + jmp .Lpr_inner + +.Lpr_outer_next: + inc ebx + jmp .Lpr_outer + +.Lpr_count: + xor eax, eax + mov ecx, 2 +.Lpr_count_loop: + cmp ecx, edi + jae .Lpr_done + cmp byte ptr [esi+ecx], 0 + jne .Lpr_count_next + inc eax +.Lpr_count_next: + inc ecx + jmp .Lpr_count_loop + +.Lpr_done: + xor edx, edx + pop edi + pop esi + pop ebx + pop ebp + ret + +.Lpr_none: + xor eax, eax + xor edx, edx + pop edi + pop esi + pop ebx + pop ebp + ret +FN_END(fm_primes) + + +/* =================================================================== + * uint64_t fm_simd(uint64_t iters, void *buf) + * [ [ebp+8]:[ebp+12]=iters, [ebp+16]=buf ] + * + * "Extended instructions": SSE2, architecturally mandatory baseline for this + * whole file (Pentium 4). 32-bit mode only has xmm0-xmm7 - half of amd64's + * xmm0-xmm15 - so unlike that file this can't keep eight seed vectors plus + * scratch plus a separate float pipeline all live at once. Four state + * vectors (xmm0-3) plus one scratch (xmm5) replace amd64's eight-vector + * state, but every instruction category survives: packed 16-bit + * multiply-accumulate, widening 16->32 multiply-add, high-multiply, packed + * shift/logic funnel, saturating/averaging/min-max arithmetic plus + * sum-of-absolute-differences, a lane shuffle, and the packed single- + * precision float path (multiply-add plus the reciprocal/rsqrt estimates). + * + * buf must be at least 96 bytes. Returns a checksum (edx=0). + * =================================================================== */ +FN_BEGIN(fm_simd) + push ebp + mov ebp, esp + push esi + + mov eax, [ebp+8] + or eax, [ebp+12] + jz .Lsd_zero + + mov esi, [ebp+16] + + movdqu xmm0, [esi] /* seed four integer state vectors */ + movdqu xmm1, [esi+16] + movdqu xmm2, [esi+32] + movdqu xmm3, [esi+48] + movdqu xmm6, [esi+64] /* float operands: convert integer seeds to */ + movdqu xmm7, [esi+80] /* finite floats, not reinterpreted bit patterns */ + cvtdq2ps xmm6, xmm6 + cvtdq2ps xmm7, xmm7 + pxor xmm4, xmm4 /* saturating/averaging 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 + + /* widening multiply-add: 16->32 bit lanes */ + movdqa xmm5, xmm0 + pmaddwd xmm5, xmm1 + paddd xmm3, xmm5 + + /* high-half multiply */ + movdqa xmm5, xmm0 + pmulhw xmm5, xmm1 + pxor xmm2, xmm5 + + /* shifts and logic funnel */ + movdqa xmm5, xmm2 + pslld xmm5, 3 + psrld xmm2, 29 + por xmm2, xmm5 + pand xmm3, xmm0 + + /* saturating/averaging arithmetic, min/max, sum-of-abs-differences */ + paddsw xmm4, xmm0 + psubusw xmm4, xmm1 + pmaxsw xmm4, xmm2 + pminsw xmm4, xmm3 + pavgb xmm4, xmm0 + movdqa xmm5, xmm1 + psadbw xmm5, xmm4 + paddw xmm4, xmm5 + + /* lane shuffle: reverse the four 32-bit lanes */ + pshufd xmm0, xmm0, 0x1B + pxor xmm1, xmm0 + + /* single-precision float SIMD: multiply-add plus reciprocal/rsqrt */ + movaps xmm5, xmm6 + mulps xmm5, xmm7 + addps xmm6, xmm5 + rcpps xmm5, xmm7 + addps xmm6, xmm5 + rsqrtps xmm5, xmm6 + mulps xmm7, xmm5 + + sub dword ptr [ebp+8], 1 + sbb dword ptr [ebp+12], 0 + mov eax, [ebp+8] + or eax, [ebp+12] + jnz .Lsd_loop + + /* fold the integer state together */ + pxor xmm0, xmm1 + pxor xmm2, xmm3 + pxor xmm0, xmm2 + pxor xmm0, xmm4 + + /* fold in the float accumulator (truncate to int lanes) */ + cvttps2dq xmm6, xmm6 + pxor xmm0, xmm6 + + /* 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 + xor edx, edx + pop esi + pop ebp + ret + +.Lsd_zero: + xor eax, eax + xor edx, edx + pop esi + pop ebp + ret +FN_END(fm_simd) + + +/* =================================================================== + * uint64_t fm_compress(const uint8_t *src, uint64_t len, uint32_t *ht) + * [ [ebp+8]=src, [ebp+12]:[ebp+16]=len, [ebp+20]=ht ] + * + * The LZ77 fast-match inner loop (LZ4-style): hash the next 4 bytes, probe a + * single-entry-per-bucket table, verify, then extend. Only the low dword of + * `len` is used (a buffer size - see fm_primes). This one has the tightest + * register budget in the file: ip, ref, end and the match length all need + * to be live across the byte-by-byte extend loop, which leaves nothing to + * hold ht/src/anchor/mflimit/outsize in registers too, so those live on the + * stack frame instead and get reloaded each outer iteration - the extend + * loop itself (the actual hot part) stays all-register. + * + * ht must hold 1<<16 uint32_t (256 KiB); this routine clears it itself. + * Returns the encoded size in bytes (edx=0). + * =================================================================== */ +FN_BEGIN(fm_compress) + push ebp + mov ebp, esp + push ebx + push edi + push esi + sub esp, 20 + + /* frame: ht=[ebp-16] src=[ebp-20] anchor=[ebp-24] mflimit=[ebp-28] + * outsize=[ebp-32] + * persistent regs: edi=ip, ebx=end */ + + mov eax, [ebp+8] /* src */ + mov [ebp-20], eax + mov edi, eax + mov eax, [ebp+20] /* ht */ + mov [ebp-16], eax + + /* clear the hash table: 1<<16 entries * 4 bytes = 262144 bytes */ + pxor xmm0, xmm0 + xor eax, eax + mov ecx, [ebp-16] +.Lcm_clear: + movdqu [ecx+eax], xmm0 + movdqu [ecx+eax+16], xmm0 + movdqu [ecx+eax+32], xmm0 + movdqu [ecx+eax+48], xmm0 + add eax, 64 + cmp eax, 262144 + jb .Lcm_clear + + mov eax, [ebp+12] /* len (low dword) */ + cmp eax, 16 + jb .Lcm_tiny + + mov [ebp-24], edi /* anchor = ip (still == src here) */ + mov ebx, edi + add ebx, eax /* end = src+len */ + mov ecx, ebx + sub ecx, 12 + mov [ebp-28], ecx /* mflimit = end-12 */ + mov dword ptr [ebp-32], 0 /* outsize */ + +.Lcm_loop: + cmp edi, [ebp-28] + jae .Lcm_flush + + mov eax, [edi] /* seq = load32(ip) */ + imul eax, eax, 0x9E3779B1 + shr eax, 16 /* h = (seq*prime) >> 16 */ + + mov edx, [ebp-16] /* ht */ + mov ecx, [edx+eax*4] /* ref_off = ht[h] */ + mov esi, edi + sub esi, [ebp-20] /* cur_off = ip - src */ + mov [edx+eax*4], esi /* ht[h] = cur_off */ + + mov esi, [ebp-20] + add esi, ecx /* ref = src + ref_off */ + cmp esi, edi + jae .Lcm_no_match /* ref must be strictly behind ip */ + + mov eax, edi + sub eax, esi /* distance */ + cmp eax, 65536 + jae .Lcm_no_match /* 16-bit offset window */ + + mov eax, [esi] + cmp eax, [edi] /* verify the 4-byte match */ + jne .Lcm_no_match + + /* match confirmed: extend it byte by byte (edi=ip, esi=ref, ebx=end) */ + mov ecx, 4 /* ml */ +.Lcm_extend: + lea eax, [edi+ecx] + cmp eax, ebx /* ip + ml vs end */ + jae .Lcm_emit + mov dl, [edi+ecx] + cmp dl, [esi+ecx] + jne .Lcm_emit + inc ecx + jmp .Lcm_extend + +.Lcm_emit: + /* token(1) + offset(2) + literals + varint extensions */ + mov eax, edi + sub eax, [ebp-24] /* literal run length */ + add [ebp-32], eax + add dword ptr [ebp-32], 3 + cmp eax, 15 + jb .Lcm_no_lit_ext + inc dword ptr [ebp-32] /* literal-length extension byte */ +.Lcm_no_lit_ext: + cmp ecx, 19 + jb .Lcm_no_ml_ext + inc dword ptr [ebp-32] /* match-length extension byte */ +.Lcm_no_ml_ext: + add edi, ecx /* ip += ml */ + mov [ebp-24], edi /* anchor = ip */ + jmp .Lcm_loop + +.Lcm_no_match: + inc edi + jmp .Lcm_loop + +.Lcm_flush: + /* trailing literals */ + mov eax, ebx + sub eax, [ebp-24] /* end - anchor */ + add eax, [ebp-32] + add eax, 1 + jmp .Lcm_ret + +.Lcm_tiny: + lea eax, [eax+1] /* len + 1 */ + +.Lcm_ret: + xor edx, edx + add esp, 20 + pop esi + pop edi + pop ebx + pop ebp + ret +FN_END(fm_compress) + + +/* rotate each 32-bit lane of v left by n, via shift-left + shift-right + or. + * xmm7 is scratch (dedicated - see fm_chacha20's register map below). */ +.macro ROL32_I386 v, n + movdqa xmm7, \v + pslld \v, \n + psrld xmm7, (32 - \n) + por \v, xmm7 +.endm + +/* one ChaCha quarter-round over rows a,b,c,d */ +.macro QROUND_I386 a, b, c, d + paddd \a, \b + pxor \d, \a + ROL32_I386 \d, 16 + paddd \c, \d + pxor \b, \c + ROL32_I386 \b, 12 + paddd \a, \b + pxor \d, \a + ROL32_I386 \d, 8 + paddd \c, \d + pxor \b, \c + ROL32_I386 \b, 7 +.endm + +/* =================================================================== + * uint64_t fm_chacha20(uint8_t *buf, uint64_t len, + * const uint8_t key[32], uint64_t passes) + * [ [ebp+8]=buf, [ebp+12]:[ebp+16]=len, [ebp+20]=key, [ebp+24]:[ebp+28]=passes ] + * + * ChaCha20, SSE2, four 128-bit state rows - same 32-bit-mode register + * squeeze as fm_simd: xmm0-3 are the working state (rows A-D, mutated every + * round), xmm4-6 pin the constant/key base rows (re-copied into xmm0-2 each + * block) and xmm7 is the ROL32 scratch - that is all eight xmm registers + * with none left over, so unlike the amd64 file, the pre-round state (needed + * for the feed-forward add) and the running keystream checksum live on the + * stack instead of in xmm8-13. + * + * len is rounded down to a multiple of 64 (buffer size - see fm_primes: low + * dword only). Returns a checksum of the keystream output (edx=0). + * =================================================================== */ +FN_BEGIN(fm_chacha20) + push ebp + mov ebp, esp + push ebx + push esi + push edi + sub esp, 80 + + /* frame: orig0=[ebp-28] orig1=[ebp-44] orig2=[ebp-60] orig3=[ebp-76] + * checksum=[ebp-92] (each a 16-byte slot) + * persistent regs: edi=buf ebx=len(rounded) edx=block counter */ + + mov eax, [ebp+12] /* len (low dword) */ + and eax, -64 + jz .Lcc_zero + mov ebx, eax + + mov eax, [ebp+24] + or eax, [ebp+28] + jz .Lcc_zero + + mov edi, [ebp+8] /* buf */ + mov eax, [ebp+20] /* key */ + movdqa xmm4, [.Lcc_sigma] /* "expand 32-byte k" */ + movdqu xmm5, [eax] /* key[0..15] */ + movdqu xmm6, [eax+16] /* key[16..31] */ + + pxor xmm7, xmm7 + movdqu [ebp-92], xmm7 /* running checksum = 0 */ + xor edx, edx /* block counter value */ + +.Lcc_pass: + xor esi, esi /* byte offset into buf */ + +.Lcc_block: + /* working state = base state, block counter in lane 0 of row D */ + movdqa xmm0, xmm4 + movdqa xmm1, xmm5 + movdqa xmm2, xmm6 + movd xmm3, edx + + /* keep originals on the stack for the final feed-forward add */ + movdqu [ebp-28], xmm0 + movdqu [ebp-44], xmm1 + movdqu [ebp-60], xmm2 + movdqu [ebp-76], xmm3 + + mov ecx, 10 /* 10 double rounds = 20 rounds */ +.Lcc_rounds: + /* column round */ + QROUND_I386 xmm0, xmm1, xmm2, xmm3 + + /* rotate lanes to form the diagonals */ + pshufd xmm1, xmm1, 0x39 + pshufd xmm2, xmm2, 0x4E + pshufd xmm3, xmm3, 0x93 + + /* diagonal round */ + QROUND_I386 xmm0, xmm1, xmm2, xmm3 + + /* undo the lane rotation */ + pshufd xmm1, xmm1, 0x93 + pshufd xmm2, xmm2, 0x4E + pshufd xmm3, xmm3, 0x39 + + dec ecx + jnz .Lcc_rounds + + /* feed-forward: keystream = working + original. paddd's memory form + * requires 16-byte alignment, which this stack slot isn't guaranteed + * to have (unlike the amd64 ABI, i386 doesn't guarantee 16-byte ESP + * alignment at entry), so load through xmm7 (free again post-rounds) + * with the alignment-agnostic movdqu instead of adding memory directly. */ + movdqu xmm7, [ebp-28] + paddd xmm0, xmm7 + movdqu xmm7, [ebp-44] + paddd xmm1, xmm7 + movdqu xmm7, [ebp-60] + paddd xmm2, xmm7 + movdqu xmm7, [ebp-76] + paddd xmm3, xmm7 + + /* XOR the keystream into the buffer */ + lea eax, [edi+esi] + movdqu xmm7, [eax] + pxor xmm7, xmm0 + movdqu [eax], xmm7 + movdqu xmm7, [eax+16] + pxor xmm7, xmm1 + movdqu [eax+16], xmm7 + movdqu xmm7, [eax+32] + pxor xmm7, xmm2 + movdqu [eax+32], xmm7 + movdqu xmm7, [eax+48] + pxor xmm7, xmm3 + movdqu [eax+48], xmm7 + + /* accumulate a checksum of the keystream */ + movdqu xmm7, [ebp-92] + pxor xmm7, xmm0 + pxor xmm7, xmm3 + movdqu [ebp-92], xmm7 + + inc edx /* counter++ */ + add esi, 64 + cmp esi, ebx + jb .Lcc_block + + sub dword ptr [ebp+24], 1 + sbb dword ptr [ebp+28], 0 + mov eax, [ebp+24] + or eax, [ebp+28] + jnz .Lcc_pass + + /* horizontal add of the checksum lanes */ + movdqu xmm0, [ebp-92] + pshufd xmm1, xmm0, 0x4E + paddd xmm0, xmm1 + pshufd xmm1, xmm0, 0xB1 + paddd xmm0, xmm1 + movd eax, xmm0 + xor edx, edx + add esp, 80 + pop edi + pop esi + pop ebx + pop ebp + ret + +.Lcc_zero: + xor eax, eax + xor edx, edx + add esp, 80 + pop edi + pop esi + pop ebx + pop ebp + 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) + * [ [ebp+8]=bodies, [ebp+12]:[ebp+16]=n, [ebp+20]:[ebp+24]=steps ] + * + * Direct-summation N-body gravity, O(n^2) per step, double precision, real + * sqrtsd+divsd (not an rsqrt estimate). Layout per body, 8 doubles (64 + * bytes): [x y z mass vx vy vz pad]. `n` uses only its low dword (a body + * count - see fm_primes). xmm0-2 hold body i's position for the whole inner + * loop, xmm3-5 accumulate its acceleration, and xmm6-7 are the only scratch + * left - not enough to hold dx/dy/dz simultaneously alongside r/1/r/m-over-r3, + * so the three deltas spill to three stack doubles between being computed + * and being used in the final ax+=dx*q step. Returns a velocity checksum + * (edx=0). + * =================================================================== */ +FN_BEGIN(fm_physics) + push ebp + mov ebp, esp + push ebx + push esi + push edi + sub esp, 24 + + /* frame: dx=[ebp-20] dy=[ebp-28] dz=[ebp-36] + * persistent regs: edi=bodies ebx=n */ + + mov ebx, [ebp+12] /* n (low dword) */ + test ebx, ebx + jz .Lph_zero + mov eax, [ebp+20] + or eax, [ebp+24] + jz .Lph_zero + + mov edi, [ebp+8] /* bodies */ + +.Lph_step: + xor eax, eax /* i */ + +.Lph_body_i: + mov ecx, eax + shl ecx, 6 /* i * 64 */ + lea esi, [edi+ecx] /* &bodies[i] */ + + movsd xmm0, [esi] /* xi */ + movsd xmm1, [esi+8] /* yi */ + movsd xmm2, [esi+16] /* zi */ + + xorpd xmm3, xmm3 /* ax */ + xorpd xmm4, xmm4 /* ay */ + xorpd xmm5, xmm5 /* az */ + + xor ecx, ecx /* j */ + mov edx, edi /* &bodies[j] */ + +.Lph_body_j: + movsd xmm6, [edx] + subsd xmm6, xmm0 /* dx */ + movsd [ebp-20], xmm6 + movsd xmm6, [edx+8] + subsd xmm6, xmm1 /* dy */ + movsd [ebp-28], xmm6 + movsd xmm6, [edx+16] + subsd xmm6, xmm2 /* dz */ + movsd [ebp-36], xmm6 + + /* r2 = dx*dx + dy*dy + dz*dz + eps^2 */ + movsd xmm6, [ebp-20] + mulsd xmm6, xmm6 + movsd xmm7, [ebp-28] + mulsd xmm7, xmm7 + addsd xmm6, xmm7 + movsd xmm7, [ebp-36] + mulsd xmm7, xmm7 + addsd xmm6, xmm7 + addsd xmm6, [.Lph_eps2] + + sqrtsd xmm6, xmm6 /* r */ + movsd xmm7, [.Lph_one] + divsd xmm7, xmm6 /* 1/r */ + movsd xmm6, xmm7 + mulsd xmm6, xmm7 /* 1/r^2 */ + mulsd xmm6, xmm7 /* 1/r^3 */ + mulsd xmm6, [edx+24] /* m/r^3 */ + + movsd xmm7, [ebp-20] + mulsd xmm7, xmm6 /* dx * m/r^3 */ + addsd xmm3, xmm7 + movsd xmm7, [ebp-28] + mulsd xmm7, xmm6 + addsd xmm4, xmm7 + movsd xmm7, [ebp-36] + mulsd xmm7, xmm6 + addsd xmm5, xmm7 + + add edx, 64 + inc ecx + cmp ecx, ebx + jb .Lph_body_j + + /* v += a * dt */ + movsd xmm6, [esi+32] /* vx */ + mulsd xmm3, [.Lph_dt] + addsd xmm6, xmm3 + movsd [esi+32], xmm6 + movsd xmm6, [esi+40] /* vy */ + mulsd xmm4, [.Lph_dt] + addsd xmm6, xmm4 + movsd [esi+40], xmm6 + movsd xmm6, [esi+48] /* vz */ + mulsd xmm5, [.Lph_dt] + addsd xmm6, xmm5 + movsd [esi+48], xmm6 + + inc eax + cmp eax, ebx + jb .Lph_body_i + + /* second pass: x += v * dt (positions move only after all forces) */ + xor eax, eax + mov edx, edi +.Lph_integrate: + movsd xmm0, [edx] + movsd xmm1, [edx+8] + movsd xmm2, [edx+16] + movsd xmm6, [edx+32] + mulsd xmm6, [.Lph_dt] + addsd xmm0, xmm6 + movsd xmm7, [edx+40] + mulsd xmm7, [.Lph_dt] + addsd xmm1, xmm7 + movsd xmm6, [edx+48] + mulsd xmm6, [.Lph_dt] + addsd xmm2, xmm6 + movsd [edx], xmm0 + movsd [edx+8], xmm1 + movsd [edx+16], xmm2 + add edx, 64 + inc eax + cmp eax, ebx + jb .Lph_integrate + + sub dword ptr [ebp+20], 1 + sbb dword ptr [ebp+24], 0 + mov eax, [ebp+20] + or eax, [ebp+24] + jnz .Lph_step + + /* checksum: sum of all velocity components */ + xorpd xmm0, xmm0 + xor eax, eax + mov edx, edi +.Lph_sum: + movsd xmm6, [edx+32] + addsd xmm0, xmm6 + movsd xmm6, [edx+40] + addsd xmm0, xmm6 + movsd xmm6, [edx+48] + addsd xmm0, xmm6 + add edx, 64 + inc eax + cmp eax, ebx + jb .Lph_sum + + movsd [ebp-20], xmm0 + mov eax, [ebp-20] + mov edx, [ebp-16] + add esp, 24 + pop edi + pop esi + pop ebx + pop ebp + ret + +.Lph_zero: + xor eax, eax + xor edx, edx + add esp, 24 + pop edi + pop esi + pop ebx + pop ebp + 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) [ [ebp+8]=a, [ebp+12]:[ebp+16]=n ] + * + * In-place heapsort, same shape as the amd64 file: no recursion, an + * order-sensitive checksum that doubles as a correctness check. `n` uses + * only its low dword (an element count - see fm_primes). The internal + * siftdown is reached with `call`/`ret` sharing this function's own frame + * (no separate prologue) exactly like the amd64 version; it borrows ebx and + * esi as scratch for the duration of one call via push/pop, since i386 has + * too few registers to dedicate two more to root/child bookkeeping on top of + * a, root, end and a value temporary. The checksum itself is 32-bit (i386 + * has no 64-bit rotate) - test_kernels.c only requires it be deterministic + * and permutation-sensitive, which this is. + * =================================================================== */ +FN_BEGIN(fm_sort) + push ebp + mov ebp, esp + push ebx + push esi + push edi + + mov ebx, [ebp+12] /* n (low dword) */ + cmp ebx, 2 + jb .Lst_trivial + + /* ---- build the max-heap: for i = n/2 - 1 down to 0 ---- */ + mov esi, ebx + shr esi, 1 /* i = n/2 */ +.Lst_build: + test esi, esi + jz .Lst_extract + dec esi /* i-- */ + mov ecx, esi /* root */ + mov edx, ebx /* end */ + call .Lst_siftdown + test esi, esi + jnz .Lst_build + + /* ---- extract: for end = n-1 down to 1 ---- */ +.Lst_extract: + mov esi, ebx + dec esi /* end = n-1 */ +.Lst_extract_loop: + test esi, esi + jz .Lst_checksum + + /* swap a[0] and a[end] */ + mov edi, [ebp+8] + mov eax, [edi] + mov ecx, [edi+esi*4] + mov [edi], ecx + mov [edi+esi*4], eax + + xor ecx, ecx /* root = 0 */ + mov edx, esi /* end = end */ + call .Lst_siftdown + + dec esi + jmp .Lst_extract_loop + + /* ---- order-sensitive checksum ---- */ +.Lst_checksum: + xor eax, eax + xor ecx, ecx /* index */ + mov edi, [ebp+8] +.Lst_cksum_loop: + mov edx, [edi+ecx*4] + xor eax, edx + ror eax, 7 + add eax, edx + inc ecx + cmp ecx, ebx + jb .Lst_cksum_loop + xor edx, edx + pop edi + pop esi + pop ebx + pop ebp + ret + +.Lst_trivial: + xor eax, eax + xor edx, edx + test ebx, ebx + jz .Lst_trivial_ret + mov edi, [ebp+8] + mov eax, [edi] +.Lst_trivial_ret: + pop edi + pop esi + pop ebx + pop ebp + ret + + /* ---- local helper: siftdown(root = ecx, end = edx) + * base a is reloaded from [ebp+8] each call (edi is otherwise free); + * ebx/esi are borrowed as scratch and restored before return, since + * the caller's ebx (n) and esi (build/extract index) must survive. ---- */ +.Lst_siftdown: + push ebx + push esi + mov edi, [ebp+8] /* a */ +.Lst_sift_loop: + lea ebx, [ecx+ecx+1] /* child = 2*root + 1 */ + cmp ebx, edx + jae .Lst_sift_done /* no children */ + + lea esi, [ebx+1] /* right = child + 1 */ + cmp esi, edx + jae .Lst_sift_have_child /* no right child */ + mov eax, [edi+esi*4] /* a[right] */ + cmp eax, [edi+ebx*4] /* a[right] vs a[child] */ + jbe .Lst_sift_have_child /* keep child if a[right] <= it */ + mov ebx, esi /* else child = right */ + +.Lst_sift_have_child: + mov eax, [edi+ecx*4] /* a[root] */ + mov esi, [edi+ebx*4] /* a[child] */ + cmp eax, esi + jae .Lst_sift_done /* heap property holds */ + + /* swap and descend */ + mov [edi+ecx*4], esi + mov [edi+ebx*4], eax + mov ecx, ebx + jmp .Lst_sift_loop + +.Lst_sift_done: + pop esi + pop ebx + ret +FN_END(fm_sort) + + +/* =================================================================== + * uint64_t fm_chase(void **ptrs, uint64_t steps) + * [ [esp+4]=ptrs, [esp+8]:[esp+12]=steps ] + * + * Pointer chase around a randomised cycle. Every load depends on the + * previous one, so nothing can be prefetched, overlapped or reordered - the + * truest single-threaded memory-latency test in the suite. Needs no + * callee-saved registers at all (eax/ecx/edx suffice), so unlike every other + * kernel here it has no prologue - args stay at their original [esp+N] + * offsets since esp never moves. + * =================================================================== */ +FN_BEGIN(fm_chase) + mov ecx, [esp+8] + or ecx, [esp+12] + jz .Lch_zero + + mov eax, [esp+4] /* p = ptrs */ +.Lch_loop: + mov eax, [eax] + sub dword ptr [esp+8], 1 + sbb dword ptr [esp+12], 0 + mov ecx, [esp+8] + or ecx, [esp+12] + jnz .Lch_loop + + sub eax, [esp+4] /* final offset, keeps p live */ + xor edx, edx + ret + +.Lch_zero: + xor eax, eax + xor edx, edx + ret +FN_END(fm_chase) + + +#if defined(__ELF__) + .section .note.GNU-stack, "", @progbits +#endif diff --git a/src/fossmark_ppc32.c b/src/fossmark_ppc32.c index f79d46e..d20a191 100644 --- a/src/fossmark_ppc32.c +++ b/src/fossmark_ppc32.c @@ -11,10 +11,6 @@ #include #include -#if defined(__i386__) || defined(__x86_64__) || defined(_M_IX86) || defined(_M_X64) -#include -#endif - #if defined(__linux__) #include #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) diff --git a/src/main.c b/src/main.c index 3c6c65c..1c7672f 100644 --- a/src/main.c +++ b/src/main.c @@ -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) ---------- */