Bug 106733 - bpf: facilitate constant propagation of function addresses
Summary: bpf: facilitate constant propagation of function addresses
Status: RESOLVED FIXED
Alias: None
Product: gcc
Classification: Unclassified
Component: target (show other bugs)
Version: unknown
: P3 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2022-08-24 13:22 UTC by Jose E. Marchesi
Modified: 2022-08-24 13:28 UTC (History)
0 users

See Also:
Host:
Target:
Build:
Known to work:
Known to fail:
Last reconfirmed:


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jose E. Marchesi 2022-08-24 13:22:02 UTC
eBPF effectively supports two kind of call instructions:
    
- The so called pseudo-calls ("bpf to bpf").
- External calls ("bpf to kernel").
    
The BPF call instruction always gets an immediate argument, whose
interpretation varies depending on the purpose of the instruction:
    
- For pseudo-calls, the immediate argument is interpreted as a
  32-bit PC-relative displacement measured in number of 64-bit words
  minus one.
    
- For external calls, the immediate argument is interpreted as the
  identification of a kernel helper.
    
In order to differenciate both flavors of CALL instructions the SRC
field of the instruction (otherwise unused) is abused as an opcode;
if the field holds 0 the instruction is an external call, if it holds
BPF_PSEUDO_CALL the instruction is a pseudo-call.
    
C-to-BPF toolchains, including the GNU toolchain, use the following
practical heuristic at assembly time in order to determine what kind
of CALL instruction to generate: call instructions requiring a fixup
at assembly time are interpreted as pseudo-calls.  This means that in
practice a call instruction involving symbols at assembly time (such
as `call foo') is assembled into a pseudo-call instruction, whereas
something like `call 12' is assembled into an external call
instruction.
    
In both cases, the argument of CALL is an immediate: at the time of
writing eBPF lacks support for indirect calls, i.e. there is no
call-to-register instruction.
    
This is the reason why BPF programs, in practice, rely on certain
optimizations to happen in order to generate calls to immediates.
This is a typical example involving a kernel helper:
    
  static void * (*bpf_map_lookup_elem)(void *map, const void *key
        = (void *) 1;
    
  int foo (...)
  {
    char *ret;
    
    ret = bpf_map_lookup_elem (args...);
    if (ret)
      return 1;
    return 0;
  }
    
Note how the code above relies on the compiler to do constant
propagation so the call to bpf_map_lookup_elem can be compiled to a
`call 1' instruction.
    
While GCC provides a kernel_helper function declaration attribute that
can be used in a robust way to tell GCC to generate an external call
despite of optimization level and any other consideration, the Linux
kernel bpf_helpers.h file relies on tricks like the above.
    
The BPF backend is currently causing the expander to "undo" SSA constant propagations like the above by loading function addresses into registers.  This makes code like the above to not compile properly even with optimization levels of O2 or more.
Comment 1 GCC Commits 2022-08-24 13:26:22 UTC
The master branch has been updated by Jose E. Marchesi <jemarch@gcc.gnu.org>:

https://gcc.gnu.org/g:6d1f144b3e6e3761375bea657718f58fb720fb44

commit r13-2173-g6d1f144b3e6e3761375bea657718f58fb720fb44
Author: Jose E. Marchesi <jose.marchesi@oracle.com>
Date:   Wed Aug 24 13:07:57 2022 +0200

    bpf: facilitate constant propagation of function addresses
    
    eBPF effectively supports two kind of call instructions:
    
    - The so called pseudo-calls ("bpf to bpf").
    - External calls ("bpf to kernel").
    
    The BPF call instruction always gets an immediate argument, whose
    interpretation varies depending on the purpose of the instruction:
    
    - For pseudo-calls, the immediate argument is interpreted as a
      32-bit PC-relative displacement measured in number of 64-bit words
      minus one.
    
    - For external calls, the immediate argument is interpreted as the
      identification of a kernel helper.
    
    In order to differenciate both flavors of CALL instructions the SRC
    field of the instruction (otherwise unused) is abused as an opcode;
    if the field holds 0 the instruction is an external call, if it holds
    BPF_PSEUDO_CALL the instruction is a pseudo-call.
    
    C-to-BPF toolchains, including the GNU toolchain, use the following
    practical heuristic at assembly time in order to determine what kind
    of CALL instruction to generate: call instructions requiring a fixup
    at assembly time are interpreted as pseudo-calls.  This means that in
    practice a call instruction involving symbols at assembly time (such
    as `call foo') is assembled into a pseudo-call instruction, whereas
    something like `call 12' is assembled into an external call
    instruction.
    
    In both cases, the argument of CALL is an immediate: at the time of
    writing eBPF lacks support for indirect calls, i.e. there is no
    call-to-register instruction.
    
    This is the reason why BPF programs, in practice, rely on certain
    optimizations to happen in order to generate calls to immediates.
    This is a typical example involving a kernel helper:
    
      static void * (*bpf_map_lookup_elem)(void *map, const void *key)
        = (void *) 1;
    
      int foo (...)
      {
        char *ret;
    
        ret = bpf_map_lookup_elem (args...);
        if (ret)
          return 1;
        return 0;
      }
    
    Note how the code above relies on the compiler to do constant
    propagation so the call to bpf_map_lookup_elem can be compiled to a
    `call 1' instruction.
    
    While GCC provides a kernel_helper function declaration attribute that
    can be used in a robust way to tell GCC to generate an external call
    despite of optimization level and any other consideration, the Linux
    kernel bpf_helpers.h file relies on tricks like the above.
    
    This patch modifies the BPF backend to avoid SSA sparse constant
    propagation to be "undone" by the expander loading the function
    address into a register.  A new test is also added.
    
    Tested in bpf-unknown-linux-gnu.
    No regressions.
    
    gcc/ChangeLog:
    
            PR target/106733
            * config/bpf/bpf.cc (bpf_legitimate_address_p): Recognize integer
            constants as legitimate addresses for functions.
            (bpf_small_register_classes_for_mode_p): Define target hook.
    
    gcc/testsuite/ChangeLog:
    
            PR target/106733
            * gcc.target/bpf/constant-calls.c: Rename to ...
            * gcc.target/bpf/constant-calls-1.c: and modify to not expect
            failure anymore.
            * gcc.target/bpf/constant-calls-2.c: New test.
Comment 2 Jose E. Marchesi 2022-08-24 13:28:22 UTC
Urgh I obviously meant bpf-unknown-none.
Comment 3 Jose E. Marchesi 2022-08-24 13:28:53 UTC
Changing status to fixed.