From: Yonghong Song <yonghong.song@linux.dev>
To: bot+bpf-ci@kernel.org, bpf@vger.kernel.org
Cc: ast@kernel.org, andrii@kernel.org, daniel@iogearbox.net,
eddyz87@gmail.com, kernel-team@fb.com, martin.lau@kernel.org,
clm@meta.com, ihor.solodrai@linux.dev
Subject: Re: [PATCH bpf-next v6 09/10] selftests/bpf: Add inline-asm and subprog tests for R0:R2 returns
Date: Mon, 17 Aug 2026 10:12:16 -0700 [thread overview]
Message-ID: <44048a6b-764e-4d35-8276-938682b899db@linux.dev> (raw)
In-Reply-To: <007af3660f62710298086713b7a7ffd4412ab2020a0cb628a4e100d62e783e3c@mail.kernel.org>
On 8/16/26 10:17 PM, bot+bpf-ci@kernel.org wrote:
>> diff --git a/tools/testing/selftests/bpf/progs/aggregate_ret_func.c b/tools/testing/selftests/bpf/progs/aggregate_ret_func.c
>> new file mode 100644
>> index 000000000000..e35baaa10fea
>> --- /dev/null
>> +++ b/tools/testing/selftests/bpf/progs/aggregate_ret_func.c
> [ ... ]
>
>> +SEC("tc")
>> +__failure __msg("At subprogram exit the register R2 is not a scalar value")
>> +__naked int aggregate_ret_global_ptr_fail(void)
>> +{
>> + asm volatile (
>> + "call %[global_agg_bad_ptr];"
>> + "r0 = r2;"
>> + "exit;"
>> + :
>> + : __imm(global_agg_bad_ptr)
>> + : __clobber_all);
>> +}
> Does this test need __load_if_JITed()? Without it, the test runs on
> kernels where the JIT is disabled. When env->prog->jit_requested is
> false, bpf_compute_subprog_ret_regs() returns early without marking any
> ret_reg_pair flag, so global_agg_bad_ptr is not treated as returning a
> register pair.
>
> Main verification happens first, and with nregs = 1 at the call site,
> check_func_call() marks only R0 (the callee's R2 is never propagated to
> the caller). The next instruction "r0 = r2" then reads the caller's
> NOT_INIT R2 from clear_caller_saved_regs() and dies in
> __check_reg_arg() with "R2 !read_ok", never reaching
> do_check_subprogs() where the asserted message "At subprogram exit the
> register R2 is not a scalar value" would appear.
>
> The program still fails to load, but with a different message than the
> test asserts. aggregate_ret_static_precise and
> aggregate_ret_global_precise both carry __load_if_JITed() to ensure the
> R0:R2 convention is active when they run. Would the same gate keep this
> test meaningful?
Right, we should have __load_if_JITed() in the asm test.
>
>> +/*
>> + * R2 is a return register once the subprogram returns a pair, so a stack
>> + * pointer left in it is rejected at the callee's exit exactly as one in R0
>> + * is: the callee frame is gone by the time the caller could use it.
>> + */
>> +SEC("tc")
>> +__failure __msg("cannot return stack pointer to the caller")
>> +__naked int aggregate_ret_static_ptr_fail(void)
>> +{
>> + asm volatile (
>> + "call %[static_agg_bad_ptr];"
>> + "r0 = 0;"
>> + "exit;"
>> + :
>> + : __imm(static_agg_bad_ptr)
>> + : __clobber_all);
>> +}
> Does this test also need __load_if_JITed()? When the JIT is off,
> bpf_compute_subprog_ret_regs() bails out before setting ret_reg_pair
> for any subprogram, so bpf_ret_reg_pair() returns false for
> static_agg_bad_ptr and prepare_func_exit() computes nregs = 1.
>
> The loop "for (i = 0; i < nregs; i++)" then only inspects R0. Since
> static_agg_bad_ptr leaves R0 as a scalar and the stack pointer only in
> R2, the loop finds nothing and the program verifies cleanly. The test
> body "r0 = 0; exit;" never reads R2, so the caller's R2 is already
> NOT_INIT from clear_caller_saved_regs() and stays that way.
>
> The __failure tag would then cause the subtest to report failure,
> because the program loads successfully instead of failing with the
> asserted message.
The same as above, we should have __load_if_JITed() in the above asm.
> [ ... ]
>
>> diff --git a/tools/testing/selftests/bpf/progs/compute_live_registers.c b/tools/testing/selftests/bpf/progs/compute_live_registers.c
>> index d055fc7b3b95..0be9441ec273 100644
>> --- a/tools/testing/selftests/bpf/progs/compute_live_registers.c
>> +++ b/tools/testing/selftests/bpf/progs/compute_live_registers.c
> [ ... ]
>
>> +SEC("socket")
>> +/* A program observing the pair needs the JIT; see bpf_compute_subprog_ret_regs(). */
>> +__load_if_JITed()
>> +__log_level(2)
>> +__msg("0: .12345.... (85) call pc+2")
>> +__msg("1: ..2....... (bf) r0 = r2")
>> +/* R2 is not read at the exit of this program, which returns an int, ... */
>> +__msg("2: 0......... (95) exit")
>> +__msg("3: .......... (b7) r0 = 1")
>> +__msg("4: 0......... (b7) r2 = 2")
>> +/* ... but it is at the exit of aux2(), which returns a register pair. */
>> +__msg("5: 0.2....... (95) exit")
>> +__naked void subprog_ret_reg_pair(void)
> The comment says R2 is not live at the program's exit because "this
> program ... returns an int". The program is declared "__naked void
> subprog_ret_reg_pair(void)" - it does not return an int.
You are right, the comment is not correct.
>
> Is the reason actually that bpf_compute_subprog_ret_regs() skips the
> main program?
>
> In kernel/bpf/verifier.c:437, the loop starts at subprog 1:
>
> /*
> * Skip the main program: its return value is the program's exit
> * code, read out of R0, so it never uses the register pair. ...
> */
> for (subprog = 1; subprog < env->subprog_cnt; subprog++) {
>
> So R2 would not be live at the main program's exit even if subprog 0
> were declared to return 16 bytes. The program's return type is not what
> decides this.
We skip R2 for main program (subprog 0). It is user's resposibility to
test return value for main program.
>
> [ ... ]
>
>> diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
>> index c4bc8e11c6e1..20a9b9f20e96 100644
>> --- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
>> +++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
> [ ... ]
>
>> +__bpf_kfunc struct prog_test_ret_pair bpf_kfunc_call_test_ret_fastcall(u64 a, u64 b)
>> +{
>> + struct prog_test_ret_pair r = { .lo = a + b, .hi = a - b };
>> +
>> + return r;
>> +}
>> +
>> +__bpf_kfunc struct prog_test_ret_ptr bpf_kfunc_call_test_ret_ptr(u64 tag)
>> +{
>> + struct prog_test_ret_ptr r = { .p = NULL, .tag = tag };
>> +
>> + return r;
>> +}
>> +
>> +__bpf_kfunc struct prog_test_ret_ii bpf_kfunc_call_test_ret_ii(int a, int b)
>> +{
>> + struct prog_test_ret_ii r = { .a = a, .b = b };
>> +
>> + return r;
>> +}
>> #endif /* __x86_64__ || __aarch64__ */
> Would riscv64 benefit from test coverage here? Commit 3bfe2cb81326
> ("bpf: Wire up JIT support for 16-byte kfunc returns") added
> bpf_jit_supports_kfunc_ret_reg_pair() returning true for riscv64, so
> bpf_add_kfunc_call() accepts 16-byte kfunc returns there.
>
> But the test infrastructure excludes riscv64:
>
> - bpf_kfunc_call_test_ret_pair(), _ret_fastcall(), _ret_ptr(), and
> _ret_ii() are not compiled into bpf_testmod on riscv64 (arch guard)
> - every test in progs/aggregate_ret_kfunc.c is tagged "__arch_x86_64
> __arch_arm64", so test_loader.c skips them on riscv64
>
> The riscv64 LP64D ABI returns a 16-byte two-word struct in a0:a1,
> matching the x86-64 rax:rdx and arm64 x0:x1 cases. bpf_misc.h already
> defines "__arch_riscv64", and test_loader.c maps ARCH_RISCV64.
>
> Could the arch guard be extended to "defined(__riscv) && __riscv_xlen
> == 64" and the tests tagged with "__arch_riscv64" to cover the newly
> enabled JIT path?
I didn't test riscv64 as I don't have infrastructure for this.
riscv64 can be implemented later.
>
>> +/*
>> + * Takes no argument on purpose: with no arguments there is nothing for the sret
>> + * pointer to displace, so this needs no architecture guard even though it
>> + * returns 24 bytes. See the comment on bpf_kfunc_call_test_i128() above.
>> + */
>> +__bpf_kfunc struct prog_test_ret_big bpf_kfunc_call_test_ret_big(void)
>> +{
>> + struct prog_test_ret_big r = { .a = 1, .b = 2, .c = 3 };
>> +
>> + return r;
>> +}
> The comment has two issues. First, it references "the comment on
> bpf_kfunc_call_test_i128() above", but there is no comment on
> bpf_kfunc_call_test_i128(). That function appears without explanation a
> few lines above.
Okay, we can remove 'See the comment on bpf_kfunc_call_test_i128() above'.
>
> Second, "with no arguments there is nothing for the sret pointer to
> displace" is not why this function is safe without an arch guard. On
> SysV x86-64, AAPCS64, and riscv64 LP64D, a 24-byte struct return is
> returned via memory through a hidden pointer passed in the first
> argument register.
>
> With no declared arguments, the hidden sret pointer would still be taken
> from the first argument register (BPF R1), which for this kfunc is never
> set up. But that is not what makes this safe: bpf_add_kfunc_call() calls
> btf_distill_func_proto() which rejects any return larger than 16 bytes
> with "The function %s return type %s is unsupported."
>
> The function is unreachable from BPF on every architecture, which is
> what aggregate_ret_kfunc_too_big_fail asserts. The test in
> progs/aggregate_ret_kfunc.c states this correct reason; the comment here
> states a different one.
Okay, I may just remove the above comments.
>
>
> ---
> AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
> See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
>
> CI run summary: https://github.com/kernel-patches/bpf/actions/runs/31995069373
next prev parent reply other threads:[~2026-08-17 17:12 UTC|newest]
Thread overview: 31+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-17 4:21 [PATCH bpf-next v6 00/10] bpf: Support aggregate return values up to 16 bytes Yonghong Song
2026-08-17 4:21 ` [PATCH bpf-next v6 01/10] bpf: Factor check_global_ret_scalar_reg() out of the global return check Yonghong Song
2026-08-17 4:21 ` [PATCH bpf-next v6 02/10] bpf: Add helpers to describe the R0:R2 return register pair Yonghong Song
2026-08-17 5:17 ` bot+bpf-ci
2026-08-17 15:20 ` Yonghong Song
2026-08-17 4:21 ` [PATCH bpf-next v6 03/10] bpf: Wire up JIT support for 16-byte kfunc returns Yonghong Song
2026-08-17 4:37 ` sashiko-bot
2026-08-17 15:25 ` Yonghong Song
2026-08-17 4:22 ` [PATCH bpf-next v6 04/10] bpf: Handle R2 as a return register in precision backtracking Yonghong Song
2026-08-17 5:17 ` bot+bpf-ci
2026-08-17 15:27 ` Yonghong Song
2026-08-17 4:22 ` [PATCH bpf-next v6 05/10] bpf: Account R2 of register-pair returns in live register analysis Yonghong Song
2026-08-17 4:43 ` sashiko-bot
2026-08-17 15:40 ` Yonghong Song
2026-08-17 4:22 ` [PATCH bpf-next v6 06/10] bpf: Add verifier support for 16-byte returns in R0:R2 Yonghong Song
2026-08-17 4:42 ` sashiko-bot
2026-08-17 15:46 ` Yonghong Song
2026-08-17 5:17 ` bot+bpf-ci
2026-08-17 16:02 ` Yonghong Song
2026-08-17 4:22 ` [PATCH bpf-next v6 07/10] bpf: Enable aggregate return types up to 16 bytes Yonghong Song
2026-08-17 4:42 ` sashiko-bot
2026-08-17 16:12 ` Yonghong Song
2026-08-17 4:22 ` [PATCH bpf-next v6 08/10] selftests/bpf: Add C tests for 16-byte returns in R0:R2 Yonghong Song
2026-08-17 4:45 ` sashiko-bot
2026-08-17 16:32 ` Yonghong Song
2026-08-17 4:22 ` [PATCH bpf-next v6 09/10] selftests/bpf: Add inline-asm and subprog tests for R0:R2 returns Yonghong Song
2026-08-17 4:42 ` sashiko-bot
2026-08-17 16:53 ` Yonghong Song
2026-08-17 5:17 ` bot+bpf-ci
2026-08-17 17:12 ` Yonghong Song [this message]
2026-08-17 4:22 ` [PATCH bpf-next v6 10/10] Documentation/bpf: Document up to 16-byte kfunc return values in R0:R2 Yonghong Song
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=44048a6b-764e-4d35-8276-938682b899db@linux.dev \
--to=yonghong.song@linux.dev \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bot+bpf-ci@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=clm@meta.com \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=ihor.solodrai@linux.dev \
--cc=kernel-team@fb.com \
--cc=martin.lau@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.