diff --git a/libcudacxx/share/libcudacxx/gdb/__init__.py b/libcudacxx/share/libcudacxx/gdb/__init__.py index d9f950d66c4d..acb68e151ae3 100644 --- a/libcudacxx/share/libcudacxx/gdb/__init__.py +++ b/libcudacxx/share/libcudacxx/gdb/__init__.py @@ -18,6 +18,7 @@ if _SCRIPT_DIRECTORY not in sys.path: sys.path.insert(0, _SCRIPT_DIRECTORY) +import annotated_ptr # noqa: E402 import atomic # noqa: E402 import buffer # noqa: E402 import complex # noqa: E402 @@ -37,6 +38,7 @@ _PRINTERS = ( memory_resource, atomic, + annotated_ptr, buffer, std_array, complex, diff --git a/libcudacxx/share/libcudacxx/gdb/annotated_ptr.py b/libcudacxx/share/libcudacxx/gdb/annotated_ptr.py new file mode 100644 index 000000000000..dd9d6941416e --- /dev/null +++ b/libcudacxx/share/libcudacxx/gdb/annotated_ptr.py @@ -0,0 +1,78 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +"""GDB pretty printer for cuda::annotated_ptr.""" + +from __future__ import annotations + +from types import ModuleType + +import cccl_common + +import gdb +import gdb.printing + + +def _is_annotated_ptr(value_type: gdb.Type) -> bool: + """Check if a GDB type represents cuda::annotated_ptr.""" + value_type = cccl_common.canonical_type(value_type) + type_name = cccl_common.public_type_name(value_type) + template_name = cccl_common.template_name(value_type) + return ( + type_name.startswith("cuda::") + and template_name.rsplit("::", 1)[-1] == "annotated_ptr" + ) + + +class AnnotatedPtrPrinter: + """Expose cuda::annotated_ptr type and pointer value to GDB.""" + + def __init__(self, value: gdb.Value) -> None: + # Strip reference to access members + self.value = cccl_common.strip_reference_value(value) + # Store canonical type for type name display + self.canonical_type_obj = cccl_common.canonical_type(self.value.type) + # Extract base type name without template arguments to avoid duplication + self.type_name = cccl_common.template_name( + cccl_common.public_type_name(self.canonical_type_obj) + ) + + def _template_arguments(self) -> tuple[str, str]: + """Return the pointee and property type names.""" + pointee_type = self.canonical_type_obj.template_argument(0) + property_type = self.canonical_type_obj.template_argument(1) + return ( + cccl_common.public_type_name(pointee_type), + cccl_common.public_type_name(property_type), + ) + + def _pointer_display(self) -> str: + """Return the stored pointer, spelling a null pointer as nullptr.""" + ptr_value = int(self.value["__repr"]) + return "nullptr" if ptr_value == 0 else f"{ptr_value:#x}" + + def to_string(self) -> str: + """Combine type info and pointer value into annotated_ptr display.""" + pointee_type_name, property_type_name = self._template_arguments() + full_type = f"{self.type_name}<{pointee_type_name}, {property_type_name}>" + return f"{full_type} -> {self._pointer_display()}" + + +class AnnotatedPtrPrinterLookup(gdb.printing.PrettyPrinter): + """Select the cuda::annotated_ptr printer by its public class name.""" + + def __init__(self) -> None: + super().__init__("cuda::annotated_ptr") + + def __call__(self, value: gdb.Value) -> AnnotatedPtrPrinter | None: + if _is_annotated_ptr(value.type): + return AnnotatedPtrPrinter(value) + return None + + +def register(objfile: ModuleType) -> None: + """Register the cuda::annotated_ptr printer with GDB.""" + gdb.printing.register_pretty_printer( + objfile, AnnotatedPtrPrinterLookup(), replace=True + ) diff --git a/libcudacxx/share/libcudacxx/lldb/__init__.py b/libcudacxx/share/libcudacxx/lldb/__init__.py index d810801ff255..18b1bc259e26 100644 --- a/libcudacxx/share/libcudacxx/lldb/__init__.py +++ b/libcudacxx/share/libcudacxx/lldb/__init__.py @@ -9,6 +9,7 @@ from __future__ import annotations +import annotated_ptr import atomic import buffer import complex @@ -31,6 +32,7 @@ _FORMATTERS = ( memory_resource, atomic, + annotated_ptr, buffer, std_array, complex, @@ -45,6 +47,7 @@ optional, shared_resource, ) + InternalDict = dict[str, object] diff --git a/libcudacxx/share/libcudacxx/lldb/annotated_ptr.py b/libcudacxx/share/libcudacxx/lldb/annotated_ptr.py new file mode 100644 index 000000000000..dbf89f483ba2 --- /dev/null +++ b/libcudacxx/share/libcudacxx/lldb/annotated_ptr.py @@ -0,0 +1,43 @@ +# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +"""LLDB pretty printer for cuda::annotated_ptr.""" + +from __future__ import annotations + +import re + +import cccl_common + +import lldb + +_ANNOTATED_PTR_PATTERN = re.compile(r"^cuda::annotated_ptr<.+,.+>$") +InternalDict = dict[str, object] + + +def is_annotated_ptr(value_type: lldb.SBType, _internal_dict: InternalDict) -> bool: + type_name = cccl_common.canonical_type_name(value_type) + return _ANNOTATED_PTR_PATTERN.fullmatch(type_name) is not None + + +def annotated_ptr_summary(value: lldb.SBValue, _internal_dict: InternalDict) -> str: + # LLDB already renders the declared type, so the summary only adds the pointer. + repr_ptr = cccl_common.strip_reference_value(value).GetChildMemberWithName("__repr") + if not repr_ptr.IsValid(): + return "-> unavailable" + + error = lldb.SBError() + ptr_addr = repr_ptr.GetValueAsUnsigned(error) + if error.Fail(): + return "-> unavailable" + return "-> nullptr" if ptr_addr == 0 else f"-> {ptr_addr:#x}" + + +def register(debugger: lldb.SBDebugger, category: str, module: str) -> None: + """Register CUDA annotated_ptr formatters with LLDB.""" + debugger.HandleCommand( + f"type summary add --category {category} --python-function " + f"{module}.annotated_ptr_summary --recognizer-function " + f"{module}.is_annotated_ptr" + ) diff --git a/libcudacxx/test/debugging/CMakeLists.txt b/libcudacxx/test/debugging/CMakeLists.txt index 89da4d800b0d..7ed99b1eeec3 100644 --- a/libcudacxx/test/debugging/CMakeLists.txt +++ b/libcudacxx/test/debugging/CMakeLists.txt @@ -245,3 +245,4 @@ add_subdirectory(shared_resource) add_subdirectory(span) add_subdirectory(stream) add_subdirectory(tuple) +add_subdirectory(annotated_ptr) diff --git a/libcudacxx/test/debugging/annotated_ptr/CMakeLists.txt b/libcudacxx/test/debugging/annotated_ptr/CMakeLists.txt new file mode 100644 index 000000000000..1d2a3778d186 --- /dev/null +++ b/libcudacxx/test/debugging/annotated_ptr/CMakeLists.txt @@ -0,0 +1,13 @@ +libcudacxx_add_pretty_printer_test( + NAME annotated_ptr + SOURCES source.cu + CASES + # gersemi: off + --case inspect_streaming annotated_ptr.streaming ptr + --case inspect_persisting annotated_ptr.persisting ptr + --case inspect_global annotated_ptr.global ptr + --case inspect_null annotated_ptr.null ptr + --case inspect_before_update annotated_ptr.update.before ptr + --case inspect_after_update annotated_ptr.update.after ptr + # gersemi: on +) diff --git a/libcudacxx/test/debugging/annotated_ptr/gdb.expected b/libcudacxx/test/debugging/annotated_ptr/gdb.expected new file mode 100644 index 000000000000..5f184bf80045 --- /dev/null +++ b/libcudacxx/test/debugging/annotated_ptr/gdb.expected @@ -0,0 +1,18 @@ +=============== annotated_ptr.streaming begin =============== +cuda::annotated_ptr ->
+=============== annotated_ptr.streaming end =============== +=============== annotated_ptr.persisting begin =============== +cuda::annotated_ptr ->
+=============== annotated_ptr.persisting end =============== +=============== annotated_ptr.global begin =============== +cuda::annotated_ptr ->
+=============== annotated_ptr.global end =============== +=============== annotated_ptr.null begin =============== +cuda::annotated_ptr -> nullptr +=============== annotated_ptr.null end =============== +=============== annotated_ptr.update.before begin =============== +cuda::annotated_ptr ->
+=============== annotated_ptr.update.before end =============== +=============== annotated_ptr.update.after begin =============== +cuda::annotated_ptr -> nullptr +=============== annotated_ptr.update.after end =============== diff --git a/libcudacxx/test/debugging/annotated_ptr/lldb.expected b/libcudacxx/test/debugging/annotated_ptr/lldb.expected new file mode 100644 index 000000000000..92d837e07e3e --- /dev/null +++ b/libcudacxx/test/debugging/annotated_ptr/lldb.expected @@ -0,0 +1,18 @@ +=============== annotated_ptr.streaming begin =============== +(const cuda::annotated_ptr &)
->
+=============== annotated_ptr.streaming end =============== +=============== annotated_ptr.persisting begin =============== +(const cuda::annotated_ptr &)
->
+=============== annotated_ptr.persisting end =============== +=============== annotated_ptr.global begin =============== +(const cuda::annotated_ptr &)
->
+=============== annotated_ptr.global end =============== +=============== annotated_ptr.null begin =============== +(const cuda::annotated_ptr &)
-> nullptr +=============== annotated_ptr.null end =============== +=============== annotated_ptr.update.before begin =============== +(const cuda::annotated_ptr &)
->
+=============== annotated_ptr.update.before end =============== +=============== annotated_ptr.update.after begin =============== +(const cuda::annotated_ptr &)
-> nullptr +=============== annotated_ptr.update.after end =============== diff --git a/libcudacxx/test/debugging/annotated_ptr/source.cu b/libcudacxx/test/debugging/annotated_ptr/source.cu new file mode 100644 index 000000000000..e51f0b06817e --- /dev/null +++ b/libcudacxx/test/debugging/annotated_ptr/source.cu @@ -0,0 +1,70 @@ +// Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +// +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include + +// Give the inspected parameter a stack location that survives optimization, so the +// debugger can read it in this frame. Without this the parameter stays in a +// caller-clobbered register and reads as unavailable at -O3. +#define KEEP_FOR_DEBUGGER(values) asm volatile("" : : "g"(&(values)) : "memory") + +[[gnu::noinline]] void inspect_streaming(const cuda::annotated_ptr& ptr) +{ + KEEP_FOR_DEBUGGER(ptr); +} + +[[gnu::noinline]] void inspect_persisting(const cuda::annotated_ptr& ptr) +{ + KEEP_FOR_DEBUGGER(ptr); +} + +[[gnu::noinline]] void inspect_global(const cuda::annotated_ptr& ptr) +{ + KEEP_FOR_DEBUGGER(ptr); +} + +[[gnu::noinline]] void inspect_null(const cuda::annotated_ptr& ptr) +{ + KEEP_FOR_DEBUGGER(ptr); +} + +[[gnu::noinline]] void inspect_before_update(const cuda::annotated_ptr& ptr) +{ + KEEP_FOR_DEBUGGER(ptr); +} + +[[gnu::noinline]] void inspect_after_update(const cuda::annotated_ptr& ptr) +{ + KEEP_FOR_DEBUGGER(ptr); +} + +int main() +{ + // Allocate test data with mutable backing (required by annotated_ptr) + int streaming_data = 42; + int persisting_data = 100; + float global_data = 3.14f; + int updated_data = 7; + + const cuda::annotated_ptr streaming_ptr(&streaming_data); + inspect_streaming(streaming_ptr); + + const cuda::annotated_ptr persisting_ptr(&persisting_data); + inspect_persisting(persisting_ptr); + + const cuda::annotated_ptr global_ptr(&global_data); + inspect_global(global_ptr); + + const cuda::annotated_ptr null_ptr{}; + inspect_null(null_ptr); + + // The pointer is non-null before the update and null after it, so the two stops print + // different values even though the runner masks non-zero addresses. + cuda::annotated_ptr updated_ptr(&updated_data); + inspect_before_update(updated_ptr); + updated_ptr = cuda::annotated_ptr{}; + inspect_after_update(updated_ptr); + + return 0; +}