diff --git a/debug/lldb/eigenlldb.py b/debug/lldb/eigenlldb.py new file mode 100644 index 000000000..d9b5d06e7 --- /dev/null +++ b/debug/lldb/eigenlldb.py @@ -0,0 +1,234 @@ +# -*- coding: utf-8 -*- +# This file is part of Eigen, a lightweight C++ template library +# for linear algebra. +# +# Copyright (C) 2021 Huang, Zhaoquan +# +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +# Pretty printers for Eigen::Matrix to use with LLDB debugger +# +# Usage: +# 1. Add the following line (change it according to the path to this file) +# to the file ~/.lldbinit (create one if it doesn't exist): +# `command script import /path/to/eigenlldb.py` +# 2. Inspect the variables in LLDB command line +# `frame variable` + +import lldb +from typing import List +import bisect + + +def __lldb_init_module(debugger, internal_dict): + debugger.HandleCommand("type synthetic add -x Eigen::Matrix<.*> --python-class eigenlldb.EigenMatrixChildProvider") + debugger.HandleCommand( + "type synthetic add -x Eigen::SparseMatrix<.*> --python-class eigenlldb.EigenSparseMatrixChildProvider") + + +class EigenMatrixChildProvider: + _valobj: lldb.SBValue + _scalar_type: lldb.SBType + _scalar_size: int + _rows_compile_time: int + _cols_compile_time: int + _row_major: bool + _fixed_storage: bool + + def __init__(self, valobj, internal_dict): + self._valobj = valobj + valtype = valobj.GetType().GetCanonicalType() + + scalar_type = valtype.GetTemplateArgumentType(0) + if not scalar_type.IsValid(): + # In the case that scalar_type is invalid on LLDB 9.0 on Windows with CLion + storage = valobj.GetChildMemberWithName("m_storage") + data = storage.GetChildMemberWithName("m_data") + data_type = data.GetType() + if data_type.IsPointerType(): + scalar_type = data.GetType().GetPointeeType() + else: + scalar_type = data.GetChildMemberWithName("array").GetType().GetArrayElementType() + self._scalar_type = scalar_type + self._scalar_size = self._scalar_type.GetByteSize() + + name = valtype.GetName() + template_begin = name.find("<") + template_end = name.find(">") + template_args = name[(template_begin + 1):template_end].split(",") + self._rows_compile_time = int(template_args[1]) + self._cols_compile_time = int(template_args[2]) + self._row_major = (int(template_args[3]) & 1) != 0 + + max_rows = int(template_args[4]) + max_cols = int(template_args[5]) + self._fixed_storage = (max_rows != -1 and max_cols != -1) + + def num_children(self): + return self._cols() * self._rows() + + def get_child_index(self, name): + pass + + def get_child_at_index(self, index): + storage = self._valobj.GetChildMemberWithName("m_storage") + data = storage.GetChildMemberWithName("m_data") + offset = self._scalar_size * index + + if self._row_major: + row = index // self._cols() + col = index % self._cols() + else: + row = index % self._rows() + col = index // self._rows() + if self._fixed_storage: + data = data.GetChildMemberWithName("array") + if self._cols() == 1: + name = '[{}]'.format(row) + elif self._rows() == 1: + name = '[{}]'.format(col) + else: + name = '[{},{}]'.format(row, col) + return data.CreateChildAtOffset( + name, offset, self._scalar_type + ) + + def _cols(self): + if self._cols_compile_time == -1: + storage = self._valobj.GetChildMemberWithName("m_storage") + cols = storage.GetChildMemberWithName("m_cols") + return cols.GetValueAsUnsigned() + else: + return self._cols_compile_time + + def _rows(self): + if self._rows_compile_time == -1: + storage = self._valobj.GetChildMemberWithName("m_storage") + rows = storage.GetChildMemberWithName("m_rows") + return rows.GetValueAsUnsigned() + else: + return self._rows_compile_time + + +class EigenSparseMatrixChildProvider: + _valobj: lldb.SBValue + _scalar_type: lldb.SBType + _scalar_size: int + _index_type: lldb.SBType + _index_size: int + _row_major: bool + + _outer_size: int + _nnz: int + _values: lldb.SBValue + _inner_indices: lldb.SBValue + _outer_starts: lldb.SBValue + _inner_nnzs: lldb.SBValue + _compressed: bool + + # Index of the first synthetic child under each outer index + _child_indices: List[int] + + def __init__(self, valobj, internal_dict): + self._valobj = valobj + valtype = valobj.GetType().GetCanonicalType() + scalar_type = valtype.GetTemplateArgumentType(0) + if not scalar_type.IsValid(): + # In the case that scalar_type is invalid on LLDB 9.0 on Windows with CLion + data = valobj.GetChildMemberWithName("m_data") + values = data.GetChildMemberWithName("m_values") + scalar_type = values.GetType().GetPointeeType() + self._scalar_type = scalar_type + self._scalar_size = scalar_type.GetByteSize() + + index_type = valtype.GetTemplateArgumentType(2) + if not index_type.IsValid(): + # In the case that scalar_type is invalid on LLDB 9.0 on Windows with CLion + outer_starts = valobj.GetChildMemberWithName("m_outerIndex") + index_type = outer_starts.GetType().GetPointeeType() + self._index_type = index_type + self._index_size = index_type.GetByteSize() + + name = valtype.GetName() + template_begin = name.find("<") + template_end = name.find(">") + template_args = name[(template_begin + 1):template_end].split(",") + self._row_major = (int(template_args[1]) & 1) != 0 + + def num_children(self): + return self._nnz + 2 + + def get_child_index(self, name): + pass + + def get_child_at_index(self, index): + if index == 0: + name = "rows" if self._row_major else "cols" + return self._valobj.GetChildMemberWithName("m_outerSize") \ + .CreateChildAtOffset(name, 0, self._index_type) + elif index == 1: + name = "cols" if self._row_major else "rows" + return self._valobj.GetChildMemberWithName("m_innerSize") \ + .CreateChildAtOffset(name, 0, self._index_type) + else: + index = index - 2 + outer_index = bisect.bisect_right(self._child_indices, index) - 1 + total_nnzs = self._child_indices[outer_index] + if self._compressed: + item_index = index + inner_index = self._inner_indices \ + .CreateChildAtOffset("", item_index * self._index_size, self._index_type) \ + .GetValueAsUnsigned() + return self._values \ + .CreateChildAtOffset(self._child_name(outer_index, inner_index), + item_index * self._scalar_size, + self._scalar_type) + else: + index_begin = self._outer_starts \ + .CreateChildAtOffset("", outer_index * self._index_size, self._index_type) \ + .GetValueAsUnsigned() + item_index = index - total_nnzs + index_begin + inner_index = self._inner_indices \ + .CreateChildAtOffset("", item_index * self._index_size, self._index_type) \ + .GetValueAsUnsigned() + return self._values \ + .CreateChildAtOffset(self._child_name(outer_index, inner_index), + item_index * self._scalar_size, + self._scalar_type) + + def update(self): + valobj = self._valobj + self._outer_size = valobj.GetChildMemberWithName("m_outerSize").GetValueAsUnsigned() + data = valobj.GetChildMemberWithName("m_data") + self._values = data.GetChildMemberWithName("m_values") + self._inner_indices = data.GetChildMemberWithName("m_indices") + self._outer_starts = valobj.GetChildMemberWithName("m_outerIndex") + self._inner_nnzs = valobj.GetChildMemberWithName("m_innerNonZeros") + + self._compressed = self._inner_nnzs.GetValueAsUnsigned() == 0 + + total_nnzs = 0 + child_indices = [0] + for outer_index in range(self._outer_size): + if self._compressed: + index_end = self._outer_starts \ + .CreateChildAtOffset("", (outer_index + 1) * self._index_size, self._index_type) \ + .GetValueAsUnsigned() + total_nnzs = index_end + child_indices.append(total_nnzs) + else: + nnzs = self._inner_nnzs \ + .CreateChildAtOffset("", outer_index * self._index_size, self._index_type) \ + .GetValueAsUnsigned() + total_nnzs = total_nnzs + nnzs + child_indices.append(total_nnzs) + self._child_indices = child_indices + self._nnz = total_nnzs + + def _child_name(self, outer_index, inner_index): + if self._row_major: + return "[{0},{1}]".format(outer_index, inner_index) + else: + return "[{1},{0}]".format(outer_index, inner_index)