Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Assembly (x86-64, AArch64, RISC-V)  Module 10 – Floating point and SIMD

Vector-length-agnostic code: RVV, SVE and SME

Write loops that never name a vector width: vsetvli on RISC-V, whilelo predicates in SME streaming mode on Apple silicon, and a model of vl at four widths.

  • Advanced
  • 20 minutes
  • Examples run with Python 3.14.8
  • By MySmartCoPilot
Free core, Pro depth: 8 of 20 minutes free
Free: A vector-length-agnostic loop asks for its lanes on every pass · SVE marks the lanes still inside the array · RVV returns the pass length from vsetvli · Practise. With a pass: 2 in-depth parts and 3 interview answers, about 12 minutes.

What you will learn

  • Write a strip-mined RVV loop whose pass length comes from vsetvli
  • Trace an SVE loop that uses whilelo predicates instead of a scalar tail
  • Predict how many passes a loop takes from the vector length and the vl rule
  • Explain the rules a function must follow around SME streaming mode
  • Measure the vector length with rdsvl, cntw or vlenb

Before you start

What you’ll learn · Before you start

What you will learn

  • Write a strip-mined RVV loop whose pass length comes from vsetvli
  • Trace an SVE loop that uses whilelo predicates instead of a scalar tail
  • Predict how many passes a loop takes from the vector length and the vl rule
  • Explain the rules a function must follow around SME streaming mode
  • Measure the vector length with rdsvl, cntw or vlenb

Before you start

On this page

The NEON loop of the previous lesson names four 32-bit lanes in every instruction, so 40 elements take 10 passes on any core, even one whose vector unit could take 16 at once. vector-length-agnostic code: Vector code that never states how many lanes a vector holds: every pass asks the hardware how many elements it may handle and moves on by that many, so one binary uses the full width of any core. code never names the width: each pass asks the hardware how many elements it may handle and moves on by that many, so one binary fills a 128-bit unit or a 2,048-bit one.

Two instruction sets work this way. RISC-V’s vector extension, RVV, returns the length of each pass from vsetvli, and AArch64’s SVE marks the lanes still inside the array in a predicate. Apple silicon runs SVE instructions inside SME’s streaming mode, where the run below adds 40 elements in 3 passes of 16, 16 and 8 lanes.

A vector-length-agnostic loop asks for its lanes on every pass

A fixed-width loop states its vector length: The width of a vector register on a given core, in bits or bytes. Fixed-width SIMD sets it in the instruction; SVE and the RISC-V V extension leave it to the implementation and let code read it at run time. in the code: .4s means four lanes, so it runs n / 4 vector passes and a loop tail: The leftover iterations of a vector loop that handle fewer elements than one vector pass covers, usually run as a short scalar loop. More in “Your first SIMD loop” finishes the rest. A vector-length-agnostic loop keeps the body but starts each pass by asking how many elements it may take, and ends it by moving the index on by the answer. The last pass is simply shorter, so no tail exists, and a wider core runs the same instructions fewer times.

Key idea

Write the loop once and let the hardware size each pass: the body asks how many elements it may take, handles them, and moves the index on by that number until nothing is left.
Loop Lanes a pass Passes for 40 Last elements
NEON, previous lesson 4 10 a scalar tail
SVE in streaming mode 16 3 a predicated pass of 8
RVV at VLEN 512 (model) 16 3 a pass with vl 8

Code that needs the number can read it: cntw or rdvl on SVE, rdsvl for streaming mode and the vlenb register on RVV.

Checkpoint

Check: Asking for the lanes on every pass

  1. Question 1 of 2 What does a vector-length-agnostic loop never contain?

    Choose one answer.

    Show the answer: Question 1 of 2

    Answer: A constant that says how many lanes one vector holds

    A fixed-width loop names its lanes (.4s is four), so it needs a tail for the leftover elements. A vector-length-agnostic loop asks how many elements each pass may take, and the last pass is simply shorter.

  2. Question 2 of 2 A vector-length-agnostic int32 loop runs where one vector holds 16 lanes of 32 bits. How many passes does it take for n = 50?

    Type a number.

    A hint

    Three full passes cover 48 elements.

    Show the answer: Question 2 of 2

    Answer: 4 passes

    Passes of 16, 16 and 16 cover 48 elements, and a fourth pass handles the last 2. No scalar loop follows: the fourth pass is just shorter, as the 8-lane third pass of n = 40 was in the recorded run.

SVE marks the lanes still inside the array

whilelo p0.s, x5, x3 writes a predicate register: A register that marks which vector lanes an instruction acts on. SVE has p0 to p15, with one bit for each byte of a vector register, so a 32-bit lane has four bits and its lowest one counts; AVX-512 has the mask registers k0 to k7, one bit per lane. in which lane j is active while i + j < n. Loads under p0 set the inactive lanes to zero and never fault on them, stores leave memory there unwritten, incw moves i on by one vector of 32-bit lanes and b.first loops while lane 0 is active.

vla_add.s, an int32 add that never names its width, and its driver vla_main.c

sve/vla_add.s

// A vector-length-agnostic int32 add: SVE instructions in SME streaming mode (AArch64, Apple silicon).
//
// uint64_t vla_add(int32_t *c, const int32_t *a, const int32_t *b, uint64_t n, uint64_t *active)
//   c[i] = a[i] + b[i] for every i < n, nothing else in c is written;
//   active[k] = how many lanes pass k used; the return value is the number of passes.
//
// No lane count appears in the loop. whilelo marks the lanes whose index is still below n, and
// incw moves i on by however many 32-bit lanes one vector holds on the machine that runs it.
// smstart and smstop reset the vector registers, so the callee-saved d8-d15 (AAPCS64) wait on the
// stack, and the pass count is in a general register before smstop.

        .text
        .p2align 2
        .arch_extension sme
        .arch_extension sve

        .globl  _vla_add
_vla_add:
        stp     d8, d9, [sp, #-64]!
        stp     d10, d11, [sp, #16]
        stp     d12, d13, [sp, #32]
        stp     d14, d15, [sp, #48]
        smstart sm                          // streaming mode: SVE instructions are legal until smstop
        mov     x5, #0                      // i: the first element of this pass
        mov     x6, #0                      // passes so far
        whilelo p0.s, x5, x3                // lane j is active while i + j < n
        b.none  2f                          // no active lane (n = 0): no pass at all
1:      ld1w    {z0.s}, p0/z, [x1, x5, lsl #2]  // active lanes of a; inactive lanes become 0
        ld1w    {z1.s}, p0/z, [x2, x5, lsl #2]  // active lanes of b
        add     z0.s, z0.s, z1.s
        st1w    {z0.s}, p0, [x0, x5, lsl #2]    // only the active lanes reach memory
        cntp    x7, p0, p0.s                // the lanes this pass used
        str     x7, [x4, x6, lsl #3]        // active[passes] = x7
        add     x6, x6, #1
        incw    x5                          // i += the 32-bit lanes of one vector
        whilelo p0.s, x5, x3                // the predicate for the next pass
        b.first 1b                          // go on while lane 0 is active
2:      smstop  sm
        ldp     d14, d15, [sp, #48]
        ldp     d12, d13, [sp, #32]
        ldp     d10, d11, [sp, #16]
        ldp     d8, d9, [sp], #64
        mov     x0, x6
        ret

// uint64_t svl_bytes(void): the streaming vector length in bytes. rdsvl needs no streaming mode.
        .globl  _svl_bytes
_svl_bytes:
        rdsvl   x0, #1
        ret

vla_main.c

// Runs vla_add (sve/vla_add.s) for six lengths and checks every element of c against a plain C loop,
// including the elements after n, which must keep the -1 the driver put there.
// Build: clang -O1 -o vla vla_main.c sve/vla_add.s
#include <stdint.h>
#include <stdio.h>

uint64_t vla_add(int32_t *c, const int32_t *a, const int32_t *b, uint64_t n, uint64_t *active);
uint64_t svl_bytes(void);

enum { MAX = 40 };

int main(void) {
    int32_t a[MAX], b[MAX], c[MAX];
    uint64_t active[MAX];
    const uint64_t sizes[] = {0, 1, 10, 16, 17, 40};
    for (int i = 0; i < MAX; i++) {
        a[i] = i + 1;
        b[i] = 100 * (i + 1);
    }
    uint64_t bytes = svl_bytes();
    printf("streaming vector: %llu bytes = %llu lanes of 32 bits\n", (unsigned long long)bytes,
           (unsigned long long)(bytes / 4));
    printf("  n  passes  lanes per pass  check\n");
    for (unsigned k = 0; k < sizeof sizes / sizeof sizes[0]; k++) {
        uint64_t n = sizes[k];
        for (int i = 0; i < MAX; i++) c[i] = -1;  // a store past n would show up here
        uint64_t passes = vla_add(c, a, b, n, active);
        int ok = 1;
        for (uint64_t i = 0; i < MAX; i++) ok &= c[i] == (i < n ? a[i] + b[i] : -1);
        char lanes[64] = "-";
        int len = 0;
        for (uint64_t p = 0; p < passes; p++)
            len += snprintf(lanes + len, sizeof lanes - (size_t)len, p ? ", %llu" : "%llu",
                            (unsigned long long)active[p]);
        printf("%3llu  %6llu  %-14s  %s\n", (unsigned long long)n, (unsigned long long)passes, lanes,
               ok ? "ok" : "WRONG");
    }
    return 0;
}

Apple silicon runs these instructions only in SME’s streaming mode: A processor state that SME adds to AArch64, entered with smstart sm and left with smstop sm, in which SVE instructions run at a streaming vector length of its own. More in “SIMD concepts and CPU feature detection”, entered with smstart sm and left with smstop sm. The mode has rules of its own: the switch resets the vector registers, so the function saves d8 to d15 first, and NEON instructions are not allowed inside it on the host.

The streaming SVE add for six lengths, checked against a C loop Python · vla_add_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

# Build the streaming SVE add with its C driver and run it on Apple silicon (AArch64 macOS).
session.run("""
clang -O1 -o vla vla_main.c sve/vla_add.s
./vla
""")

Output

$ clang -O1 -o vla vla_main.c sve/vla_add.s
$ ./vla
streaming vector: 64 bytes = 16 lanes of 32 bits
  n  passes  lanes per pass  check
  0       0  -               ok
  1       1  1               ok
 10       1  10              ok
 16       1  16              ok
 17       2  16, 1           ok
 40       3  16, 16, 8       ok

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 vla_add_session.py

The streaming vectors are 64 bytes, 16 lanes of 32 bits. Lengths up to 16 take one pass, 17 takes two (16 and 1) and 40 takes three. The driver fills c with -1 first, so a store past n would fail the check.

Interactive · stage by stage Three predicated passes over 40 int32 values Step through the recorded n = 40 row. Before every pass, whilelo marks which of the 16 lanes still fall inside the array.
Three predicated passes over 40 int32 values, step 5 of 5. Showing n = 40 int32 values, Pass 1, i = 0, Pass 2, i = 16, Pass 3, i = 32, i = 48, no lane active. In focus: i = 48, no lane active.n = 40 int32 values16 lanes in one vectorPass 1, i = 0lanes 0 to 15 activePass 2, i = 16lanes 0 to 15 activePass 3, i = 32lanes 0 to 7 activei = 48, no lane activeb.first falls through

The drawing shows the whole scene; its 5 steps are listed below.

Every step as text
  1. whilelo builds the first mask. Before the loop, whilelo p0.s, x5, x3 tests i + j < n for every lane j. With i = 0 and n = 40 all 16 lanes pass, so p0 marks them all active. No instruction in the loop names the number 16.
  2. Pass 1 handles 16 elements. ld1w loads a[0] to a[15] and b[0] to b[15] under p0, add sums the 16 lanes and st1w stores them. cntp counts 16 active lanes, the first number in the 16, 16, 8 of the run.
  3. incw moves i on by one vector. incw x5 adds the number of 32-bit lanes in one vector, 16 on the host, so i becomes 16. whilelo again finds every lane below 40, and b.first sees lane 0 active and branches back.
  4. The last pass is partial. At i = 32 only lanes 0 to 7 satisfy i + j < 40. Lanes 8 to 15 are inactive: ld1w sets them to zero and cannot fault on them, and st1w leaves memory there unwritten. The pass handles 8 elements.
  5. No active lane ends the loop. incw makes i = 48. whilelo finds no lane below 40 and clears the First flag, so b.first falls through to smstop. Three passes in all, and every element of c matches the plain C sum.

Checkpoint

Check: SVE predicates and whilelo

  1. Question 1 of 2 In the recorded run, the third pass for n = 40 starts at i = 32. How many lanes does whilelo mark active?

    Type a number.

    A hint

    Lane j is active while i + j < n.

    Show the answer: Question 1 of 2

    Answer: 8 lanes

    32 + j < 40 holds for j = 0 to 7, so 8 of the 16 lanes are active. The run printed the passes as 16, 16, 8.

  2. Question 2 of 2 What does st1w do with the lanes that its predicate marks inactive?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: It leaves the memory of those lanes unwritten

    The predicate decides which lanes take part. Loads give 0 in inactive lanes and never fault on them, and stores skip them, so the last pass may extend past the array without a fault or a stray write.

RVV returns the pass length from vsetvli

vsetvli t0, a3, e32, m1, ta, ma gets a3, the elements still to do, asks for 32-bit elements and writes into t0 the length of this pass, vl. A pass holds at most VLMAX = LMUL × VLEN / SEW elements, where VLEN is the register width the core was built with. The assembler refuses V instructions unless it is told the target has V.

examples/riscv/vla_add.s: the same add for RV64 with V S · riscv/vla_add.s
# A vector-length-agnostic int32 add for RV64 with the V extension (Linux, LP64D).
#
# void vla_add(int32_t *c, const int32_t *a, const int32_t *b, size_t n)
#   a0 = c, a1 = a, a2 = b, a3 = the elements still to add (the application vector length, AVL)
#
# Each pass starts with vsetvli, which returns in t0 how many elements this pass handles (vl).
# The code never states the register width: vl is at most VLMAX = LMUL * VLEN / SEW, where VLEN is
# the machine's vector register width. With n = 0, vl is 0, nothing is loaded or stored, and the
# loop ends after one pass that does nothing.

        .text
        .p2align 2
        .globl  vla_add
vla_add:
1:      vsetvli t0, a3, e32, m1, ta, ma     # 32-bit elements, one register per operand
        vle32.v v0, (a1)                    # vl elements of a
        vle32.v v1, (a2)                    # vl elements of b
        vadd.vv v0, v0, v1
        vse32.v v0, (a0)                    # vl sums into c
        sub     a3, a3, t0                  # elements left
        slli    t1, t0, 2                   # 4 bytes per element
        add     a1, a1, t1
        add     a2, a2, t1
        add     a0, a0, t1
        bnez    a3, 1b
        ret
Assembled for RISC-V and read back with llvm-objdump Python · rvv_add_session.py
import pathlib
import sys

sys.path.insert(0, str(pathlib.Path(__file__).parent / "sessions"))
import session

# RISC-V (RV64 with V, Linux): the assembler needs the extension named, then reads the loop back.
session.run("""
llvm-mc --triple=riscv64-linux-gnu -filetype=obj -o add.o riscv/vla_add.s 2>&1 | grep -m 1 -o "instruction requires the following: 'V'"
llvm-mc --triple=riscv64-linux-gnu -mattr=+v -filetype=obj -o add.o riscv/vla_add.s && echo "assembled"
llvm-objdump -d --mattr=+v add.o | sed -n '/<vla_add>:/,$p'
""")

Output

$ llvm-mc --triple=riscv64-linux-gnu -filetype=obj -o add.o riscv/vla_add.s 2>&1 | grep -m 1 -o "instruction requires the following: 'V'"
instruction requires the following: 'V'
$ llvm-mc --triple=riscv64-linux-gnu -mattr=+v -filetype=obj -o add.o riscv/vla_add.s && echo "assembled"
assembled
$ llvm-objdump -d --mattr=+v add.o | sed -n '/<vla_add>:/,$p'
0000000000000000 <vla_add>:
       0: 0d06f2d7     	vsetvli	t0, a3, e32, m1, ta, ma
       4: 0205e007     	vle32.v	v0, (a1)
       8: 02066087     	vle32.v	v1, (a2)
       c: 02008057     	vadd.vv	v0, v0, v1
      10: 02056027     	vse32.v	v0, (a0)
      14: 405686b3     	sub	a3, a3, t0
      18: 00229313     	slli	t1, t0, 0x2
      1c: 006585b3     	add	a1, a1, t1
      20: 00660633     	add	a2, a2, t1
      24: 00650533     	add	a0, a0, t1
      28: fc069ce3     	bnez	a3, 0x0 <vla_add>
      2c: 00008067     	ret

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 rvv_add_session.py

The specification sets vl to AVL, the elements left, when AVL ≤ VLMAX, and to VLMAX when AVL ≥ 2 × VLMAX. In between, a core may pick any vl from half of AVL, rounded up, to VLMAX. The model below applies that rule to 40 elements at four widths; it models the specification and runs no RISC-V code.

vl_model.py: the passes of 40 int32 elements at VLEN 128 to 1024 Python · vl_model.py
"""How many elements each vsetvli pass gets, for 40 elements of 32 bits at four vector widths.

A model of the rule in the RISC-V V 1.0 specification ("Constraints on Setting vl"), not a run on RISC-V
hardware. With AVL elements left and VLMAX = LMUL * VLEN / SEW:
  vl = AVL                        when AVL <= VLMAX
  ceil(AVL / 2) <= vl <= VLMAX    when VLMAX < AVL < 2 * VLMAX  (the implementation chooses)
  vl = VLMAX                      when AVL >= 2 * VLMAX
The model prints both ends of the choice and then tries every legal choice to count the passes.
"""

import math

N = 40  # elements to process
SEW = 32  # bits per element
LMUL = 1  # registers per operand
VLENS = (128, 256, 512, 1024)  # register widths an implementation may have


def legal_vls(avl: int, vlmax: int) -> range:
    """Every vl the rule allows for this pass."""
    if avl <= vlmax:
        return range(avl, avl + 1)
    if avl < 2 * vlmax:
        return range(math.ceil(avl / 2), vlmax + 1)
    return range(vlmax, vlmax + 1)


def passes(avl: int, vlmax: int, pick) -> list[int]:
    """The vl of every pass when the implementation picks with `pick` (min or max) each time."""
    out = []
    while avl > 0:
        vl = pick(legal_vls(avl, vlmax))
        out.append(vl)
        avl -= vl
    return out


def every_count(avl: int, vlmax: int) -> set[int]:
    """The pass counts over every sequence of legal choices."""
    if avl == 0:
        return {0}
    return {1 + rest for vl in legal_vls(avl, vlmax) for rest in every_count(avl - vl, vlmax)}


def show(vls: list[int]) -> str:
    return ", ".join(str(vl) for vl in vls)


print(f"n = {N} elements, SEW = {SEW}, LMUL = {LMUL}")
for vlen in VLENS:
    vlmax = LMUL * vlen // SEW
    widest = passes(N, vlmax, max)
    even = passes(N, vlmax, min)
    counts = every_count(N, vlmax)
    print(f"VLEN {vlen:4}: VLMAX {vlmax:2}")
    print(f"  largest vl first: {show(widest)}")
    print(f"  smallest legal vl: {show(even)}")
    print(f"  passes with any legal choice: {sorted(counts)} (ceil(n / VLMAX) = {math.ceil(N / vlmax)})")

Output

n = 40 elements, SEW = 32, LMUL = 1
VLEN  128: VLMAX  4
  largest vl first: 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
  smallest legal vl: 4, 4, 4, 4, 4, 4, 4, 4, 4, 4
  passes with any legal choice: [10] (ceil(n / VLMAX) = 10)
VLEN  256: VLMAX  8
  largest vl first: 8, 8, 8, 8, 8
  smallest legal vl: 8, 8, 8, 8, 8
  passes with any legal choice: [5] (ceil(n / VLMAX) = 5)
VLEN  512: VLMAX 16
  largest vl first: 16, 16, 8
  smallest legal vl: 16, 12, 12
  passes with any legal choice: [3] (ceil(n / VLMAX) = 3)
VLEN 1024: VLMAX 32
  largest vl first: 32, 8
  smallest legal vl: 20, 20
  passes with any legal choice: [2] (ceil(n / VLMAX) = 2)

Recorded with Python 3.14.8 on macOS 26 arm64. To run it yourself: mise exec python@3.14.8 -- python3 vl_model.py

The pass count stays ceil(40 / VLMAX) whatever the core picks: 10, 5, 3 and 2. LMUL above 1 register grouping: Treating several consecutive vector registers as one operand. In the RISC-V V extension, LMUL sets how many registers form a group, so one instruction handles more elements in each pass. to lengthen each pass. The tail and mask policy: The rule for vector elements an instruction does not compute: those past the vector length (the tail) and those its mask turns off. Undisturbed keeps their old values; agnostic lets the hardware keep them or fill them with all 1s. ta, ma let the hardware fill elements past vl, and masked-off ones, with all 1s. Linux documents how it saves and controls this vector state for each thread.

Checkpoint

Check: vsetvli and the vl rule

  1. Question 1 of 2 With e32 and m1, how many elements can one vsetvli pass handle on a core whose VLEN is 256?

    Type a number.

    A hint

    VLMAX = LMUL × VLEN / SEW.

    Show the answer: Question 1 of 2

    Answer: 8 elements

    1 × 256 / 32 = 8, so 40 elements take 5 passes there, as the model printed for VLEN 256.

  2. Question 2 of 2 vsetvli is asked for AVL = 5 elements where VLMAX is 16. Which vl does the specification require?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: 5

    The rule has three cases: vl = AVL when AVL is at most VLMAX, vl = VLMAX when AVL is at least twice VLMAX, and a choice from ceil(AVL / 2) to VLMAX in between.

Practise

Work on copies of the lesson’s files and write each prediction down before you run.

  1. In vla_main.c, add 33 and 48 to sizes, raise MAX to 48, and predict the passes and the lanes of each.
  2. In vl_model.py, set N = 100 and predict the passes at VLEN 128 and at VLEN 512.
  3. Set LMUL = 2 in vl_model.py and predict VLMAX and the passes at VLEN 256.

Practice that adapts to you

Practise this lesson

8 questions about 5 ideas of this lesson. Each answer is explained at once, and what you get right is kept in this browser.

  1. Question 1 of 8 What changes when the same vector-length-agnostic binary runs on a core with twice the vector width?

    Choose one answer.

    Show the answer: Question 1 of 8

    Answer: The number of passes; the instructions stay the same

    One binary fits every width because the width is asked for, not assumed. The table of the lesson shows 10 passes at 4 lanes and 3 at 16 for the same 40 elements.

  2. Question 2 of 8 A core's vectors hold 2,048 bits. How many passes does a vector-length-agnostic loop take for 40 int32 elements?

    Type a number.

    A hint

    Count the 32-bit lanes in 2,048 bits.

    Show the answer: Question 2 of 8

    Answer: 1 passes

    2,048 / 32 = 64 lanes, more than 40, so one pass handles every element and its predicate (or vl) covers 40 of the 64 lanes.

  3. Question 3 of 8 After whilelo writes the predicate for the next pass, what does b.first test?

    Choose one answer.

    Show the answer: Question 3 of 8

    Answer: That lane 0 of the new predicate is active

    whilelo sets the condition flags from the predicate it writes: First when lane 0 is active, None when no lane is. The loop needs no separate compare of i and n.

  4. Question 4 of 8 On the host's 16 lanes, how does the predicated loop split n = 17?

    Choose one answer.

    Show the answer: Question 4 of 8

    Answer: Two passes: 16 lanes, then 1

    Every pass but the last is full, and the last one has n minus the elements already done.

  5. Question 5 of 8 What does vla_add do when n = 0?

    Choose one answer.

    Show the answer: Question 5 of 8

    Answer: whilelo marks no lane active, b.none skips the loop, and it returns 0 passes

    The empty case needs no code of its own: the same predicate that shortens the last pass makes an empty first pass impossible.

  6. Question 6 of 8 Which formula gives VLMAX, the most elements one vsetvli pass can handle?

    Choose one answer.

    Show the answer: Question 6 of 8

    Answer: LMUL × VLEN / SEW

    At VLEN 512 with e32 and m1 the formula gives 16, the same lane count as the host's streaming vectors.

  7. Question 7 of 8 With VLMAX = 16 and 40 elements, which split of the passes can the specification not produce?

    Choose one answer.

    Show the answer: Question 7 of 8

    Answer: 16, 8, 16

    40 is at least 2 × 16, so the first pass must be 16. The choice applies only when the elements left lie between VLMAX and twice VLMAX.

  8. Question 8 of 8 By the vl rule, how many passes do 100 int32 elements take at VLEN 256 with e32 and m1?

    Type a number.

    A hint

    Work out VLMAX first, then ceil(n / VLMAX).

    Show the answer: Question 8 of 8

    Answer: 13 passes

    VLMAX = 256 / 32 = 8 and ceil(100 / 8) = 13. With 12 elements left a core may split the last two passes as 8 and 4 or as 6 and 6, but every legal choice gives 13 passes.

SME streaming mode on Apple silicon, rule by rule Pro: opens with a Pro, Premium or Ultimate pass

Streaming mode is how Apple silicon runs SVE instructions, and it brings rules that a plain SVE core does not have. Each rule below is a recorded run on the host: how long a vector is, what traps, what a mode switch erases and what a predicated pass may touch.

In this part:

  • Measuring the vector length inside and outside streaming mode
  • Advanced SIMD inside streaming mode ends the process
  • What a mode switch erases, reproduced
  • Which side of a call switches the mode
  • A predicated last pass next to a guard page

3 runnable examples (Python, S) · about 6 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

RVV in depth: vtype fields, LMUL and loops checked at four widths Pro: opens with a Pro, Premium or Ultimate pass

The RVV add above used one register per operand and never looked at its policy bits. The sections below decode those fields, double the pass length with LMUL, check three classic loops at four vector widths, reproduce a bug that only narrow cores show and compare the three loop styles of this lesson.

In this part:

  • What e32, m1, ta and ma tell the hardware
  • LMUL 2 halves the passes
  • Three loops checked at four widths
  • A pointer bug that narrow cores show
  • Choosing among NEON, streaming SVE and RVV

3 runnable examples (Python, S) · about 8 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Interview questions

Why does a vector-length-agnostic loop need no scalar tail?

Because every pass asks the hardware how many elements it may handle, and the last pass is simply told fewer. On RVV, vsetvli returns vl = AVL once the elements left fit in one pass, so the last pass handles exactly what remains. On SVE, whilelo marks only the lanes whose index is below n: loads give 0 in the other lanes and stores skip them. In the recorded run, 40 elements on 16 lanes took passes of 16, 16 and 8 with no second loop.

Three longer questions follow the same loops into code that a team has to ship:

  1. Mid to senior

    A NEON loop is ported to streaming SVE on Apple silicon by putting smstart sm and smstop sm around it and rewriting its adds. It ends with SIGILL at once; once that is fixed, a caller finds that a double it kept in d8 has become 0. Explain both failures and the rules the fixed function follows.

    Model answer, Pro. Model answers open with any pass; prices are in the notice on this page. First step and follow-ups

  2. Mid to staff

    An RVV saxpy passes every test on a board with VLEN 1024 but gives wrong results on a VLEN 128 board that the team cannot reach. How do you find the bug without the narrow board, and how do you test vector-length-agnostic code so that a bug that depends on the width cannot hide?

    Model answer, Pro. First step and follow-ups

  3. Senior to staff

    An RVV routine counts matching int32 values with vmseq.vx and vcpop.m after a vsetvli with ta, ma. A colleague fears that the agnostic tail of the mask can hold 1s and inflate the count. Is the fear justified for this code, and which change to the code would make it justified?

    Model answer, Pro. First step and follow-ups

Flashcards · 5 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. What makes a loop vector-length-agnostic?

    Every iteration asks the hardware how many elements it may handle, handles them, and moves the index on by that number until nothing is left. One binary then uses the full width of any core.

  2. How does SVE handle the last, partial vector of a loop?

    whilelo builds a predicate marking only the lanes whose index is still below n. Loads give 0 in the other lanes and stores skip them, so no scalar tail is needed.

  3. What does RVV's vsetvli return, and what happens on the last iteration?

    vl, the number of elements this iteration will handle. Once the remaining count fits in one vector, vl equals it, so the last iteration handles exactly what remains.

  4. A vector-length-agnostic loop adds 40 elements on a core with 16 lanes. How do the iterations split?

    16, 16 and 8 lanes, three iterations and no second loop for the tail.

  5. What does a predicate register do?

    It marks which vector lanes an instruction acts on. SVE has p0 to p15, with one bit per byte of a vector register; AVX-512 has the mask registers k0 to k7, one bit per lane.

Key takeaways

  • A vector-length-agnostic loop asks the hardware for the length of each pass and moves on by that many, so one binary fits any vector width.
  • SVE’s whilelo builds a predicate of the lanes still inside the array, and inactive lanes never fault on a load and are never stored.
  • Apple silicon runs SVE instructions in SME streaming mode, where the recorded loop added 40 int32 values in passes of 16, 16 and 8 lanes.
  • RVV’s vsetvli returns vl, at most LMUL × VLEN / SEW, and any legal choice of vl keeps the pass count at ceil(n / VLMAX).

Terms in this lesson

predicate register
A register that marks which vector lanes an instruction acts on. SVE has p0 to p15, with one bit for each byte of a vector register, so a 32-bit lane has four bits and its lowest one counts; AVX-512 has the mask registers k0 to k7, one bit per lane.
register grouping
Treating several consecutive vector registers as one operand. In the RISC-V V extension, LMUL sets how many registers form a group, so one instruction handles more elements in each pass.
tail and mask policy
The rule for vector elements an instruction does not compute: those past the vector length (the tail) and those its mask turns off. Undisturbed keeps their old values; agnostic lets the hardware keep them or fill them with all 1s.
vector length
The width of a vector register on a given core, in bits or bytes. Fixed-width SIMD sets it in the instruction; SVE and the RISC-V V extension leave it to the implementation and let code read it at run time.
vector-length-agnostic code
Vector code that never states how many lanes a vector holds: every pass asks the hardware how many elements it may handle and moves on by that many, so one binary uses the full width of any core.

References

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.