"""
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)})"
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::Float8E4M3Type": '"f8E4M3"',
"mlir::Float8E4M3FNType": '"f8E4M3FN"',
"mlir::Float8E5M2FNUZType": '"f8E5M2FNUZ"',
"mlir::Float8E4M3FNUZType": '"f8E4M3FNUZ"',
"mlir::Float8E4M3B11FNUZType": '"f8E4M3B11FNUZ"',
"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 = {}
type_ids = target.FindGlobalVariables("id", lldb.UINT32_MAX)
for type_id in type_ids:
name = type_id.GetName()
match = re.search("^mlir::detail::TypeIDResolver<(.*), void>::id$", name)
if not match:
continue
type_name = match.group(1)
if not type_name.endswith(("Attr", "Loc", "Type")):
continue
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
self.resolved_typeids[type_id.AddressOf().GetValueAsUnsigned()] = type
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."""
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()
if num_bases == 0:
return typeName in ["mlir::Attribute", "mlir::Type", "mlir::Location"]
if typeName.startswith(("mlir::Attribute::AttrBase", "mlir::Type::TypeBase")):
return True
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."""
if is_derived_attribute_or_type(valobj.GetType(), internal_dict):
return valobj.GetType()
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
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
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()
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,
)
if index == self.impl_type.GetNumberOfFields():
return self.valobj.CreateValueFromExpression(
"[mnemonic]", self.get_mnemonic_string(impl_ptr)
)
field: lldb.SBTypeMember = self.impl_type.GetFieldAtIndex(index)
return impl_ptr.GetChildMemberWithName(field.GetName())
def get_mnemonic_string(self, impl_ptr: lldb.SBValue):
if isinstance(self.mnemonic, str):
return self.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."""
value = valobj.GetChildMemberWithName("value")
if value and value.summary:
return value.summary
mnemonic: lldb.SBValue = valobj.GetChildMemberWithName("[mnemonic]")
if not mnemonic.summary:
return ""
mnemonicStr = mnemonic.summary.strip('"')
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()}"
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
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()"
)
def is_op(sbtype: lldb.SBType, internal_dict):
"""Return if the given type is an operation."""
typeName = sbtype.GetName()
if sbtype.GetNumberOfDirectBaseClasses() == 0:
return typeName == "mlir::OpState"
if typeName == "mlir::Operation" or typeName.startswith("mlir::Op<"):
return True
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):
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 ""
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)
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
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()
)
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):
if self.kind == 7:
return 5
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
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())
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})"
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"
)
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")
if index == 0:
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):
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())
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()
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}"
type = valobj.GetChildMemberWithName("type")
if type.summary:
summary += f": {type.summary}"
return summary
def __lldb_init_module(debugger: lldb.SBDebugger, internal_dict):
cat: lldb.SBTypeCategory = debugger.CreateCategory("mlir")
cat.SetEnabled(True)
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"
),
)
cat.AddTypeSynthetic(
lldb.SBTypeNameSpecifier("mlir::Block", lldb.eFormatterMatchExact),
lldb.SBTypeSynthetic.CreateWithClassName(
"mlirDataFormatters.BlockSynthProvider"
),
)
cat.AddTypeSummary(
lldb.SBTypeNameSpecifier("mlir::NamedAttribute", lldb.eFormatterMatchExact),
lldb.SBTypeSummary.CreateWithSummaryString("${var.name%S} = ${var.value%S}"),
)
cat.AddTypeSummary(
lldb.SBTypeNameSpecifier("mlir::OperationName", lldb.eFormatterMatchExact),
lldb.SBTypeSummary.CreateWithSummaryString("${var.impl->name%S}"),
)
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"
),
)
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")
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")