diff --git a/cross-project-tests/debuginfo-tests/llvm-prettyprinters/gdb/llvm-support.cpp b/cross-project-tests/debuginfo-tests/llvm-prettyprinters/gdb/llvm-support.cpp index 737d9bd38c27..1beef3960b72 100644 --- a/cross-project-tests/debuginfo-tests/llvm-prettyprinters/gdb/llvm-support.cpp +++ b/cross-project-tests/debuginfo-tests/llvm-prettyprinters/gdb/llvm-support.cpp @@ -33,8 +33,8 @@ llvm::PointerIntPair PointerIntPair(IntPtr, 1); struct alignas(8) Z {}; llvm::PointerUnion PointerUnion(IntPtr); -// No members which instantiate PointerUnionUIntTraits (e.g. get()) -// are called, and this instance will therefore be raw-printed. +// The PunnedPointer-based formatter can decode all PointerUnion instances +// from type alignments, regardless of member template instantiation. llvm::PointerUnion RawPrintingPointerUnion(nullptr); using IlistTag = llvm::ilist_tag; diff --git a/cross-project-tests/debuginfo-tests/llvm-prettyprinters/gdb/llvm-support.gdb b/cross-project-tests/debuginfo-tests/llvm-prettyprinters/gdb/llvm-support.gdb index de6738e53bb5..654b1180d3ea 100644 --- a/cross-project-tests/debuginfo-tests/llvm-prettyprinters/gdb/llvm-support.gdb +++ b/cross-project-tests/debuginfo-tests/llvm-prettyprinters/gdb/llvm-support.gdb @@ -44,7 +44,7 @@ p PointerIntPair # CHECK: Containing int * = {pointer = 0xabc} p PointerUnion -# CHECK: PointerUnionMembers, +# CHECK: Containing Z * = {pointer = 0x0} p RawPrintingPointerUnion # Switch to print pretty adds newlines to the following statements. diff --git a/cross-project-tests/debuginfo-tests/llvm-prettyprinters/lldb/pointer-union.cpp b/cross-project-tests/debuginfo-tests/llvm-prettyprinters/lldb/pointer-union.cpp index 361bd82c1676..be8695473b8b 100644 --- a/cross-project-tests/debuginfo-tests/llvm-prettyprinters/lldb/pointer-union.cpp +++ b/cross-project-tests/debuginfo-tests/llvm-prettyprinters/lldb/pointer-union.cpp @@ -13,19 +13,20 @@ struct DerivedWithVirtual : public HasVirtual { void DerivedWithVirtual::func() {} +struct alignas(8) Z {}; +struct Derived : public Z {}; + int main() { int a = 5; float f = 4.0; - struct alignas(8) Z {}; Z z; - - struct Derived : public Z {}; Derived derived; DerivedWithVirtual dv; llvm::PointerUnion z_float(&f); llvm::PointerUnion raw_z_float(nullptr); + llvm::PointerUnion null_float(static_cast(nullptr)); llvm::PointerUnion long_int_float(&a); llvm::PointerUnion z_only(&z); @@ -42,4 +43,11 @@ int main() { llvm::PointerUnion virtual_float(&dv); puts("Break here"); + + // Function-local types stress template_argument lookup in debuggers. + struct alignas(8) Local {}; + Local local; + llvm::PointerUnion local_float(&local); + + puts("Break here"); } diff --git a/cross-project-tests/debuginfo-tests/llvm-prettyprinters/lldb/pointer-union.test b/cross-project-tests/debuginfo-tests/llvm-prettyprinters/lldb/pointer-union.test index dc5d7bded592..1be963f5b74f 100644 --- a/cross-project-tests/debuginfo-tests/llvm-prettyprinters/lldb/pointer-union.test +++ b/cross-project-tests/debuginfo-tests/llvm-prettyprinters/lldb/pointer-union.test @@ -18,6 +18,7 @@ p &derived p &dv v -T z_float v -T raw_z_float +v -T null_float v -T long_int_float v -T z_only v -T union_int_pair @@ -34,6 +35,11 @@ continue v -T virtual_float p virtual_float.Pointer +continue + +p &local +v -T local_float + #--- checks # CHECK: (lldb) p &f # CHECK-NEXT: (float *) [[PTR_F:0x[0-9a-zA-Z]+]] @@ -60,6 +66,13 @@ p virtual_float.Pointer # CHECK-NEXT: (Z *) Pointer = nullptr # CHECK-NEXT: } +# null float* has non-zero raw_value (tag=1), pointer component=0. +# LLDB renders this as an address literal rather than nullptr. +# CHECK: (lldb) v -T null_float +# CHECK-NEXT: (llvm::PointerUnion) null_float = { +# CHECK-NEXT: (float *) Pointer = 0x{{0+}} +# CHECK-NEXT: } + # CHECK: (lldb) v -T long_int_float # CHECK-NEXT: (llvm::PointerUnion) long_int_float = { # CHECK-NEXT: (int *) Pointer = [[PTR_A]] @@ -101,3 +114,11 @@ p virtual_float.Pointer # CHECK: (lldb) p virtual_float.Pointer # CHECK-NEXT: (DerivedWithVirtual *) [[PTR_DV]] + +# CHECK: (lldb) p &local +# CHECK-NEXT: (Local *) [[PTR_LOCAL:0x[0-9a-zA-Z]+]] + +# CHECK: (lldb) v -T local_float +# CHECK-NEXT: (llvm::PointerUnion) local_float = { +# CHECK-NEXT: (Local *) Pointer = [[PTR_LOCAL]] +# CHECK-NEXT: } diff --git a/llvm/utils/gdb-scripts/prettyprinters.py b/llvm/utils/gdb-scripts/prettyprinters.py index 9a856974f9f8..0048b0c80cec 100644 --- a/llvm/utils/gdb-scripts/prettyprinters.py +++ b/llvm/utils/gdb-scripts/prettyprinters.py @@ -443,14 +443,66 @@ class PointerUnionPrinter: return "Containing %s" % self.pointer.type +def _make_pointer_union_raw_fallback(raw_value, min_low_bits): + """Strip tag bits and return as void* when active type is unknown.""" + pointer = raw_value & ~((1 << min_low_bits) - 1) + void_ptr = gdb.lookup_type("void").pointer() + return PointerUnionPrinter(gdb.Value(pointer).cast(void_ptr)) + + def make_pointer_union_printer(val): """Factory for an llvm::PointerUnion printer.""" try: - pointer, value = get_pointer_int_pair(val["Val"]) - except gdb.error: - return None # If PointerIntPair cannot be analyzed, print as raw value. - pointer_type = val.type.template_argument(int(value)) - return PointerUnionPrinter(pointer.cast(pointer_type)) + raw_value = int( + val["Val"]["Data"] + .address.cast(gdb.lookup_type("uintptr_t").pointer()) + .dereference() + ) + + # Collect template argument types. + # Distinguish truncation (RuntimeError — type resolution failure) from + # normal end-of-args (gdb.error — index out of range). + arg_types = [] + truncated = False + while True: + try: + arg_types.append(val.type.template_argument(len(arg_types))) + except RuntimeError: + truncated = True + break + except gdb.error: + break + if not arg_types: + return None + + # Compute tag from type alignments (fixed-width encoding). + num_args = len(arg_types) + min_low_bits = min( + (a.bit_length() - 1 if a > 0 else 0) + for a in (t.target().alignof for t in arg_types) + ) + + # If template args are truncated, we can't reliably decode the tag. + # Fall back to showing the raw pointer with tag bits stripped. + if truncated: + return _make_pointer_union_raw_fallback(raw_value, min_low_bits) + + tag_bits = (num_args - 1).bit_length() + if tag_bits > min_low_bits: + return _make_pointer_union_raw_fallback(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 _make_pointer_union_raw_fallback(raw_value, min_low_bits) + + pointer_type = arg_types[active_tag] + align = pointer_type.target().alignof + low_bits = align.bit_length() - 1 if align > 0 else 0 + pointer = raw_value & ~((1 << low_bits) - 1) + except (gdb.error, RuntimeError, IndexError): + return None + return PointerUnionPrinter(gdb.Value(pointer).cast(pointer_type)) class IlistNodePrinter: diff --git a/llvm/utils/lldbDataFormatters.py b/llvm/utils/lldbDataFormatters.py index 9830dfb03c18..c986f6695f88 100644 --- a/llvm/utils/lldbDataFormatters.py +++ b/llvm/utils/lldbDataFormatters.py @@ -370,35 +370,79 @@ class PointerUnionSynthProvider: 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): - pointer_int_pair: lldb.SBValue = self.valobj.GetChildMemberWithName( - "Val" - ).GetSyntheticValue() + self.pointer_valobj = None - if not pointer_int_pair: + valobj_type = self.valobj.GetType() + num_args = valobj_type.GetNumberOfTemplateArguments() + if num_args == 0: return - pointer: lldb.SBValue = pointer_int_pair.GetChildAtIndex(0) - if not pointer: + # Read the raw uintptr_t from PunnedPointer. + val: lldb.SBValue = self.valobj.GetChildMemberWithName("Val") + if not val: return - - active_tag: lldb.SBValue = pointer_int_pair.GetChildAtIndex(1) - if not active_tag: - return - - # Index into the parameter pack of llvm::PointerUnion to find the active type. - active_type: lldb.SBType = self.valobj.GetType().GetTemplateArgumentType( - active_tag.GetValueAsUnsigned() + 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 + return self._set_raw_pointer(raw_value, min_low_bits) - data = lldb.SBData() - data.SetDataFromUInt64Array([pointer.GetValueAsUnsigned()]) + # 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.valobj.CreateValueFromData( - "Pointer", data, active_type - ) + 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: