[v2] bpf: add preserve_field_info builtin

Message ID 20221025172443.6732-1-david.faust@oracle.com
State New
Headers
Series [v2] bpf: add preserve_field_info builtin |

Commit Message

David Faust Oct. 25, 2022, 5:24 p.m. UTC
  Hi Jose,

Thanks for your comments. I think I've addressed them all in the updated
patch below.

>>+  get_inner_reference (src, &bitsize, &bitpos, &var_off, &mode, &unsignedp,
>>+		       &reversep, &volatilep);
>
>Since the information returned by the builtin is always constant
>(positions, sizes) I think you will want to adjust the code for the
>eventuality of variable positioned fields and also variable sized
>fields.
>
>get_inner_reference sets var_off to a tree if the position of the field
>is variable.  In these cases `bitpos' is relative to that position.
>
>Likewise, get_inner_reference sets `mode' is set to BLKmode and
>`bitsize' will be set to -1.
>
>I'm not sure what the built-in is supposed to do/return in these cases.
>I guess it makes sense to error out, but what does LLVM do?

I would have thought erroring out the only option, but it seems that
LLVM will return a value from the builtin and record a CO-RE relocation
as normal.

What value will be returned depends of course on KIND, but from what
I can tell it seems that such fields are treated as having an offset of
0 bits and/or a size of 0 bits. For example FIELD_BYTE_SIZE for a
flexible-length array will return 0. FIELD_RSHIFT_U64 will be
calculated as 64 - 0 = 64.

This sort of makes sense if you expect that any BPF loader will honor
the CO-RE relocations and patch the return value before the program is
run, i.e. the actual values at compile time are irrelevant.

But, I'm not sure that BPF loaders in practice actually _can_ patch the
return value correctly. The source of information for resolving the
relocations is the BTF. But the BTF won't have more information about
variable position/size members. A flexible-length array for example in
BTF is represented as an array type with 0 elements. So the size
calculation when patching the relocation (looking at the impl in
libbpf) will be elem_size * nelems = 0, and the 'patched' values will
be the same as the unpatched.

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?


>If I read this properly, for something like:
>
>__builtin_preserve_field_info (a = foo.bar + bar.baz, KIND)
>
>On one side CO-RE relocations are computed for both foo.bar and bar.baz
>(I see bpf_core_compute does that) as expected.
>
>But then the builtin returns information that can only apply to one
>access.  Which one?

Expressions like this should not be accepted by the builtin. I didn't
consider this case in v1 so it led to an ICE. Clang rejects this
outright and errors with "argument 1 is not a field access". It is
actually very strict about the expressions that are accepted, unlike
__builtin_preserve_access_index.

I have updated this implementation to behave more like clang in that
it will reject any expression that isn't directly a field access. That
even includes rejecting things like:

  __builtin_preserve_field_info (&foo.bar, KIND)

Since unlike preserve_access_index this builtin does not actually
perform the operation in EXPR, it makes sense to enforce that EXPR must
be exactly a single field access.

---

[Changes from v1:
  - Error gracefully on variable-size or variable-offset fields if the
    requested information according to KIND cannot be calculated for
    an access to such a field, instead of maybe computing garbage.
  - Check for invalid expressions in EXPR and reject them rather than
    ICEing later. This includes rejecting some expressions which were
    accepted by previous patch but not by clang/LLVM.
  - Improve precision of location information for errors.
  - Add function-level comment describing maybe_make_core_relo ().
  - Document return values for all supported values of KIND.
  - Add tests for error conditions. ]

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                           |  40 ++
 .../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, 813 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

Jose E. Marchesi Oct. 25, 2022, 7:11 p.m. UTC | #1
> Hi Jose,
>
> Thanks for your comments. I think I've addressed them all in the updated
> patch below.
>
>>>+  get_inner_reference (src, &bitsize, &bitpos, &var_off, &mode, &unsignedp,
>>>+		       &reversep, &volatilep);
>>
>>Since the information returned by the builtin is always constant
>>(positions, sizes) I think you will want to adjust the code for the
>>eventuality of variable positioned fields and also variable sized
>>fields.
>>
>>get_inner_reference sets var_off to a tree if the position of the field
>>is variable.  In these cases `bitpos' is relative to that position.
>>
>>Likewise, get_inner_reference sets `mode' is set to BLKmode and
>>`bitsize' will be set to -1.
>>
>>I'm not sure what the built-in is supposed to do/return in these cases.
>>I guess it makes sense to error out, but what does LLVM do?
>
> I would have thought erroring out the only option, but it seems that
> LLVM will return a value from the builtin and record a CO-RE relocation
> as normal.
>
> What value will be returned depends of course on KIND, but from what
> I can tell it seems that such fields are treated as having an offset of
> 0 bits and/or a size of 0 bits. For example FIELD_BYTE_SIZE for a
> flexible-length array will return 0. FIELD_RSHIFT_U64 will be
> calculated as 64 - 0 = 64.
>
> This sort of makes sense if you expect that any BPF loader will honor
> the CO-RE relocations and patch the return value before the program is
> run, i.e. the actual values at compile time are irrelevant.
>
> But, I'm not sure that BPF loaders in practice actually _can_ patch the
> return value correctly. The source of information for resolving the
> relocations is the BTF. But the BTF won't have more information about
> variable position/size members. A flexible-length array for example in
> BTF is represented as an array type with 0 elements. So the size
> calculation when patching the relocation (looking at the impl in
> libbpf) will be elem_size * nelems = 0, and the 'patched' values will
> be the same as the unpatched.
>
> 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.

>>If I read this properly, for something like:
>>
>>__builtin_preserve_field_info (a = foo.bar + bar.baz, KIND)
>>
>>On one side CO-RE relocations are computed for both foo.bar and bar.baz
>>(I see bpf_core_compute does that) as expected.
>>
>>But then the builtin returns information that can only apply to one
>>access.  Which one?
>
> Expressions like this should not be accepted by the builtin. I didn't
> consider this case in v1 so it led to an ICE. Clang rejects this
> outright and errors with "argument 1 is not a field access". It is
> actually very strict about the expressions that are accepted, unlike
> __builtin_preserve_access_index.
>
> I have updated this implementation to behave more like clang in that
> it will reject any expression that isn't directly a field access. That
> even includes rejecting things like:
>
>   __builtin_preserve_field_info (&foo.bar, KIND)
>
> Since unlike preserve_access_index this builtin does not actually
> perform the operation in EXPR, it makes sense to enforce that EXPR must
> be exactly a single field access.

Ok, thanks.

> [...]
> +@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?

> +@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?

> +@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?

> +@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.
> +
> +@end deftypefn
> +
>  @node FR-V Built-in Functions
>  @subsection FR-V Built-in Functions
  

Patch

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 (&param, bpf_core_walk, (void *) &loc, NULL);
+  struct core_walk_data data;
+  data.loc = loc;
+  data.arg = is_pai ? NULL_TREE : (*params)[1];
+
+  walk_tree (&param, 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..b956a3240fe 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -15745,6 +15745,46 @@  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.
+
+@item FIELD_BYTE_SIZE = 1
+The returned value is the size, in bytes, of the field.
+
+@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.
+@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.
+
+@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 } } */