public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH] bpf: add preserve_field_info builtin
@ 2022-10-19 22:06 David Faust
  2022-10-20 12:38 ` Jose E. Marchesi
  0 siblings, 1 reply; 7+ messages in thread
From: David Faust @ 2022-10-19 22:06 UTC (permalink / raw)
  To: gcc-patches; +Cc: jose.marchesi

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.

Tested on bpf-unknown-none, no known regressions.
OK?

Thanks

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-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                         | 327 ++++++++++++++----
 gcc/config/bpf/coreout.cc                     |   5 +-
 gcc/config/bpf/coreout.h                      |   2 +-
 gcc/doc/extend.texi                           |  21 ++
 .../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 +++
 14 files changed, 676 insertions(+), 72 deletions(-)
 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..41a506349b2 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,161 @@ static tree bpf_core_compute (tree, vec<unsigned int> *);
 static int bpf_core_get_index (const tree);
 static bool is_attr_preserve_access (tree);
 
+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;
+  machine_mode mode;
+  int unsignedp, reversep, volatilep;
+
+  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 (bitfieldp)
+	  result = start_bitpos / 8;
+	else
+	  result = bitpos / 8;
+      }
+      break;
+
+    case BPF_RELO_FIELD_BYTE_SIZE:
+      {
+	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 ("unsupported field expression");
+	    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 (!bitfieldp)
+	  {
+	    if (bitsize > 64)
+	      error ("field size too large");
+	    result = 64 - bitsize;
+	    break;
+	  }
+
+	if (end_bitpos - start_bitpos > 64)
+	  error ("field size too large");
+
+	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 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 +1183,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 +1205,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,17 +1406,29 @@ 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
@@ -1278,22 +1437,33 @@ bpf_core_newdecl (tree type)
 static int
 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 1;
+    case ADDR_EXPR:
+    case NOP_EXPR:
       return bpf_core_is_maybe_aggregate_access (TREE_OPERAND (expr, 0));
-
+    default:;
+    }
   return 0;
 }
 
+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 +1472,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;
     }
@@ -1344,7 +1522,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,16 +1535,17 @@ 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");
+      error_at (loc, "wrong number of arguments");
       return NULL_TREE;
     }
 
   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;
 
   /* Do remove_c_maybe_const_expr for the arg.
@@ -1387,7 +1571,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 +1712,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..0d3bcb24ab9 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -15745,6 +15745,27 @@ 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.
+
+@var{kind} is one of:
+@smallexample
+enum
+@{
+  FIELD_BYTE_OFFSET = 0,
+  FIELD_BYTE_SIZE,
+  FIELD_EXISTENCE,
+  FIELD_SIGNEDNESS,
+  FIELD_LSHIFT_U64,
+  FIELD_RSHIFT_U64,
+@};
+@end smallexample
+@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-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 } } */
-- 
2.37.2


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] bpf: add preserve_field_info builtin
  2022-10-19 22:06 [PATCH] bpf: add preserve_field_info builtin David Faust
@ 2022-10-20 12:38 ` Jose E. Marchesi
  2022-10-25 17:24   ` [PATCH v2] " David Faust
  0 siblings, 1 reply; 7+ messages in thread
From: Jose E. Marchesi @ 2022-10-20 12:38 UTC (permalink / raw)
  To: David Faust; +Cc: gcc-patches


Hi David.
Thanks for the patch.  Please see a few comments below.

> @@ -975,6 +978,161 @@ static tree bpf_core_compute (tree, vec<unsigned int> *);
>  static int bpf_core_get_index (const tree);
>  static bool is_attr_preserve_access (tree);
>  
> +static void
> +maybe_make_core_relo (tree expr, enum btf_core_reloc_kind kind)

This function is missing a comment explaining what it does.

> +{
> +  /* 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;
> +  machine_mode mode;
> +  int unsignedp, reversep, volatilep;
> +
> +  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?

> +
> +  /* 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 (bitfieldp)
> +	  result = start_bitpos / 8;
> +	else
> +	  result = bitpos / 8;
> +      }
> +      break;
> +
> +    case BPF_RELO_FIELD_BYTE_SIZE:
> +      {
> +	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 ("unsupported field expression");
> +	    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 (!bitfieldp)
> +	  {
> +	    if (bitsize > 64)
> +	      error ("field size too large");
> +	    result = 64 - bitsize;
> +	    break;
> +	  }
> +
> +	if (end_bitpos - start_bitpos > 64)
> +	  error ("field size too large");
> +
> +	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 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 +1183,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 +1205,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 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?

> +
> +      if (result != error_mark_node)
> +	maybe_make_core_relo (src, kind);
> +
> +      return expand_normal (result);
>      }
> +
>    gcc_unreachable ();
>  }
>  
> @@ -1259,17 +1406,29 @@ 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
> @@ -1278,22 +1437,33 @@ bpf_core_newdecl (tree type)
>  static int
>  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 1;
> +    case ADDR_EXPR:
> +    case NOP_EXPR:
>        return bpf_core_is_maybe_aggregate_access (TREE_OPERAND (expr, 0));
> -
> +    default:;
> +    }
>    return 0;
>  }
>  
> +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 +1472,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;
>      }
> @@ -1344,7 +1522,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,16 +1535,17 @@ 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");
> +      error_at (loc, "wrong number of arguments");
>        return NULL_TREE;
>      }
>  
>    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;
>  
>    /* Do remove_c_maybe_const_expr for the arg.
> @@ -1387,7 +1571,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 +1712,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..0d3bcb24ab9 100644
> --- a/gcc/doc/extend.texi
> +++ b/gcc/doc/extend.texi
> @@ -15745,6 +15745,27 @@ 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.
> +
> +@var{kind} is one of:
> +@smallexample
> +enum
> +@{
> +  FIELD_BYTE_OFFSET = 0,
> +  FIELD_BYTE_SIZE,
> +  FIELD_EXISTENCE,
> +  FIELD_SIGNEDNESS,
> +  FIELD_LSHIFT_U64,
> +  FIELD_RSHIFT_U64,
> +@};
> +@end smallexample
> +@end deftypefn
> +

We need to document what all these returned values are.  It may be
more-or-less obvious, but it is better to be explicit IMO.  It would
also be good to document the fact the returned value is always known at
compile-time.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v2] bpf: add preserve_field_info builtin
  2022-10-20 12:38 ` Jose E. Marchesi
@ 2022-10-25 17:24   ` David Faust
  2022-10-25 19:11     ` Jose E. Marchesi
  0 siblings, 1 reply; 7+ messages in thread
From: David Faust @ 2022-10-25 17:24 UTC (permalink / raw)
  To: jose.marchesi; +Cc: gcc-patches

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

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 } } */
-- 
2.37.2


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v2] bpf: add preserve_field_info builtin
  2022-10-25 17:24   ` [PATCH v2] " David Faust
@ 2022-10-25 19:11     ` Jose E. Marchesi
  2022-10-26 19:23       ` [PATCH v3] " David Faust
  0 siblings, 1 reply; 7+ messages in thread
From: Jose E. Marchesi @ 2022-10-25 19:11 UTC (permalink / raw)
  To: David Faust; +Cc: gcc-patches


> 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

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [PATCH v3] bpf: add preserve_field_info builtin
  2022-10-25 19:11     ` Jose E. Marchesi
@ 2022-10-26 19:23       ` David Faust
  2022-10-26 19:33         ` Jose E. Marchesi
  0 siblings, 1 reply; 7+ messages in thread
From: David Faust @ 2022-10-26 19:23 UTC (permalink / raw)
  To: jose.marchesi; +Cc: gcc-patches

>> 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 (&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..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 } } */
-- 
2.37.2


^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v3] bpf: add preserve_field_info builtin
  2022-10-26 19:23       ` [PATCH v3] " David Faust
@ 2022-10-26 19:33         ` Jose E. Marchesi
  2022-10-26 20:21           ` David Faust
  0 siblings, 1 reply; 7+ messages in thread
From: Jose E. Marchesi @ 2022-10-26 19:33 UTC (permalink / raw)
  To: David Faust; +Cc: gcc-patches


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 (&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..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 } } */

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH v3] bpf: add preserve_field_info builtin
  2022-10-26 19:33         ` Jose E. Marchesi
@ 2022-10-26 20:21           ` David Faust
  0 siblings, 0 replies; 7+ messages in thread
From: David Faust @ 2022-10-26 20:21 UTC (permalink / raw)
  To: Jose E. Marchesi; +Cc: gcc-patches


On 10/26/22 12:33, Jose E. Marchesi wrote:
> 
> Hi David.
> 
> Thanks for the updates.
> OK for master.
> 

Pushed, thanks.

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2022-10-26 20:21 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-10-19 22:06 [PATCH] bpf: add preserve_field_info builtin David Faust
2022-10-20 12:38 ` Jose E. Marchesi
2022-10-25 17:24   ` [PATCH v2] " David Faust
2022-10-25 19:11     ` Jose E. Marchesi
2022-10-26 19:23       ` [PATCH v3] " David Faust
2022-10-26 19:33         ` Jose E. Marchesi
2022-10-26 20:21           ` David Faust

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).