[v3] bpf: add preserve_field_info builtin
Checks
Commit Message
>> I'm not sure whether this behavior is a known limitation or an
>> oversight. In my opinion it makes more sense to error at compile time,
>> becuase even after the loader patches the return value it still will
>> not be correct for these cases.
>>
>> So for now I've set these cases to error out, but it would be just as
>> simple to mimic the LLVM behavior. WDYT?
>
> I would say it makes more sense to error out than to return invalid
> data.
>
> However, the divergence wrt LLVM is a concern. What about keeping this
> behavior in the GCC backend and simultaneously raise the issue in
> bpf@vger? If it was a design oversight and the change doesn't impact
> kernel sources, they may be willing to change it.
>
OK, I will raise the question there.
>> [...]
>> +@deftypefn {Built-in Function} unsigned int __builtin_preserve_field_info (@var{expr}, unsigned int @var{kind})
>> +BPF Compile Once-Run Everywhere (CO-RE) support. This builtin is used to
>> +extract information to aid in struct/union relocations. @var{expr} is
>> +an access to a field of a struct or union. Depending on @var{kind}, different
>> +information is returned to the program. A CO-RE relocation for the access in
>> +@var{expr} with kind @var{kind} is recorded if @code{-mco-re} is in effect.
>> +
>> +The following values are supported for @var{kind}:
>> +@table @var
>> +@item FIELD_BYTE_OFFSET = 0
>> +The returned value is the offset, in bytes, of the field from the
>> +beginning of the containing structure.
>
> What about bit fields? Is this the byte offset of the containing word?
Yes.
>
>> +@item FIELD_BYTE_SIZE = 1
>> +The returned value is the size, in bytes, of the field.
>
> For bit fields, is this the size of the containing word?
Right again. I have updated the docs for these two in v3.
>
>> +@item FIELD_EXISTENCE = 2
>> +The returned value is 1 if the field exists, 0 otherwise. Always 1 at
>> +compile time.
>> +
>> +@item FIELD_SIGNEDNESS = 3
>> +The returned value is 1 if the field is signed, 0 otherwise.
>> +
>> +@item FIELD_LSHIFT_U64 = 4
>> +@itemx FIELD_RSHIFT_U64 = 5
>> +Suppose the field were loaded into a value of FIELD_BYTE_SIZE bytes
>> +and then zero or sign-extended to a 64-bit value. The returned value
>> +is the number of bits of left or right shifting respectively that
>> +would be needed to recover the original value of the field.
>
> What are the semantics for bit fields?
The semantics for bit fields are the same. These two are primarily
useful for bit fields - a common case in eBPF programs is to read
some field of a struct through a pointer. If it's a kernel struct
that may change between versions and you are reading a bit field,
you would use this builtin to get the eBPF loader to patch the
appropriate steps to extract the field.
So the process to read a bit field is the following:
1. read FIELD_BYTE_SIZE bytes and zero-extend the value of the
read into a u64
2. left shift the result FIELD_LSHIFT_U64 bits
3. if FIELD_SIGNEDNESS
then arithmetic right-shift by FIELD_RSHIFT_U64 bits
otherwise
logical right-shift by FIELD_RSHIFT_U64 bits
Where all these FIELD_* values might change between kernels and
need patching by the eBPF loader.
I struggled a bit trying to find the best wording to describe this
in the docs, and settled on adding some example code since I think
that is the most clear.
Please take a look at the updated version and let me know if you
have any suggestions, I'm happy to hear them.
Thanks
---
[Changes from v2: update documentation in extend.texi]
Add BPF __builtin_preserve_field_info. This builtin is used to extract
information to facilitate struct and union relocations performed by the
BPF loader, especially for bitfields.
The builtin has the following signature:
unsigned int __builtin_preserve_field_info (EXPR, unsigned int KIND);
Where EXPR is an expression accessing a field of a struct or union.
Depending on KIND, different information is returned to the program. The
supported values for KIND are as follows:
enum {
FIELD_BYTE_OFFSET = 0,
FIELD_BYTE_SIZE,
FIELD_EXISTENCE,
FIELD_SIGNEDNESS,
FIELD_LSHIFT_U64,
FIELD_RSHIFT_U64
};
If -mco-re is in effect (explicitly or implicitly specified), a CO-RE
relocation is added for the access in EXPR recording the relevant
information according to KIND.
gcc/
* config/bpf/bpf.cc: Support __builtin_preserve_field_info.
(enum bpf_builtins): Add new builtin.
(bpf_init_builtins): Likewise.
(bpf_core_field_info): New function.
(bpf_expand_builtin): Accomodate new builtin. Refactor adding new
relocation to...
(maybe_make_core_relo): ... here. New function.
(bpf_resolve_overloaded_builtin): Accomodate new builtin.
(bpf_core_newdecl): Likewise.
(bpf_core_walk): Likewise.
(bpf_core_is_maybe_aggregate_access): Improve logic.
(struct core_walk_data): New.
* config/bpf/coreout.cc (bpf_core_reloc_add): Allow adding different
relocation kinds.
* config/bpf/coreout.h: Analogous change.
* doc/extend.texi: Document BPF __builtin_preserve_field_info.
gcc/testsuite/
* gcc.target/bpf/core-builtin-fieldinfo-errors-1.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-errors-2.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-existence-1.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-lshift-1-be.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-lshift-1-le.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-lshift-2.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-offset-1.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-rshift-1.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-rshift-2.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-sign-1.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-sign-2.c: New test.
* gcc.target/bpf/core-builtin-fieldinfo-size-1.c: New test.
---
gcc/config/bpf/bpf.cc | 402 ++++++++++++++----
gcc/config/bpf/coreout.cc | 5 +-
gcc/config/bpf/coreout.h | 2 +-
gcc/doc/extend.texi | 77 ++++
.../bpf/core-builtin-fieldinfo-errors-1.c | 23 +
.../bpf/core-builtin-fieldinfo-errors-2.c | 23 +
.../bpf/core-builtin-fieldinfo-existence-1.c | 34 ++
.../bpf/core-builtin-fieldinfo-lshift-1-be.c | 37 ++
.../bpf/core-builtin-fieldinfo-lshift-1-le.c | 37 ++
.../bpf/core-builtin-fieldinfo-lshift-2.c | 37 ++
.../bpf/core-builtin-fieldinfo-offset-1.c | 56 +++
.../bpf/core-builtin-fieldinfo-rshift-1.c | 36 ++
.../bpf/core-builtin-fieldinfo-rshift-2.c | 35 ++
.../bpf/core-builtin-fieldinfo-sign-1.c | 33 ++
.../bpf/core-builtin-fieldinfo-sign-2.c | 45 ++
.../bpf/core-builtin-fieldinfo-size-1.c | 43 ++
16 files changed, 850 insertions(+), 75 deletions(-)
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-1.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-2.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-existence-1.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-be.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-le.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-2.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-offset-1.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-1.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-2.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-1.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-2.c
create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-size-1.c
Comments
Hi David.
Thanks for the updates.
OK for master.
>>> I'm not sure whether this behavior is a known limitation or an
>>> oversight. In my opinion it makes more sense to error at compile time,
>>> becuase even after the loader patches the return value it still will
>>> not be correct for these cases.
>>>
>>> So for now I've set these cases to error out, but it would be just as
>>> simple to mimic the LLVM behavior. WDYT?
>>
>> I would say it makes more sense to error out than to return invalid
>> data.
>>
>> However, the divergence wrt LLVM is a concern. What about keeping this
>> behavior in the GCC backend and simultaneously raise the issue in
>> bpf@vger? If it was a design oversight and the change doesn't impact
>> kernel sources, they may be willing to change it.
>>
>
> OK, I will raise the question there.
>
>>> [...]
>>> +@deftypefn {Built-in Function} unsigned int
>>> __builtin_preserve_field_info (@var{expr}, unsigned int @var{kind})
>>> +BPF Compile Once-Run Everywhere (CO-RE) support. This builtin is used to
>>> +extract information to aid in struct/union relocations. @var{expr} is
>>> +an access to a field of a struct or union. Depending on @var{kind}, different
>>> +information is returned to the program. A CO-RE relocation for the access in
>>> +@var{expr} with kind @var{kind} is recorded if @code{-mco-re} is in effect.
>>> +
>>> +The following values are supported for @var{kind}:
>>> +@table @var
>>> +@item FIELD_BYTE_OFFSET = 0
>>> +The returned value is the offset, in bytes, of the field from the
>>> +beginning of the containing structure.
>>
>> What about bit fields? Is this the byte offset of the containing word?
>
> Yes.
>
>>
>>> +@item FIELD_BYTE_SIZE = 1
>>> +The returned value is the size, in bytes, of the field.
>>
>> For bit fields, is this the size of the containing word?
>
> Right again. I have updated the docs for these two in v3.
>
>>
>>> +@item FIELD_EXISTENCE = 2
>>> +The returned value is 1 if the field exists, 0 otherwise. Always 1 at
>>> +compile time.
>>> +
>>> +@item FIELD_SIGNEDNESS = 3
>>> +The returned value is 1 if the field is signed, 0 otherwise.
>>> +
>>> +@item FIELD_LSHIFT_U64 = 4
>>> +@itemx FIELD_RSHIFT_U64 = 5
>>> +Suppose the field were loaded into a value of FIELD_BYTE_SIZE bytes
>>> +and then zero or sign-extended to a 64-bit value. The returned value
>>> +is the number of bits of left or right shifting respectively that
>>> +would be needed to recover the original value of the field.
>>
>> What are the semantics for bit fields?
>
> The semantics for bit fields are the same. These two are primarily
> useful for bit fields - a common case in eBPF programs is to read
> some field of a struct through a pointer. If it's a kernel struct
> that may change between versions and you are reading a bit field,
> you would use this builtin to get the eBPF loader to patch the
> appropriate steps to extract the field.
>
> So the process to read a bit field is the following:
>
> 1. read FIELD_BYTE_SIZE bytes and zero-extend the value of the
> read into a u64
> 2. left shift the result FIELD_LSHIFT_U64 bits
> 3. if FIELD_SIGNEDNESS
> then arithmetic right-shift by FIELD_RSHIFT_U64 bits
> otherwise
> logical right-shift by FIELD_RSHIFT_U64 bits
>
> Where all these FIELD_* values might change between kernels and
> need patching by the eBPF loader.
>
> I struggled a bit trying to find the best wording to describe this
> in the docs, and settled on adding some example code since I think
> that is the most clear.
>
> Please take a look at the updated version and let me know if you
> have any suggestions, I'm happy to hear them.
>
> Thanks
>
> ---
>
> [Changes from v2: update documentation in extend.texi]
>
> Add BPF __builtin_preserve_field_info. This builtin is used to extract
> information to facilitate struct and union relocations performed by the
> BPF loader, especially for bitfields.
>
> The builtin has the following signature:
>
> unsigned int __builtin_preserve_field_info (EXPR, unsigned int KIND);
>
> Where EXPR is an expression accessing a field of a struct or union.
> Depending on KIND, different information is returned to the program. The
> supported values for KIND are as follows:
>
> enum {
> FIELD_BYTE_OFFSET = 0,
> FIELD_BYTE_SIZE,
> FIELD_EXISTENCE,
> FIELD_SIGNEDNESS,
> FIELD_LSHIFT_U64,
> FIELD_RSHIFT_U64
> };
>
> If -mco-re is in effect (explicitly or implicitly specified), a CO-RE
> relocation is added for the access in EXPR recording the relevant
> information according to KIND.
>
> gcc/
>
> * config/bpf/bpf.cc: Support __builtin_preserve_field_info.
> (enum bpf_builtins): Add new builtin.
> (bpf_init_builtins): Likewise.
> (bpf_core_field_info): New function.
> (bpf_expand_builtin): Accomodate new builtin. Refactor adding new
> relocation to...
> (maybe_make_core_relo): ... here. New function.
> (bpf_resolve_overloaded_builtin): Accomodate new builtin.
> (bpf_core_newdecl): Likewise.
> (bpf_core_walk): Likewise.
> (bpf_core_is_maybe_aggregate_access): Improve logic.
> (struct core_walk_data): New.
> * config/bpf/coreout.cc (bpf_core_reloc_add): Allow adding different
> relocation kinds.
> * config/bpf/coreout.h: Analogous change.
> * doc/extend.texi: Document BPF __builtin_preserve_field_info.
>
> gcc/testsuite/
>
> * gcc.target/bpf/core-builtin-fieldinfo-errors-1.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-errors-2.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-existence-1.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-lshift-1-be.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-lshift-1-le.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-lshift-2.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-offset-1.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-rshift-1.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-rshift-2.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-sign-1.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-sign-2.c: New test.
> * gcc.target/bpf/core-builtin-fieldinfo-size-1.c: New test.
> ---
> gcc/config/bpf/bpf.cc | 402 ++++++++++++++----
> gcc/config/bpf/coreout.cc | 5 +-
> gcc/config/bpf/coreout.h | 2 +-
> gcc/doc/extend.texi | 77 ++++
> .../bpf/core-builtin-fieldinfo-errors-1.c | 23 +
> .../bpf/core-builtin-fieldinfo-errors-2.c | 23 +
> .../bpf/core-builtin-fieldinfo-existence-1.c | 34 ++
> .../bpf/core-builtin-fieldinfo-lshift-1-be.c | 37 ++
> .../bpf/core-builtin-fieldinfo-lshift-1-le.c | 37 ++
> .../bpf/core-builtin-fieldinfo-lshift-2.c | 37 ++
> .../bpf/core-builtin-fieldinfo-offset-1.c | 56 +++
> .../bpf/core-builtin-fieldinfo-rshift-1.c | 36 ++
> .../bpf/core-builtin-fieldinfo-rshift-2.c | 35 ++
> .../bpf/core-builtin-fieldinfo-sign-1.c | 33 ++
> .../bpf/core-builtin-fieldinfo-sign-2.c | 45 ++
> .../bpf/core-builtin-fieldinfo-size-1.c | 43 ++
> 16 files changed, 850 insertions(+), 75 deletions(-)
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-1.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-2.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-existence-1.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-be.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-le.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-2.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-offset-1.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-1.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-2.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-1.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-2.c
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-size-1.c
>
> diff --git a/gcc/config/bpf/bpf.cc b/gcc/config/bpf/bpf.cc
> index 51055651707..ea8ca64d1d6 100644
> --- a/gcc/config/bpf/bpf.cc
> +++ b/gcc/config/bpf/bpf.cc
> @@ -184,13 +184,13 @@ enum bpf_builtins
>
> /* Compile Once - Run Everywhere (CO-RE) support. */
> BPF_BUILTIN_PRESERVE_ACCESS_INDEX,
> + BPF_BUILTIN_PRESERVE_FIELD_INFO,
>
> BPF_BUILTIN_MAX,
> };
>
> static GTY (()) tree bpf_builtins[(int) BPF_BUILTIN_MAX];
>
> -
> void bpf_register_coreattr_pass (void);
>
> /* Initialize the per-function machine status. */
> @@ -966,6 +966,9 @@ bpf_init_builtins (void)
> def_builtin ("__builtin_preserve_access_index",
> BPF_BUILTIN_PRESERVE_ACCESS_INDEX,
> build_function_type_list (ptr_type_node, ptr_type_node, 0));
> + def_builtin ("__builtin_preserve_field_info",
> + BPF_BUILTIN_PRESERVE_FIELD_INFO,
> + build_function_type_list (unsigned_type_node, ptr_type_node, unsigned_type_node, 0));
> }
>
> #undef TARGET_INIT_BUILTINS
> @@ -975,6 +978,199 @@ static tree bpf_core_compute (tree, vec<unsigned int> *);
> static int bpf_core_get_index (const tree);
> static bool is_attr_preserve_access (tree);
>
> +/* BPF Compile Once - Run Everywhere (CO-RE) support. Construct a CO-RE
> + relocation record for EXPR of kind KIND to be emitted in the .BTF.ext
> + section. Does nothing if we are not targetting BPF CO-RE, or if the
> + constructed relocation would be a no-op. */
> +
> +static void
> +maybe_make_core_relo (tree expr, enum btf_core_reloc_kind kind)
> +{
> + /* If we are not targetting BPF CO-RE, do not make a relocation. We
> + might not be generating any debug info at all. */
> + if (!TARGET_BPF_CORE)
> + return;
> +
> + auto_vec<unsigned int, 16> accessors;
> + tree container = bpf_core_compute (expr, &accessors);
> +
> + /* Any valid use of the builtin must have at least one access. Otherwise,
> + there is nothing to record and nothing to do. This is primarily a
> + guard against optimizations leading to unexpected expressions in the
> + argument of the builtin. For example, if the builtin is used to read
> + a field of a structure which can be statically determined to hold a
> + constant value, the argument to the builtin will be optimized to that
> + constant. This is OK, and means the builtin call is superfluous.
> + e.g.
> + struct S foo;
> + foo.a = 5;
> + int x = __preserve_access_index (foo.a);
> + ... do stuff with x
> + 'foo.a' in the builtin argument will be optimized to '5' with -01+.
> + This sequence does not warrant recording a CO-RE relocation. */
> +
> + if (accessors.length () < 1)
> + return;
> + accessors.reverse ();
> +
> + rtx_code_label *label = gen_label_rtx ();
> + LABEL_PRESERVE_P (label) = 1;
> + emit_label (label);
> +
> + /* Determine what output section this relocation will apply to.
> + If this function is associated with a section, use that. Otherwise,
> + fall back on '.text'. */
> + const char * section_name;
> + if (current_function_decl && DECL_SECTION_NAME (current_function_decl))
> + section_name = DECL_SECTION_NAME (current_function_decl);
> + else
> + section_name = ".text";
> +
> + /* Add the CO-RE relocation information to the BTF container. */
> + bpf_core_reloc_add (TREE_TYPE (container), section_name, &accessors, label,
> + kind);
> +}
> +
> +/* Expand a call to __builtin_preserve_field_info by evaluating the requested
> + information about SRC according to KIND, and return a tree holding
> + the result. */
> +
> +static tree
> +bpf_core_field_info (tree src, enum btf_core_reloc_kind kind)
> +{
> + unsigned int result;
> + poly_int64 bitsize, bitpos;
> + tree var_off = NULL_TREE;
> + machine_mode mode;
> + int unsignedp, reversep, volatilep;
> + location_t loc = EXPR_LOCATION (src);
> +
> + get_inner_reference (src, &bitsize, &bitpos, &var_off, &mode, &unsignedp,
> + &reversep, &volatilep);
> +
> + /* Note: Use DECL_BIT_FIELD_TYPE rather than DECL_BIT_FIELD here, because it
> + remembers whether the field in question was originally declared as a
> + bitfield, regardless of how it has been optimized. */
> + bool bitfieldp = (TREE_CODE (src) == COMPONENT_REF
> + && DECL_BIT_FIELD_TYPE (TREE_OPERAND (src, 1)));
> +
> + unsigned int align = TYPE_ALIGN (TREE_TYPE (src));
> + if (TREE_CODE (src) == COMPONENT_REF)
> + {
> + tree field = TREE_OPERAND (src, 1);
> + if (DECL_BIT_FIELD_TYPE (field))
> + align = TYPE_ALIGN (DECL_BIT_FIELD_TYPE (field));
> + else
> + align = TYPE_ALIGN (TREE_TYPE (field));
> + }
> +
> + unsigned int start_bitpos = bitpos & ~(align - 1);
> + unsigned int end_bitpos = start_bitpos + align;
> +
> + switch (kind)
> + {
> + case BPF_RELO_FIELD_BYTE_OFFSET:
> + {
> + if (var_off != NULL_TREE)
> + {
> + error_at (loc, "unsupported variable field offset");
> + return error_mark_node;
> + }
> +
> + if (bitfieldp)
> + result = start_bitpos / 8;
> + else
> + result = bitpos / 8;
> + }
> + break;
> +
> + case BPF_RELO_FIELD_BYTE_SIZE:
> + {
> + if (mode == BLKmode && bitsize == -1)
> + {
> + error_at (loc, "unsupported variable size field access");
> + return error_mark_node;
> + }
> +
> + if (bitfieldp)
> + {
> + /* To match LLVM behavior, byte size of bitfields is recorded as
> + the full size of the base type. A 3-bit bitfield of type int is
> + therefore recorded as having a byte size of 4 bytes. */
> + result = end_bitpos - start_bitpos;
> + if (result & (result - 1))
> + {
> + error_at (loc, "unsupported field expression");
> + return error_mark_node;
> + }
> + result = result / 8;
> + }
> + else
> + result = bitsize / 8;
> + }
> + break;
> +
> + case BPF_RELO_FIELD_EXISTS:
> + /* The field always exists at compile time. */
> + result = 1;
> + break;
> +
> + case BPF_RELO_FIELD_SIGNED:
> + result = !unsignedp;
> + break;
> +
> + case BPF_RELO_FIELD_LSHIFT_U64:
> + case BPF_RELO_FIELD_RSHIFT_U64:
> + {
> + if (mode == BLKmode && bitsize == -1)
> + {
> + error_at (loc, "unsupported variable size field access");
> + return error_mark_node;
> + }
> + if (var_off != NULL_TREE)
> + {
> + error_at (loc, "unsupported variable field offset");
> + return error_mark_node;
> + }
> +
> + if (!bitfieldp)
> + {
> + if (bitsize > 64)
> + {
> + error_at (loc, "field size too large");
> + return error_mark_node;
> + }
> + result = 64 - bitsize;
> + break;
> + }
> +
> + if (end_bitpos - start_bitpos > 64)
> + {
> + error_at (loc, "field size too large");
> + return error_mark_node;
> + }
> +
> + if (kind == BPF_RELO_FIELD_LSHIFT_U64)
> + {
> + if (TARGET_BIG_ENDIAN)
> + result = bitpos + 64 - start_bitpos - align;
> + else
> + result = start_bitpos + 64 - bitpos - bitsize;
> + }
> + else /* RSHIFT_U64 */
> + result = 64 - bitsize;
> + }
> + break;
> +
> + default:
> + error ("invalid second argument to built-in function");
> + return error_mark_node;
> + break;
> + }
> +
> + return build_int_cst (unsigned_type_node, result);
> +}
> +
> /* Expand a call to a BPF-specific built-in function that was set up
> with bpf_init_builtins. */
>
> @@ -1025,17 +1221,15 @@ bpf_expand_builtin (tree exp, rtx target ATTRIBUTE_UNUSED,
> /* The result of the load is in R0. */
> return gen_rtx_REG (ops[0].mode, BPF_R0);
> }
> +
> else if (code == -1)
> {
> - /* A resolved overloaded builtin, e.g. __bpf_preserve_access_index_si */
> + /* A resolved overloaded __builtin_preserve_access_index. */
> tree arg = CALL_EXPR_ARG (exp, 0);
>
> if (arg == NULL_TREE)
> return NULL_RTX;
>
> - auto_vec<unsigned int, 16> accessors;
> - tree container;
> -
> if (TREE_CODE (arg) == SSA_NAME)
> {
> gimple *def_stmt = SSA_NAME_DEF_STMT (arg);
> @@ -1049,51 +1243,42 @@ bpf_expand_builtin (tree exp, rtx target ATTRIBUTE_UNUSED,
> /* Avoid double-recording information if the argument is an access to
> a struct/union marked __attribute__((preserve_access_index)). This
> Will be handled by the attribute handling pass. */
> - if (is_attr_preserve_access (arg))
> - return expand_normal (arg);
> -
> - container = bpf_core_compute (arg, &accessors);
> -
> - /* Any valid use of the builtin must have at least one access. Otherwise,
> - there is nothing to record and nothing to do. This is primarily a
> - guard against optimizations leading to unexpected expressions in the
> - argument of the builtin. For example, if the builtin is used to read
> - a field of a structure which can be statically determined to hold a
> - constant value, the argument to the builtin will be optimized to that
> - constant. This is OK, and means the builtin call is superfluous.
> - e.g.
> - struct S foo;
> - foo.a = 5;
> - int x = __preserve_access_index (foo.a);
> - ... do stuff with x
> - 'foo.a' in the builtin argument will be optimized to '5' with -01+.
> - This sequence does not warrant recording a CO-RE relocation. */
> -
> - if (accessors.length () < 1)
> - return expand_normal (arg);
> -
> - accessors.reverse ();
> -
> - container = TREE_TYPE (container);
> -
> - rtx_code_label *label = gen_label_rtx ();
> - LABEL_PRESERVE_P (label) = 1;
> - emit_label (label);
> -
> - /* Determine what output section this relocation will apply to.
> - If this function is associated with a section, use that. Otherwise,
> - fall back on '.text'. */
> - const char * section_name;
> - if (current_function_decl && DECL_SECTION_NAME (current_function_decl))
> - section_name = DECL_SECTION_NAME (current_function_decl);
> + if (!is_attr_preserve_access (arg))
> + maybe_make_core_relo (arg, BPF_RELO_FIELD_BYTE_OFFSET);
> +
> + return expand_normal (arg);
> + }
> +
> + else if (code == -2)
> + {
> + /* A resolved overloaded __builtin_preserve_field_info. */
> + tree src = CALL_EXPR_ARG (exp, 0);
> + tree kind_tree = CALL_EXPR_ARG (exp, 1);
> + unsigned HOST_WIDE_INT kind_val;
> + if (tree_fits_uhwi_p (kind_tree))
> + kind_val = tree_to_uhwi (kind_tree);
> else
> - section_name = ".text";
> + error ("invalid argument to built-in function");
>
> - /* Add the CO-RE relocation information to the BTF container. */
> - bpf_core_reloc_add (container, section_name, &accessors, label);
> + enum btf_core_reloc_kind kind = (enum btf_core_reloc_kind) kind_val;
>
> - return expand_normal (arg);
> + if (TREE_CODE (src) == SSA_NAME)
> + {
> + gimple *def_stmt = SSA_NAME_DEF_STMT (src);
> + if (is_gimple_assign (def_stmt))
> + src = gimple_assign_rhs1 (def_stmt);
> + }
> + if (TREE_CODE (src) == ADDR_EXPR)
> + src = TREE_OPERAND (src, 0);
> +
> + tree result = bpf_core_field_info (src, kind);
> +
> + if (result != error_mark_node)
> + maybe_make_core_relo (src, kind);
> +
> + return expand_normal (result);
> }
> +
> gcc_unreachable ();
> }
>
> @@ -1259,41 +1444,64 @@ bpf_core_get_index (const tree node)
> __builtin_preserve_access_index. */
>
> static tree
> -bpf_core_newdecl (tree type)
> +bpf_core_newdecl (tree type, bool is_pai)
> {
> - tree rettype = build_function_type_list (type, type, NULL);
> + tree rettype;
> char name[80];
> - int len = snprintf (name, sizeof (name), "%s", "__builtin_pai_");
> + static unsigned long pai_count = 0;
> + static unsigned long pfi_count = 0;
>
> - static unsigned long cnt = 0;
> - len = snprintf (name + len, sizeof (name) - len, "%lu", cnt++);
> + if (is_pai)
> + {
> + rettype = build_function_type_list (type, type, NULL);
> + int len = snprintf (name, sizeof (name), "%s", "__builtin_pai_");
> + len = snprintf (name + len, sizeof (name) - len, "%lu", pai_count++);
> + }
> + else
> + {
> + rettype = build_function_type_list (unsigned_type_node, type,
> + unsigned_type_node, NULL);
> + int len = snprintf (name, sizeof (name), "%s", "__builtin_pfi_");
> + len = snprintf (name + len, sizeof (name) - len, "%lu", pfi_count++);
> + }
>
> - return add_builtin_function_ext_scope (name, rettype, -1, BUILT_IN_MD, NULL,
> - NULL_TREE);
> + return add_builtin_function_ext_scope (name, rettype, is_pai ? -1 : -2,
> + BUILT_IN_MD, NULL, NULL_TREE);
> }
>
> /* Return whether EXPR could access some aggregate data structure that
> BPF CO-RE support needs to know about. */
>
> -static int
> +static bool
> bpf_core_is_maybe_aggregate_access (tree expr)
> {
> - enum tree_code code = TREE_CODE (expr);
> - if (code == COMPONENT_REF || code == ARRAY_REF)
> - return 1;
> -
> - if (code == ADDR_EXPR)
> + switch (TREE_CODE (expr))
> + {
> + case COMPONENT_REF:
> + case BIT_FIELD_REF:
> + case ARRAY_REF:
> + case ARRAY_RANGE_REF:
> + return true;
> + case ADDR_EXPR:
> + case NOP_EXPR:
> return bpf_core_is_maybe_aggregate_access (TREE_OPERAND (expr, 0));
> -
> - return 0;
> + default:
> + return false;
> + }
> }
>
> +struct core_walk_data {
> + location_t loc;
> + tree arg;
> +};
> +
> /* Callback function used with walk_tree from bpf_resolve_overloaded_builtin. */
>
> static tree
> bpf_core_walk (tree *tp, int *walk_subtrees, void *data)
> {
> - location_t loc = *((location_t *) data);
> + struct core_walk_data *dat = (struct core_walk_data *) data;
> + bool is_pai = dat->arg == NULL_TREE;
>
> /* If this is a type, don't do anything. */
> if (TYPE_P (*tp))
> @@ -1302,10 +1510,18 @@ bpf_core_walk (tree *tp, int *walk_subtrees, void *data)
> return NULL_TREE;
> }
>
> + /* Build a new function call to a resolved builtin for the desired operation.
> + If this is a preserve_field_info call, pass along the argument to the
> + resolved builtin call. */
> if (bpf_core_is_maybe_aggregate_access (*tp))
> {
> - tree newdecl = bpf_core_newdecl (TREE_TYPE (*tp));
> - tree newcall = build_call_expr_loc (loc, newdecl, 1, *tp);
> + tree newdecl = bpf_core_newdecl (TREE_TYPE (*tp), is_pai);
> + tree newcall;
> + if (is_pai)
> + newcall = build_call_expr_loc (dat->loc, newdecl, 1, *tp);
> + else
> + newcall = build_call_expr_loc (dat->loc, newdecl, 2, *tp, dat->arg);
> +
> *tp = newcall;
> *walk_subtrees = 0;
> }
> @@ -1330,6 +1546,30 @@ bpf_small_register_classes_for_mode_p (machine_mode mode)
> #define TARGET_SMALL_REGISTER_CLASSES_FOR_MODE_P \
> bpf_small_register_classes_for_mode_p
>
> +/* Return whether EXPR is a valid first argument for a call to
> + __builtin_preserve_field_info. */
> +
> +static bool
> +bpf_is_valid_preserve_field_info_arg (tree expr)
> +{
> + switch (TREE_CODE (expr))
> + {
> + case COMPONENT_REF:
> + case BIT_FIELD_REF:
> + case ARRAY_REF:
> + case ARRAY_RANGE_REF:
> + return true;
> + case NOP_EXPR:
> + return bpf_is_valid_preserve_field_info_arg (TREE_OPERAND (expr, 0));
> + case ADDR_EXPR:
> + /* Do not accept ADDR_EXPRs like &foo.bar, but do accept accesses like
> + foo.baz where baz is an array. */
> + return (TREE_CODE (TREE_TYPE (TREE_OPERAND (expr, 0))) == ARRAY_TYPE);
> + default:
> + return false;
> + }
> +}
> +
> /* Implement TARGET_RESOLVE_OVERLOADED_BUILTIN (see gccint manual section
> Target Macros::Misc.).
> We use this for the __builtin_preserve_access_index builtin for CO-RE
> @@ -1344,7 +1584,12 @@ bpf_small_register_classes_for_mode_p (machine_mode mode)
> static tree
> bpf_resolve_overloaded_builtin (location_t loc, tree fndecl, void *arglist)
> {
> - if (DECL_MD_FUNCTION_CODE (fndecl) != BPF_BUILTIN_PRESERVE_ACCESS_INDEX)
> + bool is_pai = DECL_MD_FUNCTION_CODE (fndecl)
> + == BPF_BUILTIN_PRESERVE_ACCESS_INDEX;
> + bool is_pfi = DECL_MD_FUNCTION_CODE (fndecl)
> + == BPF_BUILTIN_PRESERVE_FIELD_INFO;
> +
> + if (!is_pai && !is_pfi)
> return NULL_TREE;
>
> /* We only expect one argument, but it may be an arbitrarily-complicated
> @@ -1352,18 +1597,26 @@ bpf_resolve_overloaded_builtin (location_t loc, tree fndecl, void *arglist)
> vec<tree, va_gc> *params = static_cast<vec<tree, va_gc> *> (arglist);
> unsigned n_params = params ? params->length() : 0;
>
> - if (n_params != 1)
> + if ((is_pai && n_params != 1) || (is_pfi && n_params != 2))
> {
> - error_at (loc, "expected exactly 1 argument");
> - return NULL_TREE;
> + error_at (loc, "wrong number of arguments");
> + return error_mark_node;
> }
>
> tree param = (*params)[0];
>
> - /* If not generating BPF_CORE information, the builtin does nothing. */
> - if (!TARGET_BPF_CORE)
> + /* If not generating BPF_CORE information, preserve_access_index does nothing,
> + and simply "resolves to" the argument. */
> + if (!TARGET_BPF_CORE && is_pai)
> return param;
>
> + if (is_pfi && !bpf_is_valid_preserve_field_info_arg (param))
> + {
> + error_at (EXPR_LOC_OR_LOC (param, loc),
> + "argument is not a field access");
> + return error_mark_node;
> + }
> +
> /* Do remove_c_maybe_const_expr for the arg.
> TODO: WHY do we have to do this here? Why doesn't c-typeck take care
> of it before or after this hook? */
> @@ -1387,7 +1640,11 @@ bpf_resolve_overloaded_builtin (location_t loc, tree fndecl, void *arglist)
> This ensures that all the relevant information remains within the
> expression trees the builtin finally gets. */
>
> - walk_tree (¶m, bpf_core_walk, (void *) &loc, NULL);
> + struct core_walk_data data;
> + data.loc = loc;
> + data.arg = is_pai ? NULL_TREE : (*params)[1];
> +
> + walk_tree (¶m, bpf_core_walk, (void *) &data, NULL);
>
> return param;
> }
> @@ -1524,7 +1781,8 @@ handle_attr_preserve (function *fn)
> emit_label (label);
>
> /* Add the CO-RE relocation information to the BTF container. */
> - bpf_core_reloc_add (container, section_name, &accessors, label);
> + bpf_core_reloc_add (container, section_name, &accessors, label,
> + BPF_RELO_FIELD_BYTE_OFFSET);
> }
> }
> }
> diff --git a/gcc/config/bpf/coreout.cc b/gcc/config/bpf/coreout.cc
> index 8897a045ea1..9f71040846b 100644
> --- a/gcc/config/bpf/coreout.cc
> +++ b/gcc/config/bpf/coreout.cc
> @@ -152,7 +152,8 @@ static GTY (()) vec<bpf_core_section_ref, va_gc> *bpf_core_sections;
>
> void
> bpf_core_reloc_add (const tree type, const char * section_name,
> - vec<unsigned int> *accessors, rtx_code_label *label)
> + vec<unsigned int> *accessors, rtx_code_label *label,
> + enum btf_core_reloc_kind kind)
> {
> char buf[40];
> unsigned int i, n = 0;
> @@ -173,7 +174,7 @@ bpf_core_reloc_add (const tree type, const char * section_name,
>
> bpfcr->bpfcr_type = get_btf_id (ctf_lookup_tree_type (ctfc, type));
> bpfcr->bpfcr_insn_label = label;
> - bpfcr->bpfcr_kind = BPF_RELO_FIELD_BYTE_OFFSET;
> + bpfcr->bpfcr_kind = kind;
>
> /* Add the CO-RE reloc to the appropriate section. */
> bpf_core_section_ref sec;
> diff --git a/gcc/config/bpf/coreout.h b/gcc/config/bpf/coreout.h
> index 3c7bdfd8c2f..498853f6e00 100644
> --- a/gcc/config/bpf/coreout.h
> +++ b/gcc/config/bpf/coreout.h
> @@ -103,7 +103,7 @@ extern void btf_ext_init (void);
> extern void btf_ext_output (void);
>
> extern void bpf_core_reloc_add (const tree, const char *, vec<unsigned int> *,
> - rtx_code_label *);
> + rtx_code_label *, enum btf_core_reloc_kind);
> extern int bpf_core_get_sou_member_index (ctf_container_ref, const tree);
>
> #ifdef __cplusplus
> diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
> index 04af0584d82..d7bc252fb0b 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -15745,6 +15745,83 @@ Load 32-bits from the @code{struct sk_buff} packet data pointed by the register
> BPF Compile Once-Run Everywhere (CO-RE) support. Instruct GCC to generate CO-RE relocation records for any accesses to aggregate data structures (struct, union, array types) in @var{expr}. This builtin is otherwise transparent, the return value is whatever @var{expr} evaluates to. It is also overloaded: @var{expr} may be of any type (not necessarily a pointer), the return type is the same. Has no effect if @code{-mco-re} is not in effect (either specified or implied).
> @end deftypefn
>
> +@deftypefn {Built-in Function} unsigned int __builtin_preserve_field_info (@var{expr}, unsigned int @var{kind})
> +BPF Compile Once-Run Everywhere (CO-RE) support. This builtin is used to
> +extract information to aid in struct/union relocations. @var{expr} is
> +an access to a field of a struct or union. Depending on @var{kind}, different
> +information is returned to the program. A CO-RE relocation for the access in
> +@var{expr} with kind @var{kind} is recorded if @code{-mco-re} is in effect.
> +
> +The following values are supported for @var{kind}:
> +@table @var
> +@item FIELD_BYTE_OFFSET = 0
> +The returned value is the offset, in bytes, of the field from the
> +beginning of the containing structure. For bitfields, the byte offset
> +of the containing word.
> +
> +@item FIELD_BYTE_SIZE = 1
> +The returned value is the size, in bytes, of the field. For bitfields,
> +the size in bytes of the containing word.
> +
> +@item FIELD_EXISTENCE = 2
> +The returned value is 1 if the field exists, 0 otherwise. Always 1 at
> +compile time.
> +
> +@item FIELD_SIGNEDNESS = 3
> +The returned value is 1 if the field is signed, 0 otherwise.
> +
> +@item FIELD_LSHIFT_U64 = 4
> +@itemx FIELD_RSHIFT_U64 = 5
> +The returned value is the number of bits of left- or right-shifting
> +respectively needed in order to recover the original value of the field,
> +after it has been loaded by a read of FIELD_BYTE_SIZE bytes into an
> +unsigned 64-bit value. Primarily useful for reading bitfield values
> +from structures which may change between kernel versions.
> +
> +@end table
> +
> +Note that the return value is a constant which is known at
> +compile-time. If the field has a variable offset then
> +FIELD_BYTE_OFFSET, FIELD_LSHIFT_U64 and FIELD_RSHIFT_U64 are not
> +supported. Similarly, if the field has a variable size then
> +FIELD_BYTE_SIZE, FIELD_LSHIFT_U64 and FIELD_RSHIFT_U64 are not
> +supported.
> +
> +For example, __builtin_preserve_field_info can be used to reliably
> +extract bitfield values from a structure which may change between
> +kernel versions:
> +
> +@example
> +struct S
> +@{
> + short a;
> + int x:7;
> + int y:5;
> +@};
> +
> +int
> +read_y (struct S *arg)
> +@{
> + unsigned long long val;
> + unsigned int offset = __builtin_preserve_field_info (arg->y, FIELD_BYTE_OFFSET);
> + unsigned int size = __builtin_presrve_field_info (arg->y, FIELD_BYTE_SIZE);
> +
> + /* Read size bytes from arg + offset into val. */
> + bpf_probe_read (&val, size, arg + offset);
> +
> + val <<= __builtin_preserve_field_info (arg->y, FIELD_LSHIFT_U64);
> +
> + if (__builtin_preserve_field_info (arg->y, FIELD_SIGNEDNESS))
> + val = ((long long) val >> __builtin_preserve_field_info (arg->y, FIELD_RSHIFT_U64));
> + else
> + val >>= __builtin_preserve_field_info (arg->y, FIELD_RSHIFT_U64);
> +
> + return val;
> +@}
> +
> +@end example
> +@end deftypefn
> +
> @node FR-V Built-in Functions
> @subsection FR-V Built-in Functions
>
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-1.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-1.c
> new file mode 100644
> index 00000000000..2c67c384004
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-1.c
> @@ -0,0 +1,23 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +struct F {
> + int bar;
> + char c;
> + int baz;
> + int arr[];
> +};
> +
> +enum {
> + FIELD_BYTE_OFFSET = 0,
> + FIELD_BYTE_SIZE = 1,
> +};
> +
> +unsigned int test (struct F *f) {
> +
> + unsigned x = __builtin_preserve_field_info (f->arr, FIELD_BYTE_SIZE); /* { dg-error "unsupported variable size field access" } */
> +
> + unsigned y = __builtin_preserve_field_info (f->baz, 99); /* { dg-error "invalid second argument to built-in function" } */
> +
> + return x + y;
> +}
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-2.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-2.c
> new file mode 100644
> index 00000000000..31d7a03b757
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-errors-2.c
> @@ -0,0 +1,23 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +struct F {
> + int bar;
> + char c;
> + int baz;
> +};
> +
> +enum {
> + FIELD_BYTE_OFFSET = 0,
> + FIELD_BYTE_SIZE = 1,
> +};
> +
> +int test (struct F *f) {
> + int a;
> + unsigned x = __builtin_preserve_field_info (({ a = f->bar + f->baz; }), FIELD_BYTE_OFFSET); /* { dg-error "argument is not a field access" } */
> +
> + int b;
> + unsigned y = __builtin_preserve_field_info (&(f->c), FIELD_BYTE_SIZE); /* { dg-error "argument is not a field access" } */
> +
> + return a + b + x + y;
> +}
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-existence-1.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-existence-1.c
> new file mode 100644
> index 00000000000..c55f21a9c11
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-existence-1.c
> @@ -0,0 +1,34 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +enum {
> + FIELD_EXISTENCE = 2,
> +};
> +
> +typedef unsigned uint;
> +
> +struct S {
> + unsigned char c;
> + int d;
> + uint u;
> + short ar[3];
> +};
> +
> +unsigned int foo (struct S *s)
> +{
> + unsigned c = __builtin_preserve_field_info (s->c, FIELD_EXISTENCE);
> + unsigned d = __builtin_preserve_field_info (s->d, FIELD_EXISTENCE);
> + unsigned u = __builtin_preserve_field_info (s->u, FIELD_EXISTENCE);
> + unsigned ar = __builtin_preserve_field_info (s->ar[1], FIELD_EXISTENCE);
> +
> + return c + d + u + ar;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],1" 4 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:3:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "0x2\[\t \]+\[^\n\]*bpfcr_kind" 4 } } */
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-be.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-be.c
> new file mode 100644
> index 00000000000..dabf73dd259
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-be.c
> @@ -0,0 +1,37 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re -mbig-endian" } */
> +
> +struct S {
> + int x1: 6;
> + int x2: 3;
> + int x3: 7;
> + int x4: 16;
> +};
> +
> +enum {
> + FIELD_LSHIFT_U64 = 4,
> +};
> +
> +unsigned int foo (struct S *s)
> +{
> + /* little endian: x1=58, x2=55, x3=48, x4=32 */
> + /* big endian: x1=32, x2=38, x3=41, x4=48 */
> + unsigned x1 = __builtin_preserve_field_info (s->x1, FIELD_LSHIFT_U64);
> + unsigned x2 = __builtin_preserve_field_info (s->x2, FIELD_LSHIFT_U64);
> + unsigned x3 = __builtin_preserve_field_info (s->x3, FIELD_LSHIFT_U64);
> + unsigned x4 = __builtin_preserve_field_info (s->x4, FIELD_LSHIFT_U64);
> +
> + return x1 + x2 + x3 + x4;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],32" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],38" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],41" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],48" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "0x4\[\t \]+\[^\n\]*bpfcr_kind" 4 } } */
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-le.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-le.c
> new file mode 100644
> index 00000000000..99e3982d932
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-1-le.c
> @@ -0,0 +1,37 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re -mlittle-endian" } */
> +
> +struct S {
> + int x1: 6;
> + int x2: 3;
> + int x3: 7;
> + int x4: 16;
> +};
> +
> +enum {
> + FIELD_LSHIFT_U64 = 4,
> +};
> +
> +unsigned int foo (struct S *s)
> +{
> + /* little endian: x1=58, x2=55, x3=48, x4=32 */
> + /* big endian: x1=32, x2=38, x3=41, x4=48 */
> + unsigned x1 = __builtin_preserve_field_info (s->x1, FIELD_LSHIFT_U64);
> + unsigned x2 = __builtin_preserve_field_info (s->x2, FIELD_LSHIFT_U64);
> + unsigned x3 = __builtin_preserve_field_info (s->x3, FIELD_LSHIFT_U64);
> + unsigned x4 = __builtin_preserve_field_info (s->x4, FIELD_LSHIFT_U64);
> +
> + return x1 + x2 + x3 + x4;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],58" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],55" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],48" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],32" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "0x4\[\t \]+\[^\n\]*bpfcr_kind" 4 } } */
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-2.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-2.c
> new file mode 100644
> index 00000000000..25be969e22b
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-lshift-2.c
> @@ -0,0 +1,37 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +struct S {
> + char c;
> + short s;
> + int x;
> +};
> +
> +union U {
> + struct S s[2];
> + long long ll;
> +};
> +
> +enum {
> + FIELD_LSHIFT_U64 = 4,
> +};
> +
> +unsigned int foo (union U *u)
> +{
> + /* s0s = 48, s1c = 56, ll = 0; endianness independent. */
> + unsigned s0s = __builtin_preserve_field_info (u->s[0].s, FIELD_LSHIFT_U64);
> + unsigned s1c = __builtin_preserve_field_info (u->s[1].c, FIELD_LSHIFT_U64);
> + unsigned ll = __builtin_preserve_field_info (u->ll, FIELD_LSHIFT_U64);
> +
> + return s0s + s1c + ll;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],48" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],56" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],0" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:0:0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:0:1:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "0x4\[\t \]+\[^\n\]*bpfcr_kind" 3 } } */
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-offset-1.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-offset-1.c
> new file mode 100644
> index 00000000000..590eea007ae
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-offset-1.c
> @@ -0,0 +1,56 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +struct S {
> + unsigned int a1: 7;
> + unsigned int a2: 4;
> + unsigned int a3: 13;
> + unsigned int a4: 5;
> + int x;
> +};
> +
> +struct T {
> + unsigned int y;
> + struct S s[2];
> + char c;
> + char d;
> +};
> +
> +enum {
> + FIELD_BYTE_OFFSET = 0,
> +};
> +
> +
> +unsigned int foo (struct T *t)
> +{
> + unsigned s0a1 = __builtin_preserve_field_info (t->s[0].a1, FIELD_BYTE_OFFSET);
> + unsigned s0a4 = __builtin_preserve_field_info (t->s[0].a4, FIELD_BYTE_OFFSET);
> + unsigned s0x = __builtin_preserve_field_info (t->s[0].x, FIELD_BYTE_OFFSET);
> +
> + unsigned s1a1 = __builtin_preserve_field_info (t->s[1].a1, FIELD_BYTE_OFFSET);
> + unsigned s1a4 = __builtin_preserve_field_info (t->s[1].a4, FIELD_BYTE_OFFSET);
> + unsigned s1x = __builtin_preserve_field_info (t->s[1].x, FIELD_BYTE_OFFSET);
> +
> + unsigned c = __builtin_preserve_field_info (t->c, FIELD_BYTE_OFFSET);
> + unsigned d = __builtin_preserve_field_info (t->d, FIELD_BYTE_OFFSET);
> +
> + return s0a1 + s0a4 + s0x + s1a1 + s1a4 + s1x + c + d;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],4" 2 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],8" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],12" 2 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],16" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],20" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],21" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:1:0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:0:4.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:1:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:1:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:1:4.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "0\[\t \]+\[^\n\]*bpfcr_kind" 8 } } */
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-1.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-1.c
> new file mode 100644
> index 00000000000..d0c75d944cd
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-1.c
> @@ -0,0 +1,36 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +struct S {
> + int x1: 6;
> + int x2: 3;
> + int x3: 7;
> + int x4: 16;
> +};
> +
> +enum {
> + FIELD_RSHIFT_U64 = 5,
> +};
> +
> +unsigned int foo (struct S *s)
> +{
> + /* x1=58, x2=61, x3=57, x4=48; endianness independent. */
> + unsigned x1 = __builtin_preserve_field_info (s->x1, FIELD_RSHIFT_U64);
> + unsigned x2 = __builtin_preserve_field_info (s->x2, FIELD_RSHIFT_U64);
> + unsigned x3 = __builtin_preserve_field_info (s->x3, FIELD_RSHIFT_U64);
> + unsigned x4 = __builtin_preserve_field_info (s->x4, FIELD_RSHIFT_U64);
> +
> + return x1 + x2 + x3 + x4;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],58" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],61" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],57" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],48" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "0x5\[\t \]+\[^\n\]*bpfcr_kind" 4 } } */
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-2.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-2.c
> new file mode 100644
> index 00000000000..a71ddc17728
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-rshift-2.c
> @@ -0,0 +1,35 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +struct S {
> + int x;
> + char c;
> +};
> +
> +union U {
> + int i;
> + struct S s;
> +};
> +
> +enum {
> + FIELD_RSHIFT_U64 = 5,
> +};
> +
> +unsigned int foo (union U *u)
> +{
> + /* sx = 32, sc = 56, i = 32; endianness independent. */
> + unsigned sx = __builtin_preserve_field_info (u->s.x, FIELD_RSHIFT_U64);
> + unsigned sc = __builtin_preserve_field_info (u->s.c, FIELD_RSHIFT_U64);
> + unsigned i = __builtin_preserve_field_info (u->i, FIELD_RSHIFT_U64);
> +
> + return sx + sc + i;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],32" 2 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],56" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:1:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "0x5\[\t \]+\[^\n\]*bpfcr_kind" 3 } } */
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-1.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-1.c
> new file mode 100644
> index 00000000000..3b2081e197c
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-1.c
> @@ -0,0 +1,33 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +enum {
> + FIELD_SIGNEDNESS = 3,
> +};
> +
> +typedef unsigned uint;
> +
> +struct S {
> + unsigned char c;
> + int d;
> + uint u;
> + short ar[3];
> +};
> +
> +unsigned int foo (struct S *s)
> +{
> + unsigned d = __builtin_preserve_field_info (s->d, FIELD_SIGNEDNESS);
> + unsigned u = __builtin_preserve_field_info (s->u, FIELD_SIGNEDNESS);
> + unsigned ar = __builtin_preserve_field_info (s->ar[1], FIELD_SIGNEDNESS);
> +
> + return d + u + ar;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],1" 2 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],0" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:3:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "0x3\[\t \]+\[^\n\]*bpfcr_kind" 3 } } */
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-2.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-2.c
> new file mode 100644
> index 00000000000..bf184299984
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-sign-2.c
> @@ -0,0 +1,45 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +enum {
> + FIELD_SIGNEDNESS = 3,
> +};
> +
> +enum Esig {
> + SA = -1,
> + SB,
> + SC,
> +};
> +
> +enum Eun {
> + UA = 0,
> + UB,
> +};
> +
> +struct S {
> + enum Esig sig : 3;
> + enum Eun un : 3;
> +};
> +
> +union U {
> + int i;
> + struct S s;
> +};
> +
> +unsigned int foo (union U *u)
> +{
> + unsigned i = __builtin_preserve_field_info (u->i, FIELD_SIGNEDNESS);
> + unsigned sig = __builtin_preserve_field_info (u->s.sig, FIELD_SIGNEDNESS);
> + unsigned un = __builtin_preserve_field_info (u->s.un, FIELD_SIGNEDNESS);
> +
> + return i + sig + un;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],1" 2 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],0" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "3\[\t \]+\[^\n\]*bpfcr_kind" 3 } } */
> diff --git a/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-size-1.c b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-size-1.c
> new file mode 100644
> index 00000000000..8747bdeb9c3
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-builtin-fieldinfo-size-1.c
> @@ -0,0 +1,43 @@
> +/* { dg-do compile } */
> +/* { dg-options "-O0 -dA -gbtf -mco-re" } */
> +
> +struct S {
> + unsigned int a1: 7;
> + unsigned int a2: 4;
> + unsigned int a3: 13;
> + unsigned int a4: 5;
> + char carr[5][3];
> +};
> +
> +enum {
> + FIELD_BYTE_SIZE = 1,
> +};
> +
> +union U {
> + long long l[3];
> + struct S s;
> +};
> +
> +unsigned int foo (union U *u)
> +{
> + unsigned ls = __builtin_preserve_field_info (u->l, FIELD_BYTE_SIZE);
> + unsigned s = __builtin_preserve_field_info (u->s, FIELD_BYTE_SIZE);
> + unsigned a2 = __builtin_preserve_field_info (u->s.a2, FIELD_BYTE_SIZE);
> + unsigned a3 = __builtin_preserve_field_info (u->s.a3, FIELD_BYTE_SIZE);
> + unsigned ca = __builtin_preserve_field_info (u->s.carr, FIELD_BYTE_SIZE);
> +
> + return ls + s + a2 + a3 + ca;
> +}
> +
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],24" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],20" 1 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],4" 2 } } */
> +/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],15" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:1:4.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "0x1\[\t \]+\[^\n\]*bpfcr_kind" 5 } } */
On 10/26/22 12:33, Jose E. Marchesi wrote:
>
> Hi David.
>
> Thanks for the updates.
> OK for master.
>
Pushed, thanks.
@@ -184,13 +184,13 @@ enum bpf_builtins
/* Compile Once - Run Everywhere (CO-RE) support. */
BPF_BUILTIN_PRESERVE_ACCESS_INDEX,
+ BPF_BUILTIN_PRESERVE_FIELD_INFO,
BPF_BUILTIN_MAX,
};
static GTY (()) tree bpf_builtins[(int) BPF_BUILTIN_MAX];
-
void bpf_register_coreattr_pass (void);
/* Initialize the per-function machine status. */
@@ -966,6 +966,9 @@ bpf_init_builtins (void)
def_builtin ("__builtin_preserve_access_index",
BPF_BUILTIN_PRESERVE_ACCESS_INDEX,
build_function_type_list (ptr_type_node, ptr_type_node, 0));
+ def_builtin ("__builtin_preserve_field_info",
+ BPF_BUILTIN_PRESERVE_FIELD_INFO,
+ build_function_type_list (unsigned_type_node, ptr_type_node, unsigned_type_node, 0));
}
#undef TARGET_INIT_BUILTINS
@@ -975,6 +978,199 @@ static tree bpf_core_compute (tree, vec<unsigned int> *);
static int bpf_core_get_index (const tree);
static bool is_attr_preserve_access (tree);
+/* BPF Compile Once - Run Everywhere (CO-RE) support. Construct a CO-RE
+ relocation record for EXPR of kind KIND to be emitted in the .BTF.ext
+ section. Does nothing if we are not targetting BPF CO-RE, or if the
+ constructed relocation would be a no-op. */
+
+static void
+maybe_make_core_relo (tree expr, enum btf_core_reloc_kind kind)
+{
+ /* If we are not targetting BPF CO-RE, do not make a relocation. We
+ might not be generating any debug info at all. */
+ if (!TARGET_BPF_CORE)
+ return;
+
+ auto_vec<unsigned int, 16> accessors;
+ tree container = bpf_core_compute (expr, &accessors);
+
+ /* Any valid use of the builtin must have at least one access. Otherwise,
+ there is nothing to record and nothing to do. This is primarily a
+ guard against optimizations leading to unexpected expressions in the
+ argument of the builtin. For example, if the builtin is used to read
+ a field of a structure which can be statically determined to hold a
+ constant value, the argument to the builtin will be optimized to that
+ constant. This is OK, and means the builtin call is superfluous.
+ e.g.
+ struct S foo;
+ foo.a = 5;
+ int x = __preserve_access_index (foo.a);
+ ... do stuff with x
+ 'foo.a' in the builtin argument will be optimized to '5' with -01+.
+ This sequence does not warrant recording a CO-RE relocation. */
+
+ if (accessors.length () < 1)
+ return;
+ accessors.reverse ();
+
+ rtx_code_label *label = gen_label_rtx ();
+ LABEL_PRESERVE_P (label) = 1;
+ emit_label (label);
+
+ /* Determine what output section this relocation will apply to.
+ If this function is associated with a section, use that. Otherwise,
+ fall back on '.text'. */
+ const char * section_name;
+ if (current_function_decl && DECL_SECTION_NAME (current_function_decl))
+ section_name = DECL_SECTION_NAME (current_function_decl);
+ else
+ section_name = ".text";
+
+ /* Add the CO-RE relocation information to the BTF container. */
+ bpf_core_reloc_add (TREE_TYPE (container), section_name, &accessors, label,
+ kind);
+}
+
+/* Expand a call to __builtin_preserve_field_info by evaluating the requested
+ information about SRC according to KIND, and return a tree holding
+ the result. */
+
+static tree
+bpf_core_field_info (tree src, enum btf_core_reloc_kind kind)
+{
+ unsigned int result;
+ poly_int64 bitsize, bitpos;
+ tree var_off = NULL_TREE;
+ machine_mode mode;
+ int unsignedp, reversep, volatilep;
+ location_t loc = EXPR_LOCATION (src);
+
+ get_inner_reference (src, &bitsize, &bitpos, &var_off, &mode, &unsignedp,
+ &reversep, &volatilep);
+
+ /* Note: Use DECL_BIT_FIELD_TYPE rather than DECL_BIT_FIELD here, because it
+ remembers whether the field in question was originally declared as a
+ bitfield, regardless of how it has been optimized. */
+ bool bitfieldp = (TREE_CODE (src) == COMPONENT_REF
+ && DECL_BIT_FIELD_TYPE (TREE_OPERAND (src, 1)));
+
+ unsigned int align = TYPE_ALIGN (TREE_TYPE (src));
+ if (TREE_CODE (src) == COMPONENT_REF)
+ {
+ tree field = TREE_OPERAND (src, 1);
+ if (DECL_BIT_FIELD_TYPE (field))
+ align = TYPE_ALIGN (DECL_BIT_FIELD_TYPE (field));
+ else
+ align = TYPE_ALIGN (TREE_TYPE (field));
+ }
+
+ unsigned int start_bitpos = bitpos & ~(align - 1);
+ unsigned int end_bitpos = start_bitpos + align;
+
+ switch (kind)
+ {
+ case BPF_RELO_FIELD_BYTE_OFFSET:
+ {
+ if (var_off != NULL_TREE)
+ {
+ error_at (loc, "unsupported variable field offset");
+ return error_mark_node;
+ }
+
+ if (bitfieldp)
+ result = start_bitpos / 8;
+ else
+ result = bitpos / 8;
+ }
+ break;
+
+ case BPF_RELO_FIELD_BYTE_SIZE:
+ {
+ if (mode == BLKmode && bitsize == -1)
+ {
+ error_at (loc, "unsupported variable size field access");
+ return error_mark_node;
+ }
+
+ if (bitfieldp)
+ {
+ /* To match LLVM behavior, byte size of bitfields is recorded as
+ the full size of the base type. A 3-bit bitfield of type int is
+ therefore recorded as having a byte size of 4 bytes. */
+ result = end_bitpos - start_bitpos;
+ if (result & (result - 1))
+ {
+ error_at (loc, "unsupported field expression");
+ return error_mark_node;
+ }
+ result = result / 8;
+ }
+ else
+ result = bitsize / 8;
+ }
+ break;
+
+ case BPF_RELO_FIELD_EXISTS:
+ /* The field always exists at compile time. */
+ result = 1;
+ break;
+
+ case BPF_RELO_FIELD_SIGNED:
+ result = !unsignedp;
+ break;
+
+ case BPF_RELO_FIELD_LSHIFT_U64:
+ case BPF_RELO_FIELD_RSHIFT_U64:
+ {
+ if (mode == BLKmode && bitsize == -1)
+ {
+ error_at (loc, "unsupported variable size field access");
+ return error_mark_node;
+ }
+ if (var_off != NULL_TREE)
+ {
+ error_at (loc, "unsupported variable field offset");
+ return error_mark_node;
+ }
+
+ if (!bitfieldp)
+ {
+ if (bitsize > 64)
+ {
+ error_at (loc, "field size too large");
+ return error_mark_node;
+ }
+ result = 64 - bitsize;
+ break;
+ }
+
+ if (end_bitpos - start_bitpos > 64)
+ {
+ error_at (loc, "field size too large");
+ return error_mark_node;
+ }
+
+ if (kind == BPF_RELO_FIELD_LSHIFT_U64)
+ {
+ if (TARGET_BIG_ENDIAN)
+ result = bitpos + 64 - start_bitpos - align;
+ else
+ result = start_bitpos + 64 - bitpos - bitsize;
+ }
+ else /* RSHIFT_U64 */
+ result = 64 - bitsize;
+ }
+ break;
+
+ default:
+ error ("invalid second argument to built-in function");
+ return error_mark_node;
+ break;
+ }
+
+ return build_int_cst (unsigned_type_node, result);
+}
+
/* Expand a call to a BPF-specific built-in function that was set up
with bpf_init_builtins. */
@@ -1025,17 +1221,15 @@ bpf_expand_builtin (tree exp, rtx target ATTRIBUTE_UNUSED,
/* The result of the load is in R0. */
return gen_rtx_REG (ops[0].mode, BPF_R0);
}
+
else if (code == -1)
{
- /* A resolved overloaded builtin, e.g. __bpf_preserve_access_index_si */
+ /* A resolved overloaded __builtin_preserve_access_index. */
tree arg = CALL_EXPR_ARG (exp, 0);
if (arg == NULL_TREE)
return NULL_RTX;
- auto_vec<unsigned int, 16> accessors;
- tree container;
-
if (TREE_CODE (arg) == SSA_NAME)
{
gimple *def_stmt = SSA_NAME_DEF_STMT (arg);
@@ -1049,51 +1243,42 @@ bpf_expand_builtin (tree exp, rtx target ATTRIBUTE_UNUSED,
/* Avoid double-recording information if the argument is an access to
a struct/union marked __attribute__((preserve_access_index)). This
Will be handled by the attribute handling pass. */
- if (is_attr_preserve_access (arg))
- return expand_normal (arg);
-
- container = bpf_core_compute (arg, &accessors);
-
- /* Any valid use of the builtin must have at least one access. Otherwise,
- there is nothing to record and nothing to do. This is primarily a
- guard against optimizations leading to unexpected expressions in the
- argument of the builtin. For example, if the builtin is used to read
- a field of a structure which can be statically determined to hold a
- constant value, the argument to the builtin will be optimized to that
- constant. This is OK, and means the builtin call is superfluous.
- e.g.
- struct S foo;
- foo.a = 5;
- int x = __preserve_access_index (foo.a);
- ... do stuff with x
- 'foo.a' in the builtin argument will be optimized to '5' with -01+.
- This sequence does not warrant recording a CO-RE relocation. */
-
- if (accessors.length () < 1)
- return expand_normal (arg);
-
- accessors.reverse ();
-
- container = TREE_TYPE (container);
-
- rtx_code_label *label = gen_label_rtx ();
- LABEL_PRESERVE_P (label) = 1;
- emit_label (label);
-
- /* Determine what output section this relocation will apply to.
- If this function is associated with a section, use that. Otherwise,
- fall back on '.text'. */
- const char * section_name;
- if (current_function_decl && DECL_SECTION_NAME (current_function_decl))
- section_name = DECL_SECTION_NAME (current_function_decl);
+ if (!is_attr_preserve_access (arg))
+ maybe_make_core_relo (arg, BPF_RELO_FIELD_BYTE_OFFSET);
+
+ return expand_normal (arg);
+ }
+
+ else if (code == -2)
+ {
+ /* A resolved overloaded __builtin_preserve_field_info. */
+ tree src = CALL_EXPR_ARG (exp, 0);
+ tree kind_tree = CALL_EXPR_ARG (exp, 1);
+ unsigned HOST_WIDE_INT kind_val;
+ if (tree_fits_uhwi_p (kind_tree))
+ kind_val = tree_to_uhwi (kind_tree);
else
- section_name = ".text";
+ error ("invalid argument to built-in function");
- /* Add the CO-RE relocation information to the BTF container. */
- bpf_core_reloc_add (container, section_name, &accessors, label);
+ enum btf_core_reloc_kind kind = (enum btf_core_reloc_kind) kind_val;
- return expand_normal (arg);
+ if (TREE_CODE (src) == SSA_NAME)
+ {
+ gimple *def_stmt = SSA_NAME_DEF_STMT (src);
+ if (is_gimple_assign (def_stmt))
+ src = gimple_assign_rhs1 (def_stmt);
+ }
+ if (TREE_CODE (src) == ADDR_EXPR)
+ src = TREE_OPERAND (src, 0);
+
+ tree result = bpf_core_field_info (src, kind);
+
+ if (result != error_mark_node)
+ maybe_make_core_relo (src, kind);
+
+ return expand_normal (result);
}
+
gcc_unreachable ();
}
@@ -1259,41 +1444,64 @@ bpf_core_get_index (const tree node)
__builtin_preserve_access_index. */
static tree
-bpf_core_newdecl (tree type)
+bpf_core_newdecl (tree type, bool is_pai)
{
- tree rettype = build_function_type_list (type, type, NULL);
+ tree rettype;
char name[80];
- int len = snprintf (name, sizeof (name), "%s", "__builtin_pai_");
+ static unsigned long pai_count = 0;
+ static unsigned long pfi_count = 0;
- static unsigned long cnt = 0;
- len = snprintf (name + len, sizeof (name) - len, "%lu", cnt++);
+ if (is_pai)
+ {
+ rettype = build_function_type_list (type, type, NULL);
+ int len = snprintf (name, sizeof (name), "%s", "__builtin_pai_");
+ len = snprintf (name + len, sizeof (name) - len, "%lu", pai_count++);
+ }
+ else
+ {
+ rettype = build_function_type_list (unsigned_type_node, type,
+ unsigned_type_node, NULL);
+ int len = snprintf (name, sizeof (name), "%s", "__builtin_pfi_");
+ len = snprintf (name + len, sizeof (name) - len, "%lu", pfi_count++);
+ }
- return add_builtin_function_ext_scope (name, rettype, -1, BUILT_IN_MD, NULL,
- NULL_TREE);
+ return add_builtin_function_ext_scope (name, rettype, is_pai ? -1 : -2,
+ BUILT_IN_MD, NULL, NULL_TREE);
}
/* Return whether EXPR could access some aggregate data structure that
BPF CO-RE support needs to know about. */
-static int
+static bool
bpf_core_is_maybe_aggregate_access (tree expr)
{
- enum tree_code code = TREE_CODE (expr);
- if (code == COMPONENT_REF || code == ARRAY_REF)
- return 1;
-
- if (code == ADDR_EXPR)
+ switch (TREE_CODE (expr))
+ {
+ case COMPONENT_REF:
+ case BIT_FIELD_REF:
+ case ARRAY_REF:
+ case ARRAY_RANGE_REF:
+ return true;
+ case ADDR_EXPR:
+ case NOP_EXPR:
return bpf_core_is_maybe_aggregate_access (TREE_OPERAND (expr, 0));
-
- return 0;
+ default:
+ return false;
+ }
}
+struct core_walk_data {
+ location_t loc;
+ tree arg;
+};
+
/* Callback function used with walk_tree from bpf_resolve_overloaded_builtin. */
static tree
bpf_core_walk (tree *tp, int *walk_subtrees, void *data)
{
- location_t loc = *((location_t *) data);
+ struct core_walk_data *dat = (struct core_walk_data *) data;
+ bool is_pai = dat->arg == NULL_TREE;
/* If this is a type, don't do anything. */
if (TYPE_P (*tp))
@@ -1302,10 +1510,18 @@ bpf_core_walk (tree *tp, int *walk_subtrees, void *data)
return NULL_TREE;
}
+ /* Build a new function call to a resolved builtin for the desired operation.
+ If this is a preserve_field_info call, pass along the argument to the
+ resolved builtin call. */
if (bpf_core_is_maybe_aggregate_access (*tp))
{
- tree newdecl = bpf_core_newdecl (TREE_TYPE (*tp));
- tree newcall = build_call_expr_loc (loc, newdecl, 1, *tp);
+ tree newdecl = bpf_core_newdecl (TREE_TYPE (*tp), is_pai);
+ tree newcall;
+ if (is_pai)
+ newcall = build_call_expr_loc (dat->loc, newdecl, 1, *tp);
+ else
+ newcall = build_call_expr_loc (dat->loc, newdecl, 2, *tp, dat->arg);
+
*tp = newcall;
*walk_subtrees = 0;
}
@@ -1330,6 +1546,30 @@ bpf_small_register_classes_for_mode_p (machine_mode mode)
#define TARGET_SMALL_REGISTER_CLASSES_FOR_MODE_P \
bpf_small_register_classes_for_mode_p
+/* Return whether EXPR is a valid first argument for a call to
+ __builtin_preserve_field_info. */
+
+static bool
+bpf_is_valid_preserve_field_info_arg (tree expr)
+{
+ switch (TREE_CODE (expr))
+ {
+ case COMPONENT_REF:
+ case BIT_FIELD_REF:
+ case ARRAY_REF:
+ case ARRAY_RANGE_REF:
+ return true;
+ case NOP_EXPR:
+ return bpf_is_valid_preserve_field_info_arg (TREE_OPERAND (expr, 0));
+ case ADDR_EXPR:
+ /* Do not accept ADDR_EXPRs like &foo.bar, but do accept accesses like
+ foo.baz where baz is an array. */
+ return (TREE_CODE (TREE_TYPE (TREE_OPERAND (expr, 0))) == ARRAY_TYPE);
+ default:
+ return false;
+ }
+}
+
/* Implement TARGET_RESOLVE_OVERLOADED_BUILTIN (see gccint manual section
Target Macros::Misc.).
We use this for the __builtin_preserve_access_index builtin for CO-RE
@@ -1344,7 +1584,12 @@ bpf_small_register_classes_for_mode_p (machine_mode mode)
static tree
bpf_resolve_overloaded_builtin (location_t loc, tree fndecl, void *arglist)
{
- if (DECL_MD_FUNCTION_CODE (fndecl) != BPF_BUILTIN_PRESERVE_ACCESS_INDEX)
+ bool is_pai = DECL_MD_FUNCTION_CODE (fndecl)
+ == BPF_BUILTIN_PRESERVE_ACCESS_INDEX;
+ bool is_pfi = DECL_MD_FUNCTION_CODE (fndecl)
+ == BPF_BUILTIN_PRESERVE_FIELD_INFO;
+
+ if (!is_pai && !is_pfi)
return NULL_TREE;
/* We only expect one argument, but it may be an arbitrarily-complicated
@@ -1352,18 +1597,26 @@ bpf_resolve_overloaded_builtin (location_t loc, tree fndecl, void *arglist)
vec<tree, va_gc> *params = static_cast<vec<tree, va_gc> *> (arglist);
unsigned n_params = params ? params->length() : 0;
- if (n_params != 1)
+ if ((is_pai && n_params != 1) || (is_pfi && n_params != 2))
{
- error_at (loc, "expected exactly 1 argument");
- return NULL_TREE;
+ error_at (loc, "wrong number of arguments");
+ return error_mark_node;
}
tree param = (*params)[0];
- /* If not generating BPF_CORE information, the builtin does nothing. */
- if (!TARGET_BPF_CORE)
+ /* If not generating BPF_CORE information, preserve_access_index does nothing,
+ and simply "resolves to" the argument. */
+ if (!TARGET_BPF_CORE && is_pai)
return param;
+ if (is_pfi && !bpf_is_valid_preserve_field_info_arg (param))
+ {
+ error_at (EXPR_LOC_OR_LOC (param, loc),
+ "argument is not a field access");
+ return error_mark_node;
+ }
+
/* Do remove_c_maybe_const_expr for the arg.
TODO: WHY do we have to do this here? Why doesn't c-typeck take care
of it before or after this hook? */
@@ -1387,7 +1640,11 @@ bpf_resolve_overloaded_builtin (location_t loc, tree fndecl, void *arglist)
This ensures that all the relevant information remains within the
expression trees the builtin finally gets. */
- walk_tree (¶m, bpf_core_walk, (void *) &loc, NULL);
+ struct core_walk_data data;
+ data.loc = loc;
+ data.arg = is_pai ? NULL_TREE : (*params)[1];
+
+ walk_tree (¶m, bpf_core_walk, (void *) &data, NULL);
return param;
}
@@ -1524,7 +1781,8 @@ handle_attr_preserve (function *fn)
emit_label (label);
/* Add the CO-RE relocation information to the BTF container. */
- bpf_core_reloc_add (container, section_name, &accessors, label);
+ bpf_core_reloc_add (container, section_name, &accessors, label,
+ BPF_RELO_FIELD_BYTE_OFFSET);
}
}
}
@@ -152,7 +152,8 @@ static GTY (()) vec<bpf_core_section_ref, va_gc> *bpf_core_sections;
void
bpf_core_reloc_add (const tree type, const char * section_name,
- vec<unsigned int> *accessors, rtx_code_label *label)
+ vec<unsigned int> *accessors, rtx_code_label *label,
+ enum btf_core_reloc_kind kind)
{
char buf[40];
unsigned int i, n = 0;
@@ -173,7 +174,7 @@ bpf_core_reloc_add (const tree type, const char * section_name,
bpfcr->bpfcr_type = get_btf_id (ctf_lookup_tree_type (ctfc, type));
bpfcr->bpfcr_insn_label = label;
- bpfcr->bpfcr_kind = BPF_RELO_FIELD_BYTE_OFFSET;
+ bpfcr->bpfcr_kind = kind;
/* Add the CO-RE reloc to the appropriate section. */
bpf_core_section_ref sec;
@@ -103,7 +103,7 @@ extern void btf_ext_init (void);
extern void btf_ext_output (void);
extern void bpf_core_reloc_add (const tree, const char *, vec<unsigned int> *,
- rtx_code_label *);
+ rtx_code_label *, enum btf_core_reloc_kind);
extern int bpf_core_get_sou_member_index (ctf_container_ref, const tree);
#ifdef __cplusplus
@@ -15745,6 +15745,83 @@ Load 32-bits from the @code{struct sk_buff} packet data pointed by the register
BPF Compile Once-Run Everywhere (CO-RE) support. Instruct GCC to generate CO-RE relocation records for any accesses to aggregate data structures (struct, union, array types) in @var{expr}. This builtin is otherwise transparent, the return value is whatever @var{expr} evaluates to. It is also overloaded: @var{expr} may be of any type (not necessarily a pointer), the return type is the same. Has no effect if @code{-mco-re} is not in effect (either specified or implied).
@end deftypefn
+@deftypefn {Built-in Function} unsigned int __builtin_preserve_field_info (@var{expr}, unsigned int @var{kind})
+BPF Compile Once-Run Everywhere (CO-RE) support. This builtin is used to
+extract information to aid in struct/union relocations. @var{expr} is
+an access to a field of a struct or union. Depending on @var{kind}, different
+information is returned to the program. A CO-RE relocation for the access in
+@var{expr} with kind @var{kind} is recorded if @code{-mco-re} is in effect.
+
+The following values are supported for @var{kind}:
+@table @var
+@item FIELD_BYTE_OFFSET = 0
+The returned value is the offset, in bytes, of the field from the
+beginning of the containing structure. For bitfields, the byte offset
+of the containing word.
+
+@item FIELD_BYTE_SIZE = 1
+The returned value is the size, in bytes, of the field. For bitfields,
+the size in bytes of the containing word.
+
+@item FIELD_EXISTENCE = 2
+The returned value is 1 if the field exists, 0 otherwise. Always 1 at
+compile time.
+
+@item FIELD_SIGNEDNESS = 3
+The returned value is 1 if the field is signed, 0 otherwise.
+
+@item FIELD_LSHIFT_U64 = 4
+@itemx FIELD_RSHIFT_U64 = 5
+The returned value is the number of bits of left- or right-shifting
+respectively needed in order to recover the original value of the field,
+after it has been loaded by a read of FIELD_BYTE_SIZE bytes into an
+unsigned 64-bit value. Primarily useful for reading bitfield values
+from structures which may change between kernel versions.
+
+@end table
+
+Note that the return value is a constant which is known at
+compile-time. If the field has a variable offset then
+FIELD_BYTE_OFFSET, FIELD_LSHIFT_U64 and FIELD_RSHIFT_U64 are not
+supported. Similarly, if the field has a variable size then
+FIELD_BYTE_SIZE, FIELD_LSHIFT_U64 and FIELD_RSHIFT_U64 are not
+supported.
+
+For example, __builtin_preserve_field_info can be used to reliably
+extract bitfield values from a structure which may change between
+kernel versions:
+
+@example
+struct S
+@{
+ short a;
+ int x:7;
+ int y:5;
+@};
+
+int
+read_y (struct S *arg)
+@{
+ unsigned long long val;
+ unsigned int offset = __builtin_preserve_field_info (arg->y, FIELD_BYTE_OFFSET);
+ unsigned int size = __builtin_presrve_field_info (arg->y, FIELD_BYTE_SIZE);
+
+ /* Read size bytes from arg + offset into val. */
+ bpf_probe_read (&val, size, arg + offset);
+
+ val <<= __builtin_preserve_field_info (arg->y, FIELD_LSHIFT_U64);
+
+ if (__builtin_preserve_field_info (arg->y, FIELD_SIGNEDNESS))
+ val = ((long long) val >> __builtin_preserve_field_info (arg->y, FIELD_RSHIFT_U64));
+ else
+ val >>= __builtin_preserve_field_info (arg->y, FIELD_RSHIFT_U64);
+
+ return val;
+@}
+
+@end example
+@end deftypefn
+
@node FR-V Built-in Functions
@subsection FR-V Built-in Functions
new file mode 100644
@@ -0,0 +1,23 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+struct F {
+ int bar;
+ char c;
+ int baz;
+ int arr[];
+};
+
+enum {
+ FIELD_BYTE_OFFSET = 0,
+ FIELD_BYTE_SIZE = 1,
+};
+
+unsigned int test (struct F *f) {
+
+ unsigned x = __builtin_preserve_field_info (f->arr, FIELD_BYTE_SIZE); /* { dg-error "unsupported variable size field access" } */
+
+ unsigned y = __builtin_preserve_field_info (f->baz, 99); /* { dg-error "invalid second argument to built-in function" } */
+
+ return x + y;
+}
new file mode 100644
@@ -0,0 +1,23 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+struct F {
+ int bar;
+ char c;
+ int baz;
+};
+
+enum {
+ FIELD_BYTE_OFFSET = 0,
+ FIELD_BYTE_SIZE = 1,
+};
+
+int test (struct F *f) {
+ int a;
+ unsigned x = __builtin_preserve_field_info (({ a = f->bar + f->baz; }), FIELD_BYTE_OFFSET); /* { dg-error "argument is not a field access" } */
+
+ int b;
+ unsigned y = __builtin_preserve_field_info (&(f->c), FIELD_BYTE_SIZE); /* { dg-error "argument is not a field access" } */
+
+ return a + b + x + y;
+}
new file mode 100644
@@ -0,0 +1,34 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+enum {
+ FIELD_EXISTENCE = 2,
+};
+
+typedef unsigned uint;
+
+struct S {
+ unsigned char c;
+ int d;
+ uint u;
+ short ar[3];
+};
+
+unsigned int foo (struct S *s)
+{
+ unsigned c = __builtin_preserve_field_info (s->c, FIELD_EXISTENCE);
+ unsigned d = __builtin_preserve_field_info (s->d, FIELD_EXISTENCE);
+ unsigned u = __builtin_preserve_field_info (s->u, FIELD_EXISTENCE);
+ unsigned ar = __builtin_preserve_field_info (s->ar[1], FIELD_EXISTENCE);
+
+ return c + d + u + ar;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],1" 4 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:3:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "0x2\[\t \]+\[^\n\]*bpfcr_kind" 4 } } */
new file mode 100644
@@ -0,0 +1,37 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re -mbig-endian" } */
+
+struct S {
+ int x1: 6;
+ int x2: 3;
+ int x3: 7;
+ int x4: 16;
+};
+
+enum {
+ FIELD_LSHIFT_U64 = 4,
+};
+
+unsigned int foo (struct S *s)
+{
+ /* little endian: x1=58, x2=55, x3=48, x4=32 */
+ /* big endian: x1=32, x2=38, x3=41, x4=48 */
+ unsigned x1 = __builtin_preserve_field_info (s->x1, FIELD_LSHIFT_U64);
+ unsigned x2 = __builtin_preserve_field_info (s->x2, FIELD_LSHIFT_U64);
+ unsigned x3 = __builtin_preserve_field_info (s->x3, FIELD_LSHIFT_U64);
+ unsigned x4 = __builtin_preserve_field_info (s->x4, FIELD_LSHIFT_U64);
+
+ return x1 + x2 + x3 + x4;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],32" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],38" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],41" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],48" 1 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "0x4\[\t \]+\[^\n\]*bpfcr_kind" 4 } } */
new file mode 100644
@@ -0,0 +1,37 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re -mlittle-endian" } */
+
+struct S {
+ int x1: 6;
+ int x2: 3;
+ int x3: 7;
+ int x4: 16;
+};
+
+enum {
+ FIELD_LSHIFT_U64 = 4,
+};
+
+unsigned int foo (struct S *s)
+{
+ /* little endian: x1=58, x2=55, x3=48, x4=32 */
+ /* big endian: x1=32, x2=38, x3=41, x4=48 */
+ unsigned x1 = __builtin_preserve_field_info (s->x1, FIELD_LSHIFT_U64);
+ unsigned x2 = __builtin_preserve_field_info (s->x2, FIELD_LSHIFT_U64);
+ unsigned x3 = __builtin_preserve_field_info (s->x3, FIELD_LSHIFT_U64);
+ unsigned x4 = __builtin_preserve_field_info (s->x4, FIELD_LSHIFT_U64);
+
+ return x1 + x2 + x3 + x4;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],58" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],55" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],48" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],32" 1 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "0x4\[\t \]+\[^\n\]*bpfcr_kind" 4 } } */
new file mode 100644
@@ -0,0 +1,37 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+struct S {
+ char c;
+ short s;
+ int x;
+};
+
+union U {
+ struct S s[2];
+ long long ll;
+};
+
+enum {
+ FIELD_LSHIFT_U64 = 4,
+};
+
+unsigned int foo (union U *u)
+{
+ /* s0s = 48, s1c = 56, ll = 0; endianness independent. */
+ unsigned s0s = __builtin_preserve_field_info (u->s[0].s, FIELD_LSHIFT_U64);
+ unsigned s1c = __builtin_preserve_field_info (u->s[1].c, FIELD_LSHIFT_U64);
+ unsigned ll = __builtin_preserve_field_info (u->ll, FIELD_LSHIFT_U64);
+
+ return s0s + s1c + ll;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],48" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],56" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],0" 1 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:0:0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:0:1:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "0x4\[\t \]+\[^\n\]*bpfcr_kind" 3 } } */
new file mode 100644
@@ -0,0 +1,56 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+struct S {
+ unsigned int a1: 7;
+ unsigned int a2: 4;
+ unsigned int a3: 13;
+ unsigned int a4: 5;
+ int x;
+};
+
+struct T {
+ unsigned int y;
+ struct S s[2];
+ char c;
+ char d;
+};
+
+enum {
+ FIELD_BYTE_OFFSET = 0,
+};
+
+
+unsigned int foo (struct T *t)
+{
+ unsigned s0a1 = __builtin_preserve_field_info (t->s[0].a1, FIELD_BYTE_OFFSET);
+ unsigned s0a4 = __builtin_preserve_field_info (t->s[0].a4, FIELD_BYTE_OFFSET);
+ unsigned s0x = __builtin_preserve_field_info (t->s[0].x, FIELD_BYTE_OFFSET);
+
+ unsigned s1a1 = __builtin_preserve_field_info (t->s[1].a1, FIELD_BYTE_OFFSET);
+ unsigned s1a4 = __builtin_preserve_field_info (t->s[1].a4, FIELD_BYTE_OFFSET);
+ unsigned s1x = __builtin_preserve_field_info (t->s[1].x, FIELD_BYTE_OFFSET);
+
+ unsigned c = __builtin_preserve_field_info (t->c, FIELD_BYTE_OFFSET);
+ unsigned d = __builtin_preserve_field_info (t->d, FIELD_BYTE_OFFSET);
+
+ return s0a1 + s0a4 + s0x + s1a1 + s1a4 + s1x + c + d;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],4" 2 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],8" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],12" 2 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],16" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],20" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],21" 1 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:1:0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:0:4.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:1:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:1:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:1:4.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "0\[\t \]+\[^\n\]*bpfcr_kind" 8 } } */
new file mode 100644
@@ -0,0 +1,36 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+struct S {
+ int x1: 6;
+ int x2: 3;
+ int x3: 7;
+ int x4: 16;
+};
+
+enum {
+ FIELD_RSHIFT_U64 = 5,
+};
+
+unsigned int foo (struct S *s)
+{
+ /* x1=58, x2=61, x3=57, x4=48; endianness independent. */
+ unsigned x1 = __builtin_preserve_field_info (s->x1, FIELD_RSHIFT_U64);
+ unsigned x2 = __builtin_preserve_field_info (s->x2, FIELD_RSHIFT_U64);
+ unsigned x3 = __builtin_preserve_field_info (s->x3, FIELD_RSHIFT_U64);
+ unsigned x4 = __builtin_preserve_field_info (s->x4, FIELD_RSHIFT_U64);
+
+ return x1 + x2 + x3 + x4;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],58" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],61" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],57" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],48" 1 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:3.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "0x5\[\t \]+\[^\n\]*bpfcr_kind" 4 } } */
new file mode 100644
@@ -0,0 +1,35 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+struct S {
+ int x;
+ char c;
+};
+
+union U {
+ int i;
+ struct S s;
+};
+
+enum {
+ FIELD_RSHIFT_U64 = 5,
+};
+
+unsigned int foo (union U *u)
+{
+ /* sx = 32, sc = 56, i = 32; endianness independent. */
+ unsigned sx = __builtin_preserve_field_info (u->s.x, FIELD_RSHIFT_U64);
+ unsigned sc = __builtin_preserve_field_info (u->s.c, FIELD_RSHIFT_U64);
+ unsigned i = __builtin_preserve_field_info (u->i, FIELD_RSHIFT_U64);
+
+ return sx + sc + i;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],32" 2 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],56" 1 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:1:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "0x5\[\t \]+\[^\n\]*bpfcr_kind" 3 } } */
new file mode 100644
@@ -0,0 +1,33 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+enum {
+ FIELD_SIGNEDNESS = 3,
+};
+
+typedef unsigned uint;
+
+struct S {
+ unsigned char c;
+ int d;
+ uint u;
+ short ar[3];
+};
+
+unsigned int foo (struct S *s)
+{
+ unsigned d = __builtin_preserve_field_info (s->d, FIELD_SIGNEDNESS);
+ unsigned u = __builtin_preserve_field_info (s->u, FIELD_SIGNEDNESS);
+ unsigned ar = __builtin_preserve_field_info (s->ar[1], FIELD_SIGNEDNESS);
+
+ return d + u + ar;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],1" 2 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],0" 1 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:3:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "0x3\[\t \]+\[^\n\]*bpfcr_kind" 3 } } */
new file mode 100644
@@ -0,0 +1,45 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+enum {
+ FIELD_SIGNEDNESS = 3,
+};
+
+enum Esig {
+ SA = -1,
+ SB,
+ SC,
+};
+
+enum Eun {
+ UA = 0,
+ UB,
+};
+
+struct S {
+ enum Esig sig : 3;
+ enum Eun un : 3;
+};
+
+union U {
+ int i;
+ struct S s;
+};
+
+unsigned int foo (union U *u)
+{
+ unsigned i = __builtin_preserve_field_info (u->i, FIELD_SIGNEDNESS);
+ unsigned sig = __builtin_preserve_field_info (u->s.sig, FIELD_SIGNEDNESS);
+ unsigned un = __builtin_preserve_field_info (u->s.un, FIELD_SIGNEDNESS);
+
+ return i + sig + un;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],1" 2 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],0" 1 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "3\[\t \]+\[^\n\]*bpfcr_kind" 3 } } */
new file mode 100644
@@ -0,0 +1,43 @@
+/* { dg-do compile } */
+/* { dg-options "-O0 -dA -gbtf -mco-re" } */
+
+struct S {
+ unsigned int a1: 7;
+ unsigned int a2: 4;
+ unsigned int a3: 13;
+ unsigned int a4: 5;
+ char carr[5][3];
+};
+
+enum {
+ FIELD_BYTE_SIZE = 1,
+};
+
+union U {
+ long long l[3];
+ struct S s;
+};
+
+unsigned int foo (union U *u)
+{
+ unsigned ls = __builtin_preserve_field_info (u->l, FIELD_BYTE_SIZE);
+ unsigned s = __builtin_preserve_field_info (u->s, FIELD_BYTE_SIZE);
+ unsigned a2 = __builtin_preserve_field_info (u->s.a2, FIELD_BYTE_SIZE);
+ unsigned a3 = __builtin_preserve_field_info (u->s.a3, FIELD_BYTE_SIZE);
+ unsigned ca = __builtin_preserve_field_info (u->s.carr, FIELD_BYTE_SIZE);
+
+ return ls + s + a2 + a3 + ca;
+}
+
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],24" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],20" 1 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],4" 2 } } */
+/* { dg-final { scan-assembler-times "\[\t \]mov\[\t \]%r\[0-9\],15" 1 } } */
+
+/* { dg-final { scan-assembler-times "ascii \"0:0.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:1.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:2.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+/* { dg-final { scan-assembler-times "ascii \"0:1:4.0\"\[\t \]+\[^\n\]*btf_aux_string" 1 } } */
+
+/* { dg-final { scan-assembler-times "0x1\[\t \]+\[^\n\]*bpfcr_kind" 5 } } */