diff --git a/phmap_lldb.py b/phmap_lldb.py new file mode 100755 index 0000000..a06540c --- /dev/null +++ b/phmap_lldb.py @@ -0,0 +1,248 @@ +# Python lldb formatters for parallel-hashmap +# tested witch clang10 / lldb9 & 10 + +# to install it, type the following command or put it in $HOME/.lldbinit: +# command script import "PATH_TO_SCRIPT/lldb_phmap.py" + + +import lldb +import os +import sys +import re + +_MAX_CHILDREN = 250 +_MAX_CTRL_INDEX = 1_000 +_MODULE_NAME = os.path.basename(__file__).split(".")[0] + + +def _get_function_name(instance=None): + """Return the name of the calling function""" + class_name = f"{type(instance).__name__}." if instance else "" + return class_name + sys._getframe(1).f_code.co_name + + +class flat_map_slot_type: + CLASS_PATTERN = "^phmap::priv::raw_hash_set::slot_type$" + HAS_SUMMARY = True + IS_SYNTHETIC_PROVIDER = False + + @staticmethod + def summary(valobj, _): + try: + valobj = valobj.GetChildMemberWithName('value') + first = valobj.GetChildMemberWithName('first').GetSummary() + if not first: first = "{...}" + second = valobj.GetChildMemberWithName('second').GetSummary() + if not second: second = "{...}" + return f"{{{first}, {second}}}" + except BaseException as ex: + print(f"{_get_function_name()} -> {ex}") + return "" + + +class node_map_slot_type: + CLASS_PATTERN = r"phmap::priv::raw_hash_set::slot_type$" + HAS_SUMMARY = True + IS_SYNTHETIC_PROVIDER = False + + @staticmethod + def summary(valobj, _): + try: + valobj = valobj.Dereference() + first = valobj.GetChildMemberWithName('first').GetSummary() + if not first: first = "{...}" + second = valobj.GetChildMemberWithName('second').GetSummary() + if not second: second = "{...}" + return f"{{{first}, {second}}}" + except BaseException as ex: + print(f"{_get_function_name()} -> {ex}") + return "{?}" + + +class node_set_slot_type: + CLASS_PATTERN = r"phmap::priv::raw_hash_set::slot_type$" + HAS_SUMMARY = True + IS_SYNTHETIC_PROVIDER = False + + @staticmethod + def summary(valobj, _): + try: + summary = valobj.Dereference().GetSummary() + if not summary: summary = "{...}" + return summary + except BaseException as ex: + print(f"{_get_function_name()} -> {ex}") + return "{?}" + + +class flat_hash_map_or_set: + CLASS_PATTERN = "^phmap::flat_hash_(map|set)<.*>$" + HAS_SUMMARY = True + IS_SYNTHETIC_PROVIDER = True + + @staticmethod + def summary(valobj, _): + try: + valobj = valobj.GetNonSyntheticValue() + size = valobj.GetChildMemberWithName('size_').GetValueAsUnsigned() + capacity = valobj.GetChildMemberWithName('capacity_').GetValueAsUnsigned() + return f"size = {size} (capacity = {capacity})" + except BaseException as ex: + print(f"{_get_function_name()} -> {ex}") + return "{?}" + + def __init__(self, valobj, _): + self.valobj = valobj + self.slots_ = self.slot_type = self.ctrl_ = None + self.size_ = self.capacity_ = self.slot_size = 0 + + def num_children(self): + return min(self.size_, _MAX_CHILDREN) + + def has_children(self): + return True + + def update(self): + try: + self.size_ = self.valobj.GetChildMemberWithName('size_').GetValueAsUnsigned() + self.capacity_ = self.valobj.GetChildMemberWithName('capacity_').GetValueAsUnsigned() + self.slots_ = self.valobj.GetChildMemberWithName("slots_") + self.slot_type = self.slots_.GetType().GetPointeeType() + self.slot_size = self.slot_type.GetByteSize() + self.ctrl_ = self.valobj.GetChildMemberWithName("ctrl_") + except BaseException as ex: + print(f"{_get_function_name(self)} -> {ex}") + + def get_child_index(self, name): + try: + if name in ('size_', 'capacity_'): + return -1 + return int(name.lstrip('[').rstrip(']')) + except: + return -1 + + def get_child_at_index(self, index): + try: + if index < 0: + return None + if index >= self.size_ or index >= _MAX_CHILDREN: + return None + real_idx = -1 + for idx in range(min(self.capacity_ + 3, _MAX_CTRL_INDEX)): + ctrl = self.ctrl_.GetChildAtIndex(idx).GetValueAsSigned() + if ctrl >= -1: + real_idx += 1 + if real_idx == index: + return self.slots_.CreateChildAtOffset(f'[{index}]', idx * self.slot_size, self.slot_type) + except BaseException as ex: + print(f"{_get_function_name(self)} -> {ex}") + return None + + +class parallel_flat_or_node_map_or_set: + CLASS_PATTERN = "^phmap::parallel_(flat|node)_hash_(map|set)<.*>$" + HAS_SUMMARY = True + IS_SYNTHETIC_PROVIDER = True + REGEX_EXTRACT_ARRAY_SIZE = re.compile(r"std::array\s*<.*,\s*(\d+)\s*>") + + @staticmethod + def _get_size_and_capacity(valobj): + try: + valobj = valobj.GetNonSyntheticValue() + sets = valobj.GetChildMemberWithName('sets_') + # sets is an std::array. + # It's not possible to get the size of the array with templates parameters + # "set.GetType().GetTemplateArgumentType(1)" returns an "unsigned long" type but not the value + # so we must extract it with a regex + m = parallel_flat_or_node_map_or_set.REGEX_EXTRACT_ARRAY_SIZE.match(sets.GetType().GetName()) + n_buckets = int(m.group(1)) + # this is dependent on the implementation of the standard library + buckets = sets.GetChildMemberWithName('_M_elems') + size = capacity = 0 + for idx in range(n_buckets): + bucket = buckets.GetChildAtIndex(idx).GetChildMemberWithName('set_') + size += bucket.GetChildMemberWithName('size_').GetValueAsUnsigned() + capacity += bucket.GetChildMemberWithName('capacity_').GetValueAsUnsigned() + return size, capacity, n_buckets + except: + return '?', '?', 0 + + @staticmethod + def summary(valobj, _): + size, capacity, _ = parallel_flat_or_node_map_or_set._get_size_and_capacity(valobj) + return f"size = {size} (capacity = {capacity})" + + def __init__(self, valobj, _): + self.valobj = valobj + self.buckets = self.slot_type = None + self.size_ = self.capacity_ = self.n_buckets_ = self.slot_type = self.ctrl_size = 0 + + def num_children(self): + return min(self.size_, _MAX_CHILDREN) + + def has_children(self): + return True + + def update(self): + try: + self.size_, self.capacity_, self.n_buckets_ = self._get_size_and_capacity(self.valobj) + self.buckets = self.valobj.GetChildMemberWithName('sets_').GetChildMemberWithName('_M_elems') + bucket0 = self.buckets.GetChildAtIndex(0).GetChildMemberWithName('set_') + self.slot_type = bucket0.GetChildMemberWithName('slots_').GetType().GetPointeeType() + self.slot_size = self.slot_type.GetByteSize() + except BaseException as ex: + print(f"{_get_function_name(self)} -> {ex}") + + def get_child_index(self, name): + try: + if name in ('sets_'): + return -1 + return int(name.lstrip('[').rstrip(']')) + except: + return -1 + + def get_child_at_index(self, index): + try: + if index < 0: + return None + if index >= self.size_ or index >= _MAX_CHILDREN: + return None + real_idx = -1 + total_idx = 0 + for idx in range(self.n_buckets_): + bucket = self.buckets.GetChildAtIndex(idx).GetChildMemberWithName('set_') + size = bucket.GetChildMemberWithName("size_").GetValueAsUnsigned() + if size: + slots_ = bucket.GetChildMemberWithName("slots_") + ctrl_ = bucket.GetChildMemberWithName("ctrl_") + for jdx in range(size): + ctrl = ctrl_.GetChildAtIndex(jdx).GetValueAsSigned() + if ctrl >= -1: + real_idx += 1 + if real_idx == index: + return slots_.CreateChildAtOffset(f'[{index}]', jdx * self.slot_size, self.slot_type) + total_idx += size + if total_idx > _MAX_CHILDREN: + return None + except BaseException as ex: + print(f"{_get_function_name(self)} -> {ex}") + return None + + +def __lldb_init_module(debugger, internal_dict): + for sp in ( + flat_map_slot_type, + node_map_slot_type, + node_set_slot_type, + flat_hash_map_or_set, + parallel_flat_or_node_map_or_set, + ): + if sp.HAS_SUMMARY: + debugger.HandleCommand( + f'type summary add --regex "{sp.CLASS_PATTERN}" --python-function {_MODULE_NAME}.{sp.__name__}.summary ' + f'--category phmap --expand') + if sp.IS_SYNTHETIC_PROVIDER: + debugger.HandleCommand( + f'type synthetic add --regex "{sp.CLASS_PATTERN}" --python-class {_MODULE_NAME}.{sp.__name__} ' + f'--category phmap') + debugger.HandleCommand('type category enable phmap')