From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: bpf@vger.kernel.org
Cc: Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Eduard Zingerman <eddyz87@gmail.com>,
Emil Tsalapatis <emil@etsalapatis.com>,
kkd@meta.com, kernel-team@meta.com
Subject: [PATCH bpf-next v7 1/6] bpf: Track verifier instruction stats for each subprogram
Date: Sat, 8 Aug 2026 08:25:53 +0200 [thread overview]
Message-ID: <20260808062601.1070988-2-memxor@gmail.com> (raw)
In-Reply-To: <20260808062601.1070988-1-memxor@gmail.com>
The verifier currently records one instruction count for the main program
and each global subprogram checked independently. Static subprograms are
explored within callers, so their verification cost cannot be reported
separately.
Track both self and inclusive instruction counts for every subprogram.
Charge each processed instruction as self work to the current subprogram and
to a path-local subtotal in its function frame. When a function returns, add
the callee subtotal to its inclusive count and to its parent subtotal. Fold
any remaining frames when a path terminates or is pruned.
Instruction subtotals are accounting state, not semantic verifier state.
Clear them when a verifier state is copied so work before a path fork is
charged once, rather than again when a saved branch is explored. If copying
a saved state fails before all frames are allocated, skip missing frames
while folding the current path.
This generic frame accounting also records self and inclusive totals when an
asynchronous callback starts as a fresh frame-zero state. It does not yet
charge that independently explored callback path back to the main or global
exploration root which scheduled it. That will be done in subsequent
changes.
This does not change the verification statistics output format. It only
prepares the counters for per-subprogram reporting.
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
---
include/linux/bpf_verifier.h | 5 +++-
kernel/bpf/verifier.c | 55 ++++++++++++++++++++++++++++++------
2 files changed, 50 insertions(+), 10 deletions(-)
diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
index a2a40caca0a0..f16ee6602179 100644
--- a/include/linux/bpf_verifier.h
+++ b/include/linux/bpf_verifier.h
@@ -385,6 +385,8 @@ struct bpf_func_state {
* | number of simulations is tracked in frame N
*/
u32 callback_depth;
+ /* Instructions processed in this frame and callees on the current path. */
+ u32 insns_subtotal;
/* The following fields should be last. See copy_func_state() */
/* The state of the stack. Each element of the array describes BPF_REG_SIZE
@@ -803,7 +805,8 @@ struct bpf_subprog_info {
u32 exit_idx; /* Index of one of the BPF_EXIT instructions in this subprogram */
u16 stack_depth; /* max. stack depth used by this function */
u16 stack_extra;
- u32 insn_processed;
+ u32 insns_total;
+ u32 insns_self;
/* offsets in range [stack_depth .. fastcall_stack_off)
* are used for bpf_fastcall spills and fills.
*/
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index c9533ea700ba..855f245e7468 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -1593,6 +1593,8 @@ static int copy_func_state(struct bpf_func_state *dst,
const struct bpf_func_state *src)
{
memcpy(dst, src, offsetof(struct bpf_func_state, stack));
+ /* Instruction accounting is path-local, not part of verifier state. */
+ dst->insns_subtotal = 0;
return copy_stack_state(dst, src);
}
@@ -9832,6 +9834,42 @@ static int set_task_work_schedule_callback_state(struct bpf_verifier_env *env,
static bool is_rbtree_lock_required_kfunc(u32 btf_id);
+static void account_processed_insn(struct bpf_verifier_env *env)
+{
+ struct bpf_func_state *frame = cur_func(env);
+
+ env->insn_processed++;
+ frame->insns_subtotal++;
+ env->subprog_info[frame->subprogno].insns_self++;
+}
+
+static void account_processed_insns(struct bpf_verifier_env *env,
+ struct bpf_func_state *callee,
+ struct bpf_func_state *caller)
+{
+ u32 insns;
+
+ if (!callee)
+ return;
+
+ insns = callee->insns_subtotal;
+
+ env->subprog_info[callee->subprogno].insns_total += insns;
+ if (caller)
+ caller->insns_subtotal += insns;
+ callee->insns_subtotal = 0;
+}
+
+static void account_current_path(struct bpf_verifier_env *env)
+{
+ struct bpf_verifier_state *state = env->cur_state;
+ int frame;
+
+ for (frame = state->curframe; frame >= 0; frame--)
+ account_processed_insns(env, state->frame[frame],
+ frame ? state->frame[frame - 1] : NULL);
+}
+
/* Are we currently verifying the callback for a rbtree helper that must
* be called with lock held? If so, no need to complain about unreleased
* lock
@@ -9928,6 +9966,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx)
verbose(env, "to caller at %d:\n", *insn_idx);
print_verifier_state(env, state, caller->frameno, true);
}
+ account_processed_insns(env, callee, caller);
/* clear everything in the callee. In case of exceptional exits using
* bpf_throw, this will be done by copy_verifier_state for extra frames. */
free_func_state(callee);
@@ -17504,7 +17543,9 @@ static int do_check(struct bpf_verifier_env *env)
insn = &insns[env->insn_idx];
insn_aux = &env->insn_aux_data[env->insn_idx];
- if (++env->insn_processed > BPF_COMPLEXITY_LIMIT_INSNS) {
+ account_processed_insn(env);
+
+ if (env->insn_processed > BPF_COMPLEXITY_LIMIT_INSNS) {
verbose(env,
"BPF program is too large. Processed %d insn\n",
env->insn_processed);
@@ -17644,6 +17685,7 @@ static int do_check(struct bpf_verifier_env *env)
"speculation barrier after jump instruction may not have the desired effect"))
return -EFAULT;
process_bpf_exit:
+ account_current_path(env);
mark_verifier_state_scratched(env);
err = bpf_update_branch_counts(env, env->cur_state);
if (err)
@@ -18688,6 +18730,7 @@ static int do_check_common(struct bpf_verifier_env *env, int subprog)
ret = do_check(env);
out:
+ account_current_path(env);
if (!ret && pop_log)
bpf_vlog_reset(&env->log, 0);
free_states(env);
@@ -18719,7 +18762,6 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
struct bpf_prog_aux *aux = env->prog->aux;
struct bpf_func_info_aux *sub_aux;
int i, ret, new_cnt;
- u32 insn_processed;
if (!aux->func_info)
return 0;
@@ -18734,8 +18776,6 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
if (!bpf_subprog_is_global(env, i))
continue;
- insn_processed = env->insn_processed;
-
sub_aux = subprog_aux(env, i);
if (!sub_aux->called || sub_aux->verified)
continue;
@@ -18743,7 +18783,6 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
env->insn_idx = env->subprog_info[i].start;
WARN_ON_ONCE(env->insn_idx == 0);
ret = do_check_common(env, i);
- env->subprog_info[i].insn_processed = env->insn_processed - insn_processed;
if (ret) {
return ret;
} else if (env->log.level & BPF_LOG_LEVEL) {
@@ -18770,12 +18809,10 @@ static int do_check_subprogs(struct bpf_verifier_env *env)
static int do_check_main(struct bpf_verifier_env *env)
{
- u32 insn_processed = env->insn_processed;
int ret;
env->insn_idx = 0;
ret = do_check_common(env, 0);
- env->subprog_info[0].insn_processed = env->insn_processed - insn_processed;
if (!ret)
env->prog->aux->stack_depth = env->subprog_info[0].stack_depth;
return ret;
@@ -18794,10 +18831,10 @@ static void print_verification_stats(struct bpf_verifier_env *env)
for (i = 1; i < subprog_cnt; i++)
verbose(env, "+%d", env->subprog_info[i].stack_depth);
verbose(env, " max %d\n", env->max_stack_depth);
- verbose(env, "insns processed %d", env->subprog_info[0].insn_processed);
+ verbose(env, "insns processed %d", env->subprog_info[0].insns_total);
for (i = 1; i < subprog_cnt; i++)
if (bpf_subprog_is_global(env, i))
- verbose(env, "+%d", env->subprog_info[i].insn_processed);
+ verbose(env, "+%d", env->subprog_info[i].insns_total);
verbose(env, "\n");
}
verbose(env, "processed %d insns (limit %d) max_states_per_insn %d "
--
2.53.0
next prev parent reply other threads:[~2026-08-08 6:26 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-08 6:25 [PATCH bpf-next v7 0/6] Improve stack depth verification stats output Kumar Kartikeya Dwivedi
2026-08-08 6:25 ` Kumar Kartikeya Dwivedi [this message]
2026-08-08 6:25 ` [PATCH bpf-next v7 2/6] bpf: Attribute async callback instructions to verification roots Kumar Kartikeya Dwivedi
2026-08-08 7:40 ` bot+bpf-ci
2026-08-08 6:25 ` [PATCH bpf-next v7 3/6] bpf: Show more useful info in stack depth stats Kumar Kartikeya Dwivedi
2026-08-08 6:45 ` sashiko-bot
2026-08-08 6:25 ` [PATCH bpf-next v7 4/6] selftests/bpf: Adjust veristat stack depth parsing Kumar Kartikeya Dwivedi
2026-08-08 6:25 ` [PATCH bpf-next v7 5/6] selftests/bpf: Test stack depth stats without BTF subprog names Kumar Kartikeya Dwivedi
2026-08-08 6:25 ` [PATCH bpf-next v7 6/6] selftests/bpf: Test subprogram instruction statistics Kumar Kartikeya Dwivedi
2026-08-08 6:36 ` sashiko-bot
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=20260808062601.1070988-2-memxor@gmail.com \
--to=memxor@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=kernel-team@meta.com \
--cc=kkd@meta.com \
/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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox