Skip to content

[STF] errsink: a named dynamic is viewed, not cloned; an unhooked success passes unboxed - #12325

Merged
andralex merged 3 commits into
NVIDIA:mainfrom
andralex:error-sinks-d
Oct 29, 2026
Merged

andralex merged 3 commits into
NVIDIA:mainfrom
andralex:error-sinks-d

Conversation

@andralex

Copy link
Copy Markdown
Contributor

Ports the two shell fixes of #12000 (@caugonnet) onto the sink-is-its-own-carrier design that landed in #12107.

  1. A named dynamic is viewed by << and ->*, not cloned. The left operand of a sink's operators, __sink_at<Self>, held the sink by value; for dynamic that copy clones the erased model, so every guarded call on a named erased sink allocated. The capture now holds __sink_storage_t<Self>: the sink itself for concrete policies, __dynamic_view for dynamic. A non-const lvalue is viewed, an rvalue is moved in (a pointer move), and a const lvalue is still copied, since its hooks would otherwise run on a const object. The friend operators name their first parameter through __sink_at_for<Self, X> so that, defined inside dynamic, they do not instantiate the capture before its storage type is complete.

  2. A dynamic over a policy without a success hook passes the value unboxed. sink_base records at erasure time whether the wrapped policy has a success hook. When it has none, dynamic::on_success returns the callable's value as it is, skipping the std::any box, virtual call, and unbox. Hand-written models keep the conservative default.

Tests: a counting model shows zero clones for lvalue and rvalue use and one for a const lvalue; a copy-counting result type shows zero copies through dynamic(noop) and a hooked policy still sees the value. Host run of the header's unit tests, the 16 static-error tests, and a no-exceptions syntax pass are clean.

These are the two fixes the paper's dynamic-column numbers were measured on (#12000 head). Once this lands, #12000 can be closed.

🤖 Generated with Claude Code

andralex and others added 2 commits October 28, 2026 20:39
The left operand of a sink's operators, `__sink_at<Self>`, held the sink by
value. For `dynamic` that copy clones the erased model, so every guarded
call on a named erased sink allocated. The capture now holds
`__sink_storage_t<Self>`, which is the sink itself for every concrete
policy and `__dynamic_view` for `dynamic`: a non-const lvalue is viewed,
an rvalue is moved in (a pointer move), and a const lvalue is still copied,
since its hooks would otherwise run on a const object.

The friend operators name their first parameter through `__sink_at_for<Self, X>`
so that the capture type depends on `X`: defined inside the class, they
would otherwise instantiate `__sink_at<dynamic>` before its storage type
can be complete.

Ports the first half of NVIDIA#12000 (Cédric Augonnet) onto the sink-is-its-own-
carrier design of NVIDIA#12107.

Co-Authored-By: Cédric Augonnet <caugonnet@nvidia.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…e unboxed

`sink_base` records at erasure time whether the wrapped policy has a
success hook. When it has none, `dynamic::on_success` returns the
callable's value as it is instead of boxing it in `std::any`, calling the
model, and unboxing it. Hand-written models keep the conservative default
and still go through the box.

Ports the second half of NVIDIA#12000 (Cédric Augonnet) onto NVIDIA#12107.

Co-Authored-By: Cédric Augonnet <caugonnet@nvidia.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@andralex
andralex requested a review from a team as a code owner October 29, 2026 00:59
@copy-pr-bot

copy-pr-bot Bot commented Oct 29, 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.

@andralex
andralex requested a review from caugonnet October 29, 2026 00:59
@github-project-automation github-project-automation Bot moved this to Todo in CCCL Oct 29, 2026
@andralex

Copy link
Copy Markdown
Contributor Author

/ok to test e9cd371

@coderabbitai

coderabbitai Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Summary

Summary by CodeRabbit

  • Improvements
    • Successful dynamic operations now return values directly when no success callback is configured; when one is configured, it continues to receive the result. Void operations invoke a success callback only when available.
    • Named mutable inputs are viewed rather than copied, while temporary inputs are moved and immutable inputs are copied. This preserves the appropriate input behavior for each case.

Sink carriers now use specialized storage for dynamic policies. Named non-const dynamic objects are viewed, rvalues are moved, and const lvalues are copied. Dynamic policies record success-hook availability and use it to select result handling.

Changes: Dynamic sink operations

Layer / File(s) Summary
Dynamic sink storage and forwarding
cudax/include/cuda/experimental/__stf/utility/exception_policy.cuh
Sink carriers use customizable storage. Dynamic sink operations use a view that references non-const lvalues, owns rvalues, and copies const lvalues. Tests cover clone behavior.
Success-hook metadata and result handling
cudax/include/cuda/experimental/__stf/utility/exception_policy.cuh
Dynamic models record whether a wrapped policy has a success hook. Successful calls return results directly when no hook exists and use boxed results when a hook exists. Tests cover result copying and hook invocation.

Possibly related PRs

  • NVIDIA/cccl#12000: Implements the same sink-view and success-hook behaviors for a different sink design.
  • NVIDIA/cccl#12107: Introduces the dynamic sink design and contracts that this change updates.

Suggested reviewers: caugonnet

Priority: Normal

Change: Bug fix

Merge Risk: Moderate · up to 432fd

Named dynamic carriers can no longer be copied, so chaining from a named carrier stops compiling. A carrier returned from a scope that owns the dynamic now points to a destroyed object. Settle the lifetime contract and restore copyability before merging.

- [ ] Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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


Review info
Run configuration
  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: 06b483c
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: ef563774-1252-4d64-a1f4-c7e11ce1a563
Commits

Reviewing files that changed from the base of the PR and between 06b483c and e9cd371.

Files selected for processing: 1
  • cudax/include/cuda/experimental/__stf/utility/exception_policy.cuh

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

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

This comment has been minimized.

…ent the viewed dynamic

The test's local `counting` class was not a policy under GCC: its raw
hook took `std::function&`, which the capability probe's archetype cannot
bind to, so normalization made it a `subst_t` and the success hook never
ran. The policy is now at namespace scope with the templated `on_thrown`
hook, and the test asserts the capability it relies on.

The `sink` overview says a named sink is copied into each expression; a
named non-const `dynamic` is viewed instead, so the paragraph says so.

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

Copy link
Copy Markdown
Contributor Author

/ok to test 432fd94

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

⚠️ Outside diff range comments (2)

Major · 🎯 Functional Correctness · exception_policy.cuh:5021

cudax/include/cuda/experimental/__stf/utility/exception_policy.cuh:5021
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

important: Keep dynamic carriers copyable.

auto c = d << [] {}; c ->* [] {}; no longer compiles because operator->* takes the carrier by value and this constructor is deleted. The documented carrier API supports ->*, and the previous dynamic carrier was copyable. Implement copy semantics for the view, including its owned-storage case.

Major · 🩺 Stability & Availability · exception_policy.cuh:5007

cudax/include/cuda/experimental/__stf/utility/exception_policy.cuh:5007
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

important: Returned carriers can hold a dangling pointer to a named dynamic.

__dynamic_view(dynamic& __d) stores &__d, and its move constructor keeps that pointer for lvalue views. A carrier built from d << f is returned by value through the sink operators. If it leaves the scope that owns d, for example auto make() { dynamic d{...}; return d << f; }, the carrier points to a destroyed object. A later << or ->* then reads through that pointer.

Before this change, the carrier held a cloned dynamic, so this return was valid. The sink overview says a named non-const dynamic is viewed and that a carrier can outlive a statement. It does not say that the carrier must not outlive the named dynamic. The current code has no guard for this case.

Decide the lifetime contract before changing the code. If a carrier from a named lvalue may escape its scope, the view must not be a raw pointer. Giving the carrier shared ownership of the model changes what copying a dynamic does, so it is not a local fix. If the view is meant to stay statement-scoped, state that restriction in the sink overview and in the dynamic operator documentation.


Review info
Run configuration
  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: 06b483c
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: e1e0737f-6f98-4cb6-8a03-8fdab4c77319
Commits

Reviewing files that changed from the base of the PR and between e9cd371 and 432fd94.

Files selected for processing: 1
  • cudax/include/cuda/experimental/__stf/utility/exception_policy.cuh

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

@github-actions

Copy link
Copy Markdown
Contributor

🥳 CI Workflow Results

🟩 Finished in 1h 16m: Pass: 100%/73 | Total: 19h 02m | Max: 1h 16m | Hits: 79%/58411

See results here.

@andralex
andralex merged commit efc1ccc into NVIDIA:main Oct 29, 2026
97 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in CCCL Oct 29, 2026
@andralex
andralex deleted the error-sinks-d branch October 29, 2026 19:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants