Skip to content

Commit 91e94a7

Browse files
committed
[LLDB][Formatters] Re-enable std::function formatter with fixes to improve non-cached lookup performance
Performance issues lead to the libc++ std::function formatter to be disabled. We addressed some of those performance issues by adding caching see D67111 This PR fixes the first lookup performance by not using FindSymbolsMatchingRegExAndType(...) and instead finding the compilation unit the std::function wrapped callable should be in and then searching for the callable directly in the CU. Differential Revision: https://reviews.llvm.org/D69913
1 parent 7af6025 commit 91e94a7

File tree

8 files changed

+185
-93
lines changed

8 files changed

+185
-93
lines changed

lldb/include/lldb/Symbol/CompileUnit.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,18 @@ class CompileUnit : public std::enable_shared_from_this<CompileUnit>,
163163
void ForeachFunction(
164164
llvm::function_ref<bool(const lldb::FunctionSP &)> lambda) const;
165165

166+
/// Find a function in the compile unit based on the predicate matching_lambda
167+
///
168+
/// \param[in] matching_lambda
169+
/// A predicate that will be used within FindFunction to evaluate each
170+
/// FunctionSP in m_functions_by_uid. When the predicate returns true
171+
/// FindFunction will return the corresponding FunctionSP.
172+
///
173+
/// \return
174+
/// The first FunctionSP that the matching_lambda prediate returns true for.
175+
lldb::FunctionSP FindFunction(
176+
llvm::function_ref<bool(const lldb::FunctionSP &)> matching_lambda);
177+
166178
/// Dump the compile unit contents to the stream \a s.
167179
///
168180
/// \param[in] s

lldb/packages/Python/lldbsuite/test/functionalities/data-formatter/data-formatter-stl/libcxx/function/TestLibCxxFunction.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,22 @@ class LibCxxFunctionTestCase(TestBase):
1717

1818
# Run frame var for a variable twice. Verify we do not hit the cache
1919
# the first time but do the second time.
20-
def run_frame_var_check_cache_use(self, variable, result_to_match):
20+
def run_frame_var_check_cache_use(self, variable, result_to_match, skip_find_function=False):
2121
self.runCmd("log timers reset")
2222
self.expect("frame variable " + variable,
2323
substrs=[variable + " = " + result_to_match])
24-
self.expect("log timers dump",
25-
substrs=["lldb_private::Module::FindSymbolsMatchingRegExAndType"])
24+
if not skip_find_function:
25+
self.expect("log timers dump",
26+
substrs=["lldb_private::CompileUnit::FindFunction"])
2627

2728
self.runCmd("log timers reset")
2829
self.expect("frame variable " + variable,
2930
substrs=[variable + " = " + result_to_match])
3031
self.expect("log timers dump",
3132
matching=False,
32-
substrs=["lldb_private::Module::FindSymbolsMatchingRegExAndType"])
33+
substrs=["lldb_private::CompileUnit::FindFunction"])
3334

3435

35-
# Temporarily skipping for everywhere b/c we are disabling the std::function formatter
36-
# due to performance issues but plan on turning it back on once they are addressed.
37-
@skipIf
3836
@add_test_categories(["libc++"])
3937
def test(self):
4038
"""Test that std::function as defined by libc++ is correctly printed by LLDB"""
@@ -61,8 +59,10 @@ def test(self):
6159
lldbutil.continue_to_breakpoint(self.process(), bkpt)
6260

6361
self.run_frame_var_check_cache_use("f2", "Lambda in File main.cpp at Line 43")
64-
self.run_frame_var_check_cache_use("f3", "Lambda in File main.cpp at Line 47")
65-
self.run_frame_var_check_cache_use("f4", "Function in File main.cpp at Line 16")
62+
self.run_frame_var_check_cache_use("f3", "Lambda in File main.cpp at Line 47", True)
63+
# TODO reenable this case when std::function formatter supports
64+
# general callable object case.
65+
#self.run_frame_var_check_cache_use("f4", "Function in File main.cpp at Line 16")
6666

6767
# These cases won't hit the cache at all but also don't require
6868
# an expensive lookup.

lldb/packages/Python/lldbsuite/test/lang/cpp/std-function-step-into-callable/TestStdFunctionStepIntoCallable.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,12 @@ def test(self):
5959
self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetFileSpec().GetFilename(), self.main_source) ;
6060
process.Continue()
6161

62-
thread.StepInto()
63-
self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.source_bar_operator_line ) ;
64-
self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetFileSpec().GetFilename(), self.main_source) ;
65-
process.Continue()
62+
# TODO reenable this case when std::function formatter supports
63+
# general callable object case.
64+
#thread.StepInto()
65+
#self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.source_bar_operator_line ) ;
66+
#self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetFileSpec().GetFilename(), self.main_source) ;
67+
#process.Continue()
6668

6769
thread.StepInto()
6870
self.assertEqual( thread.GetFrameAtIndex(0).GetLineEntry().GetLine(), self.source_bar_add_num_line ) ;

lldb/packages/Python/lldbsuite/test/lang/cpp/std-function-step-into-callable/main.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ int main (int argc, char *argv[])
3232
return f_mem(bar1) + // Set break point at this line.
3333
f1(acc,acc) + // Source main invoking f1
3434
f2(acc) + // Set break point at this line.
35-
f3(acc+1,acc+2) + // Set break point at this line.
36-
f4() + // Set break point at this line.
35+
f3(acc+1,acc+2) + // Set break point at this line.
36+
// TODO reenable this case when std::function formatter supports
37+
// general callable object case.
38+
//f4() + // Set break point at this line.
3739
f5(bar1, 10); // Set break point at this line.
3840
}

lldb/source/Plugins/Language/CPlusPlus/CPlusPlusLanguage.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -567,6 +567,11 @@ static void LoadLibCxxFormatters(lldb::TypeCategoryImplSP cpp_category_sp) {
567567
"weak_ptr synthetic children",
568568
ConstString("^(std::__[[:alnum:]]+::)weak_ptr<.+>(( )?&)?$"),
569569
stl_synth_flags, true);
570+
AddCXXSummary(cpp_category_sp,
571+
lldb_private::formatters::LibcxxFunctionSummaryProvider,
572+
"libc++ std::function summary provider",
573+
ConstString("^std::__[[:alnum:]]+::function<.+>$"),
574+
stl_summary_flags, true);
570575

571576
stl_summary_flags.SetDontShowChildren(false);
572577
stl_summary_flags.SetSkipPointers(false);

lldb/source/Plugins/LanguageRuntime/CPlusPlus/CPPLanguageRuntime.cpp

Lines changed: 121 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@
2121
#include "lldb/Core/PluginManager.h"
2222
#include "lldb/Core/UniqueCStringMap.h"
2323
#include "lldb/Symbol/ClangASTContext.h"
24+
#include "lldb/Symbol/CompileUnit.h"
2425
#include "lldb/Target/ABI.h"
2526
#include "lldb/Target/ExecutionContext.h"
2627
#include "lldb/Target/RegisterContext.h"
2728
#include "lldb/Target/SectionLoadList.h"
2829
#include "lldb/Target/StackFrame.h"
2930
#include "lldb/Target/ThreadPlanRunToAddress.h"
3031
#include "lldb/Target/ThreadPlanStepInRange.h"
32+
#include "lldb/Utility/Timer.h"
3133

3234
using namespace lldb;
3335
using namespace lldb_private;
@@ -58,9 +60,53 @@ bool CPPLanguageRuntime::GetObjectDescription(
5860
return false;
5961
}
6062

63+
bool contains_lambda_identifier(llvm::StringRef &str_ref) {
64+
return str_ref.contains("$_") || str_ref.contains("'lambda'");
65+
};
66+
67+
CPPLanguageRuntime::LibCppStdFunctionCallableInfo
68+
line_entry_helper(Target &target, const SymbolContext &sc, Symbol *symbol,
69+
llvm::StringRef first_template_param_sref,
70+
bool has___invoke) {
71+
72+
CPPLanguageRuntime::LibCppStdFunctionCallableInfo optional_info;
73+
74+
AddressRange range;
75+
sc.GetAddressRange(eSymbolContextEverything, 0, false, range);
76+
77+
Address address = range.GetBaseAddress();
78+
79+
Address addr;
80+
if (target.ResolveLoadAddress(address.GetCallableLoadAddress(&target),
81+
addr)) {
82+
LineEntry line_entry;
83+
addr.CalculateSymbolContextLineEntry(line_entry);
84+
85+
if (contains_lambda_identifier(first_template_param_sref) || has___invoke) {
86+
// Case 1 and 2
87+
optional_info.callable_case = lldb_private::CPPLanguageRuntime::
88+
LibCppStdFunctionCallableCase::Lambda;
89+
} else {
90+
// Case 3
91+
optional_info.callable_case = lldb_private::CPPLanguageRuntime::
92+
LibCppStdFunctionCallableCase::CallableObject;
93+
}
94+
95+
optional_info.callable_symbol = *symbol;
96+
optional_info.callable_line_entry = line_entry;
97+
optional_info.callable_address = addr;
98+
}
99+
100+
return optional_info;
101+
}
102+
61103
CPPLanguageRuntime::LibCppStdFunctionCallableInfo
62104
CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
63105
lldb::ValueObjectSP &valobj_sp) {
106+
static Timer::Category func_cat(LLVM_PRETTY_FUNCTION);
107+
Timer scoped_timer(func_cat,
108+
"CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo");
109+
64110
LibCppStdFunctionCallableInfo optional_info;
65111

66112
if (!valobj_sp)
@@ -93,7 +139,7 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
93139
// this entry and lookup operator()() and obtain the line table entry.
94140
// 3) a callable object via operator()(). We will obtain the name of the
95141
// object from the first template parameter from __func's vtable. We will
96-
// look up the objectc operator()() and obtain the line table entry.
142+
// look up the objects operator()() and obtain the line table entry.
97143
// 4) a member function. A pointer to the function will stored after the
98144
// we will obtain the name from this pointer.
99145
// 5) a free function. A pointer to the function will stored after the vtable
@@ -113,6 +159,9 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
113159

114160
optional_info.member__f_pointer_value = member__f_pointer_value;
115161

162+
if (!member__f_pointer_value)
163+
return optional_info;
164+
116165
ExecutionContext exe_ctx(valobj_sp->GetExecutionContextRef());
117166
Process *process = exe_ctx.GetProcessPtr();
118167

@@ -130,8 +179,14 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
130179
if (status.Fail())
131180
return optional_info;
132181

182+
lldb::addr_t vtable_address_first_entry =
183+
process->ReadPointerFromMemory(vtable_address + address_size, status);
184+
185+
if (status.Fail())
186+
return optional_info;
187+
133188
lldb::addr_t address_after_vtable = member__f_pointer_value + address_size;
134-
// As commened above we may not have a function pointer but if we do we will
189+
// As commented above we may not have a function pointer but if we do we will
135190
// need it.
136191
lldb::addr_t possible_function_address =
137192
process->ReadPointerFromMemory(address_after_vtable, status);
@@ -144,9 +199,15 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
144199
if (target.GetSectionLoadList().IsEmpty())
145200
return optional_info;
146201

202+
Address vtable_first_entry_resolved;
203+
204+
if (!target.GetSectionLoadList().ResolveLoadAddress(
205+
vtable_address_first_entry, vtable_first_entry_resolved))
206+
return optional_info;
207+
147208
Address vtable_addr_resolved;
148209
SymbolContext sc;
149-
Symbol *symbol;
210+
Symbol *symbol = nullptr;
150211

151212
if (!target.GetSectionLoadList().ResolveLoadAddress(vtable_address,
152213
vtable_addr_resolved))
@@ -159,7 +220,7 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
159220
if (symbol == nullptr)
160221
return optional_info;
161222

162-
llvm::StringRef vtable_name(symbol->GetName().GetCString());
223+
llvm::StringRef vtable_name(symbol->GetName().GetStringRef());
163224
bool found_expected_start_string =
164225
vtable_name.startswith("vtable for std::__1::__function::__func<");
165226

@@ -172,6 +233,11 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
172233
// ... __func<main::$_0, std::__1::allocator<main::$_0> ...
173234
// ^^^^^^^^^
174235
//
236+
// We could see names such as:
237+
// main::$_0
238+
// Bar::add_num2(int)::'lambda'(int)
239+
// Bar
240+
//
175241
// We do this by find the first < and , and extracting in between.
176242
//
177243
// This covers the case of the lambda known at compile time.
@@ -192,17 +258,29 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
192258
function_address_resolved, eSymbolContextEverything, sc);
193259
symbol = sc.symbol;
194260
}
195-
196-
auto contains_lambda_identifier = []( llvm::StringRef & str_ref ) {
197-
return str_ref.contains("$_") || str_ref.contains("'lambda'");
198-
};
261+
262+
// These conditions are used several times to simplify statements later on.
263+
bool has___invoke =
264+
(symbol ? symbol->GetName().GetStringRef().contains("__invoke") : false);
265+
auto calculate_symbol_context_helper = [](auto &t,
266+
SymbolContextList &sc_list) {
267+
SymbolContext sc;
268+
t->CalculateSymbolContext(&sc);
269+
sc_list.Append(sc);
270+
};
271+
272+
// Case 2
273+
if (has___invoke) {
274+
SymbolContextList scl;
275+
calculate_symbol_context_helper(symbol, scl);
276+
277+
return line_entry_helper(target, scl[0], symbol, first_template_parameter,
278+
has___invoke);
279+
}
199280

200281
// Case 4 or 5
201-
// We eliminate these cases early because they don't need the potentially
202-
// expensive lookup through the symbol table.
203282
if (symbol && !symbol->GetName().GetStringRef().startswith("vtable for") &&
204-
!contains_lambda_identifier(first_template_parameter) &&
205-
!symbol->GetName().GetStringRef().contains("__invoke")) {
283+
!contains_lambda_identifier(first_template_parameter) && !has___invoke) {
206284
optional_info.callable_case =
207285
LibCppStdFunctionCallableCase::FreeOrMemberFunction;
208286
optional_info.callable_address = function_address_resolved;
@@ -211,83 +289,48 @@ CPPLanguageRuntime::FindLibCppStdFunctionCallableInfo(
211289
return optional_info;
212290
}
213291

214-
auto get_name = [&first_template_parameter, &symbol, contains_lambda_identifier]() {
215-
// Given case 1:
216-
//
217-
// main::$_0
218-
// Bar::add_num2(int)::'lambda'(int)
219-
//
220-
// we want to append ::operator()()
221-
if (contains_lambda_identifier(first_template_parameter))
222-
return llvm::Regex::escape(first_template_parameter.str()) +
223-
R"(::operator\(\)\(.*\))";
224-
225-
if (symbol != nullptr &&
226-
symbol->GetName().GetStringRef().contains("__invoke")) {
227-
228-
llvm::StringRef symbol_name = symbol->GetName().GetStringRef();
229-
size_t pos2 = symbol_name.find_last_of(':');
230-
231-
// Given case 2:
232-
//
233-
// main::$_1::__invoke(...)
234-
//
235-
// We want to slice off __invoke(...) and append operator()()
236-
std::string lambda_operator =
237-
llvm::Regex::escape(symbol_name.slice(0, pos2 + 1).str()) +
238-
R"(operator\(\)\(.*\))";
239-
240-
return lambda_operator;
241-
}
242-
243-
// Case 3
244-
return first_template_parameter.str() + R"(::operator\(\)\(.*\))";
245-
;
246-
};
247-
248-
std::string func_to_match = get_name();
292+
std::string func_to_match = first_template_parameter.str();
249293

250294
auto it = CallableLookupCache.find(func_to_match);
251295
if (it != CallableLookupCache.end())
252296
return it->second;
253297

254298
SymbolContextList scl;
255299

256-
target.GetImages().FindSymbolsMatchingRegExAndType(
257-
RegularExpression{R"(^)" + func_to_match}, eSymbolTypeAny, scl);
300+
CompileUnit *vtable_cu =
301+
vtable_first_entry_resolved.CalculateSymbolContextCompileUnit();
302+
llvm::StringRef name_to_use = func_to_match;
258303

259-
// Case 1,2 or 3
260-
if (scl.GetSize() >= 1) {
261-
SymbolContext sc2 = scl[0];
262-
263-
AddressRange range;
264-
sc2.GetAddressRange(eSymbolContextEverything, 0, false, range);
265-
266-
Address address = range.GetBaseAddress();
267-
268-
Address addr;
269-
if (target.ResolveLoadAddress(address.GetCallableLoadAddress(&target),
270-
addr)) {
271-
LineEntry line_entry;
272-
addr.CalculateSymbolContextLineEntry(line_entry);
273-
274-
if (contains_lambda_identifier(first_template_parameter) ||
275-
(symbol != nullptr &&
276-
symbol->GetName().GetStringRef().contains("__invoke"))) {
277-
// Case 1 and 2
278-
optional_info.callable_case = LibCppStdFunctionCallableCase::Lambda;
279-
} else {
280-
// Case 3
281-
optional_info.callable_case =
282-
LibCppStdFunctionCallableCase::CallableObject;
283-
}
284-
285-
optional_info.callable_symbol = *symbol;
286-
optional_info.callable_line_entry = line_entry;
287-
optional_info.callable_address = addr;
304+
// Case 3, we have a callable object instead of a lambda
305+
//
306+
// TODO
307+
// We currently don't support this case a callable object may have multiple
308+
// operator()() varying on const/non-const and number of arguments and we
309+
// don't have a way to currently distinguish them so we will bail out now.
310+
if (!contains_lambda_identifier(name_to_use))
311+
return optional_info;
312+
313+
if (vtable_cu && !has___invoke) {
314+
lldb::FunctionSP func_sp =
315+
vtable_cu->FindFunction([name_to_use](const FunctionSP &f) {
316+
auto name = f->GetName().GetStringRef();
317+
if (name.startswith(name_to_use) && name.contains("operator"))
318+
return true;
319+
320+
return false;
321+
});
322+
323+
if (func_sp) {
324+
calculate_symbol_context_helper(func_sp, scl);
288325
}
289326
}
290327

328+
// Case 1 or 3
329+
if (scl.GetSize() >= 1) {
330+
optional_info = line_entry_helper(target, scl[0], symbol,
331+
first_template_parameter, has___invoke);
332+
}
333+
291334
CallableLookupCache[func_to_match] = optional_info;
292335

293336
return optional_info;

0 commit comments

Comments
 (0)