On 10/3/26 4:59 AM, Vineet Gupta wrote:
Hi David,

Apologies for the tardy response, but better late than never, and now might be 
good time since we are in the same room.


On 7/6/26 6:34 PM, David Faust wrote:
Hi Vineet,

On 6/29/26 11:19, Vineet Gupta wrote:
Currently btf_type_tag is only allowed on pointer types. Emil wanted
this to be be attachable to typedefs too (his original request was for
derived types, but that is next step/patch).

e.g.
     typedef unsigned int __attribute__((btf_type_tag ("td_tag"))) uint_t;
     uint_t u;

with -gbtf produced
     BTF_KIND_TYPEDEF 'uint_t' -> BTF_KIND_INT 'unsigned int'

losing the tag.

Turns out this was already supported by the C front end and recorded
correctly in DWARF as well (DW_TAG_GNU_annotation / DW_AT_GNU_annotation),
but was silently dropped when generating BTF because handle_ctf_type_tags()
was invoked solely from gen_ctf_pointer_type().

Yes, I actually did this on purpose because at the time the kernel folks
were only concerned with type tags where pointers were involved...
An earlier version of the series that was eventually applied handled
type-tags in more places.  But there is no way in BTF to represent a
type-tag on e.g. a base int type that is not pointed-to by anything.


Handle type tags in gen_ctf_typedef() too, mirroring the pointer handling,
so that a BTF_KIND_TYPE_TAG record is inserted into the chain between the
typedef and its underlying type:

     BTF_KIND_TYPEDEF 'uint_t' -> BTF_KIND_TYPE_TAG 'td_tag'
                               -> BTF_KIND_INT 'unsigned int'

There are two places a tag can be attached to a typedef, both handled here:

   - A tag supplied in the typedef definition annotates the underlying type,
     so the DW_AT_GNU_annotation is found on the DW_AT_type target of the
     typedef DIE (innermost tags).  A pointer target is skipped, as pointers
     already consume their own type tags in gen_ctf_pointer_type(), otherwise
     the tag would be emitted twice.

   - A tag supplied on a use of an existing typedef is attached to a cloned
     typedef DIE (see modified_type_die), so the annotation is found on the
     typedef DIE itself (outermost tags).
So if I understand correctly, this means that both:

   typedef unsigned int __attribute__((btf_type_tag ("some-tag"))) uint_t;
   uint_t my_uint;

and

   typedef unsigned int uint_t;
   uint_t __attribute__((btf_type_tag ("some-tag"))) my_uint;

shall produce exactly the same BTF, namely:

   VAR 'my_uint' -> TYPEDEF 'uint_t' -> TYPE_TAG 'some-tag' -> INT 'unsigned 
int'

Right?

yes.

And if a given typedef has multiple uses with distinct tags we get e.g.

   typedef unsigned int uint_t;
   uint_t __attribute__((btf_type_tag ("tag1"))) uint_A;
   uint_t __attribute__((btf_type_tag ("tag2"))) uint_B;

   VAR 'uint_A' -> TYPEDEF 'uint_t' -> TYPE_TAG 'tag1' -> INT 'unsigned int'
   VAR 'uint_B' -> TYPEDEF 'uint_t' -> TYPE_TAG 'tag2' -> INT 'unsigned int'

Yes, each gets a uniq typedef record, so there are two uint_t records.

And that works fo rbase type only. With aggregate types, there is a bug in this 
posted version.
Consider this:

     struct node { int v; };
     typedef struct node __attribute__((btf_type_tag("A"))) a_t;
     typedef struct node __attribute__((btf_type_tag("B"))) b_t;
     typedef struct node untagged_t;

emits BTF_KIND_TYPE_TAG 'B' three times, once per typedef, and loses "A" entirely.  In DWARF the 
DW_AT_GNU_annotation lands on the shared DW_TAG_structure_type for "node", so the struct itself 
reads as tagged for every consumer, and "A"'s annotation DIE is left unreferenced.

The cause is that all variants of an aggregate share one DIE, so modified_type_die's 
"make a DIE for the type without the tag, then annotate it" -- which is right 
for a pointer, where each attribute variant has a DIE of its own. And since 
gen_btf_tag_dies replaces DW_AT_GNU_annotation rather than adding to it, a second tagged 
variant displaces the first.

The next version (v2: not posted yet) records such a tag on the typedef DIE 
instead, in gen_typedef_die, since the typedef is the only thing that names the 
variant.  A tag written at the aggregate's own definition is on the main 
variant, still belongs on the aggregate's DIE, and is left alone.

This approach for v2 makes good sense to me.


So, now that we will represent type_tag in BTF on more than just pointer
types, we really have two distinct representations of type_tag in BTF:
- Pointer/typedef: type_tag succeeds modified type in the chain
- Non-pointer/typedef: type_tag precedes modified type in the chain

BUT, at the same time in BTF we only emit type_tag on non-pointer/typedef
types when they are refered to by a typedef.

Is there a particular reason why only supporting type_tag on the underlying
type when a typedef is involved? Why not support e.g.
   int __attribute__((btf_type_tag ("whatever"))) foo;
   VAR 'foo' -> TYPE_TAG 'whatever' -> INT 'int'

Well for one clang also does it that way (and silently ignores this).
And technically again its the shared DIE problem.  A tag on a variant that 
nothing names has no DIE of its own to be recorded on, so v2 drops it rather 
than write it onto a shared one.  So v2 will also drop

     struct foo __attribute__((btf_type_tag ("t"))) x;

which patch 2/3 of this version accepted (and test 6 will be updated 
accordingly).

  Clang behaves the same way -- it supports the tag
on pointers and typedefs and silently drops it on a bare aggregate
variable -- so v2 is at least consistent with it, but "matches clang" is
not much of an argument if the general case is what we actually want.

Three ways to go, as I see it:

  1. Currently proposed in v2.  Record the tag wherever something names the 
variant, drop it elsewhere.  Smallest change; does not give you the general 
case you are asking for.

By "names the variant" you mean when there is a typedef?
If so, IMO this is what we want.

i.e.
- Variants of a given struct type with different attributes
  (including type-tags) should not be allowed, and should give the
  same "ignoring attributes applied..." warning as it does now.
- Distinct typedefs with different type-tags which refer to the same
  underlying type are ok.

Also, it's not so much that I am personally invested in the general
case it just seems to me that at some point it may be simpler (in design,
implementation and to understand) to handle it that way rather than
handling multiple special cases.


  2. Clone the aggregate DIE per tagged variant.  Gives the general case. Costs 
duplicated struct DIEs, and consumers then see two "struct foo"s.  There is 
precedent: modified_type_die already clones for a typedef used with extra tags, which is 
how the multi-use case above works.

I don't think we want to clone the aggregate DIE.  This is in a
similar vein to the reason for the existing "ignoring attributes
applied to struct after its definition".

Actually I think the precedent you point to actually makes the case for
doing (1), since the it is the typedef DIE that is cloned in order to
handle cases where the same typedef is used with distinct type-tags.



  3. Make the annotation a link in the type chain rather than a side attribute, so DWARF 
mirrors what BTF wants: variable -> annotation -> struct.  This is the one that makes 
your "two distinct representations" observation go away, since the tag would always 
precede the type.  It changes existing DWARF representation so likely to not fly.


I lean towards 3 being the right long-term answer and 2 being the pragmatic 
one, but I would rather have your view before redoing it, since 1 is cheap to 
abandon and the other two are not.

The (3) was always the ideal format but it's unfortunately a no-go for
the simple reason that it completely breaks reading DWARF with type-tags
for any consumer that does not know about DW_TAG_GNU_annotation, since
they cannot proceed down chain past the unknown DIE.

We settled on the attribute approach because it allows unaware consumers
to work fine (just ignore the unknown AT_GNU_annotation) while also
allowing some sharing of the annotation DIEs.

For some more context we discussed the format in 2024 at cauldron/LPC:
https://gcc.gnu.org/wiki/cauldron2024#cauldron2024talks.what_is_new_in_the_bpf_support_in_the_gnu_toolchain
https://lpc.events/event/18/contributions/1924/

So I'm inclined towards (1).


Also one thing to be aware of: with 1 the dropped case is silent. Before patch 2/3 it at 
least warned ("ignoring attributes applied to 'struct foo' after definition").  
I could not find a good place to put a diagnostic back -- at attribute-handling time you 
cannot yet know whether a typedef will name the variant, and warning from dwarf2out would 
make it appear and disappear with -g, which seems worse than saying nothing. Not relevant 
if we pursue 2 or 3.

Is the dropped case you mean:

 struct foo __attribute__((btf_type_tag ("t"))) x;

If so that is unfortunate.  This should definitely warn that the attribute
applied after definition is ignored.

Although... really both btf_type_tag and btf_decl_tag do nothing at all
(and they better not) if no debug information is generated.  They really
are intended to be only a way to inject extra information into the
debug info.  So IMO maybe ok if the warning can only be emitted with -g.


Supporting this in the general case should also support the desired behavior
in the specific case of type_tag applied to the underlying type of a typedef.

I have some objections to the BTF format but I suppose that ship has sailed.
However, at the very least we should make sure this is documented in the
gcc manual as best we can so that users can at least have some idea of
what to expect.  Could you please expand the documentation of btf_type_tag
to make these behaviors clear? Some illustrative examples would probably
also be good.

Definitely makes sense, once we settle on the the approach to be taken.

Thx,
-Vineet



The new code is guarded by btf_debuginfo_p (), so pure CTF output is
unchanged.

    PR debug/125888

gcc/ChangeLog:

    * dwarf2ctf.cc (gen_ctf_typedef): Emit BTF type tags attached to
    the typedef's underlying type or to the typedef DIE itself.
    * doc/extend.texi (btf_type_tag): Document that BTF type tags are
    now emitted for typedefs in addition to pointer types.

gcc/testsuite/ChangeLog:

    * gcc.dg/debug/btf/btf-type-tag-5.c: New test.
    * gcc.dg/debug/btf/btf-type-tag-6.c: New test.
    * gcc.dg/debug/btf/btf-type-tag-7.c: New test.

Signed-off-by: Vineet Gupta <[email protected]>
---
  gcc/doc/extend.texi                           |  6 +--
  gcc/dwarf2ctf.cc                              | 29 ++++++++++++++
  .../gcc.dg/debug/btf/btf-type-tag-5.c         | 39 +++++++++++++++++++
  .../gcc.dg/debug/btf/btf-type-tag-6.c         | 20 ++++++++++
  .../gcc.dg/debug/btf/btf-type-tag-7.c         | 29 ++++++++++++++
  5 files changed, 120 insertions(+), 3 deletions(-)
  create mode 100644 gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-5.c
  create mode 100644 gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-6.c
  create mode 100644 gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-7.c

diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi
index 2e8ec8a0af2c..dec09f599ced 100644
--- a/gcc/doc/extend.texi
+++ b/gcc/doc/extend.texi
@@ -2344,9 +2344,9 @@ when compiled with @option{-gbtf} results in an additional
  associating the string @samp{__user} with the normal @code{BTF_KIND_PTR}
  record for the pointer-to-integer type used in the declaration.
-Note that the BTF format currently only has a representation for type
-tags associated with pointer types.  Type tags on non-pointer types
-may be silently skipped when generating BTF.
+Note that when generating BTF, type tags are currently only emitted for
+type tags associated with pointer types and typedefs.  Type tags on
+other types may be silently skipped when generating BTF.
  @atindex @code{cleanup}
  @cindex cleanup functions
diff --git a/gcc/dwarf2ctf.cc b/gcc/dwarf2ctf.cc
index b9f9e1deeff4..629a48be7ebb 100644
--- a/gcc/dwarf2ctf.cc
+++ b/gcc/dwarf2ctf.cc
@@ -476,6 +476,35 @@ gen_ctf_typedef (ctf_container_ref ctfc, dw_die_ref tdef)
    dtd = gen_ctf_type (ctfc, tdef_type);
+  /* Handle BTF type tags.  Type tags may be attached to a typedef in two
+     ways, both of which insert type tag records into the chain between the
+     typedef and its underlying type:
+      - A btf_type_tag supplied in the typedef definition annotates the
+    underlying type, so the DW_AT_GNU_annotation is found on the DW_AT_type
+    target of the typedef DIE.  These tags are innermost (closest to the
+    underlying type).  A pointer target is skipped here, as pointers
+    consume their own type tags in gen_ctf_pointer_type.
+      - A btf_type_tag supplied on a use of an existing typedef is attached to
+    a cloned typedef DIE (see modified_type_die), so the annotation is
+    found on the typedef DIE itself.  These tags are outermost (closest to
+    the typedef).  */
+  if (btf_debuginfo_p ())
+    {
+      if (tdef_type
+      && dw_get_die_tag (tdef_type) != DW_TAG_pointer_type)
+    {
+      dw_die_ref def_annot = get_AT_ref (tdef_type, DW_AT_GNU_annotation);
+      ctf_dtdef_ref tag_dtd = handle_ctf_type_tags (ctfc, def_annot, dtd);
+      if (tag_dtd)
+        dtd = tag_dtd;
+    }
+
+      dw_die_ref use_annot = get_AT_ref (tdef, DW_AT_GNU_annotation);
+      ctf_dtdef_ref tag_dtd = handle_ctf_type_tags (ctfc, use_annot, dtd);
+      if (tag_dtd)
+    dtd = tag_dtd;
+    }
+
    /* Type de-duplication.
       This is necessary because the ctf for the typedef may have been already
       added due to the gen_ctf_type call above.  */
diff --git a/gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-5.c 
b/gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-5.c
new file mode 100644
index 000000000000..f121db2a42d5
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-5.c
@@ -0,0 +1,39 @@
+/* Test generation of BTF type tags applied to typedefs of non-pointer types.
+
+   Unlike btf-type-tag-3.c, where the type tags reach the typedef via a 
pointer,
+   here the tags annotate the underlying (non-pointer) type directly, or are
+   applied to a use of an existing typedef.  In both cases a BTF_KIND_TYPE_TAG
+   record is inserted into the chain between the typedef and its underlying
+   type.  */
+
+/* { dg-do compile } */
+/* { dg-options "-O0 -gbtf -dA" } */
+
+#define __tag1 __attribute__((btf_type_tag("1")))
+#define __tag2 __attribute__((btf_type_tag("2")))
+
+/* Single type tag in the typedef definition.
+   var("a") -> typedef("td1") -> type_tag("1") -> int  */
+typedef int __tag1 td1;
+td1 a;
+
+/* Multiple type tags in the typedef definition.
+   var("b") -> typedef("td2") -> type_tag("2") -> type_tag("1") -> int  */
+typedef int __tag1 __tag2 td2;
+td2 b;
+
+/* Type tag applied to a use of an existing (untagged) typedef.
+   var("c") -> typedef("base") -> type_tag("1") -> int  */
+typedef int base;
+base __tag1 c;
+
+/* { dg-final { scan-assembler-times " BTF_KIND_VAR 
'a'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPEDEF 'td1'\\)" 1 } } */
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPEDEF 
'td1'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPE_TAG '1'\\)" 1 } } */
+
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPEDEF 
'td2'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPE_TAG '2'\\)" 1 } } */
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPE_TAG 
'2'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPE_TAG '1'\\)" 1 } } */
+
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPEDEF 
'base'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPE_TAG '1'\\)" 1 } } */
+
+/* In total there should be three TYPE_TAG '1' chains feeding an int.  */
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPE_TAG 
'1'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_INT 'int'\\)" 3 } } */
diff --git a/gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-6.c 
b/gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-6.c
new file mode 100644
index 000000000000..73af0ead4dba
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-6.c
@@ -0,0 +1,20 @@
+/* Test generation of BTF type tags applied to a typedef of a struct type.
+
+   When a btf_type_tag is supplied in the definition of a typedef whose
+   underlying type is a struct, the attribute is recorded on the struct type,
+   i.e. on the DW_AT_type target of the typedef DIE.  A BTF_KIND_TYPE_TAG
+   record is therefore inserted into the chain between the typedef and the
+   struct:
+     var("s") -> typedef("S_t") -> type_tag("t1") -> struct("S")  */
+
+/* { dg-do compile } */
+/* { dg-options "-O0 -gbtf -dA" } */
+
+#define __tag1 __attribute__((btf_type_tag("t1")))
+
+typedef struct S { int x; } __tag1 S_t;
+S_t s;
+
+/* { dg-final { scan-assembler-times " BTF_KIND_VAR 
's'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPEDEF 'S_t'\\)" 1 } } */
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPEDEF 
'S_t'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPE_TAG 't1'\\)" 1 } } */
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPE_TAG 
't1'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_STRUCT 'S'\\)" 1 } } */
diff --git a/gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-7.c 
b/gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-7.c
new file mode 100644
index 000000000000..5fc1a61407be
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/debug/btf/btf-type-tag-7.c
@@ -0,0 +1,29 @@
+/* Test generation of BTF type tags on a function whose return type and
+   arguments are tagged typedefs.  PR/125888.
+
+     foo: FUNC_PROTO -> ret  typedef("PERCPU_T") -> type_tag("percpu") -> int
+                     -> args typedef("CPU_T")    -> type_tag("cpu")    -> int  
*/
+
+/* { dg-do compile } */
+/* { dg-options "-O0 -gbtf -dA" } */
+
+#define __tag1 __attribute__((btf_type_tag("percpu")))
+#define __tag2 __attribute__((btf_type_tag("cpu")))
+
+typedef int __tag1 PERCPU_T;
+typedef int __tag2 CPU_T;
+
+PERCPU_T foo (CPU_T a, CPU_T b)
+{
+  PERCPU_T c = a + b;
+  return c;
+}
+
+/* Return is the tagged typedef PERCPU_T.  */
+/* { dg-final { scan-assembler-times " BTF_KIND_FUNC_PROTO 
''(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPEDEF 'PERCPU_T'\\)" 1 } } */
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPEDEF 
'PERCPU_T'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPE_TAG 'percpu'\\)" 1 } } */
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPE_TAG 
'percpu'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_INT 'int'\\)" 1 } } */
+
+/* Arguments are the tagged typedef CPU_T.  */
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPEDEF 
'CPU_T'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_TYPE_TAG 'cpu'\\)" 1 } } */
+/* { dg-final { scan-assembler-times " BTF_KIND_TYPE_TAG 
'cpu'(\[\\r\\n\]+\[^\\r\\n\]*){2}\\(BTF_KIND_INT 'int'\\)" 1 } } */


Reply via email to