SERVER-75613 Add GDB pretty printers for immutable data structures

(cherry picked from commit 98d43cc794)

GitOrigin-RevId: fc6b7b4cb4fd271cef7abdbe43376e166002a19c
This commit is contained in:
Dan Larkin-York 2023-04-21 12:54:28 +00:00 committed by MongoDB Bot
parent 38a4eca7c0
commit defe45e44c
7 changed files with 571 additions and 4 deletions

View File

@ -13,3 +13,6 @@ source buildscripts/gdb/mongo_lock.py
# Load methods for printing in-memory contents of WT tables. # Load methods for printing in-memory contents of WT tables.
source buildscripts/gdb/wt_dump_table.py source buildscripts/gdb/wt_dump_table.py
# Load third-party pretty printers
source src/third_party/immer/dist/tools/gdb_pretty_printers/autoload.py

View File

@ -9,8 +9,12 @@ from pathlib import Path
import gdb import gdb
import gdb.printing import gdb.printing
ROOT_PATH = str(Path(os.path.abspath(__file__)).parent.parent.parent)
if ROOT_PATH not in sys.path:
sys.path.insert(0, ROOT_PATH)
from src.third_party.immer.dist.tools.gdb_pretty_printers.printers import ListIter as ImmerListIter # pylint: disable=wrong-import-position
if not gdb: if not gdb:
sys.path.insert(0, str(Path(os.path.abspath(__file__)).parent.parent.parent))
from buildscripts.gdb.mongo import get_boost_optional from buildscripts.gdb.mongo import get_boost_optional
from buildscripts.gdb.optimizer_printers import register_abt_printers from buildscripts.gdb.optimizer_printers import register_abt_printers
@ -600,6 +604,61 @@ class AbslFlatHashMapPrinter(AbslHashMapPrinterBase):
yield ('value', kvp['value']) yield ('value', kvp['value'])
class ImmutableMapIter(ImmerListIter):
def __init__(self, val):
super().__init__(val)
self.max = (1 << 64) - 1
self.pair = None
self.curr = (None, self.max, self.max)
def __next__(self):
if self.pair:
result = ('value', self.pair['second'])
self.pair = None
self.i += 1
return result
if self.i == self.size:
raise StopIteration
if self.i < self.curr[1] or self.i >= self.curr[2]:
self.curr = self.region()
self.pair = self.curr[0][self.i - self.curr[1]].cast(
gdb.lookup_type(self.v.type.template_argument(0).name))
result = ('key', self.pair['first'])
return result
class ImmutableMapPrinter:
"""Pretty-printer for mongo::immutable::map<>."""
def __init__(self, val):
self.val = val
def to_string(self):
return '%s of size %d' % (self.val.type, int(self.val['_storage']['impl_']['size']))
def children(self):
return ImmutableMapIter(self.val['_storage'])
def display_hint(self):
return 'map'
class ImmutableSetPrinter:
"""Pretty-printer for mongo::immutable::set<>."""
def __init__(self, val):
self.val = val
def to_string(self):
return '%s of size %d' % (self.val.type, int(self.val['_storage']['impl_']['size']))
def children(self):
return ImmerListIter(self.val['_storage'])
def display_hint(self):
return 'array'
def find_match_brackets(search, opening='<', closing='>'): def find_match_brackets(search, opening='<', closing='>'):
"""Return the index of the closing bracket that matches the first opening bracket. """Return the index of the closing bracket that matches the first opening bracket.
@ -925,6 +984,8 @@ def build_pretty_printer():
pp.add('__wt_update', '__wt_update', False, WtUpdateToBsonPrinter) pp.add('__wt_update', '__wt_update', False, WtUpdateToBsonPrinter)
pp.add('CodeFragment', 'mongo::sbe::vm::CodeFragment', False, SbeCodeFragmentPrinter) pp.add('CodeFragment', 'mongo::sbe::vm::CodeFragment', False, SbeCodeFragmentPrinter)
pp.add('boost::optional', 'boost::optional', True, BoostOptionalPrinter) pp.add('boost::optional', 'boost::optional', True, BoostOptionalPrinter)
pp.add('immutable::map', 'mongo::immutable::map', True, ImmutableMapPrinter)
pp.add('immutable::set', 'mongo::immutable::set', True, ImmutableSetPrinter)
# Optimizer/ABT related pretty printers that can be used only with a running process. # Optimizer/ABT related pretty printers that can be used only with a running process.
register_abt_printers(pp) register_abt_printers(pp)

View File

@ -1,6 +1,6 @@
.. image:: https://github.com/arximboldi/immer/workflows/test/badge.svg .. image:: https://github.com/arximboldi/immer/workflows/test/badge.svg
:target: https://github.com/arximboldi/immer/actions?query=workflow%3Atest+branch%3Amaster :target: https://github.com/arximboldi/immer/actions?query=workflow%3Atest+branch%3Amaster
:alt: Github Actions Badge :alt: GitHub Actions Badge
.. image:: https://codecov.io/gh/arximboldi/immer/branch/master/graph/badge.svg .. image:: https://codecov.io/gh/arximboldi/immer/branch/master/graph/badge.svg
:target: https://codecov.io/gh/arximboldi/immer :target: https://codecov.io/gh/arximboldi/immer
@ -74,7 +74,7 @@ Example
For a **complete example** check `Ewig, a simple didactic For a **complete example** check `Ewig, a simple didactic
text-editor <https://github.com/arximboldi/ewig>`_ built with this text-editor <https://github.com/arximboldi/ewig>`_ built with this
library. You may also wanna check `Lager, a Redux-like library library. You may also wanna check `Lager, a Redux-like library
<https://github.com/arximboldi/lager>`_ for writting interactive <https://github.com/arximboldi/lager>`_ for writing interactive
software in C++ using a value-oriented design. software in C++ using a value-oriented design.

View File

@ -0,0 +1 @@
# empty

View File

@ -0,0 +1,11 @@
import gdb.printing
import os
path = os.path.dirname(__file__)
if not path in sys.path:
sys.path.append(path)
from printers import immer_lookup_function
gdb.printing.register_pretty_printer(gdb.current_objfile(), immer_lookup_function)
print("immer gdb pretty-printers loaded")

View File

@ -0,0 +1,485 @@
# Sourced from https://gist.github.com/dwightguth/283afe96b60b3793f3c02036701457f8
# with light modifications.
import gdb.printing
import decimal
import traceback
import re
MAX = 1 << 64 - 1
class ArrayIter:
def __init__(self, val):
self.val_ptr = val.type.template_argument(0).pointer()
self.v = val['impl_']
self.size = self.v['size']
self.i = 0
def __iter__(self):
return self
def __next__(self):
if self.i == self.size:
raise StopIteration
ptr = self.v['ptr']
data = ptr.dereference()['impl']['d']['buffer'].address.reinterpret_cast(self.val_ptr)
result = ('[%d]' % self.i, data[self.i])
self.i += 1
return result
class Relaxed:
def __init__(self, node, shift, relaxed, it):
self.node = node
self.shift = shift
self.relaxed = relaxed
self.it = it
self.B = self.node.type.target().template_argument(2)
self.BL = self.node.type.target().template_argument(3)
def index(self, idx):
offset = idx >> self.shift
while self.relaxed.dereference()['d']['sizes'][offset] <= idx:
offset += 1
return offset
def towards(self, idx):
offset = self.index(idx)
left_size = self.relaxed.dereference()['d']['sizes'][offset - 1] if offset else 0
child = self.it.inner(self.node)[offset]
is_leaf = self.shift == self.BL
next_size = self.relaxed.dereference()['d']['sizes'][offset] - left_size
next_idx = idx - left_size
if is_leaf:
return self.it.visit_leaf(LeafSub(child, next_size), next_idx)
else:
return self.it.visit_maybe_relaxed_sub(child, self.shift - self.B, next_size, next_idx)
class LeafSub:
def __init__(self, node, count):
self.node = node
self.count_ = count
self.BL = self.node.type.target().template_argument(3)
self.MASK = (1 << self.BL) - 1
def index(self, idx):
return idx & self.MASK
def count(self):
return self.count_
class FullLeaf:
def __init__(self, node):
self.node = node
self.BL = self.node.type.target().template_argument(3)
self.BRANCHES = 1 << self.BL
self.MASK = self.BRANCHES - 1
def index(self, idx):
return idx & self.MASK
def count(self):
return self.BRANCHES
class Leaf:
def __init__(self, node, size):
self.node = node
self.size = size
self.BL = self.node.type.target().template_argument(3)
self.MASK = (1 << self.BL) - 1
def index(self, idx):
return idx & self.MASK
def count(self):
return self.index(self.size - 1) + 1
class RegularSub:
def __init__(self, node, shift, size, it):
self.node = node
self.shift = shift
self.size = size
self.it = it
self.B = self.node.type.target().template_argument(2)
self.MASK = (1 << self.B) - 1
def towards(self, idx):
offset = self.index(idx)
count = self.count()
return self.it.towards_regular(self, idx, offset, count)
def index(self, idx):
return (idx >> self.shift) & self.MASK
def count(self):
return self.subindex(self.size - 1) + 1
def subindex(self, idx):
return idx >> self.shift
class Regular:
def __init__(self, node, shift, size, it):
self.node = node
self.shift = shift
self.size = size
self.it = it
self.B = self.node.type.target().template_argument(2)
self.MASK = (1 << self.B) - 1
def index(self, idx):
return (idx >> self.shift) & self.MASK
def count(self):
return self.index(self.size - 1) + 1
def towards(self, idx):
offset = self.index(idx)
count = self.count()
return self.it.towards_regular(self, idx, offset, count)
class Full:
def __init__(self, node, shift, it):
self.node = node
self.shift = shift
self.it = it
self.B = self.node.type.target().template_argument(2)
self.BL = self.node.type.target().template_argument(3)
self.MASK = (1 << self.B) - 1
def index(self, idx):
return (idx >> self.shift) & self.MASK
def towards(self, idx):
offset = self.index(idx)
is_leaf = self.shift == self.BL
child = self.it.inner(self.node)[offset]
if is_leaf:
return self.it.visit_leaf(FullLeaf(child), idx)
else:
return Full(child, self.shift - self.B, self.it).towards(idx)
class ListIter:
def __init__(self, val):
self.v = val['impl_']
self.size = self.v['size']
self.i = 0
self.curr = (None, MAX, MAX)
self.node_ptr_ptr = self.v['root'].type.pointer()
self.B = self.v['root'].type.target().template_argument(2)
self.BL = self.v['root'].type.target().template_argument(3)
def __iter__(self):
return self
def __next__(self):
if self.i == self.size:
raise StopIteration
if self.i < self.curr[1] or self.i >= self.curr[2]:
self.curr = self.region()
result = ('[%d]' % self.i, self.curr[0][self.i - self.curr[1]].cast(
gdb.lookup_type(self.v.type.template_argument(0).name)))
self.i += 1
return result
def region(self):
tail_off = self.tail_offset()
if self.i >= tail_off:
return (self.leaf(self.v['tail']), tail_off, self.size)
else:
subs = self.visit_maybe_relaxed_sub(self.v['root'], self.v['shift'], tail_off, self.i)
first = self.i - subs[1]
end = first + subs[2]
return (subs[0], first, end)
def tail_offset(self):
r = self.relaxed(self.v['root'])
if r:
return r.dereference()['d']['sizes'][r.dereference()['d']['count'] - 1]
elif self.size:
return (self.size - 1) & ~self.leaf_mask()
else:
return 0
def relaxed(self, node):
return node.dereference()['impl']['d']['data']['inner']['relaxed']
def leaf(self, node):
return node.dereference()['impl']['d']['data']['leaf']['buffer'].address
def inner(self, node):
return node.dereference()['impl']['d']['data']['inner']['buffer'].address.reinterpret_cast(
self.node_ptr_ptr)
def visit_maybe_relaxed_sub(self, node, shift, size, idx):
relaxed = self.relaxed(node)
if relaxed:
return Relaxed(node, shift, relaxed, self).towards(idx)
else:
return RegularSub(node, shift, size, self).towards(idx)
def visit_leaf(self, pos, idx):
return (self.leaf(pos.node), pos.index(idx), pos.count())
# pos = node, idx = full, offset = shifted & masked, count = shifted
def towards_regular(self, pos, idx, offset, count):
is_leaf = pos.shift == self.BL
child = self.inner(pos.node)[offset]
is_full = offset + 1 != count
if is_full:
if is_leaf:
return self.visit_leaf(FullLeaf(child), idx)
else:
return Full(child, pos.shift - self.B, self).towards(idx)
elif is_leaf:
return self.visit_leaf(Leaf(child, pos.size), idx)
else:
return Regular(child, pos.shift - self.B, pos.size, self).towards(idx)
def leaf_mask(self):
return (1 << self.BL) - 1
def popcount(x):
b = 0
while x > 0:
x &= x - 1
b += 1
return b
class ChampIter:
def __init__(self, val):
self.depth = 0
self.count = 0
v = val['impl_']['root']
self.node_ptr_ptr = v.type.pointer()
m = self.datamap(v)
if m:
self.cur = self.values(v)
self.end = self.values(v) + popcount(m)
else:
self.cur = None
self.end = None
self.path = [v.address]
self.B = v.type.target().template_argument(4)
self.MAX_DEPTH = ((8 * 8) + self.B - 1) / 8
self.ensure_valid()
def __iter__(self):
return self
def __next__(self):
if self.cur == None:
raise StopIteration
result = self.cur.dereference()
self.cur += 1
self.count += 1
self.ensure_valid()
return result
def ensure_valid(self):
while self.cur == self.end:
while self.step_down():
if self.cur != self.end:
return
if not self.step_right():
self.cur = None
self.end = None
return
def step_down(self):
if self.depth < self.MAX_DEPTH:
parent = self.path[self.depth].dereference()
if self.nodemap(parent):
self.depth += 1
self.path.append(self.children(parent))
child = self.path[self.depth]
if self.depth < self.MAX_DEPTH:
m = self.datamap(child)
if m:
self.cur = self.values(child)
self.end = self.cur + popcount(m)
else:
self.cur = self.collisions(child)
self.end = self.cur = self.collision_count(child)
return True
return False
def step_right(self):
while self.depth > 0:
parent = self.path[self.depth - 1].dereference()
last = self.children(parent) + popcount(self.nodemap(parent))
next_ = self.path[self.depth] + 1
if next_ < last:
self.path[self.depth] = next_
child = self.path[self.depth].dereference()
if self.depth < self.MAX_DEPTH:
m = self.datamap(child)
if m:
self.cur = self.values(child)
self.end = self.cur + popcount(m)
else:
self.cur = self.collisions(child)
self.end = self.cur + self.collision_count(child)
return True
self.depth -= 1
self.path.pop()
return False
def values(self, node):
return node.dereference()['impl']['d']['data']['inner']['values'].dereference(
)['d']['buffer'].address.cast(self.T_ptr)
def children(self, node):
return node.dereference()['impl']['d']['data']['inner']['buffer'].address.cast(
self.node_ptr_ptr)
def datamap(self, node):
return node.dereference()['impl']['d']['data']['inner']['datamap']
def nodemap(self, node):
return node.dereference()['impl']['d']['data']['inner']['nodemap']
def collision_count(self, node):
return node.dereference()['impl']['d']['data']['collision']['count']
def collisions(self, node):
return node.dereference()['impl']['d']['data']['collision']['buffer'].address.cast(
self.T_ptr)
class MapIter(ChampIter):
def __init__(self, val):
self.T_ptr = gdb.lookup_type("std::pair<" + val.type.template_argument(0).name + ", " +
val.type.template_argument(1).name + ">").pointer()
ChampIter.__init__(self, val)
self.pair = None
def __next__(self):
if self.pair:
result = ('[%d]' % self.count, self.pair['second'])
self.pair = None
return result
self.pair = super().__next__()
return ('[%d]' % self.count, self.pair['first'])
class SetIter(ChampIter):
def __init__(self, val):
self.T_ptr = gdb.lookup_type(val.type.template_argument(0).name).pointer()
ChampIter.__init__(self, val)
def __next__(self):
return ('[%d]' % self.count, super().__next__())
def num_elements(num):
return '1 element' if num == 1 else '%d elements' % num
class ArrayPrinter:
"Prints an immer::array"
def __init__(self, val):
self.val = val
def to_string(self):
return 'immer::array with %s' % num_elements(self.val['impl_']['size'])
def children(self):
return ArrayIter(self.val)
def display_hint(self):
return 'array'
class MapPrinter:
"Print an immer::map"
def __init__(self, val):
self.val = val
def to_string(self):
return 'immer::map with %s' % num_elements(self.val['impl_']['size'])
def children(self):
return MapIter(self.val)
def display_hint(self):
return 'map'
class SetPrinter:
"Prints an immer::set"
def __init__(self, val):
self.val = val
def to_string(self):
return 'immer::set with %s' % num_elements(self.val['impl_']['size'])
def children(self):
return SetIter(self.val)
class TablePrinter:
"Prints an immer::table"
def __init__(self, val):
self.val = val
def to_string(self):
return 'immer::table with %s' % num_elements(self.val['impl_']['size'])
def children(self):
return SetIter(self.val)
class ListPrinter:
"Prints an immer::vector or immer::flex_vector"
def __init__(self, val, typename):
self.val = val
self.typename = typename
def to_string(self):
return '%s of length %d' % (self.typename, int(self.val['impl_']['size']))
def children(self):
return ListIter(self.val)
def display_hint(self):
return 'array'
def immer_lookup_function(val):
compiled_rx = re.compile('^([a-zA-Z0-9_:]+)(<.*>)?$')
typename = gdb.types.get_basic_type(val.type).tag
if not typename:
return None
match = compiled_rx.match(typename)
if not match:
return None
basename = match.group(1)
if basename == "immer::array":
return ArrayPrinter(val)
elif basename == "immer::map":
return MapPrinter(val)
elif basename == "immer::set":
return SetPrinter(val)
elif basename == "immer::table":
return TablePrinter(val)
elif basename == "immer::vector":
return ListPrinter(val, "immer::vector")
elif basename == "immer::flex_vector":
return ListPrinter(val, "immer::flex_vector")
return None

View File

@ -27,7 +27,13 @@ rm -rf $LIB_GIT_DIR/example
rm -rf $LIB_GIT_DIR/extra rm -rf $LIB_GIT_DIR/extra
rm -rf $LIB_GIT_DIR/nix rm -rf $LIB_GIT_DIR/nix
rm -rf $LIB_GIT_DIR/test rm -rf $LIB_GIT_DIR/test
rm -rf $LIB_GIT_DIR/tools rm -rf $LIB_GIT_DIR/tools/clojure
rm -rf $LIB_GIT_DIR/tools/docker
rm -rf $LIB_GIT_DIR/tools/include
rm -rf $LIB_GIT_DIR/tools/scala
rm -rf $LIB_GIT_DIR/tools/sinusoidal-sphinx-theme
rm -rf $LIB_GIT_DIR/tools/reproduce-paper-results.bash
rm -rf $LIB_GIT_DIR/tools/with-tee.bash
rm -f $LIB_GIT_DIR/BUILD rm -f $LIB_GIT_DIR/BUILD
rm -f $LIB_GIT_DIR/CMakeLists.txt rm -f $LIB_GIT_DIR/CMakeLists.txt
rm -f $LIB_GIT_DIR/Package.swift rm -f $LIB_GIT_DIR/Package.swift