diff --git a/llvm/utils/lldbDataFormatters.py b/llvm/utils/lldbDataFormatters.py --- a/llvm/utils/lldbDataFormatters.py +++ b/llvm/utils/lldbDataFormatters.py @@ -4,6 +4,7 @@ Load into LLDB with 'command script import /path/to/lldbDataFormatters.py' """ +import collections import lldb import json @@ -80,6 +81,17 @@ # '-x "^llvm::PointerUnion<.+>$"' # ) + debugger.HandleCommand( + "type summary add -w llvm " + "-e -F lldbDataFormatters.DenseMapSummary " + '-x "^llvm::DenseMap<.+>$"' + ) + debugger.HandleCommand( + "type synthetic add -w llvm " + "-l lldbDataFormatters.DenseMapSynthetic " + '-x "^llvm::DenseMap<.+>$"' + ) + # Pretty printer for llvm::SmallVector/llvm::SmallVectorImpl class SmallVectorSynthProvider: @@ -341,3 +353,76 @@ "", f"(int){self.val_expr_path}.getInt()" ).GetValueAsSigned() self.template_args = parse_template_parameters(self.valobj.GetType().name) + + +def DenseMapSummary(valobj: lldb.SBValue, _) -> str: + raw_value = valobj.GetNonSyntheticValue() + num_entries = raw_value.GetChildMemberWithName("NumEntries").unsigned + num_tombstones = raw_value.GetChildMemberWithName("NumTombstones").unsigned + + summary = f"size={num_entries}" + if num_tombstones == 1: + # The heuristic to identify valid entries does not handle the case of a + # single tombstone. The summary calls attention to this. + summary = f"tombstones=1, {summary}" + return summary + + +class DenseMapSynthetic: + valobj: lldb.SBValue + + # The indexes into `Buckets` that contain valid map entries. + child_buckets: list[int] + + def __init__(self, valobj: lldb.SBValue, _) -> None: + self.valobj = valobj + + def num_children(self) -> int: + return len(self.child_buckets) + + def get_child_at_index(self, child_index: int) -> lldb.SBValue: + bucket_index = self.child_buckets[child_index] + entry = self.valobj.GetValueForExpressionPath(f".Buckets[{bucket_index}]") + + # By default, DenseMap instances use DenseMapPair to hold key-value + # entries. When the entry is a DenseMapPair, unwrap it to expose the + # children as simple std::pair values. + # + # This entry type is customizable (a template parameter). For other + # types, expose the entry type as is. + if entry.type.name.startswith("llvm::detail::DenseMapPair<"): + entry = entry.GetChildAtIndex(0) + + return entry.Clone(f"[{child_index}]") + + def update(self): + self.child_buckets = [] + + num_entries = self.valobj.GetChildMemberWithName("NumEntries").unsigned + if num_entries == 0: + return + + buckets = self.valobj.GetChildMemberWithName("Buckets") + num_buckets = self.valobj.GetChildMemberWithName("NumBuckets").unsigned + + # Bucket entries contain one of the following: + # 1. Valid key-value + # 2. Empty key + # 3. Tombstone key (a deleted entry) + # + # NumBuckets is always greater than NumEntries. The empty key, and + # potentially the tombstone key, will occur multiple times. A key that + # is repeated is either the empty key or the tombstone key. + + # For each key, collect a list of buckets it appears in. + key_buckets: dict[str, list[int]] = collections.defaultdict(list) + for index in range(num_buckets): + key = buckets.GetValueForExpressionPath(f"[{index}].first") + key_buckets[str(key.data)].append(index) + + # Heuristic: This is not a multi-map, any repeated (non-unique) keys are + # either the the empty key or the tombstone key. Populate child_buckets + # with the indexes of entries containing unique keys. + for indexes in key_buckets.values(): + if len(indexes) == 1: + self.child_buckets.append(indexes[0])