Skip to content
Closed
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 @@ -27,6 +27,7 @@
import mdspan # noqa: E402
import memory_pool # noqa: E402
import memory_resource # noqa: E402
import optional # noqa: E402
import span # noqa: E402
import std_array # noqa: E402
import stream # noqa: E402
Expand All @@ -46,6 +47,7 @@
mdspan,
memory_pool,
span,
optional,
)


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

"""GDB pretty printer for cuda::std::optional."""

from __future__ import annotations

from collections.abc import Iterator
from types import ModuleType

import cccl_common

import gdb
import gdb.printing


def _is_cuda_optional(value_type: gdb.Type) -> bool:
value_type = cccl_common.canonical_type(value_type)
public_name = cccl_common.public_type_name(value_type)
template_name = cccl_common.template_name(value_type)
return (
public_name.startswith("cuda::std::")
and template_name.rsplit("::", 1)[-1] == "optional"
)


def _has_field(value_type: gdb.Type, name: str) -> bool:
return any(field.name == name for field in value_type.fields())


class OptionalPrinter:
"""Expose the contained value of a cuda::std::optional to GDB."""

def __init__(self, value: gdb.Value) -> None:
value = cccl_common.strip_reference_value(value)
self.value = value
self.type = cccl_common.canonical_type(value.type)
self.type_name = cccl_common.public_type_name(self.type)
# optional<T&> stores a pointer that is null when disengaged; every other
# specialization keeps an engaged flag next to a union payload.
self.pointer = (
self.value["__value_"] if _has_field(self.type, "__value_") else None
)
if self.pointer is not None:
self.engaged = int(self.pointer) != 0
else:
self.engaged = bool(self.value["__engaged_"])

def children(self) -> Iterator[tuple[str, gdb.Value]]:
# The payload is only read once the engaged state says it holds a value;
# a disengaged optional may still carry the bytes of a previous value.
if not self.engaged:
return
if self.pointer is not None:
yield "[contained value]", self.pointer.dereference()
else:
yield "[contained value]", self.value["__storage_"]["__val_"]

def to_string(self) -> str:
# Match the libstdc++ std::optional printer: "T [no contained value]" when empty.
if self.engaged:
return self.type_name
return f"{self.type_name} [no contained value]"


class OptionalPrinterLookup(gdb.printing.PrettyPrinter):
"""Select the cuda::std::optional printer by its public class name."""

def __init__(self) -> None:
super().__init__("cuda::std::optional")

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


def register(objfile: ModuleType) -> None:
"""Register the cuda::std::optional printer with GDB."""
gdb.printing.register_pretty_printer(objfile, OptionalPrinterLookup(), 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 @@ -18,6 +18,7 @@
import mdspan
import memory_pool
import memory_resource
import optional
import span
import std_array
import stream
Expand All @@ -40,6 +41,7 @@
mdspan,
memory_pool,
span,
optional,
)
InternalDict = dict[str, object]

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

"""LLDB pretty printer for cuda::std::optional."""

from __future__ import annotations

import re

import cccl_common

import lldb

_OPTIONAL_PATTERN = re.compile(r"^cuda::std::optional<.+>$")
InternalDict = dict[str, object]


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


def _is_engaged(value: lldb.SBValue) -> bool:
"""Return whether an optional holds a value, without touching the payload."""
# optional<T&> stores a pointer that is null when disengaged; every other
# specialization keeps an engaged flag next to a union payload.
pointer = value.GetChildMemberWithName("__value_")
if pointer.IsValid():
return pointer.GetValueAsUnsigned(0) != 0
return value.GetChildMemberWithName("__engaged_").GetValueAsUnsigned(0) != 0


def optional_summary(value: lldb.SBValue, _internal_dict: InternalDict) -> str:
"""Summarize the engaged state like LLDB's std::optional formatter."""
value = cccl_common.strip_reference_value(value).GetNonSyntheticValue()
return f"Has Value={'true' if _is_engaged(value) else 'false'}"


class OptionalSyntheticProvider:
"""Expose the contained value of a cuda::std::optional as an LLDB child."""

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

def update(self) -> bool:
self.engaged = _is_engaged(self.value)
return True

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

def has_children(self) -> bool:
return self.engaged

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

def get_child_at_index(self, index: int) -> lldb.SBValue | None:
# The payload is only read once the engaged state says it holds a value;
# a disengaged optional may still carry the bytes of a previous value.
if index != 0 or not self.engaged:
return None
pointer = self.value.GetChildMemberWithName("__value_")
if pointer.IsValid():
return pointer.Dereference().Clone("Value")
# The union member's declared type is the ``remove_cv_t<T>`` typedef, which
# leaves the value formatted under an unresolved alias (and lets a nested
# optional's leaf inherit a stray summary). Recover the real value type from
# the class template argument and read the payload through that instead.
value_type = (
self.value.GetType()
.GetCanonicalType()
.GetUnqualifiedType()
.GetTemplateArgumentType(0)
)
val = self.value.GetChildMemberWithName("__storage_").GetChildMemberWithName(
"__val_"
)
if value_type.IsValid():
val = val.Cast(value_type)
return val.Clone("Value")


def register(debugger: lldb.SBDebugger, category: str, module: str) -> None:
"""Register the cuda::std::optional formatter in an LLDB category."""
debugger.HandleCommand(
f"type summary add --category {category} --expand --python-function {module}.optional_summary "
f"--recognizer-function {module}.is_cuda_optional"
)
debugger.HandleCommand(
f"type synthetic add --category {category} --python-class {module}.OptionalSyntheticProvider "
f"--recognizer-function {module}.is_cuda_optional"
)
1 change: 1 addition & 0 deletions libcudacxx/test/debugging/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,7 @@ if (CUDAToolkit_VERSION VERSION_GREATER_EQUAL 13.0)
add_subdirectory(memory_pool_managed)
endif()
add_subdirectory(memory_resource)
add_subdirectory(optional)
add_subdirectory(span)
add_subdirectory(stream)
add_subdirectory(tuple)
17 changes: 17 additions & 0 deletions libcudacxx/test/debugging/optional/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
libcudacxx_add_pretty_printer_test(
NAME optional
SOURCES source.cu
CASES
# gersemi: off
--case inspect_engaged optional.engaged values
--case inspect_disengaged optional.disengaged values
--case inspect_nested optional.nested values
--case inspect_nested_disengaged optional.nested_disengaged values
--case inspect_non_trivial optional.non_trivial values
--case inspect_alias optional.alias values
--case inspect_reference optional.reference values
--case inspect_reference_disengaged optional.reference_disengaged values
--case inspect_before_update optional.update.before values
--case inspect_after_update optional.update.after values
# gersemi: on
)
49 changes: 49 additions & 0 deletions libcudacxx/test/debugging/optional/gdb.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
=============== optional.engaged begin ===============
cuda::std::optional<int> = {
[contained value] = 42
}
=============== optional.engaged end ===============
=============== optional.disengaged begin ===============
cuda::std::optional<int> [no contained value]
=============== optional.disengaged end ===============
=============== optional.nested begin ===============
cuda::std::optional<cuda::std::optional<int>> = {
[contained value] = cuda::std::optional<int> = {
[contained value] = 7
}
}
=============== optional.nested end ===============
=============== optional.nested_disengaged begin ===============
cuda::std::optional<cuda::std::optional<int>> = {
[contained value] = cuda::std::optional<int> [no contained value]
}
=============== optional.nested_disengaged end ===============
=============== optional.non_trivial begin ===============
cuda::std::optional<non_trivial> = {
[contained value] = {
id = 3,
weight = 2.5
}
}
=============== optional.non_trivial end ===============
=============== optional.alias begin ===============
cuda::std::optional<int> = {
[contained value] = -8
}
=============== optional.alias end ===============
=============== optional.reference begin ===============
cuda::std::optional<int&> = {
[contained value] = 11
}
=============== optional.reference end ===============
=============== optional.reference_disengaged begin ===============
cuda::std::optional<int&> [no contained value]
=============== optional.reference_disengaged end ===============
=============== optional.update.before begin ===============
cuda::std::optional<int> = {
[contained value] = 99
}
=============== optional.update.before end ===============
=============== optional.update.after begin ===============
cuda::std::optional<int> [no contained value]
=============== optional.update.after end ===============
46 changes: 46 additions & 0 deletions libcudacxx/test/debugging/optional/lldb.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
=============== optional.engaged begin ===============
(const cuda::std::optional<int> &) <address> Has Value=true: {
Value = 42
}
=============== optional.engaged end ===============
=============== optional.disengaged begin ===============
(const cuda::std::optional<int> &) <address> Has Value=false
=============== optional.disengaged end ===============
=============== optional.nested begin ===============
(const cuda::std::optional<cuda::std::optional<int>> &) <address> Has Value=true: {
Value = Has Value=true {
Value = 7
}
}
=============== optional.nested end ===============
=============== optional.nested_disengaged begin ===============
(const cuda::std::optional<cuda::std::optional<int>> &) <address> Has Value=true: {
Value = Has Value=false
}
=============== optional.nested_disengaged end ===============
=============== optional.non_trivial begin ===============
(const cuda::std::optional<non_trivial> &) <address> Has Value=true: {
Value = (id = 3, weight = 2.5)
}
=============== optional.non_trivial end ===============
=============== optional.alias begin ===============
(const optional_alias &) <address> Has Value=true: {
Value = -8
}
=============== optional.alias end ===============
=============== optional.reference begin ===============
(const cuda::std::optional<int &> &) <address> Has Value=true: {
Value = 11
}
=============== optional.reference end ===============
=============== optional.reference_disengaged begin ===============
(const cuda::std::optional<int &> &) <address> Has Value=false
=============== optional.reference_disengaged end ===============
=============== optional.update.before begin ===============
(const cuda::std::optional<int> &) <address> Has Value=true: {
Value = 99
}
=============== optional.update.before end ===============
=============== optional.update.after begin ===============
(const cuda::std::optional<int> &) <address> Has Value=false
=============== optional.update.after end ===============
Loading
Loading