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?
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'
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'
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.
>
> 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 } } */