# \[Pre-RFC\] BTF relocations

**URL:** <https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161>\
**Category:** language design\
**Created:** [April 9, 2026, 10:21pm UTC](https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161 "2026-04-09T22:21:07Z")\
**Posts on this page:** 7\
**Page:** 2

<div class="post-metadata">

**Author:** ![vad](https://avatars.discourse-cdn.com/v4/letter/v/a698b9/32.png) [@vad](https://internals.rust-lang.org/u/vad)\
**Post date:** [April 27, 2026, 7:13pm UTC](https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161/21 "2026-04-27T19:13:34Z")

</div>

> [@anakryiko](#):
>
> It's actually quite common to have `task->se.vruntime` accesses in C BPF programs, so supporting this from the get go would be essential to make this useful for real-world applications.

Right. I'm not saying that accesses to structures nested by value are uncommon. What I'm saying is that we could roll out experimental nightly-only support for BTF relocations, behind a feature gate, without waiting for `const Sized` / sized hierarchy to be fully implemented.

That said, I agree that sized hierarchy and support for accesses nested by value would definitely be needed to expose the feature without a gate and on stable Rust.

> [@anakryiko](#):
>
> Re: naming. "preserve\_access\_index" naming we use in C is quite opaque and not very meaningful. Everyone just remembered and use it without asking questions, but I always felt like having it named as "btf\_relocatable" or something along those lines ("relocatable" is the key here, IMO) would be best. Just my 2 cents on naming.

The more I think about it, the more I'm leaning towards neutral naming not tied to BTF, which could also suit Swift ABI or any other kind of field access relocation. My current implementation touches the field projection logic (`compiler/rustc_codegen_ssa/src/mir/place.rs`) in MIR in the following way:

> <https://github.com/vadorovsky/rust/commit/30cc9af56237a0f88b2c13b4b80e994b50717f24#diff-160634de1c336f2cf325ff95b312777326f1ab29fec9b9b21d5ee9aae215ecf5>
>
> BTF, the BPF Type Format, encodes type information for both the running
> Linux ke…rnel and compiled eBPF programs. An eBPF object can carry
> relocation records that describe field and aggregate accesses in terms
> of BTF types instead of fixed offsets; at load time, the loader compares
> the program's BTF with the kernel's BTF and rewrites those accesses to
> the correct offsets for the target kernel. This mechanism is often
> referred to as "CO-RE relocations" or "BTF relocations".
> 
> LLVM allows to emit such relocations with the following intrinsics:
> 
> \* \`@llvm.preserve.array.access.index\`
> \* \`@llvm.preserve.struct.access.index\`
> \* \`@llvm.preserve.union.access.index\`
> 
> When these intrinsics are preserved through codegen, LLVM emits the
> corresponding relocation records into the \`BTF.ext\` ELF section.
> 
> clang exposes this functionality through
> \`\_\_attribute\_\_((preserve\_access\_index))\` that lowers to one of the
> \`@llvm.preserve.\*.access.index\` intrinsics, depending on the annotated
> type.
> 
> Add corresponding support in Rust with \`#\[relocatable\]\` attribute,
> hidden behind \`relocatable\_types\` feature gate

Choosing a target-specific name (`bpf_*`/`btf_*`) would mean poisoning this logic with target-specific concepts. It would also require add an another boolean if an another relocation mechanism (e.g. Swift ABI) is added.

Keeping the name neutral keeps MIR target-agnostic and adds only one boolean. Therefore, I would rather call it "relocatable".

> [@anakryiko](#):
>
> Oh, and I don't know if this is in scope or not, but it would be great to be able to express all of the [libbpf/src/bpf\_core\_read.h at master · libbpf/libbpf · GitHub](https://github.com/libbpf/libbpf/blob/master/src/bpf_core_read.h#L175-L294) in Rust through whatever is the most natural way. Checks for type/field existence and enum value retrieval is critical for some real-world applications, not just relocatable field accesses.

Great suggestion. We could add the following Rust intrinsics, which would lower to the following LLVM intrinsics.

With neutral naming, this could eventually be implemented for other relocation mechanisms (e.g. Swift ABI):

- `relocatable_type_id` -\> `llvm.bpf.btf.type.id`
- `relocatable_compare` -\> `llvm.bpf.compare`
- `relocatable_getelementptr_and_load` -\> `llvm.bpf.getelementptr.and.load`
- `relocatable_getelementptr_and_store` -\> `llvm.bpf.getelementptr.and.store`
- `relocatable_preserve_enum_value` -\> `llvm.bpf.preserve.enum.value`
- `relocatable_preserve_field_info` -\> `llvm.bpf.preserve.field.info`
- `relocatable_preserve_type_info` -\> `llvm.bpf.preserve.type.info`

The following intrinsics, which are of no use outside the BPF context, could carry BPF-specific names:

- `bpf_load_byte` -\> `llvm.bpf.load.byte`
- `bpf_load_half` -\> `llvm.bpf.load.half`
- `bpf_load_word` -\> `llvm.bpf.load.word`

Furthermore, once size hierarchy is implemented, the `offset_of!` macro could lower to `relocatable_preserve_field_info` for relocatable types.

---

<div class="post-metadata">

**Author:** ![kornel](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/kornel/32/2711_2.png) [@kornel](https://internals.rust-lang.org/u/kornel)\
**Post date:** [April 30, 2026, 11:50pm UTC](https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161/22 "2026-04-30T23:50:25Z")

</div>

Does it have to be a feature of structs and arrays?

Could it instead be expressed as a macro or intrinsic function that gives an offset to use with `ptr::read`? (which could be sufficient to make getters and setters for something struct-like that isn't an actual struct)

Or maybe it could be implemented as getters and setters on extern opaque types?

It wouldn't be as neat as 1st class structs that work with literals and pattern matching, but might be sufficient to interact with kernel structs treating them as an API.

---

<div class="post-metadata">

**Author:** ![cg909](https://avatars.discourse-cdn.com/v4/letter/c/90ced4/32.png) [@cg909](https://internals.rust-lang.org/u/cg909)\
**Post date:** [May 1, 2026, 7:11pm UTC](https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161/23 "2026-05-01T19:11:50Z")

</div>

I think the current implementation in LLVM makes it quite hard to implement with anything else than real structs:

Using `@llvm.preserve.*.access.index` intrinsics instead of `getelementptr` in LLVM has two effects:

- It ensures the offset stays literally in the code and is not transformed by optimization passes,
- and it allows debuginfo metadata with type information to be attached to the access.

The BPF codegen backend then writes the used type names and field names into a symbol table and generates BTF relocations that connect each such offset in the code with the symbolic names of the accessed type and field and type layout information.

With a macro or intrinsic function, the compiler would still need to generate type debug metadata, so that the BPF codegen backend can find the names and check the types for compatibility. It's probably way easier to reuse the infrastructure that already generates debuginfo metadata for Rust types than to reimplement part of it just for BPF.

---

<div class="post-metadata">

**Author:** ![anakryiko](https://sea2.discourse-cdn.com/flex002/user_avatar/internals.rust-lang.org/anakryiko/32/14270_2.png) [@anakryiko](https://internals.rust-lang.org/u/anakryiko)\
**Post date:** [May 2, 2026, 11:21am UTC](https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161/24 "2026-05-02T11:21:00Z")

</div>

re: "relocatable". I would definitely not call it "bpf\_relocatable" (target specific), but between "relocatable" and "btf\_relocatable", BTF is the means through which relocation information is captured (BTF types/fields) and expressed (.BTF.ext section), so btf\_ prefix seems appropriate. For this Swift use case you were talking about, would relocation information be expressed differently somehow?

In the end it doesn't matter all that much, but too-generic "relocatable" has a potential to be, well, too generic and thus confusing. But just my 2 cents.

re: instrinsics, make sense and seems like a good idea. will the "does it exist" primitive be expressed as an extra argument to "preserve\_xxx\_info" or it would require its own set of intrinsics?

---

<div class="post-metadata">

**Author:** ![vad](https://avatars.discourse-cdn.com/v4/letter/v/a698b9/32.png) [@vad](https://internals.rust-lang.org/u/vad)\
**Post date:** [May 20, 2026, 8:24pm UTC](https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161/25 "2026-05-20T20:24:19Z")

</div>

Sorry for the silence. I took some time to re-think the entire approach and at the end, together with [tamird](https://github.com/tamird), we came up with way simpler solution that will not require any changes to the field projection, and aligns with what @kornel proposes.

> [@kornel](#):
>
> Does it have to be a feature of structs and arrays?
> 
> Could it instead be expressed as a macro or intrinsic function that gives an offset to use with `ptr::read`? (which could be sufficient to make getters and setters for something struct-like that isn't an actual struct)
> 
> Or maybe it could be implemented as getters and setters on extern opaque types?
> 
> It wouldn't be as neat as 1st class structs that work with literals and pattern matching, but might be sufficient to interact with kernel structs treating them as an API.

Yes, after experimenting with that idea, I'm convinced this might be a good direction.

For reference, clang has the following intrinsic that allows to request information about BTF-relocatable type:

```cc
uint32_t __builtin_bpf_preserve_field_info(void *member_ptr, uint64_t info_kind);

```

where the `info_kind` can be:

- `FIELD_EXISTENCE` (`0`) - returns a `0` or `1` indicating whether a field exists.
- `FIELD_OFFSET` (`1`) - returns an offset of the field, and emits a BTF relocation, so the loader can patch the offset.
- `FIELD_SIZE` (`2`) - returns a size of the field.

We could expose the same functionality in Rust through three intrinsics (each of them having a return type appropriate in Rust's context):

```rust
pub fn relocatable_field_byte_offset<T: PointeeSized>(variant: usize, field: usize) -> usize
pub fn relocatable_field_byte_size<T: PointeeSized>(variant: usize, field: usize) -> usize
pub fn relocatable_field_exists<T: PointeeSized>(variant: usize, field: usize) -> bool

```

And... that would be it on Rust compiler's side. No changes to MIR and the field projection. No direct exposure of `preserve_access` LLVM intrinsics (at least to the users).

Why?

With BTF-relocatable types, we are not sure whether a field exists during a runtime. Therefore, a field access does not provide any guarantees of soundess. That goes against Rust's guarantees of safety.

What goes in line with Rust's safety guarantees, however, is a way to access a relocatable field only if we meet certain circumstances - that the field exists, and that the size of the field is as we expect.

The mentioned intrinsics would provide building blocks for safe abstraction for accessing relocatable fields. To be precise - such safe abstraction could be provided outside of Rust standard library, it could be done in [Aya](https://aya-rs.dev/) or a dedicated third-party library. The first step towards safety would be the following function with an error type:

```rust
#[derive(Debug, Eq, PartialEq)]
pub enum RelocatableFieldAccessError {
    SizeMismatch { expected: usize, actual: usize },
    ProbeRead(i32),
}

#[inline(always)]
pub fn relocatable_field_read<T>(ptr: *const T) -> Result<Option<T>, RelocatableFieldAccessError> {
    if relocatable_field_exists(ptr) {
        let expected = core::mem::size_of::<T>();
        let actual = relocatable_field_byte_size(ptr);
        if expected == actual {
            let offset = relocatable_field_byte_offset(ptr);
            // SAFETY: We trust LLVM to emit a correct BTF relocation for
            // that offset, and we trust BPF loader libraries to patch it
            // accordingly.
            let field = unsafe {
                bpf_probe_read_kernel(&*ptr.add(offset))
                    .map_err(|code| RelocatableFieldAccessError::ProbeRead(code))?
            };
            Ok(Some(field))
        } else {
            Err(RelocatableFieldAccessError::SizeMismatch { expected, actual })
        }
    } else {
        Ok(None)
    }
}

```

And then a BTF-relocatable structure with getters that use the function:

```rust
#[repr(C)]
struct task_struct {
    pid: i32,
    tgid: i32,
}

impl task_struct {
    pub fn pid(&self) -> Result<Option<i32>, RelocatableFieldAccessError> {
        relocatable_field_read(::core::ptr::addr_of!(self.pid))
    }

    pub fn tgid(&self) -> Result<Option<i32>, RelocatableFieldAccessError> {
        relocatable_field_read(::core::ptr::addr_of!(self.pid))
    }
}

```

Generation of such getters could be hidden behind a proc macro.

That would also solve the problem of different kernel versions removing or renaming fields, without having to rely on version suffixes mechanism from clang that was described by @anakryiko and mentioned by @Jules-Bertholet.

The only point from @kornel's proposition which I'm unsure about is using extern opaque types. We would need some way of declaring its layout and letting Rust (and a backend, like LLVM) to generate the debug info for it. The weakness of the code example I provided above is that the `task_struct` type is a sized struct. An open question is how can it be marked as `!Sized` (or const Sized), perhaps that brings us back to the `#[repr(btf)]` idea.

Updated experimental branch: [GitHub - vadorovsky/rust at btf-relocations-v2 · GitHub](https://github.com/vadorovsky/rust/tree/btf-relocations-v2)

---

<div class="post-metadata">

**Author:** ![vad](https://avatars.discourse-cdn.com/v4/letter/v/a698b9/32.png) [@vad](https://internals.rust-lang.org/u/vad)\
**Post date:** [May 31, 2026, 8:29am UTC](https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161/26 "2026-05-31T08:29:28Z")

</div>

> [@anakryiko](#):
>
> re: "relocatable". I would definitely not call it "bpf\_relocatable" (target specific), but between "relocatable" and "btf\_relocatable", BTF is the means through which relocation information is captured (BTF types/fields) and expressed (.BTF.ext section), so btf\_ prefix seems appropriate. For this Swift use case you were talking about, would relocation information be expressed differently somehow?

After giving it more thought, I think that `btf_` prefix definitely makes sense for intrinsics. I don't expect intrinsics to be shared between different types of field relocations (e.g. BTF, Swift).

So the intrinsics I proposed above could be called:

```rust
pub fn btf_field_byte_offset<T: PointeeSized>(variant: usize, field: usize) -> usize
pub fn btf_field_byte_size<T: PointeeSized>(variant: usize, field: usize) -> usize
pub fn btf_field_exists<T: PointeeSized>(variant: usize, field: usize) -> bool

```

Field projection is where a more unified design could make more sense though.

> [@vad](#):
>
> With BTF-relocatable types, we are not sure whether a field exists during a runtime. Therefore, a field access does not provide any guarantees of soundess. That goes against Rust's guarantees of safety.

I take this point back - BPF verifier will always reject programs with field accesses to non-existing fields, so the field projection is still safe. That said, I think it makes sense to start only with intrinsics now, and leave the field projection topic for future.

Given that there are no objections to the feature itself, I went ahead and submitted an RFC, trimmed down only to intrinsics, on GitHub:

> <https://github.com/rust-lang/rfcs/pull/3966>
>
> The RFC proposes the \`btf\_relocatable\_types\` feature gate, that provides a \`#\[re…pr(btf)\]\` representation and low-level field-info intrinsics that emit \[BTF (BPF TypeFormat)\]\[btf\] \[CO-RE (Compile Once, Run Everywhere)\]\[co-re\] relocations.
> 
> It also documents the relationship to \`offset\_of!\`, ordinary field projection and LLVM BPF lowering.
> 
> This feature was originally proposed as \[pre-RFC\]\[pre-rfc\].
> 
> \[btf\]: https://docs.kernel.org/bpf/btf.html
> \[co-re\]: https://nakryiko.com/posts/bpf-portability-and-co-re/
> \[pre-rfc\]: https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161/25
> 
> 
> \[Rendered\](https://github.com/vadorovsky/rfcs/blob/btf-relocations/text/0000-btf-relocations.md)

---

<div class="post-metadata">

**Author:** ![vad](https://avatars.discourse-cdn.com/v4/letter/v/a698b9/32.png) [@vad](https://internals.rust-lang.org/u/vad)\
**Post date:** [June 1, 2026, 11:33am UTC](https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161/27 "2026-06-01T11:33:28Z")

</div>

I've made a small change to the [RFC](https://github.com/rust-lang/rfcs/pull/3966) since yesterday - I have realized that intrinsics taking `field: usize` aren't a good design for nested field access, and would require multiple subsequent calls, therefore lowering to more LLVM intrinsic calls than necessary. It also requires the caller to keep track of field indices, which seems unnecessary, given that the compiler has the information about the type layout already.

What works better in that case are builtin-syntax wrapper macros, like:

```rust
# library/core/src/btf.rs

#[allow_internal_unstable(builtin_syntax)]
pub macro field_byte_offset($Container:ty, $($fields:expr)+ $(,)?) {
    builtin # btf_field_byte_offset($Container, $($fields)+)
}

#[allow_internal_unstable(builtin_syntax)]
pub macro field_exists($Container:ty, $($fields:expr)+ $(,)?) {
    builtin # btf_field_exists($Container, $($fields)+)
}

```

Which then could be used by the user the following way:

```rust
#![feature(btf_relocations)]

#[repr(Btf)]
pub struct load_weight {
    pub weight: usize,
}

#[repr(Btf)]
pub struct sched_entity {
    pub load: load_weight,
    pub vruntime: u64,
}

#[repr(Btf)]
pub struct task_struct {
    pub se: sched_entity,
}

impl task_struct {
    pub fn sched_vruntime(&self) -> Option<&u64> {
        if core::btf::field_exists!(task_struct, se.vruntime) {
            let offset = core::btf::field_byte_offset!(task_struct, se.vruntime);
            let ptr = self as *const task_struct as *const u8;

            // SAFETY: the BTF relocation says that `se.vruntime` exists in the
            // target layout, and the returned offset is relative to `task_struct`.
            Some(unsafe { &*(ptr.add(offset) as *const u64) })
        } else {
            None
        }
    }

    pub fn sched_load_weight(&self) -> Option<&usize> {
        if core::btf::field_exists!(task_struct, se.load.weight) {
            let offset = core::btf::field_byte_offset!(task_struct, se.load.weight);
            let ptr = self as *const task_struct as *const u8;

            // SAFETY: the BTF relocation says that `se.load.weight` exists in the
            // target layout, and the returned offset is relative to `task_struct`.
            Some(unsafe { &*(ptr.add(offset) as *const usize) })
        } else {
            None
        }
    }
}

```

I would appreciate the feedback on the RFC. I'm also going to provide the implementation the next days.

[Previous page](https://internals.rust-lang.org/t/pre-rfc-btf-relocations/24161.md?page=1)
