Skip to content

Commit 6cb244e

Browse files
authored
Add shallow pretty printers and tests (#314)
New Pretty printers that would match the STL pretty printers pattern have been developed. This version of the pretty printers do not show any implementation details and focus on the sole data. Also, like the other pretty printers, those pretty printers do not deal with the permutations, so an error message was added. Unit tests were written accordingly, but not activated yet, due to their very specific nature.
1 parent 6057f59 commit 6cb244e

4 files changed

Lines changed: 398 additions & 2 deletions

File tree

scripts/gdb-printers-shallow.py

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
import abc
2+
import logging
3+
import math
4+
from typing import (
5+
Any,
6+
Callable,
7+
Iterable,
8+
)
9+
import re
10+
11+
import gdb
12+
13+
14+
class Buffer(metaclass=abc.ABCMeta):
15+
"""Base class for accessing buffer contents"""
16+
17+
def __init__(self, val: gdb.Value, data_key: str):
18+
self._val: gdb.Value = val
19+
self._data_key: str = data_key
20+
21+
@abc.abstractmethod
22+
def __len__(self) -> int:
23+
...
24+
25+
def __getitem__(self, key) -> Any:
26+
return self.data()[key]
27+
28+
def data(self) -> gdb.Value:
29+
return self._val[self._data_key]
30+
31+
32+
class StackBuffer(Buffer):
33+
34+
def __init__(self, val: gdb.Value):
35+
super().__init__(val, "m_data")
36+
37+
def __len__(self) -> int:
38+
return int(self._val.type.template_argument(1))
39+
40+
41+
class ChaiBuffer(Buffer):
42+
43+
def __init__(self, val: gdb.Value):
44+
super().__init__(val, "m_pointer")
45+
46+
def __len__(self) -> int:
47+
return int(self._val['m_capacity'])
48+
49+
50+
class MallocBuffer(Buffer):
51+
52+
def __init__(self, val: gdb.Value):
53+
super().__init__(val, "m_data")
54+
55+
def __len__(self) -> int:
56+
return int(self._val['m_capacity'])
57+
58+
59+
def extract_buffer(val: gdb.Value) -> Buffer:
60+
# Use self.val.type.fields() to know the fields.
61+
# You can also have a look at the fields of the fields.
62+
t: str = str(val.type)
63+
if re.match('LvArray::ChaiBuffer<.*>', t):
64+
return ChaiBuffer(val)
65+
elif re.match('LvArray::StackBuffer<.*>', t):
66+
return StackBuffer(val)
67+
elif re.match('LvArray::MallocBuffer<.*>', t):
68+
return MallocBuffer(val)
69+
else:
70+
raise ValueError(f"Could not build buffer from `{val.type}`.")
71+
72+
73+
class LvArrayPrinter:
74+
"""Base printer for LvArray classes"""
75+
76+
def __init__(self, val: gdb.Value):
77+
self.val: gdb.Value = val
78+
79+
def real_type(self) -> gdb.Type:
80+
return self.val.type.strip_typedefs()
81+
82+
def display_hint(self) -> str:
83+
return 'array'
84+
85+
86+
class __ArrayPrinter(LvArrayPrinter):
87+
"""Utility class for code factorization"""
88+
89+
def __init__(self,
90+
val: gdb.Value,
91+
data_extractor: Callable[[gdb.Value], gdb.Value],
92+
dimension_extractor: Callable[[gdb.Value], gdb.Value]):
93+
"""
94+
val: The initial `gdb.Value`. Typically a `LvArray::Array`.
95+
data_extractor: How to access the raw data from the initial `val`.
96+
dimension_extractor: How to access the dimensions (since this is a multi-dimensional array) from the initial `val`.
97+
"""
98+
super().__init__(val)
99+
100+
self.data = data_extractor(self.val)
101+
dimensions: gdb.Value = dimension_extractor(self.val)
102+
103+
num_dimensions: int = int(self.val.type.template_argument(1))
104+
dimensions: Iterable[gdb.Value] = map(dimensions.__getitem__, range(num_dimensions))
105+
self.dimensions: tuple[int] = tuple(map(int, dimensions))
106+
107+
def to_string(self) -> str:
108+
# Extracting the permutations from the type name (looks like the integer template parameters are optimized out)
109+
permutation = self.val.type.strip_typedefs().template_argument(2)
110+
if m := re.search(r"\d+(?:[ \t]*,[ \t]*\d+)*", str(permutation)): # Extract a list of integers separated with commas
111+
s = m.group()
112+
permutation: tuple[int, ...] = tuple(map(int, s.split(",")))
113+
if permutation != tuple(range(len(self.dimensions))):
114+
msg = f"Only sorted permutation is supported by pretty printers. " \
115+
f"{permutation} is not sorted so the output of the pretty printers will be jumbled."
116+
logging.warning(msg)
117+
else:
118+
raise ValueError(f"Could not parse permutation for {permutation}.")
119+
120+
dimensions = map(str, self.dimensions)
121+
return f'{self.real_type()} of shape [{", ".join(dimensions)}]'
122+
123+
def children(self) -> Iterable[tuple[str, gdb.Value]]:
124+
d0, ds = self.dimensions[0], self.dimensions[1:]
125+
126+
# The main idea of this loop is to build the multi-dimensional array type to the data (e.g. `int[2][3]`).
127+
# Then we'll cast the raw pointer into this n-d array and gdb will be able to manage it.
128+
array_type: gdb.Type = self.data.type.target()
129+
for d in reversed(ds): # Note that we ditch the first dimension from our loop in order to have it as first level children.
130+
array_type = array_type.array(d - 1)
131+
132+
# We manage the first level children ourselves, so we need to manage the position of the data ourselves too.
133+
stride: int = math.prod(ds)
134+
135+
for i in range(d0):
136+
array = (self.data + i * stride).dereference().cast(array_type)
137+
yield '[%d]' % i, array
138+
139+
140+
class ArrayPrinter(__ArrayPrinter):
141+
"""Pretty-print for Array(View)"""
142+
143+
def __init__(self, val: gdb.Value):
144+
super().__init__(val, lambda v: extract_buffer(v["m_dataBuffer"]).data(), lambda v: v["m_dims"]["data"])
145+
146+
147+
class ArraySlicePrinter(__ArrayPrinter):
148+
"""Pretty-print for ArraySlice"""
149+
150+
def __init__(self, val: gdb.Value):
151+
super().__init__(val, lambda v: v["m_data"], lambda v: v["m_dims"])
152+
153+
154+
class ArrayOfArraysPrinter(LvArrayPrinter):
155+
"""Pretty-print for ArrayOfArrays(View)"""
156+
157+
def __len__(self) -> int:
158+
return int(self.val['m_numArrays'])
159+
160+
def to_string(self) -> str:
161+
return '%s of size %d' % (self.real_type(), len(self))
162+
163+
def children(self) -> Iterable[tuple[str, gdb.Value]]:
164+
# In this function, we are walking along the "sub" arrays ourselves.
165+
# To do this, we manipulate the raw pointer/offsets information ourselves.
166+
data = extract_buffer(self.val["m_values"]).data()
167+
offsets = extract_buffer(self.val["m_offsets"])
168+
sizes = extract_buffer(self.val["m_sizes"])
169+
for i in range(len(self)): # Iterating over all the "sub" arrays.
170+
# Converting a raw pointer `T*` to an equivalent type including the size `T[N]` that gdb will manage.
171+
array_type = data.type.target().array(sizes[i] - 1)
172+
array = (data + offsets[i]).dereference().cast(array_type)
173+
yield '[%d]' % i, array
174+
175+
176+
def build_array_printer():
177+
pp = gdb.printing.RegexpCollectionPrettyPrinter("LvArray-arrays-shallow")
178+
pp.add_printer('LvArray::Array', '^LvArray::Array(View)?<.*>$', ArrayPrinter)
179+
pp.add_printer('LvArray::ArraySlice', '^LvArray::ArraySlice<.*>$', ArraySlicePrinter)
180+
pp.add_printer('LvArray::ArrayOfArrays', '^LvArray::ArrayOfArrays(View)?<.*>$', ArrayOfArraysPrinter)
181+
return pp
182+
183+
184+
try:
185+
import gdb.printing
186+
gdb.printing.register_pretty_printer(gdb.current_objfile(), build_array_printer())
187+
except ImportError:
188+
logging.warning("Could not register LvArray pretty printers.")

unitTests/CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,16 @@ set( testSources
5959
testIndexing.cpp
6060
testInput.cpp
6161
testIntegerConversion.cpp
62+
testInvalidOperations.cpp
6263
testMath.cpp
6364
testMemcpy.cpp
65+
testPrettyPrinters.cpp
6466
testSliceHelpers.cpp
6567
testSortedArray.cpp
6668
testSortedArrayManipulation.cpp
6769
testSparsityPattern.cpp
6870
testStackArray.cpp
71+
testStackTrace.cpp
6972
testTensorOpsDeterminant.cpp
7073
testTensorOpsEigen.cpp
7174
testTensorOpsInverseOneArg.cpp
@@ -74,8 +77,6 @@ set( testSources
7477
testTensorOpsSymInverseOneArg.cpp
7578
testTensorOpsSymInverseTwoArgs.cpp
7679
testTypeManipulation.cpp
77-
testStackTrace.cpp
78-
testInvalidOperations.cpp
7980
)
8081

8182
blt_list_append( TO testSources ELEMENTS testChaiBuffer.cpp testArray_ChaiBuffer.cpp IF ENABLE_CHAI )

unitTests/testPrettyPrinters.cpp

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
#include "Array.hpp"
2+
#include "ArrayOfArrays.hpp"
3+
#include "MallocBuffer.hpp"
4+
5+
#include <RAJA/util/Permutations.hpp>
6+
7+
void breakpoint_helper()
8+
{
9+
// Left blank. This function is there to be used as a breakpoint for gdb.
10+
}
11+
12+
13+
template< typename T >
14+
using array1d = LvArray::Array< int, 1, RAJA::PERM_I, int, LvArray::MallocBuffer >;
15+
template< typename T, typename P = RAJA::PERM_IJ >
16+
using array2d = LvArray::Array< int, 2, P, int, LvArray::MallocBuffer >;
17+
template< typename T >
18+
using array3d = LvArray::Array< int, 3, RAJA::PERM_IJK, int, LvArray::MallocBuffer >;
19+
template< typename T >
20+
using aoa = LvArray::ArrayOfArrays< T, int, LvArray::MallocBuffer >;
21+
22+
23+
int main()
24+
{
25+
array1d< int > v0;
26+
27+
array1d< int > v1;
28+
v1.emplace_back( 1 );
29+
v1.emplace_back( 2 );
30+
31+
auto v1v = v1.toView();
32+
LVARRAY_LOG( v1v[0] ); // Calling `LVARRAY_LOG` to prevent the variable from being unused.
33+
auto v1vc = v1.toViewConst();
34+
LVARRAY_LOG( v1vc[0] );
35+
36+
array2d< int > v2( 2, 3 );
37+
v2[0][0] = 1;
38+
v2[0][1] = 2;
39+
v2[0][2] = 3;
40+
v2[1][0] = 4;
41+
v2[1][1] = 5;
42+
v2[1][2] = 6;
43+
array2d< int, RAJA::PERM_JI > v2ji( 2, 3 );
44+
45+
auto v2v = v2.toView();
46+
LVARRAY_LOG( v2v[0][0] );
47+
auto v2vc = v2.toViewConst();
48+
LVARRAY_LOG( v2vc[0][0] );
49+
auto v2s = v2[0];
50+
LVARRAY_LOG( v2s[0] );
51+
auto v2sc = v2[0].toSliceConst();
52+
LVARRAY_LOG( v2sc[0] );
53+
54+
array3d< int > v3( 2, 3, 4 );
55+
for( int i = 0, count = 0; i < 2; ++i )
56+
{
57+
for( int j = 0; j < 3; ++j )
58+
{
59+
for( int k = 0; k < 4; ++k, ++count )
60+
{
61+
v3[i][j][k] = count;
62+
}
63+
}
64+
}
65+
66+
auto v3v = v3.toView();
67+
LVARRAY_LOG( v3v[0][0][0] );
68+
auto v3vc = v3.toViewConst();
69+
LVARRAY_LOG( v3vc[0][0][0] );
70+
71+
auto v3s = v3[0];
72+
LVARRAY_LOG( v3s[0][0] );
73+
auto v3s2 = v3[0][0];
74+
LVARRAY_LOG( v3s2[0] );
75+
76+
aoa< int > aoa0( 2, 10 );
77+
aoa0.emplaceBack( 0, 1 );
78+
aoa0.emplaceBack( 0, 2 );
79+
aoa0.emplaceBack( 0, 3 );
80+
aoa0.emplaceBack( 1, 7 );
81+
aoa0.emplaceBack( 1, 8 );
82+
83+
auto aoa0v = aoa0.toView();
84+
LVARRAY_LOG( aoa0v[0][0] );
85+
auto aoa0vc = aoa0.toViewConst();
86+
LVARRAY_LOG( aoa0vc[0][0] );
87+
88+
auto aoa0s = aoa0[1];
89+
LVARRAY_LOG( aoa0s[0] );
90+
91+
breakpoint_helper();
92+
93+
return 0;
94+
}

0 commit comments

Comments
 (0)