; Copyright (C) 2026 Kiyotsugu Arai ; SPDX-License-Identifier: LGPL-3.0-or-later ; ; mpn_x64_mod1.asm — dedicated 1-limb mod (preinv) function ; ; Function: ; mpn_mod_1_preinv_asm(ap, an, d_norm, dinv, shift) -> uint64_t remainder ; ; Use: inside IntPrime::isDivisibleBySmallPrime trial division, ; fast remainder of multi-limb / single-limb (equivalent to GMP's mpn_mod_1_1). ; ; Algorithm: ; Returns (Σ ap[i] * 2^{64i}) mod d_actual (d_actual = d_norm >> shift) ; For each limb, using the Möller-Granlund (2011) preinv quotient estimate, ; iterate r ← ((r << 64) | n1) mod d_norm. ; When shift > 0, combine adjacent limbs to form n1 (normalized n1). ; ; Windows x64 calling convention: ; rcx = ap, rdx = an, r8 = d_norm, r9 = dinv ; [rsp+40] = shift (uint64_t, low byte is 0..63) ; Return value: rax = remainder ; Preserved: rbx, rbp, rdi, rsi, r12-r15 ; Clobbered: rax, rcx, rdx, r8-r11 .code mpn_mod_1_preinv_asm PROC ; Prologue: push 5 callee-save regs (40 bytes), + 8 for 16-byte alignment push rbx push rsi push rdi push r12 push r13 sub rsp, 8 ; Stack at this point: ; [rsp+0..7] - alignment pad ; [rsp+8..47] - 5 pushed regs (rbx, rsi, rdi, r12, r13) ; [rsp+48..55] - return addr ; [rsp+56..87] - Win64 shadow space (caller's home for rcx, rdx, r8, r9) ; [rsp+88..95] - arg5 (shift) SHIFT_OFFSET = 88 ; Argument setup ; rcx = ap → rsi ; rdx = an → r10 ; r8 = d_norm (stay) ; r9 = dinv → r11 (RAX/RDX are used by MUL, so r9 is not freed) ; shift on stack → rcx (shift in CL, or rshift in rdi) mov rsi, rcx ; rsi = ap mov r10, rdx ; r10 = an mov r11, r9 ; r11 = dinv movzx ecx, byte ptr [rsp + SHIFT_OFFSET] ; cl = shift (0..63) ; an == 0 → return 0 test r10, r10 jz ret_zero ; r12 = r (running remainder) ; r13 = n1 (current digit) ; r8 = d_norm (unchanged) ; r11 = dinv (unchanged) ; rcx = shift ; rdi = rshift = 64 - shift (only used when shift > 0) ; shift == 0 → simpler loop test cl, cl jnz shift_nonzero ; ─────────────── shift == 0 ─────────────── xor r12, r12 ; r = 0 ; Used as loop counter rcx, so shift is unneeded (cl=0 so rcx=0) mov rcx, r10 ; rcx = an (loop counter, downto 0) shift0_loop: ; n1 = ap[rcx-1] mov r13, [rsi + rcx*8 - 8] ; mod_step(r, n1, d_norm, dinv) → r ; rdx:rax = r * dinv mov rax, r12 mul r11 ; rdx:rax = r12 * r11 (high:low) add rax, r13 ; q0 = p_lo + n1, CF = carry adc rdx, r12 ; q1 = p_hi + r + CF inc rdx ; q1++ (tentative) ; rem = n1 - q1 * d_norm mov rdi, rdx ; rdi = q1 imul rdi, r8 ; rdi = q1 * d_norm (low 64 bits) mov r9, r13 ; r9 = n1 sub r9, rdi ; r9 = rem = n1 - q1*d_norm ; if rem > q0: rem += d_norm cmp r9, rax ; rem vs q0 jbe shift0_no_corr1 add r9, r8 ; rem += d_norm shift0_no_corr1: ; if rem >= d_norm: rem -= d_norm cmp r9, r8 jb shift0_no_corr2 sub r9, r8 ; rem -= d_norm shift0_no_corr2: mov r12, r9 ; r = rem dec rcx jnz shift0_loop mov rax, r12 jmp epilogue ; ─────────────── shift != 0 ─────────────── shift_nonzero: ; rdi = rshift = 64 - shift mov rdi, 64 sub rdi, rcx ; rcx (shift) is preserved; we'll use it as CL for shifts ; rdi as CL for rshift (we'll switch via mov) ; r = ap[an-1] >> rshift mov r12, [rsi + r10*8 - 8] ; r12 = ap[an-1] push rcx ; save shift mov rcx, rdi ; cl = rshift shr r12, cl ; r = ap[an-1] >> rshift pop rcx ; restore shift in cl ; if an == 1: skip loop, just process last limb cmp r10, 1 je shift_last ; Loop: i = an-1 downto 1 ; rax (or some reg) = i; we use r10 as i (and decrement) ; n1 = (ap[i] << shift) | (ap[i-1] >> rshift) shift_loop: ; i = r10 mov r13, [rsi + r10*8 - 8] ; high = ap[i-1+1] = ap[i] (we treat r10 = i+1 for indexing convenience) ; Wait: we want ap[i] where i goes from an-1 down to 1. ; r10 currently = an. ap[i] = ap[an-1] when r10 = an. ; Use offset: ap[r10-1] = ap[i] where i = r10-1. ; So loop: while r10 > 1: i = r10-1; process ap[i] and ap[i-1]; r10-- ; Currently r10 = an (>= 2 here), so i = an-1. ; ap[i] = [rsi + (r10-1)*8] = [rsi + r10*8 - 8] ; ap[i-1] = [rsi + (r10-2)*8] = [rsi + r10*8 - 16] mov r13, [rsi + r10*8 - 8] ; ap[i] mov r9, [rsi + r10*8 - 16] ; ap[i-1] ; n1 = (ap[i] << shift) | (ap[i-1] >> rshift) ; SHLD dst, src, cl: dst = (dst << cl) | (src >> (64-cl)) ; So SHLD r13, r9, cl gives r13 = (r13 << cl) | (r9 >> (64-cl)) shld r13, r9, cl ; r13 = n1 ; mod_step(r, n1, d_norm, dinv) mov rax, r12 mul r11 ; rdx:rax = r * dinv add rax, r13 ; q0 adc rdx, r12 ; q1 (with carry) inc rdx mov r9, rdx imul r9, r8 ; q1 * d_norm mov rdx, r13 sub rdx, r9 ; rem = n1 - q1*d_norm cmp rdx, rax jbe shiftN_no_corr1 add rdx, r8 shiftN_no_corr1: cmp rdx, r8 jb shiftN_no_corr2 sub rdx, r8 shiftN_no_corr2: mov r12, rdx ; r = rem dec r10 cmp r10, 1 jne shift_loop shift_last: ; n1 = ap[0] << shift mov r13, [rsi] shl r13, cl ; mod_step mov rax, r12 mul r11 add rax, r13 adc rdx, r12 inc rdx mov r9, rdx imul r9, r8 mov rdx, r13 sub rdx, r9 cmp rdx, rax jbe last_no_corr1 add rdx, r8 last_no_corr1: cmp rdx, r8 jb last_no_corr2 sub rdx, r8 last_no_corr2: mov r12, rdx ; rax = r12 >> shift (denormalize) mov rax, r12 shr rax, cl jmp epilogue ret_zero: xor rax, rax epilogue: add rsp, 8 pop r13 pop r12 pop rdi pop rsi pop rbx ret mpn_mod_1_preinv_asm ENDP END