/*
 * 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
