From 62a79d28ac59ad608cb5c4f039a598ed64097d7c Mon Sep 17 00:00:00 2001 From: vad Date: Tue, 15 Sep 2026 14:42:22 +0200 Subject: [PATCH 1/5] Add the `btf_relocations` feature and attribute Register the unstable `btf_relocations` feature and add the `#[btf_relocatable]` built-in attribute for structs and unions, that can be used only on BPF architecture. Preserve the attribute in crate metadata, add UI coverage for feature gating and attribute target validation. --- compiler/rustc_attr_ir/src/data_structures.rs | 3 + .../rustc_attr_ir/src/encode_cross_crate.rs | 1 + .../src/attributes/btf_relocatable.rs | 22 +++++++ .../rustc_attr_parsing/src/attributes/mod.rs | 1 + compiler/rustc_attr_parsing/src/context.rs | 2 + .../rustc_attr_parsing/src/diagnostics.rs | 7 +++ compiler/rustc_feature/src/builtin_attrs.rs | 3 + compiler/rustc_feature/src/unstable.rs | 4 ++ compiler/rustc_passes/src/check_attr.rs | 1 + compiler/rustc_span/src/symbol.rs | 2 + tests/ui/README.md | 7 +++ .../btf-relocations/attribute-arch-check.rs | 48 ++++++++++++++ .../attribute-arch-check.stderr | 62 +++++++++++++++++++ tests/ui/btf-relocations/attribute.rs | 42 +++++++++++++ tests/ui/btf-relocations/attribute.stderr | 26 ++++++++ .../feature-gate-btf-relocations.rs | 16 +++++ .../feature-gate-btf-relocations.stderr | 12 ++++ 17 files changed, 259 insertions(+) create mode 100644 compiler/rustc_attr_parsing/src/attributes/btf_relocatable.rs create mode 100644 tests/ui/btf-relocations/attribute-arch-check.rs create mode 100644 tests/ui/btf-relocations/attribute-arch-check.stderr create mode 100644 tests/ui/btf-relocations/attribute.rs create mode 100644 tests/ui/btf-relocations/attribute.stderr create mode 100644 tests/ui/feature-gates/feature-gate-btf-relocations.rs create mode 100644 tests/ui/feature-gates/feature-gate-btf-relocations.stderr diff --git a/compiler/rustc_attr_ir/src/data_structures.rs b/compiler/rustc_attr_ir/src/data_structures.rs index 81887e0176ee7..3578d0a91df6c 100644 --- a/compiler/rustc_attr_ir/src/data_structures.rs +++ b/compiler/rustc_attr_ir/src/data_structures.rs @@ -1002,6 +1002,9 @@ pub enum AttributeKind { /// Represents `#[automatically_derived]` AutomaticallyDerived, + /// Represents `#[btf_relocatable]`. + BtfRelocatable(Span), + /// Represents the trace attribute of `#[cfg_attr]` CfgAttrTrace(ThinVec<(CfgEntry, Span)>), diff --git a/compiler/rustc_attr_ir/src/encode_cross_crate.rs b/compiler/rustc_attr_ir/src/encode_cross_crate.rs index 6a9f37f80868a..f88eda9c388f4 100644 --- a/compiler/rustc_attr_ir/src/encode_cross_crate.rs +++ b/compiler/rustc_attr_ir/src/encode_cross_crate.rs @@ -21,6 +21,7 @@ impl AttributeKind { AllowInternalUnsafe(..) => Yes, AllowInternalUnstable(..) => Yes, AutomaticallyDerived => Yes, + BtfRelocatable(..) => Yes, CfgAttrTrace(..) => Yes, CfgTrace(..) => Yes, CfiEncoding { .. } => Yes, diff --git a/compiler/rustc_attr_parsing/src/attributes/btf_relocatable.rs b/compiler/rustc_attr_parsing/src/attributes/btf_relocatable.rs new file mode 100644 index 0000000000000..e669dac5392a6 --- /dev/null +++ b/compiler/rustc_attr_parsing/src/attributes/btf_relocatable.rs @@ -0,0 +1,22 @@ +use rustc_feature::AttributeStability; +use rustc_target::spec::Arch; + +use super::prelude::*; +use crate::diagnostics::BtfRelocatableOnNonBpfArch; + +pub(crate) struct BtfRelocatableParser; + +impl NoArgsAttributeParser for BtfRelocatableParser { + const PATH: &[Symbol] = &[sym::btf_relocatable]; + const ALLOWED_TARGETS: AllowedTargets<'_> = + AllowedTargets::AllowList(&[Allow(Target::Struct), Allow(Target::Union)]); + const STABILITY: AttributeStability = unstable!(btf_relocations); + const CREATE: fn(Span) -> AttributeKind = AttributeKind::BtfRelocatable; + + fn finalize_check(cx: &FinalizeCheckContext<'_, '_>, attr_span: Span) { + // `#[btf_relocatable]` may be only applied on BPF architecture. + if cx.shared.cx.sess().target.arch != Arch::Bpf { + cx.shared.cx.dcx().emit_err(BtfRelocatableOnNonBpfArch { span: attr_span }); + } + } +} diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 242b4a73b06a6..05fa44b008adc 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -38,6 +38,7 @@ mod prelude; pub(crate) mod allow_unstable; pub(crate) mod autodiff; pub(crate) mod body; +pub(crate) mod btf_relocatable; pub(crate) mod cfg; pub(crate) mod cfg_select; pub(crate) mod cfi_encoding; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 73195c7b77b10..1f2d0b692d8b2 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -23,6 +23,7 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use crate::attributes::allow_unstable::*; use crate::attributes::autodiff::*; use crate::attributes::body::*; +use crate::attributes::btf_relocatable::*; use crate::attributes::cfi_encoding::*; use crate::attributes::codegen_attrs::*; use crate::attributes::confusables::*; @@ -261,6 +262,7 @@ attribute_parsers!( Single, Single>, Single>, + Single>, Single>, Single>, Single>, diff --git a/compiler/rustc_attr_parsing/src/diagnostics.rs b/compiler/rustc_attr_parsing/src/diagnostics.rs index 9d72bdcb75ce3..e5a5113ca4fc5 100644 --- a/compiler/rustc_attr_parsing/src/diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/diagnostics.rs @@ -2035,3 +2035,10 @@ pub(crate) struct UnusedDuplicate { )] pub warning: bool, } + +#[derive(Diagnostic)] +#[diag("the `btf_relocatable` attribute can only be used on BPF architecture")] +pub(crate) struct BtfRelocatableOnNonBpfArch { + #[primary_span] + pub span: Span, +} diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index bc6f87a2a7f17..eae9defdd0f62 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -183,6 +183,9 @@ pub static BUILTIN_ATTRIBUTES: &[Symbol] = &[ // RFC 2412 sym::optimize, + // BTF CO-RE relocation support. + sym::btf_relocatable, + sym::ffi_pure, sym::ffi_const, sym::register_attribute_tool, diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 3a80145e2897d..a3add3575f973 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -426,6 +426,10 @@ declare_features! ( (unstable, avx10_target_feature, "1.88.0", Some(138843)), /// Target features on bpf. (unstable, bpf_target_feature, "1.54.0", Some(150247)), + // no-tracking-issue-start + /// Allows BTF CO-RE field relocation queries. + (unstable, btf_relocations, "CURRENT_RUSTC_VERSION", Some(160616)), + // no-tracking-issue-end /// Allows defining c-variadic functions on targets where this feature has not yet /// undergone sufficient testing for stabilization. (unstable, c_variadic_experimental_arch, "1.97.0", Some(155973)), diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 44c34a2abddd1..019ebb900acb2 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -236,6 +236,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { AttributeKind::AllowInternalUnsafe(..) => (), AttributeKind::AllowInternalUnstable(..) => (), AttributeKind::AutomaticallyDerived => (), + AttributeKind::BtfRelocatable(..) => (), AttributeKind::CfgAttrTrace(..) => (), AttributeKind::CfgTrace(..) => (), AttributeKind::CfiEncoding { .. } => (), diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index cacb8582ae3c6..397fb13705a1d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -553,6 +553,8 @@ symbols! { breg, bridge, bswap, + btf_relocatable, + btf_relocations, built, builtin_syntax, bundle, diff --git a/tests/ui/README.md b/tests/ui/README.md index a3617fb6b07c9..eff0b3440c2a3 100644 --- a/tests/ui/README.md +++ b/tests/ui/README.md @@ -186,6 +186,13 @@ See: - [`std::box::Boxed`](https://doc.rust-lang.org/std/boxed/struct.Box.html) - [Tracking issue for `box_patterns` feature #29641](https://github.com/rust-lang/rust/issues/29641) +## `tests/ui/btf-relocations/`: BTF relocations + +Tests for [Compile Once, Run Everywhere (CO-RE)][co-re] relocations based on the [BPF Type Format (BTF)][btf]. + +[co-re]: https://nakryiko.com/posts/bpf-portability-and-co-re/ +[btf]: https://docs.kernel.org/bpf/btf.html + ## `tests/ui/builtin-superkinds/`: Built-in Trait Hierarchy Tests Tests for built-in trait hierarchy (Send, Sync, Sized, etc.) and their supertrait relationships. E.g. auto traits and marker trait constraints. diff --git a/tests/ui/btf-relocations/attribute-arch-check.rs b/tests/ui/btf-relocations/attribute-arch-check.rs new file mode 100644 index 0000000000000..4df06da0e8095 --- /dev/null +++ b/tests/ui/btf-relocations/attribute-arch-check.rs @@ -0,0 +1,48 @@ +//@ add-minicore +//@ needs-llvm-components: x86 +//@ compile-flags: --target x86_64-unknown-none + +#![feature(btf_relocations)] +#![feature(no_core)] +#![no_core] + +extern crate minicore; + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute can only be used on BPF architecture +struct ValidStructInner { + field: u32, +} + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute can only be used on BPF architecture +struct ValidStruct { + field: u32, + inner: ValidStructInner, +} + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute can only be used on BPF architecture +union ValidUnion { + word: u64, + half: u32, +} + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute can only be used on BPF architecture +//~| ERROR the `btf_relocatable` attribute cannot be used on enums +enum InvalidEnum { + A, +} + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute can only be used on BPF architecture +//~| ERROR the `btf_relocatable` attribute cannot be used on functions +fn invalid_function() {} + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute can only be used on BPF architecture +//~| ERROR the `btf_relocatable` attribute cannot be used on traits +trait InvalidTrait {} + +fn main() {} diff --git a/tests/ui/btf-relocations/attribute-arch-check.stderr b/tests/ui/btf-relocations/attribute-arch-check.stderr new file mode 100644 index 0000000000000..c815cac8a1397 --- /dev/null +++ b/tests/ui/btf-relocations/attribute-arch-check.stderr @@ -0,0 +1,62 @@ +error: the `btf_relocatable` attribute can only be used on BPF architecture + --> $DIR/attribute-arch-check.rs:11:1 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^^^^ + +error: the `btf_relocatable` attribute can only be used on BPF architecture + --> $DIR/attribute-arch-check.rs:17:1 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^^^^ + +error: the `btf_relocatable` attribute can only be used on BPF architecture + --> $DIR/attribute-arch-check.rs:24:1 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^^^^ + +error: the `btf_relocatable` attribute cannot be used on enums + --> $DIR/attribute-arch-check.rs:31:3 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^ + | + = help: the `btf_relocatable` attribute can be applied to structs and unions + +error: the `btf_relocatable` attribute can only be used on BPF architecture + --> $DIR/attribute-arch-check.rs:31:1 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^^^^ + +error: the `btf_relocatable` attribute cannot be used on functions + --> $DIR/attribute-arch-check.rs:38:3 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^ + | + = help: the `btf_relocatable` attribute can only be applied to data types + +error: the `btf_relocatable` attribute can only be used on BPF architecture + --> $DIR/attribute-arch-check.rs:38:1 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^^^^ + +error: the `btf_relocatable` attribute cannot be used on traits + --> $DIR/attribute-arch-check.rs:43:3 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^ + | + = help: the `btf_relocatable` attribute can only be applied to data types + +error: the `btf_relocatable` attribute can only be used on BPF architecture + --> $DIR/attribute-arch-check.rs:43:1 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^^^^ + +error: aborting due to 9 previous errors + diff --git a/tests/ui/btf-relocations/attribute.rs b/tests/ui/btf-relocations/attribute.rs new file mode 100644 index 0000000000000..157ff8a7fa4b0 --- /dev/null +++ b/tests/ui/btf-relocations/attribute.rs @@ -0,0 +1,42 @@ +//@ add-minicore +//@ needs-llvm-components: bpf +//@ compile-flags: --target bpfel-unknown-none + +#![feature(btf_relocations)] +#![feature(no_core)] +#![no_core] + +extern crate minicore; + +#[btf_relocatable] +struct ValidStructInner { + field: u32, +} + +#[btf_relocatable] +struct ValidStruct { + field: u32, + inner: ValidStructInner, +} + +#[btf_relocatable] +union ValidUnion { + word: u64, + half: u32, +} + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute cannot be used on enums +enum InvalidEnum { + A, +} + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute cannot be used on functions +fn invalid_function() {} + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute cannot be used on traits +trait InvalidTrait {} + +fn main() {} diff --git a/tests/ui/btf-relocations/attribute.stderr b/tests/ui/btf-relocations/attribute.stderr new file mode 100644 index 0000000000000..6e0e411594073 --- /dev/null +++ b/tests/ui/btf-relocations/attribute.stderr @@ -0,0 +1,26 @@ +error: the `btf_relocatable` attribute cannot be used on enums + --> $DIR/attribute.rs:28:3 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^ + | + = help: the `btf_relocatable` attribute can be applied to structs and unions + +error: the `btf_relocatable` attribute cannot be used on functions + --> $DIR/attribute.rs:34:3 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^ + | + = help: the `btf_relocatable` attribute can only be applied to data types + +error: the `btf_relocatable` attribute cannot be used on traits + --> $DIR/attribute.rs:38:3 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^ + | + = help: the `btf_relocatable` attribute can only be applied to data types + +error: aborting due to 3 previous errors + diff --git a/tests/ui/feature-gates/feature-gate-btf-relocations.rs b/tests/ui/feature-gates/feature-gate-btf-relocations.rs new file mode 100644 index 0000000000000..f61fb8a07ea1c --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-btf-relocations.rs @@ -0,0 +1,16 @@ +//@ add-minicore +//@ needs-llvm-components: bpf +//@ compile-flags: --target bpfel-unknown-none + +#![feature(no_core)] +#![no_core] + +extern crate minicore; + +#[btf_relocatable] +//~^ ERROR the `btf_relocatable` attribute is an experimental feature +struct KernelType { + field: u32, +} + +fn main() {} diff --git a/tests/ui/feature-gates/feature-gate-btf-relocations.stderr b/tests/ui/feature-gates/feature-gate-btf-relocations.stderr new file mode 100644 index 0000000000000..c1de395496515 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-btf-relocations.stderr @@ -0,0 +1,12 @@ +error[E0658]: the `btf_relocatable` attribute is an experimental feature + --> $DIR/feature-gate-btf-relocations.rs:10:3 + | +LL | #[btf_relocatable] + | ^^^^^^^^^^^^^^^ + | + = help: add `#![feature(btf_relocations)]` to the crate attributes to enable + = note: this compiler was built on YYYY-MM-DD; consider upgrading it if it is out of date + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0658`. From 27cacbf6b6c3cce0f3aeacbb8dcdb25e6b35632d Mon Sep 17 00:00:00 2001 From: vad Date: Tue, 15 Sep 2026 14:42:48 +0200 Subject: [PATCH 2/5] Reject direct field access on BTF-relocatable types Reject field projection and `offset_off!` usage on `#[btf_relocatable]` types. --- compiler/rustc_hir_typeck/src/expr.rs | 22 ++++++++++ tests/auxiliary/minicore.rs | 7 ++++ tests/ui/btf-relocations/field-access.rs | 44 ++++++++++++++++++++ tests/ui/btf-relocations/field-access.stderr | 38 +++++++++++++++++ 4 files changed, 111 insertions(+) create mode 100644 tests/ui/btf-relocations/field-access.rs create mode 100644 tests/ui/btf-relocations/field-access.stderr diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index cbbd66f648eb8..2f302e35d563b 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -2781,6 +2781,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ); if let Some((idx, field)) = self.find_adt_field(*base_def, ident) { + if find_attr!(self.tcx, base_def.did(), BtfRelocatable(..)) { + let mut err = self.dcx().struct_span_err( + expr.span, + "cannot access fields of a `btf_relocatable` type directly", + ); + err.span_label( + ident.span, + "direct field access is forbidden for BTF-relocatable types", + ); + return Ty::new_error(self.tcx, err.emit()); + } + self.write_field_index(expr.hir_id, idx); let adjustments = self.adjust_steps(&autoderef); @@ -3900,6 +3912,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { continue; } ty::Adt(container_def, args) => { + if find_attr!(self.tcx, container_def.did(), BtfRelocatable(..)) { + let mut err = self.dcx().struct_span_err( + expr.span, + "cannot use `offset_of!` with a `btf_relocatable` type", + ); + err.span_label(field.span, "this field requires BTF relocation"); + err.emit(); + break; + } + let (ident, def_scope) = self.tcx.adjust_ident_and_get_scope( field, container_def.did(), diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index e8bfdf80c98e8..c7e8c3eba8e22 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -19,6 +19,7 @@ // ignore-tidy-file-linelength #![feature( + allow_internal_unstable, no_core, intrinsics, lang_items, @@ -368,6 +369,12 @@ pub mod mem { #[rustc_nounwind] #[rustc_intrinsic] pub const fn align_of() -> usize; + + #[allow_internal_unstable(builtin_syntax)] + pub macro offset_of($Container:ty, $($fields:expr)+ $(,)?) { + // The `{}` is for better error messages + {builtin # offset_of($Container, $($fields)+)} + } } pub mod ptr { diff --git a/tests/ui/btf-relocations/field-access.rs b/tests/ui/btf-relocations/field-access.rs new file mode 100644 index 0000000000000..4e9d9087efa7c --- /dev/null +++ b/tests/ui/btf-relocations/field-access.rs @@ -0,0 +1,44 @@ +//@ add-minicore +//@ needs-llvm-components: bpf +//@ compile-flags: --target bpfel-unknown-none + +#![feature(btf_relocations)] +#![feature(no_core)] +#![no_core] + +extern crate minicore; +use minicore::*; + +#[btf_relocatable] +#[repr(C)] +struct Inner { + value: u32, +} + +#[btf_relocatable] +#[repr(C)] +struct Outer { + inner: Inner, +} + +fn direct(inner: &Inner) -> u32 { + inner.value + //~^ ERROR cannot access fields of a `btf_relocatable` type directly +} + +fn nested(outer: &Outer) -> u32 { + outer.inner.value + //~^ ERROR cannot access fields of a `btf_relocatable` type directly +} + +fn offset() -> usize { + mem::offset_of!(Inner, value) + //~^ ERROR cannot use `offset_of!` with a `btf_relocatable` type +} + +fn nested_offset() -> usize { + mem::offset_of!(Outer, inner.value) + //~^ ERROR cannot use `offset_of!` with a `btf_relocatable` type +} + +fn main() {} diff --git a/tests/ui/btf-relocations/field-access.stderr b/tests/ui/btf-relocations/field-access.stderr new file mode 100644 index 0000000000000..a00088e96a74e --- /dev/null +++ b/tests/ui/btf-relocations/field-access.stderr @@ -0,0 +1,38 @@ +error: cannot access fields of a `btf_relocatable` type directly + --> $DIR/field-access.rs:25:5 + | +LL | inner.value + | ^^^^^^----- + | | + | direct field access is forbidden for BTF-relocatable types + +error: cannot access fields of a `btf_relocatable` type directly + --> $DIR/field-access.rs:30:5 + | +LL | outer.inner.value + | ^^^^^^----- + | | + | direct field access is forbidden for BTF-relocatable types + +error: cannot use `offset_of!` with a `btf_relocatable` type + --> $DIR/field-access.rs:35:5 + | +LL | mem::offset_of!(Inner, value) + | ^^^^^^^^^^^^^^^^^^^^^^^-----^ + | | + | this field requires BTF relocation + | + = note: this error originates in the macro `mem::offset_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: cannot use `offset_of!` with a `btf_relocatable` type + --> $DIR/field-access.rs:40:5 + | +LL | mem::offset_of!(Outer, inner.value) + | ^^^^^^^^^^^^^^^^^^^^^^^-----^^^^^^^ + | | + | this field requires BTF relocation + | + = note: this error originates in the macro `mem::offset_of` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 4 previous errors + From 2c8a97f85ee5df1bf22937cb538989d9fb7350e9 Mon Sep 17 00:00:00 2001 From: vad Date: Tue, 15 Sep 2026 14:43:02 +0200 Subject: [PATCH 3/5] Add the backend-neutral BTF field info pipeline Add builtin macros for requesting BTF field information from the Rust compiler's frontend perspective: * `btf_field_byte_offset` * `btf_field_byte_size` * `btf_field_exists` Parse them as `BtfFieldInfo` expressions that carry the kind of requested information (offset, size, exists), base type and the field path. This mechanism supports nested field accesses in one query. Add internal compiler intrinsics corresponding to LLVM's and GCC's BTF preservation intrinsics: * `btf_preserve_access_index` * `btf_preserve_field_info` Lower the builtin macros to calls to these intrinsics. Add corresponding methods to the `BuilderMethods` trait in codegen SSA and use them for lowering the intrinsic calls. Backends without BTF relocation support report an error. Support in backends will be added in follow-up changes. This change does not expose the functionality to the users. A user-facing API will also be added in a follow-up change. --- compiler/rustc_ast/src/ast.rs | 48 ++++++++++ compiler/rustc_ast/src/util/classify.rs | 3 +- compiler/rustc_ast/src/visit.rs | 3 + compiler/rustc_ast_lowering/src/expr.rs | 9 ++ compiler/rustc_ast_lowering/src/lib.rs | 2 + .../rustc_ast_pretty/src/pprust/state/expr.rs | 18 ++++ compiler/rustc_attr_ir/src/lang_items.rs | 2 + .../src/assert/context.rs | 3 +- .../rustc_codegen_ssa/src/mir/intrinsic.rs | 24 ++++- .../rustc_codegen_ssa/src/traits/builder.rs | 12 +++ compiler/rustc_hir/src/hir.rs | 26 +++-- compiler/rustc_hir/src/intravisit.rs | 3 +- .../rustc_hir_analysis/src/check/intrinsic.rs | 11 +++ compiler/rustc_hir_pretty/src/lib.rs | 17 ++++ compiler/rustc_hir_typeck/src/expr.rs | 60 +++++++++++- .../rustc_hir_typeck/src/expr_use_visitor.rs | 2 + .../rustc_hir_typeck/src/naked_functions.rs | 3 +- compiler/rustc_hir_typeck/src/writeback.rs | 17 ++++ compiler/rustc_lint/src/dangling.rs | 5 +- compiler/rustc_middle/src/hir/mod.rs | 1 + compiler/rustc_middle/src/mir/visit.rs | 1 - .../rustc_middle/src/ty/typeck_results.rs | 16 ++++ compiler/rustc_mir_build/src/thir/cx/expr.rs | 95 ++++++++++++++++++- compiler/rustc_parse/src/parser/expr.rs | 53 ++++++++++- compiler/rustc_passes/src/input_stats.rs | 3 +- compiler/rustc_span/src/symbol.rs | 5 + compiler/rustc_ty_utils/src/consts.rs | 1 - tests/ui/macros/stringify.rs | 2 + 28 files changed, 421 insertions(+), 24 deletions(-) diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index ee6150322a442..2ba8acd377823 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -1621,6 +1621,7 @@ impl Expr { | ExprKind::While(..) | ExprKind::Yield(YieldKind::Postfix(..)) | ExprKind::DirectConstArg(..) + | ExprKind::BtfFieldInfo(..) | ExprKind::Err(_) | ExprKind::Dummy => prefix_attrs_precedence(&self.attrs), } @@ -1920,6 +1921,9 @@ pub enum ExprKind { /// An mGCA `direct_const_arg!()` expression. DirectConstArg(Box), + /// A BTF field metadata query. + BtfFieldInfo(BtfRelocKind, Box, ThinVec), + /// Placeholder for an expression that wasn't syntactically well formed in some way. Err(ErrorGuaranteed), @@ -2185,6 +2189,50 @@ impl YieldKind { } } +/// The kind of [BPF Type Format (BTF)][btf] relocation. +/// +/// [BTF][btf] is the type metadata format used by the Linux kernel and eBPF +/// tooling for relocations: the compiled program records which field or array +/// element it intended to access, and the loader rewrites the bytecode to +/// match the layout of the kernel it is about to run on. +/// +/// The following variants are a subset of the relocation kinds defined by +/// Linux's [`bpf_core_relo_kind`]. +/// +/// [btf]: https://docs.kernel.org/bpf/btf.html +/// [`bpf_core_relo_kind`]: https://docs.kernel.org/bpf/llvm_reloc.html#relocation-kinds +#[derive(Clone, Copy, Encodable, Decodable, Debug, Eq, PartialEq, StableHash, Walkable)] +pub enum BtfRelocKind { + /// Offset of the field. + ByteOffset, + /// Size of the field. + ByteSize, + /// Whether the field exists. + Exists, +} + +impl BtfRelocKind { + pub fn as_str(&self) -> &'static str { + match self { + Self::ByteOffset => "btf_field_byte_offset", + Self::ByteSize => "btf_field_byte_size", + Self::Exists => "btf_field_exists", + } + } + + /// Returns a code number associated with the given relocation kind that matches Linux's + /// [`bpf_core_relo_kind`]. + /// + /// [`bpf_core_relo_kind`]: https://docs.kernel.org/bpf/llvm_reloc.html#relocation-kinds + pub fn code(&self) -> u32 { + match self { + Self::ByteOffset => 0, + Self::ByteSize => 1, + Self::Exists => 2, + } + } +} + /// A literal in a meta item. #[derive(Clone, Copy, Encodable, Decodable, Debug, StableHash)] pub struct MetaItemLit { diff --git a/compiler/rustc_ast/src/util/classify.rs b/compiler/rustc_ast/src/util/classify.rs index e799f73ff544f..4765fa0bb53c8 100644 --- a/compiler/rustc_ast/src/util/classify.rs +++ b/compiler/rustc_ast/src/util/classify.rs @@ -159,6 +159,7 @@ pub fn leading_labeled_expr(mut expr: &ast::Expr) -> bool { | Yield(..) | UnsafeBinderCast(..) | DirectConstArg(..) + | BtfFieldInfo(..) | Err(..) | Dummy => return false, } @@ -218,7 +219,7 @@ pub fn expr_trailing_brace(mut expr: &ast::Expr) -> Option> { break (mac.args.delim == Delimiter::Brace).then_some(TrailingBrace::MacCall(mac)); } - InlineAsm(_) | OffsetOf(_, _) | IncludedBytes(_) | FormatArgs(_) => { + InlineAsm(_) | OffsetOf(_, _) | IncludedBytes(_) | FormatArgs(_) | BtfFieldInfo(..) => { // These should have been denied pre-expansion. break None; } diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index 9d4c32825e1e4..aaf407c4c41bc 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -422,6 +422,7 @@ macro_rules! common_visitor_and_walkers { BoundAsyncness, BoundConstness, BoundPolarity, + BtfRelocKind, ByRef, Closure, Const, @@ -1074,6 +1075,8 @@ macro_rules! common_visitor_and_walkers { visit_visitable!($($mut)? vis, kind, expr, ty), ExprKind::DirectConstArg(expr) => visit_visitable!($($mut)? vis, expr), + ExprKind::BtfFieldInfo(kind, container, fields) => + visit_visitable!($($mut)? vis, kind, container, fields), ExprKind::Err(_guar) => {} ExprKind::Dummy => {} } diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 003ca39a5bda9..0a4c34c5ab9ca 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -483,6 +483,15 @@ impl<'hir> LoweringContext<'_, 'hir> { let e = self.emit_bad_direct_const_arg(e.span, expr, "expression"); hir::ExprKind::Err(e) } + + ExprKind::BtfFieldInfo(kind, container, fields) => hir::ExprKind::BtfFieldInfo( + *kind, + self.lower_ty_alloc( + container, + ImplTraitContext::Disallowed(ImplTraitPosition::BtfFieldInfo), + ), + self.arena.alloc_from_iter(fields.iter().map(|&ident| self.lower_ident(ident))), + ), }; hir::Expr { hir_id: expr_hir_id, kind, span } diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 3111546c6e198..e4b5759e8075b 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -437,6 +437,7 @@ enum ImplTraitPosition { Cast, ImplSelf, OffsetOf, + BtfFieldInfo, } impl std::fmt::Display for ImplTraitPosition { @@ -463,6 +464,7 @@ impl std::fmt::Display for ImplTraitPosition { ImplTraitPosition::Cast => "cast expression types", ImplTraitPosition::ImplSelf => "impl headers", ImplTraitPosition::OffsetOf => "`offset_of!` parameters", + ImplTraitPosition::BtfFieldInfo => "BTF field info query parameters", }; write!(f, "{name}") diff --git a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs index 325756e90ec66..69a9768c94a2a 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/expr.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/expr.rs @@ -888,6 +888,24 @@ impl<'a> State<'a> { self.print_expr(expr, FixupContext::default()); self.pclose() } + ast::ExprKind::BtfFieldInfo(kind, container, fields) => { + self.word("builtin # "); + self.word(kind.as_str()); + self.popen(); + let ib = self.ibox(0); + self.print_type(container); + self.word(","); + self.space(); + if let Some((&first, rest)) = fields.split_first() { + self.print_ident(first); + for &field in rest { + self.word("."); + self.print_ident(field); + } + } + self.end(ib); + self.pclose(); + } } self.ann.post(self, AnnNode::Expr(expr)); diff --git a/compiler/rustc_attr_ir/src/lang_items.rs b/compiler/rustc_attr_ir/src/lang_items.rs index 34f4fba5b0eea..8e34e56243d16 100644 --- a/compiler/rustc_attr_ir/src/lang_items.rs +++ b/compiler/rustc_attr_ir/src/lang_items.rs @@ -164,6 +164,8 @@ language_item_table! { AlignOf, sym::mem_align_const, align_const, Target::AssocConst, GenericRequirement::Exact(0); SizeOf, sym::mem_size_const, size_const, Target::AssocConst, GenericRequirement::Exact(0); OffsetOf, sym::offset_of, offset_of, Target::Fn, GenericRequirement::Exact(1); + BtfPreserveAccessIndex, sym::btf_preserve_access_index, btf_preserve_access_index, Target::Fn, GenericRequirement::Exact(1); + BtfPreserveFieldInfo, sym::btf_preserve_field_info, btf_preserve_field_info, Target::Fn, GenericRequirement::Exact(0); /// Trait injected by `#[derive(PartialEq)]`, (i.e. "Partial EQ"). StructuralPeq, sym::structural_peq, structural_peq_trait, Target::Trait, GenericRequirement::None; Copy, sym::copy, copy_trait, Target::Trait, GenericRequirement::Exact(0); diff --git a/compiler/rustc_builtin_macros/src/assert/context.rs b/compiler/rustc_builtin_macros/src/assert/context.rs index 1bc2bc8342559..711ac33378f9c 100644 --- a/compiler/rustc_builtin_macros/src/assert/context.rs +++ b/compiler/rustc_builtin_macros/src/assert/context.rs @@ -324,7 +324,8 @@ impl<'cx, 'a> Context<'cx, 'a> { | ExprKind::Become(_) | ExprKind::Yield(_) | ExprKind::DirectConstArg(_) - | ExprKind::UnsafeBinderCast(..) => {} + | ExprKind::UnsafeBinderCast(..) + | ExprKind::BtfFieldInfo(..) => {} } } diff --git a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs index cc48f418d03db..7d20f955972c5 100644 --- a/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs +++ b/compiler/rustc_codegen_ssa/src/mir/intrinsic.rs @@ -1,4 +1,4 @@ -use rustc_abi::{Align, FieldIdx, WrappingRange}; +use rustc_abi::{Align, FieldIdx, VariantIdx, WrappingRange}; use rustc_middle::mir::SourceInfo; use rustc_middle::ty::{self, Ty, TyCtxt}; use rustc_middle::{bug, span_bug}; @@ -173,6 +173,28 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { let (_, llalign) = size_of_val::size_and_align_of_dst(bx, tp_ty, meta); OperandValue::Immediate(llalign) } + sym::btf_preserve_access_index => { + let base = args[0].immediate(); + let Some(variant) = bx.const_to_opt_uint(args[1].immediate()) else { + span_bug!(span, "BTF variant index is not a constant") + }; + let Some(field) = bx.const_to_opt_uint(args[2].immediate()) else { + span_bug!(span, "BTF field index is not a constant") + }; + OperandValue::Immediate(bx.btf_preserve_access_index( + base, + fn_args.type_at(0), + VariantIdx::from_u32(variant as u32), + FieldIdx::from_u32(field as u32), + )) + } + sym::btf_preserve_field_info => { + let field = args[0].immediate(); + let Some(kind) = bx.const_to_opt_uint(args[1].immediate()) else { + span_bug!(span, "BTF field information kind is not a constant") + }; + OperandValue::Immediate(bx.btf_preserve_field_info(field, kind)) + } sym::vtable_size | sym::vtable_align => { let vtable = args[0].immediate(); let idx = match name { diff --git a/compiler/rustc_codegen_ssa/src/traits/builder.rs b/compiler/rustc_codegen_ssa/src/traits/builder.rs index caf63abeef3ad..5dd7a4265b3cb 100644 --- a/compiler/rustc_codegen_ssa/src/traits/builder.rs +++ b/compiler/rustc_codegen_ssa/src/traits/builder.rs @@ -354,6 +354,18 @@ pub trait BuilderMethods<'a, 'tcx>: fn inbounds_ptradd(&mut self, ptr: Self::Value, offset: Self::Value) -> Self::Value { self.inbounds_gep(self.cx().type_i8(), ptr, &[offset]) } + fn btf_preserve_access_index( + &mut self, + _base: Self::Value, + _container_ty: Ty<'tcx>, + _variant: rustc_abi::VariantIdx, + _field: rustc_abi::FieldIdx, + ) -> Self::Value { + self.tcx().dcx().fatal("the selected codegen backend does not support BTF relocations") + } + fn btf_preserve_field_info(&mut self, _field: Self::Value, _kind: u64) -> Self::Value { + self.tcx().dcx().fatal("the selected codegen backend does not support BTF relocations") + } fn trunc(&mut self, val: Self::Value, dest_ty: Self::Type) -> Self::Value; /// Produces the same value as [`Self::trunc`] (and defaults to that), diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 112f784825975..f6e25e9e5badd 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -6,8 +6,8 @@ use std::ops::Not; use rustc_abi::ExternAbi; use rustc_ast::util::parser::ExprPrecedence; use rustc_ast::{ - self as ast, FloatTy, InlineAsmOptions, InlineAsmTemplatePiece, IntTy, Label, LitIntType, - LitKind, TraitObjectSyntax, UintTy, UnsafeBinderCastKind, + self as ast, BtfRelocKind, FloatTy, InlineAsmOptions, InlineAsmTemplatePiece, IntTy, Label, + LitIntType, LitKind, TraitObjectSyntax, UintTy, UnsafeBinderCastKind, }; pub use rustc_ast::{ AssignOp, AssignOpKind, AttrId, AttrStyle, BinOp, BinOpKind, BindingMode, BorrowKind, @@ -2260,6 +2260,7 @@ impl Expr<'_> { | ExprKind::Type(..) | ExprKind::UnsafeBinderCast(..) | ExprKind::Use(..) + | ExprKind::BtfFieldInfo(..) | ExprKind::Err(_) => prefix_attrs_precedence(), ExprKind::DropTemps(expr, ..) => expr.precedence(has_attr), @@ -2333,7 +2334,8 @@ impl Expr<'_> { | ExprKind::Binary(..) | ExprKind::Yield(..) | ExprKind::Cast(..) - | ExprKind::DropTemps(..) => false, + | ExprKind::DropTemps(..) + | ExprKind::BtfFieldInfo(..) => false, } } @@ -2386,9 +2388,11 @@ impl Expr<'_> { pub fn can_have_side_effects(&self) -> bool { match self.peel_drop_temps().kind { - ExprKind::Path(_) | ExprKind::Lit(_) | ExprKind::OffsetOf(..) | ExprKind::Use(..) => { - false - } + ExprKind::Path(_) + | ExprKind::Lit(_) + | ExprKind::OffsetOf(..) + | ExprKind::Use(..) + | ExprKind::BtfFieldInfo(..) => false, ExprKind::Type(base, _) | ExprKind::Unary(_, base) | ExprKind::Field(base, _) @@ -2689,6 +2693,16 @@ pub enum ExprKind<'hir> { /// e.g. `unsafe<'a> &'a i32` <=> `&i32`. UnsafeBinderCast(UnsafeBinderCastKind, &'hir Expr<'hir>, Option<&'hir Ty<'hir>>), + /// [BPF Type Format (BTF)][btf] relocation. + /// + /// [BTF][btf] is the type metadata format used by the Linux kernel and + /// eBPF tooling for relocations: the compiled program records which field + /// or array element it intended to access, and the loader rewrites the + /// bytecode to match the layout of the kernel it is about to run on. + /// + /// [btf]: https://docs.kernel.org/bpf/btf.html + BtfFieldInfo(BtfRelocKind, &'hir Ty<'hir>, &'hir [Ident]), + /// A placeholder for an expression that wasn't syntactically well formed in some way. Err(rustc_span::ErrorGuaranteed), } diff --git a/compiler/rustc_hir/src/intravisit.rs b/compiler/rustc_hir/src/intravisit.rs index 83b6e08e22b3c..2884a3894721a 100644 --- a/compiler/rustc_hir/src/intravisit.rs +++ b/compiler/rustc_hir/src/intravisit.rs @@ -930,7 +930,8 @@ pub fn walk_expr<'v, V: Visitor<'v>>(visitor: &mut V, expression: &'v Expr<'v>) ExprKind::InlineAsm(ref asm) => { try_visit!(visitor.visit_inline_asm(asm, *hir_id)); } - ExprKind::OffsetOf(ref container, ref fields) => { + ExprKind::OffsetOf(ref container, ref fields) + | ExprKind::BtfFieldInfo(_, ref container, ref fields) => { try_visit!(visitor.visit_ty_unambig(container)); walk_list!(visitor, visit_ident, fields.iter().copied()); } diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index 11d21744bd7ae..02a863555f9c3 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -79,6 +79,8 @@ fn intrinsic_operation_unsafety(tcx: TyCtxt<'_>, intrinsic_id: LocalDefId) -> hi | sym::black_box | sym::breakpoint | sym::bswap + | sym::btf_preserve_access_index + | sym::btf_preserve_field_info | sym::caller_location | sym::carrying_mul_add | sym::carryless_mul @@ -309,6 +311,15 @@ pub(crate) fn check_intrinsic_type( } sym::size_of_type_id => (0, 0, vec![type_id_ty()], Ty::new_option(tcx, tcx.types.usize)), sym::offset_of => (1, 0, vec![tcx.types.u32, tcx.types.u32], tcx.types.usize), + sym::btf_preserve_access_index => ( + 1, + 0, + vec![Ty::new_imm_ptr(tcx, tcx.types.unit), tcx.types.u32, tcx.types.u32], + Ty::new_imm_ptr(tcx, tcx.types.unit), + ), + sym::btf_preserve_field_info => { + (0, 0, vec![Ty::new_imm_ptr(tcx, tcx.types.unit), tcx.types.u32], tcx.types.u32) + } sym::field_offset => (1, 0, vec![], tcx.types.usize), sym::rustc_peek => (1, 0, vec![param(0)], param(0)), sym::caller_location => (0, 0, vec![], tcx.caller_location_ty()), diff --git a/compiler/rustc_hir_pretty/src/lib.rs b/compiler/rustc_hir_pretty/src/lib.rs index f2f485a30300a..b540cd2eebc1a 100644 --- a/compiler/rustc_hir_pretty/src/lib.rs +++ b/compiler/rustc_hir_pretty/src/lib.rs @@ -1778,6 +1778,23 @@ impl<'a> State<'a> { self.word_space("yield"); self.print_expr_cond_paren(expr, self.precedence(expr) < ExprPrecedence::Jump); } + hir::ExprKind::BtfFieldInfo(kind, container, fields) => { + self.word(format!("{}!(", kind.as_str())); + self.print_type(container); + self.word(","); + self.space(); + + if let Some((&first, rest)) = fields.split_first() { + self.print_ident(first); + + for &field in rest { + self.word("."); + self.print_ident(field); + } + } + + self.word(")"); + } hir::ExprKind::Err(_) => { self.popen(); self.word("/*ERROR*/"); diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index 2f302e35d563b..c35c8c2631c10 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -5,8 +5,9 @@ //! //! See [`rustc_hir_analysis::check`] for more context on type checking in general. -use rustc_abi::{FIRST_VARIANT, FieldIdx}; +use rustc_abi::{FIRST_VARIANT, FieldIdx, VariantIdx}; use rustc_ast as ast; +use rustc_ast::BtfRelocKind; use rustc_ast::util::parser::ExprPrecedence; use rustc_data_structures::fx::{FxHashMap, FxHashSet}; use rustc_data_structures::thin_vec::ThinVec; @@ -29,10 +30,12 @@ use rustc_middle::ty::adjustment::{Adjust, Adjustment, AllowTwoPhase}; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::{self, AdtKind, GenericArgsRef, Ty, TypeVisitableExt, Unnormalized}; use rustc_middle::{bug, span_bug}; +use rustc_session::config::DebugInfo; use rustc_session::diagnostics::feature_err; use rustc_span::edit_distance::find_best_match_for_name; use rustc_span::hygiene::DesugaringKind; use rustc_span::{Ident, Span, Spanned, Symbol, kw, sym}; +use rustc_target::spec::Arch; use rustc_trait_selection::infer::InferCtxtExt; use rustc_trait_selection::traits::{self, ObligationCauseCode, ObligationCtxt}; use tracing::{debug, instrument, trace}; @@ -399,6 +402,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ExprKind::UnsafeBinderCast(kind, inner_expr, ty) => { self.check_expr_unsafe_binder_cast(expr.span, kind, inner_expr, ty, expected) } + ExprKind::BtfFieldInfo(kind, container, fields) => { + self.check_expr_btf_field_info(kind, container, fields, expr) + } ExprKind::Err(guar) => Ty::new_error(tcx, guar), } } @@ -3816,6 +3822,50 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fields: &[Ident], expr: &'tcx hir::Expr<'tcx>, ) -> Ty<'tcx> { + let field_indices = self.resolve_field_path(container, fields, false, expr); + self.typeck_results.borrow_mut().offset_of_data_mut().insert(expr.hir_id, field_indices); + self.tcx.types.usize + } + + fn check_expr_btf_field_info( + &self, + kind: BtfRelocKind, + container: &'tcx hir::Ty<'tcx>, + fields: &[Ident], + expr: &'tcx hir::Expr<'tcx>, + ) -> Ty<'tcx> { + if self.tcx.sess.target.arch != Arch::Bpf { + self.dcx() + .struct_span_err( + expr.span, + "BTF field relocation queries are only supported for BPF targets", + ) + .emit(); + } else if self.tcx.sess.opts.debuginfo == DebugInfo::None { + let mut err = self + .dcx() + .struct_span_err(expr.span, "BTF field relocation queries require debug info"); + err.help("compile with `-C debuginfo=2`"); + err.emit(); + } + let field_indices = self.resolve_field_path(container, fields, true, expr); + self.typeck_results + .borrow_mut() + .btf_field_info_data_mut() + .insert(expr.hir_id, field_indices); + match kind { + BtfRelocKind::Exists => self.tcx.types.bool, + BtfRelocKind::ByteOffset | BtfRelocKind::ByteSize => self.tcx.types.usize, + } + } + + fn resolve_field_path( + &self, + container: &'tcx hir::Ty<'tcx>, + fields: &[Ident], + allow_btf_relocatable: bool, + expr: &'tcx hir::Expr<'tcx>, + ) -> Vec<(Ty<'tcx>, VariantIdx, FieldIdx)> { let mut current_container = self.lower_ty(container).normalized; let mut field_indices = Vec::with_capacity(fields.len()); let mut fields = fields.into_iter(); @@ -3912,7 +3962,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { continue; } ty::Adt(container_def, args) => { - if find_attr!(self.tcx, container_def.did(), BtfRelocatable(..)) { + if find_attr!(self.tcx, container_def.did(), BtfRelocatable(..)) + && !allow_btf_relocatable + { let mut err = self.dcx().struct_span_err( expr.span, "cannot use `offset_of!` with a `btf_relocatable` type", @@ -3989,8 +4041,6 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { break; } - self.typeck_results.borrow_mut().offset_of_data_mut().insert(expr.hir_id, field_indices); - - self.tcx.types.usize + field_indices } } diff --git a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs index b2255c8d9679a..d504347ac7423 100644 --- a/compiler/rustc_hir_typeck/src/expr_use_visitor.rs +++ b/compiler/rustc_hir_typeck/src/expr_use_visitor.rs @@ -507,6 +507,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx | hir::ExprKind::Lit(..) | hir::ExprKind::ConstBlock(..) | hir::ExprKind::OffsetOf(..) + | hir::ExprKind::BtfFieldInfo(..) | hir::ExprKind::Err(_) => {} hir::ExprKind::Loop(blk, ..) => { @@ -1394,6 +1395,7 @@ impl<'tcx, Cx: TypeInformationCtxt<'tcx>, D: Delegate<'tcx>> ExprUseVisitor<'tcx | hir::ExprKind::InlineAsm(..) | hir::ExprKind::OffsetOf(..) | hir::ExprKind::UnsafeBinderCast(UnsafeBinderCastKind::Wrap, ..) + | hir::ExprKind::BtfFieldInfo(..) | hir::ExprKind::Err(_) => Ok(self.cat_rvalue(expr.hir_id, expr_ty)), } } diff --git a/compiler/rustc_hir_typeck/src/naked_functions.rs b/compiler/rustc_hir_typeck/src/naked_functions.rs index ddeec25acad7a..effff0c2b911d 100644 --- a/compiler/rustc_hir_typeck/src/naked_functions.rs +++ b/compiler/rustc_hir_typeck/src/naked_functions.rs @@ -160,7 +160,8 @@ impl CheckInlineAssembly { | ExprKind::Become(..) | ExprKind::Struct(..) | ExprKind::Repeat(..) - | ExprKind::Yield(..) => { + | ExprKind::Yield(..) + | ExprKind::BtfFieldInfo(..) => { self.items.push((ItemKind::NonAsm, span)); } diff --git a/compiler/rustc_hir_typeck/src/writeback.rs b/compiler/rustc_hir_typeck/src/writeback.rs index 7b1f38f882747..261541b3bc5f9 100644 --- a/compiler/rustc_hir_typeck/src/writeback.rs +++ b/compiler/rustc_hir_typeck/src/writeback.rs @@ -78,6 +78,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { wbcx.visit_transmutes(); wbcx.visit_offloads(); wbcx.visit_offset_of_container_types(); + wbcx.visit_btf_field_info_container_types(); wbcx.visit_potentially_region_dependent_goals(); let used_trait_imports = @@ -803,6 +804,22 @@ impl<'cx, 'tcx> WritebackCx<'cx, 'tcx> { } } + fn visit_btf_field_info_container_types(&mut self) { + let fcx_typeck_results = self.fcx.typeck_results.borrow(); + assert_eq!(fcx_typeck_results.hir_owner, self.typeck_results.hir_owner); + let common_hir_owner = fcx_typeck_results.hir_owner; + + for (local_id, indices) in fcx_typeck_results.btf_field_info_data().items_in_stable_order() + { + let hir_id = HirId { owner: common_hir_owner, local_id }; + let indices = indices + .iter() + .map(|&(ty, variant, field)| (self.resolve(ty, &hir_id), variant, field)) + .collect(); + self.typeck_results.btf_field_info_data_mut().insert(hir_id, indices); + } + } + fn visit_potentially_region_dependent_goals(&mut self) { let obligations = self.fcx.take_hir_typeck_potentially_region_dependent_goals(); if self.fcx.tainted_by_errors().is_none() { diff --git a/compiler/rustc_lint/src/dangling.rs b/compiler/rustc_lint/src/dangling.rs index de061ceb6fd8d..d2d17ba7dce1e 100644 --- a/compiler/rustc_lint/src/dangling.rs +++ b/compiler/rustc_lint/src/dangling.rs @@ -319,7 +319,10 @@ fn is_temporary_rvalue(expr: &Expr<'_>) -> bool { ExprKind::Assign(..) | ExprKind::AssignOp(..) | ExprKind::Yield(..) => false, // Compiler-magic macros - ExprKind::AddrOf(..) | ExprKind::OffsetOf(..) | ExprKind::InlineAsm(..) => false, + ExprKind::AddrOf(..) + | ExprKind::OffsetOf(..) + | ExprKind::InlineAsm(..) + | ExprKind::BtfFieldInfo(..) => false, // We are not interested in these ExprKind::Cast(..) diff --git a/compiler/rustc_middle/src/hir/mod.rs b/compiler/rustc_middle/src/hir/mod.rs index 5099859218187..03bbbaf95f05d 100644 --- a/compiler/rustc_middle/src/hir/mod.rs +++ b/compiler/rustc_middle/src/hir/mod.rs @@ -297,6 +297,7 @@ impl<'tcx> TyCtxt<'tcx> { | ExprKind::Path(_) | ExprKind::Continue(_) | ExprKind::OffsetOf(_, _) + | ExprKind::BtfFieldInfo(..) | ExprKind::Err(_) => unreachable!("no sub-expr expected for {:?}", expr.kind), } } diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 0ae59e99c2b5a..d0dccec3c307d 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -808,7 +808,6 @@ macro_rules! make_mir_visitor { self.visit_ty($(& $mutability)? *ty, TyContext::Location(location)); } - } } diff --git a/compiler/rustc_middle/src/ty/typeck_results.rs b/compiler/rustc_middle/src/ty/typeck_results.rs index ff7cef3613437..9307c89ce9836 100644 --- a/compiler/rustc_middle/src/ty/typeck_results.rs +++ b/compiler/rustc_middle/src/ty/typeck_results.rs @@ -228,6 +228,9 @@ pub struct TypeckResults<'tcx> { /// Container types and field indices of `offset_of!` expressions offset_of_data: ItemLocalMap, VariantIdx, FieldIdx)>>, + + /// Container types and field indices of BTF field info expressions. + btf_field_info_data: ItemLocalMap, VariantIdx, FieldIdx)>>, } impl<'tcx> TypeckResults<'tcx> { @@ -261,6 +264,7 @@ impl<'tcx> TypeckResults<'tcx> { transmutes_to_check: Default::default(), offloads_to_check: Default::default(), offset_of_data: Default::default(), + btf_field_info_data: Default::default(), } } @@ -596,6 +600,18 @@ impl<'tcx> TypeckResults<'tcx> { ) -> LocalTableInContextMut<'_, Vec<(Ty<'tcx>, VariantIdx, FieldIdx)>> { LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.offset_of_data } } + + pub fn btf_field_info_data( + &self, + ) -> LocalTableInContext<'_, Vec<(Ty<'tcx>, VariantIdx, FieldIdx)>> { + LocalTableInContext { hir_owner: self.hir_owner, data: &self.btf_field_info_data } + } + + pub fn btf_field_info_data_mut( + &mut self, + ) -> LocalTableInContextMut<'_, Vec<(Ty<'tcx>, VariantIdx, FieldIdx)>> { + LocalTableInContextMut { hir_owner: self.hir_owner, data: &mut self.btf_field_info_data } + } } /// A resolved splatted function call. diff --git a/compiler/rustc_mir_build/src/thir/cx/expr.rs b/compiler/rustc_mir_build/src/thir/cx/expr.rs index 3cade7d6a0a7a..aeda36e623af1 100644 --- a/compiler/rustc_mir_build/src/thir/cx/expr.rs +++ b/compiler/rustc_mir_build/src/thir/cx/expr.rs @@ -1,6 +1,6 @@ use itertools::Itertools; use rustc_abi::{FIRST_VARIANT, FieldIdx, Size, VariantIdx}; -use rustc_ast::UnsafeBinderCastKind; +use rustc_ast::{BtfRelocKind, UnsafeBinderCastKind}; use rustc_data_structures::thin_vec::ThinVec; use rustc_hir as hir; use rustc_hir::attrs::lang_items::LangItem; @@ -1182,6 +1182,99 @@ impl<'tcx> ThirBuildCx<'tcx> { ExprKind::WrapUnsafeBinder { source: mirrored } } + hir::ExprKind::BtfFieldInfo(kind, _, _) => { + let indices = self.typeck_results.btf_field_info_data().get(expr.hir_id).unwrap(); + if indices.is_empty() { + return match kind { + BtfRelocKind::ByteOffset | BtfRelocKind::ByteSize => mk_expr( + ExprKind::NonHirLiteral { + lit: ScalarInt::try_from_target_usize(0u128, tcx).unwrap(), + user_ty: None, + }, + tcx.types.usize, + ), + BtfRelocKind::Exists => mk_expr( + ExprKind::NonHirLiteral { lit: false.into(), user_ty: None }, + tcx.types.bool, + ), + }; + } + + let preserve_access_index = + tcx.require_lang_item(LangItem::BtfPreserveAccessIndex, expr.span); + let preserve_field_info = + tcx.require_lang_item(LangItem::BtfPreserveFieldInfo, expr.span); + let unit_ptr_ty = Ty::new_imm_ptr(tcx, tcx.types.unit); + let mk_u32_kind = |value: u32| ExprKind::NonHirLiteral { + lit: ScalarInt::try_from_uint(value, Size::from_bits(32)).unwrap(), + user_ty: None, + }; + + // The access-index intrinsics use an opaque pointer only to carry the field path + // from one call to the next. No memory is accessed through this null pointer. + let zero = self.thir.exprs.push(mk_expr( + ExprKind::NonHirLiteral { + lit: ScalarInt::try_from_target_usize(0u128, tcx).unwrap(), + user_ty: None, + }, + tcx.types.usize, + )); + let mut field_ptr = + self.thir.exprs.push(mk_expr(ExprKind::Cast { source: zero }, unit_ptr_ty)); + + for &(container_ty, variant, field) in indices { + let fun_ty = tcx + .type_of(preserve_access_index) + .instantiate(tcx, &[container_ty.into()]) + .skip_norm_wip(); + let fun = self + .thir + .exprs + .push(mk_expr(ExprKind::ZstLiteral { user_ty: None }, fun_ty)); + let variant = + self.thir.exprs.push(mk_expr(mk_u32_kind(variant.as_u32()), tcx.types.u32)); + let field = + self.thir.exprs.push(mk_expr(mk_u32_kind(field.as_u32()), tcx.types.u32)); + field_ptr = self.thir.exprs.push(mk_expr( + ExprKind::Call { + ty: fun_ty, + fun, + args: Box::new([field_ptr, variant, field]), + from_hir_call: false, + fn_span: expr.span, + }, + unit_ptr_ty, + )); + } + + let fun_ty = + tcx.type_of(preserve_field_info).instantiate_identity().skip_norm_wip(); + let fun = + self.thir.exprs.push(mk_expr(ExprKind::ZstLiteral { user_ty: None }, fun_ty)); + let info_kind = + self.thir.exprs.push(mk_expr(mk_u32_kind(kind.code()), tcx.types.u32)); + let info = self.thir.exprs.push(mk_expr( + ExprKind::Call { + ty: fun_ty, + fun, + args: Box::new([field_ptr, info_kind]), + from_hir_call: false, + fn_span: expr.span, + }, + tcx.types.u32, + )); + + match kind { + BtfRelocKind::ByteOffset | BtfRelocKind::ByteSize => { + ExprKind::Cast { source: info } + } + BtfRelocKind::Exists => { + let zero = self.thir.exprs.push(mk_expr(mk_u32_kind(0), tcx.types.u32)); + ExprKind::Binary { op: BinOp::Ne, lhs: info, rhs: zero } + } + } + } + hir::ExprKind::DropTemps(source) => ExprKind::Use { source: self.mirror_expr(source) }, hir::ExprKind::Array(fields) => ExprKind::Array { fields: self.mirror_exprs(fields) }, hir::ExprKind::Tup(fields) => ExprKind::Tuple { fields: self.mirror_exprs(fields) }, diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 3e03730ab632b..75336c57faee1 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -13,9 +13,9 @@ use rustc_ast::util::parser::{AssocOp, ExprPrecedence, Fixity, prec_let_scrutine use rustc_ast::visit::{Visitor, walk_expr}; use rustc_ast::{ self as ast, AnonConst, Arm, AssignOp, AssignOpKind, AttrStyle, AttrVec, BinOp, BinOpKind, - BlockCheckMode, CaptureBy, ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, ExprKind, FnDecl, - FnRetTy, ForLoop, Guard, Label, MacCall, MetaItemLit, Movability, Param, RangeLimits, StmtKind, - Ty, TyKind, UnOp, UnsafeBinderCastKind, YieldKind, + BlockCheckMode, BtfRelocKind, CaptureBy, ClosureBinder, DUMMY_NODE_ID, Expr, ExprField, + ExprKind, FnDecl, FnRetTy, ForLoop, Guard, Label, MacCall, MetaItemLit, Movability, Param, + RangeLimits, StmtKind, Ty, TyKind, UnOp, UnsafeBinderCastKind, YieldKind, }; use rustc_ast_pretty::pprust; use rustc_errors::{Applicability, Diag, PResult, StashKey, Subdiagnostic}; @@ -2050,6 +2050,21 @@ impl<'a> Parser<'a> { sym::unwrap_binder => { Some(this.parse_expr_unsafe_binder_cast(lo, UnsafeBinderCastKind::Unwrap)?) } + sym::btf_field_byte_offset => Some(this.parse_expr_btf_field_info( + lo, + ident.as_str(), + BtfRelocKind::ByteOffset, + )?), + sym::btf_field_byte_size => Some(this.parse_expr_btf_field_info( + lo, + ident.as_str(), + BtfRelocKind::ByteSize, + )?), + sym::btf_field_exists => Some(this.parse_expr_btf_field_info( + lo, + ident.as_str(), + BtfRelocKind::Exists, + )?), _ => None, }) }) @@ -2135,6 +2150,37 @@ impl<'a> Parser<'a> { Ok(self.mk_expr(span, ExprKind::UnsafeBinderCast(kind, expr, ty))) } + pub(crate) fn parse_expr_btf_field_info( + &mut self, + lo: Span, + name: &str, + kind: BtfRelocKind, + ) -> PResult<'a, Box> { + let container = self.parse_ty()?; + self.expect(exp!(Comma))?; + + let fields = self.parse_floating_field_access()?; + let trailing_comma = self.eat_noexpect(&TokenKind::Comma); + + if let Err(mut e) = self.expect_one_of(&[], &[exp!(CloseParen)]) { + if trailing_comma { + e.note(format!("unexpected third argument to {name}")); + } else { + e.note(format!("{name} expects dot-separated field names")); + } + } + + // Eat tokens until the macro call ends. + if self.may_recover() { + while !self.token.kind.is_close_delim_or_eof() { + self.bump(); + } + } + + let span = lo.to(self.token.span); + Ok(self.mk_expr(span, ExprKind::BtfFieldInfo(kind, container, fields))) + } + /// Returns a string literal if the next token is a string literal. /// In case of error returns `Some(lit)` if the next token is a literal with a wrong kind, /// and returns `None` if the next token is not literal at all. @@ -4539,6 +4585,7 @@ impl MutVisitor for CondChecker<'_> { | ExprKind::FormatArgs(_) | ExprKind::Err(_) | ExprKind::DirectConstArg(_) + | ExprKind::BtfFieldInfo(..) | ExprKind::Dummy => { // These would forbid any let expressions they contain already. } diff --git a/compiler/rustc_passes/src/input_stats.rs b/compiler/rustc_passes/src/input_stats.rs index 87193b73a1a95..58edf8542e495 100644 --- a/compiler/rustc_passes/src/input_stats.rs +++ b/compiler/rustc_passes/src/input_stats.rs @@ -380,6 +380,7 @@ impl<'v> hir_visit::Visitor<'v> for StatCollector<'v> { Repeat, Yield, UnsafeBinderCast, + BtfFieldInfo, Err ] ); @@ -661,7 +662,7 @@ impl<'v> ast_visit::Visitor<'v> for StatCollector<'v> { If, While, ForLoop, Loop, Match, Closure, Block, Await, Move, Use, TryBlock, Assign, AssignOp, Field, Index, Range, Underscore, Path, AddrOf, Break, Continue, Ret, InlineAsm, FormatArgs, OffsetOf, MacCall, Struct, Repeat, Paren, Try, Yield, Yeet, - Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy, DirectConstArg + Become, IncludedBytes, Gen, UnsafeBinderCast, Err, Dummy, DirectConstArg, BtfFieldInfo ] ); ast_visit::walk_expr(self, e) diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 397fb13705a1d..d88536ee43950 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -553,6 +553,11 @@ symbols! { breg, bridge, bswap, + btf_field_byte_offset, + btf_field_byte_size, + btf_field_exists, + btf_preserve_access_index, + btf_preserve_field_info, btf_relocatable, btf_relocations, built, diff --git a/compiler/rustc_ty_utils/src/consts.rs b/compiler/rustc_ty_utils/src/consts.rs index cd35423c5ef14..b0b82e6df542d 100644 --- a/compiler/rustc_ty_utils/src/consts.rs +++ b/compiler/rustc_ty_utils/src/consts.rs @@ -201,7 +201,6 @@ fn recurse_build<'tcx>( ExprKind::InlineAsm { .. } => { error(GenericConstantTooComplexSub::InlineAsmNotSupported(node.span))? } - // we dont permit let stmts so `VarRef` and `UpvarRef` cant happen ExprKind::VarRef { .. } | ExprKind::UpvarRef { .. } diff --git a/tests/ui/macros/stringify.rs b/tests/ui/macros/stringify.rs index 1a65ef7200f52..b167a27bbdd8d 100644 --- a/tests/ui/macros/stringify.rs +++ b/tests/ui/macros/stringify.rs @@ -338,6 +338,8 @@ fn test_expr() { // ExprKind::FormatArgs: untestable because this test works pre-expansion. + // ExprKind::BtfFieldInfo: untestable because this test works pre-expansion. + // ExprKind::Err: untestable. // Ones involving attributes. From e2e0d5984312465893d8f5e002a44e8297cecf84 Mon Sep 17 00:00:00 2001 From: vad Date: Tue, 15 Sep 2026 14:43:15 +0200 Subject: [PATCH 4/5] Add LLVM support for BTF relocations Expose wrappers for the `llvm.preserve.struct.access.index` and `llvm.preserve.union.access.index` intrinsics. Lower backend-neutral BTF field paths by mapping Rust field indices to LLVM aggregate indices and emitting the corresponding intrinsic calls (`llvm.preserve.{struct,union}.access.index`). Pass the resulting field pointer to `llvm.bpf.preserve.field.info`. --- compiler/rustc_codegen_llvm/src/builder.rs | 76 ++++++++++++++++++- compiler/rustc_codegen_llvm/src/llvm/ffi.rs | 16 ++++ .../rustc_llvm/llvm-wrapper/RustWrapper.cpp | 16 ++++ 3 files changed, 107 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_llvm/src/builder.rs b/compiler/rustc_codegen_llvm/src/builder.rs index b8e2b0029167a..f7dfc6ce79e53 100644 --- a/compiler/rustc_codegen_llvm/src/builder.rs +++ b/compiler/rustc_codegen_llvm/src/builder.rs @@ -7,7 +7,7 @@ pub(crate) mod autodiff; pub(crate) mod gpu_offload; use libc::{c_char, c_uint}; -use rustc_abi::{self as abi, Align, CanonAbi, Size, WrappingRange}; +use rustc_abi::{self as abi, Align, CanonAbi, FieldIdx, Size, VariantIdx, WrappingRange}; use rustc_codegen_ssa::MemFlags; use rustc_codegen_ssa::common::{IntPredicate, RealPredicate, SynchronizationScope, TypeKind}; use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue}; @@ -16,6 +16,7 @@ use rustc_codegen_ssa::traits::*; use rustc_data_structures::small_c_str::SmallCStr; use rustc_hir::attrs::{AttributeKind, UnrollAttr}; use rustc_hir::def_id::DefId; +use rustc_middle::bug; use rustc_middle::middle::codegen_fn_attrs::CodegenFnAttrs; use rustc_middle::ty::layout::{ FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTypingEnv, LayoutError, LayoutOfHelpers, @@ -34,6 +35,7 @@ use crate::abi::FnAbiLlvmExt; use crate::attributes; use crate::common::Funclet; use crate::context::{CodegenCx, FullCx, GenericCx, SCx}; +use crate::debuginfo::metadata::type_di_node; use crate::llvm::{ self, AtomicOrdering, AtomicRmwBinOp, BasicBlock, FromGeneric, GEPNoWrapFlags, Metadata, TRUE, ToLlvmBool, Type, Value, @@ -1545,6 +1547,78 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> { let cold_inline = llvm::AttributeKind::Cold.create_attr(self.llcx); attributes::apply_to_callsite(llret, llvm::AttributePlace::Function, &[cold_inline]); } + + fn btf_preserve_access_index( + &mut self, + base: &'ll Value, + container_ty: Ty<'tcx>, + variant: VariantIdx, + field: FieldIdx, + ) -> &'ll Value { + fn llvm_struct_field_index<'ll, 'tcx>( + bx: &Builder<'_, 'll, 'tcx>, + layout: TyAndLayout<'tcx>, + field_index: usize, + ) -> usize { + let mut llvm_index = 0; + let mut offset = Size::ZERO; + + for i in layout.fields.index_by_increasing_offset() { + let target_offset = layout.fields.offset(i as usize); + if target_offset != offset { + llvm_index += 1; + } + if i as usize == field_index { + return llvm_index; + } + + let field = layout.field(bx.cx(), i); + llvm_index += 1; + offset = target_offset + field.size; + } + + bug!("field index {field_index} not found in layout {layout:#?}") + } + + let layout_cx = ty::layout::LayoutCx::new(self.tcx, self.typing_env()); + let layout = self.layout_of(container_ty).for_variant(&layout_cx, variant); + match container_ty.kind() { + ty::Adt(adt, _) if adt.is_union() => { + let dbg_info: &'ll Metadata = type_di_node(self.cx, container_ty); + unsafe { + llvm::LLVMRustBuildPreserveUnionAccessIndex( + self.llbuilder, + base, + field.index() as c_uint, + Some(dbg_info), + ) + } + } + ty::Adt(..) | ty::Tuple(..) => { + let llvm_index = llvm_struct_field_index(self, layout, field.index()); + let dbg_info: &'ll Metadata = type_di_node(self.cx, container_ty); + unsafe { + llvm::LLVMRustBuildPreserveStructAccessIndex( + self.llbuilder, + self.cx().backend_type(layout), + base, + llvm_index as c_uint, + field.index() as c_uint, + Some(dbg_info), + ) + } + } + _ => bug!("BTF field info query has unsupported container type: {container_ty:?}"), + } + } + + fn btf_preserve_field_info(&mut self, field: &'ll Value, kind: u64) -> &'ll Value { + self.call_intrinsic( + "llvm.bpf.preserve.field.info", + &[self.val_ty(field)], + &[field, self.const_u64(kind)], + ) + } } impl<'ll> StaticBuilderMethods for Builder<'_, 'll, '_> { diff --git a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs index 1a60b59a93525..36e4b657173ea 100644 --- a/compiler/rustc_codegen_llvm/src/llvm/ffi.rs +++ b/compiler/rustc_codegen_llvm/src/llvm/ffi.rs @@ -1711,6 +1711,22 @@ unsafe extern "C" { NumBundles: c_uint, Name: *const c_char, ) -> &'a Value; + + // BTF relocations + pub(crate) fn LLVMRustBuildPreserveUnionAccessIndex<'a>( + B: &Builder<'a>, + Base: &'a Value, + FieldIndex: c_uint, + DbgInfo: Option<&'a Metadata>, + ) -> &'a Value; + pub(crate) fn LLVMRustBuildPreserveStructAccessIndex<'a>( + B: &Builder<'a>, + ElTy: &'a Type, + Base: &'a Value, + Index: c_uint, + FieldIndex: c_uint, + DbgInfo: Option<&'a Metadata>, + ) -> &'a Value; } // FFI bindings for `DIBuilder` functions in the LLVM-C API. diff --git a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp index 983a506bd4ac6..9b07148d32fce 100644 --- a/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp +++ b/compiler/rustc_llvm/llvm-wrapper/RustWrapper.cpp @@ -1776,6 +1776,22 @@ extern "C" LLVMValueRef LLVMRustConstPtrAuth(LLVMValueRef Ptr, uint32_t Key, #endif } +extern "C" LLVMValueRef +LLVMRustBuildPreserveUnionAccessIndex(LLVMBuilderRef B, LLVMValueRef Base, + unsigned FieldIndex, + LLVMMetadataRef DbgInfo) { + return wrap(unwrap(B)->CreatePreserveUnionAccessIndex( + unwrap(Base), FieldIndex, unwrapDI(DbgInfo))); +} + +extern "C" LLVMValueRef LLVMRustBuildPreserveStructAccessIndex( + LLVMBuilderRef B, LLVMTypeRef ElTy, LLVMValueRef Base, unsigned Index, + unsigned FieldIndex, LLVMMetadataRef DbgInfo) { + return wrap(unwrap(B)->CreatePreserveStructAccessIndex( + unwrap(ElTy), unwrap(Base), Index, FieldIndex, + unwrapDI(DbgInfo))); +} + // Statically assert that the fixed metadata kind IDs declared in // `metadata_kind.rs` match the ones actually used by LLVM. #define FIXED_MD_KIND(VARIANT, VALUE) \ From 2a1be298704e81d04037bcc85c75a7a01b788b20 Mon Sep 17 00:00:00 2001 From: vad Date: Tue, 15 Sep 2026 14:44:34 +0200 Subject: [PATCH 5/5] Add codegen test for BTF relocations Test the `btf_field_exists`, `btf_field_byte_offset` and `btf_field_byte_size` builtins and make sure they emit correct LLVM intrinsic calls. --- tests/auxiliary/minicore.rs | 14 +++ tests/codegen-llvm/btf-field-info-minicore.rs | 103 ++++++++++++++++++ 2 files changed, 117 insertions(+) create mode 100644 tests/codegen-llvm/btf-field-info-minicore.rs diff --git a/tests/auxiliary/minicore.rs b/tests/auxiliary/minicore.rs index c7e8c3eba8e22..fea11f86f2324 100644 --- a/tests/auxiliary/minicore.rs +++ b/tests/auxiliary/minicore.rs @@ -358,6 +358,20 @@ trait Drop { #[rustc_intrinsic] pub const unsafe fn copy_nonoverlapping(src: *const T, dst: *mut T, count: usize); +#[rustc_nounwind] +#[rustc_intrinsic] +#[lang = "btf_preserve_access_index"] +pub fn btf_preserve_access_index( + base: *const (), + variant: u32, + field: u32, +) -> *const (); + +#[rustc_nounwind] +#[rustc_intrinsic] +#[lang = "btf_preserve_field_info"] +pub fn btf_preserve_field_info(field: *const (), kind: u32) -> u32; + pub mod mem { #[rustc_nounwind] #[rustc_intrinsic] diff --git a/tests/codegen-llvm/btf-field-info-minicore.rs b/tests/codegen-llvm/btf-field-info-minicore.rs new file mode 100644 index 0000000000000..d49d7833ebf75 --- /dev/null +++ b/tests/codegen-llvm/btf-field-info-minicore.rs @@ -0,0 +1,103 @@ +//@ add-minicore +//@ needs-llvm-components: bpf +//@ compile-flags: --target bpfel-unknown-none -Cdebuginfo=2 + +#![feature(allow_internal_unstable, btf_relocations, decl_macro, no_core)] +#![no_core] +#![no_std] +#![no_main] + +extern crate minicore; +use minicore::*; + +#[allow_internal_unstable(builtin_syntax)] +pub macro field_byte_offset($Container:ty, $($fields:expr)+ $(,)?) {{ + if builtin # btf_field_exists($Container, $($fields)+) { + ::minicore::Option::Some(builtin # btf_field_byte_offset($Container, $($fields)+)) + } else { + ::minicore::Option::None + } +}} + +#[allow_internal_unstable(builtin_syntax)] +pub macro field_byte_size($Container:ty, $($fields:expr)+ $(,)?) {{ + if builtin # btf_field_exists($Container, $($fields)+) { + ::minicore::Option::Some(builtin # btf_field_byte_size($Container, $($fields)+)) + } else { + ::minicore::Option::None + } +}} + +#[btf_relocatable] +#[repr(C)] +pub struct Inner { + pub x: u32, + pub y: u64, +} + +#[btf_relocatable] +#[repr(C)] +pub union Payload { + pub word: u64, + pub half: u32, +} + +#[btf_relocatable] +#[repr(C)] +pub struct Outer { + pub pad: u32, + pub inner: Inner, + pub payload: Payload, +} + +// Each `Option` query emits a `BPF_CORE_FIELD_EXISTS` relocation (kind 2), followed by either +// `BPF_CORE_FIELD_BYTE_OFFSET` (kind 0) or `BPF_CORE_FIELD_BYTE_SIZE` (kind 1). The value between +// the second and third colons is the compile-time fallback. +// +// CHECK-DAG: @"llvm.Outer:2:1$0:1" = external global i32, !llvm.preserve.access.index +// CHECK-DAG: @"llvm.Outer:0:8$0:1" = external global i32, !llvm.preserve.access.index +// CHECK-DAG: @"llvm.Outer:1:16$0:1" = external global i32, !llvm.preserve.access.index +// +// CHECK-DAG: @"llvm.Outer:2:1$0:1:1" = external global i32, !llvm.preserve.access.index +// CHECK-DAG: @"llvm.Outer:0:16$0:1:1" = external global i32, !llvm.preserve.access.index +// CHECK-DAG: @"llvm.Outer:1:8$0:1:1" = external global i32, !llvm.preserve.access.index +// +// CHECK-DAG: @"llvm.Outer:2:1$0:2:1" = external global i32, !llvm.preserve.access.index +// CHECK-DAG: @"llvm.Outer:0:24$0:2:1" = external global i32, !llvm.preserve.access.index +// CHECK-DAG: @"llvm.Outer:1:4$0:2:1" = external global i32, !llvm.preserve.access.index + +// CHECK-LABEL: define{{.*}} @field_offset( +#[unsafe(no_mangle)] +pub fn field_offset() -> Option { + field_byte_offset!(Outer, inner) +} + +// CHECK-LABEL: define{{.*}} @field_size( +#[unsafe(no_mangle)] +pub fn field_size() -> Option { + field_byte_size!(Outer, inner) +} + +// CHECK-LABEL: define{{.*}} @nested_field_offset( +#[unsafe(no_mangle)] +pub fn nested_field_offset() -> Option { + field_byte_offset!(Outer, inner.y) +} + +// CHECK-LABEL: define{{.*}} @nested_field_size( +#[unsafe(no_mangle)] +pub fn nested_field_size() -> Option { + field_byte_size!(Outer, inner.y) +} + +// CHECK-LABEL: define{{.*}} @union_field_offset( +#[unsafe(no_mangle)] +pub fn union_field_offset() -> Option { + field_byte_offset!(Outer, payload.half) +} + +// CHECK-LABEL: define{{.*}} @union_field_size( +#[unsafe(no_mangle)] +pub fn union_field_size() -> Option { + field_byte_size!(Outer, payload.half) +}