Hi Cupertino,
On 2/19/26 12:38, Cupertino Miranda wrote:
> Hi everyone,
>
> This is v4. This patch goes one step further and simplifies how the
> expressions are converted to CO-RE in the case where
> explicit expressions should be converted, i.e. no
> builtin_preserve_access_index is used.
> Since the expression split separates CO-RE expressions from non CO-RE
> expressions it also allowed to simplify the walker that creates the
> internal core_builtin calls.
Overall this version is looking quite nice.
Just a couple of notes inline below.
Thanks!
>
> This is a delta between the before and after of the patch.
>
> # diff /tmp/baseline.txt /tmp/new.txt
> 155c155
> < #35/15 bpf_tcp_ca/cc_cubic:FAIL
> ---
>> #35/15 bpf_tcp_ca/cc_cubic:OK
> 157c157
> < #35 bpf_tcp_ca:FAIL
> ---
>> #35 bpf_tcp_ca:OK
> 1166c1166
> < #109/5 fd_array_cnt/fd-array-ref-btfs:FAIL
> ---
>> #109/5 fd_array_cnt/fd-array-ref-btfs:OK
> 1169c1169
> < #109 fd_array_cnt:FAIL
> ---
>> #109 fd_array_cnt:OK
> 1686c1686
> < #189/1 lsm_cgroup/functional:FAIL
> ---
>> #189/1 lsm_cgroup/functional:OK
> 1688c1688
> < #189 lsm_cgroup:FAIL
> ---
>> #189 lsm_cgroup:OK
> 4155,4159c4155,4159
> < #546/1 verifier_bitfield_write/single CO-RE bitfield roundtrip:FAIL
> < #546/2 verifier_bitfield_write/multiple CO-RE bitfield roundtrip:FAIL
> < #546/3 verifier_bitfield_write/adjacent CO-RE bitfield roundtrip:FAIL
> < #546/4 verifier_bitfield_write/multibyte CO-RE bitfield roundtrip:FAIL
> < #546 verifier_bitfield_write:FAIL
> ---
>> #546/1 verifier_bitfield_write/single CO-RE bitfield roundtrip:OK
>> #546/2 verifier_bitfield_write/multiple CO-RE bitfield roundtrip:OK
>> #546/3 verifier_bitfield_write/adjacent CO-RE bitfield roundtrip:OK
>> #546/4 verifier_bitfield_write/multibyte CO-RE bitfield roundtrip:OK
>> #546 verifier_bitfield_write:OK
Very good.
>
> Looking forward to your review.
>
> Thanks,
> Cupertino
>
> ---------
>
> This patch corrects CO-RE generation for the cases where an expression
> starts with a non-CO-RE access, but in the middle it requires to
> generate CO-RE to correctly compute the access location.
>
> It fixes it by splitting the expression into their CO-RE and non-CO-RE
> counterparts. It performs this by walking gimple expressions, and for
> each field access to which its type is a struct or union, it verifies if
> both the types for the base and field are attributed similarly.
> Otherwise, it splits the expression at this location by creating a
> temporary variable and performing any required pointer conversions.
> This smaller expressions are converted to CO-RE in the subsequent
> gimple walker.
>
> There is no way in GCC to distinguish nested struct/union definitions
> from non-nested ones.
> This patch simplifies code and enforces that all preserve_access_index
> structs/unions would be attributed explicitly.
> Previous approach was error prone as it would extend CO-RE accesses to
> structures which would not be attributed.
>
> All GCC BPF dejagnu passes tests:
> # of expected passes 553
> # of expected failures 6
>
> kernel-next bpf selftests:
> before: Summary: 543/4888 PASSED, 113 SKIPPED, 136 FAILED
> after: Summary: 545/4893 PASSED, 113 SKIPPED, 134 FAILED
>
> gcc/ChangeLog:
> PR target/120241
> * config/bpf/core-builtins.cc
> (is_attr_preserve_access): Correct for pointer types.
> (maybe_get_base_for_field_expr, core_access_index_map,
> core_access_clean, core_is_access_index, core_mark_as_access_index):
> Remove.
> (make_gimple_core_safe_access_index): Remove function.
> (struct walker_data): New struct to pass data to tree walker.
> (callback_should_do_core_access, should_do_core_access): Add
> function to identify expressions that should not be converted to
> CO-RE.
> (core_make_builtins): Add callback tree walker function to
> convert expressions to CO-RE.
> (callback_find_next_split_location, core_should_split_expr,
> find_next_split_location, gimple_core_early_split_expr): Add
> function to split expressions in CO-RE and non-CO-RE
> expressions.
> (execute_lower_bpf_core): Adapt to new code.
>
> gcc/testsuite/ChangeLog:
> PR target/120241
> * gcc.target/bpf/core-attr-3.c: Add attribute.
> * gcc.target/bpf/core-attr-4.c: Add attribute.
> * gcc.target/bpf/core-attr-5.c: Add attribute.
> * gcc.target/bpf/core-attr-6.c: Add attribute.
> * gcc.target/bpf/core-attr-7.c: New test.
> * gcc.target/bpf/core-attr-calls.c: Adapt.
> ---
> gcc/config/bpf/bpf.opt | 4 +
> gcc/config/bpf/core-builtins.cc | 306 ++++++++++++------
> gcc/testsuite/gcc.target/bpf/core-attr-3.c | 4 +-
> gcc/testsuite/gcc.target/bpf/core-attr-4.c | 4 +-
> gcc/testsuite/gcc.target/bpf/core-attr-5.c | 2 +-
> gcc/testsuite/gcc.target/bpf/core-attr-6.c | 4 +-
> gcc/testsuite/gcc.target/bpf/core-attr-7.c | 150 +++++++++
> .../gcc.target/bpf/core-attr-calls.c | 4 +-
> 8 files changed, 371 insertions(+), 107 deletions(-)
> create mode 100644 gcc/testsuite/gcc.target/bpf/core-attr-7.c
>
> diff --git a/gcc/config/bpf/bpf.opt b/gcc/config/bpf/bpf.opt
> index 54d038a7fdea..fe34b18c19d6 100644
> --- a/gcc/config/bpf/bpf.opt
> +++ b/gcc/config/bpf/bpf.opt
> @@ -112,3 +112,7 @@ Enum(asm_dialect) String(pseudoc) Value(ASM_PSEUDOC)
> minline-memops-threshold=
> Target RejectNegative Joined UInteger Var(bpf_inline_memops_threshold)
> Init(1024)
> -minline-memops-threshold=<number> Maximum size of memset/memmove/memcpy to
> inline, larger sizes will use a library call.
> +
> +Wbpf-core
> +Target Warning
> +Warn when CO-RE support is not guaranteed.
I like this new warning option.
But it needs a corresponding entry and documentation in doc/invoke.texi
under the BPF Options.
> diff --git a/gcc/config/bpf/core-builtins.cc b/gcc/config/bpf/core-builtins.cc
> index 152da94a9761..cf32fe08a3f5 100644
> --- a/gcc/config/bpf/core-builtins.cc
> +++ b/gcc/config/bpf/core-builtins.cc
> @@ -324,8 +324,13 @@ is_attr_preserve_access (tree t)
> TYPE_ATTRIBUTES (TREE_TYPE (base)));
>
> if (TREE_CODE (base) == MEM_REF)
> - return lookup_attribute ("preserve_access_index",
> - TYPE_ATTRIBUTES (TREE_TYPE (base)));
> + {
> + tree type = TREE_TYPE (base);
> + if (POINTER_TYPE_P (type))
> + type = TREE_TYPE (type);
> + return lookup_attribute ("preserve_access_index",
> + TYPE_ATTRIBUTES (type));
> + }
>
> if (TREE_CODE (t) == COMPONENT_REF)
> {
> @@ -1710,137 +1715,241 @@ bpf_output_core_reloc (rtx *operands, int nr_ops)
> }
> }
>
> +/* This function verifies if the passed tree node t is a struct field
> + expression and if at this location is is necessary to break the expression
> + in order to split CO-RE and non CO-RE required field/union accesses. */
> +
> +static bool
> +core_should_split_expr (tree t)
> +{
> + tree type = TREE_TYPE (t);
> + if (TREE_CODE (type) != RECORD_TYPE
> + && TREE_CODE (type) != UNION_TYPE)
> + return false;
> +
> + if (TREE_CODE (t) == COMPONENT_REF)
> + {
> + tree base = TREE_OPERAND (t, 0);
> +
> + bool base_is_attr = lookup_attribute ("preserve_access_index",
> + TYPE_ATTRIBUTES (TREE_TYPE (base))) != NULL_TREE;
> + bool cur_is_attr = lookup_attribute ("preserve_access_index",
> + TYPE_ATTRIBUTES (TREE_TYPE (t))) != NULL_TREE;
> +
> + if (base_is_attr != cur_is_attr)
> + return true;
> + }
> + return false;
> +}
> +
> +struct walker_data {
> + bool is_root;
> + tree *core_base_pointer;
> +};
> +
> static tree
> -maybe_get_base_for_field_expr (tree expr)
> +callback_find_next_split_location (tree *tp,
> + int *walk_subtrees ATTRIBUTE_UNUSED,
> + void *data)
> {
> - poly_int64 bitsize, bitpos;
> - tree var_off;
> - machine_mode mode;
> - int sign, reverse, vol;
> + struct walker_data *d = (struct walker_data *) data;
>
> - if (expr == NULL_TREE)
> + if (d->is_root == true && TREE_CODE (*tp) == ADDR_EXPR)
> return NULL_TREE;
>
> - return get_inner_reference (expr, &bitsize, &bitpos, &var_off, &mode,
> - &sign, &reverse, &vol);
> + if (d->is_root == false && core_should_split_expr (*tp))
> + {
> + d->core_base_pointer = tp;
> + return *tp;
> + }
> + else
> + {
> + /* Keep walking the expression. */
> + d->is_root = false;
> + return NULL_TREE;
> + }
> }
>
> -/* Access functions to mark sub expressions as attributed with
> - __preserve_access_index.
> - This is required since in gimple format, in order to convert an
> expression as
> - CO-RE safe, we must create multiple gimple statements.
> - Also, only the type of the base of the expression might be attributed with
> - __preserve_access_index. Nevertheless all the consecutive accesses to
> this
> - attributed node should also be converted to CO-RE safe.
> - Any LHS assigned values with CO-RE converted expressions are marked and
> - any uses of these values are later checked for further convertion.
> - The core_access_index_map functions allow to mark this nodes for later
> - convertion to CO-RE.
> - This mechanism are used by make_gimple_core_safe_access_index. */
> -
> -static GTY(()) hash_map<tree, tree> *core_access_index_map = NULL;
> +/* Walk an expression and return the node at any location in the expression
> + where we should break the expression in its CO-RE and non CO-RE parts. */
>
> -static void
> -core_access_clean (void)
> +static tree *
> +find_next_split_location (tree *tp, bool is_first)
> {
> - if (core_access_index_map == NULL)
> - core_access_index_map = hash_map<tree, tree>::create_ggc (10);
> - core_access_index_map->empty ();
> + struct walker_data data = { is_first, NULL };
> + walk_tree (tp, callback_find_next_split_location, &data, NULL);
> + return data.core_base_pointer;
> }
>
> -static bool
> -core_is_access_index (tree expr)
> +
> +static tree
> +callback_should_do_core_access (tree *tp,
> + int *walk_subtrees ATTRIBUTE_UNUSED,
> + void *data ATTRIBUTE_UNUSED)
> {
> - if (TREE_CODE (expr) == MEM_REF
> - || TREE_CODE (expr) == INDIRECT_REF)
> - expr = TREE_OPERAND (expr, 0);
> +/* TODO NOTE: This might have to be removed later as it is an exception
> + to mimic clang behavior. */
What behavior? Please elaborate a little in the comment.
> + if (lookup_attribute ("packed", TYPE_ATTRIBUTES (TREE_TYPE (*tp))) !=
> NULL_TREE)
> + {
> + warning_at (EXPR_LOC_OR_LOC (*tp, UNKNOWN_LOCATION), OPT_Wbpf_core,
> + "CO-RE expression not guaranteed, struct attribute packed with "
> + "preserve-access-index attributed struct childs");
This warning message as written doesn't make much sense to me.
>From what I understand (also based on discussions in IRC), the problem
is that if we access a p-a-i struct member of a struct which is packed
like in:
struct A {
int x;
} __attribute__((preserve_access_index))
struct B {
struct A a;
} __attribute__((packed))
int foo (struct B *b)
{
return b->a.x;
}
...Then it may not be possible to always patch the instructions which
access 'a.x' depending on how the structs change on a different kernel
version. Right?
I suggest rewording the warning message to something like
"Access [to member x] through packed struct may not be possible
to patch via CO-RE"
(bonus points for stuff in [] or precise location info to point to it)
> + return *tp;
> + }
> + return NULL_TREE;
> +}
>
> - tree *def = core_access_index_map->get (expr);
> - if (def)
> - return true;
> - return false;
> +
> +static bool
> +should_do_core_access (tree *tp)
> +{
> + tree tfail = walk_tree (tp, callback_should_do_core_access, NULL, NULL);
> + return tfail == NULL_TREE;
> }
>
> -static void
> -core_mark_as_access_index (tree expr)
> +/* This is the main callback function to the gimple traversal that will split
> + any field/union accesses in their CO-RE and non CO-RE parts.
> + An early version of this callback would allow also allow its walker
> function
> + to traverse the parent tree expression on any initial expression (children
> + tree node), by not setting walk_subtrees to false. In current version only
> + the main tree node of an expression is reached by the traversal function
> + that uses this callback. The sub traversal of the main tree nodes is done
> in
> + find_next_split_location call. The function iterates on the parent
> + expressions within the loop that call find_next_split_location function.
> */
> +
> +static tree
> +gimple_core_early_split_expr (tree *tp,
> + int *walk_subtrees,
> + void *data)
> {
> - if (TREE_CODE (expr) == MEM_REF
> - || TREE_CODE (expr) == INDIRECT_REF)
> - expr = TREE_OPERAND (expr, 0);
> + struct walk_stmt_info *wi = (struct walk_stmt_info *) data;
> + tree *split_loc = tp;
>
> - if (core_access_index_map->get (expr) == NULL)
> - core_access_index_map->put (expr, NULL_TREE);
> -}
> + if (!should_do_core_access (tp))
> + {
> + *walk_subtrees = false;
> + return NULL_TREE;
> + }
> +
> + /* Find the next split location within expression tree. */
> + tree *expr = NULL;
> + bool is_first = true;
> + while ((expr = find_next_split_location (split_loc, is_first)) != NULL)
> + {
> + gimple_seq before = NULL;
> +
> + if (*expr == *split_loc && is_first == true)
> + break;
> + is_first = false;
>
> -/* This function is an adaptation of make_core_safe_access_index but to be
> used
> - in gimple format trees. It is used by execute_lower_bpf_core, when
> - traversing the gimple tree looking for nodes that would have its type
> - attributed with __preserve_access_index. In this particular cases any of
> - the expressions using such attributed types must be made CO-RE safe. */
> + push_gimplify_context ();
> + split_loc = &TREE_OPERAND (*expr, 0);
> +
> + tree rhs = *expr;
> + bool pointer_base = false;
> + if (!POINTER_TYPE_P (TREE_TYPE (rhs)))
> + {
> + rhs = fold_build1 (ADDR_EXPR,
> + build_pointer_type (TREE_TYPE (*expr)),
> + *expr);
> + pointer_base = true;
> + }
> + tree lhs = create_tmp_var_raw (TREE_TYPE (rhs), "core_split");
> + gimple_add_tmp_var (lhs);
> + gimplify_assign (lhs, rhs, &before);
> +
> + if (pointer_base == true)
> + lhs = fold_build2 (MEM_REF, TREE_TYPE (lhs), lhs,
> + build_int_cst (ptr_type_node, 0));
> + *expr = lhs;
> +
> + gsi_insert_seq_before (&(wi->gsi), before, GSI_NEW_STMT);
> + pop_gimplify_context (NULL);
> + }
> +
> + /* Never traverse subtrees, as find_next_split_location already does it. */
> + *walk_subtrees = false;
> +
> + /* Keep traverse all the other tree expressions in gimple. */
> + return NULL_TREE;
very minor nit but I noticed it so: dot, space, space, star slash
for the two comments above.
> +}
>
> static tree
> -make_gimple_core_safe_access_index (tree *tp,
> - int *walk_subtrees ATTRIBUTE_UNUSED,
> - void *data)
> +core_make_builtins (tree *tp, int *walk_subtrees, void *data)
> {
> struct walk_stmt_info *wi = (struct walk_stmt_info *) data;
> - bool valid = true;
> int n = 0;
> + bool valid = true;
> + tree expr = *tp;
> + bool is_addr_expr = false;
>
> - tree *patch = tp;
> - if (TREE_CODE (*patch) == ADDR_EXPR)
> - patch = &(TREE_OPERAND (*tp, 0));
> - tree orig_type = TREE_TYPE (*patch);
> + if (!should_do_core_access (tp))
> + {
> + *walk_subtrees = false;
> + return NULL_TREE;
> + }
>
> - if ((is_attr_preserve_access (*patch)
> - || core_is_access_index (maybe_get_base_for_field_expr (*patch)))
> - && (n = compute_field_expr (*patch, NULL, &valid, NULL)) > 0
> + /* Skip ADDR_EXPR node since we will convert to pointer anyway. */
> + if (TREE_CODE (*tp) == ADDR_EXPR)
> + {
> + is_addr_expr = true;
> + expr = TREE_OPERAND (*tp, 0);
> + }
> +
> + if (is_attr_preserve_access (expr)
> + && (n = compute_field_expr (expr, NULL, &valid, NULL)) > 0
> && valid == true)
> {
> - bool changed = false;
> - tree expr_test = make_core_safe_access_index (*patch, &changed);
> + poly_int64 bitsize, bitpos;
> + tree var_off;
> + machine_mode mode;
> + int sign, reverse, vol;
>
> + tree base = get_inner_reference (expr, &bitsize, &bitpos, &var_off,
> &mode,
> + &sign, &reverse, &vol);
>
> - gimple_seq before = NULL;
> - push_gimplify_context ();
> - gimplify_expr (&expr_test, &before, NULL, is_gimple_val, fb_rvalue);
> + tree new_expr = core_expr_with_field_expr_plus_base (base, expr, true);
>
> - /* In case the ADDR_EXPR bypassed above is no longer needed. */
> - if (patch != tp && TREE_TYPE (expr_test) == TREE_TYPE (*tp))
> - *tp = expr_test;
> - /* For non pointer value accesses. */
> - else if (TREE_TYPE (expr_test) == build_pointer_type (orig_type))
> - *patch = fold_build2 (MEM_REF, TREE_TYPE (*patch),
> - expr_test, build_int_cst (ptr_type_node, 0));
> - else
> - *patch = expr_test;
> + /* Abort convertion if it is just a pointer or a reference to an
> + attributed type. */
> + if (new_expr != expr)
> + {
>
> - *tp = fold (*tp);
> + gimple_seq before = NULL;
> + push_gimplify_context ();
>
> - gsi_insert_seq_before (&(wi->gsi), before, GSI_LAST_NEW_STMT);
> - pop_gimplify_context (NULL);
> + gimplify_expr (&new_expr, &before, NULL, is_gimple_val, fb_rvalue);
>
> - wi->changed = true;
> - *walk_subtrees = false;
> + tree new_expr_type = TREE_TYPE (new_expr);
> +
> + /* Replace original expression by new_expr. If the type is the same
> + * tree node, good! If it is a pointer type, we need to the type
> + * within the pointer to guarantee it is actually the same. */
nit but the block comment styling should not prepend * on every line.
> + if (TREE_TYPE (new_expr) == TREE_TYPE (*tp)
> + || (is_addr_expr
> + && TREE_TYPE (new_expr_type) == TREE_TYPE (expr)))
> + *tp = fold (new_expr);
> + else if (TREE_TYPE (new_expr) == build_pointer_type (TREE_TYPE (*tp)))
> + *tp = fold_build2 (MEM_REF, TREE_TYPE (*tp),
> + new_expr, build_int_cst (ptr_type_node, 0));
> + else
> + gcc_assert (0);
>
> - tree lhs;
> - if (!wi->is_lhs
> - && gimple_code (wi->stmt) != GIMPLE_CALL
> - && (lhs = gimple_get_lhs (wi->stmt)) != NULL_TREE)
> - core_mark_as_access_index (lhs);
> + gsi_insert_seq_before (&(wi->gsi), before, GSI_SAME_STMT);
> + pop_gimplify_context (NULL);
> +
> + *walk_subtrees = false;
> + }
> }
> return NULL_TREE;
> }
>
> /* This is the entry point for the pass_data_lower_bpg_core. It walks all
> the
> statements in gimple, looking for expressions that are suppose to be CO-RE
> - preserve_access_index attributed.
> - Those expressions are processed and split by
> - make_gimple_core_safe_access_index function, which will both create the
> - calls to __build_core_reloc and split the expression in smaller parts in
> - case it cannot be represented CO-RE safeguarded by a single CO-RE
> - relocation. */
> -
> + preserve_access_index attributed, and should not.
> + It first splits CO-RE expressions from non CO-RE ones.
> + Then the CO-RE expressions are processed by core_make_builtins which will
> + create the required builtins that will result in the CO-RE relocations.
> */
> static unsigned int
> execute_lower_bpf_core (void)
> {
> @@ -1851,13 +1960,14 @@ execute_lower_bpf_core (void)
> gimple_seq body = gimple_body (current_function_decl);
>
> struct walk_stmt_info wi;
> - core_access_clean ();
> -
> memset (&wi, 0, sizeof (wi));
> - wi.info = NULL;
>
> - /* Split preserve_access_index expressions when needed. */
> - walk_gimple_seq_mod (&body, NULL, make_gimple_core_safe_access_index, &wi);
> + /* Early split to guarantee base of expression is a preserve_access_index
> + structure. */
> + walk_gimple_seq_mod (&body, NULL, gimple_core_early_split_expr, &wi);
> +
> + /* Add CO-RE builtins for all expressions that need them. */
> + walk_gimple_seq_mod (&body, NULL, core_make_builtins, &wi);
> return 0;
> }
>
> diff --git a/gcc/testsuite/gcc.target/bpf/core-attr-3.c
> b/gcc/testsuite/gcc.target/bpf/core-attr-3.c
> index 12354fc6f86d..58c27fd43bb4 100644
> --- a/gcc/testsuite/gcc.target/bpf/core-attr-3.c
> +++ b/gcc/testsuite/gcc.target/bpf/core-attr-3.c
> @@ -11,14 +11,14 @@
> struct O {
> int e;
> int f;
> -};
> +} __attribute__((preserve_access_index));
>
> struct S {
> int a;
> struct {
> int b;
> int c;
> - } inner;
> + } __attribute__((preserve_access_index)) inner;
> struct O other;
> } __attribute__((preserve_access_index));
>
> diff --git a/gcc/testsuite/gcc.target/bpf/core-attr-4.c
> b/gcc/testsuite/gcc.target/bpf/core-attr-4.c
> index 6f025f42f3ee..c001b5b76ef9 100644
> --- a/gcc/testsuite/gcc.target/bpf/core-attr-4.c
> +++ b/gcc/testsuite/gcc.target/bpf/core-attr-4.c
> @@ -13,8 +13,8 @@ struct T {
> int d;
> int e[4];
> int f;
> - } v;
> - } u;
> + } __attribute__((preserve_access_index)) v;
> + } __attribute__((preserve_access_index)) u;
> } __attribute__((preserve_access_index));
>
>
> diff --git a/gcc/testsuite/gcc.target/bpf/core-attr-5.c
> b/gcc/testsuite/gcc.target/bpf/core-attr-5.c
> index 81e25fa85de7..4d443d88b0ae 100644
> --- a/gcc/testsuite/gcc.target/bpf/core-attr-5.c
> +++ b/gcc/testsuite/gcc.target/bpf/core-attr-5.c
> @@ -11,7 +11,7 @@ struct U {
> int e[4];
> int f;
> int *g;
> - } v;
> + } __attribute__((preserve_access_index)) v;
> } __attribute__((preserve_access_index));
>
> struct T {
> diff --git a/gcc/testsuite/gcc.target/bpf/core-attr-6.c
> b/gcc/testsuite/gcc.target/bpf/core-attr-6.c
> index 25215b5ae376..d43825ea97c3 100644
> --- a/gcc/testsuite/gcc.target/bpf/core-attr-6.c
> +++ b/gcc/testsuite/gcc.target/bpf/core-attr-6.c
> @@ -11,8 +11,8 @@ struct U {
> int e[4];
> int f;
> int *g;
> - } v;
> -} u;
> + } __attribute__((preserve_access_index)) v;
> +} __attribute__((preserve_access_index)) u;
>
> struct T {
> int a;
> diff --git a/gcc/testsuite/gcc.target/bpf/core-attr-7.c
> b/gcc/testsuite/gcc.target/bpf/core-attr-7.c
> new file mode 100644
> index 000000000000..342f69e379ba
> --- /dev/null
> +++ b/gcc/testsuite/gcc.target/bpf/core-attr-7.c
> @@ -0,0 +1,150 @@
> +/* Test for BPF CO-RE __attribute__((preserve_access_index)) with accesses on
> + LHS and both LHS and RHS of assignment. */
> +
> +/* { dg-do compile } */
> +/* { dg-options "-O2 -dA -gbtf -mco-re -masm=normal" } */
> +
> +struct other
> +{
> + char c;
> + int i;
> +};
> +
> +struct inner_attr1
> +{
> + int i1;
> + int i2;
> +} __attribute__((preserve_access_index));
> +
> +struct inner_noattr
> +{
> + int i1;
> + int i2;
> +};
> +
> +union A_noattr
> +{
> + struct inner_attr1 inner_attr;
> + struct inner_noattr inner_no_attr;
> + struct inner_noattr *inner_p;
> +};
> +union A_attr
> +{
> + struct inner_attr1 inner_attr;
> + struct inner_noattr inner_no_attr;
> + struct inner_noattr *inner_p;
> +} __attribute__((preserve_access_index));
> +
> +
> +struct outer_noattr
> +{
> + struct other *other;
> + struct inner_attr
> + {
> + int i1;
> + int i2;
> + } __attribute__((preserve_access_index)) inner;
> + struct inner_noattr inner_no_attr;
> + struct inner_attr1 *inner_p;
> + union A_attr a_attr;
> + union A_noattr a_noattr;
> +};
> +
> +struct outer_attr
> +{
> + struct other *other;
> + struct inner_attr
> + {
> + int i1;
> + int i2;
> + } __attribute__((preserve_access_index)) inner;
> +
> + struct inner_noattr inner_no_attr;
> + struct inner_attr1 *inner_p;
> + union A_attr a_attr;
> + union A_noattr a_noattr;
> +} __attribute__((preserve_access_index));
> +
> +extern void use_value(int);
> +
> +void
> +func (struct outer_noattr *o, struct outer_attr *o_attr)
> +{
> + /* 0:1 */
> + o->inner.i2 = 7;
> + /* 0:1 */
> + o->inner_p->i2 = 8;
> + /* nothing */
> + o->inner_no_attr.i2 = 9;
> +
> + /* 0:2 */
> + o_attr->inner_no_attr.i2 = 10;
> +
> + /* nothing */
> + o->a_noattr.inner_no_attr.i1 = 1;
> + /* 0:1 */
> + o->a_attr.inner_no_attr.i1 = 2;
> + /* 0:0 */
> + o->a_noattr.inner_attr.i1 = 3;
> + /* 0:4:0:0 */
> + o_attr->a_attr.inner_attr.i1 = 4;
> + /* 0:5 0:0 */
> + o_attr->a_noattr.inner_attr.i1 = 5;
> +
> + /* This would still force everything as being attributed, although none of
> + the structs has the attribute. */
> + /* 0:5:2 0:0 */
> + __builtin_preserve_access_index (({ o->a_noattr.inner_p->i1 = 20; }));
> +
> + /* 0:2 */
> + struct inner_noattr d = o_attr->inner_no_attr;
> + use_value(d.i2);
> +}
> +
> +
> +extern void use(void *);
> +
> +struct udphdr {
> + int source;
> + int dest;
> + int len;
> + int check;
> +} __attribute__((preserve_access_index));
> +
> +union l4hdr {
> + struct udphdr udp;
> + int c;
> +};
> +
> +struct v4hdr {
> + int a;
> + union l4hdr l4hdr;
> + int b;
> +};
> +
> +void gimple_loop_error(void)
> +{
> + struct v4hdr h_outer;
> + h_outer.l4hdr.udp.source = 1024;
> + use(&h_outer.l4hdr.udp.source);
> +}
> +
> +/* { dg-final { scan-assembler-times "ascii \"0: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 "ascii \"0:2.0\"\[\t
> \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:5.0\"\[\t
> \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:4:0:0.0\"\[\t
> \]+\[^\n\]*btf_aux_string" 1 } } */
> +/* { dg-final { scan-assembler-times "ascii \"0:5:2.0\"\[\t
> \]+\[^\n\]*btf_aux_string" 1 } } */
> +
> +/* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:0\"\\)" 4 } } */
> +/* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:1\"\\)" 3 } } */
> +/* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:2\"\\)" 2 } } */
> +/* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:5\"\\)" 1 } } */
> +/* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:4:0:0\"\\)" 1 }
> } */
> +/* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:5:2\"\\)" 1 } }
> */
> +/* { dg-final { scan-assembler-times "bpfcr_type \\(struct inner_attr\\)" 1
> } } */
> +/* { dg-final { scan-assembler-times "bpfcr_type \\(struct inner_attr1\\)" 3
> } } */
> +/* { dg-final { scan-assembler-times "bpfcr_type \\(struct outer_attr\\)" 4
> } } */
> +/* { dg-final { scan-assembler-times "bpfcr_type \\(union A_attr\\)" 1 } } */
> +
> +
> diff --git a/gcc/testsuite/gcc.target/bpf/core-attr-calls.c
> b/gcc/testsuite/gcc.target/bpf/core-attr-calls.c
> index 87290c5c2116..27b08af1bb79 100644
> --- a/gcc/testsuite/gcc.target/bpf/core-attr-calls.c
> +++ b/gcc/testsuite/gcc.target/bpf/core-attr-calls.c
> @@ -37,13 +37,13 @@ func (struct T *t, int i)
> /* 0:3 */
> get_other_u(t->ptr_u)->c = 43;
>
> - /* 0:2:1 */
> + /* 0:2 */
> get_other_v(&t->u.v)->d = 44;
> }
>
> /* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:3\"\\)" 2 } } */
> /* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:0\"\\)" 1 } } */
> -/* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:2:1\"\\)" 1 } }
> */
> +/* { dg-final { scan-assembler-times "bpfcr_astr_off \\(\"0:2\"\\)" 1 } } */
> /* { dg-final { scan-assembler-times "bpfcr_type \\(struct T\\)" 3 } } */
> /* { dg-final { scan-assembler-times "bpfcr_type \\(struct U\\)" 1 } } */
>