From: Eduard Zingerman <eddyz87@gmail.com>
To: bpf@vger.kernel.org, ast@kernel.org
Cc: andrii@kernel.org, daniel@iogearbox.net, martin.lau@linux.dev,
kernel-team@fb.com, yonghong.song@linux.dev, memxor@gmail.com,
Eduard Zingerman <eddyz87@gmail.com>
Subject: [RFC bpf-next 00/11] bpf: inlinable kfuncs for BPF
Date: Thu, 7 Nov 2024 09:50:29 -0800 [thread overview]
Message-ID: <20241107175040.1659341-1-eddyz87@gmail.com> (raw)
Some time ago, in an off-list discussion, Alexei Starovoitov suggested
compiling certain kfuncs to BPF to allow inlining calls to such kfuncs
during verification. This RFC explores the idea.
This RFC introduces a notion of inlinable BPF kfuncs.
Inlinable kfuncs are compiled to BPF and are inlined by verifier after
program verification. Inlined kfunc bodies are subject to dead code
removal and removal of conditional jumps, if such jumps are proved to
always follow a single branch.
Motivation
----------
The patch set uses bpf_dynptr_slice() as guinea pig, as this function
is relatively complex and has a few conditionals that could be
removed most of the times. The function has the following structure:
void *bpf_dynptr_slice(const struct bpf_dynptr *p, u32 offset,
void *buffer__opt, u32 buffer__szk)
{
...
type = bpf_dynptr_get_type(ptr);
switch (type) {
...
case BPF_DYNPTR_TYPE_SKB:
if (buffer__opt)
return skb_header_pointer(...);
else
return skb_pointer_if_linear(...);
...
}
Parameters 'type' and 'buffer__opt' are most likely to be known at
callsite, and thus the function could be inlined w/o 'switch' and 'if'
checks above.
This has a measurable speedup on microbenchmark, e.g. a simple test
measuring number of bpf_dynptr_slice() executions per second shows
~1.5 speedup compared to master (see last patch in the series).
Granted, real world programs do some other work beside slice calls.
Mechanism
---------
Implementation pursues three main goals:
- avoid differences in program verification whether or not certain
kfunc is inlinable;
- predict branches in inlined kfuncs bodies;
- allow inlined kfunc bodies to do arbitrary computations.
The goals are achieved in the following steps:
- Inlinable kfuncs are defined in kernel/bpf/inlinable_kfuncs.c.
- In order to include kernel headers as-is the C file is compiled to
llvm bitcode targeting native architecture and then to BPF elf
object using llc utility.
- BPF elf object is embedded in kernel data section.
- At kernel initialization time the elf object is parsed and functions
defined in the object are deemed to be inlinable kfuncs.
- Before main verification pass, for each inlinable kfunc callsite,
inlinable kfunc is cloned as a hidden subprogram. Such subprograms
are called kfunc instances.
- A new KERNEL_VALUE register type is added:
- ALU operations on any type and KERNEL_VALUE return KERNEL_VALUE;
- load / store operations with base register having type
KERNEL_VALUE return KERNEL_VALUE.
- During main verification pass:
- inlinable kfunc calls are verified as regular kfunc calls;
- the bodies of the corresponding kfunc instances are visited
in a "distilled" context:
- no caller stack frame;
- scalar or null pointer r1-r5 from caller stack frame are copied
to instance call frame verbatim;
- pointer to dynptr r1-r5 from caller stack frame are represented
in the instance call frame as pointers to a fake stack frame.
For each dynptr this fake stack frame contains two register spills:
- one scalar for 'size' field, which also encodes type;
- one KERNEL_VALUE for 'data' field;
- r1-r5 of all other types are represented in the instance call
frame as KERNEL_VALUEs;
- when 'exit' instruction within kfunc instance body is processed,
verification for the current path is assumed complete.
- After main verification pass:
- rely on existing passes opt_hard_wire_dead_code_branches() and
opt_remove_dead_code() to simplify kfunc instance bodies;
- calls to inlinable kfuncs are replaced with corresponding kfunc
instance bodies, instance subprograms are removed.
Patch-set structure
-------------------
Implementation of the above mechanics is split in several patches to
simplify the review. The following patches are most interesting:
- "bpf: shared BPF/native kfuncs":
Build system integration and kfuncs inlining after verification.
- "bpf: KERNEL_VALUE register type"
Adds an opaque type for registers that could be used for ALU
and memory access.
- "bpf: allow specifying inlinable kfuncs in modules"
This is mostly for tests: for testing purposes
it is necessary to control exact assembly code for
both test case calling kfunc and kfunc itself.
- "bpf: instantiate inlinable kfuncs before verification"
Adds a pass that clones inlinable kfunc bodies as hidden
subprograms, one subprogram per callsite.
- "bpf: special rules for kernel function calls inside inlinable kfuncs"
Allows to call arbitrary kernel functions from kfunc instances.
Limitations
-----------
Some existing verifier restrictions are not lifted for inlinable kfuncs:
- stack is limited to 512 bytes;
- max 8 call frames (7 with the fake call frame);
- loops are still verified in a "brute force" way;
- instructions processed for kfunc instances are counted in 1M
instructions budget.
The list is probably not exhaustive.
TODO items
----------
The following items are currently on the TODO list:
- consider getting rid of bpftool linking phase for BPF elf object;
- consider getting rid of clang->bitcode->llc->elf dance;
- make bpf_dynptr_from_skb() inlinable and allow passing objects state
between kfunc instances, thus avoiding special case for dynptr;
- determine the set of kfuncs for which inlining makes sense.
Alternatives
------------
Imo, this RFC is worth following through only if number of kfuncs
benefiting from inlining is big. If the set is limited to dynptr
family of functions, it is simpler to add a number of hard-coded
inlining templates for such functions (similarly to what is currently
done for some helpers).
Eduard Zingerman (11):
bpf: use branch predictions in opt_hard_wire_dead_code_branches()
selftests/bpf: tests for opt_hard_wire_dead_code_branches()
bpf: shared BPF/native kfuncs
bpf: allow specifying inlinable kfuncs in modules
bpf: dynamic allocation for bpf_verifier_env->subprog_info
bpf: KERNEL_VALUE register type
bpf: instantiate inlinable kfuncs before verification
bpf: special rules for kernel function calls inside inlinable kfuncs
bpf: move selected dynptr kfuncs to inlinable_kfuncs.c
selftests/bpf: tests to verify handling of inlined kfuncs
selftests/bpf: dynptr_slice benchmark
Makefile | 22 +-
include/linux/bpf.h | 40 +-
include/linux/bpf_verifier.h | 12 +-
include/linux/btf.h | 7 +
kernel/bpf/Makefile | 25 +-
kernel/bpf/btf.c | 2 +-
kernel/bpf/helpers.c | 130 +-
kernel/bpf/inlinable_kfuncs.c | 113 ++
kernel/bpf/log.c | 1 +
kernel/bpf/verifier.c | 1187 ++++++++++++++++-
tools/testing/selftests/bpf/Makefile | 2 +
tools/testing/selftests/bpf/bench.c | 2 +
.../selftests/bpf/benchs/bench_dynptr_slice.c | 76 ++
.../selftests/bpf/bpf_testmod/Makefile | 24 +-
.../{bpf_testmod.c => bpf_testmod_core.c} | 25 +
.../bpf/bpf_testmod/test_inlinable_kfuncs.c | 132 ++
.../selftests/bpf/prog_tests/verifier.c | 9 +
.../selftests/bpf/progs/dynptr_slice_bench.c | 29 +
.../selftests/bpf/progs/verifier_dead_code.c | 63 +
.../bpf/progs/verifier_inlinable_kfuncs.c | 253 ++++
20 files changed, 1970 insertions(+), 184 deletions(-)
create mode 100644 kernel/bpf/inlinable_kfuncs.c
create mode 100644 tools/testing/selftests/bpf/benchs/bench_dynptr_slice.c
rename tools/testing/selftests/bpf/bpf_testmod/{bpf_testmod.c => bpf_testmod_core.c} (97%)
create mode 100644 tools/testing/selftests/bpf/bpf_testmod/test_inlinable_kfuncs.c
create mode 100644 tools/testing/selftests/bpf/progs/dynptr_slice_bench.c
create mode 100644 tools/testing/selftests/bpf/progs/verifier_dead_code.c
create mode 100644 tools/testing/selftests/bpf/progs/verifier_inlinable_kfuncs.c
--
2.47.0
next reply other threads:[~2024-11-07 17:51 UTC|newest]
Thread overview: 34+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-11-07 17:50 Eduard Zingerman [this message]
2024-11-07 17:50 ` [RFC bpf-next 01/11] bpf: use branch predictions in opt_hard_wire_dead_code_branches() Eduard Zingerman
2024-11-14 22:20 ` Eduard Zingerman
2024-11-15 0:17 ` Andrii Nakryiko
2024-11-15 0:19 ` Andrii Nakryiko
2024-11-15 0:50 ` Eduard Zingerman
2024-11-15 3:03 ` Andrii Nakryiko
2024-11-15 0:20 ` Eduard Zingerman
2024-11-15 0:27 ` Alexei Starovoitov
2024-11-15 0:33 ` Eduard Zingerman
2024-11-15 0:38 ` Alexei Starovoitov
2024-11-15 0:43 ` Eduard Zingerman
2024-11-15 0:16 ` Andrii Nakryiko
2024-11-07 17:50 ` [RFC bpf-next 02/11] selftests/bpf: tests for opt_hard_wire_dead_code_branches() Eduard Zingerman
2024-11-07 17:50 ` [RFC bpf-next 03/11] bpf: shared BPF/native kfuncs Eduard Zingerman
2024-11-08 20:43 ` Toke Høiland-Jørgensen
2024-11-08 21:25 ` Eduard Zingerman
2024-11-11 18:41 ` Toke Høiland-Jørgensen
2024-11-15 0:27 ` Andrii Nakryiko
2024-11-07 17:50 ` [RFC bpf-next 04/11] bpf: allow specifying inlinable kfuncs in modules Eduard Zingerman
2024-11-07 17:50 ` [RFC bpf-next 05/11] bpf: dynamic allocation for bpf_verifier_env->subprog_info Eduard Zingerman
2024-11-07 17:50 ` [RFC bpf-next 06/11] bpf: KERNEL_VALUE register type Eduard Zingerman
2024-11-07 17:50 ` [RFC bpf-next 07/11] bpf: instantiate inlinable kfuncs before verification Eduard Zingerman
2024-11-07 17:50 ` [RFC bpf-next 08/11] bpf: special rules for kernel function calls inside inlinable kfuncs Eduard Zingerman
2024-11-07 17:50 ` [RFC bpf-next 09/11] bpf: move selected dynptr kfuncs to inlinable_kfuncs.c Eduard Zingerman
2024-11-07 17:50 ` [RFC bpf-next 10/11] selftests/bpf: tests to verify handling of inlined kfuncs Eduard Zingerman
2024-11-07 22:04 ` Jeff Johnson
2024-11-07 22:08 ` Eduard Zingerman
2024-11-07 22:19 ` Jeff Johnson
2024-11-07 23:00 ` Eduard Zingerman
2024-11-07 17:50 ` [RFC bpf-next 11/11] selftests/bpf: dynptr_slice benchmark Eduard Zingerman
2024-11-08 20:41 ` [RFC bpf-next 00/11] bpf: inlinable kfuncs for BPF Toke Høiland-Jørgensen
2024-11-08 23:01 ` Eduard Zingerman
2024-11-11 18:42 ` Toke Høiland-Jørgensen
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=20241107175040.1659341-1-eddyz87@gmail.com \
--to=eddyz87@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=kernel-team@fb.com \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=yonghong.song@linux.dev \
/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;
as well as URLs for NNTP newsgroup(s).