diff --git a/libcudacxx/include/cuda/__memory_resource/any_resource.h b/libcudacxx/include/cuda/__memory_resource/any_resource.h index 02b39a2a62a..c8ba23853f3 100644 --- a/libcudacxx/include/cuda/__memory_resource/any_resource.h +++ b/libcudacxx/include/cuda/__memory_resource/any_resource.h @@ -166,6 +166,18 @@ using __iresource _CCCL_NODEBUG_ALIAS = ::cuda:: template using __iasync_resource _CCCL_NODEBUG_ALIAS = __iset<__iresource<_Properties...>, __ibasic_async_resource<>>; +// Resource-ref variants omit __icopyable: resource_ref is a reference (non-owning) +// wrapper, so the underlying resource need not be copyable. Including __icopyable +// here causes GCC to evaluate copyable during overload resolution, which can +// create a recursive constraint-satisfaction cycle when T has a constructor +// accepting resource_ref (see https://github.com/NVIDIA/cccl/issues/8037). +template +using __iresource_ref _CCCL_NODEBUG_ALIAS = ::cuda:: + __iset<__ibasic_resource<>, __iproperty_set<_Properties...>, ::cuda::__iequality_comparable<>>; + +template +using __iasync_resource_ref _CCCL_NODEBUG_ALIAS = __iset<__iresource_ref<_Properties...>, __ibasic_async_resource<>>; + template using __try_property_result_t = ::cuda::std::conditional_t, void>, // @@ -260,11 +272,11 @@ struct _CCCL_DECLSPEC_EMPTY_BASES any_resource //! @tparam _Properties The properties that any resource wrapped within the `synchronous_resource_ref` needs to satisfy template struct _CCCL_DECLSPEC_EMPTY_BASES synchronous_resource_ref - : __basic_any<__iresource<_Properties...>&> + : __basic_any<__iresource_ref<_Properties...>&> , __with_try_get_property> { // Inherit constructors from __basic_any - _CCCL_DELEGATE_CONSTRUCTORS(synchronous_resource_ref, ::cuda::__basic_any, __iresource<_Properties...>&); + _CCCL_DELEGATE_CONSTRUCTORS(synchronous_resource_ref, ::cuda::__basic_any, __iresource_ref<_Properties...>&); synchronous_resource_ref(const synchronous_resource_ref& __other) noexcept = default; @@ -316,11 +328,11 @@ struct _CCCL_DECLSPEC_EMPTY_BASES synchronous_resource_ref //! @tparam _Properties The properties that any async resource wrapped within the `resource_ref` needs to satisfy template struct _CCCL_DECLSPEC_EMPTY_BASES resource_ref - : __basic_any<__iasync_resource<_Properties...>&> + : __basic_any<__iasync_resource_ref<_Properties...>&> , __with_try_get_property> { // Inherit other constructors from __basic_any - _CCCL_DELEGATE_CONSTRUCTORS(resource_ref, ::cuda::__basic_any, __iasync_resource<_Properties...>&); + _CCCL_DELEGATE_CONSTRUCTORS(resource_ref, ::cuda::__basic_any, __iasync_resource_ref<_Properties...>&); resource_ref(const resource_ref& __other) noexcept = default; diff --git a/libcudacxx/test/libcudacxx/cuda/memory_resource/resource_ref/shared_resource_derived.pass.cpp b/libcudacxx/test/libcudacxx/cuda/memory_resource/resource_ref/shared_resource_derived.pass.cpp new file mode 100644 index 00000000000..98ccb2d8571 --- /dev/null +++ b/libcudacxx/test/libcudacxx/cuda/memory_resource/resource_ref/shared_resource_derived.pass.cpp @@ -0,0 +1,92 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: msvc-19.16 +// UNSUPPORTED: nvrtc + +// Regression test for https://github.com/NVIDIA/cccl/issues/8037 +// +// Constructing a resource_ref from a type that: +// (1) Publicly inherits from cuda::mr::shared_resource, AND +// (2) Has a constructor accepting resource_ref +// triggered a recursive constraint satisfaction error on GCC 14.3.0 with C++20: +// +// error: satisfaction of atomic constraint '...' depends on itself +// +// Root cause: resource_ref previously used __iasync_resource which includes +// __icopyable. Checking __icopyable evaluates copyable, +// which evaluates is_constructible. When T's constructors include one +// accepting resource_ref, the compiler considers whether T& converts to +// resource_ref, re-entering the original __satisfies check — a cycle. +// +// Fix: resource_ref and synchronous_resource_ref now use __iasync_resource_ref +// / __iresource_ref respectively, which omit __icopyable. Reference wrappers +// don't own the resource and therefore don't need the underlying type to be +// copyable. + +#include +#include +#include + +struct my_resource_impl +{ + void* allocate(cuda::stream_ref, ::cuda::std::size_t, ::cuda::std::size_t) + { + return nullptr; + } + void deallocate(cuda::stream_ref, void*, ::cuda::std::size_t, ::cuda::std::size_t) noexcept {} + void* allocate_sync(::cuda::std::size_t, ::cuda::std::size_t) + { + return nullptr; + } + void deallocate_sync(void*, ::cuda::std::size_t, ::cuda::std::size_t) noexcept {} + bool operator==(my_resource_impl const&) const + { + return true; + } + bool operator!=(my_resource_impl const&) const + { + return false; + } + friend void get_property(my_resource_impl const&, cuda::mr::device_accessible) noexcept {} +}; + +using resource_ref = cuda::mr::resource_ref; + +// A type that inherits from shared_resource AND has a constructor taking +// resource_ref. This pattern is common in RMM pool/arena resources. +struct derived_resource : public cuda::mr::shared_resource +{ + using shared_base = cuda::mr::shared_resource; + + explicit derived_resource(resource_ref /*upstream*/) + : shared_base(cuda::std::in_place_type) + {} + + friend void get_property(derived_resource const&, cuda::mr::device_accessible) noexcept {} +}; + +void test() +{ + auto base_sr = cuda::mr::make_shared_resource(); + resource_ref ref{base_sr}; + derived_resource dr{ref}; + + // Previously caused: error: satisfaction of atomic constraint depends on + // itself. Must compile cleanly after the fix. + ref = dr; + + // Workaround (casting to shared_resource base) should still compile + ref = static_cast(dr); +} + +int main(int, char**) +{ + return 0; +}