mirror of
				https://github.com/boostorg/unordered.git
				synced 2025-10-30 23:31:41 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			392 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			392 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
| # Copyright 2024 Braden Ganetsky
 | |
| # Distributed under the Boost Software License, Version 1.0.
 | |
| # https://www.boost.org/LICENSE_1_0.txt
 | |
| 
 | |
| import gdb.printing
 | |
| import gdb.xmethod
 | |
| import re
 | |
| import math
 | |
| 
 | |
| class BoostUnorderedHelpers:
 | |
|     def maybe_unwrap_atomic(n):
 | |
|         if f"{n.type.strip_typedefs()}".startswith("std::atomic<"):
 | |
|             underlying_type = n.type.template_argument(0)
 | |
|             return n.cast(underlying_type)
 | |
|         else:
 | |
|             return n
 | |
|             
 | |
|     def maybe_unwrap_foa_element(e):
 | |
|         if f"{e.type.strip_typedefs()}".startswith("boost::unordered::detail::foa::element_type<"):
 | |
|             return e["p"]
 | |
|         else:
 | |
|             return e
 | |
| 
 | |
|     def maybe_unwrap_reference(value):
 | |
|         if value.type.code == gdb.TYPE_CODE_REF:
 | |
|             return value.referenced_value()
 | |
|         else:
 | |
|             return value
 | |
| 
 | |
|     def countr_zero(n):
 | |
|         for i in range(32):
 | |
|             if (n & (1 << i)) != 0:
 | |
|                 return i
 | |
|         return 32
 | |
| 
 | |
| class BoostUnorderedPointerCustomizationPoint:
 | |
|     def __init__(self, any_ptr):
 | |
|         vis = gdb.default_visualizer(any_ptr)
 | |
|         if vis is None:
 | |
|             self.to_address = lambda ptr: ptr
 | |
|             self.next = lambda ptr, offset: ptr + offset
 | |
|         else:
 | |
|             self.to_address = lambda ptr: ptr if (ptr.type.code == gdb.TYPE_CODE_PTR) else type(vis).boost_to_address(ptr)
 | |
|             self.next = lambda ptr, offset: type(vis).boost_next(ptr, offset)
 | |
| 
 | |
| class BoostUnorderedFcaPrinter:
 | |
|     def __init__(self, val):
 | |
|         self.val = BoostUnorderedHelpers.maybe_unwrap_reference(val)
 | |
|         self.name = f"{self.val.type.strip_typedefs()}".split("<")[0]
 | |
|         self.name = self.name.replace("boost::unordered::", "boost::")
 | |
|         self.is_map = self.name.endswith("map")
 | |
|         self.cpo = BoostUnorderedPointerCustomizationPoint(self.val["table_"]["buckets_"]["buckets"])
 | |
| 
 | |
|     def to_string(self):
 | |
|         size = self.val["table_"]["size_"]
 | |
|         return f"{self.name} with {size} elements"
 | |
| 
 | |
|     def display_hint(self):
 | |
|         return "map"
 | |
| 
 | |
|     def children(self):
 | |
|         def generator():
 | |
|             grouped_buckets = self.val["table_"]["buckets_"]
 | |
| 
 | |
|             size = grouped_buckets["size_"]
 | |
|             buckets = grouped_buckets["buckets"]
 | |
|             bucket_index = 0
 | |
| 
 | |
|             count = 0
 | |
|             while bucket_index != size:
 | |
|                 current_bucket = self.cpo.next(self.cpo.to_address(buckets), bucket_index)
 | |
|                 node = self.cpo.to_address(current_bucket.dereference()["next"])
 | |
|                 while node != 0:
 | |
|                     value = node.dereference()["buf"]["t_"]
 | |
|                     if self.is_map:
 | |
|                         first = value["first"]
 | |
|                         second = value["second"]
 | |
|                         yield "", first
 | |
|                         yield "", second
 | |
|                     else:
 | |
|                         yield "", count
 | |
|                         yield "", value
 | |
|                     count += 1
 | |
|                     node = self.cpo.to_address(node.dereference()["next"])
 | |
|                 bucket_index += 1
 | |
|         
 | |
|         return generator()
 | |
| 
 | |
| class BoostUnorderedFcaIteratorPrinter:
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
|         self.cpo = BoostUnorderedPointerCustomizationPoint(self.val["p"])
 | |
| 
 | |
|     def to_string(self):
 | |
|         if self.valid():
 | |
|             value = self.cpo.to_address(self.val["p"]).dereference()["buf"]["t_"]
 | |
|             return f"iterator = {{ {value} }}"
 | |
|         else:
 | |
|             return "iterator = { end iterator }"
 | |
| 
 | |
|     def valid(self):
 | |
|         return (self.cpo.to_address(self.val["p"]) != 0) and (self.cpo.to_address(self.val["itb"]["p"]) != 0)
 | |
| 
 | |
| class BoostUnorderedFoaTableCoreCumulativeStatsPrinter:
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
| 
 | |
|     def to_string(self):
 | |
|         return "[stats]"
 | |
| 
 | |
|     def display_hint(self):
 | |
|         return "map"
 | |
| 
 | |
|     def children(self):
 | |
|         def generator():
 | |
|             members = ["insertion", "successful_lookup", "unsuccessful_lookup"]
 | |
|             for member in members:
 | |
|                 yield "", member
 | |
|                 yield "", self.val[member]
 | |
|         return generator()
 | |
|     
 | |
| class BoostUnorderedFoaCumulativeStatsPrinter:
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
|         self.n = self.val["n"]
 | |
|         self.N = self.val.type.template_argument(0)
 | |
| 
 | |
|     def display_hint(self):
 | |
|         return "map"
 | |
|     
 | |
|     def children(self):
 | |
|         def generator():
 | |
|             yield "", "count"
 | |
|             yield "", self.n
 | |
| 
 | |
|             sequence_stats_data = gdb.lookup_type("boost::unordered::detail::foa::sequence_stats_data")
 | |
|             data = self.val["data"]
 | |
|             arr = data.address.reinterpret_cast(sequence_stats_data.pointer())
 | |
|             def build_string(idx):
 | |
|                 entry = arr[idx]
 | |
|                 avg = float(entry["m"])
 | |
|                 var = float(entry["s"] / self.n) if (self.n != 0) else 0.0
 | |
|                 dev = math.sqrt(var)
 | |
|                 return f"{{avg = {avg}, var = {var}, dev = {dev}}}"
 | |
| 
 | |
|             if self.N > 0:
 | |
|                 yield "", "probe_length"
 | |
|                 yield "", build_string(0)
 | |
|             if self.N > 1:
 | |
|                 yield "", "num_comparisons"
 | |
|                 yield "", build_string(1)
 | |
| 
 | |
|         return generator()
 | |
| 
 | |
| class BoostUnorderedFoaPrinter:
 | |
|     def __init__(self, val):
 | |
|         self.val = BoostUnorderedHelpers.maybe_unwrap_reference(val)
 | |
|         self.name = f"{self.val.type.strip_typedefs()}".split("<")[0]
 | |
|         self.name = self.name.replace("boost::unordered::", "boost::")
 | |
|         self.is_map = self.name.endswith("map")
 | |
|         self.cpo = BoostUnorderedPointerCustomizationPoint(self.val["table_"]["arrays"]["groups_"])
 | |
| 
 | |
|     def to_string(self):
 | |
|         size = BoostUnorderedHelpers.maybe_unwrap_atomic(self.val["table_"]["size_ctrl"]["size"])
 | |
|         return f"{self.name} with {size} elements"
 | |
| 
 | |
|     def display_hint(self):
 | |
|         return "map"
 | |
| 
 | |
|     def is_regular_layout(self, group):
 | |
|         typename = group["m"].type.strip_typedefs()
 | |
|         array_size = typename.sizeof // typename.target().sizeof
 | |
|         if array_size == 16:
 | |
|             return True
 | |
|         elif array_size == 2:
 | |
|             return False
 | |
| 
 | |
|     def match_occupied(self, group):
 | |
|         m = group["m"]
 | |
|         at = lambda b: BoostUnorderedHelpers.maybe_unwrap_atomic(m[b]["n"])
 | |
| 
 | |
|         if self.is_regular_layout(group):
 | |
|             bits = [1 << b for b in range(16) if at(b) == 0]
 | |
|             return 0x7FFF & ~sum(bits)
 | |
|         else:
 | |
|             xx = at(0) | at(1)
 | |
|             yy = xx | (xx >> 32)
 | |
|             return 0x7FFF & (yy | (yy >> 16))
 | |
| 
 | |
|     def is_sentinel(self, group, pos):
 | |
|         m = group["m"]
 | |
|         at = lambda b: BoostUnorderedHelpers.maybe_unwrap_atomic(m[b]["n"])
 | |
| 
 | |
|         N = group["N"]
 | |
|         sentinel_ = group["sentinel_"]
 | |
|         if self.is_regular_layout(group):
 | |
|             return pos == N-1 and at(N-1) == sentinel_
 | |
|         else:
 | |
|             return pos == N-1 and (at(0) & 0x4000400040004000) == 0x4000 and (at(1) & 0x4000400040004000) == 0
 | |
| 
 | |
|     def children(self):
 | |
|         def generator():
 | |
|             table = self.val["table_"]
 | |
|             groups = self.cpo.to_address(table["arrays"]["groups_"])
 | |
|             elements = self.cpo.to_address(table["arrays"]["elements_"])
 | |
| 
 | |
|             pc_ = groups.cast(gdb.lookup_type("unsigned char").pointer())
 | |
|             p_ = elements
 | |
|             first_time = True
 | |
|             mask = 0
 | |
|             n0 = 0
 | |
|             n = 0
 | |
| 
 | |
|             count = 0
 | |
|             while p_ != 0:
 | |
|                 # This if block mirrors the condition in the begin() call
 | |
|                 if (not first_time) or (self.match_occupied(groups.dereference()) & 1):
 | |
|                     pointer = BoostUnorderedHelpers.maybe_unwrap_foa_element(p_)
 | |
|                     value = self.cpo.to_address(pointer).dereference()
 | |
|                     if self.is_map:
 | |
|                         first = value["first"]
 | |
|                         second = value["second"]
 | |
|                         yield "", first
 | |
|                         yield "", second
 | |
|                     else:
 | |
|                         yield "", count
 | |
|                         yield "", value
 | |
|                     count += 1
 | |
|                 first_time = False
 | |
| 
 | |
|                 n0 = pc_.cast(gdb.lookup_type("uintptr_t")) % groups.dereference().type.sizeof
 | |
|                 pc_ = self.cpo.next(pc_, -n0)
 | |
| 
 | |
|                 mask = (self.match_occupied(pc_.cast(groups.type).dereference()) >> (n0+1)) << (n0+1)
 | |
|                 while mask == 0:
 | |
|                     pc_ = self.cpo.next(pc_, groups.dereference().type.sizeof)
 | |
|                     p_ = self.cpo.next(p_, groups.dereference()["N"])
 | |
|                     mask = self.match_occupied(pc_.cast(groups.type).dereference())
 | |
|                 
 | |
|                 n = BoostUnorderedHelpers.countr_zero(mask)
 | |
|                 if self.is_sentinel(pc_.cast(groups.type).dereference(), n):
 | |
|                     p_ = 0
 | |
|                 else:
 | |
|                     pc_ = self.cpo.next(pc_, n)
 | |
|                     p_ = self.cpo.next(p_, n - n0)
 | |
| 
 | |
|         return generator()
 | |
| 
 | |
| class BoostUnorderedFoaIteratorPrinter:
 | |
|     def __init__(self, val):
 | |
|         self.val = val
 | |
|         self.cpo = BoostUnorderedPointerCustomizationPoint(self.val["p_"])
 | |
| 
 | |
|     def to_string(self):
 | |
|         if self.valid():
 | |
|             element = self.cpo.to_address(self.val["p_"])
 | |
|             pointer = BoostUnorderedHelpers.maybe_unwrap_foa_element(element)
 | |
|             value = self.cpo.to_address(pointer).dereference()
 | |
|             return f"iterator = {{ {value} }}"
 | |
|         else:
 | |
|             return "iterator = { end iterator }"
 | |
| 
 | |
|     def valid(self):
 | |
|         return (self.cpo.to_address(self.val["p_"]) != 0) and (self.cpo.to_address(self.val["pc_"]) != 0)
 | |
| 
 | |
| def boost_unordered_build_pretty_printer():
 | |
|     pp = gdb.printing.RegexpCollectionPrettyPrinter("boost_unordered")
 | |
|     add_template_printer = lambda name, printer: pp.add_printer(name, f"^{name}<.*>$", printer)
 | |
|     add_concrete_printer = lambda name, printer: pp.add_printer(name, f"^{name}$", printer)
 | |
| 
 | |
|     add_template_printer("boost::unordered::unordered_map", BoostUnorderedFcaPrinter)
 | |
|     add_template_printer("boost::unordered::unordered_multimap", BoostUnorderedFcaPrinter)
 | |
|     add_template_printer("boost::unordered::unordered_set", BoostUnorderedFcaPrinter)
 | |
|     add_template_printer("boost::unordered::unordered_multiset", BoostUnorderedFcaPrinter)
 | |
| 
 | |
|     add_template_printer("boost::unordered::detail::iterator_detail::iterator", BoostUnorderedFcaIteratorPrinter)
 | |
|     add_template_printer("boost::unordered::detail::iterator_detail::c_iterator", BoostUnorderedFcaIteratorPrinter)
 | |
| 
 | |
|     add_template_printer("boost::unordered::unordered_flat_map", BoostUnorderedFoaPrinter)
 | |
|     add_template_printer("boost::unordered::unordered_flat_set", BoostUnorderedFoaPrinter)
 | |
|     add_template_printer("boost::unordered::unordered_node_map", BoostUnorderedFoaPrinter)
 | |
|     add_template_printer("boost::unordered::unordered_node_set", BoostUnorderedFoaPrinter)
 | |
|     add_template_printer("boost::unordered::concurrent_flat_map", BoostUnorderedFoaPrinter)
 | |
|     add_template_printer("boost::unordered::concurrent_flat_set", BoostUnorderedFoaPrinter)
 | |
|     add_template_printer("boost::unordered::concurrent_node_map", BoostUnorderedFoaPrinter)
 | |
|     add_template_printer("boost::unordered::concurrent_node_set", BoostUnorderedFoaPrinter)
 | |
|     
 | |
|     add_template_printer("boost::unordered::detail::foa::table_iterator", BoostUnorderedFoaIteratorPrinter)
 | |
| 
 | |
|     add_concrete_printer("boost::unordered::detail::foa::table_core_cumulative_stats", BoostUnorderedFoaTableCoreCumulativeStatsPrinter)
 | |
|     add_template_printer("boost::unordered::detail::foa::cumulative_stats", BoostUnorderedFoaCumulativeStatsPrinter)
 | |
|     add_template_printer("boost::unordered::detail::foa::concurrent_cumulative_stats", BoostUnorderedFoaCumulativeStatsPrinter)
 | |
| 
 | |
|     return pp
 | |
| 
 | |
| gdb.printing.register_pretty_printer(gdb.current_objfile(), boost_unordered_build_pretty_printer())
 | |
| 
 | |
| 
 | |
| 
 | |
| # https://sourceware.org/gdb/current/onlinedocs/gdb.html/Writing-an-Xmethod.html
 | |
| class BoostUnorderedFoaGetStatsMethod(gdb.xmethod.XMethod):
 | |
|     def __init__(self):
 | |
|         gdb.xmethod.XMethod.__init__(self, "get_stats")
 | |
|  
 | |
|     def get_worker(self, method_name):
 | |
|         if method_name == "get_stats":
 | |
|             return BoostUnorderedFoaGetStatsWorker()
 | |
| 
 | |
| class BoostUnorderedFoaGetStatsWorker(gdb.xmethod.XMethodWorker):
 | |
|     def get_arg_types(self):
 | |
|         return None
 | |
| 
 | |
|     def get_result_type(self, obj):
 | |
|         return gdb.lookup_type("boost::unordered::detail::foa::table_core_cumulative_stats")
 | |
|  
 | |
|     def __call__(self, obj):
 | |
|         try:
 | |
|             return obj["table_"]["cstats"]
 | |
|         except gdb.error:
 | |
|             print("Error: Binary was compiled without stats. Recompile with `BOOST_UNORDERED_ENABLE_STATS` defined.")
 | |
|             return
 | |
|  
 | |
| class BoostUnorderedFoaMatcher(gdb.xmethod.XMethodMatcher):
 | |
|     def __init__(self):
 | |
|         gdb.xmethod.XMethodMatcher.__init__(self, 'BoostUnorderedFoaMatcher')
 | |
|         self.methods = [BoostUnorderedFoaGetStatsMethod()]
 | |
|  
 | |
|     def match(self, class_type, method_name):
 | |
|         template_name = f"{class_type.strip_typedefs()}".split("<")[0]
 | |
|         regex = "^boost::unordered::(unordered|concurrent)_(flat|node)_(map|set)$"
 | |
|         if not re.match(regex, template_name):
 | |
|             return None
 | |
| 
 | |
|         workers = []
 | |
|         for method in self.methods:
 | |
|             if method.enabled:
 | |
|                 worker = method.get_worker(method_name)
 | |
|                 if worker:
 | |
|                     workers.append(worker)
 | |
|         return workers
 | |
| 
 | |
| gdb.xmethod.register_xmethod_matcher(None, BoostUnorderedFoaMatcher())
 | |
| 
 | |
| 
 | |
| 
 | |
| """ Fancy pointer support """
 | |
| 
 | |
| """
 | |
| To allow your own fancy pointer type to interact with Boost.Unordered GDB pretty-printers,
 | |
| create a pretty-printer for your own type with the following additional methods.
 | |
| 
 | |
| (Note, this is assuming the presence of a type alias `pointer` for the underlying
 | |
| raw pointer type, Substitute whichever name is applicable in your case.)
 | |
| 
 | |
| `boost_to_address(fancy_ptr)`
 | |
|     * A static method, but `@staticmethod` is not required
 | |
|     * Parameter `fancy_ptr` of type `gdb.Value`
 | |
|         * Its `.type` will be your fancy pointer type
 | |
|     * Returns a `gdb.Value` with the raw pointer equivalent to your fancy pointer
 | |
|         * This method should be equivalent to calling `operator->()` on your fancy pointer in C++
 | |
| 
 | |
| `boost_next(raw_ptr, offset)`
 | |
|     * Parameter `raw_ptr` of type `gdb.Value`
 | |
|         * Its `.type` will be `pointer`
 | |
|     * Parameter `offset`
 | |
|         * Either has integer type, or is of type `gdb.Value` with an underlying integer
 | |
|     * Returns a `gdb.Value` with the raw pointer equivalent to your fancy pointer, as if you did the following operations
 | |
|         1. Convert the incoming raw pointer to your fancy pointer
 | |
|         2. Use operator+= to add the offset to the fancy pointer
 | |
|         3. Convert back to the raw pointer
 | |
|     * Note, you will not actually do these operations as stated. You will do equivalent lower-level operations that emulate having done the above
 | |
|         * Ultimately, it will be as if you called `operator+()` on your fancy pointer in C++, but using only raw pointers
 | |
| 
 | |
| Example
 | |
| ```
 | |
| class MyFancyPtrPrinter:
 | |
|     ...
 | |
| 
 | |
|     # Equivalent to `operator->()`
 | |
|     def boost_to_address(fancy_ptr):
 | |
|         ...
 | |
|         return ...
 | |
|         
 | |
|     # Equivalent to `operator+()`
 | |
|     def boost_next(raw_ptr, offset):
 | |
|         ...
 | |
|         return ...
 | |
|     
 | |
|     ...
 | |
| ```
 | |
| """
 |