Skip to content

[BPF] Handle nested wrapper structs in BPF map definition traversal #144097

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jun 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions llvm/lib/Target/BPF/BTFDebug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -976,11 +976,24 @@ void BTFDebug::visitMapDefType(const DIType *Ty, uint32_t &TypeId) {
if (Tag != dwarf::DW_TAG_structure_type || CTy->isForwardDecl())
return;

// Visit all struct members to ensure pointee type is visited
// 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);
visitTypeEntry(MemberType->getBaseType());
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
Expand Down
117 changes: 117 additions & 0 deletions llvm/test/CodeGen/BPF/BTF/map-def-nested.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
; 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 code:
Copy link
Contributor

@eddyz87 eddyz87 Jun 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please try to minimize your test case?
As far as I understand, the intended use case looks as follows in C:

$ cat test_struct_dbg.c
struct key { int i; };
struct val { int j; };

#define __uint(name, val) int (*name)[val]
#define __type(name, val) typeof(val) *name

struct {
        __uint(type, 1);
        __uint(max_entries, 1);
        __type(key, struct key);
        __type(value, struct val);
} map __attribute__((section(".maps")));

And it generates a much smaller amount of IR:

$ clang -g -emit-llvm -S test_struct_dbg.c -o - | wc -l
45

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Such test wouldn't be really relevant to the issue that this PR is trying to fix. The simple, not nested map structs in C, like the one you wrote, always worked fine.

What doesn't work is wrapping such map structs in nested structs, which is necessary in Rust. I tried my best explaining that in the PR description and commit message, please let me know if something is unclear there.

The C code, capable of reproducing the issue, would look like:

struct hash_map {
    struct {
        __uint(type, 1);
        __uint(max_entries, 1);
        __type(key, struct key);
        __type(value, struct val);
    } __0;
}:

struct hash_map map __attribute__((section(".maps")));

where the actual map definition is wrapped in some other type.

; struct key { int i; };
; struct val { int j; };
;
; #define __uint(name, val) int (*name)[val]
; #define __type(name, val) typeof(val) *name
;
; struct {
; struct {
; __uint(type, 1);
; __uint(max_entries, 1337);
; __type(key, struct key);
; __type(value, struct val);
; } map_def;
; } map __attribute__((section(".maps")));
; Compilation flag:
; clang -target bpf -O2 -g -S -emit-llvm t.c

; ModuleID = 'bpf.c'
source_filename = "bpf.c"
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
target triple = "bpf"

%struct.anon = type { %struct.anon.0 }
%struct.anon.0 = type { ptr, ptr, ptr, ptr }

@map = dso_local local_unnamed_addr global %struct.anon zeroinitializer, section ".maps", align 8, !dbg !0

; We expect exactly 4 structs:
; * key
; * val
; * inner map type (the actual definition)
; * outer map type (the wrapper)
;
; CHECK-BTF-SHORT-COUNT-4: STRUCT
; CHECK-BTF-SHORT-NOT: STRUCT

; We expect no forward declarations.
;
; CHECK-BTF-SHORT-NOT: FWD

; Assert the whole BTF.
;
; CHECK-BTF: [1] PTR '(anon)' type_id=3
; CHECK-BTF-NEXT: [2] INT 'int' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
; CHECK-BTF-NEXT: [3] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=1
; CHECK-BTF-NEXT: [4] INT '__ARRAY_SIZE_TYPE__' size=4 bits_offset=0 nr_bits=32 encoding=(none)
; CHECK-BTF-NEXT: [5] PTR '(anon)' type_id=6
; CHECK-BTF-NEXT: [6] ARRAY '(anon)' type_id=2 index_type_id=4 nr_elems=1337
; CHECK-BTF-NEXT: [7] PTR '(anon)' type_id=8
;
; Before bug https://github.com/llvm/llvm-project/issues/143361 was fixed, the
; BTF kind of MyKey (#6) and MyValue (#9) would be FWD instead of STRUCT. The
; main goal of this test is making sure that the full STRUCT BTF is generated
; for these types.
;
; CHECK-BTF-NEXT: [8] STRUCT 'key' size=4 vlen=1
; CHECK-BTF-NEXT: 'i' type_id=2 bits_offset=0
; CHECK-BTF-NEXT: [9] PTR '(anon)' type_id=10
; CHECK-BTF-NEXT: [10] STRUCT 'val' size=4 vlen=1
; CHECK-BTF-NEXT: 'j' type_id=2 bits_offset=0
; CHECK-BTF-NEXT: [11] STRUCT '(anon)' size=32 vlen=4
; CHECK-BTF-NEXT: 'type' type_id=1 bits_offset=0
; CHECK-BTF-NEXT: 'max_entries' type_id=5 bits_offset=64
; CHECK-BTF-NEXT: 'key' type_id=7 bits_offset=128
; CHECK-BTF-NEXT: 'value' type_id=9 bits_offset=192
; CHECK-BTF-NEXT: [12] STRUCT '(anon)' size=32 vlen=1
; CHECK-BTF-NEXT: 'map_def' type_id=11 bits_offset=0
; CHECK-BTF-NEXT: [13] VAR 'map' type_id=12, linkage=global
; CHECK-BTF-NEXT: [14] DATASEC '.maps' size=0 vlen=1
; CHECK-BTF-NEXT: type_id=13 offset=0 size=32

!llvm.dbg.cu = !{!2}
!llvm.module.flags = !{!31, !32, !33, !34}
!llvm.ident = !{!35}

!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
!1 = distinct !DIGlobalVariable(name: "map", scope: !2, file: !3, line: 14, type: !5, isLocal: false, isDefinition: true)
!2 = distinct !DICompileUnit(language: DW_LANG_C11, file: !3, producer: "clang version 21.0.0git ([email protected]:llvm/llvm-project.git c935bd3798b39330aab2c9ca29a519457d5e5245)", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug, globals: !4, splitDebugInlining: false, nameTableKind: None)
!3 = !DIFile(filename: "bpf.c", directory: "/tmp", checksumkind: CSK_MD5, checksum: "2330cce6d83c72ef5335abc3016de28e")
!4 = !{!0}
!5 = distinct !DICompositeType(tag: DW_TAG_structure_type, file: !3, line: 7, size: 256, elements: !6)
!6 = !{!7}
!7 = !DIDerivedType(tag: DW_TAG_member, name: "map_def", scope: !5, file: !3, line: 13, baseType: !8, size: 256)
!8 = distinct !DICompositeType(tag: DW_TAG_structure_type, scope: !5, file: !3, line: 8, size: 256, elements: !9)
!9 = !{!10, !16, !21, !26}
!10 = !DIDerivedType(tag: DW_TAG_member, name: "type", scope: !8, file: !3, line: 9, baseType: !11, size: 64)
!11 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !12, size: 64)
!12 = !DICompositeType(tag: DW_TAG_array_type, baseType: !13, size: 32, elements: !14)
!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
!14 = !{!15}
!15 = !DISubrange(count: 1)
!16 = !DIDerivedType(tag: DW_TAG_member, name: "max_entries", scope: !8, file: !3, line: 10, baseType: !17, size: 64, offset: 64)
!17 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !18, size: 64)
!18 = !DICompositeType(tag: DW_TAG_array_type, baseType: !13, size: 42784, elements: !19)
!19 = !{!20}
!20 = !DISubrange(count: 1337)
!21 = !DIDerivedType(tag: DW_TAG_member, name: "key", scope: !8, file: !3, line: 11, baseType: !22, size: 64, offset: 128)
!22 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !23, size: 64)
!23 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "key", file: !3, line: 1, size: 32, elements: !24)
!24 = !{!25}
!25 = !DIDerivedType(tag: DW_TAG_member, name: "i", scope: !23, file: !3, line: 1, baseType: !13, size: 32)
!26 = !DIDerivedType(tag: DW_TAG_member, name: "value", scope: !8, file: !3, line: 12, baseType: !27, size: 64, offset: 192)
!27 = !DIDerivedType(tag: DW_TAG_pointer_type, baseType: !28, size: 64)
!28 = distinct !DICompositeType(tag: DW_TAG_structure_type, name: "val", file: !3, line: 2, size: 32, elements: !29)
!29 = !{!30}
!30 = !DIDerivedType(tag: DW_TAG_member, name: "j", scope: !28, file: !3, line: 2, baseType: !13, size: 32)
!31 = !{i32 7, !"Dwarf Version", i32 5}
!32 = !{i32 2, !"Debug Info Version", i32 3}
!33 = !{i32 1, !"wchar_size", i32 4}
!34 = !{i32 7, !"frame-pointer", i32 2}
!35 = !{!"clang version 21.0.0git ([email protected]:llvm/llvm-project.git c935bd3798b39330aab2c9ca29a519457d5e5245)"}
Loading