Skip to content

radix60: fix the Fermat-mod AVX/AVX2 carry reading past its subchain tables (SIGSEGV in short/hiacc chain mode) - #369

Open
MarkRose wants to merge 1 commit into
primesearch:mainfrom
MarkRose:fix-radix60-fermat-subchain-counts
Open

MarkRose wants to merge 1 commit into
primesearch:mainfrom
MarkRose:fix-radix60-fermat-subchain-counts

Conversation

@MarkRose

@MarkRose MarkRose commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

What crashes

An AVX2 build doing Pepin tests can segfault in cy60_process_chunk, at radix60_main_carry_loop.h:664 (SSE2_fermat_carry_norm_errcheck_X4_loacc), as soon as the carry chain length is raised to short or hiacc on a leading-radix-60 FFT.

Reproduction (current main, f05d3f0, avx2, gcc 16.2)

worktodo.txt:

Fermat,Test=17
Fermat,Test=18

run with ./Mlucas -shift 0 -cpu 0:3. fermat.cfg has 7K 28 8 16 and 15K 60 8 16 entries from -f 17 -fft 7 / -f 18 -fft 15, plus 6K and 13K entries that lose on timing. On current main worktodo mode needs those 6K/13K entries to get going at all: the default lengths for F17/F18 (6K/13K) have no Fermat-capable radix set, and without an entry for them the run loops on self-tests forever. #174 fixes that part.

F18 starts at 15K (60 8 16), above its 13K default. At the first checkpoint the FFT-length reversion steps back toward 13K (goto SETUP_FFT), picks 15K again, raises USE_SHORT_CY_CHAIN to hiacc and reloads the savefile in-process. The next carry pass segfaults. Run alone in worktodo, F18 survives all 26 checkpoints on the optimized build. That is only luck: under ASan the same F18-alone run stops at the first hiacc pass with the overflow below.

ASan (avx2, -O1):

ERROR: AddressSanitizer: stack-buffer-overflow ... READ of size 4
    #0 cy60_process_chunk ../src/radix60_main_carry_loop.h:630
    [4160, 4180) 'nexec_hiacc' (line 2638) <== Memory access at offset 4180 overflows this variable

Root cause

The Fermat-mod AVX/AVX2 carry loop runs 1 << nfold subchains, where nfold = USE_SHORT_CY_CHAIN (0..3). For each one it reads one entry of inc_arr, which is that subchain's length (the entries must sum to RADIX/4 = 15). radix60's tables were

nexec_long[] = {15}, nexec_med[] = {8,7}, nexec_short[] = {5,5,5}, nexec_hiacc[] = {3,3,3,3,3};

so short reads 1 int past nexec_short (4 subchains, 3 entries) and hiacc reads 3 past nexec_hiacc (8 subchains, 5 entries). Whatever is on the stack there becomes both the inner-loop count and the offset into the roots table. When it is 0 the extra subchains do nothing and the residue is even right. Otherwise tm0/tm1 run past the carry arrays, and the result depends on the value: a SIGSEGV, a thread-local memcheck abort, or a silently wrong residue. To see which, I gave main's tables an explicit trailing value standing in for that stack word and forced the chain mode on F18 at 15K (60,8,16), 1000 iterations. With 1 in short mode the run finished normally with a wrong Res64 (1511AE8A19230CAC threaded, 580BF0ECE62C9213 single-threaded, against 8193BD41931E9DE8), with no error reported; with 2 or 7, or in hi-acc mode, it aborted on the memcheck at radix60_ditN_cy_dif1.c:1916/1921. The single-threaded copy of the tables (line 232) has the same defect. radix224, radix240 and radix960 are the only other users of this loop, and their tables all have exactly 2^nfold entries. radix60 is the only one whose RADIX/4 is odd, and it fell back to equal pieces with the wrong count.

Two paths raise the chain length mid-run: the per-checkpoint FFT-length reversion (#21, which #74 gates), and the roundoff handling in fermat_mod_square, which steps the chain down one notch on a 0.4375 fractional part before it gives up on the FFT length. #74 and #174 only change how often this code is reached. The ROE-retry path reaches it either way.

The fix

Split 15 into 4 and 8 pieces, as the sibling radices do with their odd quotients:

nexec_short[] = {4,4,4,3}, nexec_hiacc[] = {2,2,2,2,2,2,2,1};

in both copies, plus a comment saying the tables need exactly 2^nfold entries. AVX-512 is untouched: radix60 has no AVX-512 Fermat carry and keeps its {0} placeholders.

Tests

  • Reproduction above, before: SIGSEGV at F18's first checkpoint. After: F17 and F18 run to completion, F18 through 26 hiacc restarts. F18 Res64 = 506A5A0ABC27E6F0 (SH 10874364700, 14070013587). That matches the run with Gate the per-checkpoint FFT-length reversion (fixes #21) #74 merged, which never leaves the long chain, and main's F18-alone run.
  • ASan (avx2 -O1) on the reproduction, and on F18 alone: before, stack-buffer-overflow on nexec_hiacc. After, no report, and the same residues.
  • Each chain mode forced on -f 18 -fft 15 -iters 1000 (60,8,16), with a throwaway env override of USE_SHORT_CY_CHAIN in a test build. Threaded (4 threads, cy60_process_chunk copy), before, under ASan: long and medium clean, short and hiacc stack-buffer-overflow. After: all four give Res64 8193BD41931E9DE8, with AvgMaxErr falling from long to hiacc (0.00334 / 0.00215 / 0.00173 / 0.00151), on both the ASan and the -O3 build. Unthreaded ASan build (the line-232 copy): before, short overflows nexec_short and hiacc overflows nexec_hiacc. After, both are clean with the same Res64.
  • -s tt -cpu 0:3 (avx2): no Res64 Error. The 1K (0 of 2) and 2K (1 of 2) radix-set lines are the same as on main.
  • Fermat lengths with a leading-radix-60 set (-f 18 -fft 15, -f 19 -fft 30, -f 20 -fft 60) at 100/1000/10000 iterations, single-threaded and -cpu 0:3: every radix set passes (60,8,16 at 15K; 60,8,32 and 60,16,16 at 30K; 60,16,32 and 60,32,16 at 60K). The Res64 agrees between radix sets and thread counts: F18 7C6B681485EB86DB / 8193BD41931E9DE8 / 9D6A242467D28700, F19 529E54642A813995 / E24EAE4B153EE86B / 787FABD98DD5FEC0, F20 64629CED6E218018 / A380121F6FD26B2A / 352CB92DABA82A8B. These runs use the default long chain, so they check that nothing else changed. The forced-mode runs above are the ones that exercise the fix.
  • avx and avx512 builds compile cleanly. radix60 rejects Fermat-mod under AVX-512, so there is nothing to run there.

Overlap with other open PRs

No open PR touches these tables or this loop. #74 (reversion gate) and #174 (Fermat-aware default FFT length, no self-test retry loop) change how often the chain length gets raised, so either one hides this particular reproduction. Neither fixes the table. #364 changes radix60's AVX-512 Mersenne HIACC weights, which are separate code. #316 and #325 also edit radix60_ditN_cy_dif1.c, but git merges them with this change cleanly.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Refined carry handling for selected AVX Fermat-mod calculations, including high-accuracy configurations. This update is limited to those calculation paths; other carry handling remains unchanged. No user-facing controls or features have changed.

The AVX/AVX2 Fermat-mod carry loop in radix60_main_carry_loop.h runs
1 << nfold subchains (nfold = USE_SHORT_CY_CHAIN) and reads one
inc_arr entry per subchain. radix60's nexec_short and nexec_hiacc
tables had 3 and 5 entries, so the short and hiacc chain modes read
1 and 3 ints past the end of the array. Whatever was on the stack
became the inner-loop count and the root-table offset; when it was
nonzero the carry macro walked off the end of the carry arrays and
segfaulted (ASan: stack-buffer-overflow on nexec_hiacc).

The run hits this whenever the chain length is raised to short or
hiacc: by the FFT-length reversion at a checkpoint (e.g. F18 at 15K
in worktodo mode, which reverts toward 13K at every checkpoint and
switches to hiacc), or by the ROE retry in fermat_mod_square.

Split RADIX/4 = 15 into 4 and 8 pieces, as radix224/240/960 already
do, in both the single-threaded and the cy60_process_chunk copies.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review.

Recent review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: ASSERTIVE
  • Plan: Advanced
  • Run ID: 6fa32778-475a-4fc5-8875-e5d77806771d
Commits

Reviewing files that changed from the base of the PR and between f05d3f0 and f75d404.

Files selected for processing: 1
  • src/radix60_ditN_cy_dif1.c

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

The AVX Fermat-mod carry setup now uses four nex entries for the short-chain case and eight for HIACC. Each partition sums to 15. The long and med partitions remain unchanged.

Changes: AVX carry setup

Layer / File(s) Summary
Update nex partition counts
src/radix60_ditN_cy_dif1.c
The setup and worker now use four nex entries for the short-chain case and eight for HIACC. Each partition sums to 15. The comments state the 2^nfold entry-count and sum requirement. The long and med partitions are unchanged.

Priority: Normal

Estimated code review effort: 2 (Simple) | ~5 minutes

Change: Bug fix

Suggested labels: bug

Suggested reviewers: tdulcet

Merge Risk: Minimal · up to f75d4

No actionable merge-blocking risk is identified from the supplied change context.

Pre-merge checks: Passed 5
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly identifies the radix60 Fermat-mod AVX/AVX2 carry-table bug and the resulting SIGSEGV. It accurately summarizes the main change, although it is somewhat long.
Docstring Coverage Passed No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 1 files.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MarkRose

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Oct 28, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added the bug Something isn't working label Oct 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant