[INFRA] Import NVIDIA/CCCL upstream as optimization reference library

CCCL (CUDA C++ Core Libraries) provides:
- CUB: device/block/warp-level GPU primitives (reduce, scan, sort, topk)
- Thrust: high-level parallel algorithms (transform_reduce, sort, scan)
- libcudacxx: CUDA C++ standard library (atomics, barriers, memory)
- cudax: experimental features (memory resources, allocators)
- Tuning policies: per-SM hardware-specific algorithm parameters

Competition optimization vectors mapped to CCCL:
- Output TPS (83% weight): warp_reduce, block_reduce, device_topk
- Input TPS (14% weight): device_scan, block_load, prefetch
- Cache TPS (3% weight): prefix caching strategy patterns
- Memory (0.9 util): pooled/cached/buddy allocators

Source: https://github.com/NVIDIA/cccl (shallow clone, HEAD only)
License: Apache-2.0
This commit is contained in:
EngineX CI
2026-07-30 09:35:51 +00:00
parent b4d01f481e
commit 56fd68e7dd
8871 changed files with 1454674 additions and 0 deletions

View File

@@ -0,0 +1,202 @@
set(LIBCUDACXX_SUPPORTED_DEBUGGERS lldb gdb)
set(LIBCUDACXX_DEBUGGING_BASE_TARGET libcudacxx.test.debugging)
function(libcudacxx_init_debugger_testing enabled_var)
# Unconditionally create the umbrella target to make CMakePresets easier to setup. If we
# don't have the debuggers available, this target doesn't do anything
add_custom_target(${LIBCUDACXX_DEBUGGING_BASE_TARGET})
# Windows lldb is broken sometimes (see
# https://github.com/llvm/llvm-project/issues/74073) so merely finding it does not mean
# it is functional. In any case, it is good to validate the binary since even a found
# lldb/gdb on other platforms that ends up not working is annoying to work around
function(validator result_var item)
execute_process(
COMMAND ${item} --version
OUTPUT_QUIET
ERROR_QUIET
RESULT_VARIABLE result
TIMEOUT 10
)
if (result EQUAL 0)
set(${result_var} TRUE PARENT_SCOPE)
else()
set(${result_var} FALSE PARENT_SCOPE)
endif()
endfunction()
set(enabled FALSE)
foreach (debugger IN LISTS LIBCUDACXX_SUPPORTED_DEBUGGERS)
string(TOUPPER "${debugger}" DEBUGGER_UPPER)
find_program(
LIBCUDACXX_${DEBUGGER_UPPER}
NAMES "${debugger}"
VALIDATOR validator
)
if (LIBCUDACXX_${DEBUGGER_UPPER})
set(debugger_exe "${LIBCUDACXX_${DEBUGGER_UPPER}}")
message(STATUS "Found ${debugger}: ${debugger_exe}")
execute_process(
COMMAND ${debugger_exe} --version
OUTPUT_VARIABLE version
ERROR_VARIABLE version
OUTPUT_STRIP_TRAILING_WHITESPACE
ERROR_STRIP_TRAILING_WHITESPACE
COMMAND_ERROR_IS_FATAL ANY
)
message(STATUS "${debugger_exe} --version: ${version}")
endif()
set(default OFF)
if (LIBCUDACXX_${DEBUGGER_UPPER})
set(default ON)
endif()
option(
LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING
"Run libcudacxx pretty-printer tests with ${debugger}"
${default}
)
if (
LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING
AND NOT LIBCUDACXX_${DEBUGGER_UPPER}
)
message(
FATAL_ERROR
"LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING is enabled, but ${debugger} was not found"
)
endif()
set(
LIBCUDACXX_${DEBUGGER_UPPER}
"${LIBCUDACXX_${DEBUGGER_UPPER}}"
PARENT_SCOPE
)
set(
LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING
"${LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING}"
PARENT_SCOPE
)
if (LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING)
set(enabled TRUE)
endif()
endforeach()
set(${enabled_var} ${enabled} PARENT_SCOPE)
endfunction()
libcudacxx_init_debugger_testing(testing_enabled)
if (NOT testing_enabled)
return()
endif()
#[=======================================================================[.rst:
libcudacxx_add_pretty_printer_test
---------------------------------
Build a CUDA pretty-printer scenario and register its enabled debugger tests.
The function creates the executable target
``libcudacxx.test.debugging.<NAME>`` with host debug information and optimization
disabled. For each enabled debugger, it registers a serial CTest named
``libcudacxx.test.debugging.<debugger>.<NAME>``. The test uses the debugger's
formatter entry point and the ``<debugger>.expected`` file in the caller's source
directory.
Arguments
^^^^^^^^^
``NAME``
Scenario name used in the executable target, CTest names, and diagnostic output.
``SOURCES``
Source files used to build the CUDA scenario executable. Relative paths are resolved
against the caller's source directory.
``CASES``
Ordered runner arguments describing the debugger stops and expressions. Each case
has the form ``--case <breakpoint> <caller-frame-index> <section-name>
<expression>``.
#]=======================================================================]
function(libcudacxx_add_pretty_printer_test)
set(options)
set(one_value_arguments NAME)
set(multi_value_arguments SOURCES CASES)
cmake_parse_arguments(
pretty_printer
"${options}"
"${one_value_arguments}"
"${multi_value_arguments}"
${ARGN}
)
if (pretty_printer_UNPARSED_ARGUMENTS)
message(
FATAL_ERROR
"Unrecognized arguments: ${pretty_printer_UNPARSED_ARGUMENTS}"
)
endif()
if (NOT pretty_printer_NAME)
message(FATAL_ERROR "libcudacxx_add_pretty_printer_test requires NAME")
endif()
if (NOT pretty_printer_SOURCES)
message(FATAL_ERROR "libcudacxx_add_pretty_printer_test requires SOURCES")
endif()
if (NOT pretty_printer_CASES)
message(FATAL_ERROR "libcudacxx_add_pretty_printer_test requires CASES")
endif()
set(target_name "${LIBCUDACXX_DEBUGGING_BASE_TARGET}.${pretty_printer_NAME}")
cccl_add_executable(
${target_name}
DIALECT 17
NO_CLANG_TIDY
SOURCES ${pretty_printer_SOURCES}
)
target_compile_options(
${target_name}
PRIVATE $<$<COMPILE_LANGUAGE:CUDA>:-g> $<$<COMPILE_LANGUAGE:CUDA>:-O0>
)
target_link_libraries(${target_name} PRIVATE libcudacxx.compiler_interface)
foreach (debugger IN LISTS LIBCUDACXX_SUPPORTED_DEBUGGERS)
string(TOUPPER "${debugger}" DEBUGGER_UPPER)
if (NOT LIBCUDACXX_ENABLE_${DEBUGGER_UPPER}_TESTING)
continue()
endif()
set(executable "${LIBCUDACXX_${DEBUGGER_UPPER}}")
set(
formatter
"${libcudacxx_SOURCE_DIR}/share/libcudacxx/${debugger}/__init__.py"
)
set(test_name "${target_name}.${debugger}")
add_test(
NAME ${test_name}
COMMAND
# gersemi: off
"${Python_EXECUTABLE}" "${CMAKE_CURRENT_FUNCTION_LIST_DIR}/run_pretty_printer_test.py"
--debugger "${debugger}"
--debugger-executable "${executable}"
--program "$<TARGET_FILE:${target_name}>"
--formatter-init "${formatter}"
--expected "${CMAKE_CURRENT_SOURCE_DIR}/${debugger}.expected"
--output-log "${CMAKE_CURRENT_BINARY_DIR}/${debugger}.log"
${pretty_printer_CASES}
# gersemi: on
)
set_tests_properties(${test_name} PROPERTIES TIMEOUT 60)
endforeach()
endfunction()
add_subdirectory(array)
add_subdirectory(buffer)
add_subdirectory(memory_resource)

View File

@@ -0,0 +1,13 @@
libcudacxx_add_pretty_printer_test(
NAME array
SOURCES source.cu
CASES
# gersemi: off
--case inspect_normal 1 array.normal normal
--case inspect_empty 1 array.empty empty
--case inspect_nested 1 array.nested nested
--case inspect_alias 1 array.alias alias
--case inspect_before_update 1 array.update.before updated_values
--case inspect_after_update 1 array.update.after updated_values
# gersemi: on
)

View File

@@ -0,0 +1,44 @@
=============== array.normal begin ===============
cuda::std::array<int, 3> = {
[0] = -7,
[1] = 0,
[2] = 42
}
=============== array.normal end ===============
=============== array.empty begin ===============
cuda::std::array<int, 0>
=============== array.empty end ===============
=============== array.nested begin ===============
cuda::std::array<cuda::std::array<int, 2>, 2> = {
[0] = cuda::std::array<int, 2> = {
[0] = 13,
[1] = -5
},
[1] = cuda::std::array<int, 2> = {
[0] = 0,
[1] = 88
}
}
=============== array.nested end ===============
=============== array.alias begin ===============
cuda::std::array<int, 4> = {
[0] = -31,
[1] = 17,
[2] = 8,
[3] = -64
}
=============== array.alias end ===============
=============== array.update.before begin ===============
cuda::std::array<int, 3> = {
[0] = 6,
[1] = -91,
[2] = 52
}
=============== array.update.before end ===============
=============== array.update.after begin ===============
cuda::std::array<int, 3> = {
[0] = 3,
[1] = 85,
[2] = -12
}
=============== array.update.after end ===============

View File

@@ -0,0 +1,21 @@
=============== array.normal begin ===============
(cuda::std::array<int, 3>) ([0] = -7, [1] = 0, [2] = 42)
=============== array.normal end ===============
=============== array.empty begin ===============
(cuda::std::array<int, 0>)
=============== array.empty end ===============
=============== array.nested begin ===============
(cuda::std::array<cuda::std::array<int, 2>, 2>) {
[0] = ([0] = 13, [1] = -5)
[1] = ([0] = 0, [1] = 88)
}
=============== array.nested end ===============
=============== array.alias begin ===============
(cuda::std::array<int, 4>) ([0] = -31, [1] = 17, [2] = 8, [3] = -64)
=============== array.alias end ===============
=============== array.update.before begin ===============
(cuda::std::array<int, 3>) ([0] = 6, [1] = -91, [2] = 52)
=============== array.update.before end ===============
=============== array.update.after begin ===============
(cuda::std::array<int, 3>) ([0] = 3, [1] = 85, [2] = -12)
=============== array.update.after end ===============

View File

@@ -0,0 +1,60 @@
// Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cuda/std/array>
template <class T>
[[gnu::noinline]] void keep_for_debugger(const T& value)
{
asm volatile("" : : "g"(&value) : "memory");
}
using array_alias = cuda::std::array<int, 4>;
[[gnu::noinline]] void inspect_normal(const cuda::std::array<int, 3>& values)
{
keep_for_debugger(values);
}
[[gnu::noinline]] void inspect_empty(const cuda::std::array<int, 0>& values)
{
keep_for_debugger(values);
}
[[gnu::noinline]] void inspect_nested(const cuda::std::array<cuda::std::array<int, 2>, 2>& values)
{
keep_for_debugger(values);
}
[[gnu::noinline]] void inspect_alias(const array_alias& values)
{
keep_for_debugger(values);
}
[[gnu::noinline]] void inspect_before_update(const cuda::std::array<int, 3>& values)
{
keep_for_debugger(values);
}
[[gnu::noinline]] void inspect_after_update(const cuda::std::array<int, 3>& values)
{
keep_for_debugger(values);
}
int main()
{
const cuda::std::array<int, 3> normal = {-7, 0, 42};
const cuda::std::array<int, 0> empty = {};
const cuda::std::array<cuda::std::array<int, 2>, 2> nested = {{{13, -5}, {0, 88}}};
const array_alias alias = {-31, 17, 8, -64};
cuda::std::array<int, 3> updated_values = {6, -91, 52};
inspect_normal(normal);
inspect_empty(empty);
inspect_nested(nested);
inspect_alias(alias);
inspect_before_update(updated_values);
updated_values = {3, 85, -12};
inspect_after_update(updated_values);
}

View File

@@ -0,0 +1,15 @@
libcudacxx_add_pretty_printer_test(
NAME buffer
SOURCES source.cu
CASES
# gersemi: off
--case inspect_normal 1 buffer.normal normal_values
--case inspect_alias 1 buffer.alias aliased_values
--case inspect_vector 1 buffer.vector.0 "buffer_vector[0]"
--case inspect_vector 1 buffer.vector.1 "buffer_vector[1]"
--case inspect_host_device 1 buffer.host_device host_device_values
--case inspect_empty 1 buffer.empty empty_values
--case inspect_before_update 1 buffer.update.before updated_values
--case inspect_after_update 1 buffer.update.after updated_values
# gersemi: on
)

View File

@@ -0,0 +1,63 @@
=============== buffer.normal begin ===============
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=10, align=4, data=<address> (device) = {
[0] = -56,
[1] = 22,
[2] = 94,
[3] = -13,
[4] = 7,
[5] = 41,
[6] = -82,
[7] = 0,
[8] = 63,
[9] = -5
}
=============== buffer.normal end ===============
=============== buffer.alias begin ===============
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) = {
[0] = 17,
[1] = -31,
[2] = 8,
[3] = 55
}
=============== buffer.alias end ===============
=============== buffer.vector.0 begin ===============
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=3, align=4, data=<address> (device) = {
[0] = -2,
[1] = 4,
[2] = 6
}
=============== buffer.vector.0 end ===============
=============== buffer.vector.1 begin ===============
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=3, align=4, data=<address> (device) = {
[0] = 11,
[1] = -9,
[2] = 27
}
=============== buffer.vector.1 end ===============
=============== buffer.host_device begin ===============
cuda::buffer<int, cuda::mr::device_accessible, cuda::mr::host_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (host/device) = {
[0] = 3,
[1] = 14,
[2] = -15,
[3] = 92
}
=============== buffer.host_device end ===============
=============== buffer.empty begin ===============
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=0, align=4, data=0x0 (device)
=============== buffer.empty end ===============
=============== buffer.update.before begin ===============
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) = {
[0] = 1,
[1] = 2,
[2] = 3,
[3] = 4
}
=============== buffer.update.before end ===============
=============== buffer.update.after begin ===============
cuda::buffer<int, cuda::mr::device_accessible> mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) = {
[0] = -8,
[1] = 13,
[2] = 21,
[3] = -34
}
=============== buffer.update.after end ===============

View File

@@ -0,0 +1,63 @@
=============== buffer.normal begin ===============
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=10, align=4, data=<address> (device) {
[0] = -56
[1] = 22
[2] = 94
[3] = -13
[4] = 7
[5] = 41
[6] = -82
[7] = 0
[8] = 63
[9] = -5
}
=============== buffer.normal end ===============
=============== buffer.alias begin ===============
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) {
[0] = 17
[1] = -31
[2] = 8
[3] = 55
}
=============== buffer.alias end ===============
=============== buffer.vector.0 begin ===============
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=3, align=4, data=<address> (device) {
[0] = -2
[1] = 4
[2] = 6
}
=============== buffer.vector.0 end ===============
=============== buffer.vector.1 begin ===============
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=3, align=4, data=<address> (device) {
[0] = 11
[1] = -9
[2] = 27
}
=============== buffer.vector.1 end ===============
=============== buffer.host_device begin ===============
(cuda::buffer<int, cuda::mr::device_accessible, cuda::mr::host_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (host/device) {
[0] = 3
[1] = 14
[2] = -15
[3] = 92
}
=============== buffer.host_device end ===============
=============== buffer.empty begin ===============
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=0, align=4, data=0x0 (device)
=============== buffer.empty end ===============
=============== buffer.update.before begin ===============
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) {
[0] = 1
[1] = 2
[2] = 3
[3] = 4
}
=============== buffer.update.before end ===============
=============== buffer.update.after begin ===============
(cuda::buffer<int, cuda::mr::device_accessible>) mr=cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>, stream=<address>, size=4, align=4, data=<address> (device) {
[0] = -8
[1] = 13
[2] = 21
[3] = -34
}
=============== buffer.update.after end ===============

View File

@@ -0,0 +1,102 @@
// Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cuda/buffer>
#include <cuda/memory_resource>
#include <cuda/std/array>
#include <cuda/stream>
#include <vector>
#include <cuda_runtime_api.h>
template <class T>
[[gnu::noinline]] void keep_for_debugger(const T& value)
{
asm volatile("" : : "g"(&value) : "memory");
}
[[gnu::noinline]] void inspect_normal(const cuda::device_buffer<int>& values)
{
keep_for_debugger(values);
}
using device_buffer_alias = cuda::buffer<int, cuda::mr::device_accessible>;
[[gnu::noinline]] void inspect_alias(const device_buffer_alias& values)
{
keep_for_debugger(values);
}
[[gnu::noinline]] void inspect_vector(const std::vector<cuda::device_buffer<int>>& values)
{
keep_for_debugger(values[0]);
keep_for_debugger(values[1]);
}
template <class Buffer>
[[gnu::noinline]] void inspect_host_device(const Buffer& values)
{
keep_for_debugger(values);
}
[[gnu::noinline]] void inspect_empty(const cuda::device_buffer<int>& values)
{
keep_for_debugger(values);
}
[[gnu::noinline]] void inspect_before_update(const cuda::device_buffer<int>& values)
{
keep_for_debugger(values);
}
[[gnu::noinline]] void inspect_after_update(const cuda::device_buffer<int>& values)
{
keep_for_debugger(values);
}
int main()
{
constexpr cuda::device_ref device{0};
cuda::stream stream{device};
const cuda::std::array normal_host_values{-56, 22, 94, -13, 7, 41, -82, 0, 63, -5};
const auto normal_values = cuda::make_device_buffer<int>(stream, device, normal_host_values);
const cuda::std::array alias_host_values{17, -31, 8, 55};
const device_buffer_alias aliased_values = cuda::make_device_buffer<int>(stream, device, alias_host_values);
std::vector<cuda::device_buffer<int>> buffer_vector;
buffer_vector.emplace_back(cuda::make_device_buffer<int>(stream, device, cuda::std::array{-2, 4, 6}));
buffer_vector.emplace_back(cuda::make_device_buffer<int>(stream, device, cuda::std::array{11, -9, 27}));
cuda::mr::legacy_managed_memory_resource managed_resource;
const cuda::std::array host_device_host_values{3, 14, -15, 92};
const auto host_device_values = cuda::make_buffer<int>(stream, managed_resource, host_device_host_values);
const auto empty_values = cuda::make_device_buffer<int>(stream, device);
const cuda::std::array initial_updated_host_values{1, 2, 3, 4};
auto updated_values = cuda::make_device_buffer<int>(stream, device, initial_updated_host_values);
stream.sync();
inspect_normal(normal_values);
inspect_alias(aliased_values);
inspect_vector(buffer_vector);
inspect_host_device(host_device_values);
inspect_empty(empty_values);
inspect_before_update(updated_values);
const cuda::std::array replacement_host_values{-8, 13, 21, -34};
if (cudaMemcpyAsync(updated_values.data(),
replacement_host_values.data(),
replacement_host_values.size() * sizeof(*updated_values.data()),
cudaMemcpyDefault,
stream.get())
!= cudaSuccess)
{
return 1;
}
stream.sync();
inspect_after_update(updated_values);
}

View File

@@ -0,0 +1,10 @@
libcudacxx_add_pretty_printer_test(
NAME memory_resource
SOURCES source.cu
CASES
# gersemi: off
--case inspect_device 1 memory_resource.device device_resource
--case inspect_host_device 1 memory_resource.host_device host_device_resource
--case inspect_alias 1 memory_resource.alias aliased_resource
# gersemi: on
)

View File

@@ -0,0 +1,9 @@
=============== memory_resource.device begin ===============
cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>
=============== memory_resource.device end ===============
=============== memory_resource.host_device begin ===============
cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible> @ <address>
=============== memory_resource.host_device end ===============
=============== memory_resource.alias begin ===============
cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>
=============== memory_resource.alias end ===============

View File

@@ -0,0 +1,9 @@
=============== memory_resource.device begin ===============
(const device_resource_type) cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>
=============== memory_resource.device end ===============
=============== memory_resource.host_device begin ===============
(const host_device_resource_type) cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible> @ <address>
=============== memory_resource.host_device end ===============
=============== memory_resource.alias begin ===============
(const resource_alias) cuda::mr::any_resource<cuda::mr::device_accessible> @ <address>
=============== memory_resource.alias end ===============

View File

@@ -0,0 +1,43 @@
// Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
//
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
#include <cuda/memory_resource>
template <class T>
[[gnu::noinline]] void keep_for_debugger(const T& value)
{
asm volatile("" : : "g"(&value) : "memory");
}
using device_resource_type = cuda::mr::any_resource<cuda::mr::device_accessible>;
using host_device_resource_type = cuda::mr::any_resource<cuda::mr::device_accessible, cuda::mr::host_accessible>;
using resource_alias = device_resource_type;
[[gnu::noinline]] void inspect_device(const device_resource_type& resource)
{
keep_for_debugger(resource);
}
[[gnu::noinline]] void inspect_host_device(const host_device_resource_type& resource)
{
keep_for_debugger(resource);
}
[[gnu::noinline]] void inspect_alias(const resource_alias& resource)
{
keep_for_debugger(resource);
}
int main()
{
using adapted_resource = cuda::mr::synchronous_resource_adapter<cuda::mr::legacy_managed_memory_resource>;
const adapted_resource managed_resource{cuda::mr::legacy_managed_memory_resource{}};
const device_resource_type device_resource{managed_resource};
const host_device_resource_type host_device_resource{managed_resource};
const resource_alias aliased_resource{managed_resource};
inspect_device(device_resource);
inspect_host_device(host_device_resource);
inspect_alias(aliased_resource);
}

View File

@@ -0,0 +1,790 @@
# Copyright (c) 2026, NVIDIA CORPORATION & AFFILIATES. All rights reserved.
#
# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
"""Run a libcudacxx pretty-printer scenario under LLDB or GDB."""
from __future__ import annotations
import argparse
import difflib
import re
import subprocess
import sys
from abc import ABC, abstractmethod
from collections.abc import Sequence
from dataclasses import dataclass
from enum import StrEnum
from pathlib import Path
_MARKER_EDGE = "=" * 15
_MARKER_PATTERN = re.compile(
rf"^{re.escape(_MARKER_EDGE)} (?P<section>.+) (?P<kind>begin|end) {re.escape(_MARKER_EDGE)}$"
)
_LLDB_ECHO_PATTERN = re.compile(r"^\s*\(lldb\)\s")
_GDB_VALUE_PREFIX_PATTERN = re.compile(r"^\s*\$\d+ = ")
_NONZERO_HEX_PATTERN = re.compile(r"\b0x(?!0+\b)[0-9a-fA-F]+\b")
class HarnessError(RuntimeError):
"""Report invalid test input or debugger output."""
class DebuggerError(RuntimeError):
"""Report a debugger launch, timeout, or exit failure."""
class Debugger(StrEnum):
LLDB = "lldb"
GDB = "gdb"
@dataclass(frozen=True)
class Case:
breakpoint: str
frame: int
section: str
expression: str
class CaseAction(argparse.Action):
"""Parse and validate one four-part ``--case`` option."""
def __call__(
self,
parser: argparse.ArgumentParser,
namespace: argparse.Namespace,
values: Sequence[str],
option_string: str | None = None,
) -> None:
"""Append one parsed case to the argument namespace.
Parameters
----------
parser : argparse.ArgumentParser
Parser handling the command line.
namespace : argparse.Namespace
Namespace receiving parsed cases.
values : Sequence[str]
Breakpoint, frame, section, and expression values.
option_string : str or None
Option spelling that supplied the values.
Raises
------
SystemExit
If the frame, breakpoint, section, or expression is invalid, or if
the section name is duplicated.
"""
breakpoint, raw_frame, section, expression = values
try:
frame = int(raw_frame)
except ValueError:
parser.error(f"invalid caller frame index {raw_frame!r}")
if frame < 0:
parser.error(f"caller frame index must be nonnegative: {frame}")
for label, value in (
("breakpoint", breakpoint),
("section", section),
("expression", expression),
):
if not value or "\n" in value or "\r" in value:
parser.error(f"{label} must be a nonempty single line")
cases: list[Case] = getattr(namespace, self.dest) or []
if any(case.section == section for case in cases):
parser.error(f"duplicate section name: {section}")
cases.append(Case(breakpoint, frame, section, expression))
setattr(namespace, self.dest, cases)
def marker(section: str, kind: str) -> str:
"""Build an exact marker for a captured section.
Parameters
----------
section : str
Unique name of the output section.
kind : str
Marker kind, either ``begin`` or ``end``.
Returns
-------
str
Complete marker line expected in debugger output.
"""
return f"{_MARKER_EDGE} {section} {kind} {_MARKER_EDGE}"
class DebuggerAdapter(ABC):
"""Provide debugger-specific command and transcript hooks.
Parameters
----------
executable : Path
Debugger executable path.
formatter_init : Path
Pretty-printer entry-point path.
program : Path
Scenario executable path.
"""
kind: Debugger
def __init__(self, executable: Path, formatter_init: Path, program: Path) -> None:
"""Store paths shared by debugger-specific operations.
Parameters
----------
executable : Path
Debugger executable path.
formatter_init : Path
Pretty-printer entry-point path.
program : Path
Scenario executable path.
"""
self.executable = executable
self.formatter_init = formatter_init
self.program = program
def generate_commands(self, cases: Sequence[Case]) -> str:
"""Generate commands for an ordered list of cases.
Parameters
----------
cases : Sequence[Case]
Cases to execute in order.
Returns
-------
str
Complete debugger command-file contents.
Raises
------
HarnessError
If a completed breakpoint group is reopened later.
"""
closed_stops: set[tuple[str, int]] = set()
previous_stop: tuple[str, int] | None = None
for case in cases:
stop = (case.breakpoint, case.frame)
if stop == previous_stop:
continue
if stop in closed_stops:
raise HarnessError(
f"breakpoint group {case.breakpoint!r} at frame {case.frame} was reopened"
)
if previous_stop is not None:
closed_stops.add(previous_stop)
previous_stop = stop
return self._generate_commands(cases)
@abstractmethod
def _generate_commands(self, cases: Sequence[Case]) -> str:
"""Generate commands for validated cases.
Parameters
----------
cases : Sequence[Case]
Cases to execute in order.
Returns
-------
str
Complete debugger command-file contents.
"""
raise NotImplementedError
@abstractmethod
def command(self, command_file: Path) -> list[str]:
"""Build the debugger subprocess argument list.
Parameters
----------
command_file : Path
Generated debugger command-file path.
Returns
-------
list[str]
Subprocess arguments for this debugger.
"""
raise NotImplementedError
def include_transcript_line(self, line: str) -> bool:
"""Return whether a line inside a marked section should be retained.
Parameters
----------
line : str
Transcript line inside a marked section.
Returns
-------
bool
``True`` when the line belongs in normalized output.
"""
return True
def normalize_line(self, line: str) -> str:
"""Apply debugger-specific normalization to one output line.
Parameters
----------
line : str
Extracted debugger output line.
Returns
-------
str
Line after debugger-specific normalization.
"""
return line
class GDB(DebuggerAdapter):
"""Provide GDB-specific pretty-printer test behavior."""
kind = Debugger.GDB
def _generate_commands(self, cases: Sequence[Case]) -> str:
"""Generate a GDB command file for ordered cases.
Parameters
----------
cases : Sequence[Case]
Cases to execute in order.
Returns
-------
str
Complete GDB command-file contents.
"""
lines = [
"set pagination off",
"set print pretty on",
"set print array-indexes on",
"set debuginfod enabled off",
f"source {self.formatter_init}",
]
seen_breakpoints: set[str] = set()
for case in cases:
if case.breakpoint in seen_breakpoints:
continue
lines.append(f"break {case.breakpoint}")
seen_breakpoints.add(case.breakpoint)
lines.append("run")
previous_stop: tuple[str, int] | None = None
for case in cases:
stop = (case.breakpoint, case.frame)
if previous_stop is not None and stop != previous_stop:
lines.append("continue")
if stop != previous_stop:
lines.append(f"frame {case.frame}")
previous_stop = stop
begin = marker(case.section, "begin")
end = marker(case.section, "end")
expression = repr(case.expression)
lines.extend(
[
f"python print({begin!r})",
"python",
"try:",
f" print(gdb.execute('print ' + {expression}, from_tty=True, to_string=True), end='')",
"except Exception as error:",
" print(error)",
"end",
f"python print({end!r})",
]
)
return "\n".join(lines) + "\n"
def command(self, command_file: Path) -> list[str]:
"""Build the GDB subprocess argument list.
Parameters
----------
command_file : Path
Generated GDB command-file path.
Returns
-------
list[str]
GDB subprocess arguments.
"""
return [
str(self.executable),
"--quiet",
"--batch",
"--nx",
"--command",
str(command_file),
str(self.program),
]
def normalize_line(self, line: str) -> str:
"""Remove GDB value-history prefixes from an output line.
Parameters
----------
line : str
Extracted GDB output line.
Returns
-------
str
Line without a leading ``$N =`` prefix.
"""
return _GDB_VALUE_PREFIX_PATTERN.sub("", line)
class LLDB(DebuggerAdapter):
"""Provide LLDB-specific pretty-printer test behavior."""
kind = Debugger.LLDB
def _generate_commands(self, cases: Sequence[Case]) -> str:
"""Generate an LLDB command file for ordered cases.
Parameters
----------
cases : Sequence[Case]
Cases to execute in order.
Returns
-------
str
Complete LLDB command-file contents.
"""
lines = [f'command script import "{self.formatter_init}"']
seen_breakpoints: set[str] = set()
for case in cases:
if case.breakpoint in seen_breakpoints:
continue
lines.append(f"breakpoint set --name {case.breakpoint}")
seen_breakpoints.add(case.breakpoint)
lines.append("run")
previous_stop: tuple[str, int] | None = None
for case in cases:
stop = (case.breakpoint, case.frame)
if previous_stop is not None and stop != previous_stop:
lines.append("continue")
if stop != previous_stop:
lines.append(f"frame select {case.frame}")
previous_stop = stop
begin = marker(case.section, "begin")
end = marker(case.section, "end")
debugger_command = f"dwim-print -- {case.expression}"
lines.extend(
[
f"script print({begin!r})",
"script result = lldb.SBCommandReturnObject(); "
f"status = lldb.debugger.GetCommandInterpreter().HandleCommand({debugger_command!r}, result); "
"print(result.GetOutput(), end=''); print(result.GetError(), end='')",
f"script print({end!r})",
]
)
return "\n".join(lines) + "\n"
def command(self, command_file: Path) -> list[str]:
"""Build the LLDB subprocess argument list.
Parameters
----------
command_file : Path
Generated LLDB command-file path.
Returns
-------
list[str]
LLDB subprocess arguments.
"""
return [
str(self.executable),
"--batch",
"--no-lldbinit",
"--source",
str(command_file),
str(self.program),
]
def include_transcript_line(self, line: str) -> bool:
"""Exclude LLDB prompt and command-echo lines from marked output.
Parameters
----------
line : str
Transcript line inside a marked section.
Returns
-------
bool
``False`` for LLDB prompt or command-echo lines.
"""
return _LLDB_ECHO_PATTERN.match(line) is None
def extract_sections(
transcript: str, section_order: Sequence[str], debugger: DebuggerAdapter
) -> str:
"""Extract and validate marked sections from a debugger transcript.
Parameters
----------
transcript : str
Complete combined debugger output.
section_order : Sequence[str]
Expected section names in manifest order.
debugger : DebuggerAdapter
Adapter for the debugger that produced the transcript.
Returns
-------
str
Marked sections concatenated in manifest order.
Raises
------
HarnessError
If markers are unexpected, missing, duplicated, nested, mismatched, or
unterminated.
"""
expected_sections = set(section_order)
captured: dict[str, list[str]] = {}
active_section: str | None = None
for line in transcript.splitlines():
match = _MARKER_PATTERN.fullmatch(line)
if not match:
if active_section is None:
continue
if not debugger.include_transcript_line(line):
continue
captured[active_section].append(line)
continue
section = match.group("section")
if section not in expected_sections:
raise HarnessError(f"unexpected marked section: {section}")
kind = match.group("kind")
match kind:
case "begin":
if active_section is not None:
raise HarnessError(
f"nested section {section!r} inside {active_section!r}"
)
if section in captured:
raise HarnessError(f"duplicate marked section: {section}")
captured[section] = [line]
active_section = section
case "end":
if active_section is None:
raise HarnessError(f"end marker without begin marker: {section}")
if active_section != section:
raise HarnessError(
f"mismatched end marker for {section!r}; expected {active_section!r}"
)
captured[section].append(line)
active_section = None
case _:
raise HarnessError(f"invalid marker kind: {kind}")
if active_section is not None:
raise HarnessError(f"unterminated marked section: {active_section}")
missing = [section for section in section_order if section not in captured]
if missing:
raise HarnessError(f"missing marked sections: {', '.join(missing)}")
lines: list[str] = []
for section in section_order:
lines.extend(captured[section])
return "\n".join(lines) + "\n"
def normalize_output(output: str, debugger: DebuggerAdapter) -> str:
"""Normalize unstable values while preserving output structure.
Parameters
----------
output : str
Extracted marked output.
debugger : DebuggerAdapter
Adapter that applies debugger-specific line normalization.
Returns
-------
str
Output with unstable addresses and debugger prefixes normalized.
"""
normalized_lines: list[str] = []
for line in output.splitlines():
line = debugger.normalize_line(line.rstrip())
# Some debuggers may print C++98 style > > for multiple templates.
line = re.sub(r">\s+>", ">>", line)
line = _NONZERO_HEX_PATTERN.sub("<address>", line)
normalized_lines.append(line)
return "\n".join(normalized_lines) + "\n"
def compare_expected(
actual: str, expected: str, debugger: DebuggerAdapter, scenario: str
) -> None:
"""Compare normalized output with its checked-in golden text.
Parameters
----------
actual : str
Normalized debugger output.
expected : str
Checked-in golden output.
debugger : DebuggerAdapter
Adapter for the debugger that produced the output.
scenario : str
Scenario name used in diagnostics.
Raises
------
HarnessError
If actual and expected output differ.
"""
if actual == expected:
return
difference = "".join(
difflib.unified_diff(
expected.splitlines(keepends=True),
actual.splitlines(keepends=True),
fromfile=f"{scenario}/{debugger.kind}.expected",
tofile=f"{scenario}/{debugger.kind}.actual",
)
)
raise HarnessError(
f"{debugger.kind} pretty-printer output mismatch for {scenario}:\n{difference}"
)
def _parse_arguments(arguments: Sequence[str] | None) -> argparse.Namespace:
"""Parse debugger configuration and ordered case definitions.
Parameters
----------
arguments : Sequence[str] or None
Command-line arguments, or ``None`` to use ``sys.argv``.
Returns
-------
argparse.Namespace
Parsed command-line namespace.
"""
parser = argparse.ArgumentParser()
parser.add_argument("--debugger", type=Debugger, choices=Debugger, required=True)
parser.add_argument("--debugger-executable", type=Path, required=True)
parser.add_argument("--program", type=Path, required=True)
parser.add_argument("--formatter-init", type=Path, required=True)
parser.add_argument("--expected", type=Path, required=True)
parser.add_argument("--output-log", type=Path, required=True)
parser.add_argument("--timeout", type=float, default=90.0)
parser.add_argument("--update-expected", action="store_true")
parser.add_argument(
"--case",
dest="cases",
nargs=4,
action=CaseAction,
default=None,
required=True,
)
return parser.parse_args(arguments)
def _create_debugger(args: argparse.Namespace) -> DebuggerAdapter:
"""Create the debugger adapter selected by command-line arguments.
Parameters
----------
args : argparse.Namespace
Parsed runner arguments.
Returns
-------
DebuggerAdapter
Configured adapter for the selected debugger.
Raises
------
HarnessError
If the selected debugger is unsupported.
"""
match args.debugger:
case Debugger.LLDB:
return LLDB(args.debugger_executable, args.formatter_init, args.program)
case Debugger.GDB:
return GDB(args.debugger_executable, args.formatter_init, args.program)
case _:
raise HarnessError(f"unsupported debugger: {args.debugger}")
def _run_debugger(
args: argparse.Namespace, debugger: DebuggerAdapter, command_file: Path
) -> str:
"""Run the debugger and persist its complete transcript.
Parameters
----------
args : argparse.Namespace
Parsed runner arguments.
debugger : DebuggerAdapter
Configured debugger adapter.
command_file : Path
Generated debugger command-file path.
Returns
-------
str
Complete combined debugger output.
Raises
------
DebuggerError
If the debugger cannot launch, times out, or exits with a nonzero status.
OSError
If the transcript cannot be written.
"""
try:
completed = subprocess.run(
debugger.command(command_file),
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT,
text=True,
timeout=args.timeout,
)
except subprocess.TimeoutExpired as error:
if isinstance(error.stdout, str):
args.output_log.write_text(error.stdout)
raise DebuggerError(
f"{debugger.kind} timed out after {args.timeout:g} seconds"
) from error
except OSError as error:
raise DebuggerError(f"failed to launch {debugger.kind}: {error}") from error
args.output_log.write_text(completed.stdout)
if completed.returncode != 0:
raise DebuggerError(
f"{debugger.kind} exited with status {completed.returncode}"
)
return completed.stdout
def _match_output(
args: argparse.Namespace,
debugger: DebuggerAdapter,
cases: Sequence[Case],
transcript: str,
) -> None:
"""Extract, normalize, and compare or update debugger output.
Parameters
----------
args : argparse.Namespace
Parsed runner arguments.
debugger : DebuggerAdapter
Configured debugger adapter.
cases : Sequence[Case]
Validated cases in manifest order.
transcript : str
Complete combined debugger output.
Raises
------
HarnessError
If marked output is invalid or differs from the golden.
OSError
If the golden file cannot be read or updated.
"""
extracted = extract_sections(transcript, [case.section for case in cases], debugger)
actual = normalize_output(extracted, debugger)
if args.update_expected:
args.expected.write_text(actual)
return
compare_expected(
actual,
args.expected.read_text(),
debugger,
args.expected.parent.name,
)
def _report_error(
args: argparse.Namespace,
debugger: DebuggerAdapter,
command_file: Path,
error: Exception,
) -> None:
"""Report a debugger or output-matching failure with artifact paths.
Parameters
----------
args : argparse.Namespace
Parsed runner arguments.
debugger : DebuggerAdapter
Configured debugger adapter.
command_file : Path
Generated debugger command-file path.
error : Exception
Failure being reported.
"""
scenario = args.expected.parent.name
print(
f"error: {debugger.kind} pretty-printer test for {scenario}: {error}",
file=sys.stderr,
)
print(f"debugger commands: {command_file}", file=sys.stderr)
if args.output_log.exists():
print(f"complete transcript: {args.output_log}", file=sys.stderr)
def main(arguments: Sequence[str] | None = None) -> int:
"""Run one debugger pretty-printer test from command-line arguments.
Parameters
----------
arguments : Sequence[str] or None
Command-line arguments, or ``None`` to use ``sys.argv``.
Returns
-------
int
Zero on success and one for handled debugger or matching failures.
Raises
------
HarnessError
If case setup is invalid.
OSError
If setup artifacts or golden files cannot be accessed.
"""
args = _parse_arguments(arguments)
debugger = _create_debugger(args)
commands = debugger.generate_commands(args.cases)
command_file = args.output_log.with_suffix(".commands")
args.output_log.parent.mkdir(parents=True, exist_ok=True)
args.output_log.unlink(missing_ok=True)
command_file.write_text(commands)
try:
transcript = _run_debugger(args, debugger, command_file)
except DebuggerError as error:
_report_error(args, debugger, command_file, error)
return 1
try:
_match_output(args, debugger, args.cases, transcript)
except HarnessError as error:
_report_error(args, debugger, command_file, error)
return 1
return 0
if __name__ == "__main__":
raise SystemExit(main())