Skip to content

Commit c3d8eb4

Browse files
committed
[BPF] Support for DW_TAG_variant_part in BTF generation
Variant part, represented by `DW_TAG_variant_part` is a structure with a discriminant and different variants, from which only one can be active and valid at the same time. The discriminant is the main difference between variant parts and unions represented by `DW_TAG_union` type. Variant parts are used by Rust enums, which look like: ```rust pub enum MyEnum { First { a: u32, b: i32 }, Second(u32), } ``` This type's debug info is the following `DICompositeType` with `DW_TAG_structure_type` tag: ```llvm !4 = !DICompositeType(tag: DW_TAG_structure_type, name: "MyEnum", scope: !2, file: !5, size: 96, align: 32, flags: DIFlagPublic, elements: !6, templateParams: !16, identifier: "faba668fd9f71e9b7cf3b9ac5e8b93cb") ``` With one element being also a `DICompositeType`, but with `DW_TAG_variant_part` tag: ```llvm !6 = !{!7} !7 = !DICompositeType(tag: DW_TAG_variant_part, scope: !4, file: !5, size: 96, align: 32, elements: !8, templateParams: !16, identifier: "e4aee046fc86d111657622fdcb8c42f7", discriminator: !21) ``` Which has a discriminator: ```llvm !21 = !DIDerivedType(tag: DW_TAG_member, scope: !4, file: !5, baseType: !13, size: 32, align: 32, flags: DIFlagArtificial) ``` Which then holds different variants as `DIDerivedType` elements with `DW_TAG_member` tag: ```llvm !8 = !{!9, !17} !9 = !DIDerivedType(tag: DW_TAG_member, name: "First", scope: !7, file: !5, baseType: !10, size: 96, align: 32, extraData: i32 0) !10 = !DICompositeType(tag: DW_TAG_structure_type, name: "First", scope: !4, file: !5, size: 96, align: 32, flags: DIFlagPublic, elements: !11, templateParams: !16, identifier: "cc7748c842e275452db4205b190c8ff7") !11 = !{!12, !14} !12 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !10, file: !5, baseType: !13, size: 32, align: 32, offset: 32, flags: DIFlagPublic) !13 = !DIBasicType(name: "u32", size: 32, encoding: DW_ATE_unsigned) !14 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !10, file: !5, baseType: !15, size: 32, align: 32, offset: 64, flags: DIFlagPublic) !15 = !DIBasicType(name: "i32", size: 32, encoding: DW_ATE_signed) !16 = !{} !17 = !DIDerivedType(tag: DW_TAG_member, name: "Second", scope: !7, file: !5, baseType: !18, size: 96, align: 32, extraData: i32 1) !18 = !DICompositeType(tag: DW_TAG_structure_type, name: "Second", scope: !4, file: !5, size: 96, align: 32, flags: DIFlagPublic, elements: !19, templateParams: !16, identifier: "a2094b1381f3082d504fbd0903aa7c06") !19 = !{!20} !20 = !DIDerivedType(tag: DW_TAG_member, name: "__0", scope: !18, file: !5, baseType: !13, size: 32, align: 32, offset: 32, flags: DIFlagPublic) ``` BPF backend was assuming that all the elements of any `DICompositeType` have tag `DW_TAG_member` and are instances of `DIDerivedType`. However, the single element of the outer composite type `!4` has tag `DW_TAG_variant_part` and is an instance of `DICompositeType`. The unconditional call of `cast<DIDerivedType>` on all elements was causing an assertion failure when any Rust code with enums was compiled to the BPF target. Fix that by: * Handling `DW_TAG_variant_part` in `visitStructType`. * Replacing unconditional call of `cast<DIDerivedType>` over `DICompositeType` elements with a `switch` statement, handling both `DW_TAG_member` and `DW_TAG_variant_part` and casting the element to an appropriate type (`DIDerivedType` or `DICompositeType`). To keep BTF simple and make BTF relocations correct, represent the discriminator as the first element and apply an offset to all elements. Fixes: #155778
1 parent 6a71938 commit c3d8eb4

File tree

2 files changed

+177
-19
lines changed

2 files changed

+177
-19
lines changed

llvm/lib/Target/BPF/BTFDebug.cpp

Lines changed: 90 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
#include "BPF.h"
1515
#include "BPFCORE.h"
1616
#include "MCTargetDesc/BPFMCTargetDesc.h"
17+
#include "llvm/BinaryFormat/Dwarf.h"
1718
#include "llvm/BinaryFormat/ELF.h"
1819
#include "llvm/CodeGen/AsmPrinter.h"
1920
#include "llvm/CodeGen/MachineModuleInfo.h"
@@ -23,6 +24,7 @@
2324
#include "llvm/MC/MCObjectFileInfo.h"
2425
#include "llvm/MC/MCSectionELF.h"
2526
#include "llvm/MC/MCStreamer.h"
27+
#include "llvm/Support/ErrorHandling.h"
2628
#include "llvm/Support/LineIterator.h"
2729
#include "llvm/Support/MemoryBuffer.h"
2830
#include "llvm/Target/TargetLoweringObjectFile.h"
@@ -301,21 +303,61 @@ void BTFTypeStruct::completeType(BTFDebug &BDebug) {
301303

302304
BTFType.NameOff = BDebug.addString(STy->getName());
303305

306+
uint64_t InitialOffset = 0;
307+
if (STy->getTag() == dwarf::DW_TAG_variant_part) {
308+
// Variant parts have a discriminator, which has its own memory location at
309+
// the beginning, and variants, which share the memory location afterwards.
310+
// LLVM DI doesn't consider discriminator as an element and instead keeps
311+
// it as a separate reference.
312+
// To keep BTF simple, let's represent the structure as an union with
313+
// discriminator as the first element and apply offsets to the other
314+
// elements.
315+
struct BTF::BTFMember Discriminator;
316+
const auto *DDTy = STy->getDiscriminator();
317+
318+
InitialOffset += DDTy->getOffsetInBits() + DDTy->getSizeInBits();
319+
320+
Discriminator.NameOff = BDebug.addString(DDTy->getName());
321+
Discriminator.Offset = DDTy->getOffsetInBits();
322+
const auto *BaseTy = DDTy->getBaseType();
323+
Discriminator.Type = BDebug.getTypeId(BaseTy);
324+
325+
Members.push_back(Discriminator);
326+
}
327+
304328
// Add struct/union members.
305329
const DINodeArray Elements = STy->getElements();
306330
for (const auto *Element : Elements) {
307331
struct BTF::BTFMember BTFMember;
308-
const auto *DDTy = cast<DIDerivedType>(Element);
309332

310-
BTFMember.NameOff = BDebug.addString(DDTy->getName());
311-
if (HasBitField) {
312-
uint8_t BitFieldSize = DDTy->isBitField() ? DDTy->getSizeInBits() : 0;
313-
BTFMember.Offset = BitFieldSize << 24 | DDTy->getOffsetInBits();
314-
} else {
315-
BTFMember.Offset = DDTy->getOffsetInBits();
333+
switch (Element->getTag()) {
334+
case dwarf::DW_TAG_member: {
335+
const auto *DDTy = cast<DIDerivedType>(Element);
336+
337+
BTFMember.NameOff = BDebug.addString(DDTy->getName());
338+
uint64_t Offset;
339+
if (HasBitField) {
340+
uint8_t BitFieldSize = DDTy->isBitField() ? DDTy->getSizeInBits() : 0;
341+
Offset = BitFieldSize << 24 | DDTy->getOffsetInBits();
342+
} else {
343+
Offset = DDTy->getOffsetInBits();
344+
}
345+
BTFMember.Offset = InitialOffset + Offset;
346+
const auto *BaseTy = tryRemoveAtomicType(DDTy->getBaseType());
347+
BTFMember.Type = BDebug.getTypeId(BaseTy);
348+
break;
349+
}
350+
case dwarf::DW_TAG_variant_part: {
351+
const auto *DCTy = dyn_cast<DICompositeType>(Element);
352+
353+
BTFMember.NameOff = BDebug.addString(DCTy->getName());
354+
BTFMember.Offset = InitialOffset + DCTy->getOffsetInBits();
355+
BTFMember.Type = BDebug.getTypeId(DCTy);
356+
break;
357+
}
358+
default:
359+
llvm_unreachable("Unexpected DI tag of a struct/union element");
316360
}
317-
const auto *BaseTy = tryRemoveAtomicType(DDTy->getBaseType());
318-
BTFMember.Type = BDebug.getTypeId(BaseTy);
319361
Members.push_back(BTFMember);
320362
}
321363
}
@@ -672,16 +714,23 @@ void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
672714
uint32_t &TypeId) {
673715
const DINodeArray Elements = CTy->getElements();
674716
uint32_t VLen = Elements.size();
717+
// Variant parts have a discriminator. LLVM DI doesn't consider it as an
718+
// element and instead keeps it as a separate reference. But we represent it
719+
// as an element in BTF.
720+
if (CTy->getTag() == dwarf::DW_TAG_variant_part)
721+
VLen++;
675722
if (VLen > BTF::MAX_VLEN)
676723
return;
677724

678725
// Check whether we have any bitfield members or not
679726
bool HasBitField = false;
680727
for (const auto *Element : Elements) {
681-
auto E = cast<DIDerivedType>(Element);
682-
if (E->isBitField()) {
683-
HasBitField = true;
684-
break;
728+
if (Element->getTag() == dwarf::DW_TAG_member) {
729+
auto E = cast<DIDerivedType>(Element);
730+
if (E->isBitField()) {
731+
HasBitField = true;
732+
break;
733+
}
685734
}
686735
}
687736

@@ -696,9 +745,22 @@ void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
696745
// Visit all struct members.
697746
int FieldNo = 0;
698747
for (const auto *Element : Elements) {
699-
const auto Elem = cast<DIDerivedType>(Element);
700-
visitTypeEntry(Elem);
701-
processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
748+
switch (Element->getTag()) {
749+
case dwarf::DW_TAG_member: {
750+
const auto Elem = cast<DIDerivedType>(Element);
751+
visitTypeEntry(Elem);
752+
processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
753+
break;
754+
}
755+
case dwarf::DW_TAG_variant_part: {
756+
const auto Elem = cast<DICompositeType>(Element);
757+
visitTypeEntry(Elem);
758+
processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
759+
break;
760+
}
761+
default:
762+
llvm_unreachable("Unexpected DI tag of a struct/union element");
763+
}
702764
FieldNo++;
703765
}
704766
}
@@ -781,16 +843,25 @@ void BTFDebug::visitFwdDeclType(const DICompositeType *CTy, bool IsUnion,
781843
void BTFDebug::visitCompositeType(const DICompositeType *CTy,
782844
uint32_t &TypeId) {
783845
auto Tag = CTy->getTag();
784-
if (Tag == dwarf::DW_TAG_structure_type || Tag == dwarf::DW_TAG_union_type) {
846+
switch (CTy->getTag()) {
847+
case dwarf::DW_TAG_structure_type:
848+
case dwarf::DW_TAG_union_type:
849+
case dwarf::DW_TAG_variant_part:
785850
// Handle forward declaration differently as it does not have members.
786851
if (CTy->isForwardDecl())
787852
visitFwdDeclType(CTy, Tag == dwarf::DW_TAG_union_type, TypeId);
788853
else
789854
visitStructType(CTy, Tag == dwarf::DW_TAG_structure_type, TypeId);
790-
} else if (Tag == dwarf::DW_TAG_array_type)
855+
break;
856+
case dwarf::DW_TAG_array_type:
791857
visitArrayType(CTy, TypeId);
792-
else if (Tag == dwarf::DW_TAG_enumeration_type)
858+
break;
859+
case dwarf::DW_TAG_enumeration_type:
793860
visitEnumType(CTy, TypeId);
861+
break;
862+
default:
863+
llvm_unreachable("Unexpected DI tag of a composite type");
864+
}
794865
}
795866

796867
bool BTFDebug::IsForwardDeclCandidate(const DIType *Base) {
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
; RUN: llc -mtriple=bpfel -filetype=obj -o %t1 %s
2+
; RUN: llvm-objcopy --dump-section='.BTF'=%t2 %t1
3+
; RUN: %python %p/print_btf.py %t2 | FileCheck -check-prefixes=CHECK-BTF %s
4+
; RUN: llc -mtriple=bpfeb -filetype=obj -o %t1 %s
5+
; RUN: llvm-objcopy --dump-section='.BTF'=%t2 %t1
6+
; RUN: %python %p/print_btf.py %t2 | FileCheck -check-prefixes=CHECK-BTF %s
7+
;
8+
; Source:
9+
; #![no_std]
10+
; #![no_main]
11+
;
12+
; pub enum MyEnum {
13+
; First { a: u32, b: i32 },
14+
; Second(u32),
15+
; }
16+
;
17+
; #[unsafe(no_mangle)]
18+
; pub static X: MyEnum = MyEnum::First { a: 54, b: -23 };
19+
;
20+
; #[cfg(not(test))]
21+
; #[panic_handler]
22+
; fn panic(_info: &core::panic::PanicInfo) -> ! {
23+
; loop {}
24+
; }
25+
; Compilation flag:
26+
; cargo +nightly rustc -Zbuild-std=core --target=bpfel-unknown-none -- --emit=llvm-bc
27+
; llvm-extract --glob=X $(find target/ -name "*.bc" | head -n 1) -o variant-part.bc
28+
; llvm-dis variant-part.bc -o variant-part.ll
29+
30+
; ModuleID = 'variant-part.bc'
31+
source_filename = "c0znihgkvro8hs0n88fgrtg6x"
32+
target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
33+
target triple = "bpfel"
34+
35+
@X = constant [12 x i8] c"\00\00\00\006\00\00\00\E9\FF\FF\FF", align 4, !dbg !0
36+
37+
!llvm.module.flags = !{!22, !23, !24, !25}
38+
!llvm.ident = !{!26}
39+
!llvm.dbg.cu = !{!27}
40+
41+
; CHECK-BTF: [1] STRUCT 'MyEnum' size=12 vlen=1
42+
; CHECK-BTF-NEXT: '(anon)' type_id=2 bits_offset=0
43+
; CHECK-BTF-NEXT: [2] UNION '(anon)' size=12 vlen=3
44+
; CHECK-BTF-NEXT: '(anon)' type_id=4 bits_offset=0
45+
; CHECK-BTF-NEXT: 'First' type_id=3 bits_offset=32
46+
; CHECK-BTF-NEXT: 'Second' type_id=6 bits_offset=32
47+
; CHECK-BTF-NEXT: [3] STRUCT 'First' size=12 vlen=2
48+
; CHECK-BTF-NEXT: 'a' type_id=4 bits_offset=32
49+
; CHECK-BTF-NEXT: 'b' type_id=5 bits_offset=64
50+
; CHECK-BTF-NEXT: [4] INT 'u32' size=4 bits_offset=0 nr_bits=32 encoding=(none)
51+
; CHECK-BTF-NEXT: [5] INT 'i32' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
52+
; CHECK-BTF-NEXT: [6] STRUCT 'Second' size=12 vlen=1
53+
; CHECK-BTF-NEXT: '__0' type_id=4 bits_offset=32
54+
; CHECK-BTF-NEXT: [7] VAR 'X' type_id=1, linkage=global
55+
; CHECK-BTF-NEXT: [8] DATASEC '.rodata' size=0 vlen=1
56+
; CHECK-BTF-NEXT: type_id=7 offset=0 size=1
57+
58+
!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
59+
!1 = distinct !DIGlobalVariable(name: "X", scope: !2, file: !3, line: 10, type: !4, isLocal: false, isDefinition: true, align: 32)
60+
!2 = !DINamespace(name: "variant_part", scope: null)
61+
!3 = !DIFile(filename: "variant-part/src/main.rs", directory: "/tmp/variant-part", checksumkind: CSK_MD5, checksum: "b94cd53886ea8f14cbc116b36bc7dd36")
62+
!4 = !DICompositeType(tag: DW_TAG_structure_type, name: "MyEnum", scope: !2, file: !5, size: 96, align: 32, flags: DIFlagPublic, elements: !6, templateParams: !16, identifier: "faba668fd9f71e9b7cf3b9ac5e8b93cb")
63+
!5 = !DIFile(filename: "<unknown>", directory: "")
64+
!6 = !{!7}
65+
!7 = !DICompositeType(tag: DW_TAG_variant_part, scope: !4, file: !5, size: 96, align: 32, elements: !8, templateParams: !16, identifier: "e4aee046fc86d111657622fdcb8c42f7", discriminator: !21)
66+
!8 = !{!9, !17}
67+
!9 = !DIDerivedType(tag: DW_TAG_member, name: "First", scope: !7, file: !5, baseType: !10, size: 96, align: 32, extraData: i32 0)
68+
!10 = !DICompositeType(tag: DW_TAG_structure_type, name: "First", scope: !4, file: !5, size: 96, align: 32, flags: DIFlagPublic, elements: !11, templateParams: !16, identifier: "cc7748c842e275452db4205b190c8ff7")
69+
!11 = !{!12, !14}
70+
!12 = !DIDerivedType(tag: DW_TAG_member, name: "a", scope: !10, file: !5, baseType: !13, size: 32, align: 32, offset: 32, flags: DIFlagPublic)
71+
!13 = !DIBasicType(name: "u32", size: 32, encoding: DW_ATE_unsigned)
72+
!14 = !DIDerivedType(tag: DW_TAG_member, name: "b", scope: !10, file: !5, baseType: !15, size: 32, align: 32, offset: 64, flags: DIFlagPublic)
73+
!15 = !DIBasicType(name: "i32", size: 32, encoding: DW_ATE_signed)
74+
!16 = !{}
75+
!17 = !DIDerivedType(tag: DW_TAG_member, name: "Second", scope: !7, file: !5, baseType: !18, size: 96, align: 32, extraData: i32 1)
76+
!18 = !DICompositeType(tag: DW_TAG_structure_type, name: "Second", scope: !4, file: !5, size: 96, align: 32, flags: DIFlagPublic, elements: !19, templateParams: !16, identifier: "a2094b1381f3082d504fbd0903aa7c06")
77+
!19 = !{!20}
78+
!20 = !DIDerivedType(tag: DW_TAG_member, name: "__0", scope: !18, file: !5, baseType: !13, size: 32, align: 32, offset: 32, flags: DIFlagPublic)
79+
!21 = !DIDerivedType(tag: DW_TAG_member, scope: !4, file: !5, baseType: !13, size: 32, align: 32, flags: DIFlagArtificial)
80+
!22 = !{i32 8, !"PIC Level", i32 2}
81+
!23 = !{i32 7, !"PIE Level", i32 2}
82+
!24 = !{i32 7, !"Dwarf Version", i32 4}
83+
!25 = !{i32 2, !"Debug Info Version", i32 3}
84+
!26 = !{!"rustc version 1.91.0-nightly (160e7623e 2025-08-26)"}
85+
!27 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !28, producer: "clang LLVM (rustc version 1.91.0-nightly (160e7623e 2025-08-26))", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !29, splitDebugInlining: false, nameTableKind: None)
86+
!28 = !DIFile(filename: "variant-part/src/main.rs/@/c0znihgkvro8hs0n88fgrtg6x", directory: "/tmp/variant-part")
87+
!29 = !{!0}

0 commit comments

Comments
 (0)