RVV disassembly check
Disassembly is right when every inner-loop instruction is explainable as load, MAC, vsetvli, or control, and no spills appear.
The listing, filtered
riscv64-linux-gnu-objdump -d bin/matmul_rvv | sed -n '/<matmul>:/,$p'
riscv64-linux-gnu-objdump -d bin/matmul_rvv | grep -c vfmacc
The inner loop has a shape. Yours should look like this, with the branch target named rather than printed as an address:
.Lloop:
vsetvli a3,a0,e32,m2,ta,ma
vle32.v v10,(a2)
vle32.v v12,(a1)
vfmacc.vv v8,v10,v12
sub a0,a0,a3
slli a4,a3,2
add a1,a1,a4
add a2,a2,a4
bne a0,zero,.Lloop
Nine instructions, and every one of them is a load, a MAC, or loop control. vsetvli is the control that also produces the vl in a3. The two vle32.v fetch the next slice of A and of B. vfmacc.vv adds v10 * v12 into v8, which is the accumulator and lives across iterations, so no load in the loop body is allowed to target it. The sub produces the next AVL, slli turns lanes into bytes, the two adds bump pointers, and bne closes the loop. If you cannot assign a line to one of those three jobs, you do not yet know what the loop does.
Contraction is a permission
vfmacc is fused. It appears only when the multiply and the add may become one operation: in MLIR the contract fast-math flag has to reach the arith ops, and in C it depends on -ffp-contract, where fast permits fusion and off forbids it. With contraction disabled the loop shows vfmul.vv followed by vfadd.vv: correct, one instruction longer, not a bug.
The test is not whether vfmacc occurs but whether each instruction is explainable: a vfmul and vfadd you can explain beat a vfmacc you cannot.
The mnemonics are fixed by the ISA. What moves between LLVM releases is when the backend fuses a multiply and add, and which policy pair it prints, so confirm those against the release you pinned rather than an older listing you saved.
Things that break
vs1r.v and vl1r.v in the loop body are whole-register spills and reloads, so the live tile does not fit the register budget; the fix is a smaller tile or a smaller LMUL, not another optimisation flag. A vsetvli zero,... that nothing consumes means the length was discarded, so the pointer advance must be VLMAX and any other stride walks off the end of the buffer. A slli by 2 in an f64 kernel is a half-finished type change: the listing is identical in shape and the stride is wrong.
Sources
- llvm-objdump command guidedocs
- GNU binutils manualdocs
- LLVM RISC-V Vector Extension guidedocs · LLVM 23
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
The loop shows `vfmul.vv` then `vfadd.vv` where you expected `vfmacc.vv`. What is the likely cause?
`vs1r.v` and `vl1r.v` appear inside the inner loop. What does that tell you?
A `vsetvli zero,a0,e32,m2,ta,ma` in the loop, with nothing consuming the `vl`. What must be true of the pointer advance?