DenseMap no longer use in-band sentinel keys. (#200595 and #201281). Update the GDB pretty printer and LLDB data formatters to test the used bit rather than comparing keys. GDB: advancePastEmptyBuckets relied on DenseMapInfo::getEmptyKey(), which could not be evaluated in GDB and so was disabled, leaving the printer to emit empty and erased buckets. It now walks bucket indices and skips any whose used bit is clear. LLDB: DenseMapSynthetic used a key-uniqueness heuristic to guess which buckets were live, which mishandled a lone erased bucket (hence the former tombstones=1 summary note). It now reads the used array directly, so erased entries are skipped exactly. NumTombstones no longer exists, so drop it from the summary. Written by Claude Opus 4.8 --------- Co-authored-by: Dave Lee <davelee.com@gmail.com>
597 lines
20 KiB
Python
597 lines
20 KiB
Python
"""
|
|
LLDB Formatters for LLVM data types.
|
|
|
|
Load into LLDB with 'command script import /path/to/lldbDataFormatters.py'
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Literal, Optional
|
|
import lldb
|
|
|
|
|
|
def __lldb_init_module(debugger: lldb.SBDebugger, internal_dict) -> None:
|
|
debugger.HandleCommand("type category define -e llvm -l c++")
|
|
debugger.HandleCommand(
|
|
"type synthetic add -w llvm "
|
|
f"-l {__name__}.SmallVectorSynthProvider "
|
|
'-x "^llvm::SmallVectorImpl<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type summary add -w llvm "
|
|
'-e -s "size=${svar%#}" '
|
|
'-x "^llvm::SmallVectorImpl<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type synthetic add -w llvm "
|
|
f"-l {__name__}.SmallVectorSynthProvider "
|
|
'-x "^llvm::SmallVector<.+,.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type summary add -w llvm "
|
|
'-e -s "size=${svar%#}" '
|
|
'-x "^llvm::SmallVector<.+,.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type synthetic add -w llvm "
|
|
f"-l {__name__}.ArrayRefSynthProvider "
|
|
'-x "^llvm::ArrayRef<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type summary add -w llvm "
|
|
'-e -s "size=${svar%#}" '
|
|
'-x "^llvm::ArrayRef<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type summary add -w llvm "
|
|
f"-F {__name__}.SmallStringSummaryProvider "
|
|
'-x "^llvm::SmallString<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type summary add -w llvm "
|
|
f"-F {__name__}.StringRefSummaryProvider "
|
|
"llvm::StringRef"
|
|
)
|
|
debugger.HandleCommand(
|
|
"type summary add -w llvm "
|
|
f"-F {__name__}.ConstStringSummaryProvider "
|
|
"lldb_private::ConstString"
|
|
)
|
|
debugger.HandleCommand(
|
|
"type synthetic add -w llvm "
|
|
f"-l {__name__}.PointerIntPairSynthProvider "
|
|
'-x "^llvm::PointerIntPair<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type synthetic add -w llvm "
|
|
f"-l {__name__}.PointerUnionSynthProvider "
|
|
'-x "^llvm::PointerUnion<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type summary add -w llvm "
|
|
f"-e -F {__name__}.DenseMapSummary "
|
|
'-x "^llvm::DenseMap<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type synthetic add -w llvm "
|
|
f"-l {__name__}.DenseMapSynthetic "
|
|
'-x "^llvm::DenseMap<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type synthetic add -w llvm "
|
|
f"-l {__name__}.DenseSetSynthetic "
|
|
'-x "^llvm::DenseSet<.+>$"'
|
|
)
|
|
|
|
debugger.HandleCommand(
|
|
"type synthetic add -w llvm "
|
|
f"-l {__name__}.ExpectedSynthetic "
|
|
'-x "^llvm::Expected<.+>$"'
|
|
)
|
|
debugger.HandleCommand(
|
|
"type summary add -w llvm "
|
|
f"-F {__name__}.SmallBitVectorSummary "
|
|
"llvm::SmallBitVector"
|
|
)
|
|
|
|
|
|
# Pretty printer for llvm::SmallVector/llvm::SmallVectorImpl
|
|
class SmallVectorSynthProvider:
|
|
valobj: lldb.SBValue
|
|
begin: lldb.SBValue
|
|
size: lldb.SBValue
|
|
data_type: lldb.SBType
|
|
type_size: int
|
|
|
|
def __init__(self, valobj, internal_dict) -> None:
|
|
self.valobj = valobj
|
|
self.update() # initialize this provider
|
|
|
|
def num_children(self) -> int:
|
|
return self.size.GetValueAsUnsigned(0)
|
|
|
|
def get_child_index(self, name):
|
|
try:
|
|
return int(name.lstrip("[").rstrip("]"))
|
|
except:
|
|
return -1
|
|
|
|
def get_child_at_index(self, index) -> Optional[lldb.SBValue]:
|
|
# Do bounds checking.
|
|
if index < 0:
|
|
return None
|
|
if index >= self.num_children():
|
|
return None
|
|
|
|
offset = index * self.type_size
|
|
return self.begin.CreateChildAtOffset(
|
|
"[" + str(index) + "]", offset, self.data_type
|
|
)
|
|
|
|
def update(self):
|
|
self.begin = self.valobj.GetChildMemberWithName("BeginX")
|
|
self.size = self.valobj.GetChildMemberWithName("Size")
|
|
the_type = self.valobj.GetType()
|
|
# If this is a reference type we have to dereference it to get to the
|
|
# template parameter.
|
|
if the_type.IsReferenceType():
|
|
the_type = the_type.GetDereferencedType()
|
|
|
|
if the_type.IsPointerType():
|
|
the_type = the_type.GetPointeeType()
|
|
|
|
self.data_type = the_type.GetTemplateArgumentType(0)
|
|
self.type_size = self.data_type.GetByteSize()
|
|
assert self.type_size != 0
|
|
|
|
|
|
class ArrayRefSynthProvider:
|
|
"""Provider for llvm::ArrayRef"""
|
|
|
|
valobj: lldb.SBValue
|
|
data: lldb.SBValue
|
|
length: int
|
|
data_type: lldb.SBType
|
|
type_size: int
|
|
|
|
def __init__(self, valobj: lldb.SBValue, internal_dict) -> None:
|
|
self.valobj = valobj
|
|
self.update() # initialize this provider
|
|
|
|
def num_children(self) -> int:
|
|
return self.length
|
|
|
|
def get_child_index(self, name):
|
|
try:
|
|
return int(name.lstrip("[").rstrip("]"))
|
|
except:
|
|
return -1
|
|
|
|
def get_child_at_index(self, index) -> Optional[lldb.SBValue]:
|
|
if index < 0 or index >= self.num_children():
|
|
return None
|
|
offset = index * self.type_size
|
|
return self.data.CreateChildAtOffset(
|
|
"[" + str(index) + "]", offset, self.data_type
|
|
)
|
|
|
|
def update(self):
|
|
self.data = self.valobj.GetChildMemberWithName("Data")
|
|
length_obj = self.valobj.GetChildMemberWithName("Length")
|
|
self.length = length_obj.GetValueAsUnsigned(0)
|
|
self.data_type = self.data.GetType().GetPointeeType()
|
|
self.type_size = self.data_type.GetByteSize()
|
|
assert self.type_size != 0
|
|
|
|
|
|
def SmallStringSummaryProvider(valobj: lldb.SBValue, internal_dict) -> str:
|
|
# The underlying SmallVector base class is the first child.
|
|
vector = valobj.GetChildAtIndex(0)
|
|
num_elements = vector.GetNumChildren()
|
|
res = '"'
|
|
for i in range(num_elements):
|
|
c = vector.GetChildAtIndex(i)
|
|
if c:
|
|
res += chr(c.GetValueAsUnsigned())
|
|
res += '"'
|
|
return res
|
|
|
|
|
|
def StringRefSummaryProvider(valobj: lldb.SBValue, internal_dict) -> str:
|
|
data_pointer = valobj.GetChildMemberWithName("Data")
|
|
length = valobj.GetChildMemberWithName("Length").unsigned
|
|
if data_pointer.unsigned == 0 or length == 0:
|
|
return '""'
|
|
|
|
data = data_pointer.deref
|
|
# StringRef may be uninitialized with length exceeding available memory,
|
|
# potentially causing bad_alloc exceptions. Limit the length to max string summary setting.
|
|
limit_obj = valobj.target.debugger.GetSetting("target.max-string-summary-length")
|
|
if limit_obj:
|
|
length = min(length, limit_obj.GetUnsignedIntegerValue())
|
|
# Get a char[N] type, from the underlying char type.
|
|
array_type = data.type.GetArrayType(length)
|
|
# Cast the char* string data to a char[N] array.
|
|
char_array = data.Cast(array_type)
|
|
# Use the builtin summary for its support of max-string-summary-length and
|
|
# display of non-printable bytes.
|
|
return char_array.summary
|
|
|
|
|
|
def ConstStringSummaryProvider(valobj: lldb.SBValue, internal_dict) -> str:
|
|
if valobj.GetNumChildren() == 1:
|
|
return valobj.GetChildAtIndex(0).GetSummary()
|
|
return ""
|
|
|
|
|
|
class PointerIntPairSynthProvider:
|
|
valobj: lldb.SBValue
|
|
byteorder: Literal["big", "little"]
|
|
ptr_size: int
|
|
value: lldb.SBValue
|
|
pointer_valobj: Optional[lldb.SBValue]
|
|
int_valobj: Optional[lldb.SBValue]
|
|
|
|
def __init__(self, valobj: lldb.SBValue, internal_dict) -> None:
|
|
self.valobj = valobj
|
|
self.update()
|
|
|
|
def num_children(self) -> int:
|
|
return 2
|
|
|
|
def get_child_index(self, name: str) -> int:
|
|
if name == "Pointer":
|
|
return 0
|
|
if name == "Int":
|
|
return 1
|
|
return -1
|
|
|
|
def _get_raw_value(self) -> Optional[bytes]:
|
|
data: lldb.SBData = self.value.GetData()
|
|
error = lldb.SBError()
|
|
raw_bytes = data.ReadRawData(error, 0, self.ptr_size)
|
|
if error.Fail():
|
|
return None
|
|
|
|
return raw_bytes
|
|
|
|
def _get_pointer(
|
|
self, pointer_bit_mask: int, pointer_ty: lldb.SBType
|
|
) -> Optional[lldb.SBValue]:
|
|
raw_bytes = self._get_raw_value()
|
|
if raw_bytes is None:
|
|
return
|
|
|
|
unmasked_pointer = int.from_bytes(raw_bytes, self.byteorder)
|
|
pointer_value = unmasked_pointer & pointer_bit_mask
|
|
|
|
data = lldb.SBData()
|
|
data.SetDataFromUInt64Array([pointer_value])
|
|
return self.valobj.CreateValueFromData("Pointer", data, pointer_ty)
|
|
|
|
def _get_int(
|
|
self, int_shift: int, int_mask: int, int_ty: lldb.SBType
|
|
) -> Optional[lldb.SBValue]:
|
|
raw_bytes = self._get_raw_value()
|
|
if raw_bytes is None:
|
|
return
|
|
|
|
unmasked_pointer = int.from_bytes(raw_bytes, self.byteorder)
|
|
int_value = (unmasked_pointer >> int_shift) & int_mask
|
|
|
|
data = lldb.SBData()
|
|
data.SetDataFromUInt64Array([int_value])
|
|
return self.valobj.CreateValueFromData("Int", data, int_ty)
|
|
|
|
def get_child_at_index(self, index) -> Optional[lldb.SBValue]:
|
|
if index == 0:
|
|
return self.pointer_valobj
|
|
if index == 1:
|
|
return self.int_valobj
|
|
return None
|
|
|
|
def update(self):
|
|
self.byteorder = (
|
|
"big"
|
|
if self.valobj.target.GetByteOrder() == lldb.eByteOrderBig
|
|
else "little"
|
|
)
|
|
self.ptr_size = self.valobj.target.GetAddressByteSize()
|
|
self.value: lldb.SBValue = self.valobj.GetChildMemberWithName("Value")
|
|
if not self.value:
|
|
return
|
|
|
|
valobj_type = self.valobj.GetType()
|
|
|
|
pointer_ty: lldb.SBType = valobj_type.GetTemplateArgumentType(0)
|
|
if not pointer_ty:
|
|
return
|
|
|
|
int_ty: lldb.SBType = valobj_type.GetTemplateArgumentType(2)
|
|
if not int_ty:
|
|
return
|
|
|
|
pointer_info = valobj_type.GetTemplateArgumentType(4)
|
|
if not pointer_info:
|
|
return
|
|
|
|
mask_and_shift_constants = pointer_info.FindDirectNestedType(
|
|
"MaskAndShiftConstants"
|
|
).GetEnumMembers()
|
|
|
|
# FIXME: SBAPI should provide a way to retrieve an enum member
|
|
# by name.
|
|
pointer_bit_mask: lldb.SBTypeEnumMember = (
|
|
mask_and_shift_constants.GetTypeEnumMemberAtIndex(0)
|
|
)
|
|
if pointer_bit_mask.name != "PointerBitMask":
|
|
return
|
|
|
|
int_shift: lldb.SBTypeEnumMember = (
|
|
mask_and_shift_constants.GetTypeEnumMemberAtIndex(1)
|
|
)
|
|
if int_shift.name != "IntShift":
|
|
return
|
|
|
|
int_mask: lldb.SBTypeEnumMember = (
|
|
mask_and_shift_constants.GetTypeEnumMemberAtIndex(2)
|
|
)
|
|
if int_mask.name != "IntMask":
|
|
return
|
|
|
|
self.pointer_valobj = self._get_pointer(
|
|
pointer_bit_mask.GetValueAsUnsigned(), pointer_ty
|
|
)
|
|
self.int_valobj = self._get_int(
|
|
int_shift.GetValueAsUnsigned(), int_mask.GetValueAsUnsigned(), int_ty
|
|
)
|
|
|
|
|
|
class PointerUnionSynthProvider:
|
|
valobj: lldb.SBValue
|
|
pointer_valobj: lldb.SBValue
|
|
|
|
def __init__(self, valobj: lldb.SBValue, internal_dict) -> None:
|
|
self.valobj = valobj
|
|
self.update()
|
|
|
|
def num_children(self) -> int:
|
|
return 1
|
|
|
|
def get_child_index(self, name: str) -> int:
|
|
if name == "Pointer":
|
|
return 0
|
|
return -1
|
|
|
|
def get_child_at_index(self, index: int) -> Optional[lldb.SBValue]:
|
|
if index != 0:
|
|
return None
|
|
|
|
return self.pointer_valobj
|
|
|
|
@staticmethod
|
|
def _get_low_bits_for_type(ty: lldb.SBType) -> int:
|
|
"""Return NumLowBitsAvailable for a pointer type (from pointee byte alignment)."""
|
|
pointee = ty.GetPointeeType()
|
|
if pointee.IsValid():
|
|
align = pointee.GetByteAlign()
|
|
return align.bit_length() - 1 if align > 0 else 0
|
|
return 0
|
|
|
|
def _make_pointer_value(self, name, pointer, ty):
|
|
"""Create an SBValue for a pointer, using proper byte order and address size."""
|
|
data = lldb.SBData.CreateDataFromUInt64Array(
|
|
self.valobj.target.GetByteOrder(),
|
|
self.valobj.process.GetAddressByteSize(),
|
|
[pointer],
|
|
)
|
|
return self.valobj.CreateValueFromData(name, data, ty)
|
|
|
|
def update(self):
|
|
self.pointer_valobj = None
|
|
|
|
valobj_type = self.valobj.GetType()
|
|
num_args = valobj_type.GetNumberOfTemplateArguments()
|
|
if num_args == 0:
|
|
return
|
|
|
|
# Read the raw uintptr_t from PunnedPointer<void*>.
|
|
val: lldb.SBValue = self.valobj.GetChildMemberWithName("Val")
|
|
if not val:
|
|
return
|
|
byteorder = (
|
|
"big"
|
|
if self.valobj.target.GetByteOrder() == lldb.eByteOrderBig
|
|
else "little"
|
|
)
|
|
raw_bytes = val.GetData().uint8s
|
|
if not raw_bytes:
|
|
return
|
|
raw_value = int.from_bytes(raw_bytes, byteorder)
|
|
|
|
# Compute tag from type alignments (fixed-width encoding).
|
|
tag_bits = (num_args - 1).bit_length()
|
|
min_low_bits = min(
|
|
self._get_low_bits_for_type(valobj_type.GetTemplateArgumentType(i))
|
|
for i in range(num_args)
|
|
)
|
|
if tag_bits > min_low_bits:
|
|
return self._set_raw_pointer(raw_value, min_low_bits)
|
|
tag_shift = min_low_bits - tag_bits
|
|
tag_mask = (1 << tag_bits) - 1
|
|
active_tag = (raw_value >> tag_shift) & tag_mask
|
|
|
|
if active_tag >= num_args:
|
|
return self._set_raw_pointer(raw_value, min_low_bits)
|
|
|
|
active_type: lldb.SBType = valobj_type.GetTemplateArgumentType(active_tag)
|
|
if not active_type:
|
|
return self._set_raw_pointer(raw_value, min_low_bits)
|
|
|
|
# Clear the active type's low bits to recover the pointer.
|
|
low_bits = self._get_low_bits_for_type(active_type)
|
|
pointer = raw_value & ~((1 << low_bits) - 1)
|
|
|
|
self.pointer_valobj = self._make_pointer_value("Pointer", pointer, active_type)
|
|
|
|
def _set_raw_pointer(self, raw_value, min_low_bits):
|
|
"""Fallback: strip tag bits and show as void* when active type is unknown."""
|
|
pointer = raw_value & ~((1 << min_low_bits) - 1)
|
|
void_ptr_ty = self.valobj.target.FindFirstType("void").GetPointerType()
|
|
if void_ptr_ty.IsValid():
|
|
self.pointer_valobj = self._make_pointer_value(
|
|
"Pointer", pointer, void_ptr_ty
|
|
)
|
|
|
|
|
|
def DenseMapSummary(valobj: lldb.SBValue, _) -> str:
|
|
raw_value = valobj.GetNonSyntheticValue()
|
|
num_entries = raw_value.GetChildMemberWithName("NumEntries").unsigned
|
|
return f"size={num_entries}"
|
|
|
|
|
|
class DenseMapSynthetic:
|
|
valobj: lldb.SBValue
|
|
|
|
# The indexes into `Buckets` that contain valid map entries.
|
|
child_buckets: list[int]
|
|
|
|
def __init__(self, valobj: lldb.SBValue, _) -> None:
|
|
self.valobj = valobj
|
|
|
|
def num_children(self) -> int:
|
|
return len(self.child_buckets)
|
|
|
|
def get_child_at_index(self, child_index: int) -> lldb.SBValue:
|
|
bucket_index = self.child_buckets[child_index]
|
|
entry = self.valobj.GetValueForExpressionPath(f".Buckets[{bucket_index}]")
|
|
|
|
# By default, DenseMap instances use DenseMapPair to hold key-value
|
|
# entries. When the entry is a DenseMapPair, unwrap it to expose the
|
|
# children as simple std::pair values.
|
|
#
|
|
# This entry type is customizable (a template parameter). For other
|
|
# types, expose the entry type as is.
|
|
if entry.type.name.startswith("llvm::detail::DenseMapPair<"):
|
|
entry = entry.GetChildAtIndex(0)
|
|
|
|
return entry.Clone(f"[{child_index}]")
|
|
|
|
def update(self):
|
|
self.child_buckets = []
|
|
|
|
num_entries = self.valobj.GetChildMemberWithName("NumEntries").unsigned
|
|
if num_entries == 0:
|
|
return
|
|
|
|
num_buckets = self.valobj.GetChildMemberWithName("NumBuckets").unsigned
|
|
used = self.valobj.GetChildMemberWithName("Used")
|
|
|
|
# Occupancy is tracked in a packed 1-bit-per-bucket "used" array of
|
|
# uint32_t words. A bucket holds a valid entry iff its bit is set;
|
|
# empty and erased buckets are clear. Read the whole array in one go
|
|
# rather than fetching each word with a separate expression path.
|
|
num_words = (num_buckets + 31) // 32
|
|
words = used.GetPointeeData(0, num_words).uint32s
|
|
for index in range(num_buckets):
|
|
if (words[index >> 5] >> (index & 31)) & 1:
|
|
self.child_buckets.append(index)
|
|
|
|
|
|
class DenseSetSynthetic:
|
|
valobj: lldb.SBValue
|
|
map: lldb.SBValue
|
|
|
|
def __init__(self, valobj: lldb.SBValue, _) -> None:
|
|
self.valobj = valobj
|
|
|
|
def num_children(self) -> int:
|
|
return self.map.num_children
|
|
|
|
def get_child_at_index(self, idx: int) -> lldb.SBValue:
|
|
map_entry = self.map.child[idx]
|
|
set_entry = map_entry.GetChildAtIndex(0)
|
|
return set_entry.Clone(f"[{idx}]")
|
|
|
|
def update(self):
|
|
raw_map = self.valobj.GetChildMemberWithName("TheMap")
|
|
self.map = raw_map.GetSyntheticValue()
|
|
|
|
|
|
class ExpectedSynthetic:
|
|
# The llvm::Expected<T> value.
|
|
expected: lldb.SBValue
|
|
# The stored success value or error value.
|
|
stored_value: lldb.SBValue
|
|
|
|
def __init__(self, valobj: lldb.SBValue, _) -> None:
|
|
self.expected = valobj
|
|
|
|
def update(self) -> None:
|
|
has_error = self.expected.GetChildMemberWithName("HasError").unsigned
|
|
if not has_error:
|
|
name = "value"
|
|
member = "TStorage"
|
|
else:
|
|
name = "error"
|
|
member = "ErrorStorage"
|
|
# Anonymous union.
|
|
union = self.expected.child[0]
|
|
storage = union.GetChildMemberWithName(member)
|
|
# For reference types, storage is std::reference_wrapper<T>, so we
|
|
# unwrap to get T. For non-reference types, storage is T directly.
|
|
# Use GetCanonicalType() to resolve the typedef to the underlying type.
|
|
canonical_type_name = storage.type.GetCanonicalType().name
|
|
if "reference_wrapper<" in canonical_type_name:
|
|
# reference_wrapper<T> stores a T* pointer to the referenced value.
|
|
# Get the first child (the pointer member) and dereference it.
|
|
ptr_member = storage.GetChildAtIndex(0)
|
|
if ptr_member and ptr_member.IsValid() and ptr_member.type.IsPointerType():
|
|
self.stored_value = ptr_member.Dereference().Clone(name)
|
|
else:
|
|
# Fallback: just use storage as-is.
|
|
self.stored_value = storage.Clone(name)
|
|
else:
|
|
self.stored_value = storage.Clone(name)
|
|
|
|
def num_children(self) -> int:
|
|
return 1
|
|
|
|
def get_child_index(self, name: str) -> int:
|
|
if name == self.stored_value.name:
|
|
return 0
|
|
# Allow dereferencing for values, not errors.
|
|
if name == "$$dereference$$" and self.stored_value.name == "value":
|
|
return 0
|
|
return -1
|
|
|
|
def get_child_at_index(self, idx: int) -> lldb.SBValue:
|
|
if idx == 0:
|
|
return self.stored_value
|
|
return lldb.SBValue()
|
|
|
|
|
|
def SmallBitVectorSummary(valobj, _) -> str:
|
|
underlyingValue = valobj.GetChildMemberWithName("X").unsigned
|
|
numBaseBits = valobj.target.addr_size * 8
|
|
smallNumRawBits = numBaseBits - 1
|
|
smallNumSizeBits = None
|
|
if numBaseBits == 32:
|
|
smallNumSizeBits = 5
|
|
elif numBaseBits == 64:
|
|
smallNumSizeBits = 6
|
|
else:
|
|
smallNumSizeBits = smallNumRawBits
|
|
smallNumDataBits = smallNumRawBits - smallNumSizeBits
|
|
|
|
# If our underlying value is not small, print we can not dump large values.
|
|
isSmallMask = 1
|
|
if underlyingValue & isSmallMask == 0:
|
|
return "<can not read large SmallBitVector>"
|
|
|
|
smallRawBits = underlyingValue >> 1
|
|
smallSize = smallRawBits >> smallNumDataBits
|
|
bits = smallRawBits & ((1 << (smallSize + 1)) - 1)
|
|
# format `bits` in binary (b), with 0 padding, of width `smallSize`, and left aligned (>)
|
|
return f"[{bits:0>{smallSize}b}]"
|