bpf_loop inlining
Subsystems (5)
Eduard Zingerman
maintainerv2 cover 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 show 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 commit messages for each patch. Hope you will find this useful.
Note: the newly added bpf_loop/check_stack test case in test_progs currently fails in no_alu32 mode with error "BPF program is too large". I'm still investigating this issue. Nevertheless, I'm still interested in overall feedback for the patch.
Best regards,
Eduard Zingerman
Changes since v1:
- allow to use SKIP_INSNS in instruction pattern specification in test_verifier tests;
- fix for a bug in spill offset assignement for loop vars when bpf_loop is located in a non-main function.
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 | 16 + kernel/bpf/bpf_iter.c | 9 +- kernel/bpf/verifier.c | 184 +++++++++- .../selftests/bpf/prog_tests/bpf_loop.c | 62 ++++ tools/testing/selftests/bpf/prog_tests/btf.c | 1 - tools/testing/selftests/bpf/progs/bpf_loop.c | 122 +++++++ tools/testing/selftests/bpf/test_btf.h | 2 + tools/testing/selftests/bpf/test_verifier.c | 338 +++++++++++++++++- .../selftests/bpf/verifier/bpf_loop_inline.c | 244 +++++++++++++ 9 files changed, 951 insertions(+), 27 deletions(-) create mode 100644 tools/testing/selftests/bpf/verifier/bpf_loop_inline.c
Signature · 1 line
2.25.1
Eduard Zingerman
maintainerPATCH v2 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 sequence of instructions expected to be found in the program;
- unexpected_insn field specifies a sequence 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.
- SKIP_INSNS could be used to specify that some instructions in the (un)expected pattern are not important (behavior similar to usage of \t in errstr field).
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_ALU64_IMM(BPF_MOV, BPF_REG_1, 1),
SKIP_INSN(),
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 move of 1 to register 1 would remain in place and helper function call instruction would be replaced by a relative call instruction.
--- tools/testing/selftests/bpf/test_verifier.c | 228 ++++++++++++++++++++ 1 file changed, 228 insertions(+)
tools/testing/selftests/bpf/test_verifier.c +228 -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,10 @@#define POINTER_VALUE 0xcafe4all#define TEST_DATA_LEN 64 +#define INSN_OFF_MASK ((s16)0xFFFF) +#define INSN_IMM_MASK ((s32)0xFFFFFFFF) +#define SKIP_INSNS () BPF_RAW_INSN(0xde, 0xa, 0xd, 0xbeef, 0xdeadbeef) +#define F_NEEDS_EFFICIENT_UNALIGNED_ACCESS (1 << 0)#define F_LOAD_WITH_STRICT_ALIGNMENT (1 << 1)
@@ -79,6 +85,19 @@ 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. +* +* The sequence could be split into sub-sequences by adding +* SKIP_INSNS instruction at the end of each sub-sequence. In +* such case sub-sequences are searched for one after another. +*/ +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 +1145,212 @@ 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 bool is_skip_insn (struct bpf_insn *insn) +{ +struct bpf_insn skip_insn =SKIP_INSNS (); + +return memcmp (insn, &skip_insn,sizeof (skip_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 int find_skip_insn_marker (struct bpf_insn *seq,int len) +{ +for (int i =0 ; i < len; ++i) +if (is_skip_insn (&seq[i])) +return i; + +return -1 ; +} + +/* Return true if all sub-sequences in `subseqs` could be found in +* `seq` one after another. Sub-sequences are separated by a single +* nil instruction. +*/ +static bool find_all_insn_subseqs (struct bpf_insn *seq,struct bpf_insn *subseqs, +int seq_len,int max_subseqs_len) +{ +int subseqs_len =null_terminated_insn_len (subseqs, max_subseqs_len); + +while (subseqs_len >0 ) { +int skip_idx =find_skip_insn_marker (subseqs, subseqs_len); +int cur_subseq_len = skip_idx <0 ? subseqs_len : skip_idx; +int subseq_idx =find_insn_subseq (seq, subseqs, + seq_len, cur_subseq_len); + +if (subseq_idx <0 ) +return false; + seq += subseq_idx + cur_subseq_len; + seq_len -= subseq_idx + cur_subseq_len; + subseqs += cur_subseq_len +1 ; + subseqs_len -= cur_subseq_len +1 ; + } + +return true; +} + +static void print_insn (struct bpf_insn *buf,int cnt) +{ +printf (" addr op d s off imm\n" ); +for (int i =0 ; i < cnt; ++i) { +struct bpf_insn *insn = &buf[i]; + +if (is_null_insn (insn)) +break ; + +if (is_skip_insn (insn)) +printf (" ...\n" ); +else +printf (" %04x: %02x %1x %x %04hx %08x\n" , + 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; +bool check_expected = !is_null_insn (test->expected_insns); +bool check_unexpected = !is_null_insn (test->unexpected_insns); + +if (!check_expected && !check_unexpected) + goto out; + +if (get_xlated_program (fd_prog, &buf, &cnt)) { +printf ("FAIL: can't get xlated program\n" ); + result = false; + goto out; + } + +if (check_expected && + !find_all_insn_subseqs (buf, test->expected_insns, + cnt,MAX_EXPECTED_INSNS )) { +printf ("FAIL: can't find expected subsequence of instructions\n" ); + result = false; +if (verbose) { +printf ("Program:\n" ); +print_insn (buf, cnt); +printf ("Expected subsequence:\n" ); +print_insn (test->expected_insns,MAX_EXPECTED_INSNS ); + } + } + +if (check_unexpected && +find_all_insn_subseqs (buf, test->unexpected_insns, + cnt,MAX_UNEXPECTED_INSNS )) { +printf ("FAIL: found unexpected subsequence of instructions\n" ); + result = false; +if (verbose) { +printf ("Program:\n" ); +print_insn (buf, cnt); +printf ("Un-expected subsequence:\n" ); +print_insn (test->unexpected_insns,MAX_UNEXPECTED_INSNS ); + } + } + +free (buf); + out: +return result; +} +static void do_test_single (struct bpf_test *test,bool unpriv,int *passes,int *errors) {
@@ -1262,6 +1487,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
Re: [PATCH bpf-next v2 1/3] selftests/bpf: specify expected instructions in test_verifier tests
Quoted 18 lines · Eduard Zingerman
Allows to specify expected and unexpected instruction sequences in
test_verifiertest cases. The instructions are requested from kernel after BPF program loading, thus allowing to check some of the transformations applied by BPF verifier.-
expected_insnfield specifies a sequence of instructions expected to be found in the program;
-unexpected_insnfield specifies a sequence of instructions that are not expected to be found in the program;
-INSN_OFF_MASKandINSN_IMM_MASKvalues could be used to maskoffandimmfields.
-SKIP_INSNScould be used to specify that some instructions in the (un)expected pattern are not important (behavior similar to usage of\tinerrstrfield).The intended usage is as follows:
Quoted 16 lines
{
"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_ALU64_IMM(BPF_MOV, BPF_REG_1, 1),
SKIP_INSN(),nit: SKIP_INSNS(),
Quoted 17 lines
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 move of 1 to register 1 would remain in place and helper function call instruction would be replaced by a relative call instruction.
Signed-off-by Eduard Zingerman <[email protected]>---
[...]
tools/testing/selftests/bpf/test_verifier.c static bool cmp_str_seq(const char *log, const char *exp) · line 1154
Quoted 8 lines
tools/testing/selftests/bpf/test_verifier.c +7
-0+static __u32 roundup_u32 (__u32 number,__u32 divisor) +{ +if (number % divisor ==0 ) +return number / divisor; +else +return number / divisor +1 ; +}
Do we really need this roundup? If so, maybe give it a different name?
Quoted 23 lines
tools/testing/selftests/bpf/test_verifier.c +23
-0+ +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));
tools/testing/selftests/bpf/test_verifier.c static bool cmp_str_seq(const char *log, const char *exp) · line 1187
Quoted 10 lines
tools/testing/selftests/bpf/test_verifier.c +10
-0+ 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;
Maybe just return 0 here, and ...
tools/testing/selftests/bpf/test_verifier.c static bool cmp_str_seq(const char *log, const char *exp) · line 1191
Quoted 4 lines
tools/testing/selftests/bpf/test_verifier.c +4
-0+ + out_free_buf: +free (*buf); + out:
... remove label "out:".
tools/testing/selftests/bpf/test_verifier.c static bool cmp_str_seq(const char *log, const char *exp) · line 1194
Quoted 3 lines
tools/testing/selftests/bpf/test_verifier.c +3
-0+return err; +} +
Other than these, looks good to me.
Eduard Zingerman
maintainerPATCH v2 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
199 lines not on this page · raw message
Signature · 1 line
2.25.1
Song Liu
reviewerRe: [PATCH bpf-next v2 2/3] selftests/bpf: allow BTF specs and func infos in test_verifier tests
Quoted 11 lines · Eduard Zingerman
The BTF and
func_infospecification fortest_verifiertests follows the same notation as inprog_tests/btf.ctests. 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),
Quoted 6 lines
},
...The BTF specification is loaded only when specified.
Signed-off-by Eduard Zingerman <[email protected]>
This can be very useful! Thanks!
Quoted 5 lines
--- 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(-)
[...]
Quoted 34 lines
tools/testing/selftests/bpf/test_verifier.c +11
-8-static int load_btf (void ) +static char bpf_vlog[UINT_MAX >>8 ]; + +static int load_btf_spec (__u32 *types,int types_len, +const char *strings,int strings_len) {struct btf_header hdr = { .magic =BTF_MAGIC , .version =BTF_VERSION , .hdr_len =sizeof (struct btf_header ), - .type_len =sizeof (btf_raw_types), - .str_off =sizeof (btf_raw_types), - .str_len =sizeof (btf_str_sec), + .type_len = types_len, + .str_off = types_len, + .str_len = strings_len, };void *ptr, *raw_btf;int btf_fd; - ptr = raw_btf =malloc (sizeof (hdr) +sizeof (btf_raw_types) + -sizeof (btf_str_sec)); + raw_btf =malloc (sizeof (hdr) + types_len + strings_len); + ptr = raw_btf;memcpy (ptr, &hdr,sizeof (hdr)); ptr +=sizeof (hdr); -memcpy (ptr, btf_raw_types, hdr.type_len); +memcpy (ptr, types, hdr.type_len); ptr += hdr.type_len; -memcpy (ptr, btf_str_sec, hdr.str_len); +memcpy (ptr, strings, hdr.str_len); ptr += hdr.str_len;
tools/testing/selftests/bpf/test_verifier.c static __u32 btf_raw_types[] = { · line 732
Quoted 10 lines
tools/testing/selftests/bpf/test_verifier.c +7
-2- btf_fd =bpf_btf_load (raw_btf, ptr - raw_btf,NULL ); -free (raw_btf); +LIBBPF_OPTS (bpf_btf_load_opts, opts, + .log_buf = bpf_vlog, + .log_size = sizeof(bpf_vlog), + .log_level = (verbose + ?VERBOSE_LIBBPF_LOG_LEVEL + : DEFAULT_LIBBPF_LOG_LEVEL), + );
Please move the local variable definition to the beginning of the function.
Quoted 12 lines
tools/testing/selftests/bpf/test_verifier.c +9
-2+ + btf_fd =bpf_btf_load (raw_btf, ptr - raw_btf, &opts);if (btf_fd <0 ) -return -1 ; -return btf_fd; +printf ("Failed to load BTF spec: '%s'\n" ,strerror (errno)); + +free (raw_btf); + +return btf_fd <0 ?-1 : btf_fd; +} +
[...]
Quoted 10 lines
tools/testing/selftests/bpf/test_verifier.c +2
-1+static void do_test_single (struct bpf_test *test,bool unpriv,int *passes,int *errors) { -int fd_prog, expected_ret, alignment_prevented_execution; +int fd_prog, btf_fd, expected_ret, alignment_prevented_execution;int prog_len, prog_type = test->prog_type;struct bpf_insn *prog = test->insns;LIBBPF_OPTS (bpf_prog_load_opts, opts);
tools/testing/selftests/bpf/test_verifier.c static void do_test_single(struct bpf_test *test, bool unpriv, · line 1427
Quoted 4 lines
tools/testing/selftests/bpf/test_verifier.c +1
-0@@ -1366,8 +1424,10 @@ static void do_test_single(struct bpf_test *test, bool unpriv,__u32 pflags;int i, err; + fd_prog =-1 ;
This is not really necessary.
Quoted 6 lines
tools/testing/selftests/bpf/test_verifier.c +1
-0for (i =0 ; i <MAX_NR_MAPS ; i++) map_fds[i] =-1 ; + btf_fd =-1 ;if (!prog_type) prog_type =BPF_PROG_TYPE_SOCKET_FILTER ;
[...]
tools/testing/selftests/bpf/test_verifier.c static void do_test_single(struct bpf_test *test, bool unpriv, · line 1618
Quoted 6 lines
tools/testing/selftests/bpf/test_verifier.c +2
-0@@ -1540,6 +1614,8 @@ static void do_test_single(struct bpf_test *test, bool unpriv, fail_log: (*errors)++;printf ("%s" , bpf_vlog); +if (!string_ends_with_nl (bpf_vlog,sizeof (bpf_vlog))) +printf ("\n" );
This seems like overkill. Can we just print "\n" in all cases?
Quoted 6 lines
tools/testing/selftests/bpf/test_verifier.c +0
-1goto close_fds; } --2.25.1
Eduard Zingerman
maintainerRe: [PATCH bpf-next v2 2/3] selftests/bpf: allow BTF specs and func infos in test_verifier tests
Quoted 1 line · Song Liu
Hi Song,
Thanks a lot for the review, I'll apply the suggested changes and provide the v3 in one or two days. My only objection is below.
Quoted 11 lines
tools/testing/selftests/bpf/test_verifier.c static bool check_xlated_program(struct bpf_test *test, int fd_prog)
Quoted 9 lines
{
tools/testing/selftests/bpf/test_verifier.c +2
-1-int fd_prog, expected_ret, alignment_prevented_execution; +int fd_prog, btf_fd, expected_ret, alignment_prevented_execution;int prog_len, prog_type = test->prog_type;struct bpf_insn *prog = test->insns;LIBBPF_OPTS (bpf_prog_load_opts, opts);__u32 pflags;int i, err; + fd_prog =-1 ;This is not really necessary.
Actually this one is necessary to avoid compiler warning, note the following fragment of the do_test_single function below:
static void do_test_single(...)
{
...
if (...) {
btf_fd = load_btf_for_test(...);
if (btf_fd < 0)
goto fail_log;
opts.prog_btf_fd = btf_fd;
}
...
fd_prog = bpf_prog_load(..., &opts);
...
close_fds:
...
close(fd_prog);
close(btf_fd);
...
return;
fail_log:
...
goto close_fds;
}If load_btf_for_test fails the goto fail_log would eventually jump to close_fds, where fd_prog would be in an uninitialised state.
Best regards,
Eduard
Song Liu
reviewerRe: [PATCH bpf-next v2 2/3] selftests/bpf: allow BTF specs and func infos in test_verifier tests
Quoted 46 lines · Eduard Zingerman
Quoted 2 lines · Song Liu
Hi Song,
Thanks a lot for the review, I'll apply the suggested changes and provide the v3 in one or two days. My only objection is below.
Quoted 11 lines
tools/testing/selftests/bpf/test_verifier.c static bool check_xlated_program(struct bpf_test *test, int fd_prog)
Quoted 9 lines
{
tools/testing/selftests/bpf/test_verifier.c +2
-1-int fd_prog, expected_ret, alignment_prevented_execution; +int fd_prog, btf_fd, expected_ret, alignment_prevented_execution;int prog_len, prog_type = test->prog_type;struct bpf_insn *prog = test->insns;LIBBPF_OPTS (bpf_prog_load_opts, opts);__u32 pflags;int i, err; + fd_prog =-1 ;This is not really necessary.
Actually this one is necessary to avoid compiler warning, note the following fragment of the
do_test_singlefunction below:static void
do_test_single(...){ ... if (...) { btf_fd = load_btf_for_test(...); if (btf_fd < 0) goto fail_log; opts.prog_btf_fd = btf_fd; } ... fd_prog = bpf_prog_load(..., &opts); ... close_fds: ... close(fd_prog); close(btf_fd); ... return; fail_log: ... goto close_fds; }
Quoted 2 lines
If
load_btf_for_testfails the gotofail_logwould eventually jump toclose_fds, wherefd_progwould be in an uninitialised state.
Got it. Thanks for the explanation!
Song
Eduard Zingerman
maintainerPATCH v2 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 | 16 ++ kernel/bpf/bpf_iter.c | 9 +- kernel/bpf/verifier.c | 184 ++++++++++++- .../selftests/bpf/prog_tests/bpf_loop.c | 62 +++++ tools/testing/selftests/bpf/progs/bpf_loop.c | 122 +++++++++ .../selftests/bpf/verifier/bpf_loop_inline.c | 244 ++++++++++++++++++ 6 files changed, 628 insertions(+), 9 deletions(-) create mode 100644 tools/testing/selftests/bpf/verifier/bpf_loop_inline.c
include/linux/bpf_verifier.h +16 -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,16 @@ 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 */ +s32 stack_base;/* stack offset for loop vars */ +}; +/* Possible states for alu_state member. */ #define BPF_ALU_SANITIZE_SRC (1U << 0)#define BPF_ALU_SANITIZE_DST (1U << 1)
@@ -380,6 +392,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 +179 -5
232 lines not on this page · raw message
tools/testing/selftests/bpf/prog_tests/bpf_loop.c +62 -0
74 lines not on this page · raw message
tools/testing/selftests/bpf/progs/bpf_loop.c +122 -0
136 lines not on this page · raw message
tools/testing/selftests/bpf/verifier/bpf_loop_inline.c new file +244 -0
244 lines not on this page · raw message
Signature · 1 line
2.25.1
Song Liu
reviewerRe: [PATCH bpf-next v2 3/3] bpf: Inline calls to bpf_loop when callback is known
Quoted 1 line · Eduard Zingerman
[...]
Quoted 2 lines
Signed-off-by Eduard Zingerman <[email protected]>
Please put kernel changes, test_prog changes, and test_verifier changes to 3 separate patches.
Quoted 9 lines
--- include/linux/bpf_verifier.h | 16 ++ kernel/bpf/bpf_iter.c | 9 +- kernel/bpf/verifier.c | 184 ++++++++++++- .../selftests/bpf/prog_tests/bpf_loop.c | 62 +++++ tools/testing/selftests/bpf/progs/bpf_loop.c | 122 +++++++++ .../selftests/bpf/verifier/bpf_loop_inline.c | 244 ++++++++++++++++++ 6 files changed, 628 insertions(+), 9 deletions(-) create mode 100644 tools/testing/selftests/bpf/verifier/bpf_loop_inline.c
[...]
kernel/bpf/verifier.c static int check_get_func_ip(struct bpf_verifier_env *env) · line 7130
Quoted 13 lines
kernel/bpf/verifier.c +13
-0+/* For all sub-programs in the program (including main) checks +* insn_aux_data to see if there are bpf_loop calls that require +* inlining. If such calls are found subprog stack_depth is increased +* by the size of 3 registers. Reserved space would be used in the +* do_misc_fixups to spill values of the R6, R7, R8 to use these +* registers for loop iteration. +*/ +static void adjust_stack_depth_for_loop_inlining (struct bpf_verifier_env *env) +{ +int i, subprog_end, cur_subprog =0 ; +struct bpf_subprog_info *subprogs = env->subprog_info; +int insn_cnt = env->prog->len; +bool proc_updated = false;
nit: I guess this should be called subprog_updated?
Quoted 8 lines
kernel/bpf/verifier.c +8
-0+s32 stack_base; + + subprog_end = (env->subprog_cnt >1 + ? subprogs[cur_subprog +1 ].start + : insn_cnt); +for (i =0 ; i < insn_cnt; i++) { +struct bpf_insn_aux_data *aux = &env->insn_aux_data[i]; +
[...]
Quoted 57 lines
kernel/bpf/verifier.c +57
-0+static struct bpf_prog *inline_bpf_loop (struct bpf_verifier_env *env, +int position,u32 *cnt) +{ +struct bpf_insn_aux_data *aux = &env->insn_aux_data[position]; +s32 stack_base = aux->loop_inline_state.stack_base; +s32 r6_offset = stack_base +0 *BPF_REG_SIZE ; +s32 r7_offset = stack_base +1 *BPF_REG_SIZE ; +s32 r8_offset = stack_base +2 *BPF_REG_SIZE ; +int reg_loop_max =BPF_REG_6 ; +int reg_loop_cnt =BPF_REG_7 ; +int reg_loop_ctx =BPF_REG_8 ; + +struct bpf_prog *new_prog; +u32 callback_subprogno = aux->loop_inline_state.callback_subprogno; +u32 callback_start; +u32 call_insn_offset; +s32 callback_offset; + +struct bpf_insn insn_buf[] = { +/* Return error and jump to the end of the patch if +* expected number of iterations is too big. This +* repeats the check done in bpf_loop helper function, +* be careful to modify this code in sync. +*/ +BPF_JMP_IMM (BPF_JLE ,BPF_REG_1 ,BPF_MAX_LOOPS ,2 ), +BPF_MOV32_IMM (BPF_REG_0 , -E2BIG ), +BPF_JMP_IMM (BPF_JA ,0 ,0 ,16 ), +/* spill R6, R7, R8 to use these as loop vars */ +BPF_STX_MEM (BPF_DW ,BPF_REG_10 ,BPF_REG_6 , r6_offset), +BPF_STX_MEM (BPF_DW ,BPF_REG_10 ,BPF_REG_7 , r7_offset), +BPF_STX_MEM (BPF_DW ,BPF_REG_10 ,BPF_REG_8 , r8_offset), +/* initialize loop vars */ +BPF_MOV64_REG (reg_loop_max,BPF_REG_1 ), +BPF_MOV32_IMM (reg_loop_cnt,0 ), +BPF_MOV64_REG (reg_loop_ctx,BPF_REG_3 ), +/* loop header, +* if reg_loop_cnt >= reg_loop_max skip the loop body +*/ +BPF_JMP_REG (BPF_JGE , reg_loop_cnt, reg_loop_max,5 ), +/* callback call, +* correct callback offset would be set after patching +*/ +BPF_MOV64_REG (BPF_REG_1 , reg_loop_cnt), +BPF_MOV64_REG (BPF_REG_2 , reg_loop_ctx), +BPF_CALL_REL (0 ), +/* increment loop counter */ +BPF_ALU64_IMM (BPF_ADD , reg_loop_cnt,1 ), +/* jump to loop header if callback returned 0 */ +BPF_JMP_IMM (BPF_JEQ ,BPF_REG_0 ,0 ,-6 ), +/* return value of bpf_loop, +* set R0 to the number of iterations +*/ +BPF_MOV64_REG (BPF_REG_0 , reg_loop_cnt), +/* restore original values of R6, R7, R8 */ +BPF_LDX_MEM (BPF_DW ,BPF_REG_6 ,BPF_REG_10 , r6_offset), +BPF_LDX_MEM (BPF_DW ,BPF_REG_7 ,BPF_REG_10 , r7_offset), +BPF_LDX_MEM (BPF_DW ,BPF_REG_8 ,BPF_REG_10 , r8_offset),
kernel/bpf/verifier.c static int fixup_kfunc_call(struct bpf_verifier_env *env, · line 13928
Quoted 10 lines
kernel/bpf/verifier.c +10
-0+ }; + + *cnt =ARRAY_SIZE (insn_buf); + new_prog =bpf_patch_insn_data (env, position, insn_buf, *cnt); +if (!new_prog) +return new_prog; + +/* callback start is known only after patching */ + callback_start = env->subprog_info[callback_subprogno].start; + call_insn_offset = position +12 ;
IIUC, magic number 12 is calculated based on the content of insn_buf. Can we make this more robust for future changes? We should at least add a comment here.
Quoted 62 lines
kernel/bpf/verifier.c +21
-0+ callback_offset = callback_start - call_insn_offset -1 ; + env->prog->insnsi[call_insn_offset].imm = callback_offset; + +return new_prog; +} +/* Do various post-verification rewrites in a single program pass. * These rewrites simplify JIT and interpreter implementations. */ @@ -14258,6 +14417,18 @@ static int do_misc_fixups(struct bpf_verifier_env *env)continue ; } +if (insn->imm == BPF_FUNC_loop && +fit_for_bpf_loop_inline (&env->insn_aux_data[i + delta])) { + new_prog =inline_bpf_loop (env, i + delta, &cnt); +if (!new_prog) +return -ENOMEM ; + + delta += cnt -1 ; + env->prog = prog = new_prog; + insn = new_prog->insnsi + i + delta; +continue ; + } + patch_call_imm: fn = env->ops->get_func_proto (insn->imm, env->prog);/* all functions that have prototype and verifier allowed @@ -15030,6 +15201,9 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr)if (ret ==0 ) ret =check_max_stack_depth (env); +if (ret ==0 ) +adjust_stack_depth_for_loop_inlining (env); +/* instruction rewrites happen after this point */ if (is_priv) {if (ret ==0 )tools/testing/selftests/bpf/prog_tests/bpf_loop.c +16
-0@@ -120,6 +120,64 @@ 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 = +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" ); +
tools/testing/selftests/bpf/prog_tests/bpf_loop.c static void check_nested_calls(struct bpf_loop *skel) · line 148
Quoted 10 lines
tools/testing/selftests/bpf/prog_tests/bpf_loop.c +10
-0+bpf_link__destroy (link); +} + +static void check_stack (struct bpf_loop *skel) +{ +struct bpf_link *link = +bpf_program__attach (skel->progs.stack_check); + +if (!ASSERT_OK_PTR (link,"link" )) + goto out;
We can just return here.
tools/testing/selftests/bpf/prog_tests/bpf_loop.c static void check_nested_calls(struct bpf_loop *skel) · line 150
Quoted 2 lines
tools/testing/selftests/bpf/prog_tests/bpf_loop.c +2
-0+ +int map_fd =bpf_map__fd (skel->maps.map1);
Please move variable definition to the beginning of the function.
Quoted 16 lines
tools/testing/selftests/bpf/prog_tests/bpf_loop.c +16
-0+ +if (!ASSERT_GE (map_fd,0 ,"bpf_map__fd" )) + goto out; + +for (int key =1 ; key <=16 ; ++key) { +int val = key; +int err =bpf_map_update_elem (map_fd, &key, &val,BPF_NOEXIST ); + +if (!ASSERT_OK (err,"bpf_map_update_elem" )) + goto out; + } + +usleep (1 ); + +for (int key =1 ; key <=16 ; ++key) { +int val;
tools/testing/selftests/bpf/prog_tests/bpf_loop.c static void check_nested_calls(struct bpf_loop *skel) · line 176
Quoted 10 lines
tools/testing/selftests/bpf/prog_tests/bpf_loop.c +10
-0+int err =bpf_map_lookup_elem (map_fd, &key, &val); + +if (!ASSERT_OK (err,"bpf_map_lookup_elem" )) + goto out; +ASSERT_EQ (val, key +1 ,"bad value in the map" ); + } + +out: +if (map_fd >=0 ) +close (map_fd);
map1 is part of the skeleton, we should not close it here.
tools/testing/selftests/bpf/prog_tests/bpf_loop.c static void check_nested_calls(struct bpf_loop *skel) · line 178
Quoted 2 lines
tools/testing/selftests/bpf/prog_tests/bpf_loop.c +2
-0+if (link) +bpf_link__destroy (link);
No need to check "if (link)"
Quoted 16 lines
tools/testing/selftests/bpf/prog_tests/bpf_loop.c +6
-0+} +void test_bpf_loop (void ) {struct bpf_loop *skel;@@ -140,6 +198,10 @@ 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); +if (test__start_subtest ("check_stack" )) +check_stack (skel);bpf_loop__destroy (skel); }
[...]