* [PATCH bpf-next v2 0/3] Add BPF Exceptions support for RISC-V
@ 2026-06-28 8:17 Varun R Mallya
2026-06-28 8:17 ` [PATCH bpf-next v2 1/3] riscv: stacktrace: Implement arch_bpf_stack_walk() for BPF Varun R Mallya
` (2 more replies)
0 siblings, 3 replies; 9+ messages in thread
From: Varun R Mallya @ 2026-06-28 8:17 UTC (permalink / raw)
To: pjw, palmer, aou, ast, daniel, andrii, eddyz87, memxor, bjorn,
pulehui
Cc: alex, martin.lau, song, yonghong.song, jolsa, emil, puranjay,
shuah, linux-riscv, linux-kernel, bpf, linux-kselftest,
varunrmallya
This patchset aims to add BPF exceptions supports for riscv64 by
implementing the arch_bpf_stack_walk function and then updating the
prologue and epilogue for BPF JIT on riscv. Also,
bpf_jit_supports_exceptions() returns true now so that the verifier does
not reject programs containing BPF exceptions on riscv64.
On riscv the unwinder used by arch_bpf_stack_walk() is the frame-pointer
unwinder, so exception support is gated on CONFIG_FRAME_POINTER.
In the prologue and epilogue of the RISC-V JIT, I saved the
return address and then the frame-pointer according to [1]. Also,
according to [2], s0 to s11 are callee saved registers, which is why a
new array (rv_exception_csave_regs) has been created to save these
registers which contains all the required registers along with the frame
pointer as well as return address.
The following demonstrates that all the selftests for BPF exceptions
apart from the ones that mix bpf-to-bpf calls and tailcalls pass.
This patch was tested using vmtest.sh to run the selftests.
test_exceptions_success:PASS:exceptions__open 0 nsec
libbpf: prog 'exception_tail_call': BPF program load failed: -EINVAL
libbpf: prog 'exception_tail_call': -- BEGIN PROG LOAD LOG --
0: R1=ctx() R10=fp0
; volatile int ret = 0; @ exceptions.c:106
0: (b4) w2 = 0 ; R2=0
1: (63) *(u32 *)(r10 -4) = r2 ; R2=0 R10=fp0
; ret = exception_tail_call_subprog(ctx); @ exceptions.c:108
2: (85) call pc+4
caller:
R10=fp0
callee:
frame1: R1=ctx() R2=0 R10=fp0
7: frame1: R1=ctx() R10=fp0
; int exception_tail_call_subprog(struct __sk_buff *ctx) @ exceptions.c:96
7: (bf) r6 = r1 ; frame1: R1=ctx() R6=ctx()
; volatile int ret = 10; @ exceptions.c:98
8: (b4) w1 = 10 ; frame1: R1=10
9: (63) *(u32 *)(r10 -4) = r1 ; frame1: R1=10 R10=fp0 fp-8=mmmm????
; asm volatile("r1 = %[ctx]\n\t" @ bpf_helpers.h:169
10: (18) r7 = 0xff60000080df1800 ; frame1: R7=map_ptr(map=jmp_table,ks=4,vs=4)
12: (bf) r1 = r6 ; frame1: R1=ctx() R6=ctx()
13: (bf) r2 = r7 ; frame1: R2=map_ptr(map=jmp_table,ks=4,vs=4) R7=map_ptr(map=jmp_table,ks=4,vs=4)
14: (b7) r3 = 0 ; frame1: R3=0
15: (85) call bpf_tail_call#12
mixing of tail_calls and bpf-to-bpf calls is not supported
processed 11 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
-- END PROG LOAD LOG --
libbpf: prog 'exception_tail_call': failed to load: -EINVAL
libbpf: failed to load object 'exceptions'
libbpf: failed to load BPF skeleton 'exceptions': -EINVAL
test_exceptions_success:FAIL:exceptions__load unexpected error: -22 (errno 22)
tester_init:PASS:tester_log_buf 0 nsec
process_subtest:PASS:obj_open_mem 0 nsec
process_subtest:PASS:specs_alloc 0 nsec
tester_init:PASS:tester_log_buf 0 nsec
process_subtest:PASS:obj_open_mem 0 nsec
process_subtest:PASS:specs_alloc 0 nsec
#113/1 exceptions/reject_exception_cb_type_1:OK
#113/2 exceptions/reject_exception_cb_type_2:OK
#113/3 exceptions/reject_exception_cb_type_3:OK
#113/4 exceptions/reject_exception_cb_type_4:OK
#113/5 exceptions/reject_exception_cb_type_5:OK
#113/6 exceptions/reject_async_callback_throw:OK
#113/7 exceptions/reject_with_lock:OK
#113/8 exceptions/reject_subprog_with_lock:OK
#113/9 exceptions/reject_with_rcu_read_lock:OK
#113/10 exceptions/reject_subprog_with_rcu_read_lock:OK
#113/11 exceptions/reject_with_rbtree_add_throw:OK
#113/12 exceptions/reject_with_reference:OK
#113/13 exceptions/reject_global_subprog_throw_with_reference:OK
#113/14 exceptions/reject_with_cb_reference:OK
#113/15 exceptions/reject_with_cb:OK
#113/16 exceptions/reject_with_subprog_reference:OK
#113/17 exceptions/reject_throwing_exception_cb:OK
#113/18 exceptions/reject_exception_cb_call_global_func:OK
#113/19 exceptions/reject_exception_cb_call_static_func:OK
#113/20 exceptions/reject_multiple_exception_cb:OK
#113/21 exceptions/reject_exception_throw_cb:OK
#113/22 exceptions/reject_exception_throw_cb_diff:OK
#113/23 exceptions/reject_subprog_rcu_lock_throw:OK
#113/24 exceptions/reject_subprog_throw_preempt_lock:OK
#113/25 exceptions/reject_subprog_throw_irq_lock:OK
#113/26 exceptions/reject_set_exception_cb_bad_ret1:OK
#113/27 exceptions/reject_set_exception_cb_bad_ret2:OK
#113/28 exceptions/reject_out_of_range_global_throw:OK
#113/29 exceptions/check_assert_eq_int_min:OK
#113/30 exceptions/check_assert_eq_int_max:OK
#113/31 exceptions/check_assert_eq_zero:OK
#113/32 exceptions/check_assert_eq_llong_min:OK
#113/33 exceptions/check_assert_eq_llong_max:OK
#113/34 exceptions/check_assert_lt_pos:OK
#113/35 exceptions/check_assert_lt_zero:OK
#113/36 exceptions/check_assert_lt_neg:OK
#113/37 exceptions/check_assert_le_pos:OK
#113/38 exceptions/check_assert_le_zero:OK
#113/39 exceptions/check_assert_le_neg:OK
#113/40 exceptions/check_assert_gt_pos:OK
#113/41 exceptions/check_assert_gt_zero:OK
#113/42 exceptions/check_assert_gt_neg:OK
#113/43 exceptions/check_assert_ge_pos:OK
#113/44 exceptions/check_assert_ge_zero:OK
#113/45 exceptions/check_assert_ge_neg:OK
#113/46 exceptions/check_assert_range_s64:OK
#113/47 exceptions/check_assert_range_u64:OK
#113/48 exceptions/check_assert_single_range_s64:OK
#113/49 exceptions/check_assert_single_range_u64:OK
#113/50 exceptions/check_assert_generic:OK
#113/51 exceptions/check_assert_with_return:OK
#113 exceptions:FAIL
All error logs:
test_exceptions_success:PASS:exceptions__open 0 nsec
libbpf: prog 'exception_tail_call': BPF program load failed: -EINVAL
libbpf: prog 'exception_tail_call': -- BEGIN PROG LOAD LOG --
0: R1=ctx() R10=fp0
; volatile int ret = 0; @ exceptions.c:106
0: (b4) w2 = 0 ; R2=0
1: (63) *(u32 *)(r10 -4) = r2 ; R2=0 R10=fp0
; ret = exception_tail_call_subprog(ctx); @ exceptions.c:108
2: (85) call pc+4
caller:
R10=fp0
callee:
frame1: R1=ctx() R2=0 R10=fp0
7: frame1: R1=ctx() R10=fp0
; int exception_tail_call_subprog(struct __sk_buff *ctx) @ exceptions.c:96
7: (bf) r6 = r1 ; frame1: R1=ctx() R6=ctx()
; volatile int ret = 10; @ exceptions.c:98
8: (b4) w1 = 10 ; frame1: R1=10
9: (63) *(u32 *)(r10 -4) = r1 ; frame1: R1=10 R10=fp0 fp-8=mmmm????
; asm volatile("r1 = %[ctx]\n\t" @ bpf_helpers.h:169
10: (18) r7 = 0xff60000080df1800 ; frame1: R7=map_ptr(map=jmp_table,ks=4,vs=4)
12: (bf) r1 = r6 ; frame1: R1=ctx() R6=ctx()
13: (bf) r2 = r7 ; frame1: R2=map_ptr(map=jmp_table,ks=4,vs=4) R7=map_ptr(map=jmp_table,ks=4,vs=4)
14: (b7) r3 = 0 ; frame1: R3=0
15: (85) call bpf_tail_call#12
mixing of tail_calls and bpf-to-bpf calls is not supported
processed 11 insns (limit 1000000) max_states_per_insn 0 total_states 0 peak_states 0 mark_read 0
-- END PROG LOAD LOG --
libbpf: prog 'exception_tail_call': failed to load: -EINVAL
libbpf: failed to load object 'exceptions'
libbpf: failed to load BPF skeleton 'exceptions': -EINVAL
test_exceptions_success:FAIL:exceptions__load unexpected error: -22 (errno 22)
Changes in v1->v2:
v1: https://lore.kernel.org/bpf/20260621144259.288135-1-varunrmallya@gmail.com/
- resolve the ftrace trampoline address with ftrace_graph_ret_addr()
inside arch_bpf_stack_walk()
- Remove duplicated code in bpf_jit_build_prologue()
[1]: https://riscv-non-isa.github.io/riscv-elf-psabi-doc/#_frame_pointer_convention
[2]: https://riscv-non-isa.github.io/riscv-elf-psabi-doc/#_integer_register_convention
Varun R Mallya (3):
riscv: stacktrace: Implement arch_bpf_stack_walk() for BPF
riscv, bpf: Add support for BPF exceptions
riscv, bpf: Remove BPF exceptions from BPF CI denylist
arch/riscv/kernel/stacktrace.c | 31 +++++
arch/riscv/net/bpf_jit_comp64.c | 115 ++++++++++++++++---
tools/testing/selftests/bpf/DENYLIST.riscv64 | 1 -
3 files changed, 133 insertions(+), 14 deletions(-)
--
2.54.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 1/3] riscv: stacktrace: Implement arch_bpf_stack_walk() for BPF
2026-06-28 8:17 [PATCH bpf-next v2 0/3] Add BPF Exceptions support for RISC-V Varun R Mallya
@ 2026-06-28 8:17 ` Varun R Mallya
2026-06-28 17:26 ` Björn Töpel
2026-06-28 8:17 ` [PATCH bpf-next v2 2/3] riscv, bpf: Add support for BPF exceptions Varun R Mallya
2026-06-28 8:17 ` [PATCH bpf-next v2 3/3] riscv, bpf: Remove BPF exceptions from BPF CI denylist Varun R Mallya
2 siblings, 1 reply; 9+ messages in thread
From: Varun R Mallya @ 2026-06-28 8:17 UTC (permalink / raw)
To: pjw, palmer, aou, ast, daniel, andrii, eddyz87, memxor, bjorn,
pulehui
Cc: alex, martin.lau, song, yonghong.song, jolsa, emil, puranjay,
shuah, linux-riscv, linux-kernel, bpf, linux-kselftest,
varunrmallya
This will be used by bpf_throw() to unwind till the program marked as
exception boundary and run the callback with the stack of the main
program.
This is required for supporting BPF exceptions on RISC-V.
This depends on the frame pointer unwinder, so it is only built under
CONFIG_FRAME_POINTER, else falls back to the weak no-op.
Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
Reviewed-by: Pu Lehui <pulehui@huawei.com>
---
arch/riscv/kernel/stacktrace.c | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
index c7555447149b..64929381bb30 100644
--- a/arch/riscv/kernel/stacktrace.c
+++ b/arch/riscv/kernel/stacktrace.c
@@ -5,6 +5,7 @@
*/
#include <linux/export.h>
+#include <linux/filter.h>
#include <linux/kallsyms.h>
#include <linux/sched.h>
#include <linux/sched/debug.h>
@@ -102,6 +103,36 @@ void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
}
}
+void notrace arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp),
+ void *cookie)
+{
+ unsigned long fp, sp, pc;
+ int graph_idx = 0;
+
+ fp = (unsigned long)__builtin_frame_address(0);
+ sp = current_stack_pointer;
+ pc = (unsigned long)arch_bpf_stack_walk;
+
+ for (;;) {
+ struct stackframe *frame;
+
+ if (unlikely(!__kernel_text_address(pc)))
+ break;
+ /* pc belongs to the function whose frame pointer is fp */
+ if (!consume_fn(cookie, pc, sp, fp))
+ break;
+ if (unlikely(!fp_is_valid(fp, sp)))
+ break;
+
+ frame = (struct stackframe *)fp - 1;
+ sp = fp;
+ fp = READ_ONCE_TASK_STACK(current, frame->fp);
+ pc = READ_ONCE_TASK_STACK(current, frame->ra);
+ pc = ftrace_graph_ret_addr(current, &graph_idx, pc,
+ &frame->ra);
+ }
+}
+
#else /* !CONFIG_FRAME_POINTER */
void notrace walk_stackframe(struct task_struct *task,
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 2/3] riscv, bpf: Add support for BPF exceptions
2026-06-28 8:17 [PATCH bpf-next v2 0/3] Add BPF Exceptions support for RISC-V Varun R Mallya
2026-06-28 8:17 ` [PATCH bpf-next v2 1/3] riscv: stacktrace: Implement arch_bpf_stack_walk() for BPF Varun R Mallya
@ 2026-06-28 8:17 ` Varun R Mallya
2026-06-28 17:50 ` Björn Töpel
2026-06-28 8:17 ` [PATCH bpf-next v2 3/3] riscv, bpf: Remove BPF exceptions from BPF CI denylist Varun R Mallya
2 siblings, 1 reply; 9+ messages in thread
From: Varun R Mallya @ 2026-06-28 8:17 UTC (permalink / raw)
To: pjw, palmer, aou, ast, daniel, andrii, eddyz87, memxor, bjorn,
pulehui
Cc: alex, martin.lau, song, yonghong.song, jolsa, emil, puranjay,
shuah, linux-riscv, linux-kernel, bpf, linux-kselftest,
varunrmallya
Add the JIT support required for BPF exceptions (bpf_throw()) on riscv64.
Two kinds of program need special prologue/epilogue handling:
- A program acting as an exception boundary must save the full set of
riscv callee-saved GP registers (ra, s0-s11), not just the ones it
happens to clobber, so that the exception callback can restore the
state that was live at the boundary. ra and fp are stored first so
the saved ra/fp pair forms a valid stackframe record for the
unwinder.
- The exception callback (exception_cb) does not allocate its own
frame. It reuses the boundary program's frame, whose frame pointer
is passed in a2, by setting SP = FP - stack_adjust. This lines the
epilogue's loads up with the registers the boundary saved, so both
paths restore the same order.
Wire up bpf_jit_support to be true only when CONFIG_FRAME_POINTER is
enabled.
Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
Reviewed-by: Pu Lehui <pulehui@huawei.com>
---
arch/riscv/net/bpf_jit_comp64.c | 115 ++++++++++++++++++++++++++++----
1 file changed, 102 insertions(+), 13 deletions(-)
diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
index c03c1de16b79..c6e2f852e854 100644
--- a/arch/riscv/net/bpf_jit_comp64.c
+++ b/arch/riscv/net/bpf_jit_comp64.c
@@ -56,6 +56,30 @@ static const int pt_regmap[] = {
[RV_REG_T0] = offsetof(struct pt_regs, t0),
};
+/*
+ * Full set of RISC-V callee-saved GP registers (ra, s0-s11) saved by a program
+ * acting as an exception boundary, in the order they are stored on the stack.
+ * RA and FP come first so the saved ra/fp pair forms a valid stackframe record
+ * at [FP-8]/[FP-16] for the unwinder. The exception callback reuses the
+ * boundary program's frame and restores this same set in its epilogue, so both
+ * paths must agree on the contents and ordering of this list.
+ */
+static const int rv_exception_csave_regs[] = {
+ RV_REG_RA,
+ RV_REG_FP,
+ RV_REG_S1,
+ RV_REG_S2,
+ RV_REG_S3,
+ RV_REG_S4,
+ RV_REG_S5,
+ RV_REG_S6,
+ RV_REG_S7,
+ RV_REG_S8,
+ RV_REG_S9,
+ RV_REG_S10,
+ RV_REG_S11,
+};
+
enum {
RV_CTX_F_SEEN_TAIL_CALL = 0,
RV_CTX_F_SEEN_CALL = RV_REG_RA,
@@ -231,6 +255,22 @@ static void emit_imm(u8 rd, s64 val, struct rv_jit_context *ctx)
static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
{
int stack_adjust = ctx->stack_size, store_offset = stack_adjust - 8;
+ struct bpf_prog_aux *aux = ctx->prog->aux;
+ int i;
+
+ if (aux->exception_boundary || aux->exception_cb) {
+ /*
+ * An exception boundary saved the full callee-saved register
+ * set and the exception callback restores it from the boundary's
+ * frame. Both restore the same fixed set, in the same order it
+ * was stored by bpf_jit_build_prologue().
+ */
+ for (i = 0; i < ARRAY_SIZE(rv_exception_csave_regs); i++) {
+ emit_ld(rv_exception_csave_regs[i], store_offset, RV_REG_SP, ctx);
+ store_offset -= 8;
+ }
+ goto epilogue_tail;
+ }
if (seen_reg(RV_REG_RA, ctx)) {
emit_ld(RV_REG_RA, store_offset, RV_REG_SP, ctx);
@@ -267,6 +307,7 @@ static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
store_offset -= 8;
}
+epilogue_tail:
emit_addi(RV_REG_SP, RV_REG_SP, stack_adjust, ctx);
/* Set return value. */
if (!is_tail_call)
@@ -2002,11 +2043,61 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
{
int i, stack_adjust = 0, store_offset, bpf_stack_adjust;
+ struct bpf_prog_aux *aux = ctx->prog->aux;
bpf_stack_adjust = round_up(ctx->prog->aux->stack_depth, STACK_ALIGN);
if (bpf_stack_adjust)
mark_fp(ctx);
+ /* emit kcfi type preamble immediately before the first insn */
+ emit_kcfi(is_subprog ? cfi_bpf_subprog_hash : cfi_bpf_hash, ctx);
+
+ /* nops reserved for auipc+jalr pair */
+ for (i = 0; i < RV_FENTRY_NINSNS; i++)
+ emit(rv_nop(), ctx);
+
+ /* First instruction is always setting the tail-call-counter
+ * (TCC) register. This instruction is skipped for tail calls.
+ * Force using a 4-byte (non-compressed) instruction.
+ */
+ emit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);
+
+ if (aux->exception_boundary || aux->exception_cb) {
+ /*
+ * A program acting as an exception boundary saves the full set
+ * of riscv callee saved registers (ra, s0-s11).
+ */
+ stack_adjust = round_up(ARRAY_SIZE(rv_exception_csave_regs) * 8,
+ STACK_ALIGN);
+ stack_adjust += bpf_stack_adjust;
+ store_offset = stack_adjust - 8;
+
+ if (!aux->exception_cb && aux->exception_boundary) {
+ /*
+ * Boundary program: allocate the frame and save the
+ * full callee-saved set, capturing the caller's values.
+ */
+ emit_addi(RV_REG_SP, RV_REG_SP, -stack_adjust, ctx);
+ for (i = 0; i < ARRAY_SIZE(rv_exception_csave_regs); i++) {
+ emit_sd(RV_REG_SP, store_offset,
+ rv_exception_csave_regs[i], ctx);
+ store_offset -= 8;
+ }
+ emit_addi(RV_REG_FP, RV_REG_SP, stack_adjust, ctx);
+ } else {
+ /*
+ * Exception callback, reuse the boundary program's
+ * frame, whose frame pointer is passed in a2. Setting
+ * SP = FP - stack_adjust lines the epilogue's loads up
+ * with the registers the boundary saved.
+ */
+ emit_mv(RV_REG_FP, RV_REG_A2, ctx);
+ emit_addi(RV_REG_SP, RV_REG_FP, -stack_adjust, ctx);
+ }
+
+ goto tail_setup;
+ }
+
if (seen_reg(RV_REG_RA, ctx))
stack_adjust += 8;
stack_adjust += 8; /* RV_REG_FP */
@@ -2030,19 +2121,6 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
store_offset = stack_adjust - 8;
- /* emit kcfi type preamble immediately before the first insn */
- emit_kcfi(is_subprog ? cfi_bpf_subprog_hash : cfi_bpf_hash, ctx);
-
- /* nops reserved for auipc+jalr pair */
- for (i = 0; i < RV_FENTRY_NINSNS; i++)
- emit(rv_nop(), ctx);
-
- /* First instruction is always setting the tail-call-counter
- * (TCC) register. This instruction is skipped for tail calls.
- * Force using a 4-byte (non-compressed) instruction.
- */
- emit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);
-
emit_addi(RV_REG_SP, RV_REG_SP, -stack_adjust, ctx);
if (seen_reg(RV_REG_RA, ctx)) {
@@ -2082,6 +2160,7 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
emit_addi(RV_REG_FP, RV_REG_SP, stack_adjust, ctx);
+tail_setup:
if (bpf_stack_adjust)
emit_addi(RV_REG_S5, RV_REG_SP, bpf_stack_adjust, ctx);
@@ -2157,3 +2236,13 @@ bool bpf_jit_supports_fsession(void)
{
return true;
}
+
+bool bpf_jit_supports_exceptions(void)
+{
+ /*
+ * bpf_throw() unwinds by walking the frame-pointer chain from inside
+ * the kernel back into the BPF frames (see arch_bpf_stack_walk()), so
+ * exceptions require the frame-pointer unwinder to be enabled.
+ */
+ return IS_ENABLED(CONFIG_FRAME_POINTER);
+}
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH bpf-next v2 3/3] riscv, bpf: Remove BPF exceptions from BPF CI denylist
2026-06-28 8:17 [PATCH bpf-next v2 0/3] Add BPF Exceptions support for RISC-V Varun R Mallya
2026-06-28 8:17 ` [PATCH bpf-next v2 1/3] riscv: stacktrace: Implement arch_bpf_stack_walk() for BPF Varun R Mallya
2026-06-28 8:17 ` [PATCH bpf-next v2 2/3] riscv, bpf: Add support for BPF exceptions Varun R Mallya
@ 2026-06-28 8:17 ` Varun R Mallya
2026-06-28 18:00 ` Björn Töpel
2 siblings, 1 reply; 9+ messages in thread
From: Varun R Mallya @ 2026-06-28 8:17 UTC (permalink / raw)
To: pjw, palmer, aou, ast, daniel, andrii, eddyz87, memxor, bjorn,
pulehui
Cc: alex, martin.lau, song, yonghong.song, jolsa, emil, puranjay,
shuah, linux-riscv, linux-kernel, bpf, linux-kselftest,
varunrmallya
This patch removes BPF exceptions from riscv64 denylist on
BPF selftests since support for exceptions has been added now.
Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
Reviewed-by: Pu Lehui <pulehui@huawei.com>
---
tools/testing/selftests/bpf/DENYLIST.riscv64 | 1 -
1 file changed, 1 deletion(-)
diff --git a/tools/testing/selftests/bpf/DENYLIST.riscv64 b/tools/testing/selftests/bpf/DENYLIST.riscv64
index 4fc4dfdde293..9268351ce4c1 100644
--- a/tools/testing/selftests/bpf/DENYLIST.riscv64
+++ b/tools/testing/selftests/bpf/DENYLIST.riscv64
@@ -1,3 +1,2 @@
# riscv64 deny list for BPF CI and local vmtest
-exceptions # JIT does not support exceptions
tailcalls/tailcall_bpf2bpf* # JIT does not support mixing bpf2bpf and tailcalls
--
2.54.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 1/3] riscv: stacktrace: Implement arch_bpf_stack_walk() for BPF
2026-06-28 8:17 ` [PATCH bpf-next v2 1/3] riscv: stacktrace: Implement arch_bpf_stack_walk() for BPF Varun R Mallya
@ 2026-06-28 17:26 ` Björn Töpel
2026-06-29 2:45 ` Pu Lehui
0 siblings, 1 reply; 9+ messages in thread
From: Björn Töpel @ 2026-06-28 17:26 UTC (permalink / raw)
To: Varun R Mallya, pjw, palmer, aou, ast, daniel, andrii, eddyz87,
memxor, pulehui
Cc: alex, martin.lau, song, yonghong.song, jolsa, emil, puranjay,
shuah, linux-riscv, linux-kernel, bpf, linux-kselftest,
varunrmallya
Varun!
Thanks for spending time on getting RV eBPF more feature complete! Sorry
for the slow replies.
Varun R Mallya <varunrmallya@gmail.com> writes:
> This will be used by bpf_throw() to unwind till the program marked as
> exception boundary and run the callback with the stack of the main
> program.
> This is required for supporting BPF exceptions on RISC-V.
> This depends on the frame pointer unwinder, so it is only built under
> CONFIG_FRAME_POINTER, else falls back to the weak no-op.
>
> Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
> Reviewed-by: Pu Lehui <pulehui@huawei.com>
> ---
> arch/riscv/kernel/stacktrace.c | 31 +++++++++++++++++++++++++++++++
> 1 file changed, 31 insertions(+)
>
> diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
> index c7555447149b..64929381bb30 100644
> --- a/arch/riscv/kernel/stacktrace.c
> +++ b/arch/riscv/kernel/stacktrace.c
> @@ -5,6 +5,7 @@
> */
>
> #include <linux/export.h>
> +#include <linux/filter.h>
> #include <linux/kallsyms.h>
> #include <linux/sched.h>
> #include <linux/sched/debug.h>
> @@ -102,6 +103,36 @@ void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
> }
> }
>
> +void notrace arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp),
> + void *cookie)
> +{
> + unsigned long fp, sp, pc;
> + int graph_idx = 0;
> +
> + fp = (unsigned long)__builtin_frame_address(0);
> + sp = current_stack_pointer;
> + pc = (unsigned long)arch_bpf_stack_walk;
> +
> + for (;;) {
> + struct stackframe *frame;
> +
> + if (unlikely(!__kernel_text_address(pc)))
> + break;
> + /* pc belongs to the function whose frame pointer is fp */
> + if (!consume_fn(cookie, pc, sp, fp))
> + break;
> + if (unlikely(!fp_is_valid(fp, sp)))
> + break;
> +
> + frame = (struct stackframe *)fp - 1;
> + sp = fp;
> + fp = READ_ONCE_TASK_STACK(current, frame->fp);
> + pc = READ_ONCE_TASK_STACK(current, frame->ra);
> + pc = ftrace_graph_ret_addr(current, &graph_idx, pc,
> + &frame->ra);
> + }
> +}
We don't need more stack unwinders. Can you see if you can extend
walk_stackframe() with "cookie" to match BPF's needs? Have a look at
what arm64 does.
Sashiko had a good point about ftrace_graph_ret_addr() -- now what about
kretprobe? Do we need to take that in consideration as well?
Thanks,
Björn
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 2/3] riscv, bpf: Add support for BPF exceptions
2026-06-28 8:17 ` [PATCH bpf-next v2 2/3] riscv, bpf: Add support for BPF exceptions Varun R Mallya
@ 2026-06-28 17:50 ` Björn Töpel
2026-06-29 3:01 ` Pu Lehui
0 siblings, 1 reply; 9+ messages in thread
From: Björn Töpel @ 2026-06-28 17:50 UTC (permalink / raw)
To: Varun R Mallya, pjw, palmer, aou, ast, daniel, andrii, eddyz87,
memxor, pulehui
Cc: alex, martin.lau, song, yonghong.song, jolsa, emil, puranjay,
shuah, linux-riscv, linux-kernel, bpf, linux-kselftest,
varunrmallya
Varun R Mallya <varunrmallya@gmail.com> writes:
> Add the JIT support required for BPF exceptions (bpf_throw()) on riscv64.
>
> Two kinds of program need special prologue/epilogue handling:
>
> - A program acting as an exception boundary must save the full set of
> riscv callee-saved GP registers (ra, s0-s11), not just the ones it
> happens to clobber, so that the exception callback can restore the
> state that was live at the boundary. ra and fp are stored first so
> the saved ra/fp pair forms a valid stackframe record for the
> unwinder.
>
> - The exception callback (exception_cb) does not allocate its own
> frame. It reuses the boundary program's frame, whose frame pointer
> is passed in a2, by setting SP = FP - stack_adjust. This lines the
> epilogue's loads up with the registers the boundary saved, so both
> paths restore the same order.
>
> Wire up bpf_jit_support to be true only when CONFIG_FRAME_POINTER is
> enabled.
>
> Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
> Reviewed-by: Pu Lehui <pulehui@huawei.com>
> ---
> arch/riscv/net/bpf_jit_comp64.c | 115 ++++++++++++++++++++++++++++----
> 1 file changed, 102 insertions(+), 13 deletions(-)
>
> diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
> index c03c1de16b79..c6e2f852e854 100644
> --- a/arch/riscv/net/bpf_jit_comp64.c
> +++ b/arch/riscv/net/bpf_jit_comp64.c
> @@ -56,6 +56,30 @@ static const int pt_regmap[] = {
> [RV_REG_T0] = offsetof(struct pt_regs, t0),
> };
>
> +/*
> + * Full set of RISC-V callee-saved GP registers (ra, s0-s11) saved by a program
> + * acting as an exception boundary, in the order they are stored on the stack.
> + * RA and FP come first so the saved ra/fp pair forms a valid stackframe record
> + * at [FP-8]/[FP-16] for the unwinder. The exception callback reuses the
> + * boundary program's frame and restores this same set in its epilogue, so both
> + * paths must agree on the contents and ordering of this list.
> + */
nit: Please use netdev style multi-line comments (/* Blah, vs /*\n)
> +static const int rv_exception_csave_regs[] = {
> + RV_REG_RA,
> + RV_REG_FP,
> + RV_REG_S1,
> + RV_REG_S2,
> + RV_REG_S3,
> + RV_REG_S4,
> + RV_REG_S5,
> + RV_REG_S6,
> + RV_REG_S7,
> + RV_REG_S8,
> + RV_REG_S9,
> + RV_REG_S10,
> + RV_REG_S11,
> +};
> +
> enum {
> RV_CTX_F_SEEN_TAIL_CALL = 0,
> RV_CTX_F_SEEN_CALL = RV_REG_RA,
> @@ -231,6 +255,22 @@ static void emit_imm(u8 rd, s64 val, struct rv_jit_context *ctx)
> static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
> {
> int stack_adjust = ctx->stack_size, store_offset = stack_adjust - 8;
> + struct bpf_prog_aux *aux = ctx->prog->aux;
> + int i;
> +
> + if (aux->exception_boundary || aux->exception_cb) {
> + /*
nit: Comment again.
> + * An exception boundary saved the full callee-saved register
> + * set and the exception callback restores it from the boundary's
> + * frame. Both restore the same fixed set, in the same order it
> + * was stored by bpf_jit_build_prologue().
> + */
> + for (i = 0; i < ARRAY_SIZE(rv_exception_csave_regs); i++) {
> + emit_ld(rv_exception_csave_regs[i], store_offset, RV_REG_SP, ctx);
> + store_offset -= 8;
> + }
> + goto epilogue_tail;
> + }
>
> if (seen_reg(RV_REG_RA, ctx)) {
> emit_ld(RV_REG_RA, store_offset, RV_REG_SP, ctx);
> @@ -267,6 +307,7 @@ static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
> store_offset -= 8;
> }
>
> +epilogue_tail:
> emit_addi(RV_REG_SP, RV_REG_SP, stack_adjust, ctx);
> /* Set return value. */
> if (!is_tail_call)
> @@ -2002,11 +2043,61 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
> void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
> {
> int i, stack_adjust = 0, store_offset, bpf_stack_adjust;
> + struct bpf_prog_aux *aux = ctx->prog->aux;
>
> bpf_stack_adjust = round_up(ctx->prog->aux->stack_depth, STACK_ALIGN);
> if (bpf_stack_adjust)
> mark_fp(ctx);
>
> + /* emit kcfi type preamble immediately before the first insn */
> + emit_kcfi(is_subprog ? cfi_bpf_subprog_hash : cfi_bpf_hash, ctx);
> +
> + /* nops reserved for auipc+jalr pair */
> + for (i = 0; i < RV_FENTRY_NINSNS; i++)
> + emit(rv_nop(), ctx);
> +
> + /* First instruction is always setting the tail-call-counter
> + * (TCC) register. This instruction is skipped for tail calls.
> + * Force using a 4-byte (non-compressed) instruction.
> + */
> + emit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);
> +
> + if (aux->exception_boundary || aux->exception_cb) {
> + /*
> + * A program acting as an exception boundary saves the full set
> + * of riscv callee saved registers (ra, s0-s11).
> + */
> + stack_adjust = round_up(ARRAY_SIZE(rv_exception_csave_regs) * 8,
> + STACK_ALIGN);
> + stack_adjust += bpf_stack_adjust;
> + store_offset = stack_adjust - 8;
> +
> + if (!aux->exception_cb && aux->exception_boundary) {
> + /*
> + * Boundary program: allocate the frame and save the
> + * full callee-saved set, capturing the caller's values.
> + */
> + emit_addi(RV_REG_SP, RV_REG_SP, -stack_adjust, ctx);
> + for (i = 0; i < ARRAY_SIZE(rv_exception_csave_regs); i++) {
> + emit_sd(RV_REG_SP, store_offset,
> + rv_exception_csave_regs[i], ctx);
> + store_offset -= 8;
> + }
> + emit_addi(RV_REG_FP, RV_REG_SP, stack_adjust, ctx);
> + } else {
> + /*
> + * Exception callback, reuse the boundary program's
> + * frame, whose frame pointer is passed in a2. Setting
> + * SP = FP - stack_adjust lines the epilogue's loads up
> + * with the registers the boundary saved.
> + */
> + emit_mv(RV_REG_FP, RV_REG_A2, ctx);
> + emit_addi(RV_REG_SP, RV_REG_FP, -stack_adjust, ctx);
> + }
> +
> + goto tail_setup;
> + }
> +
This function is getting large... I tend do forget details, so having it
in smaller helpers would be good. Let's try to refactor a bit.
The special case is really just:
* exception boundary: allocate frame and save full layout
* exception_cb: reuse boundary FP from a2 and derive SP from it
* both: restore the same fixed layout
So helpers like is_exception_prog(), exception_stack_adjust(),
emit_exception_boundary_prologue(), emit_exception_cb_prologue(), and
emit_exception_restore() would make the prologue/epilogue changes much
easier to review and also reduce churn in bpf_jit_build_prologue().
> if (seen_reg(RV_REG_RA, ctx))
> stack_adjust += 8;
> stack_adjust += 8; /* RV_REG_FP */
> @@ -2030,19 +2121,6 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
>
> store_offset = stack_adjust - 8;
>
> - /* emit kcfi type preamble immediately before the first insn */
> - emit_kcfi(is_subprog ? cfi_bpf_subprog_hash : cfi_bpf_hash, ctx);
> -
> - /* nops reserved for auipc+jalr pair */
> - for (i = 0; i < RV_FENTRY_NINSNS; i++)
> - emit(rv_nop(), ctx);
> -
> - /* First instruction is always setting the tail-call-counter
> - * (TCC) register. This instruction is skipped for tail calls.
> - * Force using a 4-byte (non-compressed) instruction.
> - */
> - emit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);
> -
> emit_addi(RV_REG_SP, RV_REG_SP, -stack_adjust, ctx);
>
> if (seen_reg(RV_REG_RA, ctx)) {
> @@ -2082,6 +2160,7 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
>
> emit_addi(RV_REG_FP, RV_REG_SP, stack_adjust, ctx);
>
> +tail_setup:
Hmm, thinking more about it, maybe folding the "normal_prologue()" in as
pre-commit, so we can avoid more gotos.
| if (is_exception_prog(aux))
| emit_exception_restore(ctx, stack_adjust);
| else
| emit_normal_restore(ctx, stack_adjust);
|
| emit_addi(RV_REG_SP, RV_REG_SP, stack_adjust, ctx);
| ...
or smth.
> if (bpf_stack_adjust) emit_addi(RV_REG_S5, RV_REG_SP,
> bpf_stack_adjust, ctx);
>
> @@ -2157,3 +2236,13 @@ bool bpf_jit_supports_fsession(void)
> {
> return true;
> }
> +
> +bool bpf_jit_supports_exceptions(void)
> +{
> + /*
> + * bpf_throw() unwinds by walking the frame-pointer chain from inside
> + * the kernel back into the BPF frames (see arch_bpf_stack_walk()), so
> + * exceptions require the frame-pointer unwinder to be enabled.
> + */
nit: Comments...
Thanks,
Björn
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 3/3] riscv, bpf: Remove BPF exceptions from BPF CI denylist
2026-06-28 8:17 ` [PATCH bpf-next v2 3/3] riscv, bpf: Remove BPF exceptions from BPF CI denylist Varun R Mallya
@ 2026-06-28 18:00 ` Björn Töpel
0 siblings, 0 replies; 9+ messages in thread
From: Björn Töpel @ 2026-06-28 18:00 UTC (permalink / raw)
To: Varun R Mallya, pjw, palmer, aou, ast, daniel, andrii, eddyz87,
memxor, pulehui
Cc: alex, martin.lau, song, yonghong.song, jolsa, emil, puranjay,
shuah, linux-riscv, linux-kernel, bpf, linux-kselftest,
varunrmallya
Varun R Mallya <varunrmallya@gmail.com> writes:
> This patch removes BPF exceptions from riscv64 denylist on
> BPF selftests since support for exceptions has been added now.
>
> Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
> Reviewed-by: Pu Lehui <pulehui@huawei.com>
> ---
> tools/testing/selftests/bpf/DENYLIST.riscv64 | 1 -
> 1 file changed, 1 deletion(-)
>
> diff --git a/tools/testing/selftests/bpf/DENYLIST.riscv64 b/tools/testing/selftests/bpf/DENYLIST.riscv64
> index 4fc4dfdde293..9268351ce4c1 100644
> --- a/tools/testing/selftests/bpf/DENYLIST.riscv64
> +++ b/tools/testing/selftests/bpf/DENYLIST.riscv64
> @@ -1,3 +1,2 @@
> # riscv64 deny list for BPF CI and local vmtest
> -exceptions # JIT does not support exceptions
Hmm, reading your cover the kselftest run still says "#113
exceptions:FAIL", so from a CI perspective this is not much help. ;-)
Can we restructure/split so that we're not bitten by the lack of
BPF-to-BPF calls and tail calls?
Thanks,
Björn
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 1/3] riscv: stacktrace: Implement arch_bpf_stack_walk() for BPF
2026-06-28 17:26 ` Björn Töpel
@ 2026-06-29 2:45 ` Pu Lehui
0 siblings, 0 replies; 9+ messages in thread
From: Pu Lehui @ 2026-06-29 2:45 UTC (permalink / raw)
To: Björn Töpel, Varun R Mallya, pjw, palmer, aou, ast,
daniel, andrii, eddyz87, memxor
Cc: alex, martin.lau, song, yonghong.song, jolsa, emil, puranjay,
shuah, linux-riscv, linux-kernel, bpf, linux-kselftest
On 2026/6/29 1:26, Björn Töpel wrote:
> Varun!
>
> Thanks for spending time on getting RV eBPF more feature complete! Sorry
> for the slow replies.
>
> Varun R Mallya <varunrmallya@gmail.com> writes:
>
>> This will be used by bpf_throw() to unwind till the program marked as
>> exception boundary and run the callback with the stack of the main
>> program.
>> This is required for supporting BPF exceptions on RISC-V.
>> This depends on the frame pointer unwinder, so it is only built under
>> CONFIG_FRAME_POINTER, else falls back to the weak no-op.
>>
>> Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
>> Reviewed-by: Pu Lehui <pulehui@huawei.com>
>> ---
>> arch/riscv/kernel/stacktrace.c | 31 +++++++++++++++++++++++++++++++
>> 1 file changed, 31 insertions(+)
>>
>> diff --git a/arch/riscv/kernel/stacktrace.c b/arch/riscv/kernel/stacktrace.c
>> index c7555447149b..64929381bb30 100644
>> --- a/arch/riscv/kernel/stacktrace.c
>> +++ b/arch/riscv/kernel/stacktrace.c
>> @@ -5,6 +5,7 @@
>> */
>>
>> #include <linux/export.h>
>> +#include <linux/filter.h>
>> #include <linux/kallsyms.h>
>> #include <linux/sched.h>
>> #include <linux/sched/debug.h>
>> @@ -102,6 +103,36 @@ void notrace walk_stackframe(struct task_struct *task, struct pt_regs *regs,
>> }
>> }
>>
>> +void notrace arch_bpf_stack_walk(bool (*consume_fn)(void *cookie, u64 ip, u64 sp, u64 bp),
>> + void *cookie)
>> +{
>> + unsigned long fp, sp, pc;
>> + int graph_idx = 0;
>> +
>> + fp = (unsigned long)__builtin_frame_address(0);
>> + sp = current_stack_pointer;
>> + pc = (unsigned long)arch_bpf_stack_walk;
>> +
>> + for (;;) {
>> + struct stackframe *frame;
>> +
>> + if (unlikely(!__kernel_text_address(pc)))
>> + break;
>> + /* pc belongs to the function whose frame pointer is fp */
>> + if (!consume_fn(cookie, pc, sp, fp))
>> + break;
>> + if (unlikely(!fp_is_valid(fp, sp)))
>> + break;
>> +
>> + frame = (struct stackframe *)fp - 1;
>> + sp = fp;
>> + fp = READ_ONCE_TASK_STACK(current, frame->fp);
>> + pc = READ_ONCE_TASK_STACK(current, frame->ra);
>> + pc = ftrace_graph_ret_addr(current, &graph_idx, pc,
>> + &frame->ra);
>> + }
>> +}
>
> We don't need more stack unwinders. Can you see if you can extend
> walk_stackframe() with "cookie" to match BPF's needs? Have a look at
> what arm64 does.
>
> Sashiko had a good point about ftrace_graph_ret_addr() -- now what about
> kretprobe? Do we need to take that in consideration as well?
IIUC, arch_bpf_stack_walk only unwind for bpf prog stack, maybe we could
not need to handle kretprobe.
>
>
> Thanks,
> Björn
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v2 2/3] riscv, bpf: Add support for BPF exceptions
2026-06-28 17:50 ` Björn Töpel
@ 2026-06-29 3:01 ` Pu Lehui
0 siblings, 0 replies; 9+ messages in thread
From: Pu Lehui @ 2026-06-29 3:01 UTC (permalink / raw)
To: Varun R Mallya, Björn Töpel, pjw, palmer, aou, ast,
daniel, andrii, eddyz87, memxor
Cc: alex, martin.lau, song, yonghong.song, jolsa, emil, puranjay,
shuah, linux-riscv, linux-kernel, bpf, linux-kselftest
On 2026/6/29 1:50, Björn Töpel wrote:
> Varun R Mallya <varunrmallya@gmail.com> writes:
>
>> Add the JIT support required for BPF exceptions (bpf_throw()) on riscv64.
>>
>> Two kinds of program need special prologue/epilogue handling:
>>
>> - A program acting as an exception boundary must save the full set of
>> riscv callee-saved GP registers (ra, s0-s11), not just the ones it
>> happens to clobber, so that the exception callback can restore the
>> state that was live at the boundary. ra and fp are stored first so
>> the saved ra/fp pair forms a valid stackframe record for the
>> unwinder.
>>
>> - The exception callback (exception_cb) does not allocate its own
>> frame. It reuses the boundary program's frame, whose frame pointer
>> is passed in a2, by setting SP = FP - stack_adjust. This lines the
>> epilogue's loads up with the registers the boundary saved, so both
>> paths restore the same order.
>>
>> Wire up bpf_jit_support to be true only when CONFIG_FRAME_POINTER is
>> enabled.
>>
>> Signed-off-by: Varun R Mallya <varunrmallya@gmail.com>
>> Reviewed-by: Pu Lehui <pulehui@huawei.com>
not yet Reviewed-by
>> ---
>> arch/riscv/net/bpf_jit_comp64.c | 115 ++++++++++++++++++++++++++++----
>> 1 file changed, 102 insertions(+), 13 deletions(-)
>>
>> diff --git a/arch/riscv/net/bpf_jit_comp64.c b/arch/riscv/net/bpf_jit_comp64.c
>> index c03c1de16b79..c6e2f852e854 100644
>> --- a/arch/riscv/net/bpf_jit_comp64.c
>> +++ b/arch/riscv/net/bpf_jit_comp64.c
>> @@ -56,6 +56,30 @@ static const int pt_regmap[] = {
>> [RV_REG_T0] = offsetof(struct pt_regs, t0),
>> };
>>
>> +/*
>> + * Full set of RISC-V callee-saved GP registers (ra, s0-s11) saved by a program
>> + * acting as an exception boundary, in the order they are stored on the stack.
>> + * RA and FP come first so the saved ra/fp pair forms a valid stackframe record
>> + * at [FP-8]/[FP-16] for the unwinder. The exception callback reuses the
>> + * boundary program's frame and restores this same set in its epilogue, so both
>> + * paths must agree on the contents and ordering of this list.
>> + */
>
> nit: Please use netdev style multi-line comments (/* Blah, vs /*\n)
>
>> +static const int rv_exception_csave_regs[] = {
>> + RV_REG_RA,
>> + RV_REG_FP,
>> + RV_REG_S1,
>> + RV_REG_S2,
>> + RV_REG_S3,
>> + RV_REG_S4,
>> + RV_REG_S5,
>> + RV_REG_S6,
>> + RV_REG_S7,
>> + RV_REG_S8,
>> + RV_REG_S9,
>> + RV_REG_S10,
>> + RV_REG_S11,
>> +};
>> +
>> enum {
>> RV_CTX_F_SEEN_TAIL_CALL = 0,
>> RV_CTX_F_SEEN_CALL = RV_REG_RA,
>> @@ -231,6 +255,22 @@ static void emit_imm(u8 rd, s64 val, struct rv_jit_context *ctx)
>> static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
>> {
>> int stack_adjust = ctx->stack_size, store_offset = stack_adjust - 8;
>> + struct bpf_prog_aux *aux = ctx->prog->aux;
>> + int i;
>> +
>> + if (aux->exception_boundary || aux->exception_cb) {
>> + /*
>
> nit: Comment again.
>
>> + * An exception boundary saved the full callee-saved register
>> + * set and the exception callback restores it from the boundary's
>> + * frame. Both restore the same fixed set, in the same order it
>> + * was stored by bpf_jit_build_prologue().
>> + */
>> + for (i = 0; i < ARRAY_SIZE(rv_exception_csave_regs); i++) {
>> + emit_ld(rv_exception_csave_regs[i], store_offset, RV_REG_SP, ctx);
>> + store_offset -= 8;
>> + }
>> + goto epilogue_tail;
>> + }
>>
>> if (seen_reg(RV_REG_RA, ctx)) {
>> emit_ld(RV_REG_RA, store_offset, RV_REG_SP, ctx);
>> @@ -267,6 +307,7 @@ static void __build_epilogue(bool is_tail_call, struct rv_jit_context *ctx)
>> store_offset -= 8;
>> }
>>
>> +epilogue_tail:
>> emit_addi(RV_REG_SP, RV_REG_SP, stack_adjust, ctx);
>> /* Set return value. */
>> if (!is_tail_call)
>> @@ -2002,11 +2043,61 @@ int bpf_jit_emit_insn(const struct bpf_insn *insn, struct rv_jit_context *ctx,
>> void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
>> {
>> int i, stack_adjust = 0, store_offset, bpf_stack_adjust;
>> + struct bpf_prog_aux *aux = ctx->prog->aux;
>>
>> bpf_stack_adjust = round_up(ctx->prog->aux->stack_depth, STACK_ALIGN);
>> if (bpf_stack_adjust)
>> mark_fp(ctx);
>>
>> + /* emit kcfi type preamble immediately before the first insn */
>> + emit_kcfi(is_subprog ? cfi_bpf_subprog_hash : cfi_bpf_hash, ctx);
>> +
>> + /* nops reserved for auipc+jalr pair */
>> + for (i = 0; i < RV_FENTRY_NINSNS; i++)
>> + emit(rv_nop(), ctx);
>> +
>> + /* First instruction is always setting the tail-call-counter
>> + * (TCC) register. This instruction is skipped for tail calls.
>> + * Force using a 4-byte (non-compressed) instruction.
>> + */
>> + emit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);
>> +
>> + if (aux->exception_boundary || aux->exception_cb) {
>> + /*
>> + * A program acting as an exception boundary saves the full set
>> + * of riscv callee saved registers (ra, s0-s11).
>> + */
>> + stack_adjust = round_up(ARRAY_SIZE(rv_exception_csave_regs) * 8,
>> + STACK_ALIGN);
>> + stack_adjust += bpf_stack_adjust;
>> + store_offset = stack_adjust - 8;
>> +
>> + if (!aux->exception_cb && aux->exception_cb) {
A nit, `if (!aux->exception_cb)` is enough, as aux->exception_cb and
aux->exception_cb progs are independent.
>> + /*
>> + * Boundary program: allocate the frame and save the
>> + * full callee-saved set, capturing the caller's values.
>> + */
>> + emit_addi(RV_REG_SP, RV_REG_SP, -stack_adjust, ctx);
>> + for (i = 0; i < ARRAY_SIZE(rv_exception_csave_regs); i++) {
>> + emit_sd(RV_REG_SP, store_offset,
>> + rv_exception_csave_regs[i], ctx);
>> + store_offset -= 8;
>> + }
>> + emit_addi(RV_REG_FP, RV_REG_SP, stack_adjust, ctx);
>> + } else {
>> + /*
>> + * Exception callback, reuse the boundary program's
>> + * frame, whose frame pointer is passed in a2. Setting
>> + * SP = FP - stack_adjust lines the epilogue's loads up
>> + * with the registers the boundary saved.
>> + */
>> + emit_mv(RV_REG_FP, RV_REG_A2, ctx);
>> + emit_addi(RV_REG_SP, RV_REG_FP, -stack_adjust, ctx);
>> + }
>> +
>> + goto tail_setup;
>> + }
>> +
>
> This function is getting large... I tend do forget details, so having it
> in smaller helpers would be good. Let's try to refactor a bit.
>
> The special case is really just:
> * exception boundary: allocate frame and save full layout
> * exception_cb: reuse boundary FP from a2 and derive SP from it
> * both: restore the same fixed layout
>
> So helpers like is_exception_prog(), exception_stack_adjust(),
> emit_exception_boundary_prologue(), emit_exception_cb_prologue(), and
> emit_exception_restore() would make the prologue/epilogue changes much
> easier to review and also reduce churn in bpf_jit_build_prologue().
>
>> if (seen_reg(RV_REG_RA, ctx))
>> stack_adjust += 8;
>> stack_adjust += 8; /* RV_REG_FP */
>> @@ -2030,19 +2121,6 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
>>
>> store_offset = stack_adjust - 8;
>>
>> - /* emit kcfi type preamble immediately before the first insn */
>> - emit_kcfi(is_subprog ? cfi_bpf_subprog_hash : cfi_bpf_hash, ctx);
>> -
>> - /* nops reserved for auipc+jalr pair */
>> - for (i = 0; i < RV_FENTRY_NINSNS; i++)
>> - emit(rv_nop(), ctx);
>> -
>> - /* First instruction is always setting the tail-call-counter
>> - * (TCC) register. This instruction is skipped for tail calls.
>> - * Force using a 4-byte (non-compressed) instruction.
>> - */
>> - emit(rv_addi(RV_REG_TCC, RV_REG_ZERO, MAX_TAIL_CALL_CNT), ctx);
>> -
>> emit_addi(RV_REG_SP, RV_REG_SP, -stack_adjust, ctx);
>>
>> if (seen_reg(RV_REG_RA, ctx)) {
>> @@ -2082,6 +2160,7 @@ void bpf_jit_build_prologue(struct rv_jit_context *ctx, bool is_subprog)
>>
>> emit_addi(RV_REG_FP, RV_REG_SP, stack_adjust, ctx);
>>
>> +tail_setup:
>
> Hmm, thinking more about it, maybe folding the "normal_prologue()" in as
> pre-commit, so we can avoid more gotos.
>
> | if (is_exception_prog(aux))
> | emit_exception_restore(ctx, stack_adjust);
> | else
> | emit_normal_restore(ctx, stack_adjust);
agree this too
> |
> | emit_addi(RV_REG_SP, RV_REG_SP, stack_adjust, ctx);
> | ...
>
> or smth.
>
>> if (bpf_stack_adjust) emit_addi(RV_REG_S5, RV_REG_SP,
>> bpf_stack_adjust, ctx);
>>
>> @@ -2157,3 +2236,13 @@ bool bpf_jit_supports_fsession(void)
>> {
>> return true;
>> }
>> +
>> +bool bpf_jit_supports_exceptions(void)
>> +{
>> + /*
>> + * bpf_throw() unwinds by walking the frame-pointer chain from inside
>> + * the kernel back into the BPF frames (see arch_bpf_stack_walk()), so
>> + * exceptions require the frame-pointer unwinder to be enabled.
>> + */
>
> nit: Comments...
>
>
> Thanks,
> Björn
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-06-29 3:02 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-28 8:17 [PATCH bpf-next v2 0/3] Add BPF Exceptions support for RISC-V Varun R Mallya
2026-06-28 8:17 ` [PATCH bpf-next v2 1/3] riscv: stacktrace: Implement arch_bpf_stack_walk() for BPF Varun R Mallya
2026-06-28 17:26 ` Björn Töpel
2026-06-29 2:45 ` Pu Lehui
2026-06-28 8:17 ` [PATCH bpf-next v2 2/3] riscv, bpf: Add support for BPF exceptions Varun R Mallya
2026-06-28 17:50 ` Björn Töpel
2026-06-29 3:01 ` Pu Lehui
2026-06-28 8:17 ` [PATCH bpf-next v2 3/3] riscv, bpf: Remove BPF exceptions from BPF CI denylist Varun R Mallya
2026-06-28 18:00 ` Björn Töpel
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox