Re: [PATCH bpf-next v2 3/3] bpf: Inline calls to bpf_loop when callback is known

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Sun, May 29, 2022 at 3:37 PM Eduard Zingerman <eddyz87@xxxxxxxxx> wrote:
>
[...]
>
> Signed-off-by: Eduard Zingerman <eddyz87@xxxxxxxxx>

Please put kernel changes, test_prog changes, and test_verifier changes to 3
separate patches.

> ---
>  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
[...]
> +/* 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?

> +       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];
> +

[...]

> +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),
> +       };
> +
> +       *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.

> +       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)
> diff --git a/tools/testing/selftests/bpf/prog_tests/bpf_loop.c b/tools/testing/selftests/bpf/prog_tests/bpf_loop.c
> index 380d7a2072e3..1caa495be48c 100644
> --- a/tools/testing/selftests/bpf/prog_tests/bpf_loop.c
> +++ b/tools/testing/selftests/bpf/prog_tests/bpf_loop.c
> @@ -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");
> +
> +       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.

> +
> +       int map_fd = bpf_map__fd(skel->maps.map1);
Please move variable definition to the beginning of the function.

> +
> +       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;
> +               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.

> +       if (link)
> +               bpf_link__destroy(link);

No need to check "if (link)"

> +}
> +
>  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);
>  }

[...]



[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux