From b04634a0cdedd39872407f2940e6fb51c6d3308f Mon Sep 17 00:00:00 2001 From: Geoff Romer Date: Wed, 10 Jun 2026 11:19:07 -0700 Subject: [PATCH] Implement a custom LLDB formatter for Carbon IDs (#7333) This removes the inheritance noise, and adds label prefixes for consistency with the raw textual format. Before: ``` > p function_info.first_owning_decl_id (Carbon::SemIR::InstId) { Carbon::IdBase = { Carbon::AnyIdBase = (index = 0x50000023) } } > p function_info.call_param_ranges.implicit_end_ (Carbon::SemIR::CallParamIndex) { Carbon::IndexBase = { Carbon::IdBase = { Carbon::AnyIdBase = (index = 0x00000001) } } } ``` After: ``` > p function_info.first_owning_decl_id (Carbon::SemIR::InstId) inst50000023 > p function_info.call_param_ranges.implicit_end_ (Carbon::SemIR::CallParamIndex) call_param1 ``` The benefit compounds when printing aggregates (which often lack `Dump` support or omit information from it). For example, this change reduces `p function_info` from 129 lines to just 31 (including reducing the `call_param_ranges` field from 32 lines to just 1): ``` > p function_info (Carbon::SemIR::Function) { Carbon::SemIR::EntityWithParamsBase = { name_id = name1 parent_scope_id = name_scope50000002 generic_id = generic first_param_node_id = nodeB last_param_node_id = node12 pattern_block_id = inst_block5000000E implicit_param_patterns_id = inst_block5000000A param_patterns_id = inst_block0 is_extern = false extern_library_id = library_name non_owning_decl_id = inst first_owning_decl_id = inst50000023 definition_id = inst } Carbon::SemIR::FunctionFields = { call_param_patterns_id = inst_block5000000C call_params_id = inst_block5000000D call_param_ranges = (implicit_end_ = call_param1, explicit_end_ = call_param1, return_end_ = call_param1) return_type_inst_id = inst return_form_inst_id = inst return_pattern_id = inst special_function_kind = None virtual_modifier = None virtual_index = 0xffffffff evaluation_mode = None self_param_id = inst50000020 special_function_kind_data = any_raw body_block_ids = size=0 {} } } ``` --- scripts/lldbinit.py | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/scripts/lldbinit.py b/scripts/lldbinit.py index 7e7fbdc0fe8c..296ac7ca8142 100644 --- a/scripts/lldbinit.py +++ b/scripts/lldbinit.py @@ -149,5 +149,54 @@ Example usage: print_dump(context, expr) +# Returns true if sbtype is a Carbon ID type (i.e. is derived from +# `Carbon::AnyIdBase`). +def is_carbon_id(sbtype: lldb.SBType, internal_dict: Any) -> bool: + for base in sbtype.get_bases_array(): + if "Carbon::AnyIdBase" in base.GetName(): + return True + if is_carbon_id(base.type, internal_dict): + return True + return False + + +# Formats a Carbon ID value to roughly match its format in raw SemIR, without +# calling any user code. +def format_carbon_id( + valobj: lldb.SBValue, internal_dict: Any, options: Any +) -> str: + # TODO: It would be safer and more efficient to get these by traversing the + # member graph using the Python API, rather than by evaluating C++ + # expressions. However, that doesn't seem to work in this case + # (`SBTypeStaticField.GetConstantValue` seems to be broken), and even if it + # did, it would be fairly verbose and probably more brittle. + label = valobj.EvaluateExpression("Label.Data") + label_size = valobj.EvaluateExpression("Label.Length") + if label and label_size: + # Clamp the read size, to limit the impact of memory corruption. + # 40 chars should be enough for any legitimate ID label. + read_size = min(label_size.GetValueAsUnsigned(), 40) + label_data = valobj.process.ReadMemory( + label.GetValueAsAddress(), read_size, lldb.SBError() + ) + label_str = label_data.decode("utf-8") + else: + label_str = "" + + index_int = valobj.GetChildMemberWithName("index").GetValueAsUnsigned() + if index_int == 0xFFFFFFFF: + # We can't handle all the special cases that ID printing does, but we + # can at least handle the most common one. + index_str = "" + else: + index_str = f"{index_int:X}" + + return f"{label_str}{index_str}" + + def __lldb_init_module(debugger: Any, internal_dict: Any) -> None: RunCommand("command script add -f lldbinit.cmd_dump dump") + RunCommand( + "type summary add --python-function lldbinit.format_carbon_id" + + " --recognizer-function lldbinit.is_carbon_id" + )