Files
app/src/fossbench_i386.S
T
owen ad5791bbb7 Add authenticated uploads and rename fossmark to fossbench
Uploads can now be attributed to a fossbench.net profile: set
FOSSBENCH_TOKEN and the client sends it as a Bearer token, which the
server auto-approves and links to the account. Anonymous, pending-review
upload stays the default when no token is set. New --upload/--noupload
flags skip the interactive prompt for scripted runs, and the client
reports HTTP 401/422 distinctly from other failures. The token is never
printed or logged.

Also renames the project and its internal identifiers (FM_/fm_ macros
and symbols, source filenames, binary output names) from fossmark to
fossbench, matching the actual product name. The "fossmark_version"
field in the upload payload is left as-is, since it's the server API's
fixed contract field, not this client's own name.
2026-07-18 06:02:24 -05:00

1345 lines
31 KiB
ArmAsm

/*
* fossbench_i386.S - x86 32-bit (i386) CPU benchmark kernels
*
* The i386 counterpart to fossbench_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 fb_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(fb_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(fb_int_math)
/* ===================================================================
* uint64_t fb_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(fb_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(fb_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 fb_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(fb_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(fb_primes)
/* ===================================================================
* uint64_t fb_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(fb_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(fb_simd)
/* ===================================================================
* uint64_t fb_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 fb_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(fb_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(fb_compress)
/* rotate each 32-bit lane of v left by n, via shift-left + shift-right + or.
* xmm7 is scratch (dedicated - see fb_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 fb_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 fb_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 fb_primes: low
* dword only). Returns a checksum of the keystream output (edx=0).
* =================================================================== */
FN_BEGIN(fb_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(fb_chacha20)
.p2align 4
.Lcc_sigma:
.long 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
/* ===================================================================
* uint64_t fb_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 fb_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(fb_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(fb_physics)
.p2align 4
.Lph_dt:
.double 0.0078125 /* dt */
.Lph_eps2:
.double 0.0625 /* eps^2 */
.Lph_one:
.double 1.0
/* ===================================================================
* uint64_t fb_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 fb_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(fb_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(fb_sort)
/* ===================================================================
* uint64_t fb_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(fb_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(fb_chase)
#if defined(__ELF__)
.section .note.GNU-stack, "", @progbits
#endif