Skip to main content

Module guide

Module guide 

Source
Expand description

Dummy doc-only module: renders src/guide.md (the one-file API overview, also linked from the README) into the rustdoc output.

§Functionality guide — one-file API overview

This document is a compact but complete map of everything lightweight-simd offers, written for two audiences: API users who want the big picture without reading every module, and AI coding agents that need to know what this crate can and cannot do before writing code against it. The rustdoc API pages remain the source of truth for exact signatures; this file is the orientation layer.

  • Design stance: portable fixed-lane vector types whose every operation is a plain loop that the compiler (LLVM) auto-vectorizes. No intrinsics, no inline assembly, no runtime CPU-feature dispatch, no fallback code paths. SIMD-level performance comes from building with -C target-cpu=native (or a fixed level such as x86-64-v3); correctness never depends on it.
  • Everything is stable Rust; dependencies are duplicate (codegen), and num-traits (element trait bounds).
use lightweight_simd::{f64x4, Simd};

let a = f64x4::from([1.0, 2.0, 3.0, 4.0]);
let b = a * 2.0 + f64x4::splat(1.0);              // vector-scalar and vector-vector ops
assert_eq!(b.to_array(), [3.0, 5.0, 7.0, 9.0]);
assert_eq!(b.reduce_sum(), 24.0);                 // horizontal sum

let m = a.simd_gt(f64x4::splat(2.0));             // lane-wise compare -> mask
assert_eq!(a.mask_select(m, f64x4::zero()).to_array(), [0.0, 0.0, 3.0, 4.0]);

§The six vector types

There is one generic core type and five aligned siblings; they share a single implementation, so every operation listed in this guide exists on all six types (except the handful of mask-only helpers, noted below):

TypeAlignmentRole
Simd<T, N>natural (element’s)the core type; public field .0 of type [T; N]
Aligned4<T, N>4 Benforced-alignment siblings,
Aligned8<T, N>8 Bstructurally identical
Aligned16<T, N>16 B(#[repr(align(n))] over
Aligned32<T, N>32 Ba public [T; N])
Aligned64<T, N>64 B
  • The field .0 is public on every type: zero-friction interop with plain array code, and .0 / From conversions move between plain and aligned forms losslessly (Aligned64::to_simd too).
  • Masks — Simd<bool, N> — are never aligned. Comparisons on any vector type return plain Simd<bool, N>; masks are register-resident temporaries.

§Concrete type aliases

Defined in the crate root (alias module). The aligned backing type is chosen as byte size rounded up to a power of two, capped at 64 bytes (so e.g. f64x16 is 128 B of data with 64 B alignment):

Feature-gated aliases (off by default):

  • feature complex (dep num-complex): c64x2 c64x4 c64x8 c32x4 c32x8 c32x16 over num_complex::Complex<f64/f32>.
  • feature half (dep half): f16x2 … f16x64 and bf16x2 … bf16x64. Note: f16/bf16 arithmetic goes through the half crate’s scalar impls and stays scalar in the emitted code on x86 (documented in the asm checker’s informational notes).

The lane count N is an arbitrary const generic on Simd/aligned types — powers of two that fill whole registers are the sweet spot, not a requirement. Non-builtin element types work wherever their trait bounds fit (bool supports splat/bitwise/compare/select; Complex<T> supports arithmetic, compare, and mul_add behind the complex feature; half::f16 supports the float functions).

§Operation surface

All methods below are inherent methods, #[inline(always)], identical across the six types. Simd is written below for brevity; read it as “any of the six”.

§Construction, access, conversion

  • Simd::from_fn(f) — lane i is f(i).
  • Simd::splat(val) — all lanes val (const fn).
  • Simd::fill(&mut self, val) — overwrite all lanes.
  • Simd::zero() / Simd::one() — via num_traits.
  • From<[T; N]> on every type; plain↔aligned via From, .0, or to_simd. No From<&[T]>/Vec conversions — use from_slice.
  • Simd::as_array / as_array_mut / Simd::to_array — borrow/move the backing array (as_array is const fn). There is no iterator over lanes; use .as_array().iter().
  • Index<usize> / IndexMut<usize> — per-lane access.
  • Derived traits on every type: Clone, Copy, Debug, PartialEq, Eq, Hash, Default; num_traits::Zero/One where the element admits them. PartialOrd/Ord are deliberately not implemented — comparisons go through the simd_* methods below so they always produce masks.

§Arithmetic

  • Add/Sub/Mul/Div as vectoropvector and vectoropscalar; plus the four *Assign forms in both shapes. Requires T: num_traits::Num (NumAssignOps for the assign forms).
  • Neg (element’s Neg).
  • No saturating/wrapping/checked variants, no widening/narrowing, no Rem.

§Bitwise and shifts

  • BitAnd/BitOr/BitXor (+ assign forms) and Not, by the element’s own core::ops impls. On Simd<bool, N> these are the lane-wise logical operators — combine masks with &, |, ^, !.
  • Shl<usize>/Shr<usize>: shift every lane by one scalar usize. There is no lane-wise/varying shift.

§Multiply-add — the one target-dependent area

Simd::mul_add(b, c) → self * b + c, and Simd::fma_from(b, c) → *self = b * c + *self (accumulator is the receiver — note the operand-order difference; it matches self += b * c for micro-kernels). Evaluation follows the build target (policy and measurements: docs/adr/0005-mul-add-fallback-policy.md):

  1. Hardware-FMA targets (x86 with -C target-cpu ≥ x86-64-v3, including native; all aarch64): the element’s num_traits::MulAdd — one rounding per lane, one FMA instruction per register.
  2. Targets without (generic x86-64, x86-64-v2, unknown arches): separated multiply + add, two roundings — deliberately never the slow software libm fma (~4.6× slower measured). Fuses back to a hardware FMA under -C llvm-args=-fp-contract=fast.
  3. Feature use_libm_fma pins case 1 everywhere, accepting slow libm calls where hardware is absent — the only way to get bit-identical mul_add results across build targets.

Floats always have mul_add available (case 1 or 2 by target). Agents: never assume a.mul_add(b, c) equals a * b + c bit-for-bit; it does only on FMA targets (single vs double rounding).

§Float functions (T: num_traits::Float)

  • Unary, lane-wise: abs, sqrt, recip, floor, ceil, round, trunc, fract.
  • Binary: min, max (NaN handling follows the scalar Float methods), copysign.
  • round_ties_even exists only for f32/f64 vectors (not reachable generically through num_traits::Float).
  • No transcendental functions (exp/log/sin/…): use the escape hatch Simd::map, e.g. v.map(f64::exp) — it vectorizes the same way everything else does.

§Comparisons and masks

  • simd_lt/simd_le/simd_gt/simd_ge (need PartialOrd) and simd_eq/simd_ne (need PartialEq) return Simd<bool, N> masks.
  • Mask-side (Simd<bool, N> only): select(if_true, if_false) — lane-wise pick; any_true() / all_true().
  • Value-side (any of the six types): mask_select(mask, other) — keep self where mask is true, other where false.

§Reductions

  • Simd::reduce_sum / Simd::reduce_product — all lanes, accumulated in plain lane order (sequential fold, not a pairwise/tree reduction; float results depend on that order).
  • reduce_sum_n(n) / reduce_product_n(n) — first n lanes; panic if n > N. Useful for masked-out tails.
  • No horizontal min/max/and/or reductions.

§Slice load/store

  • Simd::from_slice(src) — first N elements; panics if src.len() < N.
  • from_slice_pad(src) — same, zero-filling lanes past src.len() (tail-safe load).
  • store_slice(&self, dst) — writes N lanes; panics if dst.len() < N.
  • store_slice_partial(&self, dst) — writes min(dst.len(), N) lanes (tail-safe store).

§Unsafe constructor

  • Simd::uninit() — MaybeUninit-backed scratch vector; caller must fully initialize every lane before reading; element must admit arbitrary bit patterns (floats/integers yes, types with niches no). Intended for hot loops that write-then-read, e.g. accumulate-then-reduce.

§Cargo features

FeatureDefaultEffect
complexoffnum-complex dep + c64x*/c32x* aliases
halfoffhalf dep + f16x*/bf16x* aliases
use_libm_fmaoffpins fused mul_add on every target (see above)

§Performance model

  • Build with -C target-cpu=native or a fixed level (x86-64-v3/-v4); without it the code is still correct but scalar-ish. A single binary is not expected to be fast on multiple micro-architectures — that is the accepted trade (see README “Scopes” and docs/adr/0001-no-dispatch-autovectorization-fallback.md).
  • This crate sets [profile.dev] opt-level = 2, so tests and dev runs already vectorize without --release.
  • Codegen is statically verified by scripts/check_asm.py: it compiles probe functions at several target levels and asserts expected vector instruction families (details and CI wiring in README; rationale in docs/adr/0004-codegen-verification-by-assembly-matching.md).
  • Evidence the approach works end-to-end: examples/blis_matmul.rs reaches OpenBLAS-parity (~1.2 TFLOP/s) GEMM on an AVX-512 host using only these portable types.

§What this crate does not provide

So agents do not hallucinate APIs: no intrinsics or inline asm, no runtime CPU dispatch, no shuffles/permutes/interleaves/transposes/swizzles/rotates, no lane insert/extract beyond Index, no horizontal min/max, no saturating/wrapping arithmetic, no gather/scatter or masked load/store, no core::simd (std::simd) interop, no serde, no lane iterator, no FromIterator, no transcendental math, no PartialOrd operators.

§Non-obvious points (agent checklist)

  1. </>/== operators do not exist on vectors — comparisons are the simd_* methods returning masks; branch on them with any_true/all_true.
  2. select lives on the mask; mask_select lives on the value. Same operation, two receivers.
  3. fma_from’s receiver is the accumulator (self += b * c shape), unlike mul_add.
  4. mul_add results differ across build targets unless use_libm_fma is on.
  5. reduce_sum folds in lane order — not pairwise; do not assume associativity-exact float sums.
  6. Only splat and as_array are const fn; everything else is runtime (though always #[inline(always)]).
  7. Alignment is capped at 64 bytes even for >64-byte vectors (f64x16).
  8. bool lanes: arithmetic traits do not apply — use bitwise/logical ops and select/mask_select.
  9. Tail handling: from_slice_pad + reduce_sum_n + store_slice_partial are the intended ragged-edge toolkit.
  10. Custom element types need: Copy + num_traits::Num for arithmetic, num_traits::Float for float math, the element’s own core::ops impls for bitwise.

§Repository pointers

  • README.md — project scope, FMA notes, codegen verification, usage example.
  • examples/blis_matmul.rs — BLIS-style GEMM built only on these types.
  • tests/basic.rs, tests/ops.rs, tests/aligned.rs — behavioral ground truth, including target-dependent mul_add tests and feature-gated complex/half tests.
  • docs/adr/0001–0005 — architecture decisions: no-dispatch rationale, single generic type, aligned aliases, assembly-based codegen verification, mul-add fallback policy.
  • scripts/check_asm.py (+ scripts/asm/probes.rs) — static codegen checker; --dump/--hist for inspecting emitted assembly.
  • src/lib.rs — Simd and shared impls; src/aligned.rs, src/alias.rs, src/ops.rs, src/reduce.rs, src/memory.rs — one module each for the surfaces described above.