Author: Abdullah Mohammad Amin Date: 2025-08-30T13:17:05-07:00 New Revision: d39772cb053c194ef809f3ca765f37237c4e1956
URL: https://github.com/llvm/llvm-project/commit/d39772cb053c194ef809f3ca765f37237c4e1956 DIFF: https://github.com/llvm/llvm-project/commit/d39772cb053c194ef809f3ca765f37237c4e1956.diff LOG: [lldb] Refactor variable annotation logic in Disassembler::PrintInstructions (#156118) This patch is a follow-up to [#152887](https://github.com/llvm/llvm-project/pull/152887), addressing review comments that came in after the original change was merged. - Move `VarState` definition out of `PrintInstructions` into a private helper, with member comments placed before fields. - Introduce a `VariableAnnotator` helper class to encapsulate state and logic for live variable tracking across instructions. - Replace `seen_this_inst` flag with a map-diff approach: recompute the current variable set per instruction and diff against the previous set. - Use `nullptr` instead of an empty `ProcessSP` when calling `ABI::FindPlugin`. - Narrow `Block*` scope with `if (Block *B = ...)`. - Set `DIDumpOptions::PrintRegisterOnly` directly from `static_cast<bool>(abi_sp)`. - Prefer `emplace_back` over `push_back` for event strings. - General cleanup to match LLVM coding style and reviewer feedback. This makes the annotation code easier to read and consistent with LLVM/LLDB conventions while preserving functionality. Added: Modified: lldb/include/lldb/Core/Disassembler.h lldb/source/Core/Disassembler.cpp Removed: ################################################################################ diff --git a/lldb/include/lldb/Core/Disassembler.h b/lldb/include/lldb/Core/Disassembler.h index 6a21470b2472c..db186dd33d774 100644 --- a/lldb/include/lldb/Core/Disassembler.h +++ b/lldb/include/lldb/Core/Disassembler.h @@ -566,6 +566,26 @@ class Disassembler : public std::enable_shared_from_this<Disassembler>, const Disassembler &operator=(const Disassembler &) = delete; }; +/// Tracks live variable annotations across instructions and produces +/// per-instruction "events" like `name = RDI` or `name = <undef>`. +class VariableAnnotator { + struct VarState { + /// Display name. + std::string name; + /// Last printed location (empty means <undef>). + std::string last_loc; + }; + + // Live state from the previous instruction, keyed by Variable::GetID(). + llvm::DenseMap<lldb::user_id_t, VarState> Live_; + +public: + /// Compute annotation strings for a single instruction and update `Live_`. + /// Returns only the events that should be printed *at this instruction*. + std::vector<std::string> annotate(Instruction &inst, Target &target, + const lldb::ModuleSP &module_sp); +}; + } // namespace lldb_private #endif // LLDB_CORE_DISASSEMBLER_H diff --git a/lldb/source/Core/Disassembler.cpp b/lldb/source/Core/Disassembler.cpp index 9b0b604d6caa0..f2ed1f7395346 100644 --- a/lldb/source/Core/Disassembler.cpp +++ b/lldb/source/Core/Disassembler.cpp @@ -286,6 +286,127 @@ bool Disassembler::ElideMixedSourceAndDisassemblyLine( return false; } +// For each instruction, this block attempts to resolve in-scope variables +// and determine if the current PC falls within their +// DWARF location entry. If so, it prints a simplified annotation using the +// variable name and its resolved location (e.g., "var = reg; " ). +// +// Annotations are only included if the variable has a valid DWARF location +// entry, and the location string is non-empty after filtering. Decoding +// errors and DWARF opcodes are intentionally omitted to keep the output +// concise and user-friendly. +// +// The goal is to give users helpful live variable hints alongside the +// disassembled instruction stream, similar to how debug information +// enhances source-level debugging. +std::vector<std::string> +VariableAnnotator::annotate(Instruction &inst, Target &target, + const lldb::ModuleSP &module_sp) { + std::vector<std::string> events; + + // If we lost module context, everything becomes <undef>. + if (!module_sp) { + for (const auto &KV : Live_) + events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str()); + Live_.clear(); + return events; + } + + // Resolve function/block at this *file* address. + SymbolContext sc; + const Address &iaddr = inst.GetAddress(); + const auto mask = eSymbolContextFunction | eSymbolContextBlock; + if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) || + !sc.function) { + // No function context: everything dies here. + for (const auto &KV : Live_) + events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str()); + Live_.clear(); + return events; + } + + // Collect in-scope variables for this instruction into Current. + VariableList var_list; + // Innermost block containing iaddr. + if (Block *B = sc.block) { + auto filter = [](Variable *v) -> bool { return v && !v->IsArtificial(); }; + B->AppendVariables(/*can_create*/ true, + /*get_parent_variables*/ true, + /*stop_if_block_is_inlined_function*/ false, + /*filter*/ filter, + /*variable_list*/ &var_list); + } + + const lldb::addr_t pc_file = iaddr.GetFileAddress(); + const lldb::addr_t func_file = sc.function->GetAddress().GetFileAddress(); + + // ABI from Target (pretty reg names if plugin exists). Safe to be null. + lldb::ABISP abi_sp = ABI::FindPlugin(nullptr, target.GetArchitecture()); + ABI *abi = abi_sp.get(); + + llvm::DIDumpOptions opts; + opts.ShowAddresses = false; + // Prefer "register-only" output when we have an ABI. + opts.PrintRegisterOnly = static_cast<bool>(abi_sp); + + llvm::DenseMap<lldb::user_id_t, VarState> Current; + + for (size_t i = 0, e = var_list.GetSize(); i != e; ++i) { + lldb::VariableSP v = var_list.GetVariableAtIndex(i); + if (!v || v->IsArtificial()) + continue; + + const char *nm = v->GetName().AsCString(); + llvm::StringRef name = nm ? nm : "<anon>"; + + DWARFExpressionList &exprs = v->LocationExpressionList(); + if (!exprs.IsValid()) + continue; + + auto entry_or_err = exprs.GetExpressionEntryAtAddress(func_file, pc_file); + if (!entry_or_err) + continue; + + auto entry = *entry_or_err; + + StreamString loc_ss; + entry.expr->DumpLocation(&loc_ss, eDescriptionLevelBrief, abi, opts); + + llvm::StringRef loc = llvm::StringRef(loc_ss.GetString()).trim(); + if (loc.empty()) + continue; + + Current.try_emplace(v->GetID(), + VarState{std::string(name), std::string(loc)}); + } + + // Diff Live_ → Current. + + // 1) Starts/changes: iterate Current and compare with Live_. + for (const auto &KV : Current) { + auto it = Live_.find(KV.first); + if (it == Live_.end()) { + // Newly live. + events.emplace_back( + llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str()); + } else if (it->second.last_loc != KV.second.last_loc) { + // Location changed. + events.emplace_back( + llvm::formatv("{0} = {1}", KV.second.name, KV.second.last_loc).str()); + } + } + + // 2) Ends: anything that was live but is not in Current becomes <undef>. + for (const auto &KV : Live_) { + if (!Current.count(KV.first)) + events.emplace_back(llvm::formatv("{0} = <undef>", KV.second.name).str()); + } + + // Commit new state. + Live_ = std::move(Current); + return events; +} + void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, const ExecutionContext &exe_ctx, bool mixed_source_and_assembly, @@ -382,147 +503,7 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, } } - // Add variable location annotations to the disassembly output. - // - // For each instruction, this block attempts to resolve in-scope variables - // and determine if the current PC falls within their - // DWARF location entry. If so, it prints a simplified annotation using the - // variable name and its resolved location (e.g., "var = reg; " ). - // - // Annotations are only included if the variable has a valid DWARF location - // entry, and the location string is non-empty after filtering. Decoding - // errors and DWARF opcodes are intentionally omitted to keep the output - // concise and user-friendly. - // - // The goal is to give users helpful live variable hints alongside the - // disassembled instruction stream, similar to how debug information - // enhances source-level debugging. - - struct VarState { - std::string name; ///< Display name. - std::string last_loc; ///< Last printed location (empty means <undef>). - bool seen_this_inst = false; - }; - - // Track live variables across instructions. - llvm::DenseMap<lldb::user_id_t, VarState> live_vars; - - // Stateful annotator: updates live_vars and returns only what should be - // printed for THIS instruction. - auto annotate_static = [&](Instruction &inst, Target &target, - ModuleSP module_sp) -> std::vector<std::string> { - std::vector<std::string> events; - - // Reset per-instruction seen flags. - for (auto &kv : live_vars) - kv.second.seen_this_inst = false; - - const Address &iaddr = inst.GetAddress(); - if (!module_sp) { - // Everything previously live becomes <undef>. - for (auto I = live_vars.begin(), E = live_vars.end(); I != E;) { - auto Cur = I++; - events.push_back( - llvm::formatv("{0} = <undef>", Cur->second.name).str()); - live_vars.erase(Cur); - } - return events; - } - - // Resolve innermost block at this *file* address. - SymbolContext sc; - const lldb::SymbolContextItem mask = - eSymbolContextFunction | eSymbolContextBlock; - if (!module_sp->ResolveSymbolContextForAddress(iaddr, mask, sc) || - !sc.function) { - // No function context: everything dies here. - for (auto I = live_vars.begin(), E = live_vars.end(); I != E;) { - auto Cur = I++; - events.push_back( - llvm::formatv("{0} = <undef>", Cur->second.name).str()); - live_vars.erase(Cur); - } - return events; - } - - Block *B = sc.block; ///< Innermost block containing iaddr. - VariableList var_list; - if (B) { - auto filter = [](Variable *v) -> bool { return v && !v->IsArtificial(); }; - - B->AppendVariables(/*can_create*/ true, - /*get_parent_variables*/ true, - /*stop_if_block_is_inlined_function*/ false, - /*filter*/ filter, - /*variable_list*/ &var_list); - } - - const lldb::addr_t pc_file = iaddr.GetFileAddress(); - const lldb::addr_t func_file = sc.function->GetAddress().GetFileAddress(); - - // ABI from Target (pretty reg names if plugin exists). Safe to be null. - lldb::ProcessSP no_process; - lldb::ABISP abi_sp = ABI::FindPlugin(no_process, target.GetArchitecture()); - ABI *abi = abi_sp.get(); - - llvm::DIDumpOptions opts; - opts.ShowAddresses = false; - if (abi) - opts.PrintRegisterOnly = true; - - for (size_t i = 0, e = var_list.GetSize(); i != e; ++i) { - lldb::VariableSP v = var_list.GetVariableAtIndex(i); - if (!v || v->IsArtificial()) - continue; - - const char *nm = v->GetName().AsCString(); - llvm::StringRef name = nm ? nm : "<anon>"; - - lldb_private::DWARFExpressionList &exprs = v->LocationExpressionList(); - if (!exprs.IsValid()) - continue; - - auto entry_or_err = exprs.GetExpressionEntryAtAddress(func_file, pc_file); - if (!entry_or_err) - continue; - - auto entry = *entry_or_err; - - StreamString loc_ss; - entry.expr->DumpLocation(&loc_ss, eDescriptionLevelBrief, abi, opts); - llvm::StringRef loc = llvm::StringRef(loc_ss.GetString()).trim(); - if (loc.empty()) - continue; - - auto ins = live_vars.insert( - {v->GetID(), VarState{name.str(), loc.str(), /*seen*/ true}}); - if (ins.second) { - // Newly live. - events.push_back(llvm::formatv("{0} = {1}", name, loc).str()); - } else { - VarState &vs = ins.first->second; - vs.seen_this_inst = true; - if (vs.last_loc != loc) { - vs.last_loc = loc.str(); - events.push_back(llvm::formatv("{0} = {1}", vs.name, loc).str()); - } - } - } - - // Anything previously live that we didn't see a location for at this inst - // is now <undef>. - for (auto I = live_vars.begin(), E = live_vars.end(); I != E;) { - auto Cur = I++; - if (!Cur->second.seen_this_inst) { - events.push_back( - llvm::formatv("{0} = <undef>", Cur->second.name).str()); - live_vars.erase(Cur); - } - } - - return events; - }; - + VariableAnnotator annot; previous_symbol = nullptr; SourceLine previous_line; for (size_t i = 0; i < num_instructions_found; ++i) { @@ -695,7 +676,7 @@ void Disassembler::PrintInstructions(Debugger &debugger, const ArchSpec &arch, address_text_size); if ((options & eOptionVariableAnnotations) && target_sp) { - auto annotations = annotate_static(*inst, *target_sp, module_sp); + auto annotations = annot.annotate(*inst, *target_sp, module_sp); if (!annotations.empty()) { const size_t annotation_column = 100; inst_line.FillLastLineToColumn(annotation_column, ' '); _______________________________________________ lldb-commits mailing list lldb-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits