Skip to content
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
112 changes: 93 additions & 19 deletions llvm/lib/Target/BPF/BTFDebug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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"
Expand Down Expand Up @@ -301,21 +303,59 @@ void BTFTypeStruct::completeType(BTFDebug &BDebug) {

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

if (STy->getTag() == dwarf::DW_TAG_variant_part) {
// Variant parts might have a discriminator, which has its own memory
// location, and variants, which share the memory location afterwards. LLVM
// DI doesn't consider discriminator as an element and instead keeps
// it as a separate reference.
// To keep BTF simple, let's represent the structure as an union with
// discriminator as the first element.
// The offsets inside variant types are already handled correctly in the
// DI.
const auto *DTy = STy->getDiscriminator();
if (DTy) {
struct BTF::BTFMember Discriminator;

Discriminator.NameOff = BDebug.addString(DTy->getName());
Discriminator.Offset = DTy->getOffsetInBits();
const auto *BaseTy = DTy->getBaseType();
Discriminator.Type = BDebug.getTypeId(BaseTy);

Members.push_back(Discriminator);
}
}

// Add struct/union members.
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();
BTFMember.Type = BDebug.getTypeId(DCTy);
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);
}
}
Expand Down Expand Up @@ -672,16 +712,28 @@ void BTFDebug::visitStructType(const DICompositeType *CTy, bool IsStruct,
uint32_t &TypeId) {
const DINodeArray Elements = CTy->getElements();
uint32_t VLen = Elements.size();
// Variant parts might have a discriminator. LLVM DI doesn't consider it as
// an element and instead keeps it as a separate reference. But we represent
// it as an element in BTF.
if (CTy->getTag() == dwarf::DW_TAG_variant_part) {
const auto *DTy = CTy->getDiscriminator();
if (DTy) {
visitTypeEntry(DTy);
VLen++;
}
}
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;
if (Element->getTag() == dwarf::DW_TAG_member) {
auto E = cast<DIDerivedType>(Element);
if (E->isBitField()) {
HasBitField = true;
break;
}
}
}

Expand All @@ -696,9 +748,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++;
}
}
Expand Down Expand Up @@ -781,16 +846,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 (Tag) {
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) {
Expand Down
87 changes: 87 additions & 0 deletions llvm/test/CodeGen/BPF/BTF/variant-part.ll
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
; 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 },
; Second(u32),
; }
;
; #[unsafe(no_mangle)]
; pub static X: MyEnum = MyEnum::First { a: 54, b: -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 $(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"

@X = constant [12 x i8] c"\00\00\00\006\00\00\00\E9\FF\FF\FF", align 4, !dbg !0

!llvm.module.flags = !{!22, !23, !24, !25}
!llvm.ident = !{!26}
!llvm.dbg.cu = !{!27}

; CHECK-BTF: [1] STRUCT 'MyEnum' size=12 vlen=1
; CHECK-BTF-NEXT: '(anon)' type_id=3 bits_offset=0
Copy link
Contributor

@yonghong-song yonghong-song Oct 17, 2025

Choose a reason for hiding this comment

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

Not sure whether this is an issue or not. Here, we have '(anon)' field name which essentially is a 'empty' name. If people goes to access 'First' struct from Struct 'MyEnum', the C access type will be like MyEnum.(anon).First,
could this be a problem or there is rust way to handle this?

Also without 'name' for discriminator field, not sure how it will be used by BTF/kernel etc.

Copy link
Member

Choose a reason for hiding this comment

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

This anon is the variant union member - Rust enums are represented as

struct MyEnum {
  union { // <--- both the member and the type are anonymous
    u32, // <-- anonymous discriminator
    Variant1,
    ...
    VariantN,
  }
}

In Rust you never access the discriminator nor the union directly. Instead you do something like:

fn use_myEnum(value: myEnum) {
  match value {
    MyEnum::First { a, b } => println!("first! a={a}, b={b}"),
    MyEnum::Second(value) => println!("second! value={value}")
  }
}

the compiler checks that your match is exhaustive - meaning it covers all variants of MyEnum; otherwise compilation fails.

Also without 'name' for discriminator field, not sure how it will be used by BTF/kernel etc.

It's probably not usable without a name, but I think we shouldn't invent a name for it here. The discriminator's (anon) comes from Discriminator.NameOff = BDebug.addString(DDTy->getName()); on line 318 (I think) -- so the "solution" would be to change rustc to give it a name, which will automatically show up here.

Having said that, I think the name would be mostly useful for relocations, and we're a long way off from being able to apply BTF relocations to BPF generated from Rust.

The point of this PR is to translate the Rust DI to BTF as faithfully as possible, and that's how we get these anons.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Even when it comes to applying BTF relocations in Rust, given that discriminators are in practically always first in the whole memory layout of the enum/union, we won't need to relocate the discriminators. I think focusing on relocating variants would be fine.

Copy link
Contributor

Choose a reason for hiding this comment

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

Discriminator is not really used by btf, and the anonymous 'union' struct does not need a name. Okay make sense with no name for anywhere discriminator is placed. Thanks for explanation.

; CHECK-BTF-NEXT: [2] INT 'u32' size=4 bits_offset=0 nr_bits=32 encoding=(none)
; CHECK-BTF-NEXT: [3] UNION '(anon)' size=12 vlen=3
; CHECK-BTF-NEXT: '(anon)' type_id=2 bits_offset=0
; CHECK-BTF-NEXT: 'First' type_id=4 bits_offset=0
; CHECK-BTF-NEXT: 'Second' type_id=6 bits_offset=0
; CHECK-BTF-NEXT: [4] STRUCT 'First' size=12 vlen=2
; CHECK-BTF-NEXT: 'a' type_id=2 bits_offset=32
; CHECK-BTF-NEXT: 'b' type_id=5 bits_offset=64
; CHECK-BTF-NEXT: [5] INT 'i32' size=4 bits_offset=0 nr_bits=32 encoding=SIGNED
; CHECK-BTF-NEXT: [6] STRUCT 'Second' size=12 vlen=1
; CHECK-BTF-NEXT: '__0' type_id=2 bits_offset=32
; CHECK-BTF-NEXT: [7] VAR 'X' type_id=1, linkage=global
; CHECK-BTF-NEXT: [8] DATASEC '.rodata' size=0 vlen=1
; CHECK-BTF-NEXT: type_id=7 offset=0 size=12

!0 = !DIGlobalVariableExpression(var: !1, expr: !DIExpression())
!1 = distinct !DIGlobalVariable(name: "X", scope: !2, file: !3, line: 10, type: !4, isLocal: false, isDefinition: true, align: 32)
!2 = !DINamespace(name: "variant_part", scope: null)
!3 = !DIFile(filename: "variant-part/src/main.rs", directory: "/tmp/variant-part", checksumkind: CSK_MD5, checksum: "b94cd53886ea8f14cbc116b36bc7dd36")
!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")
!5 = !DIFile(filename: "<unknown>", directory: "")
!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)
!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)
!21 = !DIDerivedType(tag: DW_TAG_member, scope: !4, file: !5, baseType: !13, size: 32, align: 32, flags: DIFlagArtificial)
!22 = !{i32 8, !"PIC Level", i32 2}
!23 = !{i32 7, !"PIE Level", i32 2}
!24 = !{i32 7, !"Dwarf Version", i32 4}
!25 = !{i32 2, !"Debug Info Version", i32 3}
!26 = !{!"rustc version 1.91.0-nightly (160e7623e 2025-08-26)"}
!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)
!28 = !DIFile(filename: "variant-part/src/main.rs/@/c0znihgkvro8hs0n88fgrtg6x", directory: "/tmp/variant-part")
!29 = !{!0}
Loading