-
Notifications
You must be signed in to change notification settings - Fork 55
[feat] Initial version of the Numba CUDA GDB pretty-printer #692
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
63de2ba
ebfe957
47827ea
df30db0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,208 @@ | ||||||||||||||||||||||||||||
| # SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||||||||||||||||||||||||||||
| # SPDX-License-Identifier: BSD-2-Clause | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| """gdb printing extension for Numba types.""" | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| import re | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| import gdb.printing | ||||||||||||||||||||||||||||
| import gdb | ||||||||||||||||||||||||||||
| except ImportError: | ||||||||||||||||||||||||||||
| raise ImportError("GDB python support is not available.") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class NumbaArrayPrinter: | ||||||||||||||||||||||||||||
| def __init__(self, val): | ||||||||||||||||||||||||||||
| self.val = val | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def to_string(self): | ||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| import numpy as np | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| HAVE_NUMPY = True | ||||||||||||||||||||||||||||
| except ImportError: | ||||||||||||||||||||||||||||
| HAVE_NUMPY = False | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||
| NULL = 0x0 | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Raw data references, these need unpacking/interpreting. | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Member "data" is... | ||||||||||||||||||||||||||||
| # DW_TAG_member of DIDerivedType, tag of DW_TAG_pointer_type | ||||||||||||||||||||||||||||
| # encoding e.g. DW_ATE_float | ||||||||||||||||||||||||||||
| data = self.val["data"] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Member "itemsize" is... | ||||||||||||||||||||||||||||
| # DW_TAG_member of DIBasicType encoding DW_ATE_signed | ||||||||||||||||||||||||||||
| itemsize = self.val["itemsize"] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # Members "shape" and "strides" are... | ||||||||||||||||||||||||||||
| # DW_TAG_member of DIDerivedType, the type is a DICompositeType | ||||||||||||||||||||||||||||
| # (it's a Numba UniTuple) with tag: DW_TAG_array_type, i.e. it's an | ||||||||||||||||||||||||||||
| # array repr, it has a basetype of e.g. DW_ATE_unsigned and also | ||||||||||||||||||||||||||||
| # "elements" which are referenced with a DISubrange(count: <const>) | ||||||||||||||||||||||||||||
| # to say how many elements are in the array. | ||||||||||||||||||||||||||||
| rshp = self.val["shape"] | ||||||||||||||||||||||||||||
| rstrides = self.val["strides"] | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # bool on whether the data is aligned. | ||||||||||||||||||||||||||||
| is_aligned = False | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # type information decode, simple type: | ||||||||||||||||||||||||||||
| ty_str = str(self.val.type) | ||||||||||||||||||||||||||||
| if HAVE_NUMPY and ("aligned" in ty_str or "Record" in ty_str): | ||||||||||||||||||||||||||||
| ty_str = ty_str.replace("unaligned ", "").strip() | ||||||||||||||||||||||||||||
| matcher = re.compile(r"array\((Record.*), (.*), (.*)\)\ \(.*") | ||||||||||||||||||||||||||||
| # NOTE: need to deal with "Alignment" else dtype size is wrong | ||||||||||||||||||||||||||||
| arr_info = [x.strip() for x in matcher.match(ty_str).groups()] | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the regex match result is not checked for None before calling
Suggested change
|
||||||||||||||||||||||||||||
| dtype_str, ndim_str, order_str = arr_info | ||||||||||||||||||||||||||||
| rstr = r"Record\((.*\[.*\]);([0-9]+);(True|False)" | ||||||||||||||||||||||||||||
| rstr_match = re.match(rstr, dtype_str) | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The
Suggested change
|
||||||||||||||||||||||||||||
| # balign is unused, it's the alignment | ||||||||||||||||||||||||||||
| fields, balign, is_aligned_str = rstr_match.groups() | ||||||||||||||||||||||||||||
|
Comment on lines
+62
to
+64
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the regex match result is not checked for None before calling
Suggested change
|
||||||||||||||||||||||||||||
| is_aligned = is_aligned_str == "True" | ||||||||||||||||||||||||||||
| field_dts = fields.split(",") | ||||||||||||||||||||||||||||
| struct_entries = [] | ||||||||||||||||||||||||||||
| for f in field_dts: | ||||||||||||||||||||||||||||
| splitted = f.split("[") | ||||||||||||||||||||||||||||
| name = splitted[0] | ||||||||||||||||||||||||||||
| dt_part = splitted[1:] | ||||||||||||||||||||||||||||
|
Comment on lines
+69
to
+71
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if the field string
Suggested change
|
||||||||||||||||||||||||||||
| if len(dt_part) > 1: | ||||||||||||||||||||||||||||
| raise TypeError("Unsupported sub-type: %s" % f) | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| dt_part = dt_part[0] | ||||||||||||||||||||||||||||
| if "nestedarray" in dt_part: | ||||||||||||||||||||||||||||
| raise TypeError("Unsupported sub-type: %s" % f) | ||||||||||||||||||||||||||||
| dt_as_str = dt_part.split(";")[0].split("=")[1] | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. array index access without bounds checking - if the split operations don't produce the expected number of elements, this will cause an IndexError
Suggested change
|
||||||||||||||||||||||||||||
| dtype = np.dtype(dt_as_str) | ||||||||||||||||||||||||||||
| struct_entries.append((name, dtype)) | ||||||||||||||||||||||||||||
| # The dtype is actually a record of some sort | ||||||||||||||||||||||||||||
| dtype_str = struct_entries | ||||||||||||||||||||||||||||
| else: # simple type | ||||||||||||||||||||||||||||
| matcher = re.compile(r"array\((.*),(.*),(.*)\)\ \(.*") | ||||||||||||||||||||||||||||
| arr_info = [x.strip() for x in matcher.match(ty_str).groups()] | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same issue as line 59 -
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the regex match result is not checked for None before calling
Suggested change
|
||||||||||||||||||||||||||||
| dtype_str, ndim_str, order_str = arr_info | ||||||||||||||||||||||||||||
| # fix up unichr dtype | ||||||||||||||||||||||||||||
| if "unichr x " in dtype_str: | ||||||||||||||||||||||||||||
| dtype_str = dtype_str[1:-1].replace("unichr x ", "<U") | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def dwarr2inttuple(dwarr): | ||||||||||||||||||||||||||||
| # Converts a gdb handle to a dwarf array to a tuple of ints | ||||||||||||||||||||||||||||
| fields = dwarr.type.fields() | ||||||||||||||||||||||||||||
| lo, hi = fields[0].type.range() | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Accessing
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. accessing fields[0] without checking if the fields list is empty will cause an IndexError
Suggested change
|
||||||||||||||||||||||||||||
| return tuple([int(dwarr[x]) for x in range(lo, hi + 1)]) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # shape/strides extraction | ||||||||||||||||||||||||||||
| shape = dwarr2inttuple(rshp) | ||||||||||||||||||||||||||||
| strides = dwarr2inttuple(rstrides) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # if data is not NULL | ||||||||||||||||||||||||||||
| if data != NULL: | ||||||||||||||||||||||||||||
| if HAVE_NUMPY: | ||||||||||||||||||||||||||||
| # The data extent in bytes is: | ||||||||||||||||||||||||||||
| # sum(shape * strides) | ||||||||||||||||||||||||||||
| # get the data, then wire to as_strided | ||||||||||||||||||||||||||||
| shp_arr = np.array([max(0, x - 1) for x in shape]) | ||||||||||||||||||||||||||||
| strd_arr = np.array(strides) | ||||||||||||||||||||||||||||
| extent = np.sum(shp_arr * strd_arr) | ||||||||||||||||||||||||||||
| extent += int(itemsize) | ||||||||||||||||||||||||||||
|
Comment on lines
+107
to
+110
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the extent calculation could produce a negative value or zero if shape contains values <= 0, which would cause issues with read_memory. The
Suggested change
|
||||||||||||||||||||||||||||
| dtype_clazz = np.dtype(dtype_str, align=is_aligned) | ||||||||||||||||||||||||||||
| dtype = dtype_clazz | ||||||||||||||||||||||||||||
| this_proc = gdb.selected_inferior() | ||||||||||||||||||||||||||||
| mem = this_proc.read_memory(int(data), extent) | ||||||||||||||||||||||||||||
| arr_data = np.frombuffer(mem, dtype=dtype) | ||||||||||||||||||||||||||||
| new_arr = np.lib.stride_tricks.as_strided( | ||||||||||||||||||||||||||||
| arr_data, | ||||||||||||||||||||||||||||
| shape=shape, | ||||||||||||||||||||||||||||
| strides=strides, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| return "\n" + str(new_arr) | ||||||||||||||||||||||||||||
| # Catch all for no NumPy | ||||||||||||||||||||||||||||
| return "array([...], dtype=%s, shape=%s)" % (dtype_str, shape) | ||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| # Not yet initialized or NULLed out data | ||||||||||||||||||||||||||||
| buf = list(["NULL/Uninitialized"]) | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use a list literal directly instead of wrapping it in
Suggested change
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time! |
||||||||||||||||||||||||||||
| return "array([" + ", ".join(buf) + "]" + ")" | ||||||||||||||||||||||||||||
| except Exception as e: | ||||||||||||||||||||||||||||
| return "array[Exception: Failed to parse. %s]" % e | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class NumbaComplexPrinter: | ||||||||||||||||||||||||||||
| def __init__(self, val): | ||||||||||||||||||||||||||||
| self.val = val | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def to_string(self): | ||||||||||||||||||||||||||||
| return "%s+%sj" % (self.val["real"], self.val["imag"]) | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The complex number formatting doesn't handle negative imaginary parts correctly. A complex number like
Suggested change
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class NumbaTuplePrinter: | ||||||||||||||||||||||||||||
| def __init__(self, val): | ||||||||||||||||||||||||||||
| self.val = val | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def to_string(self): | ||||||||||||||||||||||||||||
| fields = self.val.type.fields() | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. accessing
Suggested change
|
||||||||||||||||||||||||||||
| buf = [str(self.val[f.name]) for f in fields] | ||||||||||||||||||||||||||||
| return "(%s)" % ", ".join(buf) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class NumbaUniTuplePrinter: | ||||||||||||||||||||||||||||
| def __init__(self, val): | ||||||||||||||||||||||||||||
| self.val = val | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def to_string(self): | ||||||||||||||||||||||||||||
| # unituples are arrays | ||||||||||||||||||||||||||||
| fields = self.val.type.fields() | ||||||||||||||||||||||||||||
| lo, hi = fields[0].type.range() | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same issue as line 94 - accessing
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. accessing fields[0] without checking if the fields list is empty will cause an IndexError
Suggested change
|
||||||||||||||||||||||||||||
| buf = [str(self.val[i]) for i in range(lo, hi + 1)] | ||||||||||||||||||||||||||||
| return "(%s)" % ", ".join(buf) | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| class NumbaUnicodeTypePrinter: | ||||||||||||||||||||||||||||
| def __init__(self, val): | ||||||||||||||||||||||||||||
| self.val = val | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def to_string(self): | ||||||||||||||||||||||||||||
| NULL = 0x0 | ||||||||||||||||||||||||||||
| data = self.val["data"] | ||||||||||||||||||||||||||||
| nitems = self.val["length"] | ||||||||||||||||||||||||||||
| kind = self.val["kind"] | ||||||||||||||||||||||||||||
| if data != NULL: | ||||||||||||||||||||||||||||
| # This needs sorting out, encoding is wrong | ||||||||||||||||||||||||||||
| this_proc = gdb.selected_inferior() | ||||||||||||||||||||||||||||
| mem = this_proc.read_memory(int(data), nitems * kind) | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The memory read size calculation
Suggested change
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. memory read size calculation
Suggested change
|
||||||||||||||||||||||||||||
| if isinstance(mem, memoryview): | ||||||||||||||||||||||||||||
| buf = bytes(mem).decode() | ||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. calling
Suggested change
|
||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| buf = mem.decode("utf-8") | ||||||||||||||||||||||||||||
|
Comment on lines
+175
to
+178
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The encoding handling is inconsistent between the two branches. When
Suggested change
|
||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||
| buf = str(data) | ||||||||||||||||||||||||||||
| return "'%s'" % buf | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| def _create_printers(): | ||||||||||||||||||||||||||||
| printer = gdb.printing.RegexpCollectionPrettyPrinter("Numba") | ||||||||||||||||||||||||||||
| printer.add_printer( | ||||||||||||||||||||||||||||
| "Numba unaligned array printer", | ||||||||||||||||||||||||||||
| "^unaligned array\\(", | ||||||||||||||||||||||||||||
| NumbaArrayPrinter, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| printer.add_printer("Numba array printer", "^array\\(", NumbaArrayPrinter) | ||||||||||||||||||||||||||||
| printer.add_printer( | ||||||||||||||||||||||||||||
| "Numba complex printer", "^complex[0-9]+\\ ", NumbaComplexPrinter | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| printer.add_printer("Numba Tuple printer", "^Tuple\\(", NumbaTuplePrinter) | ||||||||||||||||||||||||||||
| printer.add_printer( | ||||||||||||||||||||||||||||
| "Numba UniTuple printer", "^UniTuple\\(", NumbaUniTuplePrinter | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| printer.add_printer( | ||||||||||||||||||||||||||||
| "Numba unicode_type printer", | ||||||||||||||||||||||||||||
| "^unicode_type\\s+\\(", | ||||||||||||||||||||||||||||
| NumbaUnicodeTypePrinter, | ||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||
| return printer | ||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||
| # register the Numba pretty printers for the current object | ||||||||||||||||||||||||||||
| gdb.printing.register_pretty_printer(gdb.current_objfile(), _create_printers()) | ||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The
matcher.match(ty_str)can returnNoneif the regex pattern doesn't match the type string, which will cause anAttributeErrorwhen calling.groups()onNone. This should be checked before accessing groups.