diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 000000000..31b45589a --- /dev/null +++ b/.gdbinit @@ -0,0 +1,5 @@ +python +import sys +sys.path += ['debug/gdb', 'vendor/small_vector/source/support/python'] +import celerity.gdb, gch.gdb.prettyprinters.small_vector +end diff --git a/.github/workflows/celerity_ci.yml b/.github/workflows/celerity_ci.yml index 2e857fae9..2c141ebfc 100644 --- a/.github/workflows/celerity_ci.yml +++ b/.github/workflows/celerity_ci.yml @@ -178,6 +178,9 @@ jobs: # We build examples twice, but only run the installed version (which probably has more failure modes) working-directory: ${{ env.examples-build-dir }} run: ${{ env.container-workspace }}/ci/run-examples.sh /data/Lenna.png 1 2 4 + - name: Run debugging tests + if: matrix.build-type == 'Debug' && matrix.sycl == 'hipsycl' # newer DPC++ generates DWARF5 which is incompatible with Ubuntu 20.04's GDB + run: ${{ env.container-workspace }}/test/debug/pretty-print-test.py ${{ env.build-dir }}/test/debug/pretty_printables - name: Run system tests working-directory: ${{ env.build-dir }} run: ${{ env.container-workspace }}/ci/run-system-tests.sh 2 4 diff --git a/.gitignore b/.gitignore index 91dccbf85..23863578d 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ install .vscode/ .idea/ /tags +__pycache__/ diff --git a/debug/gdb/celerity/gdb/__init__.py b/debug/gdb/celerity/gdb/__init__.py new file mode 100644 index 000000000..6478610aa --- /dev/null +++ b/debug/gdb/celerity/gdb/__init__.py @@ -0,0 +1,7 @@ +import gdb + +def register_printers(obj): + from .prettyprinters import pretty_printer + gdb.printing.register_pretty_printer(obj, pretty_printer) + +register_printers(None) diff --git a/debug/gdb/celerity/gdb/prettyprinters.py b/debug/gdb/celerity/gdb/prettyprinters.py new file mode 100644 index 000000000..ac35ad297 --- /dev/null +++ b/debug/gdb/celerity/gdb/prettyprinters.py @@ -0,0 +1,159 @@ +import gdb +from typing import Iterable, Tuple + + +def iter_child_values(value: gdb.Value) -> Iterable[gdb.Value]: + for _, child in gdb.default_visualizer(value).children(): + yield child + + +def deref_unique_ptr(unique_ptr: gdb.Value) -> gdb.Value: + children = list(iter_child_values(unique_ptr)) + assert len(children) == 1 + return children[0].dereference() + + +def get_variant_content(variant: gdb.Value) -> gdb.Value: + return gdb.default_visualizer(variant).contained_value + + +class PhantomTypePrinter: + def __init__(self, prefix: str, val: gdb.Value): + self.prefix = prefix + self.value = val['m_value'] + + def to_string(self) -> str: + return self.prefix + str(self.value) + + +class CoordinatePrinter: + def __init__(self, val: gdb.Value): + self.values = val['m_values']['values'] + + def to_string(self) -> str: + i_min, i_max = self.values.type.range() + return '[' + ', '.join(str(self.values[i]) for i in range(i_min, i_max+1)) + ']' + + +class SubrangePrinter: + def __init__(self, val: gdb.Value): + self.offset = val['offset'] + self.range = val['range'] + + def to_string(self) -> str: + return str(self.offset) + ' + ' + str(self.range) + + +class NdRangePrinter: + def __init__(self, val): + self.global_range = val['m_global_range'] + self.local_range = val['m_local_range'] + self.offset = val['m_offset'] + + def children(self) -> Iterable[Tuple[str, gdb.Value]]: + yield 'global_range', self.global_range + yield 'local_range', self.local_range + yield 'offset', self.offset + + +class ChunkPrinter: + def __init__(self, val: gdb.Value): + self.offset = val['offset'] + self.range = val['range'] + self.global_size = val['global_size'] + + def children(self) -> Iterable[Tuple[str, gdb.Value]]: + yield 'offset', self.offset + yield 'range', self.range + yield 'global_size', self.global_size + + +class BoxPrinter: + def __init__(self, val: gdb.Value): + self.min = val['m_min'] + self.max = val['m_max'] + + def to_string(self) -> str: + return str(self.min) + ' - ' + str(self.max) + + +class RegionPrinter: + def __init__(self, val: gdb.Value): + self.boxes = val['m_boxes'] + + def to_string(self) -> str: + if next(gdb.default_visualizer(self.boxes).children(), None) is None: + return '{}' + + def children(self) -> Iterable[gdb.Value]: + return gdb.default_visualizer(self.boxes).children() + + def display_hint(self) -> str: + return 'array' + + +class RegionMapPrinter: + def __init__(self, val: gdb.Value): + impl = get_variant_content(val['m_region_map']) + self.extent = impl['m_extent'] + self.root = deref_unique_ptr(impl['m_root']) + + def to_string(self) -> str: + return 'region_map({})'.format(self.extent) + + def children(self) -> Iterable[Tuple[str, gdb.Value]]: + def recurse_tree(root: gdb.Value): + child_boxes = root['m_child_boxes'] + children = root['m_children'] + for box, child in zip(iter_child_values(child_boxes), iter_child_values(children)): + child = get_variant_content(child) + if 'celerity::detail::region_map_detail::inner_node' in str(child.type): + yield from recurse_tree(deref_unique_ptr(child)) + else: + yield ('[' + str(box) + ']', child) + yield from recurse_tree(self.root) + + +class WriteCommandStatePrinter: + def __init__(self, val: gdb.Value): + bits = int(val['m_cid']['m_value']) + self.cid = (bits & 0x3fff_ffff_ffff_ffff) + self.fresh = (bits & 0x8000_0000_0000_0000) == 0 + self.replicated = (bits & 0x4000_0000_0000_0000) != 0 + + def to_string(self) -> str: + return 'C{} ({}{})'.format(self.cid, + 'fresh' if self.fresh else 'stale', + ', replicated' if self.replicated else '') + + +def add_phantom_type_printer(pp: gdb.printing.RegexpCollectionPrettyPrinter, type: str, prefix: str): + pp.add_printer(type, '^celerity::detail::PhantomType<.*celerity::detail::{}_PhantomType>$'.format(type), + lambda val: PhantomTypePrinter(prefix, val)) + + +def build_pretty_printer(): + pp = gdb.printing.RegexpCollectionPrettyPrinter("Celerity") + add_phantom_type_printer(pp, 'task_id', 'T') + add_phantom_type_printer(pp, 'buffer_id', 'B') + add_phantom_type_printer(pp, 'node_id', 'N') + add_phantom_type_printer(pp, 'command_id', 'C') + add_phantom_type_printer(pp, 'collective_group_id', 'CG') + add_phantom_type_printer(pp, 'reduction_id', 'R') + add_phantom_type_printer(pp, 'host_object_id', 'H') + add_phantom_type_printer(pp, 'hydration_id', 'HY') + add_phantom_type_printer(pp, 'transfer_id', 'TR') + pp.add_printer('id', '^celerity::id<.*>$', CoordinatePrinter) + pp.add_printer('range', '^celerity::range<.*>$', CoordinatePrinter) + pp.add_printer('subrange', '^celerity::subrange<.*>$', SubrangePrinter) + pp.add_printer('nd_range', '^celerity::nd_range<.*>$', NdRangePrinter) + pp.add_printer('chunk', '^celerity::chunk<.*>$', ChunkPrinter) + pp.add_printer('box', '^celerity::detail::box<.*>$', BoxPrinter) + pp.add_printer('region', '^celerity::detail::region<.*>$', RegionPrinter) + pp.add_printer('region_map', '^celerity::detail::region_map<.*>$', RegionMapPrinter) + pp.add_printer('write_command_state', '^celerity::detail::write_command_state$', WriteCommandStatePrinter) + + return pp + + +pretty_printer = build_pretty_printer() diff --git a/include/distributed_graph_generator.h b/include/distributed_graph_generator.h index f64180d7f..e0b4c878c 100644 --- a/include/distributed_graph_generator.h +++ b/include/distributed_graph_generator.h @@ -27,9 +27,9 @@ using node_bitset = std::bitset; * - Whether this data has been replicated from somewhere else, i.e., we are not the original producer */ class write_command_state { - constexpr static int64_t fresh_bit = 1ll << 63; + constexpr static int64_t stale_bit = 1ll << 63; constexpr static int64_t replicated_bit = 1ll << 62; - static_assert(sizeof(fresh_bit) == sizeof(command_id)); + static_assert(sizeof(stale_bit) == sizeof(command_id)); public: constexpr write_command_state() = default; @@ -40,13 +40,13 @@ class write_command_state { if(is_replicated) { m_cid |= replicated_bit; } } - bool is_fresh() const { return (m_cid & fresh_bit) == 0u; } + bool is_fresh() const { return (m_cid & stale_bit) == 0u; } bool is_replicated() const { return (m_cid & replicated_bit) != 0u; } - void mark_as_stale() { m_cid |= fresh_bit; } + void mark_as_stale() { m_cid |= stale_bit; } - operator command_id() const { return m_cid & ~fresh_bit & ~replicated_bit; } + operator command_id() const { return m_cid & ~stale_bit & ~replicated_bit; } friend bool operator==(const write_command_state& lhs, const write_command_state& rhs) { return lhs.m_cid == rhs.m_cid; } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0ee0566fd..4edd407c6 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -106,3 +106,7 @@ if(CAIRO_INCLUDE_DIRS AND CAIRO_LIBRARIES) endforeach() target_link_libraries(all_tests PRIVATE ${CAIRO_LIBRARIES}) endif() + +if(CMAKE_BUILD_TYPE STREQUAL Debug) + add_subdirectory(debug) +endif() diff --git a/test/debug/CMakeLists.txt b/test/debug/CMakeLists.txt new file mode 100644 index 000000000..b1e9b1423 --- /dev/null +++ b/test/debug/CMakeLists.txt @@ -0,0 +1,2 @@ +add_executable(pretty_printables pretty_printables.cc) +add_celerity_to_target(TARGET pretty_printables SOURCES pretty_printables.cc) \ No newline at end of file diff --git a/test/debug/pretty-print-test.py b/test/debug/pretty-print-test.py new file mode 100755 index 000000000..e6b147be7 --- /dev/null +++ b/test/debug/pretty-print-test.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +import subprocess +import sys + +EXPECTED = b'''__builtin_trap(); +tid = T10 +bid = B11 +nid = N12 +cid = C13 +cgid = CG14 +rid = R15 +hoid = H16 +hyid = HY17 +trid = TR18 +id = [1, 2, 3] +range = [1, 2, 3] +subrange = [1, 2, 3] + [4, 5, 6] +chunk = {offset = [1, 2, 3], range = [4, 5, 6], global_size = [7, 8, 9]} +nd_range = {global_range = [2, 4, 6], local_range = [1, 2, 3], offset = [7, 8, 9]} +box = [1, 2, 3] - [4, 5, 6] +empty_region = {} +region = {[1, 2, 3] - [4, 5, 6], [11, 2, 3] - [14, 5, 6], [21, 2, 3] - [24, 5, 6]} +region_map = region_map([0, 0, 0] - [10, 10, 10]) = {[[0, 0, 0] - [10, 10, 1]] = 0, [[0, 0, 5] - [10, 10, 10]] = 0, [[0, 0, 1] - [10, 1, 5]] = 0, [[0, 5, 1] - [10, 10, 5]] = 0, [[0, 1, 1] - [1, 5, 5]] = 0, [[1, 1, 1] - [2, 2, 2]] = 1337, [[1, 1, 2] - [3, 3, 3]] = 69, [[1, 2, 1] - [3, 3, 2]] = 69, [[2, 1, 1] - [3, 2, 2]] = 69, [[1, 1, 3] - [5, 5, 5]] = 42, [[3, 1, 1] - [5, 3, 3]] = 42, [[1, 3, 1] - [5, 5, 3]] = 42, [[5, 1, 1] - [10, 5, 5]] = 0} +wcs_fresh = C123 (fresh) +wcs_stale = C123 (stale) +wcs_replicated = C123 (fresh, replicated) +''' + +# invoke as `pretty-print-test.py build/test/debug/pretty_printables` +assert len(sys.argv) == 2 + +out = subprocess.check_output(['gdb', '-batch', sys.argv[1], '-ex', 'r', '-ex', 'i locals']) +if not out.endswith(EXPECTED): + print('-*- pretty-print test FAILED: expected GDB output to end with', file=sys.stderr) + print(EXPECTED.decode('utf-8'), file=sys.stderr) + print('-*- but got', file=sys.stderr) + print(out.decode('utf-8'), file=sys.stderr) + print('-*-', file=sys.stderr) + sys.exit(1) diff --git a/test/debug/pretty_printables.cc b/test/debug/pretty_printables.cc new file mode 100644 index 000000000..580d45343 --- /dev/null +++ b/test/debug/pretty_printables.cc @@ -0,0 +1,43 @@ +#include "distributed_graph_generator.h" +#include "grid.h" +#include "ranges.h" +#include "region_map.h" +#include "types.h" + +int main() { + [[maybe_unused]] celerity::detail::task_id tid = 10; + [[maybe_unused]] celerity::detail::buffer_id bid = 11; + [[maybe_unused]] celerity::detail::node_id nid = 12; + [[maybe_unused]] celerity::detail::command_id cid = 13; + [[maybe_unused]] celerity::detail::collective_group_id cgid = 14; + [[maybe_unused]] celerity::detail::reduction_id rid = 15; + [[maybe_unused]] celerity::detail::host_object_id hoid = 16; + [[maybe_unused]] celerity::detail::hydration_id hyid = 17; + [[maybe_unused]] celerity::detail::transfer_id trid = 18; + + [[maybe_unused]] celerity::id<3> id(1, 2, 3); + [[maybe_unused]] celerity::range<3> range(1, 2, 3); + [[maybe_unused]] celerity::subrange<3> subrange(celerity::id(1, 2, 3), celerity::range(4, 5, 6)); + [[maybe_unused]] celerity::chunk<3> chunk(celerity::id(1, 2, 3), celerity::range(4, 5, 6), celerity::range(7, 8, 9)); + [[maybe_unused]] celerity::nd_range<3> nd_range(celerity::range(2, 4, 6), celerity::range(1, 2, 3), celerity::id(7, 8, 9)); + [[maybe_unused]] celerity::detail::box<3> box(celerity::id(1, 2, 3), celerity::id(4, 5, 6)); + [[maybe_unused]] celerity::detail::region<3> empty_region; + [[maybe_unused]] celerity::detail::region<3> region({ + celerity::detail::box(celerity::id(1, 2, 3), celerity::id(4, 5, 6)), + celerity::detail::box(celerity::id(11, 2, 3), celerity::id(14, 5, 6)), + celerity::detail::box(celerity::id(21, 2, 3), celerity::id(24, 5, 6)), + }); + + [[maybe_unused]] celerity::detail::region_map region_map(celerity::range<3>(10, 10, 10), 3); + region_map.update_region(celerity::detail::box<3>({1, 1, 1}, {5, 5, 5}), 42); + region_map.update_region(celerity::detail::box<3>({1, 1, 1}, {3, 3, 3}), 69); + region_map.update_region(celerity::detail::box<3>({1, 1, 1}, {2, 2, 2}), 1337); + + [[maybe_unused]] celerity::detail::write_command_state wcs_fresh(celerity::detail::command_id(123)); + [[maybe_unused]] celerity::detail::write_command_state wcs_stale(celerity::detail::command_id(123)); + wcs_stale.mark_as_stale(); + [[maybe_unused]] celerity::detail::write_command_state wcs_replicated(celerity::detail::command_id(123), true /* replicated */); + + // tell GDB to break here so we can examine locals + __builtin_trap(); +}