[llvm] 78c9122 - [llvm][utils] Add DenseMap data formatters

Dave Lee via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 19 13:22:11 PDT 2023


Author: Dave Lee
Date: 2023-07-19T13:21:49-07:00
New Revision: 78c9122ac9ad5562d65852eb38322f131cffbfd6

URL: https://github.com/llvm/llvm-project/commit/78c9122ac9ad5562d65852eb38322f131cffbfd6
DIFF: https://github.com/llvm/llvm-project/commit/78c9122ac9ad5562d65852eb38322f131cffbfd6.diff

LOG: [llvm][utils] Add DenseMap data formatters

Add summary and synthetic data formatters for `llvm::DenseMap`.

This implementation avoids expression evaluation by using a heuristic. However, as
heuristics go, there is a corner case: A single deleted entry (a single "tombstone"),
will result in a child value with an invalid key but a valid value.

Instead of calling `getEmptyKey()` and `getTombstoneKey()` to determine which buckets
are empty, and which contain real key-values, the heuristic scans all buckets to
identify keys that exist only once. These singleton keys are considered valid.

The empty key will always exist multiple times. However the tombstone key may exist
zero, one, or many times. The heuristic has no problems when there are zero or many
tombstones, but when there is exactly one deleted entry (one tombstone), then the
heuristic will incorrectly identify it as valid.

Differential Revision: https://reviews.llvm.org/D137028

Added: 
    

Modified: 
    llvm/utils/lldbDataFormatters.py

Removed: 
    


################################################################################
diff  --git a/llvm/utils/lldbDataFormatters.py b/llvm/utils/lldbDataFormatters.py
index 29c17adf382e52..9ead7bfa4f7dd1 100644
--- 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 @@ def __lldb_init_module(debugger, internal_dict):
     #     '-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 @@ def update(self):
             "", 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])


        


More information about the llvm-commits mailing list