Skip to content

Fxes msvc multi-inheritance cross host-device for output_ordering - #9415

Open
elstehle wants to merge 1 commit into
NVIDIA:mainfrom
elstehle:fix/out-ordering-multi-inheritance-msvc
Open

elstehle wants to merge 1 commit into
NVIDIA:mainfrom
elstehle:fix/out-ordering-multi-inheritance-msvc

Conversation

@elstehle

Copy link
Copy Markdown
Contributor

cuda::execution::output_ordering::__output_ordering_holder_t inherits from two empty base classes, which can give it a different layout/size between host and device on MSVC and is therefore UB to pass into a kernel. This mirrors the fix applied to tie_break to follow the __determinism_holder_t patter. We now keep a single empty base and expose the preference via a static constexpr value member instead (see #9238 (comment)).

@elstehle
elstehle requested a review from a team as a code owner June 12, 2026 05:56
@elstehle
elstehle requested a review from wmaxey June 12, 2026 05:56
@github-project-automation github-project-automation Bot moved this to Todo in CCCL Jun 12, 2026
@elstehle
elstehle enabled auto-merge (squash) June 12, 2026 05:57
@cccl-authenticator-app cccl-authenticator-app Bot moved this from Todo to In Review in CCCL Jun 12, 2026
@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Note: CodeRabbit is enabled on this repository as a convenience for maintainers
and contributors. Use your best judgment when considering its review comments and
suggestions — a suggested change may be inadequate, unnecessary, or safe to ignore.
Contributors are not expected to address every comment. Human reviews are what
ultimately matter for merging.

Summary

This PR fixes an undefined behavior issue in cuda::execution::output_ordering::__output_ordering_holder_t where multi-inheritance from two empty base classes could produce differing layout/size between host and device on MSVC when passed into a kernel.

Changes

The fix refactors __output_ordering_holder_t<_Guarantee> from a type inheriting from ::cuda::std::integral_constant<__output_ordering_t, _Guarantee> to one that:

  • Derives solely from __requirement (single inheritance)
  • Stores the ordering value as static constexpr __output_ordering_t value = _Guarantee

This pattern mirrors the existing approach used in the determinism module (which has __determinism_holder_t), establishing a consistent design pattern across execution requirements.

Public API Impact

The public surface remains unchanged:

  • Enum values (__output_ordering_t::__sorted, __unsorted, __stable_sorted)
  • Type aliases (sorted_t, stable_sorted_t, unsorted_t)
  • Global constants (sorted, stable_sorted, unsorted)
  • Query mechanism (__get_output_ordering_t)

All existing test assertions (e.g., is_base_of_v<__requirement, sorted_t>) continue to pass.

Technical Details

Root Cause: The previous implementation's multiple inheritance could cause MSVC to generate different type layouts on host vs. device compilation, leading to potential kernel parameter passing issues.

Solution: By reducing to single inheritance and using a static constexpr member instead of inheriting from integral_constant, the type layout is deterministic across host and device compilation contexts.

Related Discussion: References #9238 (comment) where a similar fix was applied to tie_break.

Lines Changed: +3/-4

Walkthrough

This PR removes output_ordering.h's dependency on integral_constant by refactoring __output_ordering_holder_t<_Guarantee> to inherit from __requirement and store the guarantee value as a static constexpr member instead of deriving from integral_constant<__output_ordering_t, _Guarantee>.

Changes

Output Ordering Type Decoupling

Layer / File(s) Summary
Decouple output_ordering_holder_t from integral_constant
libcudacxx/include/cuda/__execution/output_ordering.h
Include of integral_constant header removed; __output_ordering_holder_t base class changed from integral_constant<..., _Guarantee> to __requirement with static constexpr __output_ordering_t value = _Guarantee member.

Possibly Related PRs

  • NVIDIA/cccl#9355: Adds stable_sorted output-ordering category, which depends on the refactored __output_ordering_holder_t structure modified in this PR.

Suggested Reviewers

  • Jacobfaib
  • miscco

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

@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: a924df08-036b-48c2-936b-df1ac9f8733e

📥 Commits

Reviewing files that changed from the base of the PR and between eb7ee5e and 1b564fa.

📒 Files selected for processing (1)
  • libcudacxx/include/cuda/__execution/output_ordering.h

Comment on lines 53 to +56
template <__output_ordering_t _Guarantee>
struct _CCCL_DECLSPEC_EMPTY_BASES __output_ordering_holder_t
: __requirement
, ::cuda::std::integral_constant<__output_ordering_t, _Guarantee>
struct __output_ordering_holder_t : __requirement
{
static constexpr __output_ordering_t value = _Guarantee;

@coderabbitai coderabbitai Bot Jun 12, 2026 •

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show relevant sections of output_ordering.h
FILE="libcudacxx/include/cuda/__execution/output_ordering.h"
echo "=== $FILE (around 1-160) ==="
sed -n '1,200p' "$FILE" | nl -ba | sed -n '1,200p'

echo "=== $FILE (around 40-120) ==="
sed -n '35,130p' "$FILE" | nl -ba

echo "=== $FILE (around 120-220) ==="
sed -n '120,260p' "$FILE" | nl -ba

# Locate any mention of integral_constant in this header
echo "=== rg integral_constant in output_ordering.h ==="
rg -n "integral_constant|value_type|operator.*(T|int)|type[[:space:]]*=" "$FILE" || true

# Inspect require.h contract
REQ="libcudacxx/include/cuda/__execution/require.h"
echo "=== $REQ ==="
sed -n '1,220p' "$REQ" | nl -ba | sed -n '1,220p'
rg -n "struct __requirement|require\\(" "$REQ" || true

# Inspect determinism.h holder precedent
DET="libcudacxx/include/cuda/__execution/determinism.h"
echo "=== $DET (around relevant parts) ==="
sed -n '1,260p' "$DET" | nl -ba | sed -n '1,260p'
rg -n "__determinism_holder_t|integral_constant|static constexpr __determinism_t|value" "$DET" || true

# Find where sorted_t/stable_sorted_t/unsorted_t are defined/used
echo "=== rg definitions of sorted_t stable_sorted_t unsorted_t ==="
rg -n "sorted_t|stable_sorted_t|unsorted_t" libcudacxx/include/cuda/__execution/output_ordering.h

echo "=== rg usages of sorted_t/stable_sorted_t/unsorted_t ==="
rg -n "sorted_t|stable_sorted_t|unsorted_t" libcudacxx/include/cuda | head -n 50

echo "=== rg for value_type usage with ordering tags ==="
rg -n "value_type" libcudacxx/include/cuda/__execution/output_ordering.h libcudacxx/include/cuda | head -n 80

Repository: NVIDIA/cccl

Length of output: 193


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="libcudacxx/include/cuda/__execution/output_ordering.h"
echo "=== $FILE (full excerpt) ==="
nl -ba "$FILE" | sed -n '1,220p'

echo "=== rg integral_constant/value_type in $FILE ==="
rg -n "integral_constant|value_type|operator|type[[:space:]]*=" "$FILE" || true

REQ="libcudacxx/include/cuda/__execution/require.h"
echo "=== $REQ ==="
nl -ba "$REQ" | sed -n '1,260p'
rg -n "struct __requirement|require\\(" "$REQ" || true

DET="libcudacxx/include/cuda/__execution/determinism.h"
echo "=== $DET ==="
nl -ba "$DET" | sed -n '1,320p'
rg -n "__determinism_holder_t|integral_constant|static constexpr.*value" "$DET" || true

echo "=== ordering tags defined in $FILE ==="
rg -n "sorted_t|stable_sorted_t|unsorted_t|__output_ordering_t" "$FILE"

echo "=== usages of ordering tags (limited) ==="
rg -n "sorted_t|stable_sorted_t|unsorted_t" libcudacxx/include/cuda | head -n 80

echo "=== potential use of integral_constant API with ordering tags ==="
rg -n "stable_sorted_t::|sorted_t::|unsorted_t::" libcudacxx/include/cuda | head -n 80
rg -n "stable_sorted_t\\b|sorted_t\\b|unsorted_t\\b" libcudacxx/include/cuda | head -n 80

Repository: NVIDIA/cccl

Length of output: 193


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="libcudacxx/include/cuda/__execution/output_ordering.h"
echo "=== $FILE (excerpt) ==="
nl -ba "$FILE" | sed -n '1,260p'

echo "=== rg integral_constant/value_type in $FILE ==="
rg -n "integral_constant|value_type|operator|type[[:space:]]*=" "$FILE" || true

REQ="libcudacxx/include/cuda/__execution/require.h"
echo "=== $REQ ==="
nl -ba "$REQ" | sed -n '1,220p'
rg -n "struct __requirement|require\\(" "$REQ" || true

DET="libcudacxx/include/cuda/__execution/determinism.h"
echo "=== $DET ==="
nl -ba "$DET" | sed -n '1,320p'
rg -n "__determinism_holder_t|integral_constant|static constexpr.*value" "$DET" || true

echo "=== ordering tags defined in $FILE ==="
rg -n "sorted_t|stable_sorted_t|unsorted_t|__output_ordering_t" "$FILE"

echo "=== usages of ordering tags (limited) ==="
rg -n "sorted_t|stable_sorted_t|unsorted_t" libcudacxx/include/cuda | head -n 120

echo "=== use of integral_constant API (value_type/type/operator) with ordering tags ==="
rg -n "(sorted_t|stable_sorted_t|unsorted_t)::(value_type|type)" libcudacxx/include/cuda | head -n 120
rg -n "(sorted_t|stable_sorted_t|unsorted_t)\\b.*(value_type|::type|operator)" libcudacxx/include/cuda | head -n 120

Repository: NVIDIA/cccl

Length of output: 188


🏁 Script executed:

# The sandbox will execute the script and return output

Repository: NVIDIA/cccl

Length of output: 37


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="libcudacxx/include/cuda/__execution/output_ordering.h"
REQ="libcudacxx/include/cuda/__execution/require.h"
DET="libcudacxx/include/cuda/__execution/determinism.h"

echo "=== output_ordering.h: first 220 lines ==="
sed -n '1,220p' "$FILE"

echo "=== output_ordering.h: around __output_ordering_holder_t (lines 1-1200 search for it) ==="
rg -n "__output_ordering_holder_t" "$FILE" || true
# Print a window around the match
line=$(rg -n "__output_ordering_holder_t" "$FILE" | head -n1 | cut -d: -f1 || echo 1)
start=$((line-30)); end=$((line+40))
[ "$start" -lt 1 ] && start=1
sed -n "${start},${end}p" "$FILE"

echo "=== output_ordering.h: ordering tag aliases ==="
rg -n "using (sorted_t|stable_sorted_t|unsorted_t)\\b|struct (sorted_t|stable_sorted_t|unsorted_t)\\b|sorted_t\\b" "$FILE" || true

echo "=== output_ordering.h: integral_constant/value_type/type/operator mentions ==="
rg -n "integral_constant|value_type|\\btype\\b|operator[[:space:]]" "$FILE" || true

echo "=== usages across libcudacxx/include/cuda for ordering tags ==="
rg -n "(sorted_t|stable_sorted_t|unsorted_t)::(value_type|type)" libcudacxx/include/cuda || true
rg -n "(sorted_t|stable_sorted_t|unsorted_t)\\b" libcudacxx/include/cuda | head -n 200 || true

echo "=== require.h: __requirement contract ==="
sed -n '1,220p' "$REQ"
rg -n "struct __requirement|require\\(" "$REQ" || true

echo "=== determinism.h: holder precedent ==="
sed -n '1,260p' "$DET"
rg -n "__determinism_holder_t|integral_constant|static constexpr __determinism_t|static constexpr" "$DET" || true

Repository: NVIDIA/cccl

Length of output: 13511


important: In libcudacxx/include/cuda/__execution/output_ordering.h (the __output_ordering_holder_t / sorted_t / stable_sorted_t / unsorted_t aliases), the ordering tags no longer inherit ::cuda::std::integral_constant—they now only expose static constexpr __output_ordering_t value plus query(...). If any callers previously relied on integral_constant members/conversions (e.g., value_type, type, implicit integral conversion), this is a source-compat break; re-expose the needed integral_constant surface on the holder or explicitly confirm/document the intentional break.

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.

Yeah, I've been thinking about how we'd treat this breakage in libcu++. @miscco, how do we think about this interface change on the query side and how do we trade-off with the potential issue Georgii pointed out here: #9238 (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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

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.

Were those already publicly available in a released CCCL version?

If so, then we can just add the respective functionality to the class too and deprecate it for 4.0

struct _CCCL_DECLSPEC_EMPTY_BASES __output_ordering_holder_t
: __requirement
, ::cuda::std::integral_constant<__output_ordering_t, _Guarantee>
struct __output_ordering_holder_t : __requirement

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.

Q: __requirement is never empty?

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.

__requirement is expectedly empty. Can you elaborate where you're leading to with the question, not sure I follow.

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.

Is your concern that we'd still be running into issues with MSVC due to (source):

All direct and indirect base classes B are empty and the type of the first field F of T uses B in its definition, such that B is laid out at offset 0 in the definition of F.

@github-actions

Copy link
Copy Markdown
Contributor

🥳 CI Workflow Results

🟩 Finished in 2h 18m: Pass: 100%/118 | Total: 21h 50m | Max: 50m 39s | Hits: 98%/341893

See results here.

Comment on lines -55 to 65
struct _CCCL_DECLSPEC_EMPTY_BASES __output_ordering_holder_t
: __requirement
, ::cuda::std::integral_constant<__output_ordering_t, _Guarantee>
struct __output_ordering_holder_t : __requirement
{
static constexpr __output_ordering_t value = _Guarantee;

[[nodiscard]] _CCCL_NODEBUG_API constexpr auto query(const __get_output_ordering_t&) const noexcept
-> __output_ordering_holder_t<_Guarantee>
{
return *this;
}
};

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.

I believe we should be safe due to _CCCL_DECLSPEC_EMPTY_BASES .

Given that this is a fully empty class with only 3 specializations I believe we should just add the three static_asserts after the class that it is indeed empty to be super safe

This branch has not been deployed

No deployments
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.

3 participants