Re: [PATCH bpf-next v4 09/18] bpf: Support stack arguments for bpf functions

Alexei Starovoitov
2026-04-12T22:24:11Z

On Sat, Apr 11, 2026 at 10:00 PM Yonghong Song <yonghong.song@linux.dev> wrote:
>
> Currently BPF functions (subprogs) are limited to 5 register arguments.
> With [1], the compiler can emit code that passes additional arguments
> via a dedicated stack area through bpf register
> BPF_REG_STACK_ARG_BASE (r12), introduced in the previous patch.
>
> The compiler uses positive r12 offsets for incoming (callee-side) args
> and negative r12 offsets for outgoing (caller-side) args, following the
> x86_64/arm64 calling convention direction. There is an 8-byte gap at
> offset 0 separating the two regions:
>
> Incoming (callee reads): r12+8 (arg6), r12+16 (arg7), ...
> Outgoing (caller writes): r12-N*8 (arg6), ..., r12-8 (last arg)
>
> The following is an example to show how stack arguments are saved
> and transferred between caller and callee:
>
> int foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7) {
> ...
> bar(a1, a2, a3, a4, a5, a6, a7, a8);
> ...
> }
>
> Caller (foo) Callee (bar)
> ============ ============
> Incoming (positive offsets): Incoming (positive offsets):
>
> r12+8: [incoming arg 6] r12+8: [incoming arg 6] <-+
> r12+16: [incoming arg 7] r12+16: [incoming arg 7] <-|+
> r12+24: [incoming arg 8] <-||+
> Outgoing (negative offsets): |||
> r12-24: [outgoing arg 6 to bar] -------->-------------------------+||
> r12-16: [outgoing arg 7 to bar] -------->--------------------------+|
> r12-8: [outgoing arg 8 to bar] -------->---------------------------+
>
> Note the reversed order: the caller's most negative outgoing offset
> (arg6) maps to the callee's first positive incoming offset (arg6).
> The caller stores arg6 at r12-24 (= -3*8 for 3 stack args), and
> the callee reads it at r12+8.
>
> If the bpf function has more than one call:
>
> int foo(int a1, int a2, int a3, int a4, int a5, int a6, int a7) {
> ...
> bar1(a1, a2, a3, a4, a5, a6, a7, a8);
> ...
> bar2(a1, a2, a3, a4, a5, a6, a7, a8, a9);
> ...
> }
>
> Caller (foo) Callee (bar2)
> ============ ==============
> Incoming (positive offsets): Incoming (positive offsets):
>
> r12+8: [incoming arg 6] r12+8: [incoming arg 6] <+
> r12+16: [incoming arg 7] r12+16: [incoming arg 7] <|+
> r12+24: [incoming arg 8] <||+
> Outgoing for bar2 (negative offsets): r12+32: [incoming arg 9] <|||+
> r12-32: [outgoing arg 6] ---->----------->-------------------------+|||
> r12-24: [outgoing arg 7] ---->----------->--------------------------+||
> r12-16: [outgoing arg 8] ---->----------->---------------------------+|
> r12-8: [outgoing arg 9] ---->----------->----------------------------+

The diagram looks good.
When I suggested to use r12 I thought the whole support will take
much longer and by that time we will land Eduard's r11 work.
Now it looks like 6+ args will land first,
so let's swap them. Please use r11 as a base and Eduard's spill/fill
will use r12 if/when it happens.

> [1] https://github.com/llvm/llvm-project/pull/189060

Same for llvm. pls update it to use r11.
I feel it can land soon-ish.

> +struct bpf_stack_arg_state {
> + struct bpf_reg_state spilled_ptr; /* for spilled scalar/pointer semantics */
> + u8 slot_type[BPF_REG_SIZE];
> +};

This is pointless. Not sure whether claude came up with this idea
or you, but please think harder. Why slot_type[] is there?
What does it give?
Whether we pass u8 or u64 the slot is still 8 byte and this is
still the same spilled_ptr..
which is also wrong name here. Call it 'arg' ?

Also think through how i128 and structs of 8,16,24,32 bytes will be
passed.