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().

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.

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.

 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.

 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.

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.

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