bpf_loop inlining
Subsystems (5)
Eduard Zingerman
maintainercover bpf_loop inlining
Hi Everyone,
This patch implements inlining of calls to bpf_loop helper function when bpf_loop's callback is statically known. E.g. the rewrite does the following transformation during BPF program processing:
bpf_loop(10, foo, NULL, 0);
->
for (int i = 0; i < 10; ++i)
foo(i, NULL);The transformation leads to measurable latency change for simple loops. Measurements using benchs/run_bench_bpf_loop.sh inside QEMU / KVM on i7-4710HQ CPU shows a drop in latency from 14 ns/op to 2 ns/op.
The change is split in three parts:
* Update to test_verifier.c to specify expected and unexpected instruction sequences. This allows to check BPF program rewrites applied by do_mix_fixups function.
* Update to test_verifier.c to specify BTF function infos and types per test case. This is necessary for tests that load sub-program addresses to a variable because of the checks applied by check_ld_imm function.
* The update to verifier.c that tracks state of the parameters for each bpf_loop call in a program and decides whether it could be replaced by a loop.
Additional details are available in the commit message for each patch. Hope you find this useful.
Best regards,
Eduard Zingerman
Eduard Zingerman (3):
selftests/bpf: specify expected instructions in test_verifier tests selftests/bpf: allow BTF specs and func infos in test_verifier tests
bpf: Inline calls to bpf_loop when callback is known
include/linux/bpf_verifier.h | 15 + kernel/bpf/bpf_iter.c | 9 +- kernel/bpf/verifier.c | 160 +++++++++- .../selftests/bpf/prog_tests/bpf_loop.c | 21 ++ tools/testing/selftests/bpf/prog_tests/btf.c | 1 - tools/testing/selftests/bpf/progs/bpf_loop.c | 38 +++ tools/testing/selftests/bpf/test_btf.h | 2 + tools/testing/selftests/bpf/test_verifier.c | 290 +++++++++++++++++- .../selftests/bpf/verifier/bpf_loop_inline.c | 49 +++ 9 files changed, 558 insertions(+), 27 deletions(-) create mode 100644 tools/testing/selftests/bpf/verifier/bpf_loop_inline.c
Signature · 1 line
2.25.1
Eduard Zingerman
maintainerPATCH 1/3 selftests/bpf: specify expected instructions in test_verifier tests
Allows to specify expected and unexpected instruction sequences in test_verifier test cases. The instructions are requested from kernel after BPF program loading, thus allowing to check some of the transformations applied by BPF verifier.
- expected_insn field specifies a sequences of instructions expected to be found in the program;
- unexpected_insn field specifies a sequences of instructions that are not expected to be found in the program;
- INSN_OFF_MASK and INSN_IMM_MASK values could be used to mask off and imm fields.
The intended usage is as follows:
{
"inline simple bpf_loop call",
.insns = {
/* main */
BPF_ALU64_IMM(BPF_MOV, BPF_REG_1, 1),
BPF_RAW_INSN(BPF_LD | BPF_IMM | BPF_DW, BPF_REG_2,
BPF_PSEUDO_FUNC, 0, 6),
...
BPF_EXIT_INSN(),
/* callback */
BPF_ALU64_IMM(BPF_MOV, BPF_REG_0, 1),
BPF_EXIT_INSN(),
},
.expected_insns = {
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, BPF_PSEUDO_CALL, 8, 1)
},
.unexpected_insns = {
BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0,
INSN_OFF_MASK, INSN_IMM_MASK),
},
.prog_type = BPF_PROG_TYPE_TRACEPOINT,
.result = ACCEPT,
.runs = 0,},
Here it is expected that helper function call instruction would be replaced by a relative call instruction.
--- tools/testing/selftests/bpf/test_verifier.c | 182 ++++++++++++++++++++ 1 file changed, 182 insertions(+)
tools/testing/selftests/bpf/test_verifier.c +182 -0
@@ -51,6 +51,8 @@#endif #define MAX_INSNS BPF_MAXINSNS +#define MAX_EXPECTED_INSNS 32 +#define MAX_UNEXPECTED_INSNS 32#define MAX_TEST_INSNS 1000000#define MAX_FIXUPS 8#define MAX_NR_MAPS 23
@@ -58,6 +60,9 @@#define POINTER_VALUE 0xcafe4all#define TEST_DATA_LEN 64 +#define INSN_OFF_MASK ((s16)0xFFFF) +#define INSN_IMM_MASK ((s32)0xFFFFFFFF) +#define F_NEEDS_EFFICIENT_UNALIGNED_ACCESS (1 << 0)#define F_LOAD_WITH_STRICT_ALIGNMENT (1 << 1)
@@ -79,6 +84,15 @@ struct bpf_test {const char *descr;struct bpf_insn insns[MAX_INSNS ];struct bpf_insn *fill_insns; +/* If specified, test engine looks for this sequence of +* instructions in the BPF program after loading. Allows to +* test rewrites applied by verifier. Use values +* INSN_OFF_MASK and INSN_IMM_MASK to mask `off` and `imm` +* fields if content does not matter. The test case fails if +* specified instructions are not found. +*/ +struct bpf_insn expected_insns[MAX_EXPECTED_INSNS ]; +struct bpf_insn unexpected_insns[MAX_UNEXPECTED_INSNS ];int fixup_map_hash_8b[MAX_FIXUPS ];int fixup_map_hash_48b[MAX_FIXUPS ];int fixup_map_hash_16b[MAX_FIXUPS ];
@@ -1126,6 +1140,171 @@ static bool cmp_str_seq(const char *log, const char *exp)return true; } +static __u32 roundup_u32 (__u32 number,__u32 divisor) +{ +if (number % divisor ==0 ) +return number / divisor; +else +return number / divisor +1 ; +} + +static int get_xlated_program (int fd_prog,struct bpf_insn **buf,int *cnt) +{ +struct bpf_prog_info info = {}; +__u32 info_len =sizeof (info); +int err =0 ; + +if (bpf_obj_get_info_by_fd (fd_prog, &info, &info_len)) { + err = errno; +perror ("bpf_obj_get_info_by_fd failed" ); + goto out; + } + +__u32 xlated_prog_len = info.xlated_prog_len; + *cnt =roundup_u32 (xlated_prog_len,sizeof (**buf)); + *buf =calloc (*cnt,sizeof (**buf)); +if (!buf) { + err = -ENOMEM ; +perror ("can't allocate xlated program buffer" ); + goto out; + } + +bzero (&info,sizeof (info)); + info.xlated_prog_len = xlated_prog_len; + info.xlated_prog_insns = (__u64)*buf; + +if (bpf_obj_get_info_by_fd (fd_prog, &info, &info_len)) { + err = errno; +perror ("second bpf_obj_get_info_by_fd failed" ); + goto out_free_buf; + } + + goto out; + + out_free_buf: +free (*buf); + out: +return err; +} + +static bool is_null_insn (struct bpf_insn *insn) +{ +struct bpf_insn null_insn = {}; + +return memcmp (insn, &null_insn,sizeof (null_insn)) ==0 ; +} + +static int null_terminated_insn_len (struct bpf_insn *seq,int max_len) +{ +for (int i =0 ; i < max_len; ++i) { +if (is_null_insn (&seq[i])) +return i; + } +return max_len; +} + +static bool compare_masked_insn (struct bpf_insn *orig,struct bpf_insn *masked) +{ +struct bpf_insn orig_masked; + +memcpy (&orig_masked, orig,sizeof (orig_masked)); +if (masked->imm ==INSN_IMM_MASK ) + orig_masked.imm =INSN_IMM_MASK ; +if (masked->off ==INSN_OFF_MASK ) + orig_masked.off =INSN_OFF_MASK ; + +return memcmp (&orig_masked, masked,sizeof (orig_masked)) ==0 ; +} + +static int find_insn_subseq (struct bpf_insn *seq,struct bpf_insn *subseq, +int seq_len,int subseq_len) +{ +if (subseq_len > seq_len) +return -1 ; + +for (int i =0 ; i < seq_len - subseq_len +1 ; ++i) { +bool found = true; + +for (int j =0 ; j < subseq_len; ++j) { +if (!compare_masked_insn (&seq[i + j], &subseq[j])) { + found = false; +break ; + } + } +if (found) +return i; + } + +return -1 ; +} + +static void print_insn (struct bpf_insn *buf,int cnt, +int mark_start,int mark_count) +{ +printf (" addr op d s off imm\n" ); +for (int i =0 ; i < cnt; ++i) { +bool at_mark = (mark_start >=0 ) && + (i >= mark_start) && + (i < mark_start + mark_count); +char *mark = at_mark ?"*" :" " ; + +struct bpf_insn *insn = &buf[i]; + +printf (" %s%04x: %02x %1x %x %04hx %08x\n" , + mark, i, insn->code, insn->dst_reg, + insn->src_reg, insn->off, insn->imm); + } +} + +static bool check_xlated_program (struct bpf_test *test,int fd_prog) +{ +struct bpf_insn *buf; +int cnt; +bool result = true; +int expected_insn_cnt = +null_terminated_insn_len (test->expected_insns, +ARRAY_SIZE (test->expected_insns)); +int unexpected_insn_cnt = +null_terminated_insn_len (test->unexpected_insns, +ARRAY_SIZE (test->unexpected_insns)); + +if (expected_insn_cnt ==0 && unexpected_insn_cnt ==0 ) + goto out; + +if (get_xlated_program (fd_prog, &buf, &cnt)) { +printf ("FAIL: can't get xlated program\n" ); + result = false; + goto out; + } + +int expected_idx =find_insn_subseq (buf, test->expected_insns, + cnt, expected_insn_cnt); +int unexpected_idx =find_insn_subseq (buf, test->unexpected_insns, + cnt, unexpected_insn_cnt); + +if (expected_insn_cnt >0 && expected_idx <0 ) { +printf ("FAIL: can't find expected subsequence of instructions\n" ); + result = false; +if (verbose) { +printf ("Program:\n" ); +print_insn (buf, cnt,-1 ,-1 ); +printf ("Expected subsequence:\n" ); +print_insn (test->expected_insns, expected_insn_cnt,-1 ,-1 ); + } + } + +if (unexpected_insn_cnt >0 && unexpected_idx >=0 ) { +printf ("FAIL: found unexpected subsequence of instructions\n" ); + result = false; +if (verbose) +print_insn (buf, cnt, unexpected_idx, unexpected_insn_cnt); + } + +free (buf); + out: +return result; +} +static void do_test_single (struct bpf_test *test,bool unpriv,int *passes,int *errors) {
@@ -1262,6 +1441,9 @@ static void do_test_single(struct bpf_test *test, bool unpriv,if (verbose)printf (", verifier log:\n%s" , bpf_vlog); +if (!check_xlated_program (test, fd_prog)) + goto fail_log; + run_errs =0 ; run_successes =0 ;if (!alignment_prevented_execution && fd_prog >=0 && test->runs >=0 ) {
Signature · 1 line
2.25.1
Eduard Zingerman
maintainerPATCH 2/3 selftests/bpf: allow BTF specs and func infos in test_verifier tests
In reply to Eduard ZingermanThe BTF and func_info specification for test_verifier tests follows the same notation as in prog_tests/btf.c tests. E.g.:
...
.func_info = { { 0, 6 }, { 8, 7 } },
.func_info_cnt = 2,
.btf_strings = "\0int\0",
.btf_types = {
BTF_TYPE_INT_ENC(1, BTF_INT_SIGNED, 0, 32, 4),
BTF_PTR_ENC(1), },
...
The BTF specification is loaded only when specified.
--- tools/testing/selftests/bpf/prog_tests/btf.c | 1 - tools/testing/selftests/bpf/test_btf.h | 2 + tools/testing/selftests/bpf/test_verifier.c | 110 ++++++++++++++++--- 3 files changed, 95 insertions(+), 18 deletions(-)
tools/testing/selftests/bpf/prog_tests/btf.c +0 -1
@@ -34,7 +34,6 @@ static bool always_log;#undef CHECK #define CHECK(condition, format...) _CHECK(condition, "check", duration, format) -#define BTF_END_RAW 0xdeadbeef#define NAME_TBD 0xdeadb33f#define NAME_NTH (N ) (0xfffe0000 | N)
tools/testing/selftests/bpf/test_btf.h +2 -0
@@ -4,6 +4,8 @@#ifndef _TEST_BTF_H#define _TEST_BTF_H +#define BTF_END_RAW 0xdeadbeef +#define BTF_INFO_ENC (kind, kind_flag, vlen) \ ((!!(kind_flag) << 31) | ((kind) << 24) | ((vlen) & BTF_MAX_VLEN))
tools/testing/selftests/bpf/test_verifier.c +93 -17
198 lines not on this page · raw message
Signature · 1 line
2.25.1
Eduard Zingerman
maintainerPATCH 3/3 bpf: Inline calls to bpf_loop when callback is known
In reply to Eduard ZingermanCalls to bpf_loop are replaced with direct loops to avoid indirection. E.g. the following:
bpf_loop(10, foo, NULL, 0);
Is replaced by equivalent of the following:
for (int i = 0; i < 10; ++i)
foo(i, NULL);This transformation could be applied when:
- callback is known and does not change during program execution;
- flags passed to bpf_loop are always zero.
Inlining logic works as follows:
- During execution simulation function update_loop_inline_state tracks the following information for each bpf_loop call instruction:
- is callback known and constant?
- are flags constant and zero?
- Function adjust_stack_depth_for_loop_inlining increases stack depth for functions where bpf_loop calls could be inlined. This is needed to spill registers R6, R7 and R8. These registers are used as loop counter, loop maximal bound and callback context parameter;
- Function inline_bpf_loop called from do_misc_fixups replaces bpf_loop calls fit for inlining with corresponding loop instructions.
Measurements using benchs/run_bench_bpf_loop.sh inside QEMU / KVM on i7-4710HQ CPU show a drop in latency from 14 ns/op to 2 ns/op.
--- include/linux/bpf_verifier.h | 15 ++ kernel/bpf/bpf_iter.c | 9 +- kernel/bpf/verifier.c | 177 ++++++++++++++- .../selftests/bpf/prog_tests/bpf_loop.c | 21 ++ tools/testing/selftests/bpf/progs/bpf_loop.c | 38 ++++ .../selftests/bpf/verifier/bpf_loop_inline.c | 206 ++++++++++++++++++ 6 files changed, 457 insertions(+), 9 deletions(-) create mode 100644 tools/testing/selftests/bpf/verifier/bpf_loop_inline.c
include/linux/bpf_verifier.h +15 -0
@@ -20,6 +20,8 @@#define BPF_MAX_VAR_SIZ (1 << 29)/* size of type_str_buf in bpf_verifier. */ #define TYPE_STR_BUF_LEN 64 +/* Maximum number of loops for bpf_loop */ +#define BPF_MAX_LOOPS BIT(23)/* Liveness marks, used for registers and spilled-regs (in stack slots). * Read marks propagate upwards until they find a write mark; they record that
@@ -344,6 +346,15 @@ struct bpf_verifier_state_list {int miss_cnt, hit_cnt; }; +struct bpf_loop_inline_state { +u32 initialized:1 ;/* set to true upon first entry */ +u32 callback_is_constant:1 ;/* true if callback function +* is the same at each call +*/ +u32 flags_is_zero:1 ;/* true if flags register is zero at each call */ +u32 callback_subprogno;/* valid when callback_is_constant == 1 */ +}; +/* Possible states for alu_state member. */ #define BPF_ALU_SANITIZE_SRC (1U << 0)#define BPF_ALU_SANITIZE_DST (1U << 1)
@@ -380,6 +391,10 @@ struct bpf_insn_aux_data {bool sanitize_stack_spill;/* subject to Spectre v4 sanitation */ bool zext_dst;/* this insn zero extends dst reg */ u8 alu_state;/* used in combination with alu_limit */ +/* if instruction is a call to bpf_loop this field tracks +* the state of the relevant registers to take decision about inlining +*/ +struct bpf_loop_inline_state loop_inline_state;/* below fields are initialized once */ unsigned int orig_idx;/* original instruction index */
kernel/bpf/bpf_iter.c +5 -4
@@ -6,6 +6,7 @@#include <linux/filter.h> #include <linux/bpf.h> #include <linux/rcupdate_trace.h> +#include <linux/bpf_verifier.h> struct bpf_iter_target_info {struct list_head list;
@@ -723,9 +724,6 @@ const struct bpf_func_proto bpf_for_each_map_elem_proto = { .arg4_type = ARG_ANYTHING, }; -/* maximum number of loops */ -#define MAX_LOOPS BIT(23) - BPF_CALL_4(bpf_loop, u32, nr_loops, void *, callback_fn, void *, callback_ctx, u64, flags) {
@@ -733,9 +731,12 @@ BPF_CALL_4(bpf_loop, u32, nr_loops, void *, callback_fn, void *, callback_ctx,u64 ret;u32 i; +/* note: these safety checks are also verified when bpf_loop is inlined, +* be careful to modify this code in sync +*/ if (flags)return -EINVAL ; -if (nr_loops >MAX_LOOPS ) +if (nr_loops >BPF_MAX_LOOPS )return -E2BIG ;for (i =0 ; i < nr_loops; i++) {
kernel/bpf/verifier.c +172 -5
225 lines not on this page · raw message
tools/testing/selftests/bpf/prog_tests/bpf_loop.c +21 -0
@@ -120,6 +120,25 @@ static void check_nested_calls(struct bpf_loop *skel)bpf_link__destroy (link); } +static void check_non_constant_callback (struct bpf_loop *skel) +{ +struct bpf_link *link; + + link =bpf_program__attach (skel->progs.prog_non_constant_callback); +if (!ASSERT_OK_PTR (link,"link" )) +return ; + + skel->bss->callback_selector =0x0F ; +usleep (1 ); +ASSERT_EQ (skel->bss->g_output,0x0F ,"g_output #1" ); + + skel->bss->callback_selector =0xF0 ; +usleep (1 ); +ASSERT_EQ (skel->bss->g_output,0xF0 ,"g_output #2" ); + +bpf_link__destroy (link); +} +void test_bpf_loop (void ) {struct bpf_loop *skel;
@@ -140,6 +159,8 @@ void test_bpf_loop(void)check_invalid_flags (skel);if (test__start_subtest ("check_nested_calls" ))check_nested_calls (skel); +if (test__start_subtest ("check_non_constant_callback" )) +check_non_constant_callback (skel);bpf_loop__destroy (skel); }
tools/testing/selftests/bpf/progs/bpf_loop.c +38 -0
@@ -16,6 +16,7 @@ u32 nested_callback_nr_loops;u32 stop_index =-1 ;u32 nr_loops;int pid; +int callback_selector;/* Making these global variables so that the userspace program * can verify the output through the skeleton
@@ -111,3 +112,40 @@ int prog_nested_calls(void *ctx)return 0 ; } + +static int callback_set_f0 (int i,void *ctx) +{ + g_output =0xF0 ; +return 0 ; +} + +static int callback_set_0f (int i,void *ctx) +{ + g_output =0x0F ; +return 0 ; +} + +/* +* non-constant callback is a corner case for bpf_loop inline logic +*/ +SEC("fentry/" SYS_PREFIX "sys_nanosleep") +int prog_non_constant_callback (void *ctx) +{ +struct callback_ctx data = {}; + +if (bpf_get_current_pid_tgid () >>32 != pid) +return 0 ; + +int (*callback)(int i,void *ctx); + + g_output =0 ; + +if (callback_selector ==0x0F ) + callback = callback_set_0f; +else + callback = callback_set_f0; + +bpf_loop (1 , callback,NULL ,0 ); + +return 0 ; +}
tools/testing/selftests/bpf/verifier/bpf_loop_inline.c new file +206 -0
206 lines not on this page · raw message
Signature · 1 line
2.25.1
kernel test robot
build botRe: [PATCH bpf-next 3/3] bpf: Inline calls to bpf_loop when callback is known
Thank you for the patch! Perhaps something to improve:
Hi Eduard,
Thank you for the patch! Perhaps something to improve:
[auto build test WARNING on bpf-next/master]
url: https://github.com/intel-lab-lkp/linux/commits/Eduard-Zingerman/bpf_loop-inlining/20220528-075454 base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master config: x86_64-randconfig-a013 (https://download.01.org/0day-ci/archive/20220528/[email protected]/config) compiler: gcc-11 (Debian 11.3.0-1) 11.3.0 reproduce (this is a W=1 build): # https://github.com/intel-lab-lkp/linux/commit/05c5a11449d4e5c75ada599a71d8290bef8a5d1a git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Eduard-Zingerman/bpf_loop-inlining/20220528-075454 git checkout 05c5a11449d4e5c75ada599a71d8290bef8a5d1a # save the config file mkdir build_dir && cp config build_dir/.config make W=1 O=build_dir ARCH=x86_64 SHELL=/bin/bash kernel/bpf/
If you fix the issue, kindly add following tag where applicable
All warnings (new ones prefixed by >>):
kernel/bpf/verifier.c: In function 'adjust_stack_depth_for_loop_inlining':
>> kernel/bpf/verifier.c:7127:16: warning: variable 'subprog_start' set but not used [-Wunused-but-set-variable]
7127 | int i, subprog_start, subprog_end, cur_subprog = 0;
| ^~~~~~~~~~~~~vim +/subprog_start +7127 kernel/bpf/verifier.c
7117
7118 /* For all sub-programs in the program (including main) checks
7119 * insn_aux_data to see if there are bpf_loop calls that require
7120 * inlining. If such calls are found subprog stack_depth is increased
7121 * by the size of 3 registers. Reserved space would be used in the
7122 * do_misc_fixups to spill values of the R6, R7, R8 to use these
7123 * registers for loop iteration.
7124 */
7125 static void adjust_stack_depth_for_loop_inlining(struct bpf_verifier_env *env)
7126 {
> 7127 int i, subprog_start, subprog_end, cur_subprog = 0;
7128 struct bpf_subprog_info *subprog = env->subprog_info;
7129 int insn_cnt = env->prog->len;
7130
7131 subprog_start = subprog[cur_subprog].start;
7132 subprog_end = env->subprog_cnt > 1
7133 ? subprog[cur_subprog + 1].start
7134 : insn_cnt;
7135 for (i = 0; i < insn_cnt; i++) {
7136 if (fit_for_bpf_loop_inline(&env->insn_aux_data[i])) {
7137 /* reserve space for 3 registers */
7138 subprog->stack_depth += BPF_REG_SIZE * 3;
7139 /* skip to the next subprog */
7140 i = subprog_end - 1;
7141 }
7142 if (i == subprog_end - 1) {
7143 subprog_start = subprog_end;
7144 cur_subprog++;
7145 if (cur_subprog < env->subprog_cnt)
7146 subprog_end = subprog[cur_subprog + 1].start;
7147 }
7148 }
7149
7150 env->prog->aux->stack_depth = env->subprog_info[0].stack_depth;
7151 }
7152
Signature · 2 lines
0-DAY CI Kernel Test Service https://01.org/lkp