diff mbox series

[bpf-next,1/3] bpf: support nocsr patterns for calls to kfuncs

Message ID 20240812234356.2089263-2-eddyz87@gmail.com (mailing list archive)
State Changes Requested
Delegated to: BPF
Headers show
Series support nocsr patterns for calls to kfuncs | 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 fail Errors and warnings before: 272 this patch: 273
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 6 maintainers not CCed: song@kernel.org sdf@fomichev.me haoluo@google.com jolsa@kernel.org kpsingh@kernel.org john.fastabend@gmail.com
netdev/build_clang fail Errors and warnings before: 340 this patch: 342
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 fail Errors and warnings before: 7032 this patch: 7033
netdev/checkpatch warning CHECK: Prefer using the BIT macro WARNING: line length of 81 exceeds 80 columns
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-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
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-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
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18-O2
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-16 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-17 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-18 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17-O2
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
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-15 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-14 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-40 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-23 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-24 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-25 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-30 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-20 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-32 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-26 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-29 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-31 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-37 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-36 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-38 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-39 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-21 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc

Commit Message

Eduard Zingerman Aug. 12, 2024, 11:43 p.m. UTC
Recognize nocsr patterns around kfunc calls.
For example, suppose bpf_cast_to_kern_ctx() follows nocsr contract
(which it does, it is rewritten by verifier as "r0 = r1" insn),
in such a case, rewrite BPF program below:

  r2 = 1;
  *(u64 *)(r10 - 32) = r2;
  call %[bpf_cast_to_kern_ctx];
  r2 = *(u64 *)(r10 - 32);
  r0 = r2;

Removing the spill/fill pair:

  r2 = 1;
  call %[bpf_cast_to_kern_ctx];
  r0 = r2;

Add a KF_NOCSR flag to mark kfuncs that follow nocsr contract.

Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
---
 include/linux/btf.h   |  1 +
 kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++++++
 2 files changed, 37 insertions(+)

Comments

Yonghong Song Aug. 13, 2024, 5:36 a.m. UTC | #1
On 8/12/24 4:43 PM, Eduard Zingerman wrote:
> Recognize nocsr patterns around kfunc calls.
> For example, suppose bpf_cast_to_kern_ctx() follows nocsr contract
> (which it does, it is rewritten by verifier as "r0 = r1" insn),
> in such a case, rewrite BPF program below:
>
>    r2 = 1;
>    *(u64 *)(r10 - 32) = r2;
>    call %[bpf_cast_to_kern_ctx];
>    r2 = *(u64 *)(r10 - 32);
>    r0 = r2;
>
> Removing the spill/fill pair:
>
>    r2 = 1;
>    call %[bpf_cast_to_kern_ctx];
>    r0 = r2;

I can see this indeed a good optimization esp. when there is a register
pressure for the program, and like above r2 has to be spilled.
Using nocsr for bpf_cast_to_kern_ctx() can remove those spill/fill
insns.

>
> Add a KF_NOCSR flag to mark kfuncs that follow nocsr contract.
>
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
> ---
>   include/linux/btf.h   |  1 +
>   kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++++++
>   2 files changed, 37 insertions(+)
>
> diff --git a/include/linux/btf.h b/include/linux/btf.h
> index cffb43133c68..59ca37300423 100644
> --- a/include/linux/btf.h
> +++ b/include/linux/btf.h
> @@ -75,6 +75,7 @@
>   #define KF_ITER_NEXT    (1 << 9) /* kfunc implements BPF iter next method */
>   #define KF_ITER_DESTROY (1 << 10) /* kfunc implements BPF iter destructor */
>   #define KF_RCU_PROTECTED (1 << 11) /* kfunc should be protected by rcu cs when they are invoked */
> +#define KF_NOCSR        (1 << 12) /* kfunc follows nocsr calling contract */
>   
>   /*
>    * Tag marking a kernel function as a kfunc. This is meant to minimize the
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index df3be12096cf..c579f74be3f9 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -16140,6 +16140,28 @@ static bool verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
>   	}
>   }
>   
> +/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment above */
> +static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta *meta)
> +{
> +	const struct btf_param *params;
> +	u32 vlen, i, mask;

In helper_nocsr_clobber_mask, we have u8 mask. To be consistent, can we have 'u8 mask' here?
Are you worried that the number of arguments could be more than 7? This seems not the case
right now.

> +
> +	params = btf_params(meta->func_proto);
> +	vlen = btf_type_vlen(meta->func_proto);
> +	mask = 0;
> +	if (!btf_type_is_void(btf_type_by_id(meta->btf, meta->func_proto->type)))
> +		mask |= BIT(BPF_REG_0);
> +	for (i = 0; i < vlen; ++i)
> +		mask |= BIT(BPF_REG_1 + i);
> +	return mask;
> +}
> +
> +/* Same as verifier_inlines_helper_call() but for kfuncs, see comment above */
> +static bool verifier_inlines_kfunc_call(struct bpf_kfunc_call_arg_meta *meta)
> +{
> +	return false;
> +}
> +
>   /* GCC and LLVM define a no_caller_saved_registers function attribute.
>    * This attribute means that function scratches only some of
>    * the caller saved registers defined by ABI.
> @@ -16238,6 +16260,20 @@ static void mark_nocsr_pattern_for_call(struct bpf_verifier_env *env,
>   				  bpf_jit_inlines_helper_call(call->imm));
>   	}
>   
> +	if (bpf_pseudo_kfunc_call(call)) {
> +		struct bpf_kfunc_call_arg_meta meta;
> +		int err;
> +
> +		err = fetch_kfunc_meta(env, call, &meta, NULL);
> +		if (err < 0)
> +			/* error would be reported later */
> +			return;
> +
> +		clobbered_regs_mask = kfunc_nocsr_clobber_mask(&meta);
> +		can_be_inlined = (meta.kfunc_flags & KF_NOCSR) &&
> +				 verifier_inlines_kfunc_call(&meta);

I think we do not need both meta.kfunc_flags & KF_NOCSR and
verifier_inlines_kfunc_call(&meta). Only one of them is enough
since they test very similar thing. You do need to ensure
kfuncs with KF_NOCSR in special_kfunc_list though.
WDYT?

> +	}
> +
>   	if (clobbered_regs_mask == ALL_CALLER_SAVED_REGS)
>   		return;
>
Eduard Zingerman Aug. 13, 2024, 7:55 a.m. UTC | #2
On Mon, 2024-08-12 at 22:36 -0700, Yonghong Song wrote:

[...]

> > @@ -16140,6 +16140,28 @@ static bool verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
> >   	}
> >   }
> >   
> > +/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment above */
> > +static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta *meta)
> > +{
> > +	const struct btf_param *params;
> > +	u32 vlen, i, mask;
> 
> In helper_nocsr_clobber_mask, we have u8 mask. To be consistent, can we have 'u8 mask' here?
> Are you worried that the number of arguments could be more than 7? This seems not the case
> right now.

Before the nocsr part for helpers landed there was a change request to
make helper_nocsr_clobber_mask() return u32. I modified the function
but forgot to change the type for 'mask' local variable.

The main point in using u32 is uniformity.
I can either change kfunc_nocsr_clobber_mask() to use u8 for mask,
or update helper_nocsr_clobber_mask() to use u32 for mask.

> 
> > +
> > +	params = btf_params(meta->func_proto);
> > +	vlen = btf_type_vlen(meta->func_proto);
> > +	mask = 0;
> > +	if (!btf_type_is_void(btf_type_by_id(meta->btf, meta->func_proto->type)))
> > +		mask |= BIT(BPF_REG_0);
> > +	for (i = 0; i < vlen; ++i)
> > +		mask |= BIT(BPF_REG_1 + i);
> > +	return mask;
> > +}
> > +
> > +/* Same as verifier_inlines_helper_call() but for kfuncs, see comment above */
> > +static bool verifier_inlines_kfunc_call(struct bpf_kfunc_call_arg_meta *meta)
> > +{
> > +	return false;
> > +}
> > +
> >   /* GCC and LLVM define a no_caller_saved_registers function attribute.
> >    * This attribute means that function scratches only some of
> >    * the caller saved registers defined by ABI.
> > @@ -16238,6 +16260,20 @@ static void mark_nocsr_pattern_for_call(struct bpf_verifier_env *env,
> >   				  bpf_jit_inlines_helper_call(call->imm));
> >   	}
> >   
> > +	if (bpf_pseudo_kfunc_call(call)) {
> > +		struct bpf_kfunc_call_arg_meta meta;
> > +		int err;
> > +
> > +		err = fetch_kfunc_meta(env, call, &meta, NULL);
> > +		if (err < 0)
> > +			/* error would be reported later */
> > +			return;
> > +
> > +		clobbered_regs_mask = kfunc_nocsr_clobber_mask(&meta);
> > +		can_be_inlined = (meta.kfunc_flags & KF_NOCSR) &&
> > +				 verifier_inlines_kfunc_call(&meta);
> 
> I think we do not need both meta.kfunc_flags & KF_NOCSR and
> verifier_inlines_kfunc_call(&meta). Only one of them is enough
> since they test very similar thing. You do need to ensure
> kfuncs with KF_NOCSR in special_kfunc_list though.
> WDYT?

I can remove the flag in favour of verifier_inlines_kfunc_call().

> 
> > +	}
> > +
> >   	if (clobbered_regs_mask == ALL_CALLER_SAVED_REGS)
> >   		return;
> >
Yonghong Song Aug. 13, 2024, 3:18 p.m. UTC | #3
On 8/13/24 12:55 AM, Eduard Zingerman wrote:
> On Mon, 2024-08-12 at 22:36 -0700, Yonghong Song wrote:
>
> [...]
>
>>> @@ -16140,6 +16140,28 @@ static bool verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
>>>    	}
>>>    }
>>>    
>>> +/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment above */
>>> +static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta *meta)
>>> +{
>>> +	const struct btf_param *params;
>>> +	u32 vlen, i, mask;
>> In helper_nocsr_clobber_mask, we have u8 mask. To be consistent, can we have 'u8 mask' here?
>> Are you worried that the number of arguments could be more than 7? This seems not the case
>> right now.
> Before the nocsr part for helpers landed there was a change request to
> make helper_nocsr_clobber_mask() return u32. I modified the function
> but forgot to change the type for 'mask' local variable.
>
> The main point in using u32 is uniformity.
> I can either change kfunc_nocsr_clobber_mask() to use u8 for mask,
> or update helper_nocsr_clobber_mask() to use u32 for mask.

Changing to u32 in helper_nocsr_clobber_mask() is okay. I
just want to have consistent type for 'mask' in both functions.

>
>>> +
>>> +	params = btf_params(meta->func_proto);
>>> +	vlen = btf_type_vlen(meta->func_proto);
>>> +	mask = 0;
>>> +	if (!btf_type_is_void(btf_type_by_id(meta->btf, meta->func_proto->type)))
>>> +		mask |= BIT(BPF_REG_0);
>>> +	for (i = 0; i < vlen; ++i)
>>> +		mask |= BIT(BPF_REG_1 + i);
>>> +	return mask;
>>> +}
>>> +
>>> +/* Same as verifier_inlines_helper_call() but for kfuncs, see comment above */
>>> +static bool verifier_inlines_kfunc_call(struct bpf_kfunc_call_arg_meta *meta)
>>> +{
>>> +	return false;
>>> +}
>>> +
>>>    /* GCC and LLVM define a no_caller_saved_registers function attribute.
>>>     * This attribute means that function scratches only some of
>>>     * the caller saved registers defined by ABI.
>>> @@ -16238,6 +16260,20 @@ static void mark_nocsr_pattern_for_call(struct bpf_verifier_env *env,
>>>    				  bpf_jit_inlines_helper_call(call->imm));
>>>    	}
>>>    
>>> +	if (bpf_pseudo_kfunc_call(call)) {
>>> +		struct bpf_kfunc_call_arg_meta meta;
>>> +		int err;
>>> +
>>> +		err = fetch_kfunc_meta(env, call, &meta, NULL);
>>> +		if (err < 0)
>>> +			/* error would be reported later */
>>> +			return;
>>> +
>>> +		clobbered_regs_mask = kfunc_nocsr_clobber_mask(&meta);
>>> +		can_be_inlined = (meta.kfunc_flags & KF_NOCSR) &&
>>> +				 verifier_inlines_kfunc_call(&meta);
>> I think we do not need both meta.kfunc_flags & KF_NOCSR and
>> verifier_inlines_kfunc_call(&meta). Only one of them is enough
>> since they test very similar thing. You do need to ensure
>> kfuncs with KF_NOCSR in special_kfunc_list though.
>> WDYT?
> I can remove the flag in favour of verifier_inlines_kfunc_call().

Sounds good to me.

>
>>> +	}
>>> +
>>>    	if (clobbered_regs_mask == ALL_CALLER_SAVED_REGS)
>>>    		return;
>>>    
>
Eduard Zingerman Aug. 13, 2024, 6:57 p.m. UTC | #4
On Tue, 2024-08-13 at 08:18 -0700, Yonghong Song wrote:

[...]

> > > > @@ -16238,6 +16260,20 @@ static void mark_nocsr_pattern_for_call(struct bpf_verifier_env *env,
> > > >    				  bpf_jit_inlines_helper_call(call->imm));
> > > >    	}
> > > >    
> > > > +	if (bpf_pseudo_kfunc_call(call)) {
> > > > +		struct bpf_kfunc_call_arg_meta meta;
> > > > +		int err;
> > > > +
> > > > +		err = fetch_kfunc_meta(env, call, &meta, NULL);
> > > > +		if (err < 0)
> > > > +			/* error would be reported later */
> > > > +			return;
> > > > +
> > > > +		clobbered_regs_mask = kfunc_nocsr_clobber_mask(&meta);
> > > > +		can_be_inlined = (meta.kfunc_flags & KF_NOCSR) &&
> > > > +				 verifier_inlines_kfunc_call(&meta);
> > > I think we do not need both meta.kfunc_flags & KF_NOCSR and
> > > verifier_inlines_kfunc_call(&meta). Only one of them is enough
> > > since they test very similar thing. You do need to ensure
> > > kfuncs with KF_NOCSR in special_kfunc_list though.
> > > WDYT?
> > I can remove the flag in favour of verifier_inlines_kfunc_call().
> 
> Sounds good to me.

Just one more point. The reason I added the KF_NOCSR was to keep the code
as close to helpers case as possible. For helpers there are two guards:
- verifier_inlines_helper_call() function shared between
  mark_nocsr_pattern_for_call() and do_misc_fixups();
- bpf_func_proto->allow_nocsr flag.

The idea is that verifier might inline some functions w/o allowing nocsr.
Hence I decided to use KF_NOCSR in place of bpf_func_proto->allow_nocsr.
On the other hand, verifier_inlines_kfunc_call() is not used by any
other function except mark_nocsr_pattern_for_call() at the moment,
so the KF_NOCSR flag might be redundant indeed.
Andrii Nakryiko Aug. 15, 2024, 9:24 p.m. UTC | #5
On Mon, Aug 12, 2024 at 4:44 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> Recognize nocsr patterns around kfunc calls.
> For example, suppose bpf_cast_to_kern_ctx() follows nocsr contract
> (which it does, it is rewritten by verifier as "r0 = r1" insn),
> in such a case, rewrite BPF program below:
>
>   r2 = 1;
>   *(u64 *)(r10 - 32) = r2;
>   call %[bpf_cast_to_kern_ctx];
>   r2 = *(u64 *)(r10 - 32);
>   r0 = r2;
>
> Removing the spill/fill pair:
>
>   r2 = 1;
>   call %[bpf_cast_to_kern_ctx];
>   r0 = r2;
>
> Add a KF_NOCSR flag to mark kfuncs that follow nocsr contract.
>
> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
> ---
>  include/linux/btf.h   |  1 +
>  kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++++++
>  2 files changed, 37 insertions(+)
>
> diff --git a/include/linux/btf.h b/include/linux/btf.h
> index cffb43133c68..59ca37300423 100644
> --- a/include/linux/btf.h
> +++ b/include/linux/btf.h
> @@ -75,6 +75,7 @@
>  #define KF_ITER_NEXT    (1 << 9) /* kfunc implements BPF iter next method */
>  #define KF_ITER_DESTROY (1 << 10) /* kfunc implements BPF iter destructor */
>  #define KF_RCU_PROTECTED (1 << 11) /* kfunc should be protected by rcu cs when they are invoked */
> +#define KF_NOCSR        (1 << 12) /* kfunc follows nocsr calling contract */
>
>  /*
>   * Tag marking a kernel function as a kfunc. This is meant to minimize the
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index df3be12096cf..c579f74be3f9 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -16140,6 +16140,28 @@ static bool verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
>         }
>  }
>
> +/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment above */
> +static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta *meta)
> +{
> +       const struct btf_param *params;
> +       u32 vlen, i, mask;
> +
> +       params = btf_params(meta->func_proto);
> +       vlen = btf_type_vlen(meta->func_proto);
> +       mask = 0;
> +       if (!btf_type_is_void(btf_type_by_id(meta->btf, meta->func_proto->type)))
> +               mask |= BIT(BPF_REG_0);
> +       for (i = 0; i < vlen; ++i)
> +               mask |= BIT(BPF_REG_1 + i);

Somewhere deep in btf_dump implementation of libbpf, there is a
special handling of `<whatever> func(void)` (no args) function as
having vlen == 1 and type being VOID (i.e., zero). I don't know if
that still can happen, but I believe at some point we could get this
vlen==1 and type=VOID for no-args functions. So I wonder if we should
handle that here as well, or is it some compiler atavism we can forget
about?

> +       return mask;
> +}
> +
> +/* Same as verifier_inlines_helper_call() but for kfuncs, see comment above */
> +static bool verifier_inlines_kfunc_call(struct bpf_kfunc_call_arg_meta *meta)
> +{
> +       return false;
> +}
> +
>  /* GCC and LLVM define a no_caller_saved_registers function attribute.
>   * This attribute means that function scratches only some of
>   * the caller saved registers defined by ABI.
> @@ -16238,6 +16260,20 @@ static void mark_nocsr_pattern_for_call(struct bpf_verifier_env *env,
>                                   bpf_jit_inlines_helper_call(call->imm));
>         }
>
> +       if (bpf_pseudo_kfunc_call(call)) {
> +               struct bpf_kfunc_call_arg_meta meta;
> +               int err;
> +
> +               err = fetch_kfunc_meta(env, call, &meta, NULL);
> +               if (err < 0)
> +                       /* error would be reported later */
> +                       return;
> +
> +               clobbered_regs_mask = kfunc_nocsr_clobber_mask(&meta);
> +               can_be_inlined = (meta.kfunc_flags & KF_NOCSR) &&
> +                                verifier_inlines_kfunc_call(&meta);
> +       }
> +
>         if (clobbered_regs_mask == ALL_CALLER_SAVED_REGS)
>                 return;
>
> --
> 2.45.2
>
Eduard Zingerman Aug. 15, 2024, 10:07 p.m. UTC | #6
On Thu, 2024-08-15 at 14:24 -0700, Andrii Nakryiko wrote:

[...]

> > @@ -16140,6 +16140,28 @@ static bool verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
> >         }
> >  }
> > 
> > +/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment above */
> > +static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta *meta)
> > +{
> > +       const struct btf_param *params;
> > +       u32 vlen, i, mask;
> > +
> > +       params = btf_params(meta->func_proto);
> > +       vlen = btf_type_vlen(meta->func_proto);
> > +       mask = 0;
> > +       if (!btf_type_is_void(btf_type_by_id(meta->btf, meta->func_proto->type)))
> > +               mask |= BIT(BPF_REG_0);
> > +       for (i = 0; i < vlen; ++i)
> > +               mask |= BIT(BPF_REG_1 + i);
> 
> Somewhere deep in btf_dump implementation of libbpf, there is a
> special handling of `<whatever> func(void)` (no args) function as
> having vlen == 1 and type being VOID (i.e., zero). I don't know if
> that still can happen, but I believe at some point we could get this
> vlen==1 and type=VOID for no-args functions. So I wonder if we should
> handle that here as well, or is it some compiler atavism we can forget
> about?
>

I just checked BTF generated for 'int filelock_init(void)',
for gcc compiled kernel using latest pahole and func proto looks as follows:

  FUNC_PROTO '(anon)' ret_type_id=12 vlen=0

So I assume this is an atavism.

[...]
Yonghong Song Aug. 15, 2024, 10:16 p.m. UTC | #7
On 8/15/24 2:24 PM, Andrii Nakryiko wrote:
> On Mon, Aug 12, 2024 at 4:44 PM Eduard Zingerman <eddyz87@gmail.com> wrote:
>> Recognize nocsr patterns around kfunc calls.
>> For example, suppose bpf_cast_to_kern_ctx() follows nocsr contract
>> (which it does, it is rewritten by verifier as "r0 = r1" insn),
>> in such a case, rewrite BPF program below:
>>
>>    r2 = 1;
>>    *(u64 *)(r10 - 32) = r2;
>>    call %[bpf_cast_to_kern_ctx];
>>    r2 = *(u64 *)(r10 - 32);
>>    r0 = r2;
>>
>> Removing the spill/fill pair:
>>
>>    r2 = 1;
>>    call %[bpf_cast_to_kern_ctx];
>>    r0 = r2;
>>
>> Add a KF_NOCSR flag to mark kfuncs that follow nocsr contract.
>>
>> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
>> ---
>>   include/linux/btf.h   |  1 +
>>   kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++++++
>>   2 files changed, 37 insertions(+)
>>
>> diff --git a/include/linux/btf.h b/include/linux/btf.h
>> index cffb43133c68..59ca37300423 100644
>> --- a/include/linux/btf.h
>> +++ b/include/linux/btf.h
>> @@ -75,6 +75,7 @@
>>   #define KF_ITER_NEXT    (1 << 9) /* kfunc implements BPF iter next method */
>>   #define KF_ITER_DESTROY (1 << 10) /* kfunc implements BPF iter destructor */
>>   #define KF_RCU_PROTECTED (1 << 11) /* kfunc should be protected by rcu cs when they are invoked */
>> +#define KF_NOCSR        (1 << 12) /* kfunc follows nocsr calling contract */
>>
>>   /*
>>    * Tag marking a kernel function as a kfunc. This is meant to minimize the
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index df3be12096cf..c579f74be3f9 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -16140,6 +16140,28 @@ static bool verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
>>          }
>>   }
>>
>> +/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment above */
>> +static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta *meta)
>> +{
>> +       const struct btf_param *params;
>> +       u32 vlen, i, mask;
>> +
>> +       params = btf_params(meta->func_proto);
>> +       vlen = btf_type_vlen(meta->func_proto);
>> +       mask = 0;
>> +       if (!btf_type_is_void(btf_type_by_id(meta->btf, meta->func_proto->type)))
>> +               mask |= BIT(BPF_REG_0);
>> +       for (i = 0; i < vlen; ++i)
>> +               mask |= BIT(BPF_REG_1 + i);
> Somewhere deep in btf_dump implementation of libbpf, there is a
> special handling of `<whatever> func(void)` (no args) function as
> having vlen == 1 and type being VOID (i.e., zero). I don't know if
> that still can happen, but I believe at some point we could get this
> vlen==1 and type=VOID for no-args functions. So I wonder if we should
> handle that here as well, or is it some compiler atavism we can forget
> about?

The case to have vlen=1 and type=VOID only happens for
bpf programs with llvm19 and later.
For example,

$ cat t.c
int foo(); // a kfunc or a helper
int bar() {
   return foo(1, 2);
}

$ clang --target=bpf -O2 -g -c t.c && llvm-dwarfdump t.o
t.c:3:13: warning: passing arguments to 'foo' without a prototype is deprecated in all versions of C and is not supported in C23 [-Wdeprecated-non-prototype]
     3 |   return foo(1, 2);
       |             ^
1 warning generated.
t.o:    file format elf64-bpf
...
0x00000039:   DW_TAG_subprogram
                 DW_AT_name      ("foo")
                 DW_AT_decl_file ("/home/yhs/t.c")
                 DW_AT_decl_line (1)
                 DW_AT_type      (0x00000043 "int")
                 DW_AT_declaration       (true)
                 DW_AT_external  (true)

0x00000041:     DW_TAG_unspecified_parameters

0x00000042:     NULL
...

If we do see a BPF kfunc/helper with vlen=1 and type is VOID,
that means the number of arguments is actual UNKNOWN
based on dwarf DW_TAG_subprogram tag. Although it is unlikely
people to write code like above, it might be still useful
to add check with vlen=1 and type=VOID and reject such a case.


>
>> +       return mask;
>> +}
>> +
>> +/* Same as verifier_inlines_helper_call() but for kfuncs, see comment above */
>> +static bool verifier_inlines_kfunc_call(struct bpf_kfunc_call_arg_meta *meta)
>> +{
>> +       return false;
>> +}
>> +
>>   /* GCC and LLVM define a no_caller_saved_registers function attribute.
>>    * This attribute means that function scratches only some of
>>    * the caller saved registers defined by ABI.
>> @@ -16238,6 +16260,20 @@ static void mark_nocsr_pattern_for_call(struct bpf_verifier_env *env,
>>                                    bpf_jit_inlines_helper_call(call->imm));
>>          }
>>
>> +       if (bpf_pseudo_kfunc_call(call)) {
>> +               struct bpf_kfunc_call_arg_meta meta;
>> +               int err;
>> +
>> +               err = fetch_kfunc_meta(env, call, &meta, NULL);
>> +               if (err < 0)
>> +                       /* error would be reported later */
>> +                       return;
>> +
>> +               clobbered_regs_mask = kfunc_nocsr_clobber_mask(&meta);
>> +               can_be_inlined = (meta.kfunc_flags & KF_NOCSR) &&
>> +                                verifier_inlines_kfunc_call(&meta);
>> +       }
>> +
>>          if (clobbered_regs_mask == ALL_CALLER_SAVED_REGS)
>>                  return;
>>
>> --
>> 2.45.2
>>
Yonghong Song Aug. 15, 2024, 10:22 p.m. UTC | #8
On 8/15/24 3:16 PM, Yonghong Song wrote:
>
> On 8/15/24 2:24 PM, Andrii Nakryiko wrote:
>> On Mon, Aug 12, 2024 at 4:44 PM Eduard Zingerman <eddyz87@gmail.com> 
>> wrote:
>>> Recognize nocsr patterns around kfunc calls.
>>> For example, suppose bpf_cast_to_kern_ctx() follows nocsr contract
>>> (which it does, it is rewritten by verifier as "r0 = r1" insn),
>>> in such a case, rewrite BPF program below:
>>>
>>>    r2 = 1;
>>>    *(u64 *)(r10 - 32) = r2;
>>>    call %[bpf_cast_to_kern_ctx];
>>>    r2 = *(u64 *)(r10 - 32);
>>>    r0 = r2;
>>>
>>> Removing the spill/fill pair:
>>>
>>>    r2 = 1;
>>>    call %[bpf_cast_to_kern_ctx];
>>>    r0 = r2;
>>>
>>> Add a KF_NOCSR flag to mark kfuncs that follow nocsr contract.
>>>
>>> Signed-off-by: Eduard Zingerman <eddyz87@gmail.com>
>>> ---
>>>   include/linux/btf.h   |  1 +
>>>   kernel/bpf/verifier.c | 36 ++++++++++++++++++++++++++++++++++++
>>>   2 files changed, 37 insertions(+)
>>>
>>> diff --git a/include/linux/btf.h b/include/linux/btf.h
>>> index cffb43133c68..59ca37300423 100644
>>> --- a/include/linux/btf.h
>>> +++ b/include/linux/btf.h
>>> @@ -75,6 +75,7 @@
>>>   #define KF_ITER_NEXT    (1 << 9) /* kfunc implements BPF iter next 
>>> method */
>>>   #define KF_ITER_DESTROY (1 << 10) /* kfunc implements BPF iter 
>>> destructor */
>>>   #define KF_RCU_PROTECTED (1 << 11) /* kfunc should be protected by 
>>> rcu cs when they are invoked */
>>> +#define KF_NOCSR        (1 << 12) /* kfunc follows nocsr calling 
>>> contract */
>>>
>>>   /*
>>>    * Tag marking a kernel function as a kfunc. This is meant to 
>>> minimize the
>>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>>> index df3be12096cf..c579f74be3f9 100644
>>> --- a/kernel/bpf/verifier.c
>>> +++ b/kernel/bpf/verifier.c
>>> @@ -16140,6 +16140,28 @@ static bool 
>>> verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
>>>          }
>>>   }
>>>
>>> +/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment 
>>> above */
>>> +static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta 
>>> *meta)
>>> +{
>>> +       const struct btf_param *params;
>>> +       u32 vlen, i, mask;
>>> +
>>> +       params = btf_params(meta->func_proto);
>>> +       vlen = btf_type_vlen(meta->func_proto);
>>> +       mask = 0;
>>> +       if (!btf_type_is_void(btf_type_by_id(meta->btf, 
>>> meta->func_proto->type)))
>>> +               mask |= BIT(BPF_REG_0);
>>> +       for (i = 0; i < vlen; ++i)
>>> +               mask |= BIT(BPF_REG_1 + i);
>> Somewhere deep in btf_dump implementation of libbpf, there is a
>> special handling of `<whatever> func(void)` (no args) function as
>> having vlen == 1 and type being VOID (i.e., zero). I don't know if
>> that still can happen, but I believe at some point we could get this
>> vlen==1 and type=VOID for no-args functions. So I wonder if we should
>> handle that here as well, or is it some compiler atavism we can forget
>> about?
>
> The case to have vlen=1 and type=VOID only happens for
> bpf programs with llvm19 and later.
> For example,
>
> $ cat t.c
> int foo(); // a kfunc or a helper
> int bar() {
>   return foo(1, 2);
> }
>
> $ clang --target=bpf -O2 -g -c t.c && llvm-dwarfdump t.o
> t.c:3:13: warning: passing arguments to 'foo' without a prototype is 
> deprecated in all versions of C and is not supported in C23 
> [-Wdeprecated-non-prototype]
>     3 |   return foo(1, 2);
>       |             ^
> 1 warning generated.
> t.o:    file format elf64-bpf
> ...
> 0x00000039:   DW_TAG_subprogram
>                 DW_AT_name      ("foo")
>                 DW_AT_decl_file ("/home/yhs/t.c")
>                 DW_AT_decl_line (1)
>                 DW_AT_type      (0x00000043 "int")
>                 DW_AT_declaration       (true)
>                 DW_AT_external  (true)
>
> 0x00000041:     DW_TAG_unspecified_parameters
>
> 0x00000042:     NULL
> ...
>
> If we do see a BPF kfunc/helper with vlen=1 and type is VOID,
> that means the number of arguments is actual UNKNOWN
> based on dwarf DW_TAG_subprogram tag. Although it is unlikely
> people to write code like above, it might be still useful
> to add check with vlen=1 and type=VOID and reject such a case.


For vmlinux BTF, this is not possible since eventually all function
has a definition which will define the function precisely w.r.t.
the number of arguments and their types.


>
>
>>
>>> +       return mask;
>>> +}
>>> +
>>> +/* Same as verifier_inlines_helper_call() but for kfuncs, see 
>>> comment above */
>>> +static bool verifier_inlines_kfunc_call(struct 
>>> bpf_kfunc_call_arg_meta *meta)
>>> +{
>>> +       return false;
>>> +}
>>> +
>>>   /* GCC and LLVM define a no_caller_saved_registers function 
>>> attribute.
>>>    * This attribute means that function scratches only some of
>>>    * the caller saved registers defined by ABI.
>>> @@ -16238,6 +16260,20 @@ static void 
>>> mark_nocsr_pattern_for_call(struct bpf_verifier_env *env,
>>> bpf_jit_inlines_helper_call(call->imm));
>>>          }
>>>
>>> +       if (bpf_pseudo_kfunc_call(call)) {
>>> +               struct bpf_kfunc_call_arg_meta meta;
>>> +               int err;
>>> +
>>> +               err = fetch_kfunc_meta(env, call, &meta, NULL);
>>> +               if (err < 0)
>>> +                       /* error would be reported later */
>>> +                       return;
>>> +
>>> +               clobbered_regs_mask = kfunc_nocsr_clobber_mask(&meta);
>>> +               can_be_inlined = (meta.kfunc_flags & KF_NOCSR) &&
>>> + verifier_inlines_kfunc_call(&meta);
>>> +       }
>>> +
>>>          if (clobbered_regs_mask == ALL_CALLER_SAVED_REGS)
>>>                  return;
>>>
>>> -- 
>>> 2.45.2
>>>
Yonghong Song Aug. 15, 2024, 10:23 p.m. UTC | #9
On 8/15/24 3:07 PM, Eduard Zingerman wrote:
> On Thu, 2024-08-15 at 14:24 -0700, Andrii Nakryiko wrote:
>
> [...]
>
>>> @@ -16140,6 +16140,28 @@ static bool verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
>>>          }
>>>   }
>>>
>>> +/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment above */
>>> +static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta *meta)
>>> +{
>>> +       const struct btf_param *params;
>>> +       u32 vlen, i, mask;
>>> +
>>> +       params = btf_params(meta->func_proto);
>>> +       vlen = btf_type_vlen(meta->func_proto);
>>> +       mask = 0;
>>> +       if (!btf_type_is_void(btf_type_by_id(meta->btf, meta->func_proto->type)))
>>> +               mask |= BIT(BPF_REG_0);
>>> +       for (i = 0; i < vlen; ++i)
>>> +               mask |= BIT(BPF_REG_1 + i);
>> Somewhere deep in btf_dump implementation of libbpf, there is a
>> special handling of `<whatever> func(void)` (no args) function as
>> having vlen == 1 and type being VOID (i.e., zero). I don't know if
>> that still can happen, but I believe at some point we could get this
>> vlen==1 and type=VOID for no-args functions. So I wonder if we should
>> handle that here as well, or is it some compiler atavism we can forget
>> about?
>>
> I just checked BTF generated for 'int filelock_init(void)',
> for gcc compiled kernel using latest pahole and func proto looks as follows:
>
>    FUNC_PROTO '(anon)' ret_type_id=12 vlen=0
>
> So I assume this is an atavism.

Agree, for kernel vmlinux BTF, we should be fine.

>
> [...]
>
>
Eduard Zingerman Aug. 15, 2024, 10:29 p.m. UTC | #10
On Thu, 2024-08-15 at 15:23 -0700, Yonghong Song wrote:
> On 8/15/24 3:07 PM, Eduard Zingerman wrote:
> > On Thu, 2024-08-15 at 14:24 -0700, Andrii Nakryiko wrote:
> > 
> > [...]
> > 
> > > > @@ -16140,6 +16140,28 @@ static bool verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
> > > >          }
> > > >   }
> > > > 
> > > > +/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment above */
> > > > +static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta *meta)
> > > > +{
> > > > +       const struct btf_param *params;
> > > > +       u32 vlen, i, mask;
> > > > +
> > > > +       params = btf_params(meta->func_proto);
> > > > +       vlen = btf_type_vlen(meta->func_proto);
> > > > +       mask = 0;
> > > > +       if (!btf_type_is_void(btf_type_by_id(meta->btf, meta->func_proto->type)))
> > > > +               mask |= BIT(BPF_REG_0);
> > > > +       for (i = 0; i < vlen; ++i)
> > > > +               mask |= BIT(BPF_REG_1 + i);
> > > Somewhere deep in btf_dump implementation of libbpf, there is a
> > > special handling of `<whatever> func(void)` (no args) function as
> > > having vlen == 1 and type being VOID (i.e., zero). I don't know if
> > > that still can happen, but I believe at some point we could get this
> > > vlen==1 and type=VOID for no-args functions. So I wonder if we should
> > > handle that here as well, or is it some compiler atavism we can forget
> > > about?
> > > 
> > I just checked BTF generated for 'int filelock_init(void)',
> > for gcc compiled kernel using latest pahole and func proto looks as follows:
> > 
> >    FUNC_PROTO '(anon)' ret_type_id=12 vlen=0
> > 
> > So I assume this is an atavism.
> 
> Agree, for kernel vmlinux BTF, we should be fine.

Right, since we are dealing only with vmlinux BTF special case is not needed.
Please let me know if I misunderstand you or Andrii.
diff mbox series

Patch

diff --git a/include/linux/btf.h b/include/linux/btf.h
index cffb43133c68..59ca37300423 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -75,6 +75,7 @@ 
 #define KF_ITER_NEXT    (1 << 9) /* kfunc implements BPF iter next method */
 #define KF_ITER_DESTROY (1 << 10) /* kfunc implements BPF iter destructor */
 #define KF_RCU_PROTECTED (1 << 11) /* kfunc should be protected by rcu cs when they are invoked */
+#define KF_NOCSR        (1 << 12) /* kfunc follows nocsr calling contract */
 
 /*
  * Tag marking a kernel function as a kfunc. This is meant to minimize the
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index df3be12096cf..c579f74be3f9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -16140,6 +16140,28 @@  static bool verifier_inlines_helper_call(struct bpf_verifier_env *env, s32 imm)
 	}
 }
 
+/* Same as helper_nocsr_clobber_mask() but for kfuncs, see comment above */
+static u32 kfunc_nocsr_clobber_mask(struct bpf_kfunc_call_arg_meta *meta)
+{
+	const struct btf_param *params;
+	u32 vlen, i, mask;
+
+	params = btf_params(meta->func_proto);
+	vlen = btf_type_vlen(meta->func_proto);
+	mask = 0;
+	if (!btf_type_is_void(btf_type_by_id(meta->btf, meta->func_proto->type)))
+		mask |= BIT(BPF_REG_0);
+	for (i = 0; i < vlen; ++i)
+		mask |= BIT(BPF_REG_1 + i);
+	return mask;
+}
+
+/* Same as verifier_inlines_helper_call() but for kfuncs, see comment above */
+static bool verifier_inlines_kfunc_call(struct bpf_kfunc_call_arg_meta *meta)
+{
+	return false;
+}
+
 /* GCC and LLVM define a no_caller_saved_registers function attribute.
  * This attribute means that function scratches only some of
  * the caller saved registers defined by ABI.
@@ -16238,6 +16260,20 @@  static void mark_nocsr_pattern_for_call(struct bpf_verifier_env *env,
 				  bpf_jit_inlines_helper_call(call->imm));
 	}
 
+	if (bpf_pseudo_kfunc_call(call)) {
+		struct bpf_kfunc_call_arg_meta meta;
+		int err;
+
+		err = fetch_kfunc_meta(env, call, &meta, NULL);
+		if (err < 0)
+			/* error would be reported later */
+			return;
+
+		clobbered_regs_mask = kfunc_nocsr_clobber_mask(&meta);
+		can_be_inlined = (meta.kfunc_flags & KF_NOCSR) &&
+				 verifier_inlines_kfunc_call(&meta);
+	}
+
 	if (clobbered_regs_mask == ALL_CALLER_SAVED_REGS)
 		return;