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.
1345 lines
31 KiB
ArmAsm
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
|