Skip to content

Commit

Permalink
[lldb] Add basic support to Rust enums in TypeSystemClang
Browse files Browse the repository at this point in the history
LLDB doesn't yet have a TypeSystemRust implemented however it is used to debug Rust applications. Most of the types map well enough to Clang types and there are python formatters implemented to display those types reasonably well in a debugger.

However, Rust enums are completely ignored by LLDB as Clang never emits DW_TAG_variant_part inside DW_TAG_structure_type

This diff adds a parser for DW_TAG_variant_part (Rust-only) that creates a matching valid Clang declaration to the Rust enum. As long as there is enough information and all fields have correct offsets synthetic/summary providers can be implemented to display it correctly when debugging Rust code

Differential Revision: https://reviews.llvm.org/D149213
  • Loading branch information
VladimirMakaev committed Aug 17, 2023
1 parent 52c62d4 commit e84751a
Show file tree
Hide file tree
Showing 6 changed files with 3,775 additions and 0 deletions.
235 changes: 235 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2496,8 +2496,163 @@ struct PropertyAttributes {
/// \see clang::ObjCPropertyAttribute
uint32_t prop_attributes = 0;
};

struct DiscriminantValue {
explicit DiscriminantValue(const DWARFDIE &die, ModuleSP module_sp);

uint32_t byte_offset;
uint32_t byte_size;
DWARFFormValue type_ref;
};

struct VariantMember {
explicit VariantMember(DWARFDIE &die, ModuleSP module_sp);
bool IsDefault() const;

std::optional<u_int32_t> discr_value;
DWARFFormValue type_ref;
ConstString variant_name;
uint32_t byte_offset;
ConstString GetName() const;
};

struct VariantPart {
explicit VariantPart(const DWARFDIE &die, const DWARFDIE &parent_die,
ModuleSP module_sp);

std::vector<VariantMember> &members();

DiscriminantValue &discriminant();

private:
std::vector<VariantMember> _members;
DiscriminantValue _discriminant;
};

} // namespace

ConstString VariantMember::GetName() const { return this->variant_name; }

bool VariantMember::IsDefault() const { return !discr_value; }

VariantMember::VariantMember(DWARFDIE &die, lldb::ModuleSP module_sp) {
assert(die.Tag() == llvm::dwarf::DW_TAG_variant);
this->discr_value =
die.GetAttributeValueAsOptionalUnsigned(DW_AT_discr_value);

for (auto child_die : die.children()) {
switch (child_die.Tag()) {
case llvm::dwarf::DW_TAG_member: {
DWARFAttributes attributes = child_die.GetAttributes();
for (std::size_t i = 0; i < attributes.Size(); ++i) {
DWARFFormValue form_value;
const dw_attr_t attr = attributes.AttributeAtIndex(i);
if (attributes.ExtractFormValueAtIndex(i, form_value)) {
switch (attr) {
case DW_AT_name:
variant_name = ConstString(form_value.AsCString());
break;
case DW_AT_type:
type_ref = form_value;
break;

case DW_AT_data_member_location:
if (form_value.BlockData()) {
Value initialValue(0);
Value memberOffset(0);
const DWARFDataExtractor &debug_info_data = die.GetData();
uint32_t block_length = form_value.Unsigned();
uint32_t block_offset =
form_value.BlockData() - debug_info_data.GetDataStart();
if (DWARFExpression::Evaluate(
nullptr, // ExecutionContext *
nullptr, // RegisterContext *
module_sp,
DataExtractor(debug_info_data, block_offset,
block_length),
die.GetCU(), eRegisterKindDWARF, &initialValue, nullptr,
memberOffset, nullptr)) {
byte_offset = memberOffset.ResolveValue(nullptr).UInt();
}
} else {
// With DWARF 3 and later, if the value is an integer constant,
// this form value is the offset in bytes from the beginning of
// the containing entity.
byte_offset = form_value.Unsigned();
}
break;

default:
break;
}
}
}
break;
}
default:
break;
}
break;
}
}

DiscriminantValue::DiscriminantValue(const DWARFDIE &die, ModuleSP module_sp) {
auto referenced_die = die.GetReferencedDIE(DW_AT_discr);
DWARFAttributes attributes = referenced_die.GetAttributes();
for (std::size_t i = 0; i < attributes.Size(); ++i) {
const dw_attr_t attr = attributes.AttributeAtIndex(i);
DWARFFormValue form_value;
if (attributes.ExtractFormValueAtIndex(i, form_value)) {
switch (attr) {
case DW_AT_type:
type_ref = form_value;
break;
case DW_AT_data_member_location:
if (form_value.BlockData()) {
Value initialValue(0);
Value memberOffset(0);
const DWARFDataExtractor &debug_info_data = die.GetData();
uint32_t block_length = form_value.Unsigned();
uint32_t block_offset =
form_value.BlockData() - debug_info_data.GetDataStart();
if (DWARFExpression::Evaluate(
nullptr, // ExecutionContext *
nullptr, // RegisterContext *
module_sp,
DataExtractor(debug_info_data, block_offset, block_length),
die.GetCU(), eRegisterKindDWARF, &initialValue, nullptr,
memberOffset, nullptr)) {
byte_offset = memberOffset.ResolveValue(nullptr).UInt();
}
} else {
// With DWARF 3 and later, if the value is an integer constant,
// this form value is the offset in bytes from the beginning of
// the containing entity.
byte_offset = form_value.Unsigned();
}
break;
default:
break;
}
}
}
}

VariantPart::VariantPart(const DWARFDIE &die, const DWARFDIE &parent_die,
lldb::ModuleSP module_sp)
: _members(), _discriminant(die, module_sp) {

for (auto child : die.children()) {
if (child.Tag() == llvm::dwarf::DW_TAG_variant) {
_members.push_back(VariantMember(child, module_sp));
}
}
}

std::vector<VariantMember> &VariantPart::members() { return this->_members; }

DiscriminantValue &VariantPart::discriminant() { return this->_discriminant; }

MemberAttributes::MemberAttributes(const DWARFDIE &die,
const DWARFDIE &parent_die,
ModuleSP module_sp) {
Expand Down Expand Up @@ -3021,6 +3176,13 @@ bool DWARFASTParserClang::ParseChildMembers(
ParseObjCProperty(die, parent_die, class_clang_type, delayed_properties);
break;

case DW_TAG_variant_part:
if (die.GetCU()->GetDWARFLanguageType() == eLanguageTypeRust) {
ParseRustVariantPart(die, parent_die, class_clang_type,
default_accessibility, layout_info);
}
break;

case DW_TAG_member:
ParseSingleMember(die, parent_die, class_clang_type,
default_accessibility, layout_info, last_field_info);
Expand Down Expand Up @@ -3728,3 +3890,76 @@ bool DWARFASTParserClang::ShouldCreateUnnamedBitfield(

return true;
}

void DWARFASTParserClang::ParseRustVariantPart(
DWARFDIE &die, const DWARFDIE &parent_die, CompilerType &class_clang_type,
const lldb::AccessType default_accesibility,
ClangASTImporter::LayoutInfo &layout_info) {
assert(die.Tag() == llvm::dwarf::DW_TAG_variant_part);
assert(SymbolFileDWARF::GetLanguage(*die.GetCU()) ==
LanguageType::eLanguageTypeRust);

ModuleSP module_sp = parent_die.GetDWARF()->GetObjectFile()->GetModule();

VariantPart variants(die, parent_die, module_sp);

auto discriminant_type =
die.ResolveTypeUID(variants.discriminant().type_ref.Reference());

auto decl_context = m_ast.GetDeclContextForType(class_clang_type);

auto inner_holder = m_ast.CreateRecordType(
decl_context, OptionalClangModuleID(), lldb::eAccessPublic,
std::string(
llvm::formatv("{0}$Inner", class_clang_type.GetTypeName(false))),
clang::TTK_Union, lldb::eLanguageTypeRust);
m_ast.StartTagDeclarationDefinition(inner_holder);
m_ast.SetIsPacked(inner_holder);

for (auto member : variants.members()) {

auto has_discriminant = !member.IsDefault();

auto member_type = die.ResolveTypeUID(member.type_ref.Reference());

auto field_type = m_ast.CreateRecordType(
m_ast.GetDeclContextForType(inner_holder), OptionalClangModuleID(),
lldb::eAccessPublic,
std::string(llvm::formatv("{0}$Variant", member.GetName())),
clang::TTK_Struct, lldb::eLanguageTypeRust);

m_ast.StartTagDeclarationDefinition(field_type);
auto offset = member.byte_offset;

if (has_discriminant) {
m_ast.AddFieldToRecordType(
field_type, "$discr$", discriminant_type->GetFullCompilerType(),
lldb::eAccessPublic, variants.discriminant().byte_offset);
offset += discriminant_type->GetByteSize(nullptr).value_or(0);
}

m_ast.AddFieldToRecordType(field_type, "value",
member_type->GetFullCompilerType(),
lldb::eAccessPublic, offset * 8);

m_ast.CompleteTagDeclarationDefinition(field_type);

auto name = has_discriminant
? llvm::formatv("$variant${0}", member.discr_value.value())
: std::string("$variant$");

auto variant_decl =
m_ast.AddFieldToRecordType(inner_holder, llvm::StringRef(name),
field_type, default_accesibility, 0);

layout_info.field_offsets.insert({variant_decl, 0});
}

auto inner_field = m_ast.AddFieldToRecordType(class_clang_type,
llvm::StringRef("$variants$"),
inner_holder, eAccessPublic, 0);

m_ast.CompleteTagDeclarationDefinition(inner_holder);

layout_info.field_offsets.insert({inner_field, 0});
}
15 changes: 15 additions & 0 deletions lldb/source/Plugins/SymbolFile/DWARF/DWARFASTParserClang.h
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,21 @@ class DWARFASTParserClang : public DWARFASTParser {
const lldb::ModuleSP &module_sp,
std::vector<std::unique_ptr<clang::CXXBaseSpecifier>> &base_classes,
lldb_private::ClangASTImporter::LayoutInfo &layout_info);

/// Parses DW_TAG_variant_part DIE into a structure that encodes all variants
/// Note that this is currently being emitted by rustc and not Clang
/// \param die DW_TAG_variant_part DIE to parse
/// \param parent_die The parent DW_TAG_structure_type to parse
/// \param class_clang_type The Rust struct representing parent_die.
/// \param default_accesibility The default accessibility that is given to
/// base classes if they don't have an explicit accessibility set
/// \param layout_info The layout information that will be updated for
// base classes with the base offset
void
ParseRustVariantPart(DWARFDIE &die, const DWARFDIE &parent_die,
lldb_private::CompilerType &class_clang_type,
const lldb::AccessType default_accesibility,
lldb_private::ClangASTImporter::LayoutInfo &layout_info);
};

/// Parsed form of all attributes that are relevant for type reconstruction.
Expand Down
63 changes: 63 additions & 0 deletions lldb/test/API/lang/rust/enum-structs/RustEnumValue.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"""Helper library to traverse data emitted for Rust enums """
from lldbsuite.test.lldbtest import *

DISCRIMINANT_MEMBER_NAME = "$discr$"
VALUE_MEMBER_NAME = "value"

class RustEnumValue:

def __init__(self, value: lldb.SBValue):
self.value = value

def getAllVariantTypes(self):
result = []
for i in range(self._inner().GetNumChildren()):
result.append(self.getVariantByIndex(i).GetDisplayTypeName())
return result

def _inner(self) -> lldb.SBValue:
return self.value.GetChildAtIndex(0)

def getVariantByIndex(self, index):
return self._inner().GetChildAtIndex(index).GetChildMemberWithName(VALUE_MEMBER_NAME)

@staticmethod
def _getDiscriminantValueAsUnsigned(discr_sbvalue: lldb.SBValue):
byte_size = discr_sbvalue.GetType().GetByteSize()
error = lldb.SBError()

# when discriminant is u16 Clang emits 'unsigned char'
# and LLDB seems to treat it as character type disalowing to call GetValueAsUnsigned
if byte_size == 1:
return discr_sbvalue.GetData().GetUnsignedInt8(error, 0)
elif byte_size == 2:
return discr_sbvalue.GetData().GetUnsignedInt16(error, 0)
elif byte_size == 4:
return discr_sbvalue.GetData().GetUnsignedInt32(error, 0)
elif byte_size == 8:
return discr_sbvalue.GetData().GetUnsignedInt64(error, 0)
else:
return discr_sbvalue.GetValueAsUnsigned()

def getCurrentVariantIndex(self):
default_index = 0
for i in range(self._inner().GetNumChildren()):
variant: lldb.SBValue = self._inner().GetChildAtIndex(i);
discr = variant.GetChildMemberWithName(DISCRIMINANT_MEMBER_NAME)
if discr.IsValid():
discr_unsigned_value = RustEnumValue._getDiscriminantValueAsUnsigned(discr)
if variant.GetName() == f"$variant${discr_unsigned_value}":
return discr_unsigned_value
else:
default_index = i
return default_index

def getFields(self):
result = []
for i in range(self._inner().GetNumChildren()):
type: lldb.SBType = self._inner().GetType()
result.append(type.GetFieldAtIndex(i).GetName())
return result

def getCurrentValue(self) -> lldb.SBValue:
return self.getVariantByIndex(self.getCurrentVariantIndex())
Loading

0 comments on commit e84751a

Please sign in to comment.