Skip to content

[libcu++] Remove cudart dependency from cuda_error - #11086

Open
davebayer wants to merge 1 commit into
NVIDIA:mainfrom
davebayer:fix_driver_apis
Open

[libcu++] Remove cudart dependency from cuda_error#11086
davebayer wants to merge 1 commit into
NVIDIA:mainfrom
davebayer:fix_driver_apis

Conversation

@davebayer

Copy link
Copy Markdown
Contributor

Currently, we use CUDA Runtime APIs in cuda_error which makes life more difficult for some of our customers, because it introduces an unwanted dependency.

The function we use is cudaGetErrorString that has a driver API equivalent. This PR refactors the code, so we can use cuGetErrorString instead.

However, this comes with some non-trivial issues. In our machinery that obtains the function entry points for us, we throw cuda_error if something goes wrong. That one would use cuGetErrorString to get the error message, which would try to get the entry point, that would... There is a circular dependency.

To work around this problem, I hardcoded several error messages to our headers, so we avoid the cuGetErrorString call in those cases.

This PR also fixes uses of _CCCL_TRY_CUDA_API in our driver APIs, that would introduce a dependency on CUDA Runtime again, because it calls cudaGetLastError when cleaning the error state.

@davebayer
davebayer requested a review from a team as a code owner September 1, 2026 09:57
@github-project-automation github-project-automation Bot moved this to Todo in CCCL Sep 1, 2026
@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Review in CCCL Sep 1, 2026
@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Summary by CodeRabbit

  • Bug Fixes

    • Improved CUDA driver API error handling across device, memory, stream, event, kernel, graph, allocation, and related operations.
    • Added clearer and more reliable CUDA error descriptions.
    • Improved driver initialization and function lookup across supported environments.
  • Compatibility

    • Enhanced support for versioned CUDA driver functions.
    • Improved compatibility with hosted and non-hosted environments.

Walkthrough

The change adds centralized CUDA driver entry-point resolution and error handling. It removes legacy driver-call helpers, updates cuda_error, and migrates affected driver API wrappers to _CCCL_TRY_DRIVER_API.

Changes

CUDA driver API

Layer / File(s) Summary
Driver entry-point resolution
libcudacxx/include/cuda/__driver/entry_point.h
Hosted and non-hosted builds resolve CUDA driver symbols through initialization-aware helpers. Versioned lookup, driver loading, error reporting, and function-retrieval macros are added.
CUDA error construction
libcudacxx/include/cuda/std/__exception/cuda_error.h
cuda_error now formats explicit error strings. Toolkit builds retrieve strings through the driver entry-point layer. Selected error codes receive compile-time message mappings.
Driver wrapper status handling
libcudacxx/include/cuda/__driver/driver_api.h
Driver initialization and affected device, context, memory, stream, event, kernel, library, graph, peer-access, green-context, and tensor-map wrappers use _CCCL_TRY_DRIVER_API.

Suggested reviewers: ericniebler

Merge Risk: 🟡 Moderate · up to c2a87

This PR removes the CUDA Runtime dependency from CUDA error handling, but the current implementation still has unresolved compile, link, and header-compatibility risks that could break Windows builds, standalone header consumers, or NVRTC-related builds. It is not merge-ready until these issues are addressed or explicitly accepted.

✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
libcudacxx/include/cuda/__driver/entry_point.h (1)

66-66: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

suggestion: In the _CCCL_OS(WINDOWS) branch, ::GetProcAddress returns FARPROC, while __fn is void*. MSVC accepts this implicit conversion as an extension, but other Windows toolchains can reject it. Use an explicit reinterpret_cast<void*> to avoid relying on the extension.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: f7e78a39-2e64-4f6e-a6c1-df26bb5f11c9

📥 Commits

Reviewing files that changed from the base of the PR and between c398e4b and 694576a.

📒 Files selected for processing (3)
  • libcudacxx/include/cuda/__driver/driver_api.h
  • libcudacxx/include/cuda/__driver/entry_point.h
  • libcudacxx/include/cuda/std/__exception/cuda_error.h

Included review availability: Your plan provides up to 12 included reviews per hour; 11 remain after this review.

static auto __driver_fn = _CCCLRT_GET_DRIVER_FUNCTION(cuCtxGetDevice);
::CUdevice __result{};
::cuda::__driver::__call_driver_fn(__driver_fn, "Failed to get current context", &__result);
_CCCL_TRY_DRIVER_API(__driver_fn, "Failed to get current context", &__result);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

suggestion: The message duplicates the one used by __ctxGetCurrent on line 244. This call queries the device of the current context, so the message misreports the failed operation.

Fix
-  _CCCL_TRY_DRIVER_API(__driver_fn, "Failed to get current context", &__result);
+  _CCCL_TRY_DRIVER_API(__driver_fn, "Failed to get the device of the current context", &__result);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_CCCL_TRY_DRIVER_API(__driver_fn, "Failed to get current context", &__result);
_CCCL_TRY_DRIVER_API(__driver_fn, "Failed to get the device of the current context", &__result);

Comment on lines +44 to +48
template <::cudaError_t _Error>
[[noreturn]] _CCCL_HOST_API void __throw_cuda_error(
const char* __msg,
const char* __api = nullptr,
const ::cuda::std::source_location& __loc = ::cuda::std::source_location::current());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
# Confirm the definition sites of __throw_cuda_error and __init and the include edges between the three headers.
set -euo pipefail

fd -t f 'entry_point.h|driver_api.h|cuda_error.h' libcudacxx/include | while IFS= read -r f; do
  echo "=== $f"
  rg -n '^\s*#\s*include' "$f"
  rg -n '__throw_cuda_error|__init\b' "$f"
done

Repository: NVIDIA/cccl

Length of output: 2783


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== entry_point.h: relevant definitions and call paths'
sed -n '35,190p' libcudacxx/include/cuda/__driver/entry_point.h

echo '=== driver_api.h: __init definition and header guards'
sed -n '1,85p' libcudacxx/include/cuda/__driver/driver_api.h

echo '=== cuda_error.h: include guard and __throw_cuda_error definition'
sed -n '1,125p' libcudacxx/include/cuda/std/__exception/cuda_error.h

echo '=== direct consumers of entry_point.h and driver_api.h'
rg -n -C 2 '# *include <cuda(/std)?/__driver/(entry_point|driver_api)\.h>|__getProcAddressFn|__get_driver_entry_point(_no_init)?' libcudacxx/include libcudacxx/test 2>/dev/null || true

Repository: NVIDIA/cccl

Length of output: 32713


important: Provide definitions for __throw_cuda_error and __init to standalone users of entry_point.h. Calls through __getProcAddressFn, __get_driver_entry_point_no_init, or __get_driver_entry_point can emit unresolved references because this header provides only declarations, while the definitions are in cuda_error.h and driver_api.h.

static void* __driver_library = ::dlopen(__driver_library_name, RTLD_NOW);
if (__driver_library == nullptr)
{
::cuda::__throw_cuda_error<::cudaErrorUnknown>("Failed to load libcuda.so.1");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

suggestion: On Android the loaded library is libcuda.so, but the failure message always reports libcuda.so.1. Report the name that was actually used. The same applies to the dlsym message on line 85.

Fix
-    ::cuda::__throw_cuda_error<::cudaErrorUnknown>("Failed to load libcuda.so.1");
+    ::cuda::__throw_cuda_error<::cudaErrorUnknown>("Failed to load the CUDA driver library");

# pragma system_header
#endif // no system header

#include <cuda/__driver/entry_point.h>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
# 1) Show the guards around the cuda_error class. 2) Show the includes entry_point.h drags in.
set -euo pipefail

f=$(fd -t f 'cuda_error.h' libcudacxx/include | head -n1)
echo "=== $f"
rg -n '#\s*if|#\s*else|#\s*endif|#\s*include|class cuda_error|__getErrorString' "$f"

e=$(fd -t f 'entry_point.h' libcudacxx/include | head -n1)
echo "=== $e"
rg -n '#\s*if|#\s*include|WIN32_LEAN_AND_MEAN|NOMINMAX' "$e"

Repository: NVIDIA/cccl

Length of output: 1772


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== cuda_error.h: class and constructor branches'
sed -n '30,110p' libcudacxx/include/cuda/std/__exception/cuda_error.h

echo '=== entry_point.h: declarations and platform includes'
sed -n '20,115p' libcudacxx/include/cuda/__driver/entry_point.h

echo '=== entry_point.h: remaining scope and getErrorString references'
sed -n '115,205p' libcudacxx/include/cuda/__driver/entry_point.h
rg -n --glob '*.{h,hpp,cu,cpp,cuh}' '__getErrorString|_CCCL_HAS_CTK\(\)|_CCCL_COMPILER\(NVRTC\)' libcudacxx/include/cuda | head -120

echo '=== relevant macro definitions'
rg -n '_CCCL_(HAS_CTK|HOSTED|COMPILER|OS)' libcudacxx/include/cuda/std/detail/__config libcudacxx/include/cuda/__driver/entry_point.h | head -120

Repository: NVIDIA/cccl

Length of output: 21840


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== hosted/compiler macro definitions'
rg -n --glob '*.{h,hpp,inc}' \
  '#[[:space:]]*define[[:space:]]+_CCCL_HOSTED|`#define`[[:space:]]+_CCCL_HOSTED|_CCCL_HOSTED\(\)|_CCCL_COMPILER\(NVRTC\)' \
  libcudacxx/include/cuda libcudacxx/include/cuda/std/detail | head -160

echo '=== Windows header protections in the relevant include tree'
rg -n --glob '*.{h,hpp,inc}' \
  'WIN32_LEAN_AND_MEAN|NOMINMAX|`#include`[[:space:]]*[<"]windows\.h[>"]' \
  libcudacxx/include/cuda libcudacxx/include/cuda/std | head -120

Repository: NVIDIA/cccl

Length of output: 14803


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== configuration candidates'
fd -t f '__config|config' libcudacxx/include | head -80

echo '=== macro definitions in configuration candidates'
files=$(fd -t f '__config|config' libcudacxx/include)
rg -n \
  '#[[:space:]]*define[[:space:]]+_CCCL_(HOSTED|FREESTANDING)|_CCCL_HOSTED|_CCCL_COMPILER|NVRTC' \
  $files | head -180

Repository: NVIDIA/cccl

Length of output: 755


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== cuda/std/detail/__config'
sed -n '1,140p' libcudacxx/include/cuda/std/detail/__config

echo '=== cuda/__cccl_config'
sed -n '1,180p' libcudacxx/include/cuda/__cccl_config

echo '=== definitions in direct configuration area'
rg -n --glob '__config*' --glob '__cccl_config*' --glob '*.h' --glob '*.hpp' \
  '#[[:space:]]*define[[:space:]]+_CCCL_(HOSTED|FREESTANDING)|_CCCL_HOSTED|_CCCL_COMPILER|NVRTC' \
  libcudacxx/include | head -180

Repository: NVIDIA/cccl

Length of output: 22075


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '=== hosted and device-compilation definitions'
rg -n \
  '#[[:space:]]*define[[:space:]]+_CCCL_(HOSTED|FREESTANDING|DEVICE_COMPILATION)|_CCCL_(HOSTED|FREESTANDING|DEVICE_COMPILATION)\(\)' \
  libcudacxx/include/cuda/std/__cccl libcudacxx/include/cuda/std/__internal | head -160

echo '=== NVRTC host/device policy comments and conditionals'
rg -n -C 2 \
  'NVRTC compiles device code only|NVRTC.*host|_CCCL_COMPILER\(NVRTC\).*_CCCL_HOSTED|_CCCL_HOSTED\(\).*NVRTC' \
  libcudacxx/include/cuda/std/__cccl libcudacxx/include/cuda/std/__internal libcudacxx/include/cuda | head -120

Repository: NVIDIA/cccl

Length of output: 7162


important: Protect the windows.h inclusion in entry_point.h. On hosted Windows builds with _CCCL_HAS_CTK() enabled, cuda_error.h exposes macros such as min and max to every including translation unit. Define WIN32_LEAN_AND_MEAN and NOMINMAX around the include, or avoid the full Windows header.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 9c7c7b74-edfe-427c-9ed9-ac28be000fe1

📥 Commits

Reviewing files that changed from the base of the PR and between 694576a and c2a87f6.

📒 Files selected for processing (1)
  • libcudacxx/include/cuda/std/__exception/cuda_error.h

Included review availability: Your plan provides up to 12 included reviews per hour; 10 remain after this review.

Comment on lines +108 to +110
template <int _Error>
[[noreturn]] friend _CCCL_HOST_API void
__throw_cuda_error(const char* __msg, const char* __api, const ::cuda::std::source_location& __loc)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 3 --glob '*.{h,hpp,cpp,cxx,cu,cuh}' \
  '\b__throw_cuda_error\b|_CCCL_TRY_DRIVER_API\b' libcudacxx

# Verify that a namespace-scope declaration is visible before this class.
# Then compile the nearest driver-wrapper error-path test with the supported toolchain.

Repository: NVIDIA/cccl

Length of output: 33304


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- cuda_error.h ---'
cat -n libcudacxx/include/cuda/std/__exception/cuda_error.h | sed -n '1,145p'

printf '%s\n' '--- entry_point.h includes and declaration ---'
cat -n libcudacxx/include/cuda/__driver/entry_point.h | sed -n '1,60p'

printf '%s\n' '--- driver_api.h includes and macro body ---'
cat -n libcudacxx/include/cuda/__driver/driver_api.h | sed -n '1,58p'

printf '%s\n' '--- direct declarations and include graph references ---'
rg -n -C 2 --glob '*.{h,hpp,cpp,cxx,cu,cuh}' \
  '#[[:space:]]*include[[:space:]]*[<"]cuda/(std/__exception/cuda_error|__driver/entry_point)|\b__throw_cuda_error\b' \
  libcudacxx/include/cuda

Repository: NVIDIA/cccl

Length of output: 32824


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- cudaError_t declarations ---'
rg -n -C 3 --glob '*.{h,hpp,cpp,cxx,cu,cuh}' \
  'typedef[[:space:]]+enum[[:space:]]+cudaError|enum[[:space:]]+cudaError|cudaError_t' \
  . | head -160

printf '%s\n' '--- all helper definitions and qualified calls ---'
rg -n -C 2 --glob '*.{h,hpp,cpp,cxx,cu,cuh}' \
  'template[[:space:]]*<[^>]*(_Error|cudaError_t)|(::cuda::)?__throw_cuda_error' \
  libcudacxx/include/cuda

Repository: NVIDIA/cccl

Length of output: 17876


🌐 Web query:

CUDA Runtime API cudaError_t typedef enum cudaError definition cuda_runtime_api.h

💡 Result:

In the CUDA Runtime API, cudaError_t is a typedef for the enumeration type cudaError [1][2][3]. This definition is located within the header file cuda_runtime_api.h (or more generally, headers included by it, such as driver_types.h) [1][4]. The enumeration defines the various error codes that can be returned by CUDA Runtime API functions [3][5]. Developers typically use the cudaError_t type for variables intended to store or return these error codes [5]. The official CUDA documentation classifies this as part of the data types used by the CUDA Runtime [1][3].

Citations:


**important: Make the friend definition use ::cudaError_t to match the namespace-scope declaration. The current template <int _Error> defines a different hidden template, so driver-wrapper calls may link without a definition. **

// We can't use ordinary cuda_error's constructor here, because it uses cuGetErrorString to get the error string which
// might be unavailable if we fail to dlopen the driver or query the cuGetProcAddress function. Instead, we hardcode
// several known error strings that are used in these APIs.
template <::cudaError_t _Error>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this need to be templated? I guess the intent is that we check at compile-time that it covers all the error messages we hardcoded, but I think we can instead use clangs [[clang::enable_if]] (https://clang.llvm.org/docs/AttributeReference.html#enable-if) to catch this, which is much cheaper.

Comment thread libcudacxx/include/cuda/__driver/entry_point.h
{
const char* __fn_name = "cuGetProcAddress_v2";
# if _CCCL_OS(WINDOWS)
static auto __driver_library = ::LoadLibraryExA("nvcuda.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static auto __driver_library = ::LoadLibraryExA("nvcuda.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
static const auto __driver_library = ::LoadLibraryExA("nvcuda.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);

(and the other statics here as well while we're in the area).

auto __driver_fn = reinterpret_cast<decltype(::cuInit)*>(
::cuda::__driver::__get_driver_entry_point_impl(__get_proc_addr_fn, "cuInit", 12, 0));
::cuda::__driver::__call_driver_fn(__driver_fn, "Failed to initialize CUDA Driver", 0);
const auto __driver_fn =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
const auto __driver_fn =
static const auto __driver_fn =

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is intentional. __init is called only once, initializing a dummy static bool variable in __get_driver_entry_point

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right but if __init fails, do we also want to reload the function? Or cache it here as well. With the current setup, if __init fails, then the next time it's called it will load the driver function from scratch, making it static will essentially cache the function load.

|| ::cuda::std::is_same_v<decltype(__cccl_try_driver_api_status), ::cudaError_t>); \
if (static_cast<::cudaError_t>(__cccl_try_driver_api_status) != ::cudaSuccess) \
{ \
_CCCL_THROW(::cuda::cuda_error, static_cast<::cudaError_t>(__cccl_try_driver_api_status), _MSG); \

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TBH, now that this is a macro, it may be useful to include the stringified call in the error message

Suggested change
_CCCL_THROW(::cuda::cuda_error, static_cast<::cudaError_t>(__cccl_try_driver_api_status), _MSG); \
_CCCL_THROW(::cuda::cuda_error, static_cast<::cudaError_t>(__cccl_try_driver_api_status), #_FN "(" #__VA_ARGS__ "): " _MSG); \

# if _CCCL_HOSTED()
//! @brief Gets the cuGetProcAddress function pointer.
template <class = void>
[[nodiscard]] _CCCL_PUBLIC_HOST_API inline auto __getProcAddressFn() -> decltype(cuGetProcAddress)*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[[nodiscard]] _CCCL_PUBLIC_HOST_API inline auto __getProcAddressFn() -> decltype(cuGetProcAddress)*
[[nodiscard]] _CCCL_PUBLIC_HOST_API auto __getProcAddressFn() -> decltype(cuGetProcAddress)*

(inline is implicit from template)

Comment on lines +126 to +127
[[nodiscard]] _CCCL_PUBLIC_HOST_API inline void* __get_driver_entry_point_no_init(
const char* __name, [[maybe_unused]] int __major = 12, [[maybe_unused]] int __minor = 0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
[[nodiscard]] _CCCL_PUBLIC_HOST_API inline void* __get_driver_entry_point_no_init(
const char* __name, [[maybe_unused]] int __major = 12, [[maybe_unused]] int __minor = 0)
[[nodiscard]] _CCCL_PUBLIC_HOST_API void* __get_driver_entry_point_no_init(
const char* __name, int __major = 12, int __minor = 0)

return __fn;
}

[[nodiscard]] _CCCL_HOST_API inline const char* __getErrorString(::cudaError_t __error)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: can we have a version of this that uses CUresult as well? Can just call this version and cast the argument

//!
//! @throws @c cuda::cuda_error if the symbol cannot be obtained or the CUDA driver failed to initialize.
[[nodiscard]] _CCCL_PUBLIC_HOST_API inline void*
__get_driver_entry_point(const char* __name, [[maybe_unused]] int __major = 12, [[maybe_unused]] int __minor = 0)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
__get_driver_entry_point(const char* __name, [[maybe_unused]] int __major = 12, [[maybe_unused]] int __minor = 0)
__get_driver_entry_point(const char* __name, int __major = 12, int __minor = 0)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

2 participants