Skip to content

[libcu++] Keep launch_transform storage alive until after the kernel launch on MSVC - #12138

Merged
pciolkosz merged 3 commits into
NVIDIA:mainfrom
SUKUNA-AI:fix/launch-transform-msvc-lifetime
Oct 23, 2026
Merged

pciolkosz merged 3 commits into
NVIDIA:mainfrom
SUKUNA-AI:fix/launch-transform-msvc-lifetime

Conversation

@SUKUNA-AI

@SUKUNA-AI SUKUNA-AI commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Description

Fixes the vector_add failure from #8902. The stdexec_stream failure there is a different bug, fixed in #12134.

cuda::launch_transform (libcudacxx/include/cuda/__stream/launch_transform.h) keeps the object returned by transform_launch_argument() in a by-value default argument: __optional_with_a_destructor<...> __storage = cuda::std::nullopt. The comment there expects this object to be destroyed in the caller, after the kernel launch. The standard leaves this implementation-defined ([expr.call]/7):

  • Itanium ABI (gcc, clang): the parameter is destroyed at the end of the caller's full-expression, after the kernel launch.
  • Microsoft ABI (MSVC): the callee destroys it when launch_transform returns, before the kernel is launched.

So with MSVC every post-launch action runs before the launch. In cudax/examples/vector.cuh, __action::~__action copies out buffers back to the host. With MSVC it runs before the kernel, so the host copy of C is stale, although the device result is correct.

This PR binds the storage to an rvalue reference parameter instead: __optional_with_a_destructor<...>&& __storage = {}. A temporary bound to a reference parameter persists until the end of the full-expression containing the call, on every ABI ([class.temporary]/6.9). The comments are updated to match.

transform_launch_argument() is also implemented by cuda::buffer, cuda::uninitialized_async_buffer, the logical_endpoint types and the cudax buffers, so this is not limited to the example. The destructor check in launch_smoke.cu that would catch this is commented out ("Disabled for now because we don't handle it with graphs").

Testing

Plain C++ (struct S { ~S(); }; int f(S s = S{}); g(f());): MSVC 19.51 runs ~S before g, g++ 16.2 after. With int f(S&& s = S{}), both run it after g.

Native Windows, RTX 5070 Ti (sm_120), driver 617.42, nvcc 13.4.92, MSVC 19.51, C++20:

before after
Minimal repro (libcudacxx only: a post-launch destructor checks a flag the kernel sets) kernel had run = 0, fails passes
cudax/examples/vector_add.cu fails result verification Test PASSED

Linux (WSL2, nvcc 13.4.59, gcc 16.2), with this change:

  • libcudacxx.test.cuda.ccclrt.launch.*, ccclrt.stream.*, ccclrt.logical_endpoint: 8/8 pass.
  • libcudacxx.test.cuda.containers.buffer.*, uninitialized_async_buffer, libcudacxx.test.debugging.buffer: 12/12 pass.
  • cudax.test.{launch,stream,algorithm,containers,execution} and the examples vector_add, async_buffer_add, stdexec_stream: 8/8 pass. cudax.test.execution and stdexec_stream need [cudax] Do not read managed memory from the host after starting work in the stream adaptor #12134 as well; that fix is unrelated to this one.

Not tested: clang-cl, nvc++.

Checklist

  • New or existing tests cover these changes. (cudax/examples/vector_add.cu on MSVC. I did not re-enable the destructor check in launch_smoke.cu, because it is disabled for graph launches.)
  • The documentation is up to date with these changes. (No user-visible API change.)

@SUKUNA-AI
SUKUNA-AI requested a review from a team as a code owner October 21, 2026 18:16
@SUKUNA-AI
SUKUNA-AI requested a review from wmaxey October 21, 2026 18:16
@copy-pr-bot

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

@coderabbitai

coderabbitai Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Important

Review skipped

Review was skipped as selected files did not have any reviewable changes.

⚙️ Run configuration
  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: b94ca2a
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: a3331173-ab38-4e22-81d6-27b6f032f461
📥 Commits

Reviewing files that changed from the base of the PR and between 6881784 and 00a68a7.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: NVIDIA/cccl
  • Branch: main
  • File: .coderabbit.yaml
  • Commit: 20bb907
  • Layers: Repository YAML
  • Review profile: CHILL
  • Plan: Enterprise
  • Run ID: a89e4cd2-f5ae-44fc-9a0e-ce2c14bc0eb2
📥 Commits

Reviewing files that changed from the base of the PR and between 9dcd7a2 and 6881784.

📒 Files selected for processing: 1
  • libcudacxx/include/cuda/__stream/launch_transform.h

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

📝 Summary

Summary by CodeRabbit

  • Bug Fixes
    • Improved temporary storage lifetime during stream transformations so storage remains available through kernel launch.
    • Avoided compiler-dependent differences in when temporary storage is destroyed across supported compiler ABIs.

The stored-result launch_transform overload now accepts optional storage by rvalue reference. Its comments describe the temporary’s full-expression lifetime and note that by-value parameter destruction timing differs between GCC, Clang, and MSVC.

Changes: Launch-transform storage lifetime

Layer / File(s) Summary
Storage parameter lifetime
libcudacxx/include/cuda/__stream/launch_transform.h
The stored-result overload accepts optional storage by rvalue reference and retains nullopt as its default. The comments describe the default temporary’s full-expression lifetime and differences in by-value parameter destruction timing across GCC, Clang, and MSVC.

Possibly related issues

  • NVIDIA/cccl issue 8902: The issue reports an MSVC vector_add failure with an incorrect result at element 0. This change addresses the launch-order cause attributed to temporary storage lifetime. The issue also describes a separate stdexec_stream failure that this change does not resolve.

Suggested reviewers: miscco, jacobfaib

Priority: ➖ Normal

Change: Bug fix

Merge Risk: ⚪ Minimal · up to 68817

No actionable merge-blocking risk is identified.

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

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

…launch on MSVC

launch_transform kept the object returned by transform_launch_argument()
in a by-value default argument and relied on it being destroyed after
the kernel launch. Whether a by-value parameter is destroyed when the
callee returns or at the end of the caller's full-expression is
implementation-defined ([expr.call]/7). The Itanium ABI destroys it
after the launch; the Microsoft ABI destroys it when launch_transform
returns, before the kernel is enqueued. So post-launch actions, such as
copying an output buffer back to the host, ran too early with MSVC.

Bind the storage to an rvalue reference parameter instead. A temporary
bound to a reference parameter persists until the end of the
full-expression containing the call on every ABI.
@SUKUNA-AI
SUKUNA-AI force-pushed the fix/launch-transform-msvc-lifetime branch from bcc0abb to 9dcd7a2 Compare October 21, 2026 18:31
@pciolkosz

Copy link
Copy Markdown
Contributor

/ok to test 9dcd7a2

@github-actions

This comment has been minimized.

@SUKUNA-AI

Copy link
Copy Markdown
Contributor Author

The only failing job here, cub.test.device.radix_sort_runs_on.lid_0 in the sm86-only build, is unrelated to this PR: on main that test aborts in any single-architecture build because it passes RunsOn a compute capability that was not compiled for. Details in #12154, fix in #12155.

_Stream&& __stream,
_Arg&& __arg,
__optional_with_a_destructor<__transform_result_t<_Arg>> __storage = cuda::std::nullopt) const -> decltype(auto)
_Stream&& __stream, _Arg&& __arg, __optional_with_a_destructor<__transform_result_t<_Arg>>&& __storage = {}) const

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.

This is wrong. This should be initialized with ::cuda::std::nullopt specifically, not {}, which may value-initialize depending on the chosen overload.

Suggested change
_Stream&& __stream, _Arg&& __arg, __optional_with_a_destructor<__transform_result_t<_Arg>>&& __storage = {}) const
_Stream&& __stream, _Arg&& __arg, __optional_with_a_destructor<__transform_result_t<_Arg>>&& __storage = ::cuda::std::nullopt) const

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.

Done in 6881784, thanks. vector_add and the minimal repro still pass on Windows/MSVC (C++17 and C++20), and the libcu++ launch/stream/buffer and cudax tests pass on Linux.

@github-project-automation github-project-automation Bot moved this from In Review to In Progress in CCCL Oct 22, 2026
@github-project-automation github-project-automation Bot moved this from In Progress to In Review in CCCL Oct 23, 2026
@Jacobfaib

Copy link
Copy Markdown
Contributor

/ok to test 6881784

@github-actions

This comment has been minimized.

@SUKUNA-AI

Copy link
Copy Markdown
Contributor Author

Thanks for running CI. The only failing job was the CUB HostLaunch(amd64, RTXA6000): sm{86} one, which this branch hit before because of the radix_sort_runs_on test that your #12166 fixed; this branch did not include #12166 yet. I merged main (00a68a7), so the next /ok to test should pick up that fix.

@Jacobfaib

Copy link
Copy Markdown
Contributor

/ok to test 00a68a7

@github-actions

Copy link
Copy Markdown
Contributor

🥳 CI Workflow Results

🟩 Finished in 2h 46m: Pass: 100%/228 | Total: 5d 05h | Max: 2h 11m | Hits: 73%/915285

See results here.

@pciolkosz
pciolkosz merged commit e9ab8fb into NVIDIA:main Oct 23, 2026
256 checks passed
@github-project-automation github-project-automation Bot moved this from In Review to Done in CCCL Oct 23, 2026
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.

3 participants