Repository navigation
Description
This is a tracking issue for #![feature(btf_tags)].
The clang frontend implements two attributes, btf_decl_tag("foo") and
btf_type_tag("bar"). They are used mostly by the Linux kernel and BPF
programs. The BPF verifier reads them to check memory accesses, because plain
C types can't express some facts the verifier needs, such as "this pointer
points to user memory", "this pointer is RCU-protected", or "this argument of
a global subprogram is a context pointer".
Each attribute attaches a free-form string to a declaration or a type. Clang
carries that string into DWARF, and from there it goes into BTF, either
through the BPF backend or through pahole for the kernel. The attributes were
first added in these commits:
1b194ef1ab3b [Clang] add btf_tag attribute (later renamed to btf_decl_tag)
737e4216c537 [Attr] support btf_type_tag attribute
Later commits added more improvements and fixes.
Clang accepts both attributes on every target, not only BPF. This lets the
kernel and other code that shares headers with BPF programs use them on any
architecture, even though BPF is the main user.
Rust can also target BPF, so it will need equivalent support for
btf_decl_tag and btf_type_tag. Without it, Rust BPF programs can't give
the verifier the same information C programs can.
Public API
The following is an example.
#![feature(btf_tags)]
#[btf_decl_tag("s_tag")]
pub struct S {
#[btf_decl_tag("f_tag")]
pub a: u32,
#[btf_type_tag("user")]
#[btf_type_tag("rcu")]
pub p: *const u64,
pub plain: *const u64,
}
For a BPF target, this gives the following BTF (bpftool btf dump file,
unrelated entries removed):
[11] STRUCT 'S' size=24 vlen=3
'p' type_id=15 bits_offset=0
'plain' type_id=17 bits_offset=64
'a' type_id=3 bits_offset=128
[12] DECL_TAG 's_tag' type_id=11 component_idx=-1
[13] TYPE_TAG 'user' type_id=16
[14] TYPE_TAG 'rcu' type_id=13
[15] PTR '(anon)' type_id=14
[16] INT 'u64' size=8 bits_offset=0 nr_bits=64 encoding=(none)
[17] PTR '(anon)' type_id=16
[18] DECL_TAG 'f_tag' type_id=11 component_idx=2
The tags have the same structure as the equivalent C code compiled by Clang.
component_idx is the member's index in the emitted layout. Without
#[repr(C)], rustc may reorder fields (here a moved last), so structs shared
with the kernel or with C should use #[repr(C)].
Both attributes take a single string literal and can be repeated. As in
Clang, stacked type tags are kept in source order, and exact duplicate decl
tags are emitted once. Both attributes are accepted on every target and
produce DW_TAG_LLVM_annotation in DWARF; the BPF backend turns them into BTF.
Like Clang, the attributes can be used on several kinds of declarations.
btf_decl_tag tags a declaration:
| Rust | BTF | Clang equivalent |
|---|---|---|
static |
DECL_TAG on the VAR, component_idx=-1 |
global variable |
fn, inherent method, trait-impl method, trait method with a body |
DECL_TAG on the FUNC, component_idx=-1 |
function definition |
| function parameter | DECL_TAG on the FUNC, component_idx=<argument index> |
parameter |
struct, union |
DECL_TAG on the STRUCT/UNION, component_idx=-1 |
struct, union |
| struct or union field | DECL_TAG on the STRUCT/UNION, component_idx=<member index> |
field |
foreign static |
accepted, no effect | Clang also drops decl tags on extern variables |
Enums and enum variant fields are rejected; Clang doesn't allow enums either.
btf_type_tag tags the pointee of the declared pointer type:
| Rust | BTF | Clang equivalent |
|---|---|---|
static |
VAR -> PTR -> TYPE_TAG -> T |
global variable |
| struct or union field | member type PTR -> TYPE_TAG -> T |
field |
| function parameter | FUNC_PROTO parameter PTR -> TYPE_TAG -> T |
parameter |
fn or method with a body |
FUNC_PROTO return type PTR -> TYPE_TAG -> T |
T __tag *f(...) |
foreign static |
accepted, no effect yet | extern variable |
On a function, btf_type_tag applies to the returned pointer. This mirrors
the other declarations: btf_decl_tag tags the declaration itself, and
btf_type_tag tags the pointer type that the declaration introduces.
#![feature(btf_tags)]
#[btf_decl_tag("func_tag")]
#[btf_type_tag("arena")]
#[no_mangle]
pub fn f(
#[btf_decl_tag("arg_tag")]
#[btf_type_tag("user")]
p: *mut u32,
) -> *mut u32 {
p
}[1] TYPE_TAG 'user' type_id=3
[2] PTR '(anon)' type_id=1
[3] INT 'u32' size=4 bits_offset=0 nr_bits=32 encoding=(none)
[4] FUNC_PROTO '(anon)' ret_type_id=6 vlen=1
'p' type_id=2
[5] TYPE_TAG 'arena' type_id=3
[6] PTR '(anon)' type_id=5
[7] FUNC 'f' type_id=4 linkage=global
[8] DECL_TAG 'arg_tag' type_id=7 component_idx=0
[9] DECL_TAG 'func_tag' type_id=7 component_idx=-1
A type tag only takes effect on the outermost thin pointer (*const T,
*mut T, &T, &mut T). On any other type, including NonNull<T>,
Option<&T>, Box<T> and wide pointers, it is silently ignored, as Clang
ignores type tags that aren't on a pointer's pointee.
Steps
- Create the feature gate
- Implement the feature
- Adjust documentation (see instructions on rustc-dev-guide)
- Style updates for any new syntax (nightly-style-procedure)
- Style team decision on new formatting
- Formatting for new syntax has been added to the Style Guide
- (non-blocking) Formatting has been implemented in
rustfmt
- Stabilization PR (see instructions on rustc-dev-guide)
Unresolved Questions
None so far.
Implementation history
None so far.
Activity
yonghong-song commentedon Nov 3, 2026
@rustbot label +T-compiler +A-debuginfo +S-tracking-unimplemented