Skip to content

Refactor

Refactor #56

Workflow file for this run

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