[llvm] [BPF] Visit nested map array during BTF generation (PR #150608)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Jul 28 10:53:09 PDT 2025
https://github.com/mtardy updated https://github.com/llvm/llvm-project/pull/150608
>From 3819d8fe9a18bdc228099afa9041b13b64cb8eb6 Mon Sep 17 00:00:00 2001
From: Mahe Tardy <mahe.tardy at gmail.com>
Date: Thu, 24 Jul 2025 17:47:50 +0000
Subject: [PATCH 1/2] [BPF] Visit nested map array during BTF generation
Fixes missing inner map struct type definitions [^1]. We should visit
the type of nested array of maps like we do for global maps. We visit
the derived type of the array directly in visitMapDefType.
Because of the small changes in visitMapDefType, ordering of the BTF is
changed, for example, the PTR for the nested maps might appear later.
This explain why this patch had to update the BTF checks of the tests
'map-def-nested-array.ll', 'map-def-2.ll' and 'map-def-3.ll'. We took
the opportunity to migrate the last two to use 'print_btf.py'.
It ressembles commit 0d21c956a5c1 ("[BPF] Handle nested wrapper structs
in BPF map definition traversal (#144097)") which focused on directly
nested wrapper structs.
Before that patch, this ARRAY_OF_MAPS definition would lead to the BTF
information include the 'missing_type' as "FWD 'missing_type'
fwd_kind=struct":
struct missing_type { uint64_t foo; };
struct {
__uint(type, BPF_MAP_TYPE_ARRAY_OF_MAPS);
[...]
__array(
values, struct {
[...]
__type(value, struct missing_type);
});
} map SEC(".maps");
Which lead to errors while trying to load the map:
libbpf: map 'outer_map.inner': can't determine value size for type [N]: -22.
To solve this issue, users had to use the struct in a dummy variable or
in a dummy function for the BTF to be generated correctly [^2].
[^1]: https://lore.kernel.org/netdev/aH_cGvgC20iD8qs9@gmail.com/T/#u
[^2]: https://github.com/cilium/ebpf/discussions/1658#discussioncomment-12491339
Co-authored-by: Eduard Zingerman <eddyz87 at gmail.com>
Signed-off-by: Mahe Tardy <mahe.tardy at gmail.com>
---
llvm/lib/Target/BPF/BTFDebug.cpp | 48 +++++++-----
llvm/test/CodeGen/BPF/BTF/map-def-2.ll | 61 ++++-----------
llvm/test/CodeGen/BPF/BTF/map-def-3.ll | 42 +++--------
.../CodeGen/BPF/BTF/map-def-nested-array.ll | 75 +++++++++++++++++++
4 files changed, 127 insertions(+), 99 deletions(-)
create mode 100644 llvm/test/CodeGen/BPF/BTF/map-def-nested-array.ll
diff --git a/llvm/lib/Target/BPF/BTFDebug.cpp b/llvm/lib/Target/BPF/BTFDebug.cpp
index 1e29a0f1e85a1..6ee5667902837 100644
--- a/llvm/lib/Target/BPF/BTFDebug.cpp
+++ b/llvm/lib/Target/BPF/BTFDebug.cpp
@@ -957,13 +957,13 @@ void BTFDebug::visitMapDefType(const DIType *Ty, uint32_t &TypeId) {
return;
}
- // MapDef type may be a struct type or a non-pointer derived type
+ // MapDef type may be a struct type or a derived type
const DIType *OrigTy = Ty;
while (auto *DTy = dyn_cast<DIDerivedType>(Ty)) {
auto Tag = DTy->getTag();
if (Tag != dwarf::DW_TAG_typedef && Tag != dwarf::DW_TAG_const_type &&
Tag != dwarf::DW_TAG_volatile_type &&
- Tag != dwarf::DW_TAG_restrict_type)
+ Tag != dwarf::DW_TAG_restrict_type && Tag != dwarf::DW_TAG_pointer_type)
break;
Ty = DTy->getBaseType();
}
@@ -973,26 +973,34 @@ void BTFDebug::visitMapDefType(const DIType *Ty, uint32_t &TypeId) {
return;
auto Tag = CTy->getTag();
- if (Tag != dwarf::DW_TAG_structure_type || CTy->isForwardDecl())
+ if ((Tag != dwarf::DW_TAG_structure_type &&
+ Tag != dwarf::DW_TAG_array_type) ||
+ CTy->isForwardDecl())
return;
- // Visit all struct members to ensure their types are visited.
- const DINodeArray Elements = CTy->getElements();
- for (const auto *Element : Elements) {
- const auto *MemberType = cast<DIDerivedType>(Element);
- const DIType *MemberBaseType = MemberType->getBaseType();
-
- // If the member is a composite type, that may indicate the currently
- // visited composite type is a wrapper, and the member represents the
- // actual map definition.
- // In that case, visit the member with `visitMapDefType` instead of
- // `visitTypeEntry`, treating it specifically as a map definition rather
- // than as a regular composite type.
- const auto *MemberCTy = dyn_cast<DICompositeType>(MemberBaseType);
- if (MemberCTy) {
- visitMapDefType(MemberBaseType, TypeId);
- } else {
- visitTypeEntry(MemberBaseType);
+ // Visit potential nested map array
+ if (CTy->getTag() == dwarf::DW_TAG_array_type) {
+ // Jump to the element type of the array
+ visitMapDefType(CTy->getBaseType(), TypeId);
+ } else {
+ // Visit all struct members to ensure their types are visited.
+ const DINodeArray Elements = CTy->getElements();
+ for (const auto *Element : Elements) {
+ const auto *MemberType = cast<DIDerivedType>(Element);
+ const DIType *MemberBaseType = MemberType->getBaseType();
+
+ // If the member is a composite type, that may indicate the currently
+ // visited composite type is a wrapper, and the member represents the
+ // actual map definition.
+ // In that case, visit the member with `visitMapDefType` instead of
+ // `visitTypeEntry`, treating it specifically as a map definition rather
+ // than as a regular composite type.
+ const auto *MemberCTy = dyn_cast<DICompositeType>(MemberBaseType);
+ if (MemberCTy) {
+ visitMapDefType(MemberBaseType, TypeId);
+ } else {
+ visitTypeEntry(MemberBaseType);
+ }
}
}
diff --git a/llvm/test/CodeGen/BPF/BTF/map-def-2.ll b/llvm/test/CodeGen/BPF/BTF/map-def-2.ll
index 5f971ec1314d9..d4c836f7c479c 100644
--- a/llvm/test/CodeGen/BPF/BTF/map-def-2.ll
+++ b/llvm/test/CodeGen/BPF/BTF/map-def-2.ll
@@ -1,5 +1,6 @@
-; RUN: llc -mtriple=bpfel -filetype=asm -o - %s | FileCheck -check-prefixes=CHECK %s
-; RUN: llc -mtriple=bpfeb -filetype=asm -o - %s | FileCheck -check-prefixes=CHECK %s
+; RUN: llc -mtriple=bpfel -mcpu=v3 -filetype=obj -o %t1 %s
+; RUN: llvm-objcopy --dump-section='.BTF'=%t2 %t1
+; RUN: %python %p/print_btf.py %t2 | FileCheck -check-prefixes=CHECK-BTF %s
;
; Source code:
; struct key_type {
@@ -18,51 +19,17 @@
@hash_map = dso_local local_unnamed_addr global %struct.map_type zeroinitializer, section ".maps", align 8, !dbg !0
-; CHECK: .long 0 # BTF_KIND_PTR(id = 1)
-; CHECK-NEXT: .long 33554432 # 0x2000000
-; CHECK-NEXT: .long 2
-; CHECK-NEXT: .long 1 # BTF_KIND_STRUCT(id = 2)
-; CHECK-NEXT: .long 67108865 # 0x4000001
-; CHECK-NEXT: .long 4
-; CHECK-NEXT: .long 10
-; CHECK-NEXT: .long 3
-; CHECK-NEXT: .long 0 # 0x0
-; CHECK-NEXT: .long 13 # BTF_KIND_INT(id = 3)
-; CHECK-NEXT: .long 16777216 # 0x1000000
-; CHECK-NEXT: .long 4
-; CHECK-NEXT: .long 16777248 # 0x1000020
-; CHECK-NEXT: .long 17 # BTF_KIND_TYPEDEF(id = 4)
-; CHECK-NEXT: .long 134217728 # 0x8000000
-; CHECK-NEXT: .long 5
-; CHECK-NEXT: .long 28 # BTF_KIND_TYPEDEF(id = 5)
-; CHECK-NEXT: .long 134217728 # 0x8000000
-; CHECK-NEXT: .long 6
-; CHECK-NEXT: .long 38 # BTF_KIND_STRUCT(id = 6)
-; CHECK-NEXT: .long 67108865 # 0x4000001
-; CHECK-NEXT: .long 8
-; CHECK-NEXT: .long 47
-; CHECK-NEXT: .long 1
-; CHECK-NEXT: .long 0 # 0x0
-; CHECK-NEXT: .long 51 # BTF_KIND_VAR(id = 7)
-; CHECK-NEXT: .long 234881024 # 0xe000000
-; CHECK-NEXT: .long 4
-; CHECK-NEXT: .long 1
-; CHECK-NEXT: .long 60 # BTF_KIND_DATASEC(id = 8)
-; CHECK-NEXT: .long 251658241 # 0xf000001
-; CHECK-NEXT: .long 0
-; CHECK-NEXT: .long 7
-; CHECK-NEXT: .long hash_map
-; CHECK-NEXT: .long 8
-
-; CHECK: .ascii "key_type" # string offset=1
-; CHECK: .ascii "a1" # string offset=10
-; CHECK: .ascii "int" # string offset=13
-; CHECK: .ascii "__map_type" # string offset=17
-; CHECK: .ascii "_map_type" # string offset=28
-; CHECK: .ascii "map_type" # string offset=38
-; CHECK: .ascii "key" # string offset=47
-; CHECK: .ascii "hash_map" # string offset=51
-; CHECK: .ascii ".maps" # string offset=60
+; CHECK-BTF: [1] PTR '(anon)' type_id=2
+; CHECK-BTF-NEXT: [2] STRUCT 'key_type' size=4 vlen=1
+; CHECK-BTF-NEXT: 'a1' type_id=3 bits_offset=0
+; CHECK-BTF-NEXT: [3] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
+; CHECK-BTF-NEXT: [4] STRUCT 'map_type' size=8 vlen=1
+; CHECK-BTF-NEXT: 'key' type_id=1 bits_offset=0
+; CHECK-BTF-NEXT: [5] TYPEDEF '_map_type' type_id=4
+; CHECK-BTF-NEXT: [6] TYPEDEF '__map_type' type_id=5
+; CHECK-BTF-NEXT: [7] VAR 'hash_map' type_id=6, linkage=global
+; CHECK-BTF-NEXT: [8] DATASEC '.maps' size=0 vlen=1
+; CHECK-BTF-NEXT: type_id=7 offset=0 size=8
!llvm.dbg.cu = !{!2}
!llvm.module.flags = !{!16, !17, !18}
diff --git a/llvm/test/CodeGen/BPF/BTF/map-def-3.ll b/llvm/test/CodeGen/BPF/BTF/map-def-3.ll
index 6aa8af98493ec..1d95f03b0d5e8 100644
--- a/llvm/test/CodeGen/BPF/BTF/map-def-3.ll
+++ b/llvm/test/CodeGen/BPF/BTF/map-def-3.ll
@@ -1,5 +1,6 @@
-; RUN: llc -mtriple=bpfel -filetype=asm -o - %s | FileCheck -check-prefixes=CHECK %s
-; RUN: llc -mtriple=bpfeb -filetype=asm -o - %s | FileCheck -check-prefixes=CHECK %s
+; RUN: llc -mtriple=bpfel -mcpu=v3 -filetype=obj -o %t1 %s
+; RUN: llvm-objcopy --dump-section='.BTF'=%t2 %t1
+; RUN: %python %p/print_btf.py %t2 | FileCheck -check-prefixes=CHECK-BTF %s
;
; Source code:
; struct key_type {
@@ -13,36 +14,13 @@
@hash_map = dso_local local_unnamed_addr constant %struct.key_type zeroinitializer, section ".maps", align 4, !dbg !0
-; CHECK: .long 1 # BTF_KIND_INT(id = 1)
-; CHECK-NEXT: .long 16777216 # 0x1000000
-; CHECK-NEXT: .long 4
-; CHECK-NEXT: .long 16777248 # 0x1000020
-; CHECK-NEXT: .long 0 # BTF_KIND_CONST(id = 2)
-; CHECK-NEXT: .long 167772160 # 0xa000000
-; CHECK-NEXT: .long 3
-; CHECK-NEXT: .long 5 # BTF_KIND_STRUCT(id = 3)
-; CHECK-NEXT: .long 67108865 # 0x4000001
-; CHECK-NEXT: .long 4
-; CHECK-NEXT: .long 14
-; CHECK-NEXT: .long 1
-; CHECK-NEXT: .long 0 # 0x0
-; CHECK-NEXT: .long 17 # BTF_KIND_VAR(id = 4)
-; CHECK-NEXT: .long 234881024 # 0xe000000
-; CHECK-NEXT: .long 2
-; CHECK-NEXT: .long 1
-; CHECK-NEXT: .long 26 # BTF_KIND_DATASEC(id = 5)
-; CHECK-NEXT: .long 251658241 # 0xf000001
-; CHECK-NEXT: .long 0
-; CHECK-NEXT: .long 4
-; CHECK-NEXT: .long hash_map
-; CHECK-NEXT: .long 4
-
-; CHECK: .ascii "int" # string offset=1
-; CHECK: .ascii "key_type" # string offset=5
-; CHECK: .ascii "a1" # string offset=14
-; CHECK: .ascii "hash_map" # string offset=17
-; CHECK: .ascii ".maps" # string offset=26
-
+; CHECK-BTF: [1] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
+; CHECK-BTF-NEXT: [2] STRUCT 'key_type' size=4 vlen=1
+; CHECK-BTF-NEXT: 'a1' type_id=1 bits_offset=0
+; CHECK-BTF-NEXT: [3] CONST '(anon)' type_id=2
+; CHECK-BTF-NEXT: [4] VAR 'hash_map' type_id=3, linkage=global
+; CHECK-BTF-NEXT: [5] DATASEC '.maps' size=0 vlen=1
+; CHECK-BTF-NEXT: type_id=4 offset=0 size=4
!llvm.dbg.cu = !{!2}
!llvm.module.flags = !{!11, !12, !13}
diff --git a/llvm/test/CodeGen/BPF/BTF/map-def-nested-array.ll b/llvm/test/CodeGen/BPF/BTF/map-def-nested-array.ll
new file mode 100644
index 0000000000000..fc95daf8c3c23
--- /dev/null
+++ b/llvm/test/CodeGen/BPF/BTF/map-def-nested-array.ll
@@ -0,0 +1,75 @@
+; RUN: llc -mtriple=bpfel -mcpu=v3 -filetype=obj -o %t1 %s
+; RUN: llvm-objcopy --dump-section='.BTF'=%t2 %t1
+; RUN: %python %p/print_btf.py %t2 | FileCheck -check-prefixes=CHECK-BTF-SHORT %s
+; RUN: %python %p/print_btf.py %t2 | FileCheck -check-prefixes=CHECK-BTF %s
+; Source:
+; struct nested_value_type {
+; int a1;
+; };
+; struct map_type {
+; struct {
+; struct nested_value_type *value;
+; } *values[];
+; };
+; Compilation flags:
+; clang -target bpf -g -O2 -S -emit-llvm prog.c
+
+; ModuleID = 'prog.c'
+source_filename = "prog.c"
+target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
+target triple = "bpf"
+
+%struct.map_type = type { [0 x ptr] }
+
+ at array_of_maps = dso_local local_unnamed_addr global %struct.map_type zeroinitializer, section ".maps", align 8, !dbg !0
+
+; We expect no forward declarations.
+;
+; CHECK-BTF-SHORT-NOT: FWD
+
+; Assert the whole BTF.
+;
+; CHECK-BTF: [1] PTR '(anon)' type_id=2
+; CHECK-BTF-NEXT: [2] STRUCT 'nested_value_type' size=4 vlen=1
+; CHECK-BTF-NEXT: 'a1' type_id=3 bits_offset=0
+; CHECK-BTF-NEXT: [3] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
+; CHECK-BTF-NEXT: [4] STRUCT '(anon)' size=8 vlen=1
+; CHECK-BTF-NEXT: 'value' type_id=1 bits_offset=0
+; CHECK-BTF-NEXT: [5] PTR '(anon)' type_id=4
+; CHECK-BTF-NEXT: [6] ARRAY '(anon)' type_id=5 index_type_id=7 nr_elems=0
+; CHECK-BTF-NEXT: [7] INT '__ARRAY_SIZE_TYPE__' size=4 bits_offset=0 nr_bits=32 encoding=(none)
+; CHECK-BTF-NEXT: [8] STRUCT 'map_type' size=0 vlen=1
+; CHECK-BTF-NEXT: 'values' type_id=6 bits_offset=0
+; CHECK-BTF-NEXT: [9] VAR 'array_of_maps' type_id=8, linkage=global
+; CHECK-BTF-NEXT: [10] DATASEC '.maps' size=0 vlen=1
+; CHECK-BTF-NEXT: type_id=9 offset=0 size=0
+
+!llvm.dbg.cu = !{!2}
+!llvm.module.flags = !{!20, !21, !22, !23}
+!llvm.ident = !{!24}
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "array_of_maps", scope: !2, file: !3, line: 9, type: !5, isLocal: false, isDefinition: true)
+!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 22.0.0git (git at github.com:llvm/llvm-project.git ed93eaa421b714028b85cc887d80c45991d7207f)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None)
+!3 = !DIFile(filename: "prog.c", directory: "/home/mtardy/llvm-bug-repro", checksumkind: CSK_MD5, checksum: "9381d9e83e9c0b235a14704224815e96")
+!4 = !{!0}
+!5 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "map_type", file: !3, line: 4, elements: !6)
+!6 = !{!7}
+!7 = !DIDerivedType(tag: DW_TAG_member, name: "values", scope: !5, file: !3, line: 7, baseType: !8)
+!8 = !DICompositeType(tag: DW_TAG_array_type, baseType: !9, elements: !18)
+!9 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !10, size: 64)
+!10 = distinct !DICompositeType(tag: DW_TAG_structure_type, scope: !5, file: !3, line: 5, size: 64, elements: !11)
+!11 = !{!12}
+!12 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !10, file: !3, line: 6, baseType: !13, size: 64)
+!13 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !14, size: 64)
+!14 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "nested_value_type", file: !3, line: 1, size: 32, elements: !15)
+!15 = !{!16}
+!16 = !DIDerivedType(tag: DW_TAG_member, name: "a1", scope: !14, file: !3, line: 2, baseType: !17, size: 32)
+!17 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!18 = !{!19}
+!19 = !DISubrange(count: -1)
+!20 = !{i32 7, !"Dwarf Version", i32 5}
+!21 = !{i32 2, !"Debug Info Version", i32 3}
+!22 = !{i32 1, !"wchar_size", i32 4}
+!23 = !{i32 7, !"frame-pointer", i32 2}
+!24 = !{!"clang version 22.0.0git (git at github.com:llvm/llvm-project.git ed93eaa421b714028b85cc887d80c45991d7207f)"}
>From f67f8ea81b332eb29dce92452568bb7de35686ec Mon Sep 17 00:00:00 2001
From: Eduard Zingerman <eddyz87 at gmail.com>
Date: Fri, 25 Jul 2025 17:19:42 -0700
Subject: [PATCH 2/2] [BPF] Reorganize BTFDebug::visitMapDefType for better
readability
Kernel BPF selftests passing. No change in BTF generated for kernel
selftests, modulo some reorderings.
Signed-off-by: Mahe Tardy <mahe.tardy at gmail.com>
---
llvm/lib/Target/BPF/BTFDebug.cpp | 50 ++++++++++++++------------------
1 file changed, 21 insertions(+), 29 deletions(-)
diff --git a/llvm/lib/Target/BPF/BTFDebug.cpp b/llvm/lib/Target/BPF/BTFDebug.cpp
index 6ee5667902837..584b6ff543c28 100644
--- a/llvm/lib/Target/BPF/BTFDebug.cpp
+++ b/llvm/lib/Target/BPF/BTFDebug.cpp
@@ -957,38 +957,26 @@ void BTFDebug::visitMapDefType(const DIType *Ty, uint32_t &TypeId) {
return;
}
- // MapDef type may be a struct type or a derived type
- const DIType *OrigTy = Ty;
- while (auto *DTy = dyn_cast<DIDerivedType>(Ty)) {
- auto Tag = DTy->getTag();
- if (Tag != dwarf::DW_TAG_typedef && Tag != dwarf::DW_TAG_const_type &&
- Tag != dwarf::DW_TAG_volatile_type &&
- Tag != dwarf::DW_TAG_restrict_type && Tag != dwarf::DW_TAG_pointer_type)
- break;
- Ty = DTy->getBaseType();
- }
-
- const auto *CTy = dyn_cast<DICompositeType>(Ty);
- if (!CTy)
- return;
-
- auto Tag = CTy->getTag();
- if ((Tag != dwarf::DW_TAG_structure_type &&
- Tag != dwarf::DW_TAG_array_type) ||
- CTy->isForwardDecl())
- return;
-
- // Visit potential nested map array
- if (CTy->getTag() == dwarf::DW_TAG_array_type) {
- // Jump to the element type of the array
- visitMapDefType(CTy->getBaseType(), TypeId);
- } else {
+ uint32_t TmpId;
+ switch (Ty->getTag()) {
+ case dwarf::DW_TAG_typedef:
+ case dwarf::DW_TAG_const_type:
+ case dwarf::DW_TAG_volatile_type:
+ case dwarf::DW_TAG_restrict_type:
+ case dwarf::DW_TAG_pointer_type:
+ visitMapDefType(dyn_cast<DIDerivedType>(Ty)->getBaseType(), TmpId);
+ break;
+ case dwarf::DW_TAG_array_type:
+ // Visit nested map array and jump to the element type
+ visitMapDefType(dyn_cast<DICompositeType>(Ty)->getBaseType(), TmpId);
+ break;
+ case dwarf::DW_TAG_structure_type: {
// Visit all struct members to ensure their types are visited.
+ const auto *CTy = cast<DICompositeType>(Ty);
const DINodeArray Elements = CTy->getElements();
for (const auto *Element : Elements) {
const auto *MemberType = cast<DIDerivedType>(Element);
const DIType *MemberBaseType = MemberType->getBaseType();
-
// If the member is a composite type, that may indicate the currently
// visited composite type is a wrapper, and the member represents the
// actual map definition.
@@ -997,15 +985,19 @@ void BTFDebug::visitMapDefType(const DIType *Ty, uint32_t &TypeId) {
// than as a regular composite type.
const auto *MemberCTy = dyn_cast<DICompositeType>(MemberBaseType);
if (MemberCTy) {
- visitMapDefType(MemberBaseType, TypeId);
+ visitMapDefType(MemberBaseType, TmpId);
} else {
visitTypeEntry(MemberBaseType);
}
}
+ break;
+ }
+ default:
+ break;
}
// Visit this type, struct or a const/typedef/volatile/restrict type
- visitTypeEntry(OrigTy, TypeId, false, false);
+ visitTypeEntry(Ty, TypeId, false, false);
}
/// Read file contents from the actual file or from the source
More information about the llvm-commits
mailing list