Skip to content

Commit 59b8e78

Browse files
committed
[lldb] Allow LLDB to automatically retry a failed expression with an imported std C++ module
By now LLDB can import the 'std' C++ module to improve expression evaluation, but there are still a few problems to solve before we can do this by default. One is that importing the C++ module is slightly slower than normal expression evaluation (mostly because the disk access and loading the initial lookup data is quite slow in comparison to the barebone Clang setup the rest of the LLDB expression evaluator is usually doing). Another problem is that some complicated types in the standard library aren't fully supported yet by the ASTImporter, so we end up types that fail to import (which usually appears to the user as if the type is empty or there is just no result variable). To still allow people to adopt this mode in their daily debugging, this patch adds a setting that allows LLDB to automatically retry failed expression with a loaded C++ module. All success expressions will behave exactly as they would do before this patch. Failed expressions get a another parse attempt if we find a usable C++ module in the current execution context. This way we shouldn't have any performance/parsing regressions in normal debugging workflows, while the debugging workflows involving STL containers benefit from the C++ module type info. This setting is off by default for now with the intention to enable it by default on macOS soon-ish. The implementation is mostly just extracting the existing parse logic into its own function and then calling the parse function again if the first evaluation failed and we have a C++ module to retry the parsing with. Reviewed By: shafik, JDevlieghere, aprantl Differential Revision: https://reviews.llvm.org/D92784 (cherry picked from commit 9586082)
1 parent 2bfe352 commit 59b8e78

File tree

8 files changed

+235
-70
lines changed

8 files changed

+235
-70
lines changed

lldb/include/lldb/Target/Target.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,12 @@ enum LoadDependentFiles {
6565
eLoadDependentsNo,
6666
};
6767

68+
enum ImportStdModule {
69+
eImportStdModuleFalse,
70+
eImportStdModuleFallback,
71+
eImportStdModuleTrue,
72+
};
73+
6874
class TargetExperimentalProperties : public Properties {
6975
public:
7076
TargetExperimentalProperties();
@@ -135,7 +141,7 @@ class TargetProperties : public Properties {
135141

136142
bool GetEnableAutoImportClangModules() const;
137143

138-
bool GetEnableImportStdModule() const;
144+
ImportStdModule GetImportStdModule() const;
139145

140146
bool GetEnableAutoApplyFixIts() const;
141147

lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.cpp

Lines changed: 95 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,6 @@ void ClangUserExpression::CreateSourceCode(
417417
DiagnosticManager &diagnostic_manager, ExecutionContext &exe_ctx,
418418
std::vector<std::string> modules_to_import, bool for_completion) {
419419

420-
m_filename = m_clang_state->GetNextExprFileName();
421420
std::string prefix = m_expr_prefix;
422421

423422
if (m_options.GetExecutionPolicy() == eExecutionPolicyTopLevel) {
@@ -477,9 +476,6 @@ CppModuleConfiguration GetModuleConfig(lldb::LanguageType language,
477476
if (!target)
478477
return LogConfigError("No target");
479478

480-
if (!target->GetEnableImportStdModule())
481-
return LogConfigError("Importing std module not enabled in settings");
482-
483479
StackFrame *frame = exe_ctx.GetFramePtr();
484480
if (!frame)
485481
return LogConfigError("No frame");
@@ -529,8 +525,6 @@ CppModuleConfiguration GetModuleConfig(lldb::LanguageType language,
529525
bool ClangUserExpression::PrepareForParsing(
530526
DiagnosticManager &diagnostic_manager, ExecutionContext &exe_ctx,
531527
bool for_completion) {
532-
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));
533-
534528
InstallContext(exe_ctx);
535529

536530
if (!SetupPersistentState(diagnostic_manager, exe_ctx))
@@ -551,50 +545,20 @@ bool ClangUserExpression::PrepareForParsing(
551545

552546
SetupDeclVendor(exe_ctx, m_target, diagnostic_manager);
553547

554-
CppModuleConfiguration module_config = GetModuleConfig(m_language, exe_ctx);
555-
llvm::ArrayRef<std::string> imported_modules =
556-
module_config.GetImportedModules();
557-
m_imported_cpp_modules = !imported_modules.empty();
558-
m_include_directories = module_config.GetIncludeDirs();
548+
m_filename = m_clang_state->GetNextExprFileName();
559549

560-
LLDB_LOG(log, "List of imported modules in expression: {0}",
561-
llvm::make_range(imported_modules.begin(), imported_modules.end()));
562-
LLDB_LOG(log, "List of include directories gathered for modules: {0}",
563-
llvm::make_range(m_include_directories.begin(),
564-
m_include_directories.end()));
550+
if (m_target->GetImportStdModule() == eImportStdModuleTrue)
551+
SetupCppModuleImports(exe_ctx);
565552

566-
CreateSourceCode(diagnostic_manager, exe_ctx, imported_modules,
553+
CreateSourceCode(diagnostic_manager, exe_ctx, m_imported_cpp_modules,
567554
for_completion);
568555
return true;
569556
}
570557

571-
bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager,
572-
ExecutionContext &exe_ctx,
573-
lldb_private::ExecutionPolicy execution_policy,
574-
bool keep_result_in_memory,
575-
bool generate_debug_info) {
576-
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));
577-
578-
if (!PrepareForParsing(diagnostic_manager, exe_ctx, /*for_completion*/ false))
579-
return false;
580-
581-
LLDB_LOGF(log, "Parsing the following code:\n%s", m_transformed_text.c_str());
582-
583-
////////////////////////////////////
584-
// Set up the target and compiler
585-
//
586-
587-
Target *target = exe_ctx.GetTargetPtr();
588-
589-
if (!target) {
590-
diagnostic_manager.PutString(eDiagnosticSeverityError, "invalid target");
591-
return false;
592-
}
593-
594-
//////////////////////////
595-
// Parse the expression
596-
//
597-
558+
bool ClangUserExpression::TryParse(
559+
DiagnosticManager &diagnostic_manager, ExecutionContextScope *exe_scope,
560+
ExecutionContext &exe_ctx, lldb_private::ExecutionPolicy execution_policy,
561+
bool keep_result_in_memory, bool generate_debug_info) {
598562
m_materializer_up = std::make_unique<Materializer>();
599563

600564
ResetDeclMap(exe_ctx, m_result_delegate, keep_result_in_memory);
@@ -612,26 +576,16 @@ bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager,
612576
DeclMap()->SetLookupsEnabled(true);
613577
}
614578

615-
Process *process = exe_ctx.GetProcessPtr();
616-
ExecutionContextScope *exe_scope = process;
617-
618-
if (!exe_scope)
619-
exe_scope = exe_ctx.GetTargetPtr();
620-
621-
// We use a shared pointer here so we can use the original parser - if it
622-
// succeeds or the rewrite parser we might make if it fails. But the
623-
// parser_sp will never be empty.
624-
625-
ClangExpressionParser parser(exe_scope, *this, generate_debug_info,
626-
m_include_directories, m_filename);
579+
m_parser = std::make_unique<ClangExpressionParser>(
580+
exe_scope, *this, generate_debug_info, m_include_directories, m_filename);
627581

628-
unsigned num_errors = parser.Parse(diagnostic_manager);
582+
unsigned num_errors = m_parser->Parse(diagnostic_manager);
629583

630584
// Check here for FixItHints. If there are any try to apply the fixits and
631585
// set the fixed text in m_fixed_text before returning an error.
632586
if (num_errors) {
633587
if (diagnostic_manager.HasFixIts()) {
634-
if (parser.RewriteExpression(diagnostic_manager)) {
588+
if (m_parser->RewriteExpression(diagnostic_manager)) {
635589
size_t fixed_start;
636590
size_t fixed_end;
637591
m_fixed_text = diagnostic_manager.GetFixedExpression();
@@ -652,7 +606,7 @@ bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager,
652606
//
653607

654608
{
655-
Status jit_error = parser.PrepareForExecution(
609+
Status jit_error = m_parser->PrepareForExecution(
656610
m_jit_start_addr, m_jit_end_addr, m_execution_unit_sp, exe_ctx,
657611
m_can_interpret, execution_policy);
658612

@@ -666,10 +620,91 @@ bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager,
666620
return false;
667621
}
668622
}
623+
return true;
624+
}
625+
626+
void ClangUserExpression::SetupCppModuleImports(ExecutionContext &exe_ctx) {
627+
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));
628+
629+
CppModuleConfiguration module_config = GetModuleConfig(m_language, exe_ctx);
630+
m_imported_cpp_modules = module_config.GetImportedModules();
631+
m_include_directories = module_config.GetIncludeDirs();
632+
633+
LLDB_LOG(log, "List of imported modules in expression: {0}",
634+
llvm::make_range(m_imported_cpp_modules.begin(),
635+
m_imported_cpp_modules.end()));
636+
LLDB_LOG(log, "List of include directories gathered for modules: {0}",
637+
llvm::make_range(m_include_directories.begin(),
638+
m_include_directories.end()));
639+
}
640+
641+
static bool shouldRetryWithCppModule(Target &target, ExecutionPolicy exe_policy) {
642+
// Top-level expression don't yet support importing C++ modules.
643+
if (exe_policy == ExecutionPolicy::eExecutionPolicyTopLevel)
644+
return false;
645+
return target.GetImportStdModule() == eImportStdModuleFallback;
646+
}
647+
648+
bool ClangUserExpression::Parse(DiagnosticManager &diagnostic_manager,
649+
ExecutionContext &exe_ctx,
650+
lldb_private::ExecutionPolicy execution_policy,
651+
bool keep_result_in_memory,
652+
bool generate_debug_info) {
653+
Log *log(lldb_private::GetLogIfAllCategoriesSet(LIBLLDB_LOG_EXPRESSIONS));
654+
655+
if (!PrepareForParsing(diagnostic_manager, exe_ctx, /*for_completion*/ false))
656+
return false;
657+
658+
LLDB_LOGF(log, "Parsing the following code:\n%s", m_transformed_text.c_str());
659+
660+
////////////////////////////////////
661+
// Set up the target and compiler
662+
//
663+
664+
Target *target = exe_ctx.GetTargetPtr();
665+
666+
if (!target) {
667+
diagnostic_manager.PutString(eDiagnosticSeverityError, "invalid target");
668+
return false;
669+
}
670+
671+
//////////////////////////
672+
// Parse the expression
673+
//
674+
675+
Process *process = exe_ctx.GetProcessPtr();
676+
ExecutionContextScope *exe_scope = process;
677+
678+
if (!exe_scope)
679+
exe_scope = exe_ctx.GetTargetPtr();
680+
681+
bool parse_success = TryParse(diagnostic_manager, exe_scope, exe_ctx,
682+
execution_policy, keep_result_in_memory,
683+
generate_debug_info);
684+
// If the expression failed to parse, check if retrying parsing with a loaded
685+
// C++ module is possible.
686+
if (!parse_success && shouldRetryWithCppModule(*target, execution_policy)) {
687+
// Load the loaded C++ modules.
688+
SetupCppModuleImports(exe_ctx);
689+
// If we did load any modules, then retry parsing.
690+
if (!m_imported_cpp_modules.empty()) {
691+
// The module imports are injected into the source code wrapper,
692+
// so recreate those.
693+
CreateSourceCode(diagnostic_manager, exe_ctx, m_imported_cpp_modules,
694+
/*for_completion*/ false);
695+
// Clear the error diagnostics from the previous parse attempt.
696+
diagnostic_manager.Clear();
697+
parse_success = TryParse(diagnostic_manager, exe_scope, exe_ctx,
698+
execution_policy, keep_result_in_memory,
699+
generate_debug_info);
700+
}
701+
}
702+
if (!parse_success)
703+
return false;
669704

670705
if (exe_ctx.GetProcessPtr() && execution_policy == eExecutionPolicyTopLevel) {
671706
Status static_init_error =
672-
parser.RunStaticInitializers(m_execution_unit_sp, exe_ctx);
707+
m_parser->RunStaticInitializers(m_execution_unit_sp, exe_ctx);
673708

674709
if (!static_init_error.Success()) {
675710
const char *error_cstr = static_init_error.AsCString();

lldb/source/Plugins/ExpressionParser/Clang/ClangUserExpression.h

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,12 +168,23 @@ class ClangUserExpression : public LLVMUserExpression {
168168
lldb::ExpressionVariableSP
169169
GetResultAfterDematerialization(ExecutionContextScope *exe_scope) override;
170170

171-
bool DidImportCxxModules() const { return m_imported_cpp_modules; }
171+
/// Returns true iff this expression is using any imported C++ modules.
172+
bool DidImportCxxModules() const { return !m_imported_cpp_modules.empty(); }
172173

173174
private:
174175
/// Populate m_in_cplusplus_method and m_in_objectivec_method based on the
175176
/// environment.
176177

178+
/// Contains the actual parsing implementation.
179+
/// The parameter have the same meaning as in ClangUserExpression::Parse.
180+
/// \see ClangUserExpression::Parse
181+
bool TryParse(DiagnosticManager &diagnostic_manager,
182+
ExecutionContextScope *exe_scope, ExecutionContext &exe_ctx,
183+
lldb_private::ExecutionPolicy execution_policy, bool keep_result_in_memory,
184+
bool generate_debug_info);
185+
186+
void SetupCppModuleImports(ExecutionContext &exe_ctx);
187+
177188
void ScanContext(ExecutionContext &exe_ctx,
178189
lldb_private::Status &err) override;
179190

@@ -219,15 +230,18 @@ class ClangUserExpression : public LLVMUserExpression {
219230
ResultDelegate m_result_delegate;
220231
ClangPersistentVariables *m_clang_state;
221232
std::unique_ptr<ClangExpressionSourceCode> m_source_code;
233+
/// The parser instance we used to parse the expression.
234+
std::unique_ptr<ClangExpressionParser> m_parser;
222235
/// File name used for the expression.
223236
std::string m_filename;
224237

225238
/// The object (if any) in which context the expression is evaluated.
226239
/// See the comment to `UserExpression::Evaluate` for details.
227240
ValueObject *m_ctx_obj;
228241

229-
/// True iff this expression explicitly imported C++ modules.
230-
bool m_imported_cpp_modules = false;
242+
/// A list of module names that should be imported when parsing.
243+
/// \see CppModuleConfiguration::GetImportedModules
244+
std::vector<std::string> m_imported_cpp_modules;
231245

232246
/// True if the expression parser should enforce the presence of a valid class
233247
/// pointer in order to generate the expression as a method.

lldb/source/Target/Target.cpp

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3461,6 +3461,28 @@ static constexpr OptionEnumValueElement g_x86_dis_flavor_value_types[] = {
34613461
},
34623462
};
34633463

3464+
static constexpr OptionEnumValueElement g_import_std_module_value_types[] = {
3465+
{
3466+
eImportStdModuleFalse,
3467+
"false",
3468+
"Never import the 'std' C++ module in the expression parser.",
3469+
},
3470+
{
3471+
eImportStdModuleFallback,
3472+
"fallback",
3473+
"Retry evaluating expressions with an imported 'std' C++ module if they"
3474+
" failed to parse without the module. This allows evaluating more "
3475+
"complex expressions involving C++ standard library types."
3476+
},
3477+
{
3478+
eImportStdModuleTrue,
3479+
"true",
3480+
"Always import the 'std' C++ module. This allows evaluating more "
3481+
"complex expressions involving C++ standard library types. This feature"
3482+
" is experimental."
3483+
},
3484+
};
3485+
34643486
static constexpr OptionEnumValueElement g_hex_immediate_style_values[] = {
34653487
{
34663488
Disassembler::eHexStyleC,
@@ -3914,10 +3936,10 @@ bool TargetProperties::GetEnableAutoImportClangModules() const {
39143936
nullptr, idx, g_target_properties[idx].default_uint_value != 0);
39153937
}
39163938

3917-
bool TargetProperties::GetEnableImportStdModule() const {
3939+
ImportStdModule TargetProperties::GetImportStdModule() const {
39183940
const uint32_t idx = ePropertyImportStdModule;
3919-
return m_collection_sp->GetPropertyAtIndexAsBoolean(
3920-
nullptr, idx, g_target_properties[idx].default_uint_value != 0);
3941+
return (ImportStdModule)m_collection_sp->GetPropertyAtIndexAsEnumeration(
3942+
nullptr, idx, g_target_properties[idx].default_uint_value);
39213943
}
39223944

39233945
bool TargetProperties::GetEnableAutoApplyFixIts() const {

lldb/source/Target/TargetProperties.td

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,11 @@ let Definition = "target" in {
4545
def AutoImportClangModules: Property<"auto-import-clang-modules", "Boolean">,
4646
DefaultTrue,
4747
Desc<"Automatically load Clang modules referred to by the program.">;
48-
def ImportStdModule: Property<"import-std-module", "Boolean">,
49-
DefaultFalse,
50-
Desc<"Import the C++ std module to improve debugging STL containers.">;
48+
def ImportStdModule: Property<"import-std-module", "Enum">,
49+
DefaultEnumValue<"eImportStdModuleFalse">,
50+
EnumValues<"OptionEnumValues(g_import_std_module_value_types)">,
51+
Desc<"Import the 'std' C++ module to improve expression parsing involving "
52+
" C++ standard library types.">;
5153
def AutoApplyFixIts: Property<"auto-apply-fixits", "Boolean">,
5254
DefaultTrue,
5355
Desc<"Automatically apply fix-it hints to expressions.">;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
USE_LIBCPP := 1
2+
CXX_SOURCES := main.cpp
3+
include Makefile.rules

0 commit comments

Comments
 (0)