BPF List
 help / color / mirror / Atom feed
From: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>
To: "Yusheng Zheng" <yunwei356@gmail.com>, <bpf@vger.kernel.org>
Cc: "Daniel Borkmann" <daniel@iogearbox.net>,
	"Andrii Nakryiko" <andrii@kernel.org>,
	"Eduard Zingerman" <eddyz87@gmail.com>,
	"Kumar Kartikeya Dwivedi" <memxor@gmail.com>,
	"Martin KaFai Lau" <martin.lau@linux.dev>,
	"Song Liu" <song@kernel.org>,
	"Yonghong Song" <yonghong.song@linux.dev>,
	"Jiri Olsa" <jolsa@kernel.org>,
	"John Fastabend" <john.fastabend@gmail.com>,
	"Emil Tsalapatis" <emil@etsalapatis.com>,
	"Ihor Solodrai" <ihor.solodrai@linux.dev>, <x86@kernel.org>,
	"Thomas Gleixner" <tglx@kernel.org>,
	"Ingo Molnar" <mingo@redhat.com>,
	"Borislav Petkov" <bp@alien8.de>,
	"Dave Hansen" <dave.hansen@linux.intel.com>,
	"H . Peter Anvin" <hpa@zytor.com>,
	"Leon Hwang" <leon.hwang@linux.dev>,
	"Puranjay Mohan" <puranjay@kernel.org>,
	"Hao Sun" <sunhao.th@gmail.com>
Subject: Re: [RFC PATCH bpf-next 0/7] bpf: Inline kfuncs that have a BPF body
Date: Sat, 17 Oct 2026 00:42:21 +0000	[thread overview]
Message-ID: <DM6P2S5E5F3M.3RB3ECL75G1PJ@gmail.com> (raw)
In-Reply-To: <20261005142219.33451-1-yunwei356@gmail.com>

On Mon, Oct 05, 2026 at 07:22 AM Yusheng Zheng <yunwei356@gmail.com> wrote:
>   1. Is it acceptable that the verifier replaces calls with their
>      bodies before the analysis and restores them afterwards?

yes.

>   2. Should kfuncs bring native code through an emit callback, or
>      should the JIT only copy compiled kfuncs? The table shows what
>      copying alone costs.

Copy only.

>   3. Native code is trusted like the rest of the JIT, and the selftests
>      compare it with the bodies on random inputs. Is that enough?
>   4. The bodies are written by hand. Should they rather be compiled from
>      the kfunc's C source, as in the inlinable kfuncs RFC [3]?

Both questions go away when C is the only implementation.

As I suggested to Leon: keep C as the fallback, let KF_FASTCALL be
the signal, check the compiled kfunc with the insn decoder and copy it
without 'ret'.
llvm keeps r1-r5 that are not arguments live across bpf_fastcall.
When JIT can inline, the verifier removes spill/fill. When it cannot,
they stay and C is called. That works on all archs from day one.
The arguments are in rdi, rsi, rdx already, so memcpy is enough.
No need to re-encode the insns.
clang compiles bpf_rol64() into
  mov %esi,%ecx; mov %rdi,%rax; rol %cl,%rax
rcx is R4 and it's not an argument, hence push/pop around it.
Then no need for bodies, emit, capture of __k constants, kfunc_bind(),
register renaming, body table in btf, hooks in do_check() and
const_fold, insn_kfuncs/ with two Kconfigs, etc.
What is missing: arm64, byte ops like setcc, selftests,
no push/pop when the register is dead.
Something like this (compile tested only):

diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
index 004e2dd..7a5667f 100644
--- a/arch/x86/net/bpf_jit_comp.c
+++ b/arch/x86/net/bpf_jit_comp.c
@@ -15,8 +15,11 @@
 #include <linux/memory.h>
 #include <linux/sort.h>
 #include <linux/execmem.h>
+#include <linux/ftrace.h>
+#include <linux/kallsyms.h>
 #include <asm/extable.h>
 #include <asm/ftrace.h>
+#include <asm/insn.h>
 #include <asm/set_memory.h>
 #include <asm/nospec-branch.h>
 #include <asm/text-patching.h>
@@ -2501,6 +2504,154 @@ static void adjust_cmp_imm(u32 *op, s32 *imm)
 	}
 }
 
+/* The longest kfunc to inline, in bytes */
+#define KFUNC_INLINE_MAX	64
+
+static u32 x86_reg_bit(u32 bpf_reg)
+{
+	return BIT(reg2hex[bpf_reg] | is_ereg(bpf_reg) << 3);
+}
+
+/*
+ * Return the mask of x86 registers named by an insn of a kfunc or -EINVAL if
+ * the insn cannot be copied. Only 32 and 64-bit moves and ALU ops can be:
+ * no control flow, prefixes, rip-relative addressing or implicit destination.
+ */
+static int kfunc_insn_regs(struct insn *insn)
+{
+	u8 rex = insn->rex_prefix.bytes[0], modrm = insn->modrm.bytes[0], sib = insn->sib.bytes[0];
+	u8 ext = X86_MODRM_REG(modrm), idx = X86_SIB_INDEX(sib) | X86_REX_X(rex) << 2;
+	u32 op = insn->opcode.bytes[0];
+	int regs = 0;
+
+	if (insn->prefixes.nbytes || insn->rex_prefix.nbytes > 1 || insn->opcode.nbytes > 2 ||
+	    insn_is_avx_or_xop(insn) || insn_rip_relative(insn))
+		return -EINVAL;
+	if (insn->opcode.nbytes == 2)
+		op = op << 8 | insn->opcode.bytes[1];
+
+	switch (op) {
+	case 0x01: case 0x03:			/* add */
+	case 0x09: case 0x0b:			/* or */
+	case 0x21: case 0x23:			/* and */
+	case 0x29: case 0x2b:			/* sub */
+	case 0x31: case 0x33:			/* xor */
+	case 0x39: case 0x3b:			/* cmp */
+	case 0x63:				/* movsxd */
+	case 0x69: case 0x6b: case 0x0faf:	/* imul */
+	case 0x85:				/* test */
+	case 0x89: case 0x8b:			/* mov */
+	case 0x8d:				/* lea */
+	case 0x0f40 ... 0x0f4f:			/* cmovcc */
+	case 0x0fb7: case 0x0fbf:		/* movzx, movsx */
+		regs = BIT(ext | X86_REX_R(rex) << 1);
+		break;
+	case 0x81: case 0x83:			/* ALU ops with imm */
+	case 0xc1: case 0xd1: case 0xd3:	/* shifts and rotates */
+		break;
+	case 0xc7:				/* mov imm */
+		if (ext)
+			return -EINVAL;
+		break;
+	case 0xf7:				/* test, not, neg */
+		if (ext > 3)
+			return -EINVAL;
+		break;
+	case 0xb8 ... 0xbf:			/* mov imm */
+	case 0x0fc8 ... 0x0fcf:			/* bswap */
+		return BIT((op & 7) | X86_REX_B(rex) << 3);
+	default:
+		return -EINVAL;
+	}
+	if (!insn->sib.nbytes)
+		return regs | BIT(X86_MODRM_RM(modrm) | X86_REX_B(rex) << 3);
+	if (idx != 4)
+		regs |= BIT(idx);
+	return regs | BIT(X86_SIB_BASE(sib) | X86_REX_B(rex) << 3);
+}
+
+/* @op is 0x50 for push and 0x58 for pop of x86 register @reg */
+static void emit_push_pop(u8 **pprog, u8 op, int reg)
+{
+	u8 *prog = *pprog;
+
+	if (reg > 7)
+		EMIT1(0x41);
+	EMIT1(op | (reg & 7));
+	*pprog = prog;
+}
+
+/*
+ * Inline a KF_FASTCALL kfunc: copy what the compiler generated for it without
+ * ENDBR, fentry and 'ret'. bpf_fastcall lets it clobber only R0 and the
+ * arguments. Push/pop the other caller saved registers that it uses.
+ */
+static int emit_kfunc_inline(u8 **pprog, unsigned long addr, const struct btf_func_model *m)
+{
+	/* a call clobbers r10 and r11, hence nothing is live there */
+	u32 scratch = x86_reg_bit(BPF_REG_AX) | x86_reg_bit(AUX_REG);
+	u32 save = x86_reg_bit(X86_REG_R9), used = 0;
+	u8 *p = (u8 *)addr, *start, *prog = *pprog;
+	unsigned long size, off;
+	struct insn insn;
+	int i, regs;
+
+	if (m->nr_args > MAX_BPF_FUNC_REG_ARGS)
+		return -EINVAL;
+	for (i = 0; i < m->nr_args; i++)
+		if (m->arg_size[i] > 8 || (m->arg_flags[i] & BTF_FMODEL_ARENA_ARG))
+			return -EINVAL;
+	for (i = BPF_REG_0; i <= BPF_REG_5; i++) {
+		if (i ? i <= m->nr_args : m->ret_size)
+			scratch |= x86_reg_bit(i);
+		else
+			save |= x86_reg_bit(i);
+	}
+
+	if (!kallsyms_lookup_size_offset(addr, &size, &off) || off)
+		return -EINVAL;
+	if (is_endbr((u32 *)p))
+		p += ENDBR_INSN_SIZE;
+	if (ftrace_location(addr) == (unsigned long)p)
+		p += MCOUNT_INSN_SIZE;
+
+	for (start = p;; p += insn.length) {
+		if (p - start > KFUNC_INLINE_MAX || p >= (u8 *)addr + size ||
+		    insn_decode_kernel(&insn, p))
+			return -EINVAL;
+		if (insn.opcode.bytes[0] == RET_INSN_OPCODE ||
+		    (insn.opcode.bytes[0] == JMP32_INSN_OPCODE &&
+		     p + insn.length + insn.immediate.value == (u8 *)x86_return_thunk))
+			break;
+		regs = kfunc_insn_regs(&insn);
+		if (regs < 0)
+			return regs;
+		used |= regs;
+	}
+	if (used & ~(scratch | save))
+		return -EINVAL;
+	save &= used;
+
+	for (i = 0; i < 16; i++)
+		if (save & BIT(i))
+			emit_push_pop(&prog, 0x50, i);
+	memcpy(prog, start, p - start);
+	prog += p - start;
+	for (i = 15; i >= 0; i--)
+		if (save & BIT(i))
+			emit_push_pop(&prog, 0x58, i);
+
+	*pprog = prog;
+	return 0;
+}
+
+bool bpf_jit_inlines_kfunc_call(unsigned long addr, const struct btf_func_model *m)
+{
+	u8 buf[BPF_MAX_INSN_SIZE], *prog = buf;
+
+	return !emit_kfunc_inline(&prog, addr, m);
+}
+
 static int do_jit(struct bpf_verifier_env *env, struct bpf_prog *bpf_prog, int *addrs, u8 *image,
 		  u8 *rw_image, int oldproglen, struct jit_context *ctx, bool jmp_padding)
 {
@@ -3424,6 +3575,12 @@ populate_extable:
 				fm = bpf_jit_find_kfunc_model(bpf_prog, insn);
 				if (!fm)
 					return -EINVAL;
+				if (fm->ret_flags & BTF_FMODEL_JIT_INLINE) {
+					err = emit_kfunc_inline(&prog, (unsigned long)func, fm);
+					if (err)
+						return err;
+					break;
+				}
 				err = emit_kfunc_arena_args(bpf_prog, fm, &prog);
 				if (err < 0)
 					return err;
diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 5414437..91b2c0e 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1256,6 +1256,9 @@ struct bpf_prog_offload {
 /* The argument is nullable. */
 #define BTF_FMODEL_NULLABLE_ARG		BIT(3)
 
+/* In ret_flags: the JIT inlines calls of this kfunc. */
+#define BTF_FMODEL_JIT_INLINE		BIT(4)
+
 struct btf_func_model {
 	u8 ret_size;
 	u8 ret_flags;
diff --git a/include/linux/filter.h b/include/linux/filter.h
index 9339c61..cd170bf 100644
--- a/include/linux/filter.h
+++ b/include/linux/filter.h
@@ -1239,6 +1239,7 @@ struct bpf_prog *bpf_int_jit_compile(struct bpf_verifier_env *env, struct bpf_pr
 void bpf_jit_compile(struct bpf_prog *prog);
 bool bpf_jit_needs_zext(void);
 bool bpf_jit_inlines_helper_call(s32 imm);
+bool bpf_jit_inlines_kfunc_call(unsigned long addr, const struct btf_func_model *m);
 bool bpf_jit_supports_subprog_tailcalls(void);
 bool bpf_jit_supports_percpu_insn(void);
 bool bpf_jit_supports_kfunc_call(void);
diff --git a/kernel/bpf/core.c b/kernel/bpf/core.c
index 642f0ab..49f331d 100644
--- a/kernel/bpf/core.c
+++ b/kernel/bpf/core.c
@@ -3295,6 +3295,15 @@ bool __weak bpf_jit_inlines_helper_call(s32 imm)
 	return false;
 }
 
+/* Return TRUE if the JIT backend inlines the KF_FASTCALL kfunc at @addr
+ * without clobbering registers that bpf_fastcall contract doesn't allow.
+ * Otherwise the kfunc is called and spills/fills around the call stay.
+ */
+bool __weak bpf_jit_inlines_kfunc_call(unsigned long addr, const struct btf_func_model *m)
+{
+	return false;
+}
+
 /* Return TRUE if the JIT backend supports mixing bpf2bpf and tailcalls. */
 bool __weak bpf_jit_supports_subprog_tailcalls(void)
 {
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 439370d..4a1c08f 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -3383,6 +3383,16 @@ __bpf_kfunc void *bpf_rdonly_cast(const void *obj__ign, u32 btf_id__k)
 	return (void *)obj__ign;
 }
 
+__bpf_kfunc u64 bpf_rol64(u64 x, u32 n)
+{
+	return rol64(x, n);
+}
+
+__bpf_kfunc u64 bpf_select64(u64 cond, u64 a, u64 b)
+{
+	return cond ? a : b;
+}
+
 __bpf_kfunc void bpf_rcu_read_lock(void)
 {
 	rcu_read_lock();
@@ -5147,6 +5157,8 @@ BTF_ID(func, bpf_cgroup_release_dtor)
 BTF_KFUNCS_START(common_btf_ids)
 BTF_ID_FLAGS(func, bpf_cast_to_kern_ctx, KF_FASTCALL)
 BTF_ID_FLAGS(func, bpf_rdonly_cast, KF_FASTCALL | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_rol64, KF_FASTCALL)
+BTF_ID_FLAGS(func, bpf_select64, KF_FASTCALL)
 BTF_ID_FLAGS(func, bpf_rcu_read_lock)
 BTF_ID_FLAGS(func, bpf_rcu_read_unlock)
 BTF_ID_FLAGS(func, bpf_dynptr_slice, KF_RET_NULL)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 62f8951..84b641d 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -3151,6 +3151,8 @@ int bpf_add_kfunc_call(struct bpf_verifier_env *env, u32 func_id, u16 offset)
 	meta.func_proto = kfunc.proto;
 	meta.func_name = kfunc.name;
 	meta.kfunc_flags = kfunc.flags ? *kfunc.flags : 0;
+	if ((meta.kfunc_flags & KF_FASTCALL) && bpf_jit_inlines_kfunc_call(addr, &func_model))
+		func_model.ret_flags |= BTF_FMODEL_JIT_INLINE;
 
 	tab = krealloc(tab, struct_size(tab, descs, tab->nr_descs + 1), GFP_KERNEL_ACCOUNT);
 	if (!tab)
@@ -19257,6 +19259,13 @@ bool bpf_verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
 	}
 }
 
+/* True if bpf_fixup_kfunc_call() replaces calls to the kfunc */
+static bool verifier_inlines_kfunc_call(u32 func_id)
+{
+	return func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx] ||
+	       func_id == special_kfunc_list[KF_bpf_rdonly_cast];
+}
+
 /* If @call is a kfunc or helper call, fills @cs and returns true,
  * otherwise returns false.
  */
@@ -19285,14 +19294,18 @@ bool bpf_get_call_summary(struct bpf_verifier_env *env, struct bpf_insn *call,
 	}
 
 	if (bpf_pseudo_kfunc_call(call)) {
+		struct bpf_kfunc_desc *desc;
 		int err;
 
 		err = bpf_fetch_kfunc_arg_meta(env, call->imm, call->off, &meta);
 		if (err < 0)
 			/* error would be reported later */
 			return false;
+		desc = find_kfunc_desc(env->prog, call->imm, call->off);
 		cs->arg_slot_cnt = btf_proto_slots(meta.btf, meta.func_proto);
-		cs->fastcall = meta.kfunc_flags & KF_FASTCALL;
+		cs->fastcall = (meta.kfunc_flags & KF_FASTCALL) &&
+			       (verifier_inlines_kfunc_call(meta.func_id) ||
+				(desc && (desc->func_model.ret_flags & BTF_FMODEL_JIT_INLINE)));
 		cs->is_void = btf_type_is_void(btf_type_by_id(meta.btf, meta.func_proto->type));
 		return true;
 	}
@@ -22945,8 +22958,7 @@ int bpf_fixup_kfunc_call(struct bpf_verifier_env *env, struct bpf_insn *insn,
 
 		__fixup_collection_insert_kfunc(&env->insn_aux_data[insn_idx], struct_meta_reg,
 						node_offset_reg, insn, insn_buf, cnt);
-	} else if (desc->func_id == special_kfunc_list[KF_bpf_cast_to_kern_ctx] ||
-		   desc->func_id == special_kfunc_list[KF_bpf_rdonly_cast]) {
+	} else if (verifier_inlines_kfunc_call(desc->func_id)) {
 		insn_buf[0] = BPF_MOV64_REG(BPF_REG_0, BPF_REG_1);
 		*cnt = 1;
 	} else if (desc->func_id == special_kfunc_list[KF_bpf_session_is_return] &&


      parent reply	other threads:[~2026-10-17  0:42 UTC|newest]

Thread overview: 14+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-05 14:22 [RFC PATCH bpf-next 0/7] bpf: Inline kfuncs that have a BPF body Yusheng Zheng
2026-10-05 14:22 ` [RFC PATCH bpf-next 1/7] bpf: Let kfunc sets give kfuncs " Yusheng Zheng
2026-10-05 14:38   ` sashiko-bot
2026-10-05 15:16   ` bot+bpf-ci
2026-10-05 14:22 ` [RFC PATCH bpf-next 2/7] bpf: Verify calls of kfuncs with a body through the body Yusheng Zheng
2026-10-05 14:41   ` sashiko-bot
2026-10-05 14:22 ` [RFC PATCH bpf-next 3/7] bpf, x86: Inline native code for kfuncs that have a body Yusheng Zheng
2026-10-05 14:22 ` [RFC PATCH bpf-next 4/7] bpf: Add kfuncs with bodies for common operations Yusheng Zheng
2026-10-05 14:39   ` sashiko-bot
2026-10-05 14:22 ` [RFC PATCH bpf-next 5/7] bpf, x86: Add native code for some inline kfuncs Yusheng Zheng
2026-10-05 14:22 ` [RFC PATCH bpf-next 6/7] selftests/bpf: Test " Yusheng Zheng
2026-10-05 14:22 ` [RFC PATCH bpf-next 7/7] Documentation/bpf: Describe " Yusheng Zheng
2026-10-05 15:16   ` bot+bpf-ci
2026-10-17  0:42 ` Alexei Starovoitov [this message]

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=DM6P2S5E5F3M.3RB3ECL75G1PJ@gmail.com \
    --to=alexei.starovoitov@gmail.com \
    --cc=andrii@kernel.org \
    --cc=bp@alien8.de \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=dave.hansen@linux.intel.com \
    --cc=eddyz87@gmail.com \
    --cc=emil@etsalapatis.com \
    --cc=hpa@zytor.com \
    --cc=ihor.solodrai@linux.dev \
    --cc=john.fastabend@gmail.com \
    --cc=jolsa@kernel.org \
    --cc=leon.hwang@linux.dev \
    --cc=martin.lau@linux.dev \
    --cc=memxor@gmail.com \
    --cc=mingo@redhat.com \
    --cc=puranjay@kernel.org \
    --cc=song@kernel.org \
    --cc=sunhao.th@gmail.com \
    --cc=tglx@kernel.org \
    --cc=x86@kernel.org \
    --cc=yonghong.song@linux.dev \
    --cc=yunwei356@gmail.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