Skip to content

Commit 1a0a383

Browse files
committed
[BPF] Support wrapping BPF map structs into nested, single field structs
In Aya/Rust, BPF map definitions are nested in two wrapper types: * Structs representing the given map type (`HashMap`, `RingBuf` etc.), which provide methods associated with that type. This way, we can define methods like `HashMap::get` or `RingBuf::reserve`, making the interaction with maps more "rusty". * `UnsafeCell`, which lets the Rust compiler know that such type is thread safe, can be defined as a static (global) variable and can be safely mutated. Linux kernel already guarantees the thread safety of map operations. The type hierarchy of such nested map definitions usually looks like: ```rust pub struct HashMap<K, V, const M: usize, const F: usize = 0>( HashMapCell<K, V, M, F> ); pub type HashMapCell<K, V, const M: usize, const F: usize = 0> = UnsafeCell<HashMapDef<K, V, M, F>>; const BPF_MAP_TYPE_HASH: usize = 1; pub struct HashMapDef<K, V, const M: usize, const F: usize = 0> { r#type: *const [i32; BPF_MAP_TYPE_HASH], key: *const K, value: *const V, max_entries: *const [i32; M], map_flags: *const [i32; F], } ``` The map type is then used in a global variable, defined in the BPF program code: ```rust #[link_section = ".maps"] static HASH_MAP: HashMap<u32, u32, 10> = HashMap::new(); ``` Which is an equivalent of the following BPF map definition in C: ```c #define BPF_MAP_TYPE_HASH 1 struct { int (*type)[BPF_MAP_TYPE_HASH]; typeof(int) *key; typeof(int) *value; int (*max_entries)[10]; } map_1 __attribute__((section(".maps"))); ``` Visiting such map requires visiting the following fields, in order to get to the actual map definition (`HashMapDef`): ``` HASH_MAP -> __0 -> value ``` Before this change, the BPF backend was visiting only the fields of the outer struct, without traversing them deeper. That caused problems with maps using custom structs as keys and/or values, like: ```rust // Define custom structs for key and values. pub struct MyKey(u32); pub struct MyValue(u32); #[link_section = ".maps"] #[export_name = "HASH_MAP"] pub static HASH_MAP: HashMap<MyKey, MyValue, 10> = HashMap::new(); ``` Because the `MyKey` and `MyValue` types didn't end up being actually visited, and therefore ended up as a forward declaration in BTF: ``` #30: <FWD> 'MyKey' kind:struct #31: <FWD> 'MyValue' kind:struct ``` Fix that by looking in the map definitions recursively, if they turn out to be single-field wrappers. The correct BTF for these types, after the fix: ``` #6: <STRUCT> 'MyKey' sz:4 n:1 #00 '__0' off:0 --> [7] #7: <INT> 'u32' bits:32 off:0 #8: <PTR> --> [9] #9: <STRUCT> 'MyValue' sz:4 n:1 #00 '__0' off:0 --> [7] ``` Fixes: #143361
1 parent 891a2c3 commit 1a0a383

File tree

2 files changed

+622
-2
lines changed

2 files changed

+622
-2
lines changed

llvm/lib/Target/BPF/BTFDebug.cpp

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@
1818
#include "llvm/CodeGen/AsmPrinter.h"
1919
#include "llvm/CodeGen/MachineModuleInfo.h"
2020
#include "llvm/CodeGen/MachineOperand.h"
21+
#include "llvm/IR/DebugInfoMetadata.h"
2122
#include "llvm/IR/Module.h"
2223
#include "llvm/MC/MCContext.h"
2324
#include "llvm/MC/MCObjectFileInfo.h"
2425
#include "llvm/MC/MCSectionELF.h"
2526
#include "llvm/MC/MCStreamer.h"
27+
#include "llvm/Support/Casting.h"
2628
#include "llvm/Support/LineIterator.h"
2729
#include "llvm/Support/MemoryBuffer.h"
2830
#include "llvm/Target/TargetLoweringObjectFile.h"
@@ -976,11 +978,23 @@ void BTFDebug::visitMapDefType(const DIType *Ty, uint32_t &TypeId) {
976978
if (Tag != dwarf::DW_TAG_structure_type || CTy->isForwardDecl())
977979
return;
978980

979-
// Visit all struct members to ensure pointee type is visited
981+
// Visit all struct members to ensure their types are visited.
980982
const DINodeArray Elements = CTy->getElements();
981983
for (const auto *Element : Elements) {
982984
const auto *MemberType = cast<DIDerivedType>(Element);
983-
visitTypeEntry(MemberType->getBaseType());
985+
const DIType *MemberBaseType = MemberType->getBaseType();
986+
987+
// If the member is a composite type, that may indicate the currently
988+
// visited composite type is a wrapper, and the member represents the
989+
// actual map definition.
990+
// In that case, visit the member with `visitMapDefType` instead of
991+
// `visitTypeEntry`, treating it specifically as a map definition rather
992+
// than as a regular composite type.
993+
const auto *MemberCTy = dyn_cast<DICompositeType>(MemberBaseType);
994+
if (MemberCTy) {
995+
visitMapDefType(MemberBaseType, TypeId);
996+
} else
997+
visitTypeEntry(MemberBaseType);
984998
}
985999

9861000
// Visit this type, struct or a const/typedef/volatile/restrict type

0 commit comments

Comments
 (0)