[llvm] [BPF] Visit nested map array during BTF generation (PR #150608)

via llvm-commits llvm-commits at lists.llvm.org
Fri Jul 25 04:46:49 PDT 2025


https://github.com/mtardy created https://github.com/llvm/llvm-project/pull/150608

Fixes missing inner map struct type definitions [^1]. We should visit the type of nested array of maps like we do for global maps. This patch adds a boolean to convey the information to visitTypeEntry and visitDerivedType that the pointee is a map definition and should be treated as such.

It ressembles and works with 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

>From feec10823af0fbc477a808ee81bf433e6aae633d 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] [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. This patch
adds a boolean to convey the information to visitTypeEntry and
visitDerivedType that the pointee is a map definition and should be
treated as such.

It ressembles and works with 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

Signed-off-by: Mahe Tardy <mahe.tardy at gmail.com>
---
 llvm/lib/Target/BPF/BTFDebug.cpp              | 83 +++++++++++--------
 llvm/lib/Target/BPF/BTFDebug.h                |  4 +-
 .../CodeGen/BPF/BTF/map-def-nested-array.ll   | 75 +++++++++++++++++
 3 files changed, 127 insertions(+), 35 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..5ad16397eb124 100644
--- a/llvm/lib/Target/BPF/BTFDebug.cpp
+++ b/llvm/lib/Target/BPF/BTFDebug.cpp
@@ -707,7 +707,7 @@ void BTFDebug::visitArrayType(const DICompositeType *CTy, uint32_t &TypeId) {
   // Visit array element type.
   uint32_t ElemTypeId;
   const DIType *ElemType = CTy->getBaseType();
-  visitTypeEntry(ElemType, ElemTypeId, false, false);
+  visitTypeEntry(ElemType, ElemTypeId, false, false, false);
 
   // Visit array dimensions.
   DINodeArray Elements = CTy->getElements();
@@ -806,12 +806,13 @@ bool BTFDebug::IsForwardDeclCandidate(const DIType *Base) {
 
 /// Handle pointer, typedef, const, volatile, restrict and member types.
 void BTFDebug::visitDerivedType(const DIDerivedType *DTy, uint32_t &TypeId,
-                                bool CheckPointer, bool SeenPointer) {
+                                bool CheckPointer, bool SeenPointer,
+                                bool NestedMap) {
   unsigned Tag = DTy->getTag();
 
   if (Tag == dwarf::DW_TAG_atomic_type)
-    return visitTypeEntry(DTy->getBaseType(), TypeId, CheckPointer,
-                          SeenPointer);
+    return visitTypeEntry(DTy->getBaseType(), TypeId, CheckPointer, SeenPointer,
+                          NestedMap);
 
   /// Try to avoid chasing pointees, esp. structure pointees which may
   /// unnecessary bring in a lot of types.
@@ -859,10 +860,15 @@ void BTFDebug::visitDerivedType(const DIDerivedType *DTy, uint32_t &TypeId,
   // Visit base type of pointer, typedef, const, volatile, restrict or
   // struct/union member.
   uint32_t TempTypeId = 0;
-  if (Tag == dwarf::DW_TAG_member)
-    visitTypeEntry(DTy->getBaseType(), TempTypeId, true, false);
-  else
-    visitTypeEntry(DTy->getBaseType(), TempTypeId, CheckPointer, SeenPointer);
+  if (Tag == dwarf::DW_TAG_member) {
+    visitTypeEntry(DTy->getBaseType(), TempTypeId, true, false, NestedMap);
+  } else {
+    if (NestedMap)
+      visitMapDefType(DTy->getBaseType(), TempTypeId);
+    else
+      visitTypeEntry(DTy->getBaseType(), TempTypeId, CheckPointer, SeenPointer,
+                     NestedMap);
+  }
 }
 
 /// Visit a type entry. CheckPointer is true if the type has
@@ -873,7 +879,8 @@ void BTFDebug::visitDerivedType(const DIDerivedType *DTy, uint32_t &TypeId,
 /// will not be emitted in BTF and rather forward declarations
 /// will be generated.
 void BTFDebug::visitTypeEntry(const DIType *Ty, uint32_t &TypeId,
-                              bool CheckPointer, bool SeenPointer) {
+                              bool CheckPointer, bool SeenPointer,
+                              bool NestedMap) {
   if (!Ty || DIToIdMap.find(Ty) != DIToIdMap.end()) {
     TypeId = DIToIdMap[Ty];
 
@@ -923,7 +930,8 @@ void BTFDebug::visitTypeEntry(const DIType *Ty, uint32_t &TypeId,
                 break;
             }
             uint32_t TmpTypeId;
-            visitTypeEntry(BaseTy, TmpTypeId, CheckPointer, SeenPointer);
+            visitTypeEntry(BaseTy, TmpTypeId, CheckPointer, SeenPointer,
+                           NestedMap);
             break;
           }
         }
@@ -941,14 +949,14 @@ void BTFDebug::visitTypeEntry(const DIType *Ty, uint32_t &TypeId,
   else if (const auto *CTy = dyn_cast<DICompositeType>(Ty))
     visitCompositeType(CTy, TypeId);
   else if (const auto *DTy = dyn_cast<DIDerivedType>(Ty))
-    visitDerivedType(DTy, TypeId, CheckPointer, SeenPointer);
+    visitDerivedType(DTy, TypeId, CheckPointer, SeenPointer, NestedMap);
   else
     llvm_unreachable("Unknown DIType");
 }
 
 void BTFDebug::visitTypeEntry(const DIType *Ty) {
   uint32_t TypeId;
-  visitTypeEntry(Ty, TypeId, false, false);
+  visitTypeEntry(Ty, TypeId, false, false, false);
 }
 
 void BTFDebug::visitMapDefType(const DIType *Ty, uint32_t &TypeId) {
@@ -973,31 +981,40 @@ 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
+    const DIType *ElementType = CTy->getBaseType();
+    visitTypeEntry(ElementType, TypeId, false, false, true);
+  } 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);
+      }
     }
   }
 
   // Visit this type, struct or a const/typedef/volatile/restrict type
-  visitTypeEntry(OrigTy, TypeId, false, false);
+  visitTypeEntry(OrigTy, TypeId, false, false, false);
 }
 
 /// Read file contents from the actual file or from the source
@@ -1275,7 +1292,7 @@ void BTFDebug::endFunctionImpl(const MachineFunction *MF) {
 /// accessing or preserve debuginfo type.
 unsigned BTFDebug::populateType(const DIType *Ty) {
   unsigned Id;
-  visitTypeEntry(Ty, Id, false, false);
+  visitTypeEntry(Ty, Id, false, false, false);
   for (const auto &TypeEntry : TypeEntries)
     TypeEntry->completeType(*this);
   return Id;
@@ -1474,7 +1491,7 @@ void BTFDebug::processGlobals(bool ProcessingMapDef) {
         visitMapDefType(DIGlobal->getType(), GVTypeId);
       else {
         const DIType *Ty = tryRemoveAtomicType(DIGlobal->getType());
-        visitTypeEntry(Ty, GVTypeId, false, false);
+        visitTypeEntry(Ty, GVTypeId, false, false, false);
       }
       break;
     }
diff --git a/llvm/lib/Target/BPF/BTFDebug.h b/llvm/lib/Target/BPF/BTFDebug.h
index 75858fcc8bfde..687e5e0199404 100644
--- a/llvm/lib/Target/BPF/BTFDebug.h
+++ b/llvm/lib/Target/BPF/BTFDebug.h
@@ -321,7 +321,7 @@ class BTFDebug : public DebugHandlerBase {
   /// @{
   void visitTypeEntry(const DIType *Ty);
   void visitTypeEntry(const DIType *Ty, uint32_t &TypeId, bool CheckPointer,
-                      bool SeenPointer);
+                      bool SeenPointer, bool NestedMap);
   void visitBasicType(const DIBasicType *BTy, uint32_t &TypeId);
   void visitSubroutineType(
       const DISubroutineType *STy, bool ForSubprog,
@@ -335,7 +335,7 @@ class BTFDebug : public DebugHandlerBase {
   void visitArrayType(const DICompositeType *ATy, uint32_t &TypeId);
   void visitEnumType(const DICompositeType *ETy, uint32_t &TypeId);
   void visitDerivedType(const DIDerivedType *DTy, uint32_t &TypeId,
-                        bool CheckPointer, bool SeenPointer);
+                        bool CheckPointer, bool SeenPointer, bool NestedMap);
   void visitMapDefType(const DIType *Ty, uint32_t &TypeId);
   /// @}
 
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..15bc1a30c4aed
--- /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=5
+; CHECK-BTF-NEXT: [2] PTR '(anon)' type_id=3
+; CHECK-BTF-NEXT: [3] STRUCT 'nested_value_type' size=4 vlen=1
+; CHECK-BTF-NEXT:         'a1' type_id=4 bits_offset=0
+; CHECK-BTF-NEXT: [4] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
+; CHECK-BTF-NEXT: [5] STRUCT '(anon)' size=8 vlen=1
+; CHECK-BTF-NEXT:         'value' type_id=2 bits_offset=0
+; CHECK-BTF-NEXT: [6] ARRAY '(anon)' type_id=1 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)"}



More information about the llvm-commits mailing list