Skip to content

NEON and Scalar Tails

Vectorizing a loop adds a new boundary: the last elements may not fill a vector. Start with an exact integer operation and test every tail length before discussing speed.

Before you start

Complete the checked array project. You can state input/output range and aliasing contracts.

Outcome: implement four-lane addition with a scalar tail and compare every output with C. Use a fresh Pi practice directory. The native Raspberry Pi 4 used for course checks supports these Advanced SIMD instructions; check your actual target before running.

Understand the lanes

The SIMD/FP register bank has v0–v31, separate from x0–x30. v0.4s interprets 128 bits as four 32-bit lanes. LD1 fills those lanes; ADD performs separate modulo-2^32 additions; ST1 writes the vector. Carries do not flow between lanes.

This routine computes output[i] = input[i] + 1 modulo 2^32. The caller supplies n readable/writable elements, with non-overlapping input and output. n=0 permits null pointers because neither range is accessed.

Implement vector and tail paths

// file: neon.S
.text
.global add_one_u32
.type add_one_u32, %function
add_one_u32:
    movi v1.4s, #1
    cmp x2, #4
    b.lo .Ltail
.Lvector:
    ld1 {v0.4s}, [x0], #16
    add v0.4s, v0.4s, v1.4s
    st1 {v0.4s}, [x1], #16
    sub x2, x2, #4
    cmp x2, #4
    b.hs .Lvector
.Ltail:
    cbz x2, .Ldone
.Lscalar:
    ldr w3, [x0], #4
    add w3, w3, #1
    str w3, [x1], #4
    subs x2, x2, #1
    b.ne .Lscalar
.Ldone:
    ret
.size add_one_u32, .-add_one_u32
.section .note.GNU-stack,"",%progbits

No vector access occurs unless at least four elements remain. The 16-byte pointer advance is four elements, not 16 elements. This leaf uses only caller-clobbered vector registers and makes no calls.

/* file: neon.c */
#include <stdint.h>
#include <stdio.h>
extern void add_one_u32(const uint32_t *, uint32_t *, uint64_t);
int main(void)
{
    const uint32_t input[9] = {0, 1, UINT32_MAX, 3, 4, 5, 6, 7, 8};
    add_one_u32(0, 0, 0);
    for (unsigned n = 0; n <= 9; ++n) {
        uint32_t output[11];
        for (unsigned i = 0; i < 11; ++i) output[i] = UINT32_C(0xdeadbeef);
        add_one_u32(input, output + 1, n);
        if (output[0] != UINT32_C(0xdeadbeef)) return 1;
        for (unsigned i = 0; i < n; ++i)
            if (output[i + 1] != (uint32_t)(input[i] + UINT32_C(1))) return 1;
        for (unsigned i = n + 1; i < 11; ++i)
            if (output[i] != UINT32_C(0xdeadbeef)) return 1;
    }
    puts("PASS NEON: lengths 0..9, wrap and output guards");
    return 0;
}
gcc -std=c11 -Wall -Wextra -Wpedantic -Werror -g -O2 neon.c neon.S -o neon
./neon
PASS NEON: lengths 0..9, wrap and output guards

Guard words detect writes outside the intended output, not all possible input overreads. The instruction/control-flow reasoning is still needed. The output is ordinary memory, not a device buffer.

Common traps

Do not round n up and read past its range. Do not interpret a .4s operation as four 64-bit elements. Do not generalize exact integer equivalence to floating-point reductions: reassociation, rounding and NaNs need a separate policy.

If the target rejects an instruction at assembly time, check the configured architecture. If execution raises illegal instruction, check hardware and execution support instead of adding speculative compiler flags.

Correct does not mean faster

A scalar compiler loop may already be vectorized. This lesson performs no timing comparison and makes no speed claim. A useful comparison must use the same operation, valid inputs, optimization options and tail policy. The final lesson separates correctness tests from measurement.

Check your understanding

For n=7, how many vector and scalar iterations run? What protects the eighth input element from being read?

Compare your reasoning

One vector iteration consumes four, then three scalar iterations consume the remaining elements. Both paths test remaining count before their access.

Continue the course

Previous: A Checked Array Project · Course roadmap · Next: Extended Inline Assembly

Donate