[llvm] [BPF] Handle data-carrying enums (PR #155783)
Michal R via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 1 02:42:13 PDT 2025
https://github.com/vadorovsky updated https://github.com/llvm/llvm-project/pull/155783
>From 413a98a961841510f813244f454a34ef6e9f116a Mon Sep 17 00:00:00 2001
From: Michal R <vad.sol at proton.me>
Date: Thu, 28 Aug 2025 10:38:15 +0200
Subject: [PATCH] [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, c: u64, d: i64 },
Second(u32, i32),
}
```
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: 256, align: 64, flags: DIFlagPublic,
elements: !6, templateParams: !20,
identifier: "e720b78cc0f1e09d59c45648bff04f5f")
```
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: 256, align: 64, elements: !8, templateParams: !20,
identifier: "410a1af65242eaaa442ea6f6927392f3", discriminator: !26)
```
Which has a discriminator:
```llvm
!26 = !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, !21}
!9 = !DIDerivedType(tag: DW_TAG_member, name: "First", scope: !7,
file: !5, baseType: !10, size: 256, align: 64, extraData: i32 0)
!10 = !DICompositeType(tag: DW_TAG_structure_type, name: "First",
scope: !4, file: !5, size: 256, align: 64, flags: DIFlagPublic,
elements: !11, templateParams: !20,
identifier: "6a46b05c482b8e5f117140bdf0fb1397")
!11 = !{!12, !14, !16, !18}
!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 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !10,
file: !5, baseType: !17, size: 64, align: 64, offset: 128,
flags: DIFlagPublic)
!17 = !DIBasicType(name: "u64", size: 64, encoding: DW_ATE_unsigned)
!18 = !DIDerivedType(tag: DW_TAG_member, name: "d", scope: !10,
file: !5, baseType: !19, size: 64, align: 64, offset: 192,
flags: DIFlagPublic)
!19 = !DIBasicType(name: "i64", size: 64, encoding: DW_ATE_signed)
[...]
!21 = !DIDerivedType(tag: DW_TAG_member, name: "Second", scope: !7,
file: !5, baseType: !22, size: 256, align: 64, extraData: i32 1)
!22 = !DICompositeType(tag: DW_TAG_structure_type, name: "Second",
scope: !4, file: !5, size: 256, align: 64, flags: DIFlagPublic,
elements: !23, templateParams: !20,
identifier: "ecf787a5313c86e9abc87344781957f")
!23 = !{!24, !25}
!24 = !DIDerivedType(tag: DW_TAG_member, name: "__0", scope: !22,
file: !5, baseType: !13, size: 32, align: 32, offset: 32,
flags: DIFlagPublic)
!25 = !DIDerivedType(tag: DW_TAG_member, name: "__1", scope: !22,
file: !5, baseType: !15, size: 32, align: 32, offset: 64,
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: #155778
---
llvm/lib/Target/BPF/BTFDebug.cpp | 98 +++++++++++++++-----
llvm/lib/Target/BPF/BTFDebug.h | 1 +
llvm/test/CodeGen/BPF/BTF/variant-part.ll | 108 ++++++++++++++++++++++
3 files changed, 183 insertions(+), 24 deletions(-)
create mode 100644 llvm/test/CodeGen/BPF/BTF/variant-part.ll
diff --git a/llvm/lib/Target/BPF/BTFDebug.cpp b/llvm/lib/Target/BPF/BTFDebug.cpp
index bed6bc98b1679..ba1161054ca98 100644
--- a/llvm/lib/Target/BPF/BTFDebug.cpp
+++ b/llvm/lib/Target/BPF/BTFDebug.cpp
@@ -14,6 +14,7 @@
#include "BPF.h"
#include "BPFCORE.h"
#include "MCTargetDesc/BPFMCTargetDesc.h"
+#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/BinaryFormat/ELF.h"
#include "llvm/CodeGen/AsmPrinter.h"
#include "llvm/CodeGen/MachineModuleInfo.h"
@@ -23,6 +24,7 @@
#include "llvm/MC/MCObjectFileInfo.h"
#include "llvm/MC/MCSectionELF.h"
#include "llvm/MC/MCStreamer.h"
+#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/LineIterator.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Target/TargetLoweringObjectFile.h"
@@ -305,17 +307,34 @@ void BTFTypeStruct::completeType(BTFDebug &BDebug) {
const DINodeArray Elements = STy->getElements();
for (const auto *Element : Elements) {
struct BTF::BTFMember BTFMember;
- const auto *DDTy = cast<DIDerivedType>(Element);
- BTFMember.NameOff = BDebug.addString(DDTy->getName());
- if (HasBitField) {
- uint8_t BitFieldSize = DDTy->isBitField() ? DDTy->getSizeInBits() : 0;
- BTFMember.Offset = BitFieldSize << 24 | DDTy->getOffsetInBits();
- } else {
- BTFMember.Offset = DDTy->getOffsetInBits();
+ switch (Element->getTag()) {
+ case dwarf::DW_TAG_member: {
+ const auto *DDTy = cast<DIDerivedType>(Element);
+
+ BTFMember.NameOff = BDebug.addString(DDTy->getName());
+ if (HasBitField) {
+ uint8_t BitFieldSize = DDTy->isBitField() ? DDTy->getSizeInBits() : 0;
+ BTFMember.Offset = BitFieldSize << 24 | DDTy->getOffsetInBits();
+ } else {
+ BTFMember.Offset = DDTy->getOffsetInBits();
+ }
+ const auto *BaseTy = tryRemoveAtomicType(DDTy->getBaseType());
+ BTFMember.Type = BDebug.getTypeId(BaseTy);
+ break;
+ }
+ case dwarf::DW_TAG_variant_part: {
+ const auto *DCTy = dyn_cast<DICompositeType>(Element);
+
+ BTFMember.NameOff = BDebug.addString(DCTy->getName());
+ BTFMember.Offset = DCTy->getOffsetInBits();
+ const auto *DTy = cast<DIType>(DCTy);
+ BTFMember.Type = BDebug.getTypeId(DTy);
+ break;
+ }
+ default:
+ llvm_unreachable("Unexpected DI tag of a struct/union element");
}
- const auto *BaseTy = tryRemoveAtomicType(DDTy->getBaseType());
- BTFMember.Type = BDebug.getTypeId(BaseTy);
Members.push_back(BTFMember);
}
}
@@ -667,6 +686,23 @@ int BTFDebug::genBTFTypeTags(const DIDerivedType *DTy, int BaseTypeId) {
return TmpTypeId;
}
+// Check whether the given composite type has any bitfield members
+bool BTFDebug::structHasBitField(const DICompositeType *CTy) {
+ const DINodeArray Elements = CTy->getElements();
+ for (const auto *Element : Elements) {
+ if (const auto *E = dyn_cast<DIDerivedType>(Element)) {
+ if (E->isBitField()) {
+ return true;
+ }
+ } else if (const auto *E = dyn_cast<DICompositeType>(Element)) {
+ if (structHasBitField(E)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
/// Handle structure/union types.
void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
uint32_t &TypeId) {
@@ -675,15 +711,7 @@ void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
if (VLen > BTF::MAX_VLEN)
return;
- // Check whether we have any bitfield members or not
- bool HasBitField = false;
- for (const auto *Element : Elements) {
- auto E = cast<DIDerivedType>(Element);
- if (E->isBitField()) {
- HasBitField = true;
- break;
- }
- }
+ bool HasBitField = structHasBitField(CTy);
auto TypeEntry =
std::make_unique<BTFTypeStruct>(CTy, IsStruct, HasBitField, VLen);
@@ -696,9 +724,22 @@ void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
// Visit all struct members.
int FieldNo = 0;
for (const auto *Element : Elements) {
- const auto Elem = cast<DIDerivedType>(Element);
- visitTypeEntry(Elem);
- processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
+ switch (Element->getTag()) {
+ case dwarf::DW_TAG_member: {
+ const auto Elem = cast<DIDerivedType>(Element);
+ visitTypeEntry(Elem);
+ processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
+ break;
+ }
+ case dwarf::DW_TAG_variant_part: {
+ const auto Elem = cast<DICompositeType>(Element);
+ visitTypeEntry(Elem);
+ processDeclAnnotations(Elem->getAnnotations(), TypeId, FieldNo);
+ break;
+ }
+ default:
+ llvm_unreachable("Unexpected DI tag of a struct/union element");
+ }
FieldNo++;
}
}
@@ -781,16 +822,25 @@ void BTFDebug::visitFwdDeclType(const DICompositeType *CTy, bool IsUnion,
void BTFDebug::visitCompositeType(const DICompositeType *CTy,
uint32_t &TypeId) {
auto Tag = CTy->getTag();
- if (Tag == dwarf::DW_TAG_structure_type || Tag == dwarf::DW_TAG_union_type) {
+ switch (CTy->getTag()) {
+ case dwarf::DW_TAG_structure_type:
+ case dwarf::DW_TAG_union_type:
+ case dwarf::DW_TAG_variant_part:
// Handle forward declaration differently as it does not have members.
if (CTy->isForwardDecl())
visitFwdDeclType(CTy, Tag == dwarf::DW_TAG_union_type, TypeId);
else
visitStructType(CTy, Tag == dwarf::DW_TAG_structure_type, TypeId);
- } else if (Tag == dwarf::DW_TAG_array_type)
+ break;
+ case dwarf::DW_TAG_array_type:
visitArrayType(CTy, TypeId);
- else if (Tag == dwarf::DW_TAG_enumeration_type)
+ break;
+ case dwarf::DW_TAG_enumeration_type:
visitEnumType(CTy, TypeId);
+ break;
+ default:
+ llvm_unreachable("Unexpected DI tag of a composite type");
+ }
}
bool BTFDebug::IsForwardDeclCandidate(const DIType *Base) {
diff --git a/llvm/lib/Target/BPF/BTFDebug.h b/llvm/lib/Target/BPF/BTFDebug.h
index 75858fcc8bfde..e373ee9ca2756 100644
--- a/llvm/lib/Target/BPF/BTFDebug.h
+++ b/llvm/lib/Target/BPF/BTFDebug.h
@@ -330,6 +330,7 @@ class BTFDebug : public DebugHandlerBase {
void visitFwdDeclType(const DICompositeType *CTy, bool IsUnion,
uint32_t &TypeId);
void visitCompositeType(const DICompositeType *CTy, uint32_t &TypeId);
+ bool structHasBitField(const DICompositeType *CTy);
void visitStructType(const DICompositeType *STy, bool IsStruct,
uint32_t &TypeId);
void visitArrayType(const DICompositeType *ATy, uint32_t &TypeId);
diff --git a/llvm/test/CodeGen/BPF/BTF/variant-part.ll b/llvm/test/CodeGen/BPF/BTF/variant-part.ll
new file mode 100644
index 0000000000000..16fa4aa76cdda
--- /dev/null
+++ b/llvm/test/CodeGen/BPF/BTF/variant-part.ll
@@ -0,0 +1,108 @@
+; RUN: llc -mtriple=bpfel -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
+; RUN: llc -mtriple=bpfeb -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:
+; #![no_std]
+; #![no_main]
+;
+; pub enum MyEnum {
+; First { a: u32, b: i32, c: u64, d: i64 },
+; Second(u32, i32),
+; }
+;
+; #[unsafe(no_mangle)]
+; pub static X: MyEnum = MyEnum::First {
+; a: 54,
+; b: -23,
+; c: 1_324,
+; d: -2_434,
+; };
+; #[unsafe(no_mangle)]
+; pub static Y: MyEnum = MyEnum::Second(54, -23);
+;
+; #[cfg(not(test))]
+; #[panic_handler]
+; fn panic(_info: &core::panic::PanicInfo) -> ! {
+; loop {}
+; }
+; Compilation flag:
+; cargo +nightly rustc -Zbuild-std=core --target=bpfel-unknown-none -- --emit=llvm-bc
+; llvm-extract --glob=X --glob=Y $(find target/ -name "*.bc" | head -n 1) -o variant-part.bc
+; llvm-dis variant-part.bc -o variant-part.ll
+
+; ModuleID = 'variant-part.bc'
+source_filename = "c0znihgkvro8hs0n88fgrtg6x"
+target datalayout = "e-m:e-p:64:64-i64:64-i128:128-n32:64-S128"
+target triple = "bpfel"
+
+ at X = constant <{ [12 x i8], [4 x i8], [16 x i8] }> <{ [12 x i8] c"\00\00\00\006\00\00\00\E9\FF\FF\FF", [4 x i8] undef, [16 x i8] c",\05\00\00\00\00\00\00~\F6\FF\FF\FF\FF\FF\FF" }>, align 8, !dbg !0
+ at Y = constant <{ [12 x i8], [20 x i8] }> <{ [12 x i8] c"\01\00\00\006\00\00\00\E9\FF\FF\FF", [20 x i8] undef }>, align 8, !dbg !27
+
+!llvm.module.flags = !{!29, !30, !31, !32}
+!llvm.ident = !{!33}
+!llvm.dbg.cu = !{!34}
+
+; CHECK-BTF: [1] STRUCT 'MyEnum' size=32 vlen=1
+; CHECK-BTF-NEXT: '(anon)' type_id=2 bits_offset=0
+; CHECK-BTF-NEXT: [2] UNION '(anon)' size=32 vlen=2
+; CHECK-BTF-NEXT: 'First' type_id=3 bits_offset=0
+; CHECK-BTF-NEXT: 'Second' type_id=8 bits_offset=0
+; CHECK-BTF-NEXT: [3] STRUCT 'First' size=32 vlen=4
+; CHECK-BTF-NEXT: 'a' type_id=4 bits_offset=32
+; CHECK-BTF-NEXT: 'b' type_id=5 bits_offset=64
+; CHECK-BTF-NEXT: 'c' type_id=6 bits_offset=128
+; CHECK-BTF-NEXT: 'd' type_id=7 bits_offset=192
+; CHECK-BTF-NEXT: [4] INT 'u32' size=4 bits_offset=0 nr_bits=32 encoding=(none)
+; CHECK-BTF-NEXT: [5] INT 'i32' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
+; CHECK-BTF-NEXT: [6] INT 'u64' size=8 bits_offset=0 nr_bits=64 encoding=(none)
+; CHECK-BTF-NEXT: [7] INT 'i64' size=8 bits_offset=0 nr_bits=64 encoding=SIGNED
+; CHECK-BTF-NEXT: [8] STRUCT 'Second' size=32 vlen=2
+; CHECK-BTF-NEXT: '__0' type_id=4 bits_offset=32
+; CHECK-BTF-NEXT: '__1' type_id=5 bits_offset=64
+; CHECK-BTF-NEXT: [9] VAR 'X' type_id=1, linkage=global
+; CHECK-BTF-NEXT: [10] VAR 'Y' type_id=1, linkage=global
+; CHECK-BTF-NEXT: [11] DATASEC '.rodata' size=0 vlen=2
+; CHECK-BTF-NEXT: type_id=9 offset=0 size=32
+; CHECK-BTF-NEXT: type_id=10 offset=0 size=32
+
+!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
+!1 = distinct !DIGlobalVariable(name: "X", scope: !2, file: !3, line: 10, type: !4, isLocal: false, isDefinition: true, align: 64)
+!2 = !DINamespace(name: "variant_part", scope: null)
+!3 = !DIFile(filename: "variant-part/src/main.rs", directory: "/tmp/variant-part", checksumkind: CSK_MD5, checksum: "10084925e25ca2b70575d3a0510e80a6")
+!4 = !DICompositeType(tag: DW_TAG_structure_type, name: "MyEnum", scope: !2, file: !5, size: 256, align: 64, flags: DIFlagPublic, elements: !6, templateParams: !20, identifier: "e720b78cc0f1e09d59c45648bff04f5f")
+!5 = !DIFile(filename: "<unknown>", directory: "")
+!6 = !{!7}
+!7 = !DICompositeType(tag: DW_TAG_variant_part, scope: !4, file: !5, size: 256, align: 64, elements: !8, templateParams: !20, identifier: "410a1af65242eaaa442ea6f6927392f3", discriminator: !26)
+!8 = !{!9, !21}
+!9 = !DIDerivedType(tag: DW_TAG_member, name: "First", scope: !7, file: !5, baseType: !10, size: 256, align: 64, extraData: i32 0)
+!10 = !DICompositeType(tag: DW_TAG_structure_type, name: "First", scope: !4, file: !5, size: 256, align: 64, flags: DIFlagPublic, elements: !11, templateParams: !20, identifier: "6a46b05c482b8e5f117140bdf0fb1397")
+!11 = !{!12, !14, !16, !18}
+!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 = !DIDerivedType(tag: DW_TAG_member, name: "c", scope: !10, file: !5, baseType: !17, size: 64, align: 64, offset: 128, flags: DIFlagPublic)
+!17 = !DIBasicType(name: "u64", size: 64, encoding: DW_ATE_unsigned)
+!18 = !DIDerivedType(tag: DW_TAG_member, name: "d", scope: !10, file: !5, baseType: !19, size: 64, align: 64, offset: 192, flags: DIFlagPublic)
+!19 = !DIBasicType(name: "i64", size: 64, encoding: DW_ATE_signed)
+!20 = !{}
+!21 = !DIDerivedType(tag: DW_TAG_member, name: "Second", scope: !7, file: !5, baseType: !22, size: 256, align: 64, extraData: i32 1)
+!22 = !DICompositeType(tag: DW_TAG_structure_type, name: "Second", scope: !4, file: !5, size: 256, align: 64, flags: DIFlagPublic, elements: !23, templateParams: !20, identifier: "ecf787a5313c86e9abc87344781957f")
+!23 = !{!24, !25}
+!24 = !DIDerivedType(tag: DW_TAG_member, name: "__0", scope: !22, file: !5, baseType: !13, size: 32, align: 32, offset: 32, flags: DIFlagPublic)
+!25 = !DIDerivedType(tag: DW_TAG_member, name: "__1", scope: !22, file: !5, baseType: !15, size: 32, align: 32, offset: 64, flags: DIFlagPublic)
+!26 = !DIDerivedType(tag: DW_TAG_member, scope: !4, file: !5, baseType: !13, size: 32, align: 32, flags: DIFlagArtificial)
+!27 = !DIGlobalVariableExpression(var: !28, expr: !DIExpression())
+!28 = distinct !DIGlobalVariable(name: "Y", scope: !2, file: !3, line: 17, type: !4, isLocal: false, isDefinition: true, align: 64)
+!29 = !{i32 8, !"PIC Level", i32 2}
+!30 = !{i32 7, !"PIE Level", i32 2}
+!31 = !{i32 7, !"Dwarf Version", i32 4}
+!32 = !{i32 2, !"Debug Info Version", i32 3}
+!33 = !{!"rustc version 1.91.0-nightly (160e7623e 2025-08-26)"}
+!34 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !35, producer: "clang LLVM (rustc version 1.91.0-nightly (160e7623e 2025-08-26))", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !36, splitDebugInlining: false, nameTableKind: None)
+!35 = !DIFile(filename: "variant-part/src/main.rs/@/c0znihgkvro8hs0n88fgrtg6x", directory: "/tmp/variant-part")
+!36 = !{!0, !27}
More information about the llvm-commits
mailing list