initial commit for alpha release and testing on MacOS
This commit is contained in:
@@ -0,0 +1,987 @@
|
||||
/*
|
||||
* fossmark_x86_64.S - x86-64 (AMD64) CPU benchmark kernels
|
||||
*
|
||||
* The AMD64 counterpart to fossmark.S. Same nine routines, same contract: each
|
||||
* is a pure function of its arguments under the System V AMD64 ABI, contains no
|
||||
* syscalls, no libc calls and no external data relocations, so it assembles and
|
||||
* runs on Linux (ELF), macOS (Mach-O) and the BSDs. The portable C driver in
|
||||
* main.c is shared unchanged between this file and the AArch64 one.
|
||||
*
|
||||
* Only baseline instructions are used: general-purpose AMD64 plus SSE2, which
|
||||
* is architecturally mandatory on x86-64. The optional extensions (SSE4, AVX,
|
||||
* FMA, AES-NI, POPCNT/BMI) are deliberately avoided - using them unguarded
|
||||
* would fault (#UD) on cores that lack them - so, exactly as the NEON file
|
||||
* sticks to mandatory ASIMD and shuns the optional AES/SHA/DotProd, this file
|
||||
* sticks to mandatory SSE2 and shuns everything above it. FMA in particular is
|
||||
* optional here, so every fused multiply-add is written as a separate multiply
|
||||
* and add.
|
||||
*
|
||||
* Register conventions (System V AMD64):
|
||||
* integer args rdi, rsi, rdx, rcx, r8, r9 (return in rax)
|
||||
* callee-saved rbx, rbp, r12, r13, r14, r15 (saved when used)
|
||||
* all of xmm0-15 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
|
||||
|
||||
#if defined(__ELF__)
|
||||
# define FN_BEGIN(name) .p2align 4 ; .globl SYM(name) ; .type SYM(name), @function ; SYM(name):
|
||||
# define FN_END(name) .size SYM(name), . - SYM(name)
|
||||
#else
|
||||
# define FN_BEGIN(name) .p2align 4 ; .globl SYM(name) ; SYM(name):
|
||||
# define FN_END(name)
|
||||
#endif
|
||||
|
||||
.text
|
||||
|
||||
/* ===================================================================
|
||||
* uint64_t fm_int_math(uint64_t iters) [rdi = iters]
|
||||
*
|
||||
* Four independent multiply-accumulate chains for instruction-level
|
||||
* parallelism, mixed with the long-latency serialising ops (mul/div) and
|
||||
* bit-manipulation. Returns a checksum so nothing can be elided.
|
||||
* =================================================================== */
|
||||
FN_BEGIN(fm_int_math)
|
||||
test rdi, rdi
|
||||
jz .Lim_zero
|
||||
|
||||
mov r8, 0x9E3779B97F4A7C15 /* a */
|
||||
mov r9, 0xBF58476D1CE4E5B9 /* b */
|
||||
mov r10, 0x94D049BB133111EB /* c */
|
||||
mov r11, 0x2545F4914F6CDD1D /* d */
|
||||
mov rsi, 0x00000000DEADBEEF /* odd multiplier, never zero */
|
||||
|
||||
.Lim_loop:
|
||||
/* four independent multiply-accumulate chains */
|
||||
imul r8, rsi
|
||||
add r8, r9
|
||||
imul r9, rsi
|
||||
add r9, r10
|
||||
imul r10, rsi
|
||||
add r10, r11
|
||||
imul r11, rsi
|
||||
add r11, r8
|
||||
|
||||
/* cross-mix with shifts and logic ops */
|
||||
mov rax, r10
|
||||
shr rax, 29
|
||||
xor r8, rax
|
||||
mov rax, r11
|
||||
shl rax, 17
|
||||
xor r9, rax
|
||||
mov rax, r8
|
||||
ror rax, 31
|
||||
xor r10, rax
|
||||
mov rax, r9
|
||||
sar rax, 7
|
||||
not rax
|
||||
and r11, rax /* r11 &= ~(r9 >> 7 arith) */
|
||||
|
||||
/* wide multiplies: the long-latency 128-bit multiplier path */
|
||||
mov rax, r8
|
||||
mul r10 /* rdx:rax = a*c, high in rdx */
|
||||
add r8, rdx
|
||||
mov rax, r9
|
||||
imul r11 /* rdx:rax = b*d signed, high rdx */
|
||||
add r9, rdx
|
||||
|
||||
/* bit manipulation: byte reverse */
|
||||
mov rax, r10
|
||||
bswap rax
|
||||
xor r8, rax
|
||||
mov rax, r11
|
||||
bswap rax
|
||||
xor r9, rax
|
||||
|
||||
/* division: fully serialising, not pipelined */
|
||||
mov rcx, rsi
|
||||
or rcx, 1 /* guarantee a non-zero divisor */
|
||||
mov rax, r8
|
||||
xor edx, edx
|
||||
div rcx /* rax = a / rcx (unsigned) */
|
||||
imul rax, rcx
|
||||
sub r10, rax /* c -= (a/div)*div */
|
||||
mov rax, r9
|
||||
cqo
|
||||
idiv rcx /* rax = b / rcx (signed) */
|
||||
add r11, rax
|
||||
|
||||
/* bitfield ops */
|
||||
ror r9, 11
|
||||
shld r8, r9, 23
|
||||
|
||||
dec rdi
|
||||
jnz .Lim_loop
|
||||
|
||||
mov rax, r8
|
||||
xor rax, r9
|
||||
xor rax, r10
|
||||
xor rax, r11
|
||||
ret
|
||||
|
||||
.Lim_zero:
|
||||
xor eax, eax
|
||||
ret
|
||||
FN_END(fm_int_math)
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* uint64_t fm_fp_math(uint64_t iters) [rdi = iters]
|
||||
*
|
||||
* Double-precision scalar FP. Four multiply-add chains for the pipelined
|
||||
* path; sqrtsd and divsd for the non-pipelined divide/sqrt unit that usually
|
||||
* separates cores. Returns the result bit-cast to u64.
|
||||
* =================================================================== */
|
||||
FN_BEGIN(fm_fp_math)
|
||||
test rdi, rdi
|
||||
jz .Lfp_zero
|
||||
|
||||
movsd xmm0, [rip + .Lfp_consts + 0] /* a = 1.5 */
|
||||
movsd xmm1, [rip + .Lfp_consts + 8] /* b = 2.5 */
|
||||
movsd xmm2, [rip + .Lfp_consts + 16] /* c = 3.5 */
|
||||
movsd xmm3, [rip + .Lfp_consts + 24] /* d = 0.5 */
|
||||
movsd xmm4, [rip + .Lfp_consts + 32] /* mul */
|
||||
movsd xmm5, [rip + .Lfp_consts + 40] /* addend */
|
||||
movsd xmm6, [rip + .Lfp_consts + 48] /* 2.0 */
|
||||
movsd xmm7, [rip + .Lfp_consts + 56] /* 1.0 */
|
||||
|
||||
.Lfp_loop:
|
||||
/* four independent multiply-add chains (no baseline FMA) */
|
||||
mulsd xmm0, xmm4
|
||||
addsd xmm0, xmm5
|
||||
mulsd xmm1, xmm4
|
||||
addsd xmm1, xmm5
|
||||
mulsd xmm2, xmm4
|
||||
addsd xmm2, xmm5
|
||||
mulsd xmm3, xmm4
|
||||
addsd xmm3, xmm5
|
||||
|
||||
/* keep the accumulators bounded so they never reach inf/NaN */
|
||||
minsd xmm0, xmm6
|
||||
minsd xmm1, xmm6
|
||||
minsd xmm2, xmm6
|
||||
minsd xmm3, xmm6
|
||||
|
||||
/* square root: long latency, low throughput */
|
||||
sqrtsd xmm8, xmm0
|
||||
sqrtsd xmm9, xmm1
|
||||
addsd xmm2, xmm8
|
||||
addsd xmm3, xmm9
|
||||
|
||||
/* divide: 1/(c+1), divisor >= 1 so never zero */
|
||||
movapd xmm10, xmm2
|
||||
addsd xmm10, xmm7
|
||||
movapd xmm11, xmm7
|
||||
divsd xmm11, xmm10
|
||||
addsd xmm0, xmm11
|
||||
|
||||
movapd xmm10, xmm3
|
||||
addsd xmm10, xmm7
|
||||
movapd xmm11, xmm7
|
||||
divsd xmm11, xmm10
|
||||
addsd xmm1, xmm11
|
||||
|
||||
/* abs/neg/max: cheap ops to balance the mix */
|
||||
andpd xmm2, [rip + .Lfp_absmask] /* fabs(c) */
|
||||
xorpd xmm3, [rip + .Lfp_signmask] /* fneg(d) */
|
||||
andpd xmm3, [rip + .Lfp_absmask] /* fabs() */
|
||||
maxsd xmm3, xmm7
|
||||
|
||||
dec rdi
|
||||
jnz .Lfp_loop
|
||||
|
||||
addsd xmm0, xmm1
|
||||
addsd xmm2, xmm3
|
||||
addsd xmm0, xmm2
|
||||
movq rax, xmm0
|
||||
ret
|
||||
|
||||
.Lfp_zero:
|
||||
xor eax, eax
|
||||
ret
|
||||
FN_END(fm_fp_math)
|
||||
|
||||
.p2align 4
|
||||
.Lfp_consts:
|
||||
.double 1.5, 2.5
|
||||
.double 3.5, 0.5
|
||||
.double 1.0625, 0.0009765625
|
||||
.double 2.0, 1.0
|
||||
.p2align 4
|
||||
.Lfp_absmask:
|
||||
.quad 0x7fffffffffffffff, 0x7fffffffffffffff
|
||||
.Lfp_signmask:
|
||||
.quad 0x8000000000000000, 0x8000000000000000
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* uint64_t fm_primes(uint64_t limit, uint8_t *sieve) [rdi, rsi]
|
||||
*
|
||||
* Sieve of Eratosthenes over [0, limit). The routine clears the caller's
|
||||
* scratch itself, so the clearing pass counts as measured work. Strided stores
|
||||
* over a buffer larger than L1 make this a memory-hierarchy test too. Returns
|
||||
* the prime count.
|
||||
* =================================================================== */
|
||||
FN_BEGIN(fm_primes)
|
||||
cmp rdi, 2
|
||||
jb .Lpr_none
|
||||
|
||||
/* zero the sieve, 32 bytes per iteration */
|
||||
pxor xmm0, xmm0
|
||||
xor rax, rax /* index */
|
||||
mov rcx, rdi
|
||||
and rcx, -32 /* bulk portion */
|
||||
.Lpr_clear32:
|
||||
cmp rax, rcx
|
||||
jae .Lpr_clear1
|
||||
movdqu [rsi + rax], xmm0
|
||||
movdqu [rsi + rax + 16], xmm0
|
||||
add rax, 32
|
||||
jmp .Lpr_clear32
|
||||
.Lpr_clear1:
|
||||
cmp rax, rdi
|
||||
jae .Lpr_clear_done
|
||||
mov byte ptr [rsi + rax], 0
|
||||
inc rax
|
||||
jmp .Lpr_clear1
|
||||
.Lpr_clear_done:
|
||||
|
||||
/* mark 0 and 1 as composite */
|
||||
mov byte ptr [rsi], 1
|
||||
mov byte ptr [rsi + 1], 1
|
||||
|
||||
/* outer loop: i = 2; i*i < limit; i++ */
|
||||
mov r8, 2
|
||||
.Lpr_outer:
|
||||
mov rax, r8
|
||||
imul rax, r8 /* i*i */
|
||||
cmp rax, rdi
|
||||
jae .Lpr_count
|
||||
|
||||
movzx edx, byte ptr [rsi + r8]
|
||||
test dl, dl
|
||||
jnz .Lpr_outer_next /* already composite, skip */
|
||||
|
||||
/* inner loop: mark multiples starting at i*i, stride i */
|
||||
mov r9, rax /* j = i*i */
|
||||
.Lpr_inner:
|
||||
cmp r9, rdi
|
||||
jae .Lpr_outer_next
|
||||
mov byte ptr [rsi + r9], 1
|
||||
add r9, r8
|
||||
jmp .Lpr_inner
|
||||
|
||||
.Lpr_outer_next:
|
||||
inc r8
|
||||
jmp .Lpr_outer
|
||||
|
||||
/* count the survivors */
|
||||
.Lpr_count:
|
||||
xor eax, eax /* count */
|
||||
mov r9, 2
|
||||
.Lpr_count_loop:
|
||||
cmp r9, rdi
|
||||
jae .Lpr_done
|
||||
cmp byte ptr [rsi + r9], 0
|
||||
jne .Lpr_count_next
|
||||
inc rax
|
||||
.Lpr_count_next:
|
||||
inc r9
|
||||
jmp .Lpr_count_loop
|
||||
|
||||
.Lpr_done:
|
||||
ret
|
||||
|
||||
.Lpr_none:
|
||||
xor eax, eax
|
||||
ret
|
||||
FN_END(fm_primes)
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* uint64_t fm_simd(uint64_t iters, void *buf) [rdi = iters, rsi = buf]
|
||||
*
|
||||
* "Extended instructions": the SSE2 unit, which is architecturally mandatory
|
||||
* on x86-64 and therefore safe without runtime feature detection. Packed
|
||||
* 16-bit integer arithmetic is used (SSE2's widest integer multiply is 16-bit;
|
||||
* 32-bit packed multiply, pmulld, is an SSE4.1 extension and is avoided), plus
|
||||
* saturating/averaging ops, widening multiply-add, shuffles and the packed
|
||||
* single-precision float path including the reciprocal/rsqrt estimates.
|
||||
*
|
||||
* buf must be at least 128 bytes. Returns a checksum.
|
||||
* =================================================================== */
|
||||
FN_BEGIN(fm_simd)
|
||||
test rdi, rdi
|
||||
jz .Lsd_zero
|
||||
|
||||
/* seed eight vectors from the scratch buffer */
|
||||
movdqu xmm0, [rsi]
|
||||
movdqu xmm1, [rsi + 16]
|
||||
movdqu xmm2, [rsi + 32]
|
||||
movdqu xmm3, [rsi + 48]
|
||||
movdqu xmm4, [rsi + 64]
|
||||
movdqu xmm5, [rsi + 80]
|
||||
movdqu xmm6, [rsi + 96]
|
||||
movdqu xmm7, [rsi + 112]
|
||||
|
||||
/* float operands: convert the integer seeds to finite floats rather
|
||||
* than reinterpreting bit patterns (which would be NaNs/denormals) */
|
||||
cvtdq2ps xmm12, xmm0
|
||||
cvtdq2ps xmm13, xmm1
|
||||
pxor xmm14, xmm14 /* float 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
|
||||
|
||||
/* saturating and averaging arithmetic */
|
||||
paddsw xmm4, xmm0
|
||||
paddusw xmm5, xmm1
|
||||
psubsw xmm6, xmm2
|
||||
psubusw xmm7, xmm3
|
||||
|
||||
/* widening multiply-add: 16->32 bit lanes */
|
||||
movdqa xmm8, xmm0
|
||||
pmaddwd xmm8, xmm1
|
||||
paddd xmm3, xmm8
|
||||
|
||||
/* high-half multiply */
|
||||
movdqa xmm9, xmm0
|
||||
pmulhw xmm9, xmm1
|
||||
pxor xmm2, xmm9
|
||||
|
||||
/* shifts and logic */
|
||||
movdqa xmm10, xmm2
|
||||
pslld xmm10, 3
|
||||
psrld xmm2, 29
|
||||
por xmm2, xmm10
|
||||
pand xmm3, xmm4
|
||||
|
||||
/* min/max */
|
||||
pmaxsw xmm5, xmm0
|
||||
pminsw xmm6, xmm1
|
||||
|
||||
/* byte average and sum-of-absolute-differences */
|
||||
pavgb xmm7, xmm4
|
||||
movdqa xmm11, xmm0
|
||||
psadbw xmm11, xmm5
|
||||
paddw xmm4, xmm11
|
||||
|
||||
/* lane shuffle: reverse the four 32-bit lanes */
|
||||
pshufd xmm0, xmm0, 0x1B
|
||||
pxor xmm1, xmm0
|
||||
|
||||
/* single-precision float SIMD: multiply-add plus the reciprocal and
|
||||
* rsqrt estimates that shader-style code leans on */
|
||||
movaps xmm15, xmm12
|
||||
mulps xmm15, xmm13
|
||||
addps xmm14, xmm15
|
||||
rcpps xmm8, xmm12
|
||||
rsqrtps xmm9, xmm14
|
||||
addps xmm14, xmm8
|
||||
mulps xmm12, xmm9
|
||||
|
||||
dec rdi
|
||||
jnz .Lsd_loop
|
||||
|
||||
/* fold the eight integer vectors together */
|
||||
pxor xmm0, xmm1
|
||||
pxor xmm2, xmm3
|
||||
pxor xmm4, xmm5
|
||||
pxor xmm6, xmm7
|
||||
pxor xmm0, xmm2
|
||||
pxor xmm4, xmm6
|
||||
pxor xmm0, xmm4
|
||||
|
||||
/* fold in the float accumulator (truncate to int lanes) */
|
||||
cvttps2dq xmm14, xmm14
|
||||
pxor xmm0, xmm14
|
||||
|
||||
/* 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
|
||||
ret
|
||||
|
||||
.Lsd_zero:
|
||||
xor eax, eax
|
||||
ret
|
||||
FN_END(fm_simd)
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* uint64_t fm_compress(const uint8_t *src, uint64_t len, uint32_t *ht)
|
||||
* [rdi, rsi, rdx]
|
||||
*
|
||||
* The match-finding inner loop of an LZ77 compressor (the LZ4 fast strategy):
|
||||
* hash the next 4 bytes, probe a single-entry-per-bucket table, verify, then
|
||||
* extend. Branch-heavy with a data-dependent, cache-missing table probe.
|
||||
*
|
||||
* ht must hold 1<<16 uint32_t (256 KiB); this routine clears it itself.
|
||||
* Returns the encoded size in bytes.
|
||||
* =================================================================== */
|
||||
FN_BEGIN(fm_compress)
|
||||
push rbp
|
||||
push rbx
|
||||
push r12
|
||||
push r13
|
||||
push r14
|
||||
push r15
|
||||
|
||||
mov r12, rdi /* src */
|
||||
mov r13, rdx /* ht */
|
||||
|
||||
/* clear the hash table: 1<<16 entries * 4 bytes = 262144 bytes */
|
||||
pxor xmm0, xmm0
|
||||
xor rax, rax
|
||||
mov ecx, 262144
|
||||
.Lcm_clear:
|
||||
movdqu [r13 + rax], xmm0
|
||||
movdqu [r13 + rax + 16], xmm0
|
||||
movdqu [r13 + rax + 32], xmm0
|
||||
movdqu [r13 + rax + 48], xmm0
|
||||
add rax, 64
|
||||
cmp rax, rcx
|
||||
jb .Lcm_clear
|
||||
|
||||
cmp rsi, 16
|
||||
jb .Lcm_tiny
|
||||
|
||||
mov r14, r12 /* ip */
|
||||
mov r15, r12 /* anchor */
|
||||
lea rbx, [r12 + rsi] /* end */
|
||||
lea r10, [rbx - 12] /* mflimit = end - 12 */
|
||||
xor ebp, ebp /* outsize */
|
||||
|
||||
.Lcm_loop:
|
||||
cmp r14, r10
|
||||
jae .Lcm_flush
|
||||
|
||||
mov eax, [r14] /* seq = load32(ip) */
|
||||
imul eax, eax, 0x9E3779B1 /* * Knuth prime 2654435761 */
|
||||
shr eax, 16 /* h = (seq*prime) >> 16 */
|
||||
|
||||
mov ecx, [r13 + rax*4] /* ref_off = ht[h] */
|
||||
mov rdx, r14
|
||||
sub rdx, r12 /* cur_off = ip - src */
|
||||
mov [r13 + rax*4], edx /* ht[h] = cur_off */
|
||||
|
||||
lea rsi, [r12 + rcx] /* ref = src + ref_off */
|
||||
cmp rsi, r14
|
||||
jae .Lcm_no_match /* ref must be strictly behind ip */
|
||||
|
||||
mov rax, r14
|
||||
sub rax, rsi /* distance */
|
||||
cmp rax, 65536
|
||||
jae .Lcm_no_match /* 16-bit offset window */
|
||||
|
||||
mov eax, [rsi]
|
||||
cmp eax, [r14] /* verify the 4-byte match */
|
||||
jne .Lcm_no_match
|
||||
|
||||
/* match confirmed: extend it byte by byte */
|
||||
mov r9, 4 /* ml */
|
||||
.Lcm_extend:
|
||||
lea rax, [r14 + r9]
|
||||
cmp rax, rbx /* ip + ml vs end */
|
||||
jae .Lcm_emit
|
||||
mov cl, [r14 + r9]
|
||||
cmp cl, [rsi + r9]
|
||||
jne .Lcm_emit
|
||||
inc r9
|
||||
jmp .Lcm_extend
|
||||
|
||||
.Lcm_emit:
|
||||
/* token(1) + offset(2) + literals + varint extensions */
|
||||
mov rax, r14
|
||||
sub rax, r15 /* literal run length */
|
||||
add rbp, rax
|
||||
add rbp, 3
|
||||
cmp rax, 15
|
||||
jb .Lcm_no_lit_ext
|
||||
inc rbp /* literal-length extension byte */
|
||||
.Lcm_no_lit_ext:
|
||||
cmp r9, 19
|
||||
jb .Lcm_no_ml_ext
|
||||
inc rbp /* match-length extension byte */
|
||||
.Lcm_no_ml_ext:
|
||||
add r14, r9 /* ip += ml */
|
||||
mov r15, r14 /* anchor = ip */
|
||||
jmp .Lcm_loop
|
||||
|
||||
.Lcm_no_match:
|
||||
inc r14
|
||||
jmp .Lcm_loop
|
||||
|
||||
.Lcm_flush:
|
||||
/* trailing literals */
|
||||
mov rax, rbx
|
||||
sub rax, r15 /* end - anchor */
|
||||
add rbp, rax
|
||||
add rbp, 1
|
||||
mov rax, rbp
|
||||
jmp .Lcm_ret
|
||||
|
||||
.Lcm_tiny:
|
||||
lea rax, [rsi + 1] /* len + 1 */
|
||||
|
||||
.Lcm_ret:
|
||||
pop r15
|
||||
pop r14
|
||||
pop r13
|
||||
pop r12
|
||||
pop rbx
|
||||
pop rbp
|
||||
ret
|
||||
FN_END(fm_compress)
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* uint64_t fm_chacha20(uint8_t *buf, uint64_t len,
|
||||
* const uint8_t key[32], uint64_t rounds)
|
||||
* [rdi, rsi, rdx, rcx]
|
||||
*
|
||||
* ChaCha20 stream cipher, SSE2, four 128-bit state rows. Chosen over AES for
|
||||
* the same reason the NEON file chose it: the AES-NI extension is optional, so
|
||||
* an AES-instruction benchmark would fault (#UD) on cores without it. ChaCha20
|
||||
* needs only baseline SSE2 and is a real, widely deployed cipher.
|
||||
*
|
||||
* len is rounded down to a multiple of 64. `rounds` = passes over the buffer.
|
||||
* Returns a checksum of the keystream output.
|
||||
* =================================================================== */
|
||||
|
||||
/* rotate each 32-bit lane left by n, via shift-left + shift-right + or */
|
||||
#define ROL32(v, n) \
|
||||
movdqa xmm14, v ;\
|
||||
pslld v, n ;\
|
||||
psrld xmm14, (32 - (n)) ;\
|
||||
por v, xmm14
|
||||
|
||||
/* one ChaCha quarter-round over rows a,b,c,d (xmm14 is scratch, via ROL32) */
|
||||
#define QROUND(a, b, c, d) \
|
||||
paddd a, b ;\
|
||||
pxor d, a ;\
|
||||
ROL32(d, 16) ;\
|
||||
paddd c, d ;\
|
||||
pxor b, c ;\
|
||||
ROL32(b, 12) ;\
|
||||
paddd a, b ;\
|
||||
pxor d, a ;\
|
||||
ROL32(d, 8) ;\
|
||||
paddd c, d ;\
|
||||
pxor b, c ;\
|
||||
ROL32(b, 7)
|
||||
|
||||
FN_BEGIN(fm_chacha20)
|
||||
and rsi, -64 /* whole 64-byte blocks only */
|
||||
jz .Lcc_zero
|
||||
test rcx, rcx
|
||||
jz .Lcc_zero
|
||||
|
||||
/* xmm4..7 hold the base state */
|
||||
movdqa xmm4, [rip + .Lcc_sigma] /* "expand 32-byte k" */
|
||||
movdqu xmm5, [rdx] /* key[0..15] */
|
||||
movdqu xmm6, [rdx + 16] /* key[16..31] */
|
||||
pxor xmm7, xmm7 /* counter || nonce = 0 */
|
||||
|
||||
pxor xmm13, xmm13 /* running checksum */
|
||||
xor r8, r8 /* block counter value */
|
||||
|
||||
.Lcc_pass:
|
||||
xor r9, r9 /* byte offset into buf */
|
||||
|
||||
.Lcc_block:
|
||||
/* working state = base state, block counter in lane 0 of row 3.
|
||||
* Row 3 is all-zero (nonce and counter), so a plain movd both sets
|
||||
* the counter lane and clears the nonce lanes. */
|
||||
movdqa xmm0, xmm4
|
||||
movdqa xmm1, xmm5
|
||||
movdqa xmm2, xmm6
|
||||
movd xmm3, r8d
|
||||
|
||||
/* keep originals for the final feed-forward add */
|
||||
movdqa xmm8, xmm0
|
||||
movdqa xmm9, xmm1
|
||||
movdqa xmm10, xmm2
|
||||
movdqa xmm11, xmm3
|
||||
|
||||
mov r10d, 10 /* 10 double rounds = 20 rounds */
|
||||
.Lcc_rounds:
|
||||
/* column round */
|
||||
QROUND(xmm0, xmm1, xmm2, xmm3)
|
||||
|
||||
/* rotate lanes to form the diagonals */
|
||||
pshufd xmm1, xmm1, 0x39 /* <<< 1 lane */
|
||||
pshufd xmm2, xmm2, 0x4E /* <<< 2 lanes */
|
||||
pshufd xmm3, xmm3, 0x93 /* <<< 3 lanes */
|
||||
|
||||
/* diagonal round */
|
||||
QROUND(xmm0, xmm1, xmm2, xmm3)
|
||||
|
||||
/* undo the lane rotation */
|
||||
pshufd xmm1, xmm1, 0x93
|
||||
pshufd xmm2, xmm2, 0x4E
|
||||
pshufd xmm3, xmm3, 0x39
|
||||
|
||||
dec r10d
|
||||
jnz .Lcc_rounds
|
||||
|
||||
/* feed-forward: keystream = working + original */
|
||||
paddd xmm0, xmm8
|
||||
paddd xmm1, xmm9
|
||||
paddd xmm2, xmm10
|
||||
paddd xmm3, xmm11
|
||||
|
||||
/* XOR the keystream into the buffer */
|
||||
lea rax, [rdi + r9]
|
||||
movdqu xmm12, [rax]
|
||||
pxor xmm12, xmm0
|
||||
movdqu [rax], xmm12
|
||||
movdqu xmm12, [rax + 16]
|
||||
pxor xmm12, xmm1
|
||||
movdqu [rax + 16], xmm12
|
||||
movdqu xmm12, [rax + 32]
|
||||
pxor xmm12, xmm2
|
||||
movdqu [rax + 32], xmm12
|
||||
movdqu xmm12, [rax + 48]
|
||||
pxor xmm12, xmm3
|
||||
movdqu [rax + 48], xmm12
|
||||
|
||||
/* accumulate a checksum of the keystream */
|
||||
pxor xmm13, xmm0
|
||||
pxor xmm13, xmm3
|
||||
|
||||
inc r8 /* counter++ */
|
||||
add r9, 64
|
||||
cmp r9, rsi
|
||||
jb .Lcc_block
|
||||
|
||||
dec rcx
|
||||
jnz .Lcc_pass
|
||||
|
||||
/* horizontal add of the checksum lanes */
|
||||
pshufd xmm0, xmm13, 0x4E
|
||||
paddd xmm13, xmm0
|
||||
pshufd xmm0, xmm13, 0xB1
|
||||
paddd xmm13, xmm0
|
||||
movd eax, xmm13
|
||||
ret
|
||||
|
||||
.Lcc_zero:
|
||||
xor eax, eax
|
||||
ret
|
||||
FN_END(fm_chacha20)
|
||||
|
||||
.p2align 4
|
||||
.Lcc_sigma:
|
||||
.long 0x61707865, 0x3320646e, 0x79622d32, 0x6b206574
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* uint64_t fm_physics(double *bodies, uint64_t n, uint64_t steps)
|
||||
* [rdi, rsi, rdx]
|
||||
*
|
||||
* Direct-summation N-body gravity, O(n^2) per step, double precision.
|
||||
* Layout per body, 8 doubles (64 bytes): [x y z mass vx vy vz pad].
|
||||
* The 1/sqrt is a real sqrtsd+divsd (not the rsqrt estimate), exercising the
|
||||
* divide/sqrt unit the way physics code does. Returns a velocity checksum.
|
||||
* =================================================================== */
|
||||
FN_BEGIN(fm_physics)
|
||||
test rsi, rsi
|
||||
jz .Lph_zero
|
||||
test rdx, rdx
|
||||
jz .Lph_zero
|
||||
|
||||
movsd xmm13, [rip + .Lph_dt] /* dt */
|
||||
movsd xmm14, [rip + .Lph_eps2] /* eps^2 */
|
||||
movsd xmm15, [rip + .Lph_one] /* 1.0 */
|
||||
|
||||
.Lph_step:
|
||||
xor r8, r8 /* i */
|
||||
|
||||
.Lph_body_i:
|
||||
mov rax, r8
|
||||
shl rax, 6 /* i * 64 */
|
||||
lea r9, [rdi + rax] /* &bodies[i] */
|
||||
|
||||
movsd xmm0, [r9] /* xi */
|
||||
movsd xmm1, [r9 + 8] /* yi */
|
||||
movsd xmm2, [r9 + 16] /* zi */
|
||||
|
||||
xorpd xmm3, xmm3 /* ax */
|
||||
xorpd xmm4, xmm4 /* ay */
|
||||
xorpd xmm5, xmm5 /* az */
|
||||
|
||||
xor r10, r10 /* j */
|
||||
mov r11, rdi /* &bodies[j] */
|
||||
|
||||
.Lph_body_j:
|
||||
movsd xmm6, [r11] /* xj */
|
||||
movsd xmm7, [r11 + 8] /* yj */
|
||||
movsd xmm8, [r11 + 16] /* zj */
|
||||
movsd xmm9, [r11 + 24] /* mj */
|
||||
|
||||
subsd xmm6, xmm0 /* dx */
|
||||
subsd xmm7, xmm1 /* dy */
|
||||
subsd xmm8, xmm2 /* dz */
|
||||
|
||||
/* r2 = dx*dx + dy*dy + dz*dz + eps^2 (>= eps^2, so the i==j self-term
|
||||
* is finite and contributes exactly 0 below) */
|
||||
movsd xmm10, xmm6
|
||||
mulsd xmm10, xmm6
|
||||
movsd xmm11, xmm7
|
||||
mulsd xmm11, xmm7
|
||||
addsd xmm10, xmm11
|
||||
movsd xmm11, xmm8
|
||||
mulsd xmm11, xmm8
|
||||
addsd xmm10, xmm11
|
||||
addsd xmm10, xmm14 /* + eps^2 */
|
||||
|
||||
sqrtsd xmm10, xmm10 /* r */
|
||||
movsd xmm11, xmm15
|
||||
divsd xmm11, xmm10 /* 1/r */
|
||||
movsd xmm12, xmm11
|
||||
mulsd xmm12, xmm11 /* 1/r^2 */
|
||||
mulsd xmm12, xmm11 /* 1/r^3 */
|
||||
mulsd xmm12, xmm9 /* m/r^3 */
|
||||
|
||||
mulsd xmm6, xmm12 /* dx * m/r^3 */
|
||||
addsd xmm3, xmm6
|
||||
mulsd xmm7, xmm12
|
||||
addsd xmm4, xmm7
|
||||
mulsd xmm8, xmm12
|
||||
addsd xmm5, xmm8
|
||||
|
||||
add r11, 64
|
||||
inc r10
|
||||
cmp r10, rsi
|
||||
jb .Lph_body_j
|
||||
|
||||
/* v += a * dt */
|
||||
movsd xmm6, [r9 + 32] /* vx */
|
||||
movsd xmm7, [r9 + 40] /* vy */
|
||||
movsd xmm8, [r9 + 48] /* vz */
|
||||
mulsd xmm3, xmm13
|
||||
addsd xmm6, xmm3
|
||||
mulsd xmm4, xmm13
|
||||
addsd xmm7, xmm4
|
||||
mulsd xmm5, xmm13
|
||||
addsd xmm8, xmm5
|
||||
movsd [r9 + 32], xmm6
|
||||
movsd [r9 + 40], xmm7
|
||||
movsd [r9 + 48], xmm8
|
||||
|
||||
inc r8
|
||||
cmp r8, rsi
|
||||
jb .Lph_body_i
|
||||
|
||||
/* second pass: x += v * dt (positions move only after all forces) */
|
||||
xor r8, r8
|
||||
mov r11, rdi
|
||||
.Lph_integrate:
|
||||
movsd xmm0, [r11]
|
||||
movsd xmm1, [r11 + 8]
|
||||
movsd xmm2, [r11 + 16]
|
||||
movsd xmm6, [r11 + 32]
|
||||
movsd xmm7, [r11 + 40]
|
||||
movsd xmm8, [r11 + 48]
|
||||
mulsd xmm6, xmm13
|
||||
addsd xmm0, xmm6
|
||||
mulsd xmm7, xmm13
|
||||
addsd xmm1, xmm7
|
||||
mulsd xmm8, xmm13
|
||||
addsd xmm2, xmm8
|
||||
movsd [r11], xmm0
|
||||
movsd [r11 + 8], xmm1
|
||||
movsd [r11 + 16], xmm2
|
||||
add r11, 64
|
||||
inc r8
|
||||
cmp r8, rsi
|
||||
jb .Lph_integrate
|
||||
|
||||
dec rdx
|
||||
jnz .Lph_step
|
||||
|
||||
/* checksum: sum of all velocity components */
|
||||
xorpd xmm0, xmm0
|
||||
xor r8, r8
|
||||
mov r11, rdi
|
||||
.Lph_sum:
|
||||
movsd xmm6, [r11 + 32]
|
||||
movsd xmm7, [r11 + 40]
|
||||
movsd xmm8, [r11 + 48]
|
||||
addsd xmm0, xmm6
|
||||
addsd xmm0, xmm7
|
||||
addsd xmm0, xmm8
|
||||
add r11, 64
|
||||
inc r8
|
||||
cmp r8, rsi
|
||||
jb .Lph_sum
|
||||
|
||||
movq rax, xmm0
|
||||
ret
|
||||
|
||||
.Lph_zero:
|
||||
xor eax, eax
|
||||
ret
|
||||
FN_END(fm_physics)
|
||||
|
||||
.p2align 4
|
||||
.Lph_dt:
|
||||
.double 0.0078125 /* dt */
|
||||
.Lph_eps2:
|
||||
.double 0.0625 /* eps^2 */
|
||||
.Lph_one:
|
||||
.double 1.0
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* uint64_t fm_sort(uint32_t *a, uint64_t n) [rdi = a, rsi = n]
|
||||
*
|
||||
* In-place heapsort: no recursion or explicit stack, aggressively
|
||||
* branch-unpredictable, with scattered memory access - it stresses the branch
|
||||
* predictor and the cache hierarchy. Returns an order-sensitive checksum,
|
||||
* which also verifies the sort.
|
||||
*
|
||||
* Uses only caller-saved registers, so no prologue is needed; the internal
|
||||
* siftdown is reached with `call` (contract below).
|
||||
* =================================================================== */
|
||||
FN_BEGIN(fm_sort)
|
||||
cmp rsi, 2
|
||||
jb .Lst_trivial
|
||||
|
||||
/* ---- build the max-heap: for i = n/2 - 1 down to 0 ---- */
|
||||
mov r8, rsi
|
||||
shr r8, 1 /* i = n/2 */
|
||||
.Lst_build:
|
||||
test r8, r8
|
||||
jz .Lst_extract
|
||||
dec r8 /* i-- */
|
||||
mov rcx, r8 /* root */
|
||||
mov rdx, rsi /* end */
|
||||
call .Lst_siftdown
|
||||
test r8, r8
|
||||
jnz .Lst_build
|
||||
|
||||
/* ---- extract: for end = n-1 down to 1 ---- */
|
||||
.Lst_extract:
|
||||
mov r9, rsi
|
||||
dec r9 /* end = n-1 */
|
||||
.Lst_extract_loop:
|
||||
test r9, r9
|
||||
jz .Lst_checksum
|
||||
|
||||
/* swap a[0] and a[end] */
|
||||
mov eax, [rdi]
|
||||
mov r10d, [rdi + r9*4]
|
||||
mov [rdi], r10d
|
||||
mov [rdi + r9*4], eax
|
||||
|
||||
xor ecx, ecx /* root = 0 */
|
||||
mov rdx, r9 /* end = end */
|
||||
call .Lst_siftdown
|
||||
|
||||
dec r9
|
||||
jmp .Lst_extract_loop
|
||||
|
||||
/* ---- order-sensitive checksum ---- */
|
||||
.Lst_checksum:
|
||||
xor eax, eax
|
||||
xor rcx, rcx /* index */
|
||||
.Lst_cksum_loop:
|
||||
mov r10d, [rdi + rcx*4]
|
||||
xor rax, r10
|
||||
ror rax, 7
|
||||
add rax, r10
|
||||
inc rcx
|
||||
cmp rcx, rsi
|
||||
jb .Lst_cksum_loop
|
||||
ret
|
||||
|
||||
.Lst_trivial:
|
||||
xor eax, eax
|
||||
test rsi, rsi
|
||||
jz .Lst_trivial_ret
|
||||
mov eax, [rdi]
|
||||
.Lst_trivial_ret:
|
||||
ret
|
||||
|
||||
/* ---- local helper: siftdown(root = rcx, end = rdx)
|
||||
* base a = rdi; clobbers rax, rcx, r10, r11 only. r8 (build i),
|
||||
* r9 (extract end), rsi (n), rdx (end) all survive. ---- */
|
||||
.Lst_siftdown:
|
||||
mov r11, rcx /* root */
|
||||
.Lst_sift_loop:
|
||||
lea r10, [r11 + r11 + 1] /* child = 2*root + 1 */
|
||||
cmp r10, rdx
|
||||
jae .Lst_sift_done /* no children */
|
||||
|
||||
lea rax, [r10 + 1] /* right = child + 1 */
|
||||
cmp rax, rdx
|
||||
jae .Lst_sift_have_child /* no right child */
|
||||
mov ecx, [rdi + rax*4] /* a[right] */
|
||||
cmp ecx, [rdi + r10*4] /* a[right] vs a[child] */
|
||||
jbe .Lst_sift_have_child /* keep child if a[right] <= it */
|
||||
mov r10, rax /* else child = right */
|
||||
|
||||
.Lst_sift_have_child:
|
||||
mov eax, [rdi + r11*4] /* a[root] */
|
||||
mov ecx, [rdi + r10*4] /* a[child] */
|
||||
cmp eax, ecx
|
||||
jae .Lst_sift_done /* heap property holds */
|
||||
|
||||
/* swap and descend */
|
||||
mov [rdi + r11*4], ecx
|
||||
mov [rdi + r10*4], eax
|
||||
mov r11, r10
|
||||
jmp .Lst_sift_loop
|
||||
|
||||
.Lst_sift_done:
|
||||
ret
|
||||
FN_END(fm_sort)
|
||||
|
||||
|
||||
/* ===================================================================
|
||||
* uint64_t fm_chase(void **ptrs, uint64_t steps) [rdi = ptrs, rsi = steps]
|
||||
*
|
||||
* Pointer chase around a randomised cycle. Every load depends on the previous
|
||||
* one, so nothing can be prefetched, overlapped or reordered - this measures
|
||||
* the pure serial latency of the memory hierarchy. The truest single-threaded
|
||||
* test in the suite.
|
||||
* =================================================================== */
|
||||
FN_BEGIN(fm_chase)
|
||||
test rsi, rsi
|
||||
jz .Lch_zero
|
||||
mov rax, rdi /* p = ptrs */
|
||||
mov rcx, rsi
|
||||
|
||||
.Lch_loop:
|
||||
mov rax, [rax]
|
||||
dec rcx
|
||||
jnz .Lch_loop
|
||||
|
||||
sub rax, rdi /* final offset, keeps p live */
|
||||
ret
|
||||
|
||||
.Lch_zero:
|
||||
xor eax, eax
|
||||
ret
|
||||
FN_END(fm_chase)
|
||||
|
||||
|
||||
#if defined(__ELF__)
|
||||
.section .note.GNU-stack, "", @progbits
|
||||
#endif
|
||||
Reference in New Issue
Block a user