Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions libcudacxx/share/libcudacxx/gdb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import memory_pool # noqa: E402
import memory_resource # noqa: E402
import optional # noqa: E402
import shared_resource # noqa: E402
import span # noqa: E402
import std_array # noqa: E402
import stream # noqa: E402
Expand All @@ -48,6 +49,7 @@
memory_pool,
span,
optional,
shared_resource,
)


Expand Down
89 changes: 89 additions & 0 deletions libcudacxx/share/libcudacxx/gdb/shared_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

"""GDB pretty printer for cuda::mr::shared_resource."""

from __future__ import annotations

from collections.abc import Iterator
from types import ModuleType

import cccl_common

import gdb
import gdb.printing


def _is_shared_resource(value_type: gdb.Type) -> bool:
value_type = cccl_common.canonical_type(value_type)
type_name = cccl_common.public_type_name(value_type)
return (
type_name.startswith("cuda::mr::")
and cccl_common.template_name(value_type).rsplit("::", 1)[-1]
== "shared_resource"
)


class SharedResourcePrinter:
"""Print the ownership state and the owned resource of a shared resource."""

def __init__(self, value: gdb.Value) -> None:
self.value = cccl_common.strip_reference_value(value)

def _control_block(self) -> gdb.Value | None:
"""Return the control block, or None if this handle is empty."""
# shared_resource holds a __shared_block_ptr, and both the wrapper and
# the pointer spell their member __block_.
control_block = self.value["__block_"]["__block_"]
if int(control_block) == 0:
return None
return control_block.dereference()

def to_string(self) -> str:
type_name = cccl_common.canonical_type_name(self.value.type)
block = self._control_block()
if block is None:
return f"{type_name} use_count=0, resource=nullptr"

# cuda::std::atomic<int> keeps its value in __a.__a_value.
use_count = int(block["__ref_count"]["__a"]["__a_value"])
# The address of a readable resource belongs to the resource child, the
# way std::shared_ptr keeps strong=/weak= in its summary and the pointer
# in its child. Only report it here when there is no child to carry it:
# a control block reached through a live pointer always has an address,
# so the branch below guards against an unreadable frame rather than
# against any state the scenario can reach.
if block["__payload"].address is None:
return f"{type_name} use_count={use_count}, resource=<invalid address>"
return f"{type_name} use_count={use_count}"

def children(self) -> Iterator[tuple[str, gdb.Value]]:
# Present the owned resource the way std::shared_ptr presents its
# pointer: one step away, so that expanding a handle does not print the
# implementation details of the resource itself.
block = self._control_block()
if block is None:
return
address = block["__payload"].address
if address is not None:
yield "resource", address


class SharedResourcePrinterLookup(gdb.printing.PrettyPrinter):
"""Select the cuda::mr::shared_resource printer by its public class name."""

def __init__(self) -> None:
super().__init__("cuda::mr::shared_resource")

def __call__(self, value: gdb.Value) -> SharedResourcePrinter | None:
if _is_shared_resource(value.type):
return SharedResourcePrinter(value)
return None


def register(objfile: ModuleType) -> None:
"""Register the cuda::mr::shared_resource printer with GDB."""
gdb.printing.register_pretty_printer(
objfile, SharedResourcePrinterLookup(), replace=True
)
2 changes: 2 additions & 0 deletions libcudacxx/share/libcudacxx/lldb/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import memory_pool
import memory_resource
import optional
import shared_resource
import span
import std_array
import stream
Expand All @@ -42,6 +43,7 @@
memory_pool,
span,
optional,
shared_resource,
)
InternalDict = dict[str, object]

Expand Down
139 changes: 139 additions & 0 deletions libcudacxx/share/libcudacxx/lldb/shared_resource.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception

"""LLDB pretty printer for cuda::mr::shared_resource."""

from __future__ import annotations

import re

import cccl_common

import lldb

_SHARED_RESOURCE_PATTERN = re.compile(r"^cuda::mr::shared_resource<.+>$")
InternalDict = dict[str, object]


def is_shared_resource(value_type: lldb.SBType, _internal_dict: InternalDict) -> bool:
type_name = cccl_common.canonical_type_name(value_type)
return _SHARED_RESOURCE_PATTERN.fullmatch(type_name) is not None


def _display_type_name(value: lldb.SBValue) -> str:
type_name = cccl_common.canonical_type_name(value.GetType())
return type_name or "cuda::mr::shared_resource"


def _block_pointer(value: lldb.SBValue) -> lldb.SBValue:
"""Return the control-block pointer, or an invalid value if unavailable."""
# shared_resource holds a __shared_block_ptr, and both the wrapper and the
# pointer spell their member __block_.
return (
cccl_common.strip_reference_value(value)
.GetNonSyntheticValue()
.GetChildMemberWithName("__block_")
.GetChildMemberWithName("__block_")
)


def _payload(value: lldb.SBValue) -> lldb.SBValue:
"""Return the owned resource, or an invalid value if this handle is empty."""
block_pointer = _block_pointer(value)
if not block_pointer.IsValid() or block_pointer.GetValueAsUnsigned(0) == 0:
return lldb.SBValue()
return block_pointer.Dereference().GetChildMemberWithName("__payload")


def _resource_pointer(payload: lldb.SBValue) -> lldb.SBValue:
"""Return the pointer both formatters report as the owned resource.

The summary reports the address only when this is invalid and there is no
child to carry it, so both have to read the same signal.
"""
return payload.AddressOf() if payload.IsValid() else payload


def shared_resource_summary(
value: lldb.SBValue, _internal_dict: InternalDict
) -> str | None:
"""Describe the ownership state of a CUDA shared resource."""
type_name = _display_type_name(value)
block_pointer = _block_pointer(value)
if not block_pointer.IsValid():
return None
if block_pointer.GetValueAsUnsigned(0) == 0:
return f"{type_name} use_count=0, resource=nullptr"

block = block_pointer.Dereference()
payload = _payload(value)
# cuda::std::atomic<int> keeps its value in __a.__a_value. Read the raw
# members: the atomic formatter replaces that layout with a synthetic
# "value" child, and one formatter should not depend on another's output.
reference_count = (
block.GetChildMemberWithName("__ref_count")
.GetNonSyntheticValue()
.GetChildMemberWithName("__a")
.GetChildMemberWithName("__a_value")
)
if not payload.IsValid() or not reference_count.IsValid():
return None

use_count = reference_count.GetValueAsSigned(0)
# The address of a readable resource belongs to the resource child, the way
# std::shared_ptr keeps strong=/weak= in its summary and the pointer in its
# child. Only report it here when there is no child to carry it: a control
# block reached through a live pointer always has a readable resource, so
# the branch below guards against an unreadable frame rather than against
# any state the scenario can reach.
if not _resource_pointer(payload).IsValid():
return f"{type_name} use_count={use_count}, resource=<invalid address>"
return f"{type_name} use_count={use_count}"


class SharedResourceSyntheticProvider:
"""Expose the owned resource as an LLDB synthetic child."""

def __init__(self, value: lldb.SBValue, _internal_dict: InternalDict) -> None:
self.value = cccl_common.strip_reference_value(value).GetNonSyntheticValue()
self.resource = lldb.SBValue()
self.update()

def update(self) -> bool:
# Present the owned resource the way std::shared_ptr presents its
# pointer: one step away, so that expanding a handle does not print the
# implementation details of the resource itself.
pointer = _resource_pointer(_payload(self.value))
self.resource = pointer.Clone("resource") if pointer.IsValid() else pointer
# Report no caching: a copy or a move changes which resource, if any,
# this handle owns, and LLDB must ask again after every stop.
return False

def num_children(self) -> int:
return 1 if self.has_children() else 0

def has_children(self) -> bool:
return self.resource.IsValid()

def get_child_index(self, name: str) -> int:
return 0 if name == "resource" else -1

def get_child_at_index(self, index: int) -> lldb.SBValue | None:
if index != 0 or not self.resource.IsValid():
return None
return self.resource


def register(debugger: lldb.SBDebugger, category: str, module: str) -> None:
"""Register the cuda::mr::shared_resource formatters in an LLDB category."""
debugger.HandleCommand(
f"type summary add --category {category} --expand --python-function "
f"{module}.shared_resource_summary --recognizer-function "
f"{module}.is_shared_resource"
)
debugger.HandleCommand(
f"type synthetic add --category {category} --python-class "
f"{module}.SharedResourceSyntheticProvider --recognizer-function "
f"{module}.is_shared_resource"
)
1 change: 1 addition & 0 deletions libcudacxx/test/debugging/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ if (CUDAToolkit_VERSION VERSION_GREATER_EQUAL 13.0)
endif()
add_subdirectory(memory_resource)
add_subdirectory(optional)
add_subdirectory(shared_resource)
add_subdirectory(span)
add_subdirectory(stream)
add_subdirectory(tuple)
15 changes: 15 additions & 0 deletions libcudacxx/test/debugging/shared_resource/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
libcudacxx_add_pretty_printer_test(
NAME shared_resource
SOURCES source.cu
CASES
# gersemi: off
--case inspect_unique shared_resource.unique resource
--case inspect_alias shared_resource.alias resource
--case inspect_before_copy shared_resource.copy.before resource
--case inspect_copy_source shared_resource.copy.after.source resource
--case inspect_copy_target shared_resource.copy.after.target resource
--case inspect_before_move shared_resource.move.before resource
--case inspect_move_source shared_resource.move.after.source resource
--case inspect_move_target shared_resource.move.after.target resource
# gersemi: on
)
38 changes: 38 additions & 0 deletions libcudacxx/test/debugging/shared_resource/gdb.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
=============== shared_resource.unique begin ===============
cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1 = {
resource = <address>
}
=============== shared_resource.unique end ===============
=============== shared_resource.alias begin ===============
cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1 = {
resource = <address>
}
=============== shared_resource.alias end ===============
=============== shared_resource.copy.before begin ===============
cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1 = {
resource = <address>
}
=============== shared_resource.copy.before end ===============
=============== shared_resource.copy.after.source begin ===============
cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=2 = {
resource = <address>
}
=============== shared_resource.copy.after.source end ===============
=============== shared_resource.copy.after.target begin ===============
cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=2 = {
resource = <address>
}
=============== shared_resource.copy.after.target end ===============
=============== shared_resource.move.before begin ===============
cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1 = {
resource = <address>
}
=============== shared_resource.move.before end ===============
=============== shared_resource.move.after.source begin ===============
cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=0, resource=nullptr
=============== shared_resource.move.after.source end ===============
=============== shared_resource.move.after.target begin ===============
cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1 = {
resource = <address>
}
=============== shared_resource.move.after.target end ===============
38 changes: 38 additions & 0 deletions libcudacxx/test/debugging/shared_resource/lldb.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
=============== shared_resource.unique begin ===============
(const shared_resource_type &) <address> cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1: {
resource = <address>
}
=============== shared_resource.unique end ===============
=============== shared_resource.alias begin ===============
(const resource_alias &) <address> cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1: {
resource = <address>
}
=============== shared_resource.alias end ===============
=============== shared_resource.copy.before begin ===============
(const shared_resource_type &) <address> cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1: {
resource = <address>
}
=============== shared_resource.copy.before end ===============
=============== shared_resource.copy.after.source begin ===============
(const shared_resource_type &) <address> cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=2: {
resource = <address>
}
=============== shared_resource.copy.after.source end ===============
=============== shared_resource.copy.after.target begin ===============
(const shared_resource_type &) <address> cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=2: {
resource = <address>
}
=============== shared_resource.copy.after.target end ===============
=============== shared_resource.move.before begin ===============
(const shared_resource_type &) <address> cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1: {
resource = <address>
}
=============== shared_resource.move.before end ===============
=============== shared_resource.move.after.source begin ===============
(const shared_resource_type &) <address> cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=0, resource=nullptr
=============== shared_resource.move.after.source end ===============
=============== shared_resource.move.after.target begin ===============
(const shared_resource_type &) <address> cuda::mr::shared_resource<cuda::mr::legacy_managed_memory_resource> use_count=1: {
resource = <address>
}
=============== shared_resource.move.after.target end ===============
Loading
Loading