diff mbox series

[RFC,bpf-next] bpf: generate 'nomerge' for map helpers in bpf_helper_defs.h

Message ID 20230615142520.10280-1-eddyz87@gmail.com (mailing list archive)
State RFC
Delegated to: BPF
Headers show
Series [RFC,bpf-next] bpf: generate 'nomerge' for map helpers in bpf_helper_defs.h | expand

Checks

Context Check Description
netdev/series_format success Single patches do not need cover letters
netdev/tree_selection success Clearly marked for bpf-next
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: 8 this patch: 8
netdev/cc_maintainers warning 10 maintainers not CCed: kpsingh@kernel.org john.fastabend@gmail.com sdf@google.com song@kernel.org trix@redhat.com nathan@kernel.org llvm@lists.linux.dev jolsa@kernel.org haoluo@google.com ndesaulniers@google.com
netdev/build_clang success Errors and warnings before: 8 this patch: 8
netdev/verify_signedoff fail author Signed-off-by missing
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: 8 this patch: 8
netdev/checkpatch success total: 0 errors, 0 warnings, 0 checks, 51 lines checked
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-PR fail PR summary
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ${{ matrix.test }} on ${{ matrix.arch }} with ${{ matrix.toolchain_full }}
bpf/vmtest-bpf-next-VM_Test-2 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-3 fail Logs for build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-4 fail Logs for build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-5 fail Logs for build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 fail Logs for build for x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-7 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-8 success Logs for veristat

Commit Message

Eduard Zingerman June 15, 2023, 2:25 p.m. UTC
Update code generation for bpf_helper_defs.h by adding
__attribute__((nomerge)) for a set of helper functions to prevent some
verifier unfriendly compiler optimizations.

This addresses a recent mailing list thread [1].
There Zhongqiu Duan and Yonghong Song discussed a C program as below:

     if (data_end - data > 1024) {
         bpf_for_each_map_elem(&map1, cb, &cb_data, 0);
     } else {
         bpf_for_each_map_elem(&map2, cb, &cb_data, 0);
     }

Which was converted by clang to something like this:

     if (data_end - data > 1024)
       tmp = &map1;
     else
       tmp = &map2;
     bpf_for_each_map_elem(tmp, cb, &cb_data, 0);

Which in turn triggered verification error, because
verifier.c:record_func_map() requires a single map address for each
bpf_for_each_map_elem() call.

In fact, this is a requirement for the following helpers:
- bpf_tail_call
- bpf_map_lookup_elem
- bpf_map_update_elem
- bpf_map_delete_elem
- bpf_map_push_elem
- bpf_map_pop_elem
- bpf_map_peek_elem
- bpf_for_each_map_elem
- bpf_redirect_map
- bpf_map_lookup_percpu_elem

I had an off-list discussion with Yonghong where we agreed that clang
attribute 'nomerge' (see [2]) could be used to prevent the
optimization hitting in [1]. However, currently 'nomerge' applies only
to functions and statements, hence I submitted change requests [3],
[4] to allow specifying 'nomerge' for function pointers as well.

The patch below updates bpf_helper_defs.h generation by adding a
definition of __nomerge macro, and using this macro in definitions of
relevant helpers.

The generated code looks as follows:

    /* This is auto-generated file. See bpf_doc.py for details. */

    #if __has_attribute(nomerge)
    #define __nomerge __attribute__((nomerge))
    #else
    #define __nomerge
    #endif

    /* Forward declarations of BPF structs */
    ...
    static long (*bpf_for_each_map_elem)(void *map, ...) __nomerge = (void *) 164;
    ...

(In non-RFC version the macro definition would have to be updated to
 check for supported clang version).

Does community agree with such approach?

[1] https://lore.kernel.org/bpf/03bdf90f-f374-1e67-69d6-76dd9c8318a4@meta.com/
[2] https://clang.llvm.org/docs/AttributeReference.html#nomerge
[3] https://reviews.llvm.org/D152986
[4] https://reviews.llvm.org/D152987
---
 scripts/bpf_doc.py | 37 ++++++++++++++++++++++++++++++-------
 1 file changed, 30 insertions(+), 7 deletions(-)

Comments

Andrii Nakryiko June 16, 2023, 5:03 p.m. UTC | #1
On Thu, Jun 15, 2023 at 7:25 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> Update code generation for bpf_helper_defs.h by adding
> __attribute__((nomerge)) for a set of helper functions to prevent some
> verifier unfriendly compiler optimizations.
>
> This addresses a recent mailing list thread [1].
> There Zhongqiu Duan and Yonghong Song discussed a C program as below:
>
>      if (data_end - data > 1024) {
>          bpf_for_each_map_elem(&map1, cb, &cb_data, 0);
>      } else {
>          bpf_for_each_map_elem(&map2, cb, &cb_data, 0);
>      }
>
> Which was converted by clang to something like this:
>
>      if (data_end - data > 1024)
>        tmp = &map1;
>      else
>        tmp = &map2;
>      bpf_for_each_map_elem(tmp, cb, &cb_data, 0);
>
> Which in turn triggered verification error, because
> verifier.c:record_func_map() requires a single map address for each
> bpf_for_each_map_elem() call.
>
> In fact, this is a requirement for the following helpers:
> - bpf_tail_call
> - bpf_map_lookup_elem
> - bpf_map_update_elem
> - bpf_map_delete_elem
> - bpf_map_push_elem
> - bpf_map_pop_elem
> - bpf_map_peek_elem
> - bpf_for_each_map_elem
> - bpf_redirect_map
> - bpf_map_lookup_percpu_elem
>
> I had an off-list discussion with Yonghong where we agreed that clang
> attribute 'nomerge' (see [2]) could be used to prevent the
> optimization hitting in [1]. However, currently 'nomerge' applies only
> to functions and statements, hence I submitted change requests [3],
> [4] to allow specifying 'nomerge' for function pointers as well.
>
> The patch below updates bpf_helper_defs.h generation by adding a
> definition of __nomerge macro, and using this macro in definitions of
> relevant helpers.
>
> The generated code looks as follows:
>
>     /* This is auto-generated file. See bpf_doc.py for details. */
>
>     #if __has_attribute(nomerge)
>     #define __nomerge __attribute__((nomerge))
>     #else
>     #define __nomerge
>     #endif
>
>     /* Forward declarations of BPF structs */
>     ...
>     static long (*bpf_for_each_map_elem)(void *map, ...) __nomerge = (void *) 164;
>     ...
>
> (In non-RFC version the macro definition would have to be updated to
>  check for supported clang version).
>
> Does community agree with such approach?

Makes sense to me. Let's just be very careful to do proper detection
of __nomerge "applicability" to ensure we don't cause compilation
errors for unsupported Clang (which I'm sure you are well aware of)
*and* make it compatible with GCC, so we don't fix it later.

>
> [1] https://lore.kernel.org/bpf/03bdf90f-f374-1e67-69d6-76dd9c8318a4@meta.com/
> [2] https://clang.llvm.org/docs/AttributeReference.html#nomerge
> [3] https://reviews.llvm.org/D152986
> [4] https://reviews.llvm.org/D152987
> ---
>  scripts/bpf_doc.py | 37 ++++++++++++++++++++++++++++++-------
>  1 file changed, 30 insertions(+), 7 deletions(-)
>
> diff --git a/scripts/bpf_doc.py b/scripts/bpf_doc.py
> index eaae2ce78381..dbd4893c793e 100755
> --- a/scripts/bpf_doc.py
> +++ b/scripts/bpf_doc.py
> @@ -777,14 +777,33 @@ class PrinterHelpers(Printer):
>          'bpf_get_socket_cookie',
>          'bpf_sk_assign',
>      ]
> +    # Helpers that need __nomerge attribute
> +    nomerge_helpers = set([
> +       "bpf_tail_call",
> +       "bpf_map_lookup_elem",
> +       "bpf_map_update_elem",
> +       "bpf_map_delete_elem",
> +       "bpf_map_push_elem",
> +       "bpf_map_pop_elem",
> +       "bpf_map_peek_elem",
> +       "bpf_for_each_map_elem",
> +       "bpf_redirect_map",
> +       "bpf_map_lookup_percpu_elem"
> +    ])
> +
> +    macros = '''\
> +#if __has_attribute(nomerge)
> +#define __nomerge __attribute__((nomerge))
> +#else
> +#define __nomerge
> +#endif'''
>
>      def print_header(self):
> -        header = '''\
> -/* This is auto-generated file. See bpf_doc.py for details. */
> -
> -/* Forward declarations of BPF structs */'''
> -
> -        print(header)
> +        print('/* This is auto-generated file. See bpf_doc.py for details. */')
> +        print()
> +        print(self.macros)
> +        print()
> +        print('/* Forward declarations of BPF structs */')
>          for fwd in self.type_fwds:
>              print('%s;' % fwd)
>          print('')
> @@ -846,7 +865,11 @@ class PrinterHelpers(Printer):
>              comma = ', '
>              print(one_arg, end='')
>
> -        print(') = (void *) %d;' % helper.enum_val)
> +        print(')', end='')
> +        if proto['name'] in self.nomerge_helpers:
> +            print(' __nomerge', end='')
> +
> +        print(' = (void *) %d;' % helper.enum_val)
>          print('')
>
>  ###############################################################################
> --
> 2.40.1
>
Jose E. Marchesi June 20, 2023, 6:27 p.m. UTC | #2
> On Thu, Jun 15, 2023 at 7:25 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
>>
>> Update code generation for bpf_helper_defs.h by adding
>> __attribute__((nomerge)) for a set of helper functions to prevent some
>> verifier unfriendly compiler optimizations.
>>
>> This addresses a recent mailing list thread [1].
>> There Zhongqiu Duan and Yonghong Song discussed a C program as below:
>>
>>      if (data_end - data > 1024) {
>>          bpf_for_each_map_elem(&map1, cb, &cb_data, 0);
>>      } else {
>>          bpf_for_each_map_elem(&map2, cb, &cb_data, 0);
>>      }
>>
>> Which was converted by clang to something like this:
>>
>>      if (data_end - data > 1024)
>>        tmp = &map1;
>>      else
>>        tmp = &map2;
>>      bpf_for_each_map_elem(tmp, cb, &cb_data, 0);
>>
>> Which in turn triggered verification error, because
>> verifier.c:record_func_map() requires a single map address for each
>> bpf_for_each_map_elem() call.
>>
>> In fact, this is a requirement for the following helpers:
>> - bpf_tail_call
>> - bpf_map_lookup_elem
>> - bpf_map_update_elem
>> - bpf_map_delete_elem
>> - bpf_map_push_elem
>> - bpf_map_pop_elem
>> - bpf_map_peek_elem
>> - bpf_for_each_map_elem
>> - bpf_redirect_map
>> - bpf_map_lookup_percpu_elem
>>
>> I had an off-list discussion with Yonghong where we agreed that clang
>> attribute 'nomerge' (see [2]) could be used to prevent the
>> optimization hitting in [1]. However, currently 'nomerge' applies only
>> to functions and statements, hence I submitted change requests [3],
>> [4] to allow specifying 'nomerge' for function pointers as well.
>>
>> The patch below updates bpf_helper_defs.h generation by adding a
>> definition of __nomerge macro, and using this macro in definitions of
>> relevant helpers.
>>
>> The generated code looks as follows:
>>
>>     /* This is auto-generated file. See bpf_doc.py for details. */
>>
>>     #if __has_attribute(nomerge)
>>     #define __nomerge __attribute__((nomerge))
>>     #else
>>     #define __nomerge
>>     #endif
>>
>>     /* Forward declarations of BPF structs */
>>     ...
>>     static long (*bpf_for_each_map_elem)(void *map, ...) __nomerge = (void *) 164;
>>     ...
>>
>> (In non-RFC version the macro definition would have to be updated to
>>  check for supported clang version).
>>
>> Does community agree with such approach?
>
> Makes sense to me. Let's just be very careful to do proper detection
> of __nomerge "applicability" to ensure we don't cause compilation
> errors for unsupported Clang (which I'm sure you are well aware of)
> *and* make it compatible with GCC, so we don't fix it later.

GCC doesn't support the "nomerge" attribute at this point.  We will look
into adding it or find some other equivalent mechanism that can be
abstracted in the __nomerge macro.

>>
>> [1] https://lore.kernel.org/bpf/03bdf90f-f374-1e67-69d6-76dd9c8318a4@meta.com/
>> [2] https://clang.llvm.org/docs/AttributeReference.html#nomerge
>> [3] https://reviews.llvm.org/D152986
>> [4] https://reviews.llvm.org/D152987
>> ---
>>  scripts/bpf_doc.py | 37 ++++++++++++++++++++++++++++++-------
>>  1 file changed, 30 insertions(+), 7 deletions(-)
>>
>> diff --git a/scripts/bpf_doc.py b/scripts/bpf_doc.py
>> index eaae2ce78381..dbd4893c793e 100755
>> --- a/scripts/bpf_doc.py
>> +++ b/scripts/bpf_doc.py
>> @@ -777,14 +777,33 @@ class PrinterHelpers(Printer):
>>          'bpf_get_socket_cookie',
>>          'bpf_sk_assign',
>>      ]
>> +    # Helpers that need __nomerge attribute
>> +    nomerge_helpers = set([
>> +       "bpf_tail_call",
>> +       "bpf_map_lookup_elem",
>> +       "bpf_map_update_elem",
>> +       "bpf_map_delete_elem",
>> +       "bpf_map_push_elem",
>> +       "bpf_map_pop_elem",
>> +       "bpf_map_peek_elem",
>> +       "bpf_for_each_map_elem",
>> +       "bpf_redirect_map",
>> +       "bpf_map_lookup_percpu_elem"
>> +    ])
>> +
>> +    macros = '''\
>> +#if __has_attribute(nomerge)
>> +#define __nomerge __attribute__((nomerge))
>> +#else
>> +#define __nomerge
>> +#endif'''
>>
>>      def print_header(self):
>> -        header = '''\
>> -/* This is auto-generated file. See bpf_doc.py for details. */
>> -
>> -/* Forward declarations of BPF structs */'''
>> -
>> -        print(header)
>> +        print('/* This is auto-generated file. See bpf_doc.py for details. */')
>> +        print()
>> +        print(self.macros)
>> +        print()
>> +        print('/* Forward declarations of BPF structs */')
>>          for fwd in self.type_fwds:
>>              print('%s;' % fwd)
>>          print('')
>> @@ -846,7 +865,11 @@ class PrinterHelpers(Printer):
>>              comma = ', '
>>              print(one_arg, end='')
>>
>> -        print(') = (void *) %d;' % helper.enum_val)
>> +        print(')', end='')
>> +        if proto['name'] in self.nomerge_helpers:
>> +            print(' __nomerge', end='')
>> +
>> +        print(' = (void *) %d;' % helper.enum_val)
>>          print('')
>>
>>  ###############################################################################
>> --
>> 2.40.1
>>
Alexei Starovoitov June 22, 2023, 9:35 p.m. UTC | #3
On Tue, Jun 20, 2023 at 11:27 AM Jose E. Marchesi <jemarch@gnu.org> wrote:
>
>
> > On Thu, Jun 15, 2023 at 7:25 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
> >>
> >> Update code generation for bpf_helper_defs.h by adding
> >> __attribute__((nomerge)) for a set of helper functions to prevent some
> >> verifier unfriendly compiler optimizations.
> >>
> >> This addresses a recent mailing list thread [1].
> >> There Zhongqiu Duan and Yonghong Song discussed a C program as below:
> >>
> >>      if (data_end - data > 1024) {
> >>          bpf_for_each_map_elem(&map1, cb, &cb_data, 0);
> >>      } else {
> >>          bpf_for_each_map_elem(&map2, cb, &cb_data, 0);
> >>      }
> >>
> >> Which was converted by clang to something like this:
> >>
> >>      if (data_end - data > 1024)
> >>        tmp = &map1;
> >>      else
> >>        tmp = &map2;
> >>      bpf_for_each_map_elem(tmp, cb, &cb_data, 0);
> >>
> >> Which in turn triggered verification error, because
> >> verifier.c:record_func_map() requires a single map address for each
> >> bpf_for_each_map_elem() call.
> >>
> >> In fact, this is a requirement for the following helpers:
> >> - bpf_tail_call
> >> - bpf_map_lookup_elem
> >> - bpf_map_update_elem
> >> - bpf_map_delete_elem
> >> - bpf_map_push_elem
> >> - bpf_map_pop_elem
> >> - bpf_map_peek_elem
> >> - bpf_for_each_map_elem
> >> - bpf_redirect_map
> >> - bpf_map_lookup_percpu_elem
> >>
> >> I had an off-list discussion with Yonghong where we agreed that clang
> >> attribute 'nomerge' (see [2]) could be used to prevent the
> >> optimization hitting in [1]. However, currently 'nomerge' applies only
> >> to functions and statements, hence I submitted change requests [3],
> >> [4] to allow specifying 'nomerge' for function pointers as well.
> >>
> >> The patch below updates bpf_helper_defs.h generation by adding a
> >> definition of __nomerge macro, and using this macro in definitions of
> >> relevant helpers.
> >>
> >> The generated code looks as follows:
> >>
> >>     /* This is auto-generated file. See bpf_doc.py for details. */
> >>
> >>     #if __has_attribute(nomerge)
> >>     #define __nomerge __attribute__((nomerge))
> >>     #else
> >>     #define __nomerge
> >>     #endif
> >>
> >>     /* Forward declarations of BPF structs */
> >>     ...
> >>     static long (*bpf_for_each_map_elem)(void *map, ...) __nomerge = (void *) 164;
> >>     ...
> >>
> >> (In non-RFC version the macro definition would have to be updated to
> >>  check for supported clang version).
> >>
> >> Does community agree with such approach?
> >
> > Makes sense to me. Let's just be very careful to do proper detection
> > of __nomerge "applicability" to ensure we don't cause compilation
> > errors for unsupported Clang (which I'm sure you are well aware of)
> > *and* make it compatible with GCC, so we don't fix it later.
>
> GCC doesn't support the "nomerge" attribute at this point.  We will look
> into adding it or find some other equivalent mechanism that can be
> abstracted in the __nomerge macro.
>
> >>
> >> [1] https://lore.kernel.org/bpf/03bdf90f-f374-1e67-69d6-76dd9c8318a4@meta.com/
> >> [2] https://clang.llvm.org/docs/AttributeReference.html#nomerge
> >> [3] https://reviews.llvm.org/D152986
> >> [4] https://reviews.llvm.org/D152987
> >> ---
> >>  scripts/bpf_doc.py | 37 ++++++++++++++++++++++++++++++-------
> >>  1 file changed, 30 insertions(+), 7 deletions(-)
> >>
> >> diff --git a/scripts/bpf_doc.py b/scripts/bpf_doc.py
> >> index eaae2ce78381..dbd4893c793e 100755
> >> --- a/scripts/bpf_doc.py
> >> +++ b/scripts/bpf_doc.py
> >> @@ -777,14 +777,33 @@ class PrinterHelpers(Printer):
> >>          'bpf_get_socket_cookie',
> >>          'bpf_sk_assign',
> >>      ]
> >> +    # Helpers that need __nomerge attribute
> >> +    nomerge_helpers = set([
> >> +       "bpf_tail_call",
> >> +       "bpf_map_lookup_elem",
> >> +       "bpf_map_update_elem",
> >> +       "bpf_map_delete_elem",
> >> +       "bpf_map_push_elem",
> >> +       "bpf_map_pop_elem",
> >> +       "bpf_map_peek_elem",
> >> +       "bpf_for_each_map_elem",
> >> +       "bpf_redirect_map",
> >> +       "bpf_map_lookup_percpu_elem"
> >> +    ])
> >> +
> >> +    macros = '''\
> >> +#if __has_attribute(nomerge)
> >> +#define __nomerge __attribute__((nomerge))
> >> +#else
> >> +#define __nomerge
> >> +#endif'''

Let's add an extensive comment here,
so that people looking at bpf_helper_defs.h don't need
to search clang documentation on what this attr is doing.
I bet even compiler experts won't be able to explain 'why'
after reading the doc:
https://clang.llvm.org/docs/AttributeReference.html#nomerge
The example from the commit log should probably be in the comment too.
Other than that the approach makes sense to me.
diff mbox series

Patch

diff --git a/scripts/bpf_doc.py b/scripts/bpf_doc.py
index eaae2ce78381..dbd4893c793e 100755
--- a/scripts/bpf_doc.py
+++ b/scripts/bpf_doc.py
@@ -777,14 +777,33 @@  class PrinterHelpers(Printer):
         'bpf_get_socket_cookie',
         'bpf_sk_assign',
     ]
+    # Helpers that need __nomerge attribute
+    nomerge_helpers = set([
+	"bpf_tail_call",
+	"bpf_map_lookup_elem",
+	"bpf_map_update_elem",
+	"bpf_map_delete_elem",
+	"bpf_map_push_elem",
+	"bpf_map_pop_elem",
+	"bpf_map_peek_elem",
+	"bpf_for_each_map_elem",
+	"bpf_redirect_map",
+	"bpf_map_lookup_percpu_elem"
+    ])
+
+    macros = '''\
+#if __has_attribute(nomerge)
+#define __nomerge __attribute__((nomerge))
+#else
+#define __nomerge
+#endif'''
 
     def print_header(self):
-        header = '''\
-/* This is auto-generated file. See bpf_doc.py for details. */
-
-/* Forward declarations of BPF structs */'''
-
-        print(header)
+        print('/* This is auto-generated file. See bpf_doc.py for details. */')
+        print()
+        print(self.macros)
+        print()
+        print('/* Forward declarations of BPF structs */')
         for fwd in self.type_fwds:
             print('%s;' % fwd)
         print('')
@@ -846,7 +865,11 @@  class PrinterHelpers(Printer):
             comma = ', '
             print(one_arg, end='')
 
-        print(') = (void *) %d;' % helper.enum_val)
+        print(')', end='')
+        if proto['name'] in self.nomerge_helpers:
+            print(' __nomerge', end='')
+
+        print(' = (void *) %d;' % helper.enum_val)
         print('')
 
 ###############################################################################