Refactor #56
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CI | |
| on: | |
| push: | |
| branches: [main] | |
| pull_request: | |
| env: | |
| CARGO_TERM_COLOR: always | |
| RUST_BACKTRACE: 1 | |
| # Treat every compiler and rustdoc warning as an error β the Rust equivalent | |
| # of building C with -Werror. Set here (CI only) rather than via | |
| # `#![deny(warnings)]` in the source, which would break downstream builds and | |
| # future compiler versions whenever a new lint is added. | |
| RUSTFLAGS: -D warnings | |
| RUSTDOCFLAGS: -D warnings | |
| jobs: | |
| msrv: | |
| name: MSRV (Rust 1.89) | |
| runs-on: ubuntu-latest | |
| env: | |
| RUSTUP_TOOLCHAIN: "1.89" | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust 1.89 | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: "1.89" | |
| targets: i686-unknown-linux-gnu | |
| - name: Cache cargo registry and build artifacts | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Run test suite | |
| run: cargo test --lib | |
| - name: Check no_std configuration | |
| run: cargo check --lib --no-default-features | |
| - name: Check 32-bit x86 | |
| run: cargo check --lib --target i686-unknown-linux-gnu | |
| - name: Build documentation | |
| run: cargo doc --lib --no-deps | |
| nightly: | |
| name: Nightly smoke test (x86-64 Linux) | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@nightly | |
| with: | |
| components: clippy | |
| - name: Cache cargo registry and build artifacts | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Run test suite | |
| run: cargo test --lib | |
| - name: Clippy | |
| run: cargo clippy --all-targets -- -D warnings | |
| - name: Run native test suite | |
| run: cargo test --lib | |
| env: | |
| RUSTFLAGS: -C target-cpu=native -D warnings | |
| test: | |
| name: Test on ${{ matrix.os }} (${{ matrix.profile }}) | |
| runs-on: ${{ matrix.os }} | |
| env: | |
| RUSTUP_TOOLCHAIN: stable | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| profile: [debug, release] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| # Stable is tested explicitly even though the repository pins nightly so | |
| # contributor benchmarks can use ARM SVE automatically. | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy, rustfmt | |
| - name: Cache cargo registry and build artifacts | |
| uses: Swatinem/rust-cache@v2 | |
| # Formatting is profile- and platform-independent, so check it just once. | |
| - name: Check formatting | |
| if: matrix.os == 'ubuntu-latest' && matrix.profile == 'debug' | |
| run: cargo fmt --check | |
| # Clippy is profile-independent; run it once per OS (in the debug job) so | |
| # both the x86 and AArch64 code paths get linted. `-D warnings` (from the | |
| # env, plus the explicit flag) fails the build on any lint. | |
| - name: Clippy | |
| if: matrix.profile == 'debug' | |
| run: cargo clippy --all-targets -- -D warnings | |
| # Debug enables overflow checks and debug_assertions; release exercises the | |
| # optimized code. `cargo test` also runs the doctests and compiles the | |
| # `benchmark` example. | |
| - name: Run test suite | |
| run: cargo test ${{ matrix.profile == 'release' && '--release' || '' }} | |
| # In release mode, also smoke-test the benchmark example (default args). | |
| - name: Run benchmark | |
| if: matrix.profile == 'release' | |
| run: cargo run --release --example benchmark | |
| # Native builds (`-C target-cpu=native`) tell the compiler to target the exact | |
| # CPU of the runner. This selects the best SIMD path at compile time and, in | |
| # doing so, *removes* the runtime dispatch: instead of `popcnt_x86_runtime`, | |
| # the `#[cfg(target_feature = "β¦")]` branches are compiled (compile-time AVX2 / | |
| # AVX512 on x86, NEON / SVE on AArch64). The default `test` job never sets any | |
| # target feature, so without this those branches are never compiled, linted or | |
| # executed. Runs on the same three OSes to cover MSVC, macOS and glibc. | |
| test_native: | |
| name: Native ${{ matrix.os }} (${{ matrix.profile }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, windows-latest, macos-latest] | |
| profile: [debug, release] | |
| env: | |
| RUSTUP_TOOLCHAIN: stable | |
| # A job-level `env:` replaces the workflow-level `RUSTFLAGS` (it does not | |
| # append), so `-D warnings` must be repeated here to keep -Werror. | |
| RUSTFLAGS: -C target-cpu=native -D warnings | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust stable | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| components: clippy | |
| - name: Cache cargo registry and build artifacts | |
| uses: Swatinem/rust-cache@v2 | |
| # Lint the compile-time SIMD branches that target-cpu=native selects; the | |
| # default clippy job cannot see them because it builds no target feature. | |
| - name: Clippy | |
| if: matrix.profile == 'debug' | |
| run: cargo clippy --all-targets -- -D warnings | |
| - name: Run test suite | |
| run: cargo test ${{ matrix.profile == 'release' && '--release' || '' }} | |
| - name: Run benchmark | |
| if: matrix.profile == 'release' | |
| run: cargo run --release --example benchmark | |
| wasm: | |
| name: WebAssembly (wasm32-wasip1) | |
| runs-on: ubuntu-latest | |
| env: | |
| RUSTUP_TOOLCHAIN: stable | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust stable + wasm target | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: wasm32-wasip1 | |
| - name: Cache cargo registry and build artifacts | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Install Wasmtime | |
| uses: taiki-e/install-action@v2 | |
| with: | |
| tool: wasmtime | |
| # Compile the whole crate to WebAssembly and run the unit tests under the | |
| # Wasmtime runtime via a cargo runner. WASM is neither x86 nor AArch64, so | |
| # this exercises the portable scalar code path. `--lib` runs the unit tests | |
| # (doctests are not run on a cross-compiled target). | |
| - name: Run tests on WebAssembly | |
| run: cargo test --target wasm32-wasip1 --lib | |
| env: | |
| CARGO_TARGET_WASM32_WASIP1_RUNNER: wasmtime | |
| # `no_std` build. The `test_native` / `arm_linux_native` jobs already compile | |
| # the crate as `no_std` implicitly (target-cpu=native disables runtime dispatch, | |
| # so the crate stops touching `std`), but they keep the `std` feature *on* and | |
| # run on std-capable targets. This job covers the two things they cannot: | |
| # * `--no-default-features` β the `std` feature actually turned off, which is | |
| # the knob downstream `no_std` users flip and which selects a distinct | |
| # scalar-fallback code path. | |
| # * bare-metal targets with no `std` in the sysroot, so a stray `std` | |
| # reference is a hard link error rather than a silently-available symbol. | |
| # Build-only: bare-metal targets have no test harness to run. | |
| no_std: | |
| name: no_std (${{ matrix.target }}) | |
| runs-on: ubuntu-latest | |
| env: | |
| RUSTUP_TOOLCHAIN: stable | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # x86_64 exercises the scalar fallback; aarch64 the baseline-NEON path. | |
| target: [x86_64-unknown-none, aarch64-unknown-none] | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust stable + bare-metal target | |
| uses: dtolnay/rust-toolchain@stable | |
| with: | |
| targets: ${{ matrix.target }} | |
| - name: Cache cargo registry and build artifacts | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Build no_std | |
| run: cargo build --no-default-features --target ${{ matrix.target }} | |
| # Run the test suite on a real AArch64 Linux CPU (the runner supports ARM SVE). | |
| # * nightly: the build.rs probe enables the SVE intrinsics, so the ARM SVE | |
| # popcount algorithm is compiled and executed (NEON otherwise). | |
| # * stable: the probe rejects the nightly SVE feature, so the crate falls | |
| # back to the ARM NEON algorithm β this checks that fallback path. | |
| # The release jobs also run the benchmark, reporting ARM SVE / NEON throughput. | |
| # RUSTUP_TOOLCHAIN overrides the repo's nightly rust-toolchain.toml so the | |
| # stable job really tests the stable NEON fallback. | |
| arm-linux: | |
| name: ARM64 Linux (${{ matrix.toolchain }}, ${{ matrix.profile }}) | |
| runs-on: ubuntu-24.04-arm | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| toolchain: [nightly, stable] | |
| profile: [debug, release] | |
| env: | |
| RUSTUP_TOOLCHAIN: ${{ matrix.toolchain }} | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust ${{ matrix.toolchain }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ matrix.toolchain }} | |
| - name: Cache cargo registry and build artifacts | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Run test suite | |
| run: cargo test ${{ matrix.profile == 'release' && '--release' || '' }} | |
| # In release mode, also run the benchmark (default args) so the ARM SVE | |
| # (nightly) and ARM NEON (stable) throughput is reported in the logs. | |
| - name: Run benchmark | |
| if: matrix.profile == 'release' | |
| run: cargo run --release --example benchmark | |
| # Native AArch64 build (`-C target-cpu=native`): as with `test_native`, this | |
| # compiles the `#[cfg(target_feature = "β¦")]` AArch64 branch selected for the | |
| # runner's CPU (SVE when the CPU and the nightly probe both support it, | |
| # otherwise NEON) instead of the runtime-dispatch path. | |
| arm_linux_native: | |
| name: Native ARM64 Linux (${{ matrix.toolchain }}, ${{ matrix.profile }}) | |
| runs-on: ubuntu-24.04-arm | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| toolchain: [nightly, stable] | |
| profile: [debug, release] | |
| env: | |
| RUSTUP_TOOLCHAIN: ${{ matrix.toolchain }} | |
| # Job-level env replaces the workflow-level RUSTFLAGS; keep -D warnings. | |
| RUSTFLAGS: -C target-cpu=native -D warnings | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust ${{ matrix.toolchain }} | |
| uses: dtolnay/rust-toolchain@master | |
| with: | |
| toolchain: ${{ matrix.toolchain }} | |
| - name: Cache cargo registry and build artifacts | |
| uses: Swatinem/rust-cache@v2 | |
| - name: Run test suite | |
| run: cargo test ${{ matrix.profile == 'release' && '--release' || '' }} | |
| - name: Run benchmark | |
| if: matrix.profile == 'release' | |
| run: cargo run --release --example benchmark | |
| # Run the test suite and the benchmark under Valgrind's memcheck tool to | |
| # dynamically verify the SIMD algorithms are free of memory-safety undefined | |
| # behavior (out-of-bounds reads, use of uninitialized memory). Unlike Miri, | |
| # Valgrind instruments the real machine code, so it checks the bytes read | |
| # inside the vector loads of whichever SIMD path actually runs (typically | |
| # AVX2 on x86-64 and NEON on AArch64). Covered on both architectures. | |
| valgrind: | |
| name: Valgrind memcheck (${{ matrix.os }}) | |
| runs-on: ${{ matrix.os }} | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, ubuntu-24.04-arm] | |
| env: | |
| # Point Cargo's target runner at Valgrind so `cargo test` / `cargo run` | |
| # execute the compiled binaries under memcheck. `--error-exitcode=1` fails | |
| # the job on any detected issue: invalid / out-of-bounds reads or writes, | |
| # use of uninitialized memory, bad frees, and memory leaks. Only "definite" | |
| # leaks count as errors: Rust's test harness and thread-local runtime leave | |
| # benign "possibly lost" / "still reachable" blocks (interior TLS pointers) | |
| # that are not real leaks and must not fail the job. Non-leak memory errors | |
| # always fail regardless. Only the runner matching the host triple is used. | |
| CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: valgrind --error-exitcode=1 --leak-check=full --errors-for-leak-kinds=definite --track-origins=yes | |
| CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_RUNNER: valgrind --error-exitcode=1 --leak-check=full --errors-for-leak-kinds=definite --track-origins=yes | |
| # Line-level debug info gives readable Valgrind backtraces without | |
| # disabling optimizations, so the SIMD paths are still exercised. | |
| CARGO_PROFILE_RELEASE_DEBUG: line-tables-only | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v4 | |
| - name: Install Rust nightly | |
| uses: dtolnay/rust-toolchain@nightly | |
| - name: Install Valgrind | |
| run: sudo apt-get update && sudo apt-get install -y valgrind | |
| - name: Cache cargo registry and build artifacts | |
| uses: Swatinem/rust-cache@v2 | |
| # Release mode exercises the optimized SIMD paths. Runs the unit tests and | |
| # doctests under Valgrind via the target runner configured above. | |
| - name: Run test suite under Valgrind | |
| run: cargo test --release | |
| # Run the benchmark under Valgrind with 100x fewer iterations than the | |
| # default (100,000 instead of 10,000,000) so it finishes quickly under | |
| # instrumentation. The first argument keeps the default 16 KiB array size. | |
| - name: Run benchmark under Valgrind | |
| run: cargo run --release --example benchmark -- 16384 100000 |