diff mbox series

[bpf-next,2/3] bpf: Remove 'may_goto 0' instruction

Message ID 20250116055134.604867-1-yonghong.song@linux.dev (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series bpf: Allow 'may_goto 0' instruction | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-PR success PR summary
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1 this patch: 1
netdev/build_tools success No tools touched, skip
netdev/cc_maintainers warning 8 maintainers not CCed: song@kernel.org sdf@fomichev.me kpsingh@kernel.org haoluo@google.com jolsa@kernel.org eddyz87@gmail.com martin.lau@linux.dev john.fastabend@gmail.com
netdev/build_clang success Errors and warnings before: 109 this patch: 109
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 11 this patch: 11
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 48 lines checked
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for s390x-gcc / veristat-kernel
bpf/vmtest-bpf-next-VM_Test-18 success Logs for s390x-gcc / veristat-meta
bpf/vmtest-bpf-next-VM_Test-19 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-17 / veristat-meta
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-17 / veristat-kernel
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-48 success Logs for x86_64-llvm-18 / veristat-kernel
bpf/vmtest-bpf-next-VM_Test-49 success Logs for x86_64-llvm-18 / veristat-meta
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-gcc / veristat-kernel / x86_64-gcc veristat_kernel
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-gcc / veristat-meta / x86_64-gcc veristat_meta
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-47 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-15 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-gcc / test (test_progs-bpf_gcc, false, 360) / test_progs-bpf_gcc on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-17 / test (test_progs-bpf_gcc, false, 360) / test_progs-bpf_gcc on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-42 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-43 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-45 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-46 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-44 success Logs for x86_64-llvm-18 / test (test_progs-bpf_gcc, false, 360) / test_progs-bpf_gcc on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-7 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-8 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat-kernel
bpf/vmtest-bpf-next-VM_Test-11 success Logs for aarch64-gcc / veristat-meta
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-14 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-16 success Logs for x86_64-gcc / build-release

Commit Message

Yonghong Song Jan. 16, 2025, 5:51 a.m. UTC
Since 'may_goto 0' insns are actually no-op, let us remove them.
Otherwise, verifier will generate code like
   /* r10 - 8 stores the implicit loop count */
   r11 = *(u64 *)(r10 -8)
   if r11 == 0x0 goto pc+2
   r11 -= 1
   *(u64 *)(r10 -8) = r11

which is the pure overhead.

The following code patterns (from the previous commit) are also
handled:
   may_goto 2
   may_goto 1
   may_goto 0

With this commit, the above three 'may_goto' insns are all
eliminated.

Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
---
 kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++++++
 1 file changed, 36 insertions(+)

Comments

Eduard Zingerman Jan. 16, 2025, 7:42 p.m. UTC | #1
On Wed, 2025-01-15 at 21:51 -0800, Yonghong Song wrote:
> Since 'may_goto 0' insns are actually no-op, let us remove them.
> Otherwise, verifier will generate code like
>    /* r10 - 8 stores the implicit loop count */
>    r11 = *(u64 *)(r10 -8)
>    if r11 == 0x0 goto pc+2
>    r11 -= 1
>    *(u64 *)(r10 -8) = r11
> 
> which is the pure overhead.
>
> The following code patterns (from the previous commit) are also
> handled:
>    may_goto 2
>    may_goto 1
>    may_goto 0
> 
> With this commit, the above three 'may_goto' insns are all
> eliminated.
> 
> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
> ---

Technically this is a side-effect, it subtracts 1 from total loop budget.
An alternative transformation might be:

    r11 = *(u64 *)(r10 -8)
    if r11 == 0x0 goto pc+2
    r11 -= 3     <---------------- note 3 here
    *(u64 *)(r10 -8) = r11

On the other hand, it looks like there is no way to trick verifier
into an infinite loop by removing these statements, so this should be
safe modulo exceeding the 8M iterations budget.

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

>  kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++++++
>  1 file changed, 36 insertions(+)
> 
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index edf3cc42a220..72b474bfba2d 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -20133,6 +20133,40 @@ static int opt_remove_nops(struct bpf_verifier_env *env)
>  	return 0;
>  }
>  
> +static int opt_remove_useless_may_gotos(struct bpf_verifier_env *env)
> +{
> +	struct bpf_insn *insn = env->prog->insnsi;
> +	int i, j, err, last_may_goto, removed_cnt;
> +	int insn_cnt = env->prog->len;
> +
> +	for (i = 0; i < insn_cnt; i++) {
> +		if (!is_may_goto_insn(&insn[i]))
> +			continue;
> +
> +		for (j = i + 1; j < insn_cnt; j++) {
> +			if (!is_may_goto_insn(&insn[j]))
> +				break;
> +		}
> +
> +		last_may_goto = --j;
> +		removed_cnt = 0;
> +		while (j >= i) {
> +			if (insn[j].off == 0) {
> +				err = verifier_remove_insns(env, j, 1);

Nit: given how ineffective the verifier_remove_insns() is I'd count
     the number of matching may_goto's and removed them using one call
     to verifier_remove_insns().

> +				if (err)
> +					return err;
> +				removed_cnt++;
> +			}
> +			j--;
> +		}
> +
> +		insn_cnt -= removed_cnt;
> +		i = last_may_goto - removed_cnt;
> +	}
> +
> +	return 0;
> +}

[...]
Alexei Starovoitov Jan. 17, 2025, 1:45 a.m. UTC | #2
On Thu, Jan 16, 2025 at 11:42 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Wed, 2025-01-15 at 21:51 -0800, Yonghong Song wrote:
> > Since 'may_goto 0' insns are actually no-op, let us remove them.
> > Otherwise, verifier will generate code like
> >    /* r10 - 8 stores the implicit loop count */
> >    r11 = *(u64 *)(r10 -8)
> >    if r11 == 0x0 goto pc+2
> >    r11 -= 1
> >    *(u64 *)(r10 -8) = r11
> >
> > which is the pure overhead.
> >
> > The following code patterns (from the previous commit) are also
> > handled:
> >    may_goto 2
> >    may_goto 1
> >    may_goto 0
> >
> > With this commit, the above three 'may_goto' insns are all
> > eliminated.
> >
> > Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
> > ---
>
> Technically this is a side-effect, it subtracts 1 from total loop budget.
> An alternative transformation might be:
>
>     r11 = *(u64 *)(r10 -8)
>     if r11 == 0x0 goto pc+2
>     r11 -= 3     <---------------- note 3 here
>     *(u64 *)(r10 -8) = r11
>
> On the other hand, it looks like there is no way to trick verifier
> into an infinite loop by removing these statements, so this should be
> safe modulo exceeding the 8M iterations budget.
>
> Acked-by: Eduard Zingerman <eddyz87@gmail.com>
>
> >  kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++++++
> >  1 file changed, 36 insertions(+)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index edf3cc42a220..72b474bfba2d 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -20133,6 +20133,40 @@ static int opt_remove_nops(struct bpf_verifier_env *env)
> >       return 0;
> >  }
> >
> > +static int opt_remove_useless_may_gotos(struct bpf_verifier_env *env)
> > +{
> > +     struct bpf_insn *insn = env->prog->insnsi;
> > +     int i, j, err, last_may_goto, removed_cnt;
> > +     int insn_cnt = env->prog->len;
> > +
> > +     for (i = 0; i < insn_cnt; i++) {
> > +             if (!is_may_goto_insn(&insn[i]))
> > +                     continue;
> > +
> > +             for (j = i + 1; j < insn_cnt; j++) {
> > +                     if (!is_may_goto_insn(&insn[j]))
> > +                             break;
> > +             }
> > +
> > +             last_may_goto = --j;
> > +             removed_cnt = 0;
> > +             while (j >= i) {
> > +                     if (insn[j].off == 0) {
> > +                             err = verifier_remove_insns(env, j, 1);
>
> Nit: given how ineffective the verifier_remove_insns() is I'd count
>      the number of matching may_goto's and removed them using one call
>      to verifier_remove_insns().

True,
but more generally I don't see why may_goto needs special treatment.
opt_remove_nops() should handle both.

if (memcmp(&insn[i], &ja, sizeof(ja)) &&
    memcmp(&insn[i], &may_goto0, sizeof(ja)))
 continue;

will almost work.
In the sequence of may_goto +2, +1, +0
only the last one will be removed, I think,
but opt_remove_nops() can be tweaked to achieve that as well.
-                 i--;
+                 i -= 2;

will do ?

pw-bot: cr
Yonghong Song Jan. 17, 2025, 3:43 a.m. UTC | #3
On 1/16/25 5:45 PM, Alexei Starovoitov wrote:
> On Thu, Jan 16, 2025 at 11:42 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
>> On Wed, 2025-01-15 at 21:51 -0800, Yonghong Song wrote:
>>> Since 'may_goto 0' insns are actually no-op, let us remove them.
>>> Otherwise, verifier will generate code like
>>>     /* r10 - 8 stores the implicit loop count */
>>>     r11 = *(u64 *)(r10 -8)
>>>     if r11 == 0x0 goto pc+2
>>>     r11 -= 1
>>>     *(u64 *)(r10 -8) = r11
>>>
>>> which is the pure overhead.
>>>
>>> The following code patterns (from the previous commit) are also
>>> handled:
>>>     may_goto 2
>>>     may_goto 1
>>>     may_goto 0
>>>
>>> With this commit, the above three 'may_goto' insns are all
>>> eliminated.
>>>
>>> Signed-off-by: Yonghong Song <yonghong.song@linux.dev>
>>> ---
>> Technically this is a side-effect, it subtracts 1 from total loop budget.
>> An alternative transformation might be:
>>
>>      r11 = *(u64 *)(r10 -8)
>>      if r11 == 0x0 goto pc+2
>>      r11 -= 3     <---------------- note 3 here
>>      *(u64 *)(r10 -8) = r11
>>
>> On the other hand, it looks like there is no way to trick verifier
>> into an infinite loop by removing these statements, so this should be
>> safe modulo exceeding the 8M iterations budget.
>>
>> Acked-by: Eduard Zingerman <eddyz87@gmail.com>
>>
>>>   kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++++++
>>>   1 file changed, 36 insertions(+)
>>>
>>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>>> index edf3cc42a220..72b474bfba2d 100644
>>> --- a/kernel/bpf/verifier.c
>>> +++ b/kernel/bpf/verifier.c
>>> @@ -20133,6 +20133,40 @@ static int opt_remove_nops(struct bpf_verifier_env *env)
>>>        return 0;
>>>   }
>>>
>>> +static int opt_remove_useless_may_gotos(struct bpf_verifier_env *env)
>>> +{
>>> +     struct bpf_insn *insn = env->prog->insnsi;
>>> +     int i, j, err, last_may_goto, removed_cnt;
>>> +     int insn_cnt = env->prog->len;
>>> +
>>> +     for (i = 0; i < insn_cnt; i++) {
>>> +             if (!is_may_goto_insn(&insn[i]))
>>> +                     continue;
>>> +
>>> +             for (j = i + 1; j < insn_cnt; j++) {
>>> +                     if (!is_may_goto_insn(&insn[j]))
>>> +                             break;
>>> +             }
>>> +
>>> +             last_may_goto = --j;
>>> +             removed_cnt = 0;
>>> +             while (j >= i) {
>>> +                     if (insn[j].off == 0) {
>>> +                             err = verifier_remove_insns(env, j, 1);
>> Nit: given how ineffective the verifier_remove_insns() is I'd count
>>       the number of matching may_goto's and removed them using one call
>>       to verifier_remove_insns().
> True,
> but more generally I don't see why may_goto needs special treatment.
> opt_remove_nops() should handle both.
>
> if (memcmp(&insn[i], &ja, sizeof(ja)) &&
>      memcmp(&insn[i], &may_goto0, sizeof(ja)))
>   continue;
>
> will almost work.
> In the sequence of may_goto +2, +1, +0
> only the last one will be removed, I think,
> but opt_remove_nops() can be tweaked to achieve that as well.
> -                 i--;
> +                 i -= 2;
>
> will do ?

Okay. Let me give a try.

>
> pw-bot: cr
diff mbox series

Patch

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index edf3cc42a220..72b474bfba2d 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -20133,6 +20133,40 @@  static int opt_remove_nops(struct bpf_verifier_env *env)
 	return 0;
 }
 
+static int opt_remove_useless_may_gotos(struct bpf_verifier_env *env)
+{
+	struct bpf_insn *insn = env->prog->insnsi;
+	int i, j, err, last_may_goto, removed_cnt;
+	int insn_cnt = env->prog->len;
+
+	for (i = 0; i < insn_cnt; i++) {
+		if (!is_may_goto_insn(&insn[i]))
+			continue;
+
+		for (j = i + 1; j < insn_cnt; j++) {
+			if (!is_may_goto_insn(&insn[j]))
+				break;
+		}
+
+		last_may_goto = --j;
+		removed_cnt = 0;
+		while (j >= i) {
+			if (insn[j].off == 0) {
+				err = verifier_remove_insns(env, j, 1);
+				if (err)
+					return err;
+				removed_cnt++;
+			}
+			j--;
+		}
+
+		insn_cnt -= removed_cnt;
+		i = last_may_goto - removed_cnt;
+	}
+
+	return 0;
+}
+
 static int opt_subreg_zext_lo32_rnd_hi32(struct bpf_verifier_env *env,
 					 const union bpf_attr *attr)
 {
@@ -23089,6 +23123,8 @@  int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3
 			ret = opt_remove_dead_code(env);
 		if (ret == 0)
 			ret = opt_remove_nops(env);
+		if (ret == 0)
+			ret = opt_remove_useless_may_gotos(env);
 	} else {
 		if (ret == 0)
 			sanitize_dead_code(env);