Commit feb8981a95

Robin Voetter <robin@voetter.nl>
2022-03-16 15:38:30
gdb: add slice, multi array list, and array hash map printers
1 parent e18c29a
Changed files (3)
lib/std/hash_map.zig
@@ -1,4 +1,5 @@
 const std = @import("std.zig");
+const builtin = @import("builtin");
 const assert = debug.assert;
 const autoHash = std.hash.autoHash;
 const debug = std.debug;
@@ -1576,6 +1577,19 @@ pub fn HashMapUnmanaged(
             self.metadata = null;
             self.available = 0;
         }
+
+        /// This function is used in tools/zig-gdb.py to fetch the header type to facilitate
+        /// fancy debug printing for this type.
+        fn gdbHelper(self: *Self, hdr: *Header) void {
+            _ = self;
+            _ = hdr;
+        }
+
+        comptime {
+            if (builtin.mode == .Debug) {
+                _ = gdbHelper;
+            }
+        }
     };
 }
 
lib/std/multi_array_list.zig
@@ -1,4 +1,5 @@
 const std = @import("std.zig");
+const builtin = @import("builtin");
 const assert = std.debug.assert;
 const meta = std.meta;
 const mem = std.mem;
@@ -433,6 +434,19 @@ pub fn MultiArrayList(comptime S: type) type {
         fn FieldType(field: Field) type {
             return meta.fieldInfo(S, field).field_type;
         }
+
+        /// This function is used in tools/zig-gdb.py to fetch the child type to facilitate
+        /// fancy debug printing for this type.
+        fn gdbHelper(self: *Self, child: *S) void {
+            _ = self;
+            _ = child;
+        }
+
+        comptime {
+            if (builtin.mode == .Debug) {
+                _ = gdbHelper;
+            }
+        }
     };
 }
 
tools/zig-gdb.py
@@ -32,6 +32,27 @@ class BufPrinter:
     def display_hint(self):
         return 'string'
 
+class SlicePrinter:
+    def __init__(self, val):
+        self.val = val
+
+    def children(self):
+        for i in range(self.val['len']):
+            yield ('[%d]' % i, (self.val['ptr'] + i).dereference())
+
+    def display_hint(self):
+        return 'array'
+
+class SliceStringPrinter:
+    def __init__(self, val):
+        self.val = val
+
+    def to_string(self):
+        return self.val['ptr'].string(length=self.val['len'])
+
+    def display_hint(self):
+        return 'string'
+
 # Handles both ArrayList and ArrayListUnmanaged.
 class ArrayListPrinter:
     def __init__(self, val):
@@ -39,52 +60,143 @@ class ArrayListPrinter:
 
     def to_string(self):
         type = self.val.type.name[len('std.array_list.'):]
-        type = re.sub(r'ArrayListAligned(Unmanaged)?\((.*),null\)$', r'ArrayList\1(\2)', type)
+        type = re.sub(r'^ArrayListAligned(Unmanaged)?\((.*),null\)$', r'ArrayList\1(\2)', type)
         return '%s of length %s, capacity %s' % (type, self.val['items']['len'], self.val['capacity'])
 
     def children(self):
-        for i in range(int(self.val['items']['len'])):
+        for i in range(self.val['items']['len']):
             item = self.val['items']['ptr'] + i
             yield ('[%d]' % i, item.dereference())
 
     def display_hint(self):
         return 'array'
 
-# Handles both HashMap and HashMapUnmanaged
+class MultiArrayListPrinter:
+    def __init__(self, val):
+        self.val = val
+
+    def child_type(self):
+        (helper_fn, _) = gdb.lookup_symbol('%s.gdbHelper' % self.val.type.name)
+        return helper_fn.type.fields()[1].type.target()
+
+    def to_string(self):
+        type = self.val.type.name[len('std.multi_array_list.'):]
+        return '%s of length %s, capacity %s' % (type, self.val['len'], self.val['capacity'])
+
+    def slice(self):
+        fields = self.child_type().fields()
+        base = self.val['bytes']
+        cap = self.val['capacity']
+        len = self.val['len']
+
+        if len == 0:
+            return
+
+        fields = sorted(fields, key=lambda field: field.type.alignof, reverse=True)
+
+        for field in fields:
+            ptr = base.cast(field.type.pointer()).dereference().cast(field.type.array(len - 1))
+            base += field.type.sizeof * cap
+            yield (field.name, ptr)
+
+    def children(self):
+        for i, (name, ptr) in enumerate(self.slice()):
+            yield ('[%d]' % i, name)
+            yield ('[%d]' % i, ptr)
+
+    def display_hint(self):
+        return 'map'
+
+# Handles both HashMap and HashMapUnmanaged.
 class HashMapPrinter:
     def __init__(self, val):
         self.type = val.type
         is_managed = re.search(r'^std\.hash_map\.HashMap\(', self.type.name)
         self.val = val['unmanaged'] if is_managed else val
 
+    def header_ptr_type(self):
+        (helper_fn, _) = gdb.lookup_symbol('%s.gdbHelper' % self.val.type.name)
+        return helper_fn.type.fields()[1].type
+
     def header(self):
-        (header_fn, _) = gdb.lookup_symbol('%s.header' % self.val.type.name)
-        header_ptr_type = header_fn.type.target()
-        return (self.val['metadata'].cast(header_ptr_type) - 1).dereference()
+        if self.val['metadata'] == 0:
+            return None
+        return (self.val['metadata'].cast(self.header_ptr_type()) - 1).dereference()
 
     def to_string(self):
         type = self.type.name[len('std.hash_map.'):]
         type = re.sub(r'^HashMap(Unmanaged)?\((.*),std.hash_map.AutoContext\(.*$', r'AutoHashMap\1(\2)', type)
-        return '%s of length %s, capacity %s' % (type, self.val['size'], self.header()['capacity'])
+        hdr = self.header()
+        if hdr is not None:
+            cap = hdr['capacity']
+        else:
+            cap = 0
+        return '%s of length %s, capacity %s' % (type, self.val['size'], cap)
 
     def children(self):
         hdr = self.header()
-        for i in range(int(hdr['capacity'])):
+        if hdr is None:
+            return
+        is_map = self.display_hint() == 'map'
+        for i in range(hdr['capacity']):
             metadata = self.val['metadata'] + i
             if metadata.dereference()['used'] == 1:
                 yield ('[%d]' % i, (hdr['keys'] + i).dereference())
-                yield ('[%d]' % i, (hdr['values'] + i).dereference())
+                if is_map:
+                    yield ('[%d]' % i, (hdr['values'] + i).dereference())
 
     def display_hint(self):
-        return 'map'
+        for field in self.header_ptr_type().target().fields():
+            if field.name == 'values':
+                return 'map'
+        return 'array'
+
+# Handles both ArrayHashMap and ArrayHashMapUnmanaged.
+class ArrayHashMapPrinter:
+    def __init__(self, val):
+        self.type = val.type
+        is_managed = re.search(r'^std\.array_hash_map\.ArrayHashMap\(', self.type.name)
+        self.val = val['unmanaged'] if is_managed else val
+
+    def to_string(self):
+        type = self.type.name[len('std.array_hash_map.'):]
+        type = re.sub(r'^ArrayHashMap(Unmanaged)?\((.*),std.array_hash_map.AutoContext\(.*$', r'AutoArrayHashMap\1(\2)', type)
+        return '%s of length %s' % (type, self.val['entries']['len'])
+
+    def children(self):
+        entries = MultiArrayListPrinter(self.val['entries'])
+        len = self.val['entries']['len']
+        fields = {}
+        for name, ptr in entries.slice():
+            fields[str(name)] = ptr
+
+        for i in range(len):
+            if 'key' in fields:
+                yield ('[%d]' % i, fields['key'][i])
+            else:
+                yield ('[%d]' % i, '{}')
+            if 'value' in fields:
+                yield ('[%d]' % i, fields['value'][i])
+
+    def display_hint(self):
+        for name, ptr in MultiArrayListPrinter(self.val['entries']).slice():
+            if name == 'value':
+                return 'map'
+        return 'array'
+
+pp1 = gdb.printing.RegexpCollectionPrettyPrinter('Zig stage1 compiler')
+pp1.add_printer('Buf', '^Buf$', BufPrinter)
+pp1.add_printer('ZigList<char>', '^ZigList<char>$', BufPrinter)
+pp1.add_printer('ZigList', '^ZigList<.*>$', ZigListPrinter)
+gdb.printing.register_pretty_printer(gdb.current_objfile(), pp1)
 
-pp = gdb.printing.RegexpCollectionPrettyPrinter('Zig stage1 compiler')
-pp.add_printer('Buf', '^Buf$', BufPrinter)
-pp.add_printer('ZigList<char>', '^ZigList<char>$', BufPrinter)
-pp.add_printer('ZigList', '^ZigList<.*>$', ZigListPrinter)
-gdb.printing.register_pretty_printer(gdb.current_objfile(), pp)
+pplang = gdb.printing.RegexpCollectionPrettyPrinter('Zig language')
+pplang.add_printer('Slice', '^\[\]u8', SliceStringPrinter)
+pplang.add_printer('Slice', '^\[\]', SlicePrinter)
 
 ppstd = gdb.printing.RegexpCollectionPrettyPrinter('Zig standard library')
 ppstd.add_printer('ArrayList', r'^std\.array_list\.ArrayListAligned(Unmanaged)?\(.*\)$', ArrayListPrinter)
+ppstd.add_printer('MultiArrayList', r'^std\.multi_array_list\.MultiArrayList\(.*\)$', MultiArrayListPrinter)
 ppstd.add_printer('HashMap', r'^std\.hash_map\.HashMap(Unmanaged)?\(.*\)$', HashMapPrinter)
+ppstd.add_printer('ArrayHashMap', r'^std\.array_hash_map\.ArrayHashMap(Unmanaged)?\(.*\)$', ArrayHashMapPrinter)
 gdb.printing.register_pretty_printer(gdb.current_objfile(), ppstd)