Build a model for explicit SIMD in Ada

Learn the vector model, arithmetic rules, masks, memory operations, conversions, backend selection, and verification through maintained numeric and byte examples.

Distinguish scalar source, operations, and backends

A scalar operation acts on one value. An ordinary Ada dot-product loop multiplies and adds one pair of F32 elements in each source iteration.

A lane is one scalar value at a fixed position in a vector.

The scalar reference backend implements each vector result with portable lane-by-lane Ada. It is the semantic authority for optimized backends, but it is not the ordinary source loop. The reported backend name scalar identifies a scalar-only build.

Generated machine code is the compiler output. GNAT can auto-vectorize an ordinary Ada source loop. Therefore, scalar source does not prove that the executable contains only scalar instructions.

This guide uses explicit vector loop for source that calls fixed-width vector operations. The backend can implement an explicit operation with one instruction, several instructions, or scalar composition.

Run the maintained dot product

The program computes the dot product of two seven-element F32_Array values. It compares ordinary Ada with static scalar and native vector composition.

git clone https://github.com/flyology-ada/flyology-simd.git
cd flyology-simd
alr exec -- gprbuild -p -P examples/examples.gpr
./bin/dot_product

The selected data has an exact binary32 result in both evaluation orders:

ordinary Ada dot: 7.00000E+01
scalar backend dot: 7.00000E+01
native backend dot: 7.00000E+01

Later chapters explain the four-lane products, the three-element tail, and compile-time composition. Floating-point association may change rounding for other inputs.

Keep semantics separate from machine mechanism

A F32x4 value has four logical binary32 lanes. Its public type is private. Applications use operations such as load, multiply, comparison, selection, and reduction.

The public semantics are identical on all backends. The scalar fallback provides source portability. Native backends provide target-specific code generation. These are three separate portability claims.

The representation and calling convention may vary with the compiler, target, and instruction switches. The library does not define a portable vector application binary interface (ABI).

Add the crate to an application

Add the Flyology index once. You can run these index commands from any directory:

alr index --reset-community
alr index --add=git+https://github.com/flyology-ada/alire-index.git \
  --name=flyology --before=community

Keep the community index. Alire uses it to find the compiler toolchain. Run alr index --list to inspect the configured indexes.

Then change to the root of your own Alire application. Add the crate there, not in the flyology-simd checkout:

cd /path/to/your/application
alr with flyology_simd
alr build

The GPR project detects aarch64 and x86_64 hosts. You do not need to set FLYOLOGY_SIMD_ARCH for a native build. Use an override for a scalar-only test or for cross-compilation.

alr build -- -XFLYOLOGY_SIMD_ARCH=scalar
Standalone crate

The library has no runtime dependency on Flyology. It defines Flyology_SIMD, not a Flyology parent package.

Follow the guide in order

The chapters build one model. The dot product is the main numeric thread. An eight-lane weighted sum introduces the initial 256-bit profile and an order-sensitive sum makes its reduction order observable. A three-point stencil combines each sample with its available neighbors. A point rotation introduces one-source lane selection. A first-difference example then selects successors across two vector blocks. A 32-lane classifier combines byte comparisons and selects display values. Stable mask compression keeps selected values in source order, and expansion restores their positions. Byte counting appears where compact masks and runtime byte algorithms need a concrete case. Chapter 6 revisits the dot product at a runtime complete-array boundary.

  1. Model vectors and lanes

    Map array elements to lanes and process the first four dot-product terms.

  2. Apply arithmetic and move lanes

    Complete the dot product, compute an eight-lane weighted sum, build a stencil, rotate points, and calculate differences across vector blocks.

  3. Compare lanes, select values, and inspect masks

    Classify 32 bytes, use floating masks for compression and expansion, and extract compact position bits.

  4. Use full, aligned, and partial memory operations

    Read the three-element dot-product tail without crossing the valid array extent.

  5. Convert values and reinterpret bits

    Distinguish numeric conversion, narrowing, widening, saturation, and bit casts.

  6. Compose backends and dispatch algorithms

    Use static floating composition or dispatch complete scaling, clamping, AXPY, sums, number extrema, dot products, and current byte algorithms at runtime.

  7. Verify code generation and measure complete work

    Use differential tests, focused disassembly checks, and repeated benchmarks.

Choose a direct route when you know the model

QuestionGo to
Does the operation I need exist?Operation coverage
How do I use a 256-bit value?Eight-lane weighted sum
How do I use adjacent lanes?Neighboring-lane stencil
How do I reorder lanes from one or two vectors?Lane maps, point rotation, and cross-block differences
How do comparison results select values?Wide digit classifier
How do I pack lanes selected by a mask?Stable compression and expansion
How do I process the last elements safely?Memory operations
Which conversion preserves bits?Conversions and bit casts
Which backend runs, and when is it chosen?Backend selection
Where did the small-set scan cross over?AArch64 journal record
Which targets have executed tests?Support and verification
What is the exact Ada declaration?Generated API reference

Know what this guide does not promise

The operation matrix is a reference, not a chapter in the learning sequence. Use it to check exact overload coverage and edge semantics.

The API is experimental. The root package contains the complete ten-type 128-bit family. Flyology_SIMD.Wide contains the corresponding ten private 256-bit types with a smaller initial operation profile. The Wide profile includes compression and expansion, one-source and two-source lane maps, bit casts, widening, narrowing, numeric conversion, and 32-entry byte-table lookup for U8x32. Bit casts preserve lane bits; they do not perform numeric conversion.

The current Wide implementation uses private 128-bit parts and does not define a 256-bit ABI. Lane movement, compression and expansion, and table lookup use separate target-selected mechanisms. An x86-64 build can statically select isolated AVX2-specific 256-bit implementations for selected U8x32 and I8x32 operations, F32x8 and F64x4 arithmetic, U8x32 table lookup, lane movement, and both Permute_Lanes overloads. The operation reference lists the exact Wide boundary and deployment requirement.

The scalar backend defines the result on every supported GNAT target. Target-specific code exists for AArch64 NEON and x86-64 SSE2. The static Wide AVX2 path is separate from the runtime-gated AVX2 path for scaling, clamping, AXPY, sums, number extrema, dot products, and selected whole-buffer byte algorithms. The support page distinguishes implemented, executed, and continuously tested configurations.