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