name: CI on: push: branches: - main pull_request: # Skip the matrix for changes that cannot affect any build or test. pull_request only: # pushes to main and the monthly schedule always run everything. A denylist rather than # an allowlist, so an unrecognised new file type still runs CI rather than silently not. paths-ignore: - '**.md' - 'docs/**' - 'LICENSE' - 'help.txt' schedule: - cron: '0 0 1 * *' # One run per ref. A pull_request run is superseded the moment its branch is pushed again, but # without a concurrency group the old run keeps its runners until it finishes on its own - and a run # is 78 jobs and over 1700 runner-minutes, against a limit of 20 concurrent jobs for the whole # organization, only 5 of which can be macOS. A queued job expires if it has not started within # 24 hours, so runners held by a run nobody is waiting for are not just waste: they push live jobs # past the point where they will ever start. # # Pushes to main and the monthly schedule are deliberately not cancelled - those runs are the record # of what the branch does, and a later push should not erase an earlier result. Every other run gets # a group of its own (github.run_id): in a shared group, a pending run is replaced by a newer one even # without cancel-in-progress. concurrency: group: ${{ github.workflow }}-${{ github.event_name == 'pull_request' && github.ref || github.run_id }} cancel-in-progress: ${{ github.event_name == 'pull_request' }} jobs: Linux: name: Mlucas Linux runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-22.04, ubuntu-24.04, ubuntu-26.04, ubuntu-22.04-arm, ubuntu-24.04-arm, ubuntu-26.04-arm] cc: [gcc, clang] fail-fast: false env: CC: ${{ matrix.cc }} # Fail the build on a strict-aliasing violation gcc can see. The level has to be 2, as -Wall's default level 3 # reports none of them, and -Werror=strict-aliasing has to come first: after -Wstrict-aliasing=2 it resets the level # to 3, and -Werror=strict-aliasing=2 does not make the warning an error. makemake.sh appends CPPFLAGS to the # compile command and keeps its default flags. CPPFLAGS: ${{ matrix.cc == 'gcc' && '-Werror=strict-aliasing -Wstrict-aliasing=2' || '' }} steps: - uses: actions/checkout@v7 - name: Install run: | sudo apt-get update -y sudo apt-get install -y libhwloc-dev - name: Before script run: | $CC --version if [[ $HOSTTYPE == aarch64 ]]; then $CC -dM -E -mcpu=native - > $GITHUB_STEP_SUMMARY grep 'warning:' build.log | sed 's/\x1B\[\([0-9]\+\(;[0-9]\+\)*\)\?[mK]//g' | awk '{ print $NF }' | sort | uniq -c | sort -nr >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY # Ladder stops at 'm'. The 'l' self-test (FFT lengths 16M-60M) ran here on every push and # every pull request and was most of this job's self-test time; it now runs monthly against # main in selftest-large.yml, together with 'h', and on demand via its workflow_dispatch. for s in tt t s m; do time ./Mlucas -s $s -cpu "0:$(( $(nproc --all) - 1 ))" |& tee -a test.log; done for s in tt t s; do time ./Mlucas -prp -s $s -cpu "0:$(( $(nproc --all) - 1 ))" |& tee -a test.log; done if [[ $HOSTTYPE == aarch64 ]]; then cd ../obj_NOSIMD fi bash -e -o pipefail -- ../config-fermat.sh -cpu "0:$(( $(nproc --all) - 1 ))" # Mfactor self-test on one build only. The explicit-ISA builds above are for compile # coverage: a runner may not be able to execute what they target. # On ARM the native build is ASIMD, whose trial-factoring routines are stubs - # twopmodq80.c's "No TF support on ARMv8!" lives under #ifdef USE_ARM_V8_SIMD - so run # the NOSIMD build there instead, which takes the scalar TF path and self-tests normally. # Same substitution config-fermat.sh already makes above, and the loop above has already # built these, so it costs no extra build time. if [[ $HOSTTYPE == aarch64 ]]; then mode=NOSIMD; else mode=; fi for word in '' {{1..4},n}word; do # M(521), not M(127): the 4-word build's 256-bit modpow needs p + 256 >= 512 to have a # lead chunk to work with, and asserts ("twopmodq256 : start_index < 2!") below that. # M(127) only ever passed because a stray -DTRYQ=4 sent that build down the single-word # path instead. 521 is the smallest Mersenne-prime exponent every word variant handles, # and costs slightly less than 127 did (726k trial divides vs 1.66M). (cd "../obj_mfac${word:+_$word}${mode:+_$mode}" && time "./Mfactor${word:+_$word}" -m 521 -bmax 20) done - name: Test run: | set -x -o pipefail CFLAGS=(-std=gnu99 -Wall -Wextra -g -O3) CPPFLAGS=(-D_GNU_SOURCE -DUSE_THREADS -DINCLUDE_GMP=0) mkdir obj_test_fft_radix cd obj_test_fft_radix $CC "${CFLAGS[@]}" "${CPPFLAGS[@]}" -c ../src/{Mlucas.c,br.c,dft_macro.c,fermat_mod_square.c,getRealTime.c,get_cpuid.c,get_fft_radices.c,get_fp_rnd_const.c,get_preferred_fft_radix.c,imul_macro.c,mers_mod_square.c,mi64.c,pair_square.c,pm1.c,qfloat.c,radix[0-9]*_ditN_cy_dif1.c,radix8_dif_dit_pass.c,radix16_dif_dit_pass.c,radix16_dyadic_square.c,radix16_wrapper_ini.c,radix16_wrapper_square.c,radix32_dif_dit_pass.c,radix32_dyadic_square.c,radix32_wrapper_ini.c,radix32_wrapper_square.c,rng_isaac.c,threadpool.c,twopmodq.c,twopmodq128.c,twopmodq192.c,twopmodq256.c,types.c} &>build.log $CC "${CFLAGS[@]}" "${CPPFLAGS[@]}" -c -DTEST_FFT_RADIX ../src/util.c &>>build.log mapfile -d '' files < <(printf '%s\0' ../src/radix[0-9]*_ditN_cy_dif1.c | sort -zV) for file in "${files[@]}"; do radix=${file##*/radix} radix=${radix%%_*} printf '\nTesting RADIX %s\n' "$radix" for ttype in 1 2 3; do printf '\n\tTTYPE %s\n\n' "$ttype" $CC "${CFLAGS[@]}" "${CPPFLAGS[@]}" -c -DRADIX="$radix" -DTTYPE="$ttype" ../src/test_fft_radix.c &>>build.log $CC "${CFLAGS[@]}" -o test_fft_radix *.o -lm -lpthread -lrt &>>build.log time ./test_fft_radix |& tee -a test_fft_radix.log | grep -i 'error\|warn\|assert\|test_fft_radix' done done rm -- *.o test_fft_radix - uses: actions/upload-artifact@v7 if: always() with: name: ${{ matrix.os }}_${{ matrix.cc }}_mlucas path: | obj*/build.log obj*/test.log obj*/mlucas.cfg obj*/config-fermat.log obj*/fermat.cfg if-no-files-found: warn Linux-32-bit: name: Mlucas Linux 32-bit runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, ubuntu-26.04-arm] cc: [gcc] # clang fail-fast: false env: CC: ${{ matrix.cc }} # Fail the build on a strict-aliasing violation gcc can see. The level has to be 2, as -Wall's default level 3 # reports none of them, and -Werror=strict-aliasing has to come first: after -Wstrict-aliasing=2 it resets the level # to 3, and -Werror=strict-aliasing=2 does not make the warning an error. makemake.sh appends CPPFLAGS to the # compile command and keeps its default flags. CPPFLAGS: ${{ matrix.cc == 'gcc' && '-Werror=strict-aliasing -Wstrict-aliasing=2' || '' }} steps: - uses: actions/checkout@v7 - name: Before script run: | sed -i 's/^MAX=29/MAX=28/' config-fermat.sh - name: Script run: | docker run --rm -i --platform "${{ endsWith(matrix.os, '-arm') && 'linux/arm/v7' || 'linux/386' }}" -v "$GITHUB_WORKSPACE:/workspace" -w /workspace -e CC=$CC -e CPPFLAGS debian:13 bash -s -e -o pipefail <<'EOF' apt-get update apt-get install -y build-essential libgmp-dev libhwloc-dev if [[ $CC == clang ]]; then apt-get install -y clang fi $CC --version for mode in '' NOSIMD; do echo -e "\n$mode\n" bash -e -o pipefail -- makemake.sh use_hwloc $mode (cd obj${mode:+_$mode}; make clean) for word in '' {{1..4},n}word; do echo -e "\nMfactor $word\n" bash -e -o pipefail -- makemake.sh mfac $word $mode (cd obj_mfac${word:+_$word}${mode:+_$mode}; make clean) done done cd obj for s in tt t s m; do time ./Mlucas -s $s -cpu "0:$(( $(nproc --all) - 1 ))"; done for s in tt t s; do time ./Mlucas -prp -s $s -cpu "0:$(( $(nproc --all) - 1 ))"; done bash -e -o pipefail -- ../config-fermat.sh -cpu "0:$(( $(nproc --all) - 1 ))" for word in '' {{1..4},n}word; do (cd "../obj_mfac${word:+_$word}" && time "./Mfactor${word:+_$word}" -m 521 -bmax 20) done EOF - uses: actions/upload-artifact@v7 with: name: linux_${{ endsWith(matrix.os, '-arm') && 'arm' || 'x86' }}_${{ matrix.cc }}_mlucas path: | obj*/build.log obj*/test.log obj*/mlucas.cfg obj*/config-fermat.log obj*/fermat.cfg if-no-files-found: warn Linux-Singlethread: name: Mlucas Linux Singlethreaded runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, ubuntu-26.04-arm] cc: [gcc] # clang fail-fast: false env: CC: ${{ matrix.cc }} # Fail the build on a strict-aliasing violation gcc can see. The level has to be 2, as -Wall's default level 3 # reports none of them, and -Werror=strict-aliasing has to come first: after -Wstrict-aliasing=2 it resets the level # to 3, and -Werror=strict-aliasing=2 does not make the warning an error. makemake.sh appends CPPFLAGS to the # compile command and keeps its default flags. CPPFLAGS: ${{ matrix.cc == 'gcc' && '-Werror=strict-aliasing -Wstrict-aliasing=2' || '' }} steps: - uses: actions/checkout@v7 - name: Before script run: | sed -i 's/-DUSE_THREADS//; s/-lpthread//' makemake.sh sed -i 's/^MAX=29/MAX=28/' config-fermat.sh $CC --version - name: Build run: | set -x MODES=( '' NOSIMD ) if [[ $HOSTTYPE == aarch64 ]]; then MODES+=( ASIMD ) else MODES+=( SSE2 AVX AVX2 AVX512 ) if echo 'int main(void){return 0;}' | $CC -march=knl -mavx512f -mavx512er -x c -o /dev/null -; then MODES+=( AVX512_KNL ) else echo "::warning::Skipping the AVX512_KNL build mode as '$($CC --version | head -n1)' does not support -march=knl." fi fi for mode in "${MODES[@]}"; do echo -e "\n$mode\n" bash -e -o pipefail -- makemake.sh $mode (cd obj${mode:+_$mode}; make clean) for word in '' {{1..4},n}word; do echo -e "\nMfactor $word\n" bash -e -o pipefail -- makemake.sh mfac $word $mode (cd obj_mfac${word:+_$word}${mode:+_$mode}; make clean) done done # The single-threaded build keeps only one core busy, but these tests are independent of each other, # so they run as three concurrent steps. # Each runs in its own directory under selftest/, so their cfg files and logs do not collide. # On ARM the native build is ASIMD, whose trial-factoring routines are stubs - twopmodq80.c's # "No TF support on ARMv8!" lives under #ifdef USE_ARM_V8_SIMD - so config-fermat.sh and the Mfactor # self-tests run from the NOSIMD builds there, which the build step has already made. The Mfactor # self-test runs on one build only: the explicit-ISA builds are for compile coverage, and a runner # may not be able to execute what they target. - parallel: - name: Self-test m run: | set -x -o pipefail mkdir -p selftest/m cd selftest/m ln -sf ../../obj/Mlucas . time ./Mlucas -s m |& tee -a test.log - name: LL and PRP self-tests tt/t/s, Mfactor run: | set -x -o pipefail if [[ $HOSTTYPE == aarch64 ]]; then mode=NOSIMD; else mode=; fi mkdir -p selftest/small cd selftest/small ln -sf ../../obj/Mlucas . for s in tt t s; do time ./Mlucas -s $s |& tee -a test.log; done for s in tt t s; do time ./Mlucas -prp -s $s |& tee -a test.log; done for word in '' {{1..4},n}word; do (cd "../../obj_mfac${word:+_$word}${mode:+_$mode}" && time "./Mfactor${word:+_$word}" -m 521 -bmax 20) done - name: Fermat self-tests F15-F28 run: | set -x -o pipefail if [[ $HOSTTYPE == aarch64 ]]; then fermat=obj_NOSIMD; else fermat=obj; fi mkdir -p selftest/fermat cd selftest/fermat ln -sf "../../$fermat/Mlucas" . bash -e -o pipefail -- ../../config-fermat.sh - name: Test run: | set -x -o pipefail CFLAGS=(-std=gnu99 -Wall -Wextra -g -O3) CPPFLAGS=(-D_GNU_SOURCE -DINCLUDE_GMP=0) mkdir obj_test_fft_radix cd obj_test_fft_radix $CC "${CFLAGS[@]}" "${CPPFLAGS[@]}" -c ../src/{Mlucas.c,br.c,dft_macro.c,fermat_mod_square.c,getRealTime.c,get_cpuid.c,get_fft_radices.c,get_fp_rnd_const.c,get_preferred_fft_radix.c,imul_macro.c,mers_mod_square.c,mi64.c,pair_square.c,pm1.c,qfloat.c,radix[0-9]*_ditN_cy_dif1.c,radix8_dif_dit_pass.c,radix16_dif_dit_pass.c,radix16_dyadic_square.c,radix16_wrapper_ini.c,radix16_wrapper_square.c,radix32_dif_dit_pass.c,radix32_dyadic_square.c,radix32_wrapper_ini.c,radix32_wrapper_square.c,rng_isaac.c,threadpool.c,twopmodq.c,twopmodq128.c,twopmodq192.c,twopmodq256.c,types.c} &>build.log $CC "${CFLAGS[@]}" "${CPPFLAGS[@]}" -c -DTEST_FFT_RADIX ../src/util.c &>>build.log mapfile -d '' files < <(printf '%s\0' ../src/radix[0-9]*_ditN_cy_dif1.c | sort -zV) for file in "${files[@]}"; do radix=${file##*/radix} radix=${radix%%_*} printf '\nTesting RADIX %s\n' "$radix" for ttype in 1 2 3; do printf '\n\tTTYPE %s\n\n' "$ttype" $CC "${CFLAGS[@]}" "${CPPFLAGS[@]}" -c -DRADIX="$radix" -DTTYPE="$ttype" ../src/test_fft_radix.c &>>build.log $CC "${CFLAGS[@]}" -o test_fft_radix *.o -lm &>>build.log time ./test_fft_radix |& tee -a test_fft_radix.log | grep -i 'error\|warn\|assert\|test_fft_radix' done done rm -- *.o test_fft_radix - uses: actions/upload-artifact@v7 if: always() with: name: ${{ matrix.os }}_${{ matrix.cc }}_singlethreaded_mlucas path: | obj*/build.log selftest/*/test.log selftest/*/mlucas.cfg selftest/*/config-fermat.log selftest/*/fermat.cfg if-no-files-found: warn Linux-old: name: Mlucas Linux old runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, ubuntu-26.04-arm] container: ["ubuntu:14.04", "ubuntu:16.04", "ubuntu:18.04", "ubuntu:20.04", "alpine:3.24"] # "ubuntu:12.04" cc: [gcc, clang] exclude: # Ubuntu 14.04's clang (3.4-era) predates AArch64 NEON 'v'-register asm syntax, so it # cannot assemble the inline asm in dft_macro.c on ARM ("unknown register name 'v0'"). # Its gcc builds fine there, as do both compilers on x86-64, so drop only this one combo. - os: ubuntu-26.04-arm container: "ubuntu:14.04" cc: clang fail-fast: false env: CC: ${{ matrix.cc }} steps: - uses: actions/checkout@v7 - name: Before script run: | sed -i 's/^MAX=29/MAX=25/' config-fermat.sh - name: Script run: | docker run --rm -i -v "$GITHUB_WORKSPACE:/workspace" -w /workspace -e CC=$CC "${{ matrix.container }}" ${{ startsWith(matrix.container, 'alpine:') && 'sh -c "apk add bash; exec bash -s"' || 'bash -s' }} <<'EOF' set -ex -o pipefail if [[ "${{ matrix.container }}" == ubuntu:* ]]; then apt-get update DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential libgmp-dev libhwloc-dev HAVE_HWLOC=1 if [[ $CC == clang ]]; then apt-get install -y clang fi elif [[ "${{ matrix.container }}" == alpine:* ]]; then apk update apk add build-base gmp-dev if apk add hwloc-dev; then HAVE_HWLOC=1 fi if [[ $CC == clang ]]; then apk add clang fi fi $CC --version ldd --version || true MODES=( '' NOSIMD ) if [[ $HOSTTYPE == aarch64 ]]; then MODES+=( ASIMD ) else MODES+=( SSE2 AVX AVX2 ) # Only test the AVX-512 build mode if the compiler's assembler can handle the AVX-512 constructs # Mlucas's kernels use: the extended registers (zmm16-31/xmm16-31/k0-7). Ancient clang can't - # clang 3.8 (ubuntu:16.04) rejects the extended register names ("unknown register name 'xmm30'"). # (The one AVX-512ER instruction, vrcp28pd, lived in an unused mi64.c routine and is now gated to # KNL-only via __AVX512ER__, so plain AVX-512 builds no longer need an ER-capable assembler - # which is why clang 5.0 on alpine:3.8 can now build AVX-512.) Probe and skip AVX-512 rather than # fail the build on a mode that provably can't work here. if echo 'int main(void){__asm__ __volatile__("vpxord %%zmm31,%%zmm31,%%zmm31\n\tvmovq %%rax,%%xmm16\n\tvpmovzxbq %%xmm16,%%zmm16\n\tkmovw %%k1,%%eax":::"zmm31","xmm16","zmm16","k1","rax");return 0;}' | $CC -mavx512f -mavx512cd -mfma -x c -o /dev/null -; then MODES+=( AVX512 ) else echo "::warning::Skipping the AVX512 build mode as the '$($CC --version | head -n1)' assembler cannot handle the required AVX512 extended registers (zmm16-31/xmm16-31/k0-7)." fi if echo 'int main(void){__asm__ __volatile__("vpxord %%zmm31,%%zmm31,%%zmm31\n\tvmovq %%rax,%%xmm16\n\tvpmovzxbq %%xmm16,%%zmm16\n\tkmovw %%k1,%%eax":::"zmm31","xmm16","zmm16","k1","rax");return 0;}' | $CC -march=knl -mavx512f -mavx512er -x c -o /dev/null -; then MODES+=( AVX512_KNL ) else echo "::warning::Skipping the AVX512_KNL build mode as '$($CC --version | head -n1)' does not support -march=knl." fi fi for mode in "${MODES[@]}"; do echo -e "\n$mode\n" bash -e -o pipefail -- makemake.sh ${HAVE_HWLOC:+use_hwloc} $mode (cd obj${mode:+_$mode}; make clean) for word in '' {{1..4},n}word; do echo -e "\nMfactor $word\n" bash -e -o pipefail -- makemake.sh mfac $word $mode (cd obj_mfac${word:+_$word}${mode:+_$mode}; make clean) done done cd obj for s in tt t s; do time ./Mlucas -s $s -cpu "0:$(( $(nproc --all) - 1 ))"; done for s in tt t; do time ./Mlucas -prp -s $s -cpu "0:$(( $(nproc --all) - 1 ))"; done if [[ $HOSTTYPE == aarch64 ]]; then cd ../obj_NOSIMD fi bash -e -o pipefail -- ../config-fermat.sh -cpu "0:$(( $(nproc --all) - 1 ))" if [[ $HOSTTYPE == aarch64 ]]; then mode=NOSIMD; else mode=; fi for word in '' {{1..4},n}word; do (cd "../obj_mfac${word:+_$word}${mode:+_$mode}" && time "./Mfactor${word:+_$word}" -m 521 -bmax 20) done EOF - uses: actions/upload-artifact@v7 if: always() with: name: container-${{ strategy.job-index }}_${{ matrix.cc }}_mlucas path: | obj*/build.log obj*/test.log obj*/mlucas.cfg obj*/config-fermat.log obj*/fermat.cfg if-no-files-found: warn Linux-K1OM: name: Mlucas K1OM runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Download run: | # Intel no longer distributes MPSS, so it is hosted unmodified as a release asset here; the # ci-intel-mpss-3.8.6 release has the licence and the Internet Archive copy it matches. wget -nv 'https://github.com/primesearch/Mlucas/releases/download/ci-intel-mpss-3.8.6/mpss-3.8.6-linux.tar' echo '3ce960db4c225f6e27ed6aa51c9dcb158fd93fe682d435ebd245849110b77a1b mpss-3.8.6-linux.tar' | sha256sum -c - tar -xvf mpss-3.8.6-linux.tar - name: Script run: | docker run --rm -i -v "$GITHUB_WORKSPACE:/workspace" -w /workspace centos:7 bash -s -e -o pipefail <<'EOF' sed -i '/^mirrorlist/d;/^#baseurl=/{s|^#||;s|/mirror|/vault|;}' /etc/yum.repos.d/CentOS*.repo # yum update -y yum install -y make yum localinstall -y mpss-3.8.6/mpss-sdk-k1om-3.8.6-1.x86_64.rpm source /opt/mpss/3.8.6/environment-setup-k1om-mpss-linux export CC=k1om-mpss-linux-gcc # The SDK environment exports its own CFLAGS, which makemake.sh would otherwise use instead of its defaults: unset CFLAGS $CC --version k1om-mpss-linux-ld --version $CC -dM -E - > "$GITHUB_ENV" sed -i 's/^MAX=29/MAX=28/' config-fermat.sh $CC --version - name: Script run: | set -x -o pipefail # Build the runner's native SIMD mode under the sanitizers: AVX2 or AVX-512 on x86, # depending on the runner, and ASIMD on ARM. This and the Windows ASan job are the only ones # that build the hand-tuned SIMD asm under a sanitizer. Issue #12, a SEGV in radix48_dit_pass1 during # 'Mlucas -s m', was reported by ASan on an ARM runner. (The macOS ASan job stays on # NOSIMD, see PR #71: there the SIMD asm fails to assemble under -Og, "invalid CFI # advance_loc expression".) bash -e -o pipefail -- makemake.sh use_hwloc (cd obj; make clean) # On ARM, config-fermat.sh runs from the NOSIMD build, as in the Linux job above: the ASIMD # build's trial-factoring routines are stubs ("No TF support on ARMv8!" in twopmodq80.c). if [[ $HOSTTYPE == aarch64 ]]; then bash -e -o pipefail -- makemake.sh use_hwloc nosimd (cd obj_nosimd; make clean) fi # Mfactor builds the native SIMD mode too, except on ARM, where it builds NOSIMD for the same reason. if [[ $HOSTTYPE == aarch64 ]]; then mode=nosimd; else mode=; fi for word in '' {{1..4},n}word; do echo -e "\nMfactor $word\n" bash -e -o pipefail -- makemake.sh mfac $word $mode (cd obj_mfac${word:+_$word}${mode:+_$mode}; make clean) done cd obj for s in tt t s m; do time ./Mlucas -s $s -cpu "0:$(( $(nproc --all) - 1 ))" |& tee -a test.log; done for s in tt t s; do time ./Mlucas -prp -s $s -cpu "0:$(( $(nproc --all) - 1 ))" |& tee -a test.log; done if [[ $HOSTTYPE == aarch64 ]]; then cd ../obj_nosimd fi bash -e -o pipefail -- ../config-fermat.sh -cpu "0:$(( $(nproc --all) - 1 ))" for word in '' {{1..4},n}word; do (cd "../obj_mfac${word:+_$word}${mode:+_$mode}" && time "./Mfactor${word:+_$word}" -m 521 -bmax 20) done - uses: actions/upload-artifact@v7 if: always() with: name: ${{ matrix.os }}_${{ matrix.cc }}_asan_mlucas path: | obj*/build.log obj*/test.log obj*/mlucas.cfg obj*/config-fermat.log obj*/fermat.cfg if-no-files-found: warn TSan-Linux: name: ThreadSanitizer Linux runs-on: ubuntu-latest strategy: matrix: cc: [gcc, clang] exclude: - cc: gcc # Too slow fail-fast: false env: CC: ${{ matrix.cc }} steps: - uses: actions/checkout@v7 - name: Install run: | sudo apt-get update -y sudo apt-get install -y libhwloc-dev - name: Before script run: | echo 'CFLAGS=-Wall -Wextra -g -Og -fsanitize=thread' >> "$GITHUB_ENV" $CC --version - name: Script run: | set -x -o pipefail bash -e -o pipefail -- makemake.sh use_hwloc nosimd cd obj_nosimd make clean time ./Mlucas -s tt -cpu "0:$(( $(nproc --all) - 1 ))" |& tee -a test.log - uses: actions/upload-artifact@v7 if: always() with: name: linux_${{ matrix.cc }}_tsan_mlucas path: | obj*/build.log obj*/test.log obj*/mlucas.cfg obj*/config-fermat.log obj*/fermat.cfg if-no-files-found: warn GCC-analyzer: name: GCC analyzer runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: GCC analyzer run: | set -x -o pipefail gcc -c -fdiagnostics-color -std=gnu99 -g -O3 -DUSE_THREADS -fanalyzer src/*.c |& tee analyzer.log rm -- *.o echo -e '## GCC analyzer\n```' >> $GITHUB_STEP_SUMMARY grep 'warning:' analyzer.log | sed 's/\x1B\[\([0-9]\+\(;[0-9]\+\)*\)\?[mK]//g' | awk '{ print $NF }' | sort | uniq -c | sort -nr >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY Clang-Tidy: name: Clang-Tidy runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: Install run: | sudo apt-get update -y sudo apt-get install -y libhwloc-dev - name: Clang-Tidy run: | set -x -o pipefail clang-tidy --use-color -checks='bugprone-*,-bugprone-reserved-identifier,cert-*,clang-analyzer-*,concurrency-*,misc-*,-misc-no-recursion,-misc-confusable-identifiers,performance-*,portability-*,readability-const-return-type,readability-container-*,readability-duplicate-include,readability-else-after-return,readability-make-member-function-cons,readability-non-const-parameter,readability-redundant-*,readability-simplify-*,readability-string-compare,readability-use-*' -header-filter='.*' src/*.c -- -std=gnu99 -Wall -Wextra -O3 -DUSE_THREADS -DINCLUDE_HWLOC=1 |& tee clang-tidy.log echo -e '## Clang-Tidy\n```' >> $GITHUB_STEP_SUMMARY grep 'warning:' clang-tidy.log | sed 's/\x1B\[\([0-9]\+\(;[0-9]\+\)*\)\?[mK]//g' | awk '{ print $NF }' | sort | uniq -c | sort -nr >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY Cppcheck: name: Cppcheck runs-on: ubuntu-22.04 # ubuntu-latest steps: - uses: actions/checkout@v7 - name: Install run: | sudo apt-get update -y sudo apt-get install -y cppcheck libhwloc-dev - name: Cppcheck run: | mkdir build cppcheck --std=c99 --enable=all -DUSE_THREADS -DINCLUDE_HWLOC=1 --force --cppcheck-build-dir=build -j "$(nproc --all)" --clang . cppcheck --std=c99 --enable=all -DUSE_THREADS -DINCLUDE_HWLOC=1 --force --cppcheck-build-dir=build --clang . &> cppcheck.log echo -e '## Cppcheck\n```' >> $GITHUB_STEP_SUMMARY grep '\(error\|warning\|style\|performance\|portability\|information\):' cppcheck.log | awk '{ print $2, $NF }' | sort | uniq -c | sort -nr >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY ShellCheck: name: ShellCheck runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - name: ShellCheck run: shellcheck --version; shopt -s globstar; shellcheck -o avoid-nullary-conditions,deprecate-which,quote-safe-variables,require-double-brackets -s bash **/*.sh macOS: name: Mlucas macOS runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-15-intel, macos-15, macos-26-intel, macos-26, xcode-27] fail-fast: false steps: - uses: actions/checkout@v7 - name: Install run: | brew install hwloc - name: Before script run: | clang --version if [[ $HOSTTYPE == arm64 ]]; then clang -dM -E -mcpu=native - > $GITHUB_STEP_SUMMARY grep 'warning:' build.log | sed 's/\x1B\[\([0-9]\+\(;[0-9]\+\)*\)\?[mK]//g' | grep -v '^ld:' | awk '{ print $NF }' | sort | uniq -c | sort -nr >> $GITHUB_STEP_SUMMARY echo '```' >> $GITHUB_STEP_SUMMARY # Ladder stops at 'm'; 'l' runs monthly against main in selftest-large.yml. See the note in # the Linux job above. for s in tt t s m; do time ./Mlucas -s $s -cpu "0:$(( $(sysctl -n hw.ncpu) - 1 ))" 2>&1 | tee -a test.log; done for s in tt t s; do time ./Mlucas -prp -s $s -cpu "0:$(( $(sysctl -n hw.ncpu) - 1 ))" 2>&1 | tee -a test.log; done if [[ $HOSTTYPE == arm64 ]]; then cd ../obj_NOSIMD fi bash -e -o pipefail -- ../config-fermat.sh -cpu "0:$(( $(sysctl -n hw.ncpu) - 1 ))" # Mfactor self-test on one build only. The explicit-ISA builds above are for compile # coverage: a runner may not be able to execute what they target. # On ARM the native build is ASIMD, whose trial-factoring routines are stubs - # twopmodq80.c's "No TF support on ARMv8!" lives under #ifdef USE_ARM_V8_SIMD - so run # the NOSIMD build there instead, which takes the scalar TF path and self-tests normally. # Same substitution config-fermat.sh already makes above, and the loop above has already # built these, so it costs no extra build time. if [[ $HOSTTYPE == arm64 ]]; then mode=NOSIMD; else mode=; fi for word in '' {{1..4},n}word; do (cd "../obj_mfac${word:+_$word}${mode:+_$mode}" && time "./Mfactor${word:+_$word}" -m 521 -bmax 20) done - uses: actions/upload-artifact@v7 if: always() with: name: ${{ matrix.os }}_mlucas path: | obj*/build.log obj*/test.log obj*/mlucas.cfg obj*/config-fermat.log obj*/fermat.cfg if-no-files-found: warn macOS-Singlethread: name: Mlucas macOS Singlethreaded runs-on: ${{ matrix.os }} strategy: matrix: os: [macos-26-intel, macos-latest] fail-fast: false steps: - uses: actions/checkout@v7 - name: Before script run: | sed -i '' 's/-DUSE_THREADS//' makemake.sh sed -i '' 's/^MAX=29/MAX=28/' config-fermat.sh clang --version - name: Build run: | set -x MODES=( '' NOSIMD ) if [[ $HOSTTYPE == arm64 ]]; then MODES+=( ASIMD ) else MODES+=( SSE2 AVX AVX2 AVX512 ) fi for mode in "${MODES[@]}"; do echo -e "\n$mode\n" bash -e -o pipefail -- makemake.sh $mode (cd obj${mode:+_$mode}; make clean) for word in '' {{1..4},n}word; do echo -e "\nMfactor $word\n" bash -e -o pipefail -- makemake.sh mfac $word $mode (cd obj_mfac${word:+_$word}${mode:+_$mode}; make clean) done done # The single-threaded build keeps only one core busy, but these tests are independent of each other, # so they run as three concurrent steps. # Each runs in its own directory under selftest/, so their cfg files and logs do not collide. # On ARM the native build is ASIMD, whose trial-factoring routines are stubs - twopmodq80.c's # "No TF support on ARMv8!" lives under #ifdef USE_ARM_V8_SIMD - so config-fermat.sh and the Mfactor # self-tests run from the NOSIMD builds there, which the build step has already made. The Mfactor # self-test runs on one build only: the explicit-ISA builds are for compile coverage, and a runner # may not be able to execute what they target. - parallel: - name: Self-test m run: | set -x -o pipefail mkdir -p selftest/m cd selftest/m ln -sf ../../obj/Mlucas . time ./Mlucas -s m 2>&1 | tee -a test.log - name: LL and PRP self-tests tt/t/s, Mfactor run: | set -x -o pipefail if [[ $HOSTTYPE == arm64 ]]; then mode=NOSIMD; else mode=; fi mkdir -p selftest/small cd selftest/small ln -sf ../../obj/Mlucas . for s in tt t s; do time ./Mlucas -s $s 2>&1 | tee -a test.log; done for s in tt t s; do time ./Mlucas -prp -s $s 2>&1 | tee -a test.log; done for word in '' {{1..4},n}word; do (cd "../../obj_mfac${word:+_$word}${mode:+_$mode}" && time "./Mfactor${word:+_$word}" -m 521 -bmax 20) done - name: Fermat self-tests F15-F28 run: | set -x -o pipefail if [[ $HOSTTYPE == arm64 ]]; then fermat=obj_NOSIMD; else fermat=obj; fi mkdir -p selftest/fermat cd selftest/fermat ln -sf "../../$fermat/Mlucas" . bash -e -o pipefail -- ../../config-fermat.sh - uses: actions/upload-artifact@v7 if: always() with: name: ${{ matrix.os }}_singlethreaded_mlucas path: | obj*/build.log selftest/*/test.log selftest/*/mlucas.cfg selftest/*/config-fermat.log selftest/*/fermat.cfg if-no-files-found: warn ASan-macOS: name: AddressSanitizer macOS runs-on: macos-latest steps: - uses: actions/checkout@v7 - name: Install run: | brew install hwloc - name: Before script run: | echo 'CFLAGS=-Wall -Wextra -g -Og -fsanitize=address,undefined' >> "$GITHUB_ENV" sed -i '' 's/^MAX=29/MAX=28/' config-fermat.sh clang --version - name: Script run: | set -x -o pipefail # Build NOSIMD under the sanitizer (see PR #71 discussion): testing the hand-tuned # SIMD asm under ASan/UBSan adds little, and on macOS it fails to assemble under -Og # ("invalid CFI advance_loc expression"). NOSIMD exercises the same C-level logic. bash -e -o pipefail -- makemake.sh use_hwloc nosimd (cd obj_nosimd; make clean) for word in '' {{1..4},n}word; do echo -e "\nMfactor $word\n" bash -e -o pipefail -- makemake.sh mfac $word nosimd (cd obj_mfac${word:+_$word}_nosimd; make clean) done cd obj_nosimd # Ladder stops at 's', as in the TSan jobs: in this NOSIMD build '-s m' took about 45 of the job's # 95 minutes, and the Linux and Windows ASan jobs run it in their SIMD builds. for s in tt t s; do time ./Mlucas -s $s -cpu "0:$(( $(sysctl -n hw.ncpu) - 1 ))" 2>&1 | tee -a test.log; done for s in tt t s; do time ./Mlucas -prp -s $s -cpu "0:$(( $(sysctl -n hw.ncpu) - 1 ))" 2>&1 | tee -a test.log; done bash -e -o pipefail -- ../config-fermat.sh -cpu "0:$(( $(sysctl -n hw.ncpu) - 1 ))" for word in '' {{1..4},n}word; do (cd "../obj_mfac${word:+_$word}_nosimd" && time "./Mfactor${word:+_$word}" -m 521 -bmax 20) done - uses: actions/upload-artifact@v7 if: always() with: name: macos_asan_mlucas path: | obj*/build.log obj*/test.log obj*/mlucas.cfg obj*/config-fermat.log obj*/fermat.cfg if-no-files-found: warn TSan-macOS: name: ThreadSanitizer macOS runs-on: macos-latest steps: - uses: actions/checkout@v7 - name: Install run: | brew install hwloc - name: Before script run: | echo 'CFLAGS=-Wall -Wextra -g -Og -fsanitize=thread' >> "$GITHUB_ENV" clang --version - name: Script run: | set -x -o pipefail bash -e -o pipefail -- makemake.sh use_hwloc nosimd cd obj_nosimd make clean # Use the 'tt' (tiny) self-test rather than 'm': TSan's instrumentation is slow enough that the # medium tier timed out (this job was previously if:false-gated "Too slow"), whereas tt still # exercises the multithreaded carry/threadpool paths for data-race detection in a few seconds. time ./Mlucas -s tt -cpu "0:$(( $(sysctl -n hw.ncpu) - 1 ))" 2>&1 | tee -a test.log - uses: actions/upload-artifact@v7 if: always() with: name: macos_tsan_mlucas path: | obj*/build.log obj*/test.log obj*/mlucas.cfg obj*/config-fermat.log obj*/fermat.cfg if-no-files-found: warn FreeBSD: name: Mlucas FreeBSD runs-on: ubuntu-latest strategy: matrix: release: [10, 11, 12, 13, 14, 15] arch: [x86_64] # aarch64 fail-fast: false env: CC: clang steps: - uses: actions/checkout@v7 - name: Script uses: vmactions/freebsd-vm@v1 with: release: ${{ matrix.release }} arch: ${{ matrix.arch }} cpu: 4 # Defaults to 2 on ARM mem: 12288 # Defaults to 6144 MiB envs: 'CC' usesh: true sync: nfs prepare: | set -e case "${{ matrix.release }}" in 1[01]) export SSL_NO_VERIFY_PEER=yes ;; esac pkg install -y bash gmp ${{ matrix.release == '10' && 'hwloc' || 'hwloc2' }} - name: Before script shell: freebsd {0} run: | set -e ${{ matrix.release != '10' && '-o pipefail' || '' }} sed -i '' 's/^MAX=29/MAX=28/' config-fermat.sh $CC --version if [ "${{ matrix.arch }}" = aarch64 ]; then $CC -dM -E -mcpu=native - > "$GITHUB_ENV" sed -i 's/^MAX=29/MAX=28/' config-fermat.sh $CC --version - name: Script shell: bash run: | set -x # Build the runner's native SIMD mode (AVX2 or AVX-512) under the sanitizers, for both Mlucas and # Mfactor, as the Linux ASan job does: in the NOSIMD build '-s m' took about 70 of the job's 150 minutes. bash -e -o pipefail -- makemake.sh use_hwloc (cd obj; make clean) for word in '' {{1..4},n}word; do echo -e "\nMfactor $word\n" bash -e -o pipefail -- makemake.sh mfac $word (cd obj_mfac${word:+_$word}; make clean) done cd obj for s in tt t s m; do time ./Mlucas -s $s -cpu "0:$(( NUMBER_OF_PROCESSORS - 1 ))" |& tee -a test.log; done for s in tt t s; do time ./Mlucas -prp -s $s -cpu "0:$(( NUMBER_OF_PROCESSORS - 1 ))" |& tee -a test.log; done bash -e -o pipefail -- ../config-fermat.sh -cpu "0:$(( NUMBER_OF_PROCESSORS - 1 ))" for word in '' {{1..4},n}word; do (cd "../obj_mfac${word:+_$word}" && time "./Mfactor${word:+_$word}" -m 521 -bmax 20) done - uses: actions/upload-artifact@v7 if: always() with: name: windows_asan_mlucas path: | obj*/build.log obj*/test.log obj*/mlucas.cfg obj*/config-fermat.log obj*/fermat.cfg if-no-files-found: warn