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