[Mlir-commits] [mlir] 62fec08 - [mlir] Add LLDB visualizers for MLIR constructs

River Riddle llvmlistbot at llvm.org
Sun Dec 11 22:45:58 PST 2022


Author: River Riddle
Date: 2022-12-11T22:45:34-08:00
New Revision: 62fec084d67af5b3d55b09271a5b9aab604698f5

URL: https://github.com/llvm/llvm-project/commit/62fec084d67af5b3d55b09271a5b9aab604698f5
DIFF: https://github.com/llvm/llvm-project/commit/62fec084d67af5b3d55b09271a5b9aab604698f5.diff

LOG: [mlir] Add LLDB visualizers for MLIR constructs

This commit adds a significant amount of visualizers attempting
to cover the majority of our visualization needs. It covers:

* Operations/OperationName/Ops/OpInterfaces
* Attributes/Types/Attr|TypeInterfaces/NamedAttribute
* Blocks/Regions
* Various range types (e.g. ValueRange/TypeRange)
* Values/BlockArguments/OpResults

This does require an NFC change to interfaces to rename
the concept field to avoid clash with the base class. It
also requires exposing a few method to the debugger
to help resolve information that is non-trivial to reconstruct.
These methods are re-exported using a debug_Blah naming
scheme to avoid messing with hot methods.

Note that this makes use of the new callback feature in lldb-16
(currently trunk) that allows for providing visualizers based on
a dynamic callback, instead of just the typename. It requires
a very new lldb, but allows for providing good default visualization
for all attributes/operations/types out of the box.

Differential Revision: https://reviews.llvm.org/D139602

Added: 
    mlir/utils/lldb-scripts/mlirDataFormatters.py

Modified: 
    mlir/include/mlir/IR/Operation.h
    mlir/include/mlir/IR/Value.h
    mlir/include/mlir/Support/InterfaceSupport.h

Removed: 
    


################################################################################
diff  --git a/mlir/include/mlir/IR/Operation.h b/mlir/include/mlir/IR/Operation.h
index 6c8f2470e66f2..61f1c028ba32a 100644
--- a/mlir/include/mlir/IR/Operation.h
+++ b/mlir/include/mlir/IR/Operation.h
@@ -798,6 +798,19 @@ class alignas(8) Operation final
   /// model.
   Block *getParent() const { return block; }
 
+  /// Expose a few methods explicitly for the debugger to call for
+  /// visualization.
+#ifndef NDEBUG
+  LLVM_DUMP_METHOD operand_range debug_getOperands() { return getOperands(); }
+  LLVM_DUMP_METHOD result_range debug_getResults() { return getResults(); }
+  LLVM_DUMP_METHOD SuccessorRange debug_getSuccessors() {
+    return getSuccessors();
+  }
+  LLVM_DUMP_METHOD MutableArrayRef<Region> debug_getRegions() {
+    return getRegions();
+  }
+#endif
+
   /// The operation block that contains this operation.
   Block *block = nullptr;
 

diff  --git a/mlir/include/mlir/IR/Value.h b/mlir/include/mlir/IR/Value.h
index 3557d2d0e0d5d..bc51d59c745a2 100644
--- a/mlir/include/mlir/IR/Value.h
+++ b/mlir/include/mlir/IR/Value.h
@@ -71,6 +71,14 @@ class alignas(8) ValueImpl : public IRObjectWithUseList<OpOperand> {
 protected:
   ValueImpl(Type type, Kind kind) : typeAndKind(type, kind) {}
 
+  /// Expose a few methods explicitly for the debugger to call for
+  /// visualization.
+#ifndef NDEBUG
+  LLVM_DUMP_METHOD Type debug_getType() const { return getType(); }
+  LLVM_DUMP_METHOD Kind debug_getKind() const { return getKind(); }
+
+#endif
+
   /// The type of this result and the kind.
   llvm::PointerIntPair<Type, 3, Kind> typeAndKind;
 };

diff  --git a/mlir/include/mlir/Support/InterfaceSupport.h b/mlir/include/mlir/Support/InterfaceSupport.h
index a612ce8a8e87b..d8f63e08ea1dc 100644
--- a/mlir/include/mlir/Support/InterfaceSupport.h
+++ b/mlir/include/mlir/Support/InterfaceSupport.h
@@ -92,22 +92,26 @@ class Interface : public BaseType {
 
   /// Construct an interface from an instance of the value type.
   Interface(ValueT t = ValueT())
-      : BaseType(t), impl(t ? ConcreteType::getInterfaceFor(t) : nullptr) {
-    assert((!t || impl) && "expected value to provide interface instance");
+      : BaseType(t),
+        conceptImpl(t ? ConcreteType::getInterfaceFor(t) : nullptr) {
+    assert((!t || conceptImpl) &&
+           "expected value to provide interface instance");
   }
-  Interface(std::nullptr_t) : BaseType(ValueT()), impl(nullptr) {}
+  Interface(std::nullptr_t) : BaseType(ValueT()), conceptImpl(nullptr) {}
 
   /// Construct an interface instance from a type that implements this
   /// interface's trait.
   template <typename T,
             std::enable_if_t<std::is_base_of<Trait<T>, T>::value> * = nullptr>
   Interface(T t)
-      : BaseType(t), impl(t ? ConcreteType::getInterfaceFor(t) : nullptr) {
-    assert((!t || impl) && "expected value to provide interface instance");
+      : BaseType(t),
+        conceptImpl(t ? ConcreteType::getInterfaceFor(t) : nullptr) {
+    assert((!t || conceptImpl) &&
+           "expected value to provide interface instance");
   }
 
   /// Constructor for DenseMapInfo's empty key and tombstone key.
-  Interface(ValueT t, std::nullptr_t) : BaseType(t), impl(nullptr) {}
+  Interface(ValueT t, std::nullptr_t) : BaseType(t), conceptImpl(nullptr) {}
 
   /// Support 'classof' by checking if the given object defines the concrete
   /// interface.
@@ -118,12 +122,12 @@ class Interface : public BaseType {
 
 protected:
   /// Get the raw concept in the correct derived concept type.
-  const Concept *getImpl() const { return impl; }
-  Concept *getImpl() { return impl; }
+  const Concept *getImpl() const { return conceptImpl; }
+  Concept *getImpl() { return conceptImpl; }
 
 private:
   /// A pointer to the impl concept object.
-  Concept *impl;
+  Concept *conceptImpl;
 };
 
 //===----------------------------------------------------------------------===//

diff  --git a/mlir/utils/lldb-scripts/mlirDataFormatters.py b/mlir/utils/lldb-scripts/mlirDataFormatters.py
new file mode 100644
index 0000000000000..f04516b181112
--- /dev/null
+++ b/mlir/utils/lldb-scripts/mlirDataFormatters.py
@@ -0,0 +1,830 @@
+"""
+LLDB Formatters for MLIR data types.
+
+Load into LLDB with 'command script import /path/to/mlirDataFormatters.py'
+"""
+
+import re
+import lldb
+
+
+def get_expression_path(val: lldb.SBValue):
+    """Compute the expression path for the given value."""
+
+    stream = lldb.SBStream()
+    if not val.GetExpressionPath(stream):
+        return None
+    return stream.GetData()
+
+
+def build_ptr_str_from_addr(addrValue: lldb.SBValue, type: lldb.SBType):
+    """Build a string that computes a pointer using the given address value and type."""
+
+    if type.is_reference:
+        type = type.GetDereferencedType()
+    if not type.is_pointer:
+        type = type.GetPointerType()
+    return f"(({type}){addrValue.GetData().GetUnsignedInt64(lldb.SBError(), 0)})"
+
+
+# ===----------------------------------------------------------------------=== #
+# Attributes and Types
+# ===----------------------------------------------------------------------=== #
+
+# This variable defines various mnemonic strings for use by the builtin
+# dialect attributes and types, which often have special formatting within
+# the parser/printer.
+builtin_attr_type_mnemonics = {
+    "mlir::AffineMapAttr": '"affine_map<...>"',
+    "mlir::ArrayAttr": '"[...]"',
+    "mlir::DenseArray": '"array<...>"',
+    "mlir::DenseResourceElementsAttr": '"dense_resource<...>"',
+    "mlir::DictionaryAttr": '"{...}"',
+    "mlir::IntegerAttr": '"float"',
+    "mlir::IntegerAttr": '"integer"',
+    "mlir::IntegerSetAttr": '"affine_set<...>"',
+    "mlir::SparseElementsAttr": '"sparse<...>"',
+    "mlir::StringAttr": '""...""',
+    "mlir::StridedLayout": '"strided_layout"',
+    "mlir::UnitAttr": '"unit"',
+    "mlir::CallSiteLoc": '"loc(callsite(...))"',
+    "mlir::FusedLoc": '"loc(fused<...>[...])"',
+    "mlir::UnknownLoc": '"loc(unknown)"',
+    "mlir::Float8E5M2Type": '"f8E5M2"',
+    "mlir::Float8E4M3FNType": '"f8E4M3FN"',
+    "mlir::BFloat16Type": '"bf16"',
+    "mlir::Float16Type": '"f16"',
+    "mlir::Float32Type": '"f32"',
+    "mlir::Float64Type": '"f64"',
+    "mlir::Float80Type": '"f80"',
+    "mlir::Float128Type": '"f128"',
+    "mlir::FunctionType": '"(...) -> (...)"',
+    "mlir::IndexType": '"index"',
+    "mlir::IntegerType": '"iN"',
+    "mlir::NoneType": '"none"',
+    "mlir::TupleType": '"tuple<...>"',
+    "mlir::MemRefType": '"memref<...>"',
+    "mlir::UnrankedMemRef": '"memref<...>"',
+    "mlir::UnrankedTensorType": '"tensor<...>"',
+    "mlir::RankedTensorType": '"tensor<...>"',
+    "mlir::VectorType": '"vector<...>"',
+}
+
+
+class ComputedTypeIDMap:
+    """Compute a map of type ids to derived attributes, types, and locations.
+
+    This is necessary for determining the C++ type when holding a base class,
+    where we really only have access to dynamic information.
+    """
+
+    def __init__(self, target: lldb.SBTarget, internal_dict: dict):
+        self.resolved_typeids = {}
+
+        # Find all of the `id` variables, which are the name of TypeID variables
+        # defined within the TypeIDResolver.
+        type_ids = target.FindGlobalVariables("id", lldb.UINT32_MAX)
+        for type_id in type_ids:
+            # Strip out any matches that didn't come from a TypeID resolver. This
+            # also lets us extract the derived type name.
+            name = type_id.GetName()
+            match = re.search("^mlir::detail::TypeIDResolver<(.*), void>::id$", name)
+            if not match:
+                continue
+            type_name = match.group(1)
+
+            # Filter out types that we don't care about.
+            if not type_name.endswith(("Attr", "Loc", "Type")):
+                continue
+
+            # Find the LLDB type for the derived type.
+            type = None
+            for typeIt in target.FindTypes(type_name):
+                if not typeIt or not typeIt.IsValid():
+                    continue
+                type = typeIt
+                break
+            if not type or not type.IsValid():
+                continue
+
+            # Map the raw address of the type id variable to the LLDB type.
+            self.resolved_typeids[type_id.AddressOf().GetValueAsUnsigned()] = type
+
+    # Resolve the type for the given TypeID address.
+    def resolve_type(self, typeIdAddr: lldb.SBValue):
+        try:
+            return self.resolved_typeids[typeIdAddr.GetValueAsUnsigned()]
+        except KeyError:
+            return None
+
+
+def is_derived_attribute_or_type(sbtype: lldb.SBType, internal_dict):
+    """Return if the given type is a derived attribute or type."""
+
+    # We only expect an AttrBase/TypeBase base class.
+    if sbtype.num_bases != 1:
+        return False
+    base_name = sbtype.GetDirectBaseClassAtIndex(0).GetName()
+    return base_name.startswith(("mlir::Attribute::AttrBase", "mlir::Type::TypeBase"))
+
+
+def get_typeid_map(target: lldb.SBTarget, internal_dict: dict):
+    """Get or construct a TypeID map for the given target."""
+
+    if "typeIdMap" not in internal_dict:
+        internal_dict["typeIdMap"] = ComputedTypeIDMap(target, internal_dict)
+    return internal_dict["typeIdMap"]
+
+
+def is_attribute_or_type(sbtype: lldb.SBType, internal_dict):
+    """Return if the given type is an attribute or type."""
+
+    num_bases = sbtype.GetNumberOfDirectBaseClasses()
+    typeName = sbtype.GetName()
+
+    # We bottom out at Attribute/Type/Location.
+    if num_bases == 0:
+        return typeName in ["mlir::Attribute", "mlir::Type", "mlir::Location"]
+
+    # Check the easy cases of AttrBase/TypeBase.
+    if typeName.startswith(("mlir::Attribute::AttrBase", "mlir::Type::TypeBase")):
+        return True
+
+    # Otherwise, recurse into the base class.
+    return is_attribute_or_type(
+        sbtype.GetDirectBaseClassAtIndex(0).GetType(), internal_dict
+    )
+
+
+def resolve_attr_type_from_value(
+    valobj: lldb.SBValue, abstractVal: lldb.SBValue, internal_dict
+):
+    """Resolve the derived C++ type of an Attribute/Type value."""
+
+    # Derived attribute/types already have the desired type.
+    if is_derived_attribute_or_type(valobj.GetType(), internal_dict):
+        return valobj.GetType()
+
+    # Otherwise, we need to resolve the ImplTy from the TypeID. This is
+    # done dynamically, because we don't use C++ RTTI of any kind.
+    typeIdMap = get_typeid_map(valobj.GetTarget(), internal_dict)
+    return typeIdMap.resolve_type(
+        abstractVal.GetChildMemberWithName("typeID").GetChildMemberWithName("storage")
+    )
+
+
+class AttrTypeSynthProvider:
+    """Define an LLDB synthetic children provider for Attributes and Types."""
+
+    def __init__(self, valobj: lldb.SBValue, internal_dict):
+        self.valobj = valobj
+
+        # Grab the impl variable, which if this is a Location needs to be
+        # resolved through the LocationAttr impl variable.
+        impl: lldb.SBValue = self.valobj.GetChildMemberWithName("impl")
+        if self.valobj.GetTypeName() == "mlir::Location":
+            impl = impl.GetChildMemberWithName("impl")
+        self.abstractVal = impl.GetChildMemberWithName("abstractType")
+        if not self.abstractVal.IsValid():
+            self.abstractVal = impl.GetChildMemberWithName("abstractAttribute")
+
+        self.type = resolve_attr_type_from_value(
+            valobj, self.abstractVal, internal_dict
+        )
+        if not self.type:
+            return
+
+        # Grab the ImplTy from the resolved type. This is the 3rd template
+        # argument of the base class.
+        self.impl_type = (
+            self.type.GetDirectBaseClassAtIndex(0).GetType().GetTemplateArgumentType(2)
+        )
+        self.impl_pointer_ty = self.impl_type.GetPointerType()
+        self.num_fields = self.impl_type.GetNumberOfFields()
+
+        # Optionally add a mnemonic field.
+        type_name = self.type.GetName()
+        if type_name in builtin_attr_type_mnemonics:
+            self.mnemonic = builtin_attr_type_mnemonics[type_name]
+        elif type_name.startswith("mlir::Dense"):
+            self.mnemonic = "dense<...>"
+        else:
+            self.mnemonic = self.valobj.CreateValueFromExpression(
+                "mnemonic", f"(llvm::StringRef){type_name}::getMnemonic()"
+            )
+            if not self.mnemonic.summary:
+                self.mnemonic = None
+        if self.mnemonic:
+            self.num_fields += 1
+
+    def num_children(self):
+        if not self.impl_type:
+            return 0
+        return self.num_fields
+
+    def get_child_index(self, name):
+        if not self.impl_type:
+            return None
+        if self.mnemonic and name == "[mnemonic]":
+            return self.impl_type.GetNumberOfFields()
+        for i in range(self.impl_type.GetNumberOfFields()):
+            if self.impl_type.GetFieldAtIndex(i).GetName() == name:
+                return i
+        return None
+
+    def get_child_at_index(self, index):
+        if not self.impl_type or index >= self.num_fields:
+            return None
+
+        impl: lldb.SBValue = self.valobj.GetChildMemberWithName("impl")
+        impl_ptr: lldb.SBValue = self.valobj.CreateValueFromData(
+            build_ptr_str_from_addr(impl, self.impl_pointer_ty),
+            impl.GetData(),
+            self.impl_pointer_ty,
+        )
+
+        # Check for the mnemonic field.
+        if index == self.impl_type.GetNumberOfFields():
+            return self.valobj.CreateValueFromExpression(
+                "[mnemonic]", self.get_mnemonic_string(impl_ptr)
+            )
+
+        # Otherwise, we expect the index to be a field.
+        field: lldb.SBTypeMember = self.impl_type.GetFieldAtIndex(index)
+
+        # Build the field access by resolving through the impl variable.
+        return impl_ptr.GetChildMemberWithName(field.GetName())
+
+    def get_mnemonic_string(self, impl_ptr: lldb.SBValue):
+        if isinstance(self.mnemonic, str):
+            return self.mnemonic
+
+        # If we don't already have the mnemonic in string form, compute
+        # it from the dialect name and the mnemonic.
+        dialect_name = self.abstractVal.GetChildMemberWithName(
+            "dialect"
+        ).GetChildMemberWithName("name")
+        self.mnemonic = f'{dialect_name.summary}"."{self.mnemonic.summary}'
+        return self.mnemonic
+
+
+def AttrTypeSummaryProvider(valobj: lldb.SBValue, internal_dict):
+    """Define an LLDB summary provider for Attributes and Types."""
+
+    # Check for a value field.
+    value = valobj.GetChildMemberWithName("value")
+    if value and value.summary:
+        return value.summary
+
+    # Otherwise, try the mnemoic.
+    mnemonic: lldb.SBValue = valobj.GetChildMemberWithName("[mnemonic]")
+    if not mnemonic.summary:
+        return ""
+    mnemonicStr = mnemonic.summary.strip('"')
+
+    # Handle a few extremely common builtin attributes/types.
+    ## IntegerType
+    if mnemonicStr == "iN":
+        signedness = valobj.GetChildMemberWithName("signedness").GetValueAsUnsigned()
+        prefix = "i"
+        if signedness == 1:
+            prefix = "si"
+        elif signedness == 2:
+            prefix = "ui"
+        return f"{prefix}{valobj.GetChildMemberWithName('width').GetValueAsUnsigned()}"
+    ## IntegerAttr
+    if mnemonicStr == "integer":
+        value = valobj.GetChildMemberWithName("value")
+        bitwidth = value.GetChildMemberWithName("BitWidth").GetValueAsUnsigned()
+        if bitwidth <= 64:
+            intVal = (
+                value.GetChildMemberWithName("U")
+                .GetChildMemberWithName("VAL")
+                .GetValueAsUnsigned()
+            )
+
+            if bitwidth == 1:
+                return "true" if intVal else "false"
+            return f"{intVal} : i{bitwidth}"
+
+    return mnemonicStr
+
+
+# ===----------------------------------------------------------------------=== #
+# mlir::Block
+# ===----------------------------------------------------------------------=== #
+
+
+class BlockSynthProvider:
+    """Define an LLDB synthetic children provider for Blocks."""
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+
+    def num_children(self):
+        return 3
+
+    def get_child_index(self, name):
+        if name == "parent":
+            return 0
+        if name == "operations":
+            return 1
+        if name == "arguments":
+            return 2
+        return None
+
+    def get_child_at_index(self, index):
+        if index >= 3:
+            return None
+        if index == 1:
+            return self.valobj.GetChildMemberWithName("operations")
+        if index == 2:
+            return self.valobj.GetChildMemberWithName("arguments")
+
+        expr_path = build_ptr_str_from_addr(self.valobj, self.valobj.GetType())
+        return self.valobj.CreateValueFromExpression(
+            "parent", f"{expr_path}->getParent()"
+        )
+
+
+# ===----------------------------------------------------------------------=== #
+# mlir::Operation
+# ===----------------------------------------------------------------------=== #
+
+
+def is_op(sbtype: lldb.SBType, internal_dict):
+    """Return if the given type is an operation."""
+
+    # Bottom out at OpState/Op.
+    typeName = sbtype.GetName()
+    if sbtype.GetNumberOfDirectBaseClasses() == 0:
+        return typeName == "mlir::OpState"
+    if typeName == "mlir::Operation" or typeName.startswith("mlir::Op<"):
+        return True
+
+    # Otherwise, recurse into the base class.
+    return is_op(sbtype.GetDirectBaseClassAtIndex(0).GetType(), internal_dict)
+
+
+class OperationSynthProvider:
+    """Define an LLDB synthetic children provider for Operations."""
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+        self.fields = []
+        self.update()
+
+    def num_children(self):
+        return len(self.fields)
+
+    def get_child_index(self, name):
+        try:
+            return self.fields.index(name)
+        except ValueError:
+            return None
+
+    def get_child_at_index(self, index):
+        if index >= len(self.fields):
+            return None
+        name = self.fields[index]
+        if name == "name":
+            return self.opobj.GetChildMemberWithName("name")
+        if name == "parent":
+            return self.opobj.GetChildMemberWithName("block").Clone("parent")
+        if name == "location":
+            return self.opobj.GetChildMemberWithName("location")
+        if name == "attributes":
+            return self.opobj.GetChildMemberWithName("attrs")
+
+        expr_path = build_ptr_str_from_addr(self.opobj, self.opobj.GetType())
+        if name == "operands":
+            return self.opobj.CreateValueFromExpression(
+                "operands", f"{expr_path}->debug_getOperands()"
+            )
+        if name == "results":
+            return self.opobj.CreateValueFromExpression(
+                "results", f"{expr_path}->debug_getResults()"
+            )
+        if name == "successors":
+            return self.opobj.CreateValueFromExpression(
+                "successors", f"{expr_path}->debug_getSuccessors()"
+            )
+        if name == "regions":
+            return self.opobj.CreateValueFromExpression(
+                "regions", f"{expr_path}->debug_getRegions()"
+            )
+        return None
+
+    def update(self):
+        # If this is a derived operation, we need to resolve through the
+        # state field.
+        self.opobj = self.valobj
+        if "mlir::Operation" not in self.valobj.GetTypeName():
+            self.opobj = self.valobj.GetChildMemberWithName("state")
+
+        self.fields = ["parent", "name", "location", "attributes"]
+        if (
+            self.opobj.GetChildMemberWithName("hasOperandStorage").GetValueAsUnsigned(0)
+            != 0
+        ):
+            self.fields.append("operands")
+        if self.opobj.GetChildMemberWithName("numResults").GetValueAsUnsigned(0) != 0:
+            self.fields.append("results")
+        if self.opobj.GetChildMemberWithName("numSuccs").GetValueAsUnsigned(0) != 0:
+            self.fields.append("successors")
+        if self.opobj.GetChildMemberWithName("numRegions").GetValueAsUnsigned(0) != 0:
+            self.fields.append("regions")
+
+
+def OperationSummaryProvider(valobj: lldb.SBValue, internal_dict):
+    """Define an LLDB summary provider for Operations."""
+
+    name = valobj.GetChildMemberWithName("name")
+    if name and name.summary:
+        return name.summary
+    return ""
+
+
+# ===----------------------------------------------------------------------=== #
+# Ranges
+# ===----------------------------------------------------------------------=== #
+
+
+class DirectRangeSynthProvider:
+    """Define an LLDB synthetic children provider for direct ranges, i.e. those
+    with a base pointer that points to the type of element we want to display.
+    """
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+        self.update()
+
+    def num_children(self):
+        return self.length
+
+    def get_child_index(self, name):
+        try:
+            return int(name.lstrip("[").rstrip("]"))
+        except:
+            return None
+
+    def get_child_at_index(self, index):
+        if index >= self.num_children():
+            return None
+        offset = index * self.type_size
+        return self.data.CreateChildAtOffset(f"[{index}]", offset, self.data_type)
+
+    def update(self):
+        length_obj = self.valobj.GetChildMemberWithName("count")
+        self.length = length_obj.GetValueAsUnsigned(0)
+
+        self.data = self.valobj.GetChildMemberWithName("base")
+        self.data_type = self.data.GetType().GetPointeeType()
+        self.type_size = self.data_type.GetByteSize()
+        assert self.type_size != 0
+
+
+class InDirectRangeSynthProvider:
+    """Define an LLDB synthetic children provider for ranges
+    that transform the underlying base pointer, e.g. to convert
+    it to a 
diff erent type depending on various characteristics
+    (e.g. mlir::ValueRange).
+    """
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+        self.update()
+
+    def num_children(self):
+        return self.length
+
+    def get_child_index(self, name):
+        try:
+            return int(name.lstrip("[").rstrip("]"))
+        except:
+            return None
+
+    def get_child_at_index(self, index):
+        if index >= self.num_children():
+            return None
+        expr_path = get_expression_path(self.valobj)
+        return self.valobj.CreateValueFromExpression(
+            f"[{index}]", f"{expr_path}[{index}]"
+        )
+
+    def update(self):
+        length_obj = self.valobj.GetChildMemberWithName("count")
+        self.length = length_obj.GetValueAsUnsigned(0)
+
+
+class IPListRangeSynthProvider:
+    """Define an LLDB synthetic children provider for an IPList.
+    """
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+        self.update()
+
+    def num_children(self):
+        sentinel = self.valobj.GetChildMemberWithName("Sentinel")
+        sentinel_addr = sentinel.AddressOf().GetValueAsUnsigned(0)
+
+        # Iterate the next pointers looking for the sentinel.
+        count = 0
+        current = sentinel.GetChildMemberWithName("Next")
+        while current.GetValueAsUnsigned(0) != sentinel_addr:
+            current = current.GetChildMemberWithName("Next")
+            count += 1
+
+        return count
+
+    def get_child_index(self, name):
+        try:
+            return int(name.lstrip("[").rstrip("]"))
+        except:
+            return None
+
+    def get_child_at_index(self, index):
+        if index >= self.num_children():
+            return None
+
+        # Start from the sentinel and grab the next pointer.
+        value: lldb.SBValue = self.valobj.GetChildMemberWithName("Sentinel")
+        it = 0
+        while it <= index:
+            value = value.GetChildMemberWithName("Next")
+            it += 1
+
+        return value.CreateValueFromExpression(
+            f"[{index}]",
+            f"(({self.value_type})({value.GetTypeName()}){value.GetValueAsUnsigned()})",
+        )
+
+    def update(self):
+        self.value_type = (
+            self.valobj.GetType().GetTemplateArgumentType(0).GetPointerType()
+        )
+
+
+# ===----------------------------------------------------------------------=== #
+# mlir::Value
+# ===----------------------------------------------------------------------=== #
+
+
+class ValueSynthProvider:
+    """Define an LLDB synthetic children provider for Values.
+    """
+
+    def __init__(self, valobj, internal_dict):
+        self.valobj = valobj
+        self.update()
+
+    def num_children(self):
+        # 7: BlockArgument:
+        #  index, type, owner, firstUse, location
+        if self.kind == 7:
+            return 5
+
+        # 0-6: OpResult:
+        #  index, type, owner, firstUse
+        return 4
+
+    def get_child_index(self, name):
+        if name == "index":
+            return 0
+        if name == "type":
+            return 1
+        if name == "owner":
+            return 2
+        if name == "firstUse":
+            return 3
+        if name == "location":
+            return 4
+        return None
+
+    def get_child_at_index(self, index):
+        if index >= self.num_children():
+            return None
+
+        # Check if the current value is already an Impl struct.
+        if self.valobj.GetTypeName().endswith("Impl"):
+            impl_ptr_str = build_ptr_str_from_addr(
+                self.valobj.AddressOf(), self.valobj.GetType().GetPointerType()
+            )
+        else:
+            impl = self.valobj.GetChildMemberWithName("impl")
+            impl_ptr_str = build_ptr_str_from_addr(impl, impl.GetType())
+
+        # Cast to the derived Impl type.
+        if self.kind == 7:
+            derived_impl_str = f"((mlir::detail::BlockArgumentImpl *){impl_ptr_str})"
+        elif self.kind == 6:
+            derived_impl_str = f"((mlir::detail::OutOfLineOpResult *){impl_ptr_str})"
+        else:
+            derived_impl_str = f"((mlir::detail::InlineOpResult *){impl_ptr_str})"
+
+        # Handle the shared fields when possible.
+        if index == 1:
+            return self.valobj.CreateValueFromExpression(
+                "type", f"{derived_impl_str}->debug_getType()"
+            )
+        if index == 3:
+            return self.valobj.CreateValueFromExpression(
+                "firstUse", f"{derived_impl_str}->firstUse"
+            )
+
+        # Handle Block argument children.
+        if self.kind == 7:
+            impl = self.valobj.CreateValueFromExpression("impl", derived_impl_str)
+            if index == 0:
+                return impl.GetChildMemberWithName("index")
+            if index == 2:
+                return impl.GetChildMemberWithName("owner")
+            if index == 4:
+                return impl.GetChildMemberWithName("loc")
+
+        # Handle OpResult children.
+        if index == 0:
+            # Handle the out of line case.
+            if self.kind == 6:
+                return self.valobj.CreateValueFromExpression(
+                    "index", f"{derived_impl_str}->outOfLineIndex + 6"
+                )
+            return self.valobj.CreateValueFromExpression("index", f"{self.kind}")
+        if index == 2:
+            return self.valobj.CreateValueFromExpression(
+                "owner", f"{derived_impl_str}->getOwner()"
+            )
+        return None
+
+    def update(self):
+        # Check if the current value is already an Impl struct.
+        if self.valobj.GetTypeName().endswith("Impl"):
+            impl_ptr_str = build_ptr_str_from_addr(
+                self.valobj, self.valobj.GetType().GetPointerType()
+            )
+        else:
+            impl = self.valobj.GetChildMemberWithName("impl")
+            impl_ptr_str = build_ptr_str_from_addr(impl, impl.GetType())
+
+        # Compute the kind of value we are dealing with.
+        self.kind = self.valobj.CreateValueFromExpression(
+            "kind", f"{impl_ptr_str}->debug_getKind()"
+        ).GetValueAsUnsigned()
+
+
+def ValueSummaryProvider(valobj: lldb.SBValue, internal_dict):
+    """Define an LLDB summary provider for Values.
+    """
+
+    index = valobj.GetChildMemberWithName("index").GetValueAsUnsigned()
+    # Check if this is a block argument or not (block arguments have locations).
+    if valobj.GetChildMemberWithName("location").IsValid():
+        summary = f"Block Argument {index}"
+    else:
+        owner_name = (
+            valobj.GetChildMemberWithName("owner")
+            .GetChildMemberWithName("name")
+            .summary
+        )
+        summary = f"{owner_name} Result {index}"
+
+    # Grab the type to help form the summary.
+    type = valobj.GetChildMemberWithName("type")
+    if type.summary:
+        summary += f": {type.summary}"
+
+    return summary
+
+
+# ===----------------------------------------------------------------------=== #
+# Initialization
+# ===----------------------------------------------------------------------=== #
+
+
+def __lldb_init_module(debugger: lldb.SBDebugger, internal_dict):
+    cat: lldb.SBTypeCategory = debugger.CreateCategory("mlir")
+    cat.SetEnabled(True)
+
+    # Attributes and Types
+    cat.AddTypeSummary(
+        lldb.SBTypeNameSpecifier(
+            "mlirDataFormatters.is_attribute_or_type", lldb.eFormatterMatchCallback
+        ),
+        lldb.SBTypeSummary.CreateWithFunctionName(
+            "mlirDataFormatters.AttrTypeSummaryProvider"
+        ),
+    )
+    cat.AddTypeSynthetic(
+        lldb.SBTypeNameSpecifier(
+            "mlirDataFormatters.is_attribute_or_type", lldb.eFormatterMatchCallback
+        ),
+        lldb.SBTypeSynthetic.CreateWithClassName(
+            "mlirDataFormatters.AttrTypeSynthProvider"
+        ),
+    )
+
+    # Operation
+    cat.AddTypeSynthetic(
+        lldb.SBTypeNameSpecifier("mlir::Block", lldb.eFormatterMatchExact),
+        lldb.SBTypeSynthetic.CreateWithClassName(
+            "mlirDataFormatters.BlockSynthProvider"
+        ),
+    )
+
+    # NamedAttribute
+    cat.AddTypeSummary(
+        lldb.SBTypeNameSpecifier("mlir::NamedAttribute", lldb.eFormatterMatchExact),
+        lldb.SBTypeSummary.CreateWithSummaryString("${var.name%S} = ${var.value%S}"),
+    )
+
+    # OperationName
+    cat.AddTypeSummary(
+        lldb.SBTypeNameSpecifier("mlir::OperationName", lldb.eFormatterMatchExact),
+        lldb.SBTypeSummary.CreateWithSummaryString("${var.impl->name%S}"),
+    )
+
+    # Operation
+    cat.AddTypeSummary(
+        lldb.SBTypeNameSpecifier(
+            "mlirDataFormatters.is_op", lldb.eFormatterMatchCallback
+        ),
+        lldb.SBTypeSummary.CreateWithFunctionName(
+            "mlirDataFormatters.OperationSummaryProvider"
+        ),
+    )
+    cat.AddTypeSynthetic(
+        lldb.SBTypeNameSpecifier(
+            "mlirDataFormatters.is_op", lldb.eFormatterMatchCallback
+        ),
+        lldb.SBTypeSynthetic.CreateWithClassName(
+            "mlirDataFormatters.OperationSynthProvider"
+        ),
+    )
+
+    # Ranges
+    def add_direct_range_summary_and_synth(name):
+        cat.AddTypeSummary(
+            lldb.SBTypeNameSpecifier(name, lldb.eFormatterMatchExact),
+            lldb.SBTypeSummary.CreateWithSummaryString("size=${svar%#}"),
+        )
+        cat.AddTypeSynthetic(
+            lldb.SBTypeNameSpecifier(name, lldb.eFormatterMatchExact),
+            lldb.SBTypeSynthetic.CreateWithClassName(
+                "mlirDataFormatters.DirectRangeSynthProvider"
+            ),
+        )
+
+    def add_indirect_range_summary_and_synth(name):
+        cat.AddTypeSummary(
+            lldb.SBTypeNameSpecifier(name, lldb.eFormatterMatchExact),
+            lldb.SBTypeSummary.CreateWithSummaryString("size=${svar%#}"),
+        )
+        cat.AddTypeSynthetic(
+            lldb.SBTypeNameSpecifier(name, lldb.eFormatterMatchExact),
+            lldb.SBTypeSynthetic.CreateWithClassName(
+                "mlirDataFormatters.InDirectRangeSynthProvider"
+            ),
+        )
+
+    def add_iplist_range_summary_and_synth(name):
+        cat.AddTypeSummary(
+            lldb.SBTypeNameSpecifier(name, lldb.eFormatterMatchExact),
+            lldb.SBTypeSummary.CreateWithSummaryString("size=${svar%#}"),
+        )
+        cat.AddTypeSynthetic(
+            lldb.SBTypeNameSpecifier(name, lldb.eFormatterMatchExact),
+            lldb.SBTypeSynthetic.CreateWithClassName(
+                "mlirDataFormatters.IPListRangeSynthProvider"
+            ),
+        )
+
+    add_direct_range_summary_and_synth("mlir::Operation::operand_range")
+    add_direct_range_summary_and_synth("mlir::OperandRange")
+    add_direct_range_summary_and_synth("mlir::Operation::result_range")
+    add_direct_range_summary_and_synth("mlir::ResultRange")
+    add_direct_range_summary_and_synth("mlir::SuccessorRange")
+    add_indirect_range_summary_and_synth("mlir::ValueRange")
+    add_indirect_range_summary_and_synth("mlir::TypeRange")
+    add_iplist_range_summary_and_synth("mlir::Block::OpListType")
+    add_iplist_range_summary_and_synth("mlir::Region::BlockListType")
+
+    # Values
+    def add_value_summary_and_synth(name):
+        cat.AddTypeSummary(
+            lldb.SBTypeNameSpecifier(name, lldb.eFormatterMatchExact),
+            lldb.SBTypeSummary.CreateWithFunctionName(
+                "mlirDataFormatters.ValueSummaryProvider"
+            ),
+        )
+        cat.AddTypeSynthetic(
+            lldb.SBTypeNameSpecifier(name, lldb.eFormatterMatchExact),
+            lldb.SBTypeSynthetic.CreateWithClassName(
+                "mlirDataFormatters.ValueSynthProvider"
+            ),
+        )
+
+    add_value_summary_and_synth("mlir::BlockArgument")
+    add_value_summary_and_synth("mlir::Value")
+    add_value_summary_and_synth("mlir::OpResult")
+    add_value_summary_and_synth("mlir::detail::OpResultImpl")


        


More information about the Mlir-commits mailing list