Skip to content

Commit 91f4a84

Browse files
[lldb][DWARFIndex] Use IDX_parent to implement GetFullyQualifiedType query (#79932)
This commit changes DebugNamesDWARFIndex so that it now overrides `GetFullyQualifiedType` and attempts to use DW_IDX_parent, when available, to speed up such queries. When this type of information is not available, the base-class implementation is used. With this commit, we now achieve the 4x speedups reported in [1]. [1]: https://discourse.llvm.org/t/rfc-improve-dwarf-5-debug-names-type-lookup-parsing-speed/74151/38
1 parent 5296149 commit 91f4a84

File tree

6 files changed

+328
-0
lines changed

6 files changed

+328
-0
lines changed

lldb/source/Plugins/SymbolFile/DWARF/DWARFDeclContext.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ class DWARFDeclContext {
4747

4848
DWARFDeclContext() : m_entries() {}
4949

50+
DWARFDeclContext(llvm::ArrayRef<Entry> entries) {
51+
llvm::append_range(m_entries, entries);
52+
}
53+
5054
void AppendDeclContext(dw_tag_t tag, const char *name) {
5155
m_entries.push_back(Entry(tag, name));
5256
}

lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.cpp

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#include "lldb/Core/Module.h"
1414
#include "lldb/Utility/RegularExpression.h"
1515
#include "lldb/Utility/Stream.h"
16+
#include "llvm/ADT/Sequence.h"
1617
#include <optional>
1718

1819
using namespace lldb_private;
@@ -218,6 +219,108 @@ void DebugNamesDWARFIndex::GetCompleteObjCClass(
218219
m_fallback.GetCompleteObjCClass(class_name, must_be_implementation, callback);
219220
}
220221

222+
namespace {
223+
using Entry = llvm::DWARFDebugNames::Entry;
224+
225+
/// If `entry` and all of its parents have an `IDX_parent`, use that information
226+
/// to build and return a list of at most `max_parents` parent Entries.
227+
/// `entry` itself is not included in the list.
228+
/// If any parent does not have an `IDX_parent`, or the Entry data is corrupted,
229+
/// nullopt is returned.
230+
std::optional<llvm::SmallVector<Entry, 4>>
231+
getParentChain(Entry entry, uint32_t max_parents) {
232+
llvm::SmallVector<Entry, 4> parent_entries;
233+
234+
do {
235+
if (!entry.hasParentInformation())
236+
return std::nullopt;
237+
238+
llvm::Expected<std::optional<Entry>> parent = entry.getParentDIEEntry();
239+
if (!parent) {
240+
// Bad data.
241+
LLDB_LOG_ERROR(
242+
GetLog(DWARFLog::Lookups), parent.takeError(),
243+
"Failed to extract parent entry from a non-empty IDX_parent");
244+
return std::nullopt;
245+
}
246+
247+
// Last parent in the chain.
248+
if (!parent->has_value())
249+
break;
250+
251+
parent_entries.push_back(**parent);
252+
entry = **parent;
253+
} while (parent_entries.size() < max_parents);
254+
255+
return parent_entries;
256+
}
257+
} // namespace
258+
259+
void DebugNamesDWARFIndex::GetFullyQualifiedType(
260+
const DWARFDeclContext &context,
261+
llvm::function_ref<bool(DWARFDIE die)> callback) {
262+
if (context.GetSize() == 0)
263+
return;
264+
265+
llvm::StringRef leaf_name = context[0].name;
266+
llvm::SmallVector<llvm::StringRef> parent_names;
267+
for (auto idx : llvm::seq<int>(1, context.GetSize()))
268+
parent_names.emplace_back(context[idx].name);
269+
270+
// For each entry, grab its parent chain and check if we have a match.
271+
for (const DebugNames::Entry &entry :
272+
m_debug_names_up->equal_range(leaf_name)) {
273+
if (!isType(entry.tag()))
274+
continue;
275+
276+
// Grab at most one extra parent, subsequent parents are not necessary to
277+
// test equality.
278+
std::optional<llvm::SmallVector<Entry, 4>> parent_chain =
279+
getParentChain(entry, parent_names.size() + 1);
280+
281+
if (!parent_chain) {
282+
// Fallback: use the base class implementation.
283+
if (!ProcessEntry(entry, [&](DWARFDIE die) {
284+
return GetFullyQualifiedTypeImpl(context, die, callback);
285+
}))
286+
return;
287+
continue;
288+
}
289+
290+
if (SameParentChain(parent_names, *parent_chain) &&
291+
!ProcessEntry(entry, callback))
292+
return;
293+
}
294+
}
295+
296+
bool DebugNamesDWARFIndex::SameParentChain(
297+
llvm::ArrayRef<llvm::StringRef> parent_names,
298+
llvm::ArrayRef<DebugNames::Entry> parent_entries) const {
299+
300+
if (parent_entries.size() != parent_names.size())
301+
return false;
302+
303+
auto SameAsEntryATName = [this](llvm::StringRef name,
304+
const DebugNames::Entry &entry) {
305+
// Peek at the AT_name of `entry` and test equality to `name`.
306+
auto maybe_dieoffset = entry.getDIEUnitOffset();
307+
if (!maybe_dieoffset)
308+
return false;
309+
auto die_ref = ToDIERef(entry);
310+
if (!die_ref)
311+
return false;
312+
return name == m_debug_info.PeekDIEName(*die_ref);
313+
};
314+
315+
// If the AT_name of any parent fails to match the expected name, we don't
316+
// have a match.
317+
for (auto [parent_name, parent_entry] :
318+
llvm::zip_equal(parent_names, parent_entries))
319+
if (!SameAsEntryATName(parent_name, parent_entry))
320+
return false;
321+
return true;
322+
}
323+
221324
void DebugNamesDWARFIndex::GetTypes(
222325
ConstString name, llvm::function_ref<bool(DWARFDIE die)> callback) {
223326
for (const DebugNames::Entry &entry :

lldb/source/Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,11 @@ class DebugNamesDWARFIndex : public DWARFIndex {
4242
void GetCompleteObjCClass(
4343
ConstString class_name, bool must_be_implementation,
4444
llvm::function_ref<bool(DWARFDIE die)> callback) override;
45+
46+
/// Uses DWARF5's IDX_parent fields, when available, to speed up this query.
47+
void GetFullyQualifiedType(
48+
const DWARFDeclContext &context,
49+
llvm::function_ref<bool(DWARFDIE die)> callback) override;
4550
void GetTypes(ConstString name,
4651
llvm::function_ref<bool(DWARFDIE die)> callback) override;
4752
void GetTypes(const DWARFDeclContext &context,
@@ -83,6 +88,10 @@ class DebugNamesDWARFIndex : public DWARFIndex {
8388
bool ProcessEntry(const DebugNames::Entry &entry,
8489
llvm::function_ref<bool(DWARFDIE die)> callback);
8590

91+
/// Returns true if `parent_entries` have identical names to `parent_names`.
92+
bool SameParentChain(llvm::ArrayRef<llvm::StringRef> parent_names,
93+
llvm::ArrayRef<DebugNames::Entry> parent_entries) const;
94+
8695
static void MaybeLogLookupError(llvm::Error error,
8796
const DebugNames::NameIndex &ni,
8897
llvm::StringRef name);

lldb/source/Plugins/SymbolFile/DWARF/SymbolFileDWARF.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,9 @@ class SymbolFileDWARF : public SymbolFileCommon {
373373

374374
Type *ResolveTypeUID(const DIERef &die_ref);
375375

376+
/// Returns the DWARFIndex for this symbol, if it exists.
377+
DWARFIndex *getIndex() { return m_index.get(); }
378+
376379
protected:
377380
SymbolFileDWARF(const SymbolFileDWARF &) = delete;
378381
const SymbolFileDWARF &operator=(const SymbolFileDWARF &) = delete;

lldb/unittests/SymbolFile/DWARF/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
add_lldb_unittest(SymbolFileDWARFTests
22
DWARFASTParserClangTests.cpp
3+
DWARFDebugNamesIndexTest.cpp
34
DWARFDIETest.cpp
45
DWARFIndexCachingTest.cpp
56
DWARFUnitTest.cpp
Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
//===-- DWARFDIETest.cpp ----------------------------------------------=---===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "Plugins/SymbolFile/DWARF/DWARFDIE.h"
10+
#include "Plugins/SymbolFile/DWARF/DWARFDebugInfo.h"
11+
#include "Plugins/SymbolFile/DWARF/DWARFDeclContext.h"
12+
#include "Plugins/SymbolFile/DWARF/DebugNamesDWARFIndex.h"
13+
#include "TestingSupport/Symbol/YAMLModuleTester.h"
14+
#include "llvm/ADT/STLExtras.h"
15+
#include "gmock/gmock.h"
16+
#include "gtest/gtest.h"
17+
18+
using namespace lldb;
19+
using namespace lldb_private;
20+
using namespace lldb_private::plugin::dwarf;
21+
using StringRef = llvm::StringRef;
22+
23+
static void
24+
check_num_matches(DebugNamesDWARFIndex &index, int expected_num_matches,
25+
llvm::ArrayRef<DWARFDeclContext::Entry> ctx_entries) {
26+
DWARFDeclContext ctx(ctx_entries);
27+
int num_matches = 0;
28+
29+
index.GetFullyQualifiedType(ctx, [&](DWARFDIE die) {
30+
num_matches++;
31+
return true;
32+
});
33+
ASSERT_EQ(num_matches, expected_num_matches);
34+
}
35+
36+
static DWARFDeclContext::Entry make_entry(const char *c) {
37+
return DWARFDeclContext::Entry(dwarf::DW_TAG_class_type, c);
38+
}
39+
40+
TEST(DWARFDebugNamesIndexTest, FullyQualifiedQueryWithIDXParent) {
41+
const char *yamldata = R"(
42+
--- !ELF
43+
FileHeader:
44+
Class: ELFCLASS64
45+
Data: ELFDATA2LSB
46+
Type: ET_EXEC
47+
Machine: EM_386
48+
DWARF:
49+
debug_str:
50+
- '1'
51+
- '2'
52+
- '3'
53+
debug_abbrev:
54+
- Table:
55+
# We intentionally don't nest types in debug_info: if the nesting is not
56+
# inferred from debug_names, we want the test to fail.
57+
- Code: 0x1
58+
Tag: DW_TAG_compile_unit
59+
Children: DW_CHILDREN_yes
60+
- Code: 0x2
61+
Tag: DW_TAG_class_type
62+
Children: DW_CHILDREN_no
63+
Attributes:
64+
- Attribute: DW_AT_name
65+
Form: DW_FORM_strp
66+
debug_info:
67+
- Version: 4
68+
AddrSize: 8
69+
Entries:
70+
- AbbrCode: 0x1
71+
- AbbrCode: 0x2
72+
Values:
73+
- Value: 0x0 # Name "1"
74+
- AbbrCode: 0x2
75+
Values:
76+
- Value: 0x2 # Name "2"
77+
- AbbrCode: 0x2
78+
Values:
79+
- Value: 0x4 # Name "3"
80+
- AbbrCode: 0x0
81+
debug_names:
82+
Abbreviations:
83+
- Code: 0x11
84+
Tag: DW_TAG_class_type
85+
Indices:
86+
- Idx: DW_IDX_parent
87+
Form: DW_FORM_flag_present
88+
- Idx: DW_IDX_die_offset
89+
Form: DW_FORM_ref4
90+
- Code: 0x22
91+
Tag: DW_TAG_class_type
92+
Indices:
93+
- Idx: DW_IDX_parent
94+
Form: DW_FORM_ref4
95+
- Idx: DW_IDX_die_offset
96+
Form: DW_FORM_ref4
97+
Entries:
98+
- Name: 0x0 # strp to Name1
99+
Code: 0x11
100+
Values:
101+
- 0xc # Die offset to entry named "1"
102+
- Name: 0x2 # strp to Name2
103+
Code: 0x22
104+
Values:
105+
- 0x0 # Parent = First entry ("1")
106+
- 0x11 # Die offset to entry named "1:2"
107+
- Name: 0x4 # strp to Name3
108+
Code: 0x22
109+
Values:
110+
- 0x6 # Parent = Second entry ("1::2")
111+
- 0x16 # Die offset to entry named "1::2::3"
112+
- Name: 0x4 # strp to Name3
113+
Code: 0x11
114+
Values:
115+
- 0x16 # Die offset to entry named "3"
116+
)";
117+
118+
YAMLModuleTester t(yamldata);
119+
auto *symbol_file =
120+
llvm::cast<SymbolFileDWARF>(t.GetModule()->GetSymbolFile());
121+
auto *index = static_cast<DebugNamesDWARFIndex *>(symbol_file->getIndex());
122+
ASSERT_NE(index, nullptr);
123+
124+
check_num_matches(*index, 1, {make_entry("1")});
125+
check_num_matches(*index, 1, {make_entry("2"), make_entry("1")});
126+
check_num_matches(*index, 1,
127+
{make_entry("3"), make_entry("2"), make_entry("1")});
128+
check_num_matches(*index, 0, {make_entry("2")});
129+
check_num_matches(*index, 1, {make_entry("3")});
130+
}
131+
132+
TEST(DWARFDebugNamesIndexTest, FullyQualifiedQueryWithoutIDXParent) {
133+
const char *yamldata = R"(
134+
--- !ELF
135+
FileHeader:
136+
Class: ELFCLASS64
137+
Data: ELFDATA2LSB
138+
Type: ET_EXEC
139+
Machine: EM_386
140+
DWARF:
141+
debug_str:
142+
- '1'
143+
- '2'
144+
debug_abbrev:
145+
- Table:
146+
- Code: 0x1
147+
Tag: DW_TAG_compile_unit
148+
Children: DW_CHILDREN_yes
149+
- Code: 0x2
150+
Tag: DW_TAG_class_type
151+
Children: DW_CHILDREN_yes
152+
Attributes:
153+
- Attribute: DW_AT_name
154+
Form: DW_FORM_strp
155+
- Code: 0x3
156+
Tag: DW_TAG_class_type
157+
Children: DW_CHILDREN_no
158+
Attributes:
159+
- Attribute: DW_AT_name
160+
Form: DW_FORM_strp
161+
debug_info:
162+
- Version: 4
163+
AddrSize: 8
164+
Entries:
165+
- AbbrCode: 0x1
166+
- AbbrCode: 0x2
167+
Values:
168+
- Value: 0x0 # Name "1"
169+
- AbbrCode: 0x3
170+
Values:
171+
- Value: 0x2 # Name "2"
172+
- AbbrCode: 0x0
173+
- AbbrCode: 0x3
174+
Values:
175+
- Value: 0x2 # Name "2"
176+
- AbbrCode: 0x0
177+
debug_names:
178+
Abbreviations:
179+
- Code: 0x1
180+
Tag: DW_TAG_class_type
181+
Indices:
182+
- Idx: DW_IDX_die_offset
183+
Form: DW_FORM_ref4
184+
Entries:
185+
- Name: 0x0 # strp to Name1
186+
Code: 0x1
187+
Values:
188+
- 0xc # Die offset to entry named "1"
189+
- Name: 0x2 # strp to Name2
190+
Code: 0x1
191+
Values:
192+
- 0x11 # Die offset to entry named "1::2"
193+
- Name: 0x2 # strp to Name2
194+
Code: 0x1
195+
Values:
196+
- 0x17 # Die offset to entry named "2"
197+
)";
198+
199+
YAMLModuleTester t(yamldata);
200+
auto *symbol_file =
201+
llvm::cast<SymbolFileDWARF>(t.GetModule()->GetSymbolFile());
202+
auto *index = static_cast<DebugNamesDWARFIndex *>(symbol_file->getIndex());
203+
ASSERT_NE(index, nullptr);
204+
205+
check_num_matches(*index, 1, {make_entry("1")});
206+
check_num_matches(*index, 1, {make_entry("2"), make_entry("1")});
207+
check_num_matches(*index, 1, {make_entry("2")});
208+
}

0 commit comments

Comments
 (0)