Skip to content

Commit b569708

Browse files
committed
compiler, library: Add BTF field-info relocation intrinsics
BTF, the BPF Type Format, encodes type information for both the running Linux kernel 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". `offset_of` always folds to a plain layout constant and does not preserve enough information for BTF CO-RE relocation emission. As a result, it is not suitable for relocatable field queries on BPF targets. Add three intrinsics for BTF field metadata queries: * `btf_field_byte_offset` * `btf_field_byte_size` * `btf_field_exists` Their availability is hidden behind the `btf_relocations` feature gate. Unlike `offset_of`, they remain visible to backend codegen and can lower to relocatable field-info queries instead of immediate layout constants. For LLVM, lower these intrinsics through `@llvm.bpf.preserve.field.info` with the corresponding query kind. The necessary `@llvm.preserve.{struct,array,union}.access.index` chain is constructed internally during lowering, but it is not exposed as part of the user-facing API. On targets or backends without BTF relocation support, fall back to: * The field offset for `btf_field_byte_offset`. * The field size for `btf_field_byte_size`. * `true` for `btf_field_exists`. The language-level design for this feature is proposed in rust-lang/rfcs#3966.
1 parent 2bd7a97 commit b569708

24 files changed

Lines changed: 571 additions & 7 deletions

File tree

compiler/rustc_abi/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ bitflags! {
9696
/// See [`TyAndLayout::pass_indirectly_in_non_rustic_abis`] for details.
9797
const PASS_INDIRECTLY_IN_NON_RUSTIC_ABIS = 1 << 5;
9898
const IS_SCALABLE = 1 << 6;
99+
const IS_BTF = 1 << 7;
99100
// Any of these flags being set prevent field reordering optimisation.
100101
const FIELD_ORDER_UNOPTIMIZABLE = ReprFlags::IS_C.bits()
101102
| ReprFlags::IS_SIMD.bits()
@@ -203,6 +204,11 @@ impl ReprOptions {
203204
self.flags.contains(ReprFlags::IS_LINEAR)
204205
}
205206

207+
#[inline]
208+
pub fn btf(&self) -> bool {
209+
self.flags.contains(ReprFlags::IS_BTF)
210+
}
211+
206212
/// Returns the discriminant type, given these `repr` options.
207213
/// This must only be called on enums!
208214
///

compiler/rustc_ast_passes/src/feature_gate.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,14 @@ impl<'a> Visitor<'a> for PostExpansionVisitor<'a> {
217217
"SIMD types are experimental and possibly buggy"
218218
);
219219
}
220+
if item.has_name(sym::btf) {
221+
gate!(
222+
&self,
223+
btf_relocations,
224+
attr.span,
225+
"BTF relocations are experimental"
226+
);
227+
}
220228
}
221229
}
222230
}

compiler/rustc_attr_parsing/src/attributes/repr.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ fn parse_repr<S: Stage>(cx: &AcceptContext<'_, '_, S>, param: &MetaItemParser) -
143143
(Some(sym::C), ArgParser::NoArgs) => Some(ReprC),
144144
(Some(sym::simd), ArgParser::NoArgs) => Some(ReprSimd),
145145
(Some(sym::transparent), ArgParser::NoArgs) => Some(ReprTransparent),
146+
(Some(sym::btf), ArgParser::NoArgs) => Some(ReprBtf),
146147
(Some(name @ int_pat!()), ArgParser::NoArgs) => {
147148
// int_pat!() should make sure it always parses
148149
Some(ReprInt(int_type_of_word(name).unwrap()))
@@ -154,6 +155,7 @@ fn parse_repr<S: Stage>(cx: &AcceptContext<'_, '_, S>, param: &MetaItemParser) -
154155
| name @ sym::C
155156
| name @ sym::simd
156157
| name @ sym::transparent
158+
| name @ sym::btf
157159
| name @ int_pat!(),
158160
),
159161
ArgParser::NameValue(_),
@@ -167,6 +169,7 @@ fn parse_repr<S: Stage>(cx: &AcceptContext<'_, '_, S>, param: &MetaItemParser) -
167169
| name @ sym::C
168170
| name @ sym::simd
169171
| name @ sym::transparent
172+
| name @ sym::btf
170173
| name @ int_pat!(),
171174
),
172175
ArgParser::List(_),

compiler/rustc_codegen_llvm/src/builder.rs

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,15 @@ pub(crate) mod gpu_offload;
88

99
use libc::{c_char, c_uint};
1010
use rustc_abi as abi;
11-
use rustc_abi::{Align, Size, WrappingRange};
11+
use rustc_abi::{Align, FieldIdx, Size, VariantIdx, WrappingRange};
1212
use rustc_codegen_ssa::MemFlags;
1313
use rustc_codegen_ssa::common::{IntPredicate, RealPredicate, SynchronizationScope, TypeKind};
1414
use rustc_codegen_ssa::mir::operand::{OperandRef, OperandValue};
1515
use rustc_codegen_ssa::mir::place::PlaceRef;
1616
use rustc_codegen_ssa::traits::*;
1717
use rustc_data_structures::small_c_str::SmallCStr;
1818
use rustc_hir::def_id::DefId;
19+
use rustc_middle::bug;
1920
use rustc_middle::middle::codegen_fn_attrs::{CodegenFnAttrs, TargetFeature, TargetFeatureKind};
2021
use rustc_middle::ty::layout::{
2122
FnAbiError, FnAbiOfHelpers, FnAbiRequest, HasTypingEnv, LayoutError, LayoutOfHelpers,
@@ -34,6 +35,7 @@ use crate::abi::FnAbiLlvmExt;
3435
use crate::attributes;
3536
use crate::common::Funclet;
3637
use crate::context::{CodegenCx, FullCx, GenericCx, SCx};
38+
use crate::debuginfo::metadata::type_di_node;
3739
use crate::llvm::{
3840
self, AtomicOrdering, AtomicRmwBinOp, BasicBlock, FromGeneric, GEPNoWrapFlags, Metadata, TRUE,
3941
ToLlvmBool, Type, Value,
@@ -936,6 +938,172 @@ impl<'a, 'll, 'tcx> BuilderMethods<'a, 'tcx> for Builder<'a, 'll, 'tcx> {
936938
}
937939
}
938940

941+
/* BTF relocations */
942+
fn btf_preserve_array_access_index(
943+
&mut self,
944+
base_ty: Ty<'tcx>,
945+
ty: &'ll Type,
946+
ptr: &'ll Value,
947+
dimension: u64,
948+
index: u64,
949+
) -> &'ll Value {
950+
if self.cx.tcx.sess.target.arch != Arch::Bpf || self.cx.dbg_cx.is_none() {
951+
let mut indices = Vec::with_capacity(dimension as usize + 1);
952+
for _ in 0..dimension {
953+
indices.push(self.const_usize(0));
954+
}
955+
indices.push(self.const_usize(index));
956+
return self.inbounds_gep(ty, ptr, &indices);
957+
}
958+
let dbg_info: &'ll Metadata = type_di_node(self.cx, base_ty);
959+
unsafe {
960+
llvm::LLVMRustBuildPreserveArrayAccessIndex(
961+
self.llbuilder,
962+
ty,
963+
ptr,
964+
dimension as c_uint,
965+
index as c_uint,
966+
Some(dbg_info),
967+
)
968+
}
969+
}
970+
971+
fn btf_preserve_struct_access_index(
972+
&mut self,
973+
base_ty: Ty<'tcx>,
974+
ty: &'ll Type,
975+
ptr: &'ll Value,
976+
gep_index: u64,
977+
field_index: u64,
978+
) -> &'ll Value {
979+
if self.cx.tcx.sess.target.arch != Arch::Bpf || self.cx.dbg_cx.is_none() {
980+
let zero = self.const_usize(0);
981+
let gep_index = self.const_usize(gep_index);
982+
return self.inbounds_gep(ty, ptr, &[zero, gep_index]);
983+
}
984+
let dbg_info: &'ll Metadata = type_di_node(self.cx, base_ty);
985+
unsafe {
986+
llvm::LLVMRustBuildPreserveStructAccessIndex(
987+
self.llbuilder,
988+
ty,
989+
ptr,
990+
gep_index as c_uint,
991+
field_index as c_uint,
992+
Some(dbg_info),
993+
)
994+
}
995+
}
996+
997+
fn btf_preserve_union_access_index(
998+
&mut self,
999+
base_ty: Ty<'tcx>,
1000+
ptr: &'ll Value,
1001+
field_index: u64,
1002+
) -> &'ll Value {
1003+
if self.cx.tcx.sess.target.arch != Arch::Bpf || self.cx.dbg_cx.is_none() {
1004+
return ptr;
1005+
}
1006+
let dbg_info: &'ll Metadata = type_di_node(self.cx, base_ty);
1007+
unsafe {
1008+
llvm::LLVMRustBuildPreserveUnionAccessIndex(
1009+
self.llbuilder,
1010+
ptr,
1011+
field_index as c_uint,
1012+
Some(dbg_info),
1013+
)
1014+
}
1015+
}
1016+
1017+
fn btf_field_info(
1018+
&mut self,
1019+
base_ty: Ty<'tcx>,
1020+
variant: VariantIdx,
1021+
field: FieldIdx,
1022+
kind: u32,
1023+
) -> &'ll Value {
1024+
const BPF_FIELD_BYTE_OFFSET: u32 = 0;
1025+
const BPF_FIELD_BYTE_SIZE: u32 = 1;
1026+
const BPF_FIELD_EXISTS: u32 = 2;
1027+
1028+
fn llvm_struct_field_index<'ll, 'tcx>(
1029+
bx: &Builder<'_, 'll, 'tcx>,
1030+
layout: TyAndLayout<'tcx>,
1031+
field_index: usize,
1032+
) -> usize {
1033+
let mut llvm_index = 0;
1034+
let mut offset = Size::ZERO;
1035+
1036+
for i in layout.fields.index_by_increasing_offset() {
1037+
let target_offset = layout.fields.offset(i as usize);
1038+
if target_offset != offset {
1039+
llvm_index += 1;
1040+
}
1041+
1042+
if i as usize == field_index {
1043+
return llvm_index;
1044+
}
1045+
1046+
let field = layout.field(bx.cx(), i);
1047+
llvm_index += 1;
1048+
offset = target_offset + field.size;
1049+
}
1050+
1051+
bug!("field index {field_index} not found in layout {layout:#?}")
1052+
}
1053+
1054+
let layout = self.layout_of(base_ty);
1055+
let cx = ty::layout::LayoutCx::new(self.tcx, self.typing_env());
1056+
let layout = layout.for_variant(&cx, variant);
1057+
let offset = layout.fields.offset(field.index()).bytes() as u32;
1058+
let field_layout = layout.field(self.cx(), field.index());
1059+
let size = field_layout.size.bytes() as u32;
1060+
1061+
if self.cx.tcx.sess.target.arch != Arch::Bpf || self.cx.dbg_cx.is_none() {
1062+
return match kind {
1063+
BPF_FIELD_BYTE_OFFSET => self.const_u32(offset),
1064+
BPF_FIELD_BYTE_SIZE => self.const_u32(size),
1065+
BPF_FIELD_EXISTS => self.const_u32(1),
1066+
_ => bug!("unsupported static fallback for btf_field_info kind {kind}"),
1067+
};
1068+
}
1069+
1070+
let base = self.const_null(self.type_ptr());
1071+
let field_ptr = match base_ty.kind() {
1072+
ty::Adt(adt, _) if adt.is_union() || adt.is_enum() => {
1073+
return match kind {
1074+
BPF_FIELD_BYTE_OFFSET => self.const_u32(offset),
1075+
BPF_FIELD_BYTE_SIZE => self.const_u32(size),
1076+
BPF_FIELD_EXISTS => self.const_u32(1),
1077+
_ => bug!("unsupported union or enum fallback for btf_field_info kind {kind}"),
1078+
};
1079+
}
1080+
ty::Adt(..) | ty::Tuple(..) => {
1081+
let llvm_index = llvm_struct_field_index(self, layout, field.index());
1082+
self.btf_preserve_struct_access_index(
1083+
base_ty,
1084+
self.cx().backend_type(layout),
1085+
base,
1086+
llvm_index as u64,
1087+
field.index() as u64,
1088+
)
1089+
}
1090+
_ => {
1091+
return match kind {
1092+
BPF_FIELD_BYTE_OFFSET => self.const_u32(offset),
1093+
BPF_FIELD_BYTE_SIZE => self.const_u32(size),
1094+
BPF_FIELD_EXISTS => self.const_u32(1),
1095+
_ => bug!("unsupported fallback for btf_field_info kind {kind}"),
1096+
};
1097+
}
1098+
};
1099+
1100+
self.call_intrinsic(
1101+
"llvm.bpf.preserve.field.info",
1102+
&[self.val_ty(field_ptr)],
1103+
&[field_ptr, self.const_u64(kind.into())],
1104+
)
1105+
}
1106+
9391107
/* Casts */
9401108
fn trunc(&mut self, val: &'ll Value, dest_ty: &'ll Type) -> &'ll Value {
9411109
unsafe { llvm::LLVMBuildTrunc(self.llbuilder, val, dest_ty, UNNAMED) }

compiler/rustc_codegen_llvm/src/llvm/ffi.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,6 +1399,30 @@ unsafe extern "C" {
13991399
Flags: GEPNoWrapFlags,
14001400
) -> &'a Value;
14011401

1402+
// BTF relocations
1403+
pub(crate) fn LLVMRustBuildPreserveArrayAccessIndex<'a>(
1404+
B: &Builder<'a>,
1405+
ElTy: &'a Type,
1406+
Base: &'a Value,
1407+
Dimension: c_uint,
1408+
LastIndex: c_uint,
1409+
DbgInfo: Option<&'a Metadata>,
1410+
) -> &'a Value;
1411+
pub(crate) fn LLVMRustBuildPreserveUnionAccessIndex<'a>(
1412+
B: &Builder<'a>,
1413+
Base: &'a Value,
1414+
FieldIndex: c_uint,
1415+
DbgInfo: Option<&'a Metadata>,
1416+
) -> &'a Value;
1417+
pub(crate) fn LLVMRustBuildPreserveStructAccessIndex<'a>(
1418+
B: &Builder<'a>,
1419+
ElTy: &'a Type,
1420+
Base: &'a Value,
1421+
Index: c_uint,
1422+
FieldIndex: c_uint,
1423+
DbgInfo: Option<&'a Metadata>,
1424+
) -> &'a Value;
1425+
14021426
// Casts
14031427
pub(crate) fn LLVMBuildTrunc<'a>(
14041428
B: &Builder<'a>,

compiler/rustc_codegen_ssa/src/mir/intrinsic.rs

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use rustc_abi::{Align, WrappingRange};
1+
use rustc_abi::{Align, FieldIdx, VariantIdx, WrappingRange};
22
use rustc_middle::mir::SourceInfo;
33
use rustc_middle::ty::{self, Ty, TyCtxt};
44
use rustc_middle::{bug, span_bug};
@@ -9,7 +9,7 @@ use rustc_target::spec::Arch;
99
use super::FunctionCx;
1010
use super::operand::OperandRef;
1111
use super::place::PlaceRef;
12-
use crate::common::{AtomicRmwBinOp, SynchronizationScope};
12+
use crate::common::{AtomicRmwBinOp, IntPredicate, SynchronizationScope};
1313
use crate::errors::InvalidMonomorphization;
1414
use crate::traits::*;
1515
use crate::{MemFlags, meth, size_of_val};
@@ -579,6 +579,36 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> {
579579
}
580580
}
581581

582+
sym::btf_field_byte_offset | sym::btf_field_byte_size | sym::btf_field_exists => {
583+
let tp_ty = fn_args.type_at(0);
584+
let Some(variant) = bx.const_to_opt_uint(args[0].immediate()) else {
585+
span_bug!(span, "`btf_field_*` variant must be a constant");
586+
};
587+
let Some(field) = bx.const_to_opt_uint(args[1].immediate()) else {
588+
span_bug!(span, "`btf_field_*` field must be a constant");
589+
};
590+
let variant: u32 = variant.try_into().unwrap_or_else(|_| {
591+
span_bug!(span, "`btf_field_*` variant does not fit in u32");
592+
});
593+
let kind = match name {
594+
sym::btf_field_byte_offset => 0,
595+
sym::btf_field_byte_size => 1,
596+
sym::btf_field_exists => 2,
597+
_ => bug!(),
598+
};
599+
let llval = bx.btf_field_info(
600+
tp_ty,
601+
VariantIdx::from_u32(variant),
602+
FieldIdx::from_usize(field as usize),
603+
kind,
604+
);
605+
if result.layout.ty.is_bool() {
606+
bx.icmp(IntPredicate::IntNE, llval, bx.const_u32(0))
607+
} else {
608+
bx.zext(llval, bx.type_isize())
609+
}
610+
}
611+
582612
sym::cold_path => {
583613
// This is a no-op. The intrinsic is just a hint to the optimizer.
584614
return Ok(());

0 commit comments

Comments
 (0)