Phase 1, week 7retrospective

vsetvli and LMUL semantics

vsetvli encodes SEW, LMUL and tail/mask policy; that sets register pressure, and the VL it returns drives strip-mining.


Four fields, one return value

vsetvli rd, rs1, e32, m2, ta, ma writes a vtype and returns the vector length in rd:

vl    = min(avl, VLMAX)
VLMAX = LMUL * VLEN / SEW

At VLEN=256 that is 16 lanes for e32,m2, 8 for e32,m1, and 32 for e8,m1. The rd is not decoration. The next AVL is avl - vl, and the pointer bump is vl * sizeof(T). That subtraction is the strip-mining, and it is the only reason the loop is correct at a vector length the compiler never knew.

vsetivli rd, uimm, e32, m1, ta, ma is the same instruction with an immediate AVL. vsetvl rd, rs2, rs1 takes vtype from a register when the loop switches configurations. Passing x0 as rd discards the result, which is safe only when the AVL is already VLMAX.

Policy bits

The last two fields decide the lanes an instruction does not touch. ta leaves tail elements past vl unspecified, tu guarantees they are undisturbed, and ma and mu make the same choice for masked-off lanes. The unspecified case is what lets the compiler drop a merge when the destination is dead, and it is also why an accumulator that survives a short final iteration needs tu: under ta the lanes past vl may hold all ones, and the next multiply would fold them in.

LMUL is the register-pressure dial. m8 claims eight of the thirty-two registers and v0 is reserved for masks, so an m8 loop with three live vectors already holds twenty-four. vsetvli itself is cheap; a loop that toggles between two vtype values is not, because it serialises on vl.

The policy reaches the instruction through the pseudo instruction’s immediate operand, and which pair LLVM picks is release-dependent. Read the ta or tu in the disassembly of the build you pinned instead of assuming the default.

Things that break

An accumulator read after a short final iteration with ta in force. The lanes past vl are unspecified, so whether they hold 1.0 or a stale partial sum is the implementation’s choice, and the result is right when n is a multiple of VLMAX and wrong otherwise. The other failure is ignoring rd. A loop that advances pointers by VLMAX while the AVL was smaller runs past the buffer, and QEMU does not always fault where you would notice.

Sources

A version records the documentation the claim was checked against, not a house pin. Confirm flags with --help on the release you installed.

Check yourself

What is VLMAX for `e32,m2` at VLEN=256?

What does `tu` do to elements past `vl`?

Which value drives the next iteration of a strip-mined loop?

0 / 3 correct

← back to the map