[PATCH v6 3/5] bpf: Add bpf_list_is_edge/empty kfuncs

Chengkaitao posted 5 patches 1 month, 1 week ago
There is a newer version of this series
[PATCH v6 3/5] bpf: Add bpf_list_is_edge/empty kfuncs
Posted by Chengkaitao 1 month, 1 week ago
From: Kaitao Cheng <chengkaitao@kylinos.cn>

Add three kfuncs for BPF linked list queries:
- bpf_list_node_is_edge(head, node, is_first): true if node is the first
  (when is_first is true) or the last (when is_first is false) in the list.
- bpf_list_empty(head): true if the list has no entries.

In previous versions, to implement the above functionality, it was
necessary to first call bpf_list_pop_front/back to retrieve the first
or last node before checking whether the passed-in node was the first
or last one. After the check, the node had to be pushed back into the
list using bpf_list_push_front/back, which was very inefficient.

Now, with the bpf_list_is_edge/empty kfuncs, we can directly
check whether a node is the first, last, or whether the list is empty,
without having to first retrieve the node.

Signed-off-by: Kaitao Cheng <chengkaitao@kylinos.cn>
---
 kernel/bpf/helpers.c  | 37 +++++++++++++++++++++++++++++++++++++
 kernel/bpf/verifier.c | 11 +++++++++--
 2 files changed, 46 insertions(+), 2 deletions(-)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 740b53024283..3d22b6080185 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -2538,6 +2538,41 @@ __bpf_kfunc int bpf_list_add_impl(struct bpf_list_head *head,
 	return -EINVAL;
 }
 
+__bpf_kfunc bool bpf_list_node_is_edge(struct bpf_list_head *head,
+				       struct bpf_list_node *node, bool is_first)
+{
+	struct list_head *h = (struct list_head *)head, *edge;
+	struct bpf_list_node_kern *n = (struct bpf_list_node_kern *)node;
+
+	/* If list_head was 0-initialized by map, bpf_obj_init_field wasn't
+	 * called on its fields, so init here
+	 */
+	if (unlikely(!h->next))
+		INIT_LIST_HEAD(h);
+
+	if (list_empty(h))
+		return false;
+
+	if (READ_ONCE(n->owner) != head)
+		return false;
+
+	edge = is_first ? h->next : h->prev;
+	return edge == &n->list_head;
+}
+
+__bpf_kfunc bool bpf_list_empty(struct bpf_list_head *head)
+{
+	struct list_head *h = (struct list_head *)head;
+
+	/* If list_head was 0-initialized by map, bpf_obj_init_field wasn't
+	 * called on its fields, so init here
+	 */
+	if (unlikely(!h->next))
+		INIT_LIST_HEAD(h);
+
+	return list_empty(h);
+}
+
 __bpf_kfunc struct bpf_rb_node *bpf_rbtree_remove(struct bpf_rb_root *root,
 						  struct bpf_rb_node *node)
 {
@@ -4608,6 +4643,8 @@ BTF_ID_FLAGS(func, bpf_list_del, KF_ACQUIRE | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_list_front, KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_list_back, KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_list_add_impl)
+BTF_ID_FLAGS(func, bpf_list_node_is_edge)
+BTF_ID_FLAGS(func, bpf_list_empty)
 BTF_ID_FLAGS(func, bpf_task_acquire, KF_ACQUIRE | KF_RCU | KF_RET_NULL)
 BTF_ID_FLAGS(func, bpf_task_release, KF_RELEASE)
 BTF_ID_FLAGS(func, bpf_rbtree_remove, KF_ACQUIRE | KF_RET_NULL)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index e458cf3b1dd1..d133d18aa0cc 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -12465,6 +12465,8 @@ enum special_kfunc_type {
 	KF_bpf_list_front,
 	KF_bpf_list_back,
 	KF_bpf_list_add_impl,
+	KF_bpf_list_node_is_edge,
+	KF_bpf_list_empty,
 	KF_bpf_cast_to_kern_ctx,
 	KF_bpf_rdonly_cast,
 	KF_bpf_rcu_read_lock,
@@ -12527,6 +12529,8 @@ BTF_ID(func, bpf_list_del)
 BTF_ID(func, bpf_list_front)
 BTF_ID(func, bpf_list_back)
 BTF_ID(func, bpf_list_add_impl)
+BTF_ID(func, bpf_list_node_is_edge)
+BTF_ID(func, bpf_list_empty)
 BTF_ID(func, bpf_cast_to_kern_ctx)
 BTF_ID(func, bpf_rdonly_cast)
 BTF_ID(func, bpf_rcu_read_lock)
@@ -13003,7 +13007,9 @@ static bool is_bpf_list_api_kfunc(u32 btf_id)
 	       btf_id == special_kfunc_list[KF_bpf_list_del] ||
 	       btf_id == special_kfunc_list[KF_bpf_list_front] ||
 	       btf_id == special_kfunc_list[KF_bpf_list_back] ||
-	       btf_id == special_kfunc_list[KF_bpf_list_add_impl];
+	       btf_id == special_kfunc_list[KF_bpf_list_add_impl] ||
+	       btf_id == special_kfunc_list[KF_bpf_list_node_is_edge] ||
+	       btf_id == special_kfunc_list[KF_bpf_list_empty];
 }
 
 static bool is_bpf_rbtree_api_kfunc(u32 btf_id)
@@ -13126,7 +13132,8 @@ static bool check_kfunc_is_graph_node_api(struct bpf_verifier_env *env,
 		ret = (kfunc_btf_id == special_kfunc_list[KF_bpf_list_push_front_impl] ||
 		       kfunc_btf_id == special_kfunc_list[KF_bpf_list_push_back_impl] ||
 		       kfunc_btf_id == special_kfunc_list[KF_bpf_list_del] ||
-		       kfunc_btf_id == special_kfunc_list[KF_bpf_list_add_impl]);
+		       kfunc_btf_id == special_kfunc_list[KF_bpf_list_add_impl] ||
+		       kfunc_btf_id == special_kfunc_list[KF_bpf_list_node_is_edge]);
 		break;
 	case BPF_RB_NODE:
 		ret = (kfunc_btf_id == special_kfunc_list[KF_bpf_rbtree_remove] ||
-- 
2.50.1 (Apple Git-155)
Re: [PATCH v6 3/5] bpf: Add bpf_list_is_edge/empty kfuncs
Posted by bot+bpf-ci@kernel.org 1 month, 1 week ago
> Add three kfuncs for BPF linked list queries:
> - bpf_list_node_is_edge(head, node, is_first): true if node is the first
>   (when is_first is true) or the last (when is_first is false) in the list.
> - bpf_list_empty(head): true if the list has no entries.

The commit message says "three kfuncs" but the description lists two
and the patch adds two (bpf_list_node_is_edge and bpf_list_empty).
It looks like this is a leftover from v5 where there were three
separate functions (bpf_list_is_first, bpf_list_is_last,
bpf_list_empty) before they were consolidated into two. Should this
say "two kfuncs"?


---
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/22674350640
Re: [PATCH v6 3/5] bpf: Add bpf_list_is_edge/empty kfuncs
Posted by Kumar Kartikeya Dwivedi 1 month, 1 week ago
On Wed, 4 Mar 2026 at 16:34, <bot+bpf-ci@kernel.org> wrote:
>
> > Add three kfuncs for BPF linked list queries:
> > - bpf_list_node_is_edge(head, node, is_first): true if node is the first
> >   (when is_first is true) or the last (when is_first is false) in the list.
> > - bpf_list_empty(head): true if the list has no entries.
>
> The commit message says "three kfuncs" but the description lists two
> and the patch adds two (bpf_list_node_is_edge and bpf_list_empty).
> It looks like this is a leftover from v5 where there were three
> separate functions (bpf_list_is_first, bpf_list_is_last,
> bpf_list_empty) before they were consolidated into two. Should this
> say "two kfuncs"?
>

I have no idea why you decided to combine this, there isn't any
indication in the changelog, but it looked fine in previous versions.
It pairs well with bpf_list_{first,last}. I would prefer going with
separate kfuncs (is_first, is_last). Please also don't skip addressing
feedback from previous versions.
E.g. https://lore.kernel.org/all/875x7bzkz2.fsf@gmail.com suggested
using list_is_first/list_is_last.

>
> ---
> 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/22674350640