freenode
Kernel & Low-Level

BPF gains 16-byte aggregate returns via R0:R2 pair

Kernel verifier and JITs learn LLVM 23's convention so subprograms and kfuncs can return __int128 and small structs by value.

The BPF subsystem is adding support for return values larger than eight bytes and up to sixteen, matching a calling convention LLVM 23 already emits for the BPF target. An __int128, or a struct or union in that size range, comes back in the R0:R2 register pair, with R2 holding the upper half.

Until now the BPF backend could not return those values at all: aggregate returns were rejected at compile time, and __int128 failed in the backend. Yonghong Song's series teaches the verifier, precision backtracking, live-register analysis, and the JITs the same R0:R2 rule so BPF-to-BPF subprogram calls and kfunc calls can use it. The main program's exit path is unchanged; its return is still the program exit code and remains capped at eight bytes.

For kfuncs and global subprograms, a by-value struct or union must contain only scalars. The verifier treats the returned bits as an unknown scalar, so a pointer member would escape provenance and reference tracking. Static subprograms are verified inline and are not restricted that way. Callbacks (timer, loop, exception handlers, and similar) stay single-register: neither bpf_callback_t nor the exception callback prototype has a second return register, so pair returns are rejected at load time.

Placing the second half in R2 for kfuncs needs architecture-specific JIT work. A new capability flag lets each JIT opt in; only x86, arm64, and riscv do so far, with x86 emitting an extra move from the native second return register. Fastcall kfuncs that would return a pair are also rejected, because the fastcall contract assumes R2 is preserved. Unreliable BTF on a subprogram that was supposed to return a pair is treated as an error rather than a silent fall-back to R0-only semantics.

Selftests cover __int128, struct, and union returns from subprograms and kfuncs, plus negative cases for oversized returns, arena-pointer leaks in R2, and illegal callback prototypes. Documentation for kfunc return values records the new limits and the scalar-only rule for aggregate returns.