Skip to content

[STF] cuda_safe_call<fun>(args...): introspected call forms shared with cuda_try - #11215

Open
andralex wants to merge 8 commits into
NVIDIA:mainfrom
andralex:cuda-safe-call-introspection
Open

[STF] cuda_safe_call<fun>(args...): introspected call forms shared with cuda_try#11215
andralex wants to merge 8 commits into
NVIDIA:mainfrom
andralex:cuda-safe-call-introspection

Conversation

@andralex

@andralex andralex commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

cuda_try<fun>(args...) supports three introspected call shapes (direct, first-output-parameter, last-output-parameter) selected at compile time. That machinery is independent of what happens to the failing status, so this PR extracts it into a shared engine (reserved::checked_api_call, parameterized on the checker) and gives cuda_safe_call the same spelling:

int dev = cuda_safe_call<cudaGetDevice>();   // aborting sibling of cuda_try<cudaGetDevice>()
cuda_safe_call<cudaSetDevice>(0);            // direct form

The two fronts are deliberately parallel so that migrating a call site between the aborting and throwing regimes is a one-token change: cuda_safe_call<f>(a) <-> cuda_try<f>(a). This is groundwork for the planned incremental cuda_safe_call -> cuda_try migration.

Limitations are unchanged and now documented in one place: overloaded names (cudaMalloc and the other templated cuda_runtime.h wrappers) cannot be auto template arguments, and the checker's defaulted source_location fires in the header rather than at the user's call site (a parameter pack followed by a defaulted parameter is non-deduced in C++17; noted for a C++20 revisit).

Unit tests extended with cuda_safe_call siblings of the cuda_try cases; run at C++17 and C++20.

🤖 Generated with Claude Code

…ith cuda_try

The compile-time introspection behind cuda_try<fun> (direct /
first-output / last-output forms, ambiguity rejection) is independent
of what happens to the status, so it moves into a shared engine,
reserved::checked_api_call, parameterized on the checker. cuda_try<fun>
becomes a thin front and cuda_safe_call<fun> joins it as the aborting
sibling, deliberately parallel so migrating a call site between the
aborting and throwing regimes is a one-token change:
cuda_safe_call<f>(a) <-> cuda_try<f>(a).

Same limitations as before, now documented in one place: overloaded
names cannot be template arguments, and the checker's source_location
fires in this header rather than at the user's call site (pack followed
by defaulted parameter is non-deduced in C++17; revisit with C++20).

Unit tests extended with the cuda_safe_call siblings, run at C++17 and
C++20.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andralex
andralex requested a review from a team as a code owner September 4, 2026 15:02
@andralex
andralex requested a review from caugonnet September 4, 2026 15:02
@andralex

andralex commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test f2153aa

@github-project-automation github-project-automation Bot moved this to Todo in CCCL Sep 4, 2026
@copy-pr-bot

copy-pr-bot Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Review in CCCL Sep 4, 2026
@coderabbitai

coderabbitai Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 7e4b2527-0ea1-4809-828d-bb2a7b8a5a44

📥 Commits

Reviewing files that changed from the base of the PR and between 25f089c and 5e25e29.

📒 Files selected for processing (1)
  • cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh
🚧 Files skipped from review as they are similar to previous changes (1)
  • cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh

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


📝 Summary

Summary by CodeRabbit

  • Improvements
    • CUDA call helpers now distinguish directly invocable calls from ambiguous output forms.
    • cuda_try and cuda_safe_call more strictly validate first output parameters, requiring writable pointer outputs and rejecting unsupported or ambiguous signatures.
    • CUDA call diagnostics are clearer for ambiguous or invalid call signatures.
    • Source-location metadata types now enforce safer construction and assignment behavior, including preventing unsupported operations on void-specialized metadata.

Walkthrough

The change updates CUDA call ambiguity detection and first-output constraints. Directly invocable calls are accepted, while first-output forms require writable pointer outputs. with_location<T> assignment is deleted, and with_location<void> cannot be constructed.

Changes

Checked CUDA calls

Layer / File(s) Summary
Checked invocation flow
cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh
ambiguous_output_forms ignores directly invocable calls and analyzes the complete argument list. The first-output overloads of cuda_try and cuda_safe_call require writable pointer outputs.
Invocation traits and location wrappers
cudax/include/cuda/experimental/__stf/utility/source_location.cuh
with_location<T> deletes copy and move assignment. with_location<void> deletes default and copy construction.

Merge Risk: 🟡 Moderate · up to 5e25e

Parameterless CUDA APIs may fail to compile through cuda_try or cuda_safe_call because output-parameter traits are instantiated for calls with no parameters. This should be resolved before merge.


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: 1

🧹 Nitpick comments (3)
cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh (3)

456-456: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

suggestion: Mark reserved::checked_api_call, cuda_try<fun>, and cuda_safe_call<fun> with _CCCL_HOST_API. Each function uses host-only error handling. The project rule requires an API annotation on all functions. As per coding guidelines, “Functions must be marked with _CCCL_HOST_API, _CCCL_DEVICE_API, _CCCL_HOST_DEVICE_API, _CCCL_TILE_API, or _CCCL_API.”

Also applies to: 502-502, 521-521

Source: Coding guidelines


505-505: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

suggestion: Declare both lambda status parameters as const auto. Neither lambda modifies the status value. As per coding guidelines, “All variables that are not modified must be declared const.”

Also applies to: 524-524

Source: Coding guidelines


499-499: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

suggestion: Use _CCCL_DOXYGEN_INVOKED in this #endif comment. The comment currently uses a negated spelling instead of the condition text from line 446. Based on learnings, annotated #endif comments must repeat the exact condition text from the corresponding #if.

Source: Learnings


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 61229d17-25c9-4687-bb6d-6d89281f66f4

📥 Commits

Reviewing files that changed from the base of the PR and between c6cdc15 and f2153aa.

📒 Files selected for processing (1)
  • cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh

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

Comment thread cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh Outdated
@andralex
andralex enabled auto-merge (squash) September 4, 2026 15:11
…location

The introspected forms previously reported errors at this header's line:
with a trailing parameter pack, a defaulted source_location parameter
is a non-deduced context, so no variadic front can capture the call
site. The fronts are now a bounded-arity overload set (arities 0..8,
stamped out mechanically), each carrying a trailing defaulted
source_location::current() that fires where the user wrote the call;
the shared engine threads it through to the status checker. Call
syntax is unchanged. No CUDA entry point is known to take more than
eight direct parameters.

Verified by probe: a failing introspected call now reports the
caller's file, line, and function rather than cuda_safe_call.cuh.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh (1)

462-470: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

important: Parameterless entry points still fail to compile.

first_output_form and last_output_form instantiate reserved::first_param<fun> and reserved::last_param<fun> eagerly. Both operands of && must be well-formed, so direct_form does not shield them. For a zero-parameter function such as cudaDeviceSynchronize or cudaGetLastError, first_param_impl<cudaError_t (*)()> and last_param_impl<> are declared but not defined, so the header hard-errors on an incomplete type.

Add the nullary specializations, then add coverage for a parameterless function and for the <fun> direct form in both UNITTEST("cuda_try2") and UNITTEST("cuda_safe_call2").

Trait specializations
 template <typename R, typename P, typename... Ps>
 struct first_param_impl<R (*)(P, Ps...)>
 {
   using type = P;
 };
+
+// A parameterless function has no first parameter; report `void` so the
+// pointer/const checks in `checked_api_call` evaluate to `false`.
+template <typename R>
+struct first_param_impl<R (*)()>
+{
+  using type = void;
+};
 
 template <typename...>
 struct last_param_impl;
+
+// Same rationale for a parameterless function's last parameter.
+template <>
+struct last_param_impl<>
+{
+  using type = void;
+};
#!/bin/bash
set -euo pipefail

f=$(fd -t f 'cuda_safe_call.cuh' cudax)
echo "file: $f"

# Do nullary specializations exist on the PR head?
ast-grep run --lang cpp --pattern 'struct first_param_impl<$$$> { $$$ }' "$f"
ast-grep run --lang cpp --pattern 'struct last_param_impl<$$$> { $$$ }' "$f"

# Any existing caller that would instantiate the broken path today.
rg -nP 'cuda_(try|safe_call)<\s*(cudaDeviceSynchronize|cudaGetLastError|cudaPeekAtLastError|cudaThreadExit|cuInit)\s*>' \
  --glob '*.{h,hpp,cuh,cu,cpp,cxx}' .
🧹 Nitpick comments (1)
cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh (1)

508-527: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

suggestion: Generate the arity fronts with a macro.

Arities 0..8 are repeated twice, once per API, for about 320 lines. The bodies differ only in the checker call. A local macro plus #undef keeps the two overload sets in sync and makes an arity bump a one-line change.

Sketch
+#define _CCCL_STF_CHECKED_FRONT(name)                                                                    \
+  template <auto fun>                                                                                    \
+  auto name(const ::cuda::std::source_location loc = ::cuda::std::source_location::current())            \
+  {                                                                                                      \
+    return reserved::checked_api_call<fun>(                                                              \
+      [](auto status, auto l) {                                                                          \
+        name(status, l);                                                                                 \
+      },                                                                                                 \
+      loc);                                                                                              \
+  }                                                                                                      \
+  /* ... one block per arity, or a nested arity macro ... */
+
+_CCCL_STF_CHECKED_FRONT(cuda_try)
+_CCCL_STF_CHECKED_FRONT(cuda_safe_call)
+#undef _CCCL_STF_CHECKED_FRONT

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: df5a81aa-8d28-44c8-9be5-554b321862c7

📥 Commits

Reviewing files that changed from the base of the PR and between f2153aa and 661431d.

📒 Files selected for processing (1)
  • cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh

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

@caugonnet

Copy link
Copy Markdown
Contributor

Nice !

@github-actions

This comment has been minimized.

@caugonnet caugonnet self-assigned this Sep 4, 2026
@caugonnet caugonnet added the stf Sequential Task Flow programming model label Sep 4, 2026
andralex and others added 2 commits September 4, 2026 16:30
Supersedes the fixed-arity overload sets of the previous commit with
the with_location design.

The introspected forms reported errors at this header's line: with a
trailing parameter pack, a defaulted source_location is a non-deduced
context, so no variadic front can capture the call site. The first
user argument (when there is one) is now taken as with_location<T>,
where T is COMPUTED from fun's signature rather than deduced. The
parameter type is therefore concrete, the raw argument converts to it,
and with_location's constructor captures the location at the point of
conversion: the user's call site. Call syntax is unchanged and the
fronts stay variadic.

Two argument-taking overloads per name: the user's first argument
aligns with fun's first parameter for the direct and last-output
forms, and with its second parameter for the first-output form, where
parameter one is the synthesized output pointer. A third overload
handles calls with no user arguments, where a plain defaulted
source_location suffices (no pack to block it).

forward<declared type> preserves the parameter's value category, so
reference parameters pass through unchanged; the engine's form
detection now uses a pointee trait that stays well-formed for
reference parameters, where remove_pointer_t<T>* was ill-formed for
T = X&.

Tests: a regression unittest pinning the reported line to the caller's,
a check that an lvalue-reference parameter is written through, and an
external probe covering all four call shapes at C++17 and C++20.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andralex

andralex commented Sep 4, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 84b5611

@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

🧹 Nitpick comments (2)
cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh (2)

701-702: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

suggestion: These lines use ::std::string and ::std::to_string, but the file does not include <string>. Add the include so the test does not depend on a transitive include.

 `#include` <cstdlib>
 `#include` <exception>
+#include <string>

As per coding guidelines: "Include all headers needed by the symbols being used; do not rely on transitive includes."

Source: Coding guidelines


250-254: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

suggestion: Remove the unused defaulted template parameter from first_param_impl. Its specializations and uses provide only the function-type argument, and no code in this header uses the second parameter.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: d968075f-e80d-4326-be43-02d6e4dc0c88

📥 Commits

Reviewing files that changed from the base of the PR and between 661431d and 84b5611.

📒 Files selected for processing (1)
  • cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh

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

Comment thread cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh
@github-actions

This comment has been minimized.

…_location

Three fixes on top of the location-capture design:

Ambiguity rejection is back to a curated static_assert. With the
front-based design, a function whose first and last parameters are both
non-const output pointers (cudaMemGetInfo) made the two argument-taking
fronts ambiguous, so the diagnostic became an overload race and the
cuda_try_ambiguous XFAIL test, which greps for 'static assertion',
failed. A reserved::ambiguous_output_forms trait now excludes those
cases from every front and selects a catch-all whose body carries the
original message.

The doc block above the introspected forms still declared @PARAM ps
from the previous variadic front and now sat above the zero-argument
overload, which the documentation build rejects. It describes the
overload set instead, without per-parameter tags.

with_location's copy and move assignment are deleted: when T is a
reference, assigning would write through the referent rather than
rebind it, silently modifying the caller's object.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andralex

andralex commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 25f089c

@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: b0e03874-aaf4-45c6-9983-fc6c3fd088d0

📥 Commits

Reviewing files that changed from the base of the PR and between 84b5611 and 25f089c.

📒 Files selected for processing (2)
  • cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh
  • cudax/include/cuda/experimental/__stf/utility/source_location.cuh

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

Comment thread cudax/include/cuda/experimental/__stf/utility/cuda_safe_call.cuh Outdated
…onst pointees

Three defects in the location-capturing fronts, all found by the CI
build failures and review:

The ambiguity trait ignored the direct form and was evaluated without
the argument the front had already consumed. CUDA handle types are
themselves pointers, so cudaFreeAsync(void*, cudaStream_t) looked like
it had output pointers at both ends and every call was rejected as
ambiguous; this broke 37 build jobs. The trait now takes the arguments
as the engine will see them and tests direct invocability first: if
fun(args...) is a valid call, nothing is synthesized and nothing can be
ambiguous.

The first-output fronts did not carry the engine's guards on the
synthesized parameter. For f(const int*, int), cuda_try<f>(1) created a
const int, passed its address as an input pointer, and returned the
never-written default. They now require a non-void, non-const pointee,
matching first_output_form exactly.

Tests: a call-shapes unittest covering derived-pointer-to-void
conversions, handle out-parameters, enum-keyed queries, nullary APIs
and the direct forms, plus documented negative shapes. Fifteen call
shapes taken from real in-tree call sites compile and run at C++17 and
C++20.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andralex

andralex commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 5e25e29

CTK 12.0's front end hits Internal Compiler Error (codegen):
"unexpected expression with aggregate type!" on the location-capturing
fronts once the full STF header set is in the translation unit. Every
isolated reproduction compiles cleanly on 12.0 (the wrapper by value,
the parameter pack, the _CCCL_TEMPLATE constraints, the engine with its
checker lambda, pointer conversions, all of them together), so the
trigger needs more of the stack than can be reduced here; 12.1 and
every later toolkit through 13.3 are unaffected.

On nvcc 12.0 only, the introspected forms use the plain variadic fronts
instead: identical call syntax and semantics, with a failing call
reporting this header's line rather than the caller's, which is the
behavior that shipped before this PR. Host compilers and every other
toolkit keep the caller's location. The location-capture unittest is
gated to match.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@andralex

andralex commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

/ok to test 6b68580

@github-actions

github-actions Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

🥳 CI Workflow Results

🟩 Finished in 1h 17m: Pass: 100%/63 | Total: 1d 07h | Max: 51m 35s | Hits: 13%/192585

See results here.

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

Labels

stf Sequential Task Flow programming model

Projects

Status: In Review

Development

Successfully merging this pull request may close these issues.

2 participants