llvm/mlir/utils/lldb-scripts/mlirDataFormatters.py

"""
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::Float6E2M3FNType": '"f6E2M3FN"',
    "mlir::Float6E3M2FNType": '"f6E3M2FN"',
    "mlir::Float8E5M2Type": '"f8E5M2"',
    "mlir::Float8E4M3Type": '"f8E4M3"',
    "mlir::Float8E4M3FNType": '"f8E4M3FN"',
    "mlir::Float8E5M2FNUZType": '"f8E5M2FNUZ"',
    "mlir::Float8E4M3FNUZType": '"f8E4M3FNUZ"',
    "mlir::Float8E4M3B11FNUZType": '"f8E4M3B11FNUZ"',
    "mlir::Float8E3M4Type": '"f8E3M4"',
    "mlir::BFloat16Type": '"bf16"',
    "mlir::Float16Type": '"f16"',
    "mlir::FloatTF32Type": '"tf32"',
    "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:
            self.impl_type = None
            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 different 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")