Skip to content

Commit 11ef752

Browse files
vadorovskyc-rhodes
authored andcommitted
[BPF] Support for DW_TAG_variant_part in BTF generation (llvm#155783)
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`). Fixes: llvm#155778
1 parent 495a2ab commit 11ef752

File tree

2 files changed

+180
-19
lines changed

2 files changed

+180
-19
lines changed

llvm/lib/Target/BPF/BTFDebug.cpp

Lines changed: 93 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,59 @@ void BTFTypeStruct::completeType(BTFDebug &BDebug) {
301303

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

306+
if (STy->getTag() == dwarf::DW_TAG_variant_part) {
307+
// Variant parts might have a discriminator, which has its own memory
308+
// location, and variants, which share the memory location afterwards. LLVM
309+
// DI doesn't consider discriminator as an element and instead keeps
310+
// it as a separate reference.
311+
// To keep BTF simple, let's represent the structure as an union with
312+
// discriminator as the first element.
313+
// The offsets inside variant types are already handled correctly in the
314+
// DI.
315+
const auto *DTy = STy->getDiscriminator();
316+
if (DTy) {
317+
struct BTF::BTFMember Discriminator;
318+
319+
Discriminator.NameOff = BDebug.addString(DTy->getName());
320+
Discriminator.Offset = DTy->getOffsetInBits();
321+
const auto *BaseTy = DTy->getBaseType();
322+
Discriminator.Type = BDebug.getTypeId(BaseTy);
323+
324+
Members.push_back(Discriminator);
325+
}
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+
if (HasBitField) {
339+
uint8_t BitFieldSize = DDTy->isBitField() ? DDTy->getSizeInBits() : 0;
340+
BTFMember.Offset = BitFieldSize << 24 | DDTy->getOffsetInBits();
341+
} else {
342+
BTFMember.Offset = DDTy->getOffsetInBits();
343+
}
344+
const auto *BaseTy = tryRemoveAtomicType(DDTy->getBaseType());
345+
BTFMember.Type = BDebug.getTypeId(BaseTy);
346+
break;
347+
}
348+
case dwarf::DW_TAG_variant_part: {
349+
const auto *DCTy = dyn_cast<DICompositeType>(Element);
350+
351+
BTFMember.NameOff = BDebug.addString(DCTy->getName());
352+
BTFMember.Offset = DCTy->getOffsetInBits();
353+
BTFMember.Type = BDebug.getTypeId(DCTy);
354+
break;
355+
}
356+
default:
357+
llvm_unreachable("Unexpected DI tag of a struct/union element");
316358
}
317-
const auto *BaseTy = tryRemoveAtomicType(DDTy->getBaseType());
318-
BTFMember.Type = BDebug.getTypeId(BaseTy);
319359
Members.push_back(BTFMember);
320360
}
321361
}
@@ -672,16 +712,28 @@ void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
672712
uint32_t &TypeId) {
673713
const DINodeArray Elements = CTy->getElements();
674714
uint32_t VLen = Elements.size();
715+
// Variant parts might have a discriminator. LLVM DI doesn't consider it as
716+
// an element and instead keeps it as a separate reference. But we represent
717+
// it as an element in BTF.
718+
if (CTy->getTag() == dwarf::DW_TAG_variant_part) {
719+
const auto *DTy = CTy->getDiscriminator();
720+
if (DTy) {
721+
visitTypeEntry(DTy);
722+
VLen++;
723+
}
724+
}
675725
if (VLen > BTF::MAX_VLEN)
676726
return;
677727

678728
// Check whether we have any bitfield members or not
679729
bool HasBitField = false;
680730
for (const auto *Element : Elements) {
681-
auto E = cast<DIDerivedType>(Element);
682-
if (E->isBitField()) {
683-
HasBitField = true;
684-
break;
731+
if (Element->getTag() == dwarf::DW_TAG_member) {
732+
auto E = cast<DIDerivedType>(Element);
733+
if (E->isBitField()) {
734+
HasBitField = true;
735+
break;
736+
}
685737
}
686738
}
687739

@@ -696,9 +748,22 @@ void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
696748
// Visit all struct members.
697749
int FieldNo = 0;
698750
for (const auto *Element : Elements) {
699-
const auto Elem = cast<DIDerivedType>(Element);
700-
visitTypeEntry(Elem);
701-
processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
751+
switch (Element->getTag()) {
752+
case dwarf::DW_TAG_member: {
753+
const auto Elem = cast<DIDerivedType>(Element);
754+
visitTypeEntry(Elem);
755+
processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
756+
break;
757+
}
758+
case dwarf::DW_TAG_variant_part: {
759+
const auto Elem = cast<DICompositeType>(Element);
760+
visitTypeEntry(Elem);
761+
processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
762+
break;
763+
}
764+
default:
765+
llvm_unreachable("Unexpected DI tag of a struct/union element");
766+
}
702767
FieldNo++;
703768
}
704769
}
@@ -781,16 +846,25 @@ void BTFDebug::visitFwdDeclType(const DICompositeType *CTy, bool IsUnion,
781846
void BTFDebug::visitCompositeType(const DICompositeType *CTy,
782847
uint32_t &TypeId) {
783848
auto Tag = CTy->getTag();
784-
if (Tag == dwarf::DW_TAG_structure_type || Tag == dwarf::DW_TAG_union_type) {
849+
switch (Tag) {
850+
case dwarf::DW_TAG_structure_type:
851+
case dwarf::DW_TAG_union_type:
852+
case dwarf::DW_TAG_variant_part:
785853
// Handle forward declaration differently as it does not have members.
786854
if (CTy->isForwardDecl())
787855
visitFwdDeclType(CTy, Tag == dwarf::DW_TAG_union_type, TypeId);
788856
else
789857
visitStructType(CTy, Tag == dwarf::DW_TAG_structure_type, TypeId);
790-
} else if (Tag == dwarf::DW_TAG_array_type)
858+
break;
859+
case dwarf::DW_TAG_array_type:
791860
visitArrayType(CTy, TypeId);
792-
else if (Tag == dwarf::DW_TAG_enumeration_type)
861+
break;
862+
case dwarf::DW_TAG_enumeration_type:
793863
visitEnumType(CTy, TypeId);
864+
break;
865+
default:
866+
llvm_unreachable("Unexpected DI tag of a composite type");
867+
}
794868
}
795869

796870
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=3 bits_offset=0
43+
; CHECK-BTF-NEXT: [2] INT 'u32' size=4 bits_offset=0 nr_bits=32 encoding=(none)
44+
; CHECK-BTF-NEXT: [3] UNION '(anon)' size=12 vlen=3
45+
; CHECK-BTF-NEXT: '(anon)' type_id=2 bits_offset=0
46+
; CHECK-BTF-NEXT: 'First' type_id=4 bits_offset=0
47+
; CHECK-BTF-NEXT: 'Second' type_id=6 bits_offset=0
48+
; CHECK-BTF-NEXT: [4] STRUCT 'First' size=12 vlen=2
49+
; CHECK-BTF-NEXT: 'a' type_id=2 bits_offset=32
50+
; CHECK-BTF-NEXT: 'b' type_id=5 bits_offset=64
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=2 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=12
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)