Skip to content

🍒[lldb] Add support for new libc++ __compressed_pair layout #10257

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Mar 18, 2025
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
1 change: 0 additions & 1 deletion clang/include/clang/AST/ASTImporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -258,7 +258,6 @@ class TypeSourceInfo;
FoundDeclsTy findDeclsInToCtx(DeclContext *DC, DeclarationName Name);

void AddToLookupTable(Decl *ToD);
llvm::Error ImportAttrs(Decl *ToD, Decl *FromD);

protected:
/// Can be overwritten by subclasses to implement their own import logic.
Expand Down
4 changes: 0 additions & 4 deletions clang/include/clang/AST/DeclCXX.h
Original file line number Diff line number Diff line change
Expand Up @@ -1188,10 +1188,6 @@ class CXXRecordDecl : public RecordDecl {
///
/// \note This does NOT include a check for union-ness.
bool isEmpty() const { return data().Empty; }
/// Marks this record as empty. This is used by DWARFASTParserClang
/// when parsing records with empty fields having [[no_unique_address]]
/// attribute
void markEmpty() { data().Empty = true; }

void setInitMethod(bool Val) { data().HasInitMethod = Val; }
bool hasInitMethod() const { return data().HasInitMethod; }
Expand Down
30 changes: 9 additions & 21 deletions clang/lib/AST/ASTImporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4201,12 +4201,6 @@ ExpectedDecl ASTNodeImporter::VisitFieldDecl(FieldDecl *D) {
D->getInClassInitStyle()))
return ToField;

// We need [[no_unqiue_address]] attributes to be added to FieldDecl, before
// we add fields in CXXRecordDecl::addedMember, otherwise record will be
// marked as having non-zero size.
Err = Importer.ImportAttrs(ToField, D);
if (Err)
return std::move(Err);
ToField->setAccess(D->getAccess());
ToField->setLexicalDeclContext(LexicalDC);
ToField->setImplicit(D->isImplicit());
Expand Down Expand Up @@ -9423,19 +9417,6 @@ TranslationUnitDecl *ASTImporter::GetFromTU(Decl *ToD) {
return FromDPos->second->getTranslationUnitDecl();
}

Error ASTImporter::ImportAttrs(Decl *ToD, Decl *FromD) {
if (!FromD->hasAttrs() || ToD->hasAttrs())
return Error::success();
for (const Attr *FromAttr : FromD->getAttrs()) {
auto ToAttrOrErr = Import(FromAttr);
if (ToAttrOrErr)
ToD->addAttr(*ToAttrOrErr);
else
return ToAttrOrErr.takeError();
}
return Error::success();
}

Expected<Decl *> ASTImporter::Import(Decl *FromD) {
if (!FromD)
return nullptr;
Expand Down Expand Up @@ -9569,8 +9550,15 @@ Expected<Decl *> ASTImporter::Import(Decl *FromD) {
}
// Make sure that ImportImpl registered the imported decl.
assert(ImportedDecls.count(FromD) != 0 && "Missing call to MapImported?");
if (auto Error = ImportAttrs(ToD, FromD))
return std::move(Error);

if (FromD->hasAttrs())
for (const Attr *FromAttr : FromD->getAttrs()) {
auto ToAttrOrErr = Import(FromAttr);
if (ToAttrOrErr)
ToD->addAttr(*ToAttrOrErr);
else
return ToAttrOrErr.takeError();
}

// Notify subclasses.
Imported(FromD, ToD);
Expand Down
23 changes: 0 additions & 23 deletions clang/unittests/AST/ASTImporterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9376,29 +9376,6 @@ TEST_P(ASTImporterOptionSpecificTestBase, VaListCpp) {
ToVaList->getUnderlyingType(), ToBuiltinVaList->getUnderlyingType()));
}

TEST_P(ASTImporterOptionSpecificTestBase,
ImportDefinitionOfEmptyClassWithNoUniqueAddressField) {
Decl *FromTU = getTuDecl(
R"(
struct B {};
struct A { B b; };
)",
Lang_CXX20);

CXXRecordDecl *FromD = FirstDeclMatcher<CXXRecordDecl>().match(
FromTU, cxxRecordDecl(hasName("A")));

for (auto *FD : FromD->fields())
FD->addAttr(clang::NoUniqueAddressAttr::Create(FromD->getASTContext(),
clang::SourceRange()));
FromD->markEmpty();

CXXRecordDecl *ToD = Import(FromD, Lang_CXX20);
EXPECT_TRUE(ToD->isEmpty());
for (auto *FD : ToD->fields())
EXPECT_EQ(true, FD->hasAttr<NoUniqueAddressAttr>());
}

TEST_P(ASTImporterOptionSpecificTestBase, ImportExistingTypedefToRecord) {
const char *Code =
R"(
Expand Down
28 changes: 22 additions & 6 deletions lldb/examples/synthetic/libcxx.py
Original file line number Diff line number Diff line change
Expand Up @@ -721,6 +721,12 @@ def _get_value_of_compressed_pair(self, pair):
def update(self):
logger = lldb.formatters.Logger.Logger()
try:
has_compressed_pair_layout = True
alloc_valobj = self.valobj.GetChildMemberWithName("__alloc_")
size_valobj = self.valobj.GetChildMemberWithName("__size_")
if alloc_valobj.IsValid() and size_valobj.IsValid():
has_compressed_pair_layout = False

# A deque is effectively a two-dim array, with fixed width.
# 'map' contains pointers to the rows of this array. The
# full memory area allocated by the deque is delimited
Expand All @@ -734,9 +740,13 @@ def update(self):
# variable tells which element in this NxM array is the 0th
# one, and the 'size' element gives the number of elements
# in the deque.
count = self._get_value_of_compressed_pair(
self.valobj.GetChildMemberWithName("__size_")
)
if has_compressed_pair_layout:
count = self._get_value_of_compressed_pair(
self.valobj.GetChildMemberWithName("__size_")
)
else:
count = size_valobj.GetValueAsUnsigned(0)

# give up now if we cant access memory reliably
if self.block_size < 0:
logger.write("block_size < 0")
Expand All @@ -748,9 +758,15 @@ def update(self):
self.map_begin = map_.GetChildMemberWithName("__begin_")
map_begin = self.map_begin.GetValueAsUnsigned(0)
map_end = map_.GetChildMemberWithName("__end_").GetValueAsUnsigned(0)
map_endcap = self._get_value_of_compressed_pair(
map_.GetChildMemberWithName("__end_cap_")
)

if has_compressed_pair_layout:
map_endcap = self._get_value_of_compressed_pair(
map_.GetChildMemberWithName("__end_cap_")
)
else:
map_endcap = map_.GetChildMemberWithName(
"__end_cap_"
).GetValueAsUnsigned(0)

# check consistency
if not map_first <= map_begin <= map_end <= map_endcap:
Expand Down
10 changes: 9 additions & 1 deletion lldb/include/lldb/Symbol/SymbolFile.h
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,15 @@ class SymbolFile : public PluginInterface {
/// The characteristics of an array type.
struct ArrayInfo {
int64_t first_index = 0;
llvm::SmallVector<uint64_t, 1> element_orders;

///< Each entry belongs to a distinct DW_TAG_subrange_type.
///< For multi-dimensional DW_TAG_array_types we would have
///< an entry for each dimension. An entry represents the
///< optional element count of the subrange.
///
///< The order of entries follows the order of the DW_TAG_subrange_type
///< children of this DW_TAG_array_type.
llvm::SmallVector<std::optional<uint64_t>, 1> element_orders;
uint32_t byte_stride = 0;
uint32_t bit_stride = 0;
};
Expand Down
85 changes: 63 additions & 22 deletions lldb/source/Plugins/Language/CPlusPlus/LibCxx.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,40 @@
#include "Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.h"
#include "Plugins/TypeSystem/Clang/TypeSystemClang.h"
#include "lldb/lldb-enumerations.h"
#include "lldb/lldb-forward.h"
#include <optional>
#include <tuple>

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::formatters;

static void consumeInlineNamespace(llvm::StringRef &name) {
// Delete past an inline namespace, if any: __[a-zA-Z0-9_]+::
auto scratch = name;
if (scratch.consume_front("__") && std::isalnum(scratch[0])) {
scratch = scratch.drop_while([](char c) { return std::isalnum(c); });
if (scratch.consume_front("::")) {
// Successfully consumed a namespace.
name = scratch;
}
}
}

bool lldb_private::formatters::isOldCompressedPairLayout(
ValueObject &pair_obj) {
return isStdTemplate(pair_obj.GetTypeName(), "__compressed_pair");
}

bool lldb_private::formatters::isStdTemplate(ConstString type_name,
llvm::StringRef type) {
llvm::StringRef name = type_name.GetStringRef();
// The type name may be prefixed with `std::__<inline-namespace>::`.
if (name.consume_front("std::"))
consumeInlineNamespace(name);
return name.consume_front(type) && name.starts_with("<");
}

lldb::ValueObjectSP lldb_private::formatters::GetChildMemberWithName(
ValueObject &obj, llvm::ArrayRef<ConstString> alternative_names) {
for (ConstString name : alternative_names) {
Expand All @@ -53,7 +80,7 @@ lldb_private::formatters::GetFirstValueOfLibCXXCompressedPair(
if (first_child)
value = first_child->GetChildMemberWithName("__value_");
if (!value) {
// pre-r300140 member name
// pre-c88580c member name
value = pair.GetChildMemberWithName("__first_");
}
return value;
Expand All @@ -70,7 +97,7 @@ lldb_private::formatters::GetSecondValueOfLibCXXCompressedPair(
}
}
if (!value) {
// pre-r300140 member name
// pre-c88580c member name
value = pair.GetChildMemberWithName("__second_");
}
return value;
Expand Down Expand Up @@ -176,7 +203,9 @@ bool lldb_private::formatters::LibcxxUniquePointerSummaryProvider(
if (!ptr_sp)
return false;

ptr_sp = GetFirstValueOfLibCXXCompressedPair(*ptr_sp);
if (isOldCompressedPairLayout(*ptr_sp))
ptr_sp = GetFirstValueOfLibCXXCompressedPair(*ptr_sp);

if (!ptr_sp)
return false;

Expand Down Expand Up @@ -363,13 +392,22 @@ lldb_private::formatters::LibcxxUniquePtrSyntheticFrontEnd::Update() {

// Retrieve the actual pointer and the deleter, and clone them to give them
// user-friendly names.
ValueObjectSP value_pointer_sp = GetFirstValueOfLibCXXCompressedPair(*ptr_sp);
if (value_pointer_sp)
m_value_ptr_sp = value_pointer_sp->Clone(ConstString("pointer"));
if (isOldCompressedPairLayout(*ptr_sp)) {
if (ValueObjectSP value_pointer_sp =
GetFirstValueOfLibCXXCompressedPair(*ptr_sp))
m_value_ptr_sp = value_pointer_sp->Clone(ConstString("pointer"));

if (ValueObjectSP deleter_sp =
GetSecondValueOfLibCXXCompressedPair(*ptr_sp))
m_deleter_sp = deleter_sp->Clone(ConstString("deleter"));
} else {
m_value_ptr_sp = ptr_sp->Clone(ConstString("pointer"));

ValueObjectSP deleter_sp = GetSecondValueOfLibCXXCompressedPair(*ptr_sp);
if (deleter_sp)
m_deleter_sp = deleter_sp->Clone(ConstString("deleter"));
if (ValueObjectSP deleter_sp =
valobj_sp->GetChildMemberWithName("__deleter_"))
if (deleter_sp->GetNumChildrenIgnoringErrors() > 0)
m_deleter_sp = deleter_sp->Clone(ConstString("deleter"));
}

return lldb::ChildCacheState::eRefetch;
}
Expand Down Expand Up @@ -407,24 +445,27 @@ namespace {
enum class StringLayout { CSD, DSC };
}

static ValueObjectSP ExtractLibCxxStringData(ValueObject &valobj) {
if (auto rep_sp = valobj.GetChildMemberWithName("__rep_"))
return rep_sp;

ValueObjectSP valobj_r_sp = valobj.GetChildMemberWithName("__r_");
if (!valobj_r_sp || !valobj_r_sp->GetError().Success())
return nullptr;

if (!isOldCompressedPairLayout(*valobj_r_sp))
return nullptr;

return GetFirstValueOfLibCXXCompressedPair(*valobj_r_sp);
}

/// Determine the size in bytes of \p valobj (a libc++ std::string object) and
/// extract its data payload. Return the size + payload pair.
// TODO: Support big-endian architectures.
static std::optional<std::pair<uint64_t, ValueObjectSP>>
ExtractLibcxxStringInfo(ValueObject &valobj) {
ValueObjectSP valobj_r_sp = valobj.GetChildMemberWithName("__r_");
if (!valobj_r_sp || !valobj_r_sp->GetError().Success())
return {};

// __r_ is a compressed_pair of the actual data and the allocator. The data we
// want is in the first base class.
ValueObjectSP valobj_r_base_sp = valobj_r_sp->GetChildAtIndex(0);
if (!valobj_r_base_sp)
return {};

ValueObjectSP valobj_rep_sp =
valobj_r_base_sp->GetChildMemberWithName("__value_");
if (!valobj_rep_sp)
ValueObjectSP valobj_rep_sp = ExtractLibCxxStringData(valobj);
if (!valobj_rep_sp || !valobj_rep_sp->GetError().Success())
return {};

ValueObjectSP l = valobj_rep_sp->GetChildMemberWithName("__l");
Expand Down
3 changes: 2 additions & 1 deletion lldb/source/Plugins/Language/CPlusPlus/LibCxx.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ GetChildMemberWithName(ValueObject &obj,

lldb::ValueObjectSP GetFirstValueOfLibCXXCompressedPair(ValueObject &pair);
lldb::ValueObjectSP GetSecondValueOfLibCXXCompressedPair(ValueObject &pair);

bool isOldCompressedPairLayout(ValueObject &pair_obj);
bool isStdTemplate(ConstString type_name, llvm::StringRef type);

bool LibcxxStringSummaryProviderASCII(
ValueObject &valobj, Stream &stream,
Expand Down
Loading