Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Host/Device accessors for mdspan #3686

Draft
wants to merge 26 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
b115039
first draft
fbusato Feb 5, 2025
0ea452d
split implemenation
fbusato Feb 5, 2025
9ae4953
add host/device pointer checks
fbusato Feb 5, 2025
96f8419
add pointer checks
fbusato Feb 6, 2025
52b12a6
add tests
fbusato Feb 6, 2025
cab748e
fix device/host memory accessibility detection
fbusato Feb 6, 2025
31b84c7
fix typo
fbusato Feb 6, 2025
430e46d
add recursive traits
fbusato Feb 6, 2025
3c76d32
prevent ADL
fbusato Feb 6, 2025
d63980d
size_t namespace
fbusato Feb 6, 2025
e9ce8a4
is_pointer namespace
fbusato Feb 6, 2025
de4264c
fix __self
fbusato Feb 7, 2025
c0d12da
add noexcept
fbusato Feb 7, 2025
572447d
Update libcudacxx/include/cuda/__mdspan/host_device_accessor.h
fbusato Feb 8, 2025
0eb90ef
Update libcudacxx/include/cuda/__mdspan/host_device_accessor.h
fbusato Feb 8, 2025
71a9161
expose accessor types
fbusato Feb 8, 2025
921273a
remove default, copy constructors
fbusato Feb 8, 2025
aef9602
fix default and copy constructors
fbusato Feb 10, 2025
0647703
add inheritance aliases
fbusato Feb 10, 2025
c30482e
Merge branch 'main' into host-device-mdspan-accessors
fbusato Feb 10, 2025
fc82009
add default_accessor constructor and conversion
fbusato Feb 10, 2025
21e2feb
guard cuda_runtime_api.h
fbusato Feb 14, 2025
22fecf3
Merge branch 'main' into host-device-mdspan-accessors
fbusato Feb 14, 2025
ca47b44
Update libcudacxx/include/cuda/__mdspan/host_device_accessor.h
fbusato Feb 20, 2025
9a7e393
add detectably_invalid
fbusato Feb 20, 2025
a0af8f4
add __managed_accessor conversions from/to default_accessors
fbusato Feb 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
283 changes: 283 additions & 0 deletions libcudacxx/include/cuda/__mdspan/host_device_accessor.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,283 @@
//===----------------------------------------------------------------------===//
//
// Part of libcu++, the C++ Standard Library for your entire system,
// 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.
//
//===----------------------------------------------------------------------===//

#ifndef _CUDA___MDSPAN_HOST_DEVICE_ACCESSOR
#define _CUDA___MDSPAN_HOST_DEVICE_ACCESSOR

#include <cuda/std/detail/__config>

#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header

#include <cuda_runtime_api.h>

#include <cuda/std/__type_traits/is_pointer.h>
#include <cuda/std/cassert>

_LIBCUDACXX_BEGIN_NAMESPACE_CUDA

template <typename _Accessor>
struct host_accessor;

template <typename _Accessor>
struct device_accessor;

template <typename _Accessor>
struct managed_accessor;

/***********************************************************************************************************************
* Host/Device/Managed Accessor Traits
**********************************************************************************************************************/

template <typename T>
inline constexpr bool is_host_accessor_v = false;

template <typename T>
inline constexpr bool is_device_accessor_v = false;

template <typename T>
inline constexpr bool is_managed_accessor_v = false;

template <typename _Accessor>
inline constexpr bool is_host_accessor_v<host_accessor<_Accessor>> = true;

template <typename _Accessor>
inline constexpr bool is_device_accessor_v<device_accessor<_Accessor>> = true;

template <typename _Accessor>
inline constexpr bool is_managed_accessor_v<managed_accessor<_Accessor>> = true;

template <typename T>
inline constexpr bool is_host_device_managed_accessor_v =
is_host_accessor_v<T> || is_device_accessor_v<T> || is_managed_accessor_v<T>;

template <typename __data_handle_type>
_LIBCUDACXX_HIDE_FROM_ABI constexpr bool __is_device_pointer(__data_handle_type __p)
{
return ::cuda::std::is_pointer_v<__data_handle_type>
&& (__p == nullptr || __isGlobal(__p) || __isShared(__p) || __isConstant(__p) || __isGridConstant(__p)
|| __isLocal(__p));
}

/***********************************************************************************************************************
* Host Accessor
**********************************************************************************************************************/

template <typename _Accessor>
struct host_accessor : public _Accessor
{
private:
using __data_handle_type = typename _Accessor::data_handle_type;
using __reference = typename _Accessor::reference;

static constexpr bool __is_ctor_noexcept = noexcept(_Accessor{});
static constexpr bool __is_copy_ctor_noexcept = noexcept(_Accessor{});
static constexpr bool __is_access_noexcept = noexcept(_Accessor{}.access(__data_handle_type{}, 0));
static constexpr bool __is_offset_noexcept = noexcept(_Accessor{}.offset(__data_handle_type{}, 0));

static_assert(!is_host_device_managed_accessor_v<_Accessor>,
"cuda::host_accessor/cuda::device_accessor/cuda::managed_accessor accessor cannot be nested");

template <typename __data_handle_type>
_LIBCUDACXX_HIDE_FROM_ABI static constexpr bool __is_host_pointer(__data_handle_type __p)
{
if constexpr (::cuda::std::is_pointer_v<__data_handle_type>)
{
cudaPointerAttributes __attrib;
auto __status = ::cudaPointerGetAttributes(&__attrib, __p);
return __status == ::cudaSuccess
&& (__attrib.type == ::cudaMemoryTypeHost || __attrib.type == ::cudaMemoryTypeUnregistered);
}
else
{
return false;
}
}

_LIBCUDACXX_HIDE_FROM_ABI static constexpr void __check_host_pointer(__data_handle_type __p)
{
_CCCL_ASSERT(__is_host_pointer(__p), "cuda::host_accessor data handle is not a HOST pointer");
}

public:
using offset_policy = host_accessor;

_CCCL_HIDE_FROM_ABI host_accessor() noexcept(__is_ctor_noexcept) = default;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessors are not required to be default constructible. Please see https://eel.is/c++draft/mdspan.accessor.reqmts for the actual requirements, and https://eel.is/c++draft/views.multidim#mdspan.mdspan.cons-1.4 for how mdspan constrains its default constructor on whether its accessor is default constructible.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty easy to fix in C++20. I have to remind myself how the work-around works in C++ < 20.

Suggested change
_CCCL_HIDE_FROM_ABI host_accessor() noexcept(__is_ctor_noexcept) = default;
requires(std::is_default_constructible_v<_Accessor>) // needs C++20
_CCCL_HIDE_FROM_ABI host_accessor() noexcept(__is_ctor_noexcept) = default;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to fix it. Without c++20, I cannot introduce a template parameter and use = default.
Also, I don't know what is the best way to use requires before c++20, without introducing a template parameter @miscco


_CCCL_HIDE_FROM_ABI host_accessor(const host_accessor&) noexcept(__is_copy_ctor_noexcept) = default;

Copy link
Contributor

@mhoemmen mhoemmen Feb 7, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A key feature of any accessor is the set of permitted conversions, both implicit and explicit. mdspan depends on accessors' conversions in order for its own conversions to work. Implicit conversions make mdspan assignment work; explicit conversions make explicit construction of an mdspan of one type from an mdspan of another type work. Even default_accessor has a conversion to permit assigning an mdspan of nonconst to an mdspan of const.

An accessor's conversions are defined by its constructors and/or conversion operators. Not exposing those from the parent class means that mdspan of host_accessor<Accessor> won't be able to perform any of the conversions that mdspan of Accessor can perform.

A public using _Accessor::_Accessor; declaration would pull in all of the base class' constructors, but it wouldn't pull in any conversion operators.

Another issue is that _Accessor might have a virtual destructor. That would be weird, but it's legal in the Standard and nothing in this class prevents it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Section 5.8 of P2897R7 elaborates the design intent behind accessor conversions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A public using _Accessor::_Accessor; declaration would pull in all of the base class' constructors, but it wouldn't pull in any conversion operators.

For example, if Accessor A provides a conversion operator to B and a conversion operator to C, the only way to provide those in device_accessor<A> would be to use reflection to enumerate all conversion operators of A, and paste new ones into device_accessor<A> for device_accessor<B> and device_accessor<C>. While I'm not a C++ reflection expert, I don't think even P2996 reflection can do that, as it would require injecting member function definitions.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I entirely missed that part. I added the constructors following the aligned_accessor model.

_LIBCUDACXX_HIDE_FROM_ABI constexpr __reference access(__data_handle_type __p, size_t __i) const
noexcept(__is_access_noexcept)
{
NV_IF_ELSE_TARGET(NV_IS_HOST,
(__check_host_pointer(__p);), //
(static_assert(false, "cuda::host_accessor cannot be used in DEVICE code");))
return _Accessor::access(__p, __i);
}

_LIBCUDACXX_HIDE_FROM_ABI constexpr __data_handle_type offset(__data_handle_type __p, size_t __i) const
noexcept(__is_offset_noexcept)
{
NV_IF_ELSE_TARGET(NV_IS_HOST,
(__check_host_pointer(__p);), //
(static_assert(false, "cuda::host_accessor cannot be used in DEVICE code");))
return _Accessor::offset(__p, __i);
}
};

/***********************************************************************************************************************
* Device Accessor
**********************************************************************************************************************/

template <typename _Accessor>
struct device_accessor : public _Accessor
{
private:
using __data_handle_type = typename _Accessor::data_handle_type;
using __reference = typename _Accessor::reference;

static constexpr bool __is_ctor_noexcept = noexcept(_Accessor{});
static constexpr bool __is_copy_ctor_noexcept = noexcept(_Accessor{});
static constexpr bool __is_access_noexcept = noexcept(_Accessor{}.access(__data_handle_type{}, 0));
static constexpr bool __is_offset_noexcept = noexcept(_Accessor{}.offset(__data_handle_type{}, 0));

static_assert(!is_host_device_managed_accessor_v<_Accessor>,
"cuda::host_accessor/cuda::device_accessor/cuda::managed_accessor accessor cannot be nested");

_LIBCUDACXX_HIDE_FROM_ABI static constexpr void __check_device_pointer(__data_handle_type __p)
{
_CCCL_ASSERT(__is_device_pointer(__p), "cuda::device_accessor data handle is not a DEVICE pointer");
}

template <typename _Sp = bool> // lazy evaluation
_LIBCUDACXX_HIDE_FROM_ABI static constexpr void __check_host_path() noexcept
{
static_assert(sizeof(_Sp) != sizeof(_Sp), "cuda::device_accessor cannot be used in HOST code");
}

public:
using offset_policy = device_accessor;

_CCCL_HIDE_FROM_ABI device_accessor() noexcept(__is_ctor_noexcept) = default;

_CCCL_HIDE_FROM_ABI device_accessor(const device_accessor&) noexcept(__is_copy_ctor_noexcept) = default;

_LIBCUDACXX_HIDE_FROM_ABI constexpr __reference access(__data_handle_type __p, size_t __i) const
noexcept(__is_access_noexcept)
{
NV_IF_ELSE_TARGET(NV_IS_DEVICE, (__check_device_pointer(__p);), (__check_host_path();))
return _Accessor::access(__p, __i);
}

_LIBCUDACXX_HIDE_FROM_ABI constexpr __data_handle_type offset(__data_handle_type __p, size_t __i) const
noexcept(__is_offset_noexcept)
{
NV_IF_ELSE_TARGET(NV_IS_DEVICE, (__check_device_pointer(__p);), (__check_host_path();))
return _Accessor::offset(__p, __i);
}
};

/***********************************************************************************************************************
* Managed Accessor
**********************************************************************************************************************/

template <typename _Accessor>
struct managed_accessor : public _Accessor
{
private:
using __data_handle_type = typename _Accessor::data_handle_type;
using __reference = typename _Accessor::reference;

static constexpr bool __is_ctor_noexcept = noexcept(_Accessor{});
static constexpr bool __is_copy_ctor_noexcept = noexcept(_Accessor{});
static constexpr bool __is_access_noexcept = noexcept(_Accessor{}.access(__data_handle_type{}, 0));
static constexpr bool __is_offset_noexcept = noexcept(_Accessor{}.offset(__data_handle_type{}, 0));

static_assert(!is_host_device_managed_accessor_v<_Accessor>,
"cuda::host_accessor/cuda::device_accessor/cuda::managed_accessor accessor cannot be nested");

template <typename __data_handle_type>
_LIBCUDACXX_HIDE_FROM_ABI static constexpr bool __is_managed_pointer(__data_handle_type __p)
{
if constexpr (::cuda::std::is_pointer_v<__data_handle_type>)
{
cudaPointerAttributes __attrib;
auto __status = ::cudaPointerGetAttributes(&__attrib, __p);
return __status == ::cudaSuccess
&& (__attrib.type == ::cudaMemoryTypeManaged || __attrib.type == ::cudaMemoryTypeUnregistered);
}
else
{
return false;
}
}

_LIBCUDACXX_HIDE_FROM_ABI static constexpr void __check_managed_pointer(__data_handle_type __p)
{
_CCCL_ASSERT(__is_managed_pointer(__p), "cuda::managed_accessor data handle is not a MANAGED pointer");
}

_LIBCUDACXX_HIDE_FROM_ABI static constexpr void __check_device_pointer(__data_handle_type __p)
{
_CCCL_ASSERT(__is_device_pointer(__p), "cuda::managed_accessor data handle is not a DEVICE/MANAGED pointer");
}

public:
using offset_policy = managed_accessor;

_CCCL_HIDE_FROM_ABI managed_accessor() noexcept(__is_ctor_noexcept) = default;

_CCCL_HIDE_FROM_ABI managed_accessor(const managed_accessor&) noexcept(__is_copy_ctor_noexcept) = default;

_LIBCUDACXX_HIDE_FROM_ABI constexpr __reference access(__data_handle_type __p, size_t __i) const
noexcept(__is_access_noexcept)
{
NV_IF_ELSE_TARGET(NV_IS_HOST, (__check_managed_pointer(__p);), (__check_device_pointer(__p);))
return _Accessor::access(__p, __i);
}

_LIBCUDACXX_HIDE_FROM_ABI constexpr __data_handle_type offset(__data_handle_type __p, size_t __i) const
noexcept(__is_offset_noexcept)
{
NV_IF_ELSE_TARGET(NV_IS_HOST, (__check_managed_pointer(__p);), (__check_device_pointer(__p);))
return _Accessor::offset(__p, __i);
}
};

/***********************************************************************************************************************
* Accessibility Traits
**********************************************************************************************************************/

template <typename T>
inline constexpr bool is_host_accessible_v = false;

template <typename T>
inline constexpr bool is_device_accessible_v = false;

template <typename _Accessor>
inline constexpr bool is_host_accessible_v<host_accessor<_Accessor>> = true;

template <typename _Accessor>
inline constexpr bool is_host_accessible_v<managed_accessor<_Accessor>> = true;

template <typename _Accessor>
inline constexpr bool is_device_accessible_v<device_accessor<_Accessor>> = true;

template <typename _Accessor>
inline constexpr bool is_device_accessible_v<managed_accessor<_Accessor>> = true;

_LIBCUDACXX_END_NAMESPACE_CUDA

#endif // _CUDA___MDSPAN_HOST_DEVICE_ACCESSOR
59 changes: 59 additions & 0 deletions libcudacxx/include/cuda/__mdspan/host_device_mdspan.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
//===----------------------------------------------------------------------===//
//
// Part of libcu++, the C++ Standard Library for your entire system,
// 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.
//
//===----------------------------------------------------------------------===//

#ifndef _CUDA___MDSPAN_HOST_DEVICE_MDSPAN
#define _CUDA___MDSPAN_HOST_DEVICE_MDSPAN

#include <cuda/std/detail/__config>

#if defined(_CCCL_IMPLICIT_SYSTEM_HEADER_GCC)
# pragma GCC system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_CLANG)
# pragma clang system_header
#elif defined(_CCCL_IMPLICIT_SYSTEM_HEADER_MSVC)
# pragma system_header
#endif // no system header

#include <cuda/__mdspan/host_device_accessor.h>
#include <cuda/std/mdspan>

_LIBCUDACXX_BEGIN_NAMESPACE_CUDA

template <typename _ElementType,
typename _Extents,
typename _LayoutPolicy = _CUDA_VSTD::layout_right,
typename _AccessorPolicy = _CUDA_VSTD::default_accessor<_ElementType>>
using host_mdspan = _CUDA_VSTD::mdspan<_ElementType, _Extents, _LayoutPolicy, host_accessor<_AccessorPolicy>>;

template <typename _ElementType,
typename _Extents,
typename _LayoutPolicy = _CUDA_VSTD::layout_right,
typename _AccessorPolicy = _CUDA_VSTD::default_accessor<_ElementType>>
using device_mdspan = _CUDA_VSTD::mdspan<_ElementType, _Extents, _LayoutPolicy, device_accessor<_AccessorPolicy>>;

template <typename _ElementType,
typename _Extents,
typename _LayoutPolicy = _CUDA_VSTD::layout_right,
typename _AccessorPolicy = _CUDA_VSTD::default_accessor<_ElementType>>
using managed_mdspan = _CUDA_VSTD::mdspan<_ElementType, _Extents, _LayoutPolicy, managed_accessor<_AccessorPolicy>>;

/***********************************************************************************************************************
* Accessibility Traits
**********************************************************************************************************************/

template <typename _Tp, typename _Ep, typename _Lp, typename _Ap>
inline constexpr bool is_host_accessible_v<_CUDA_VSTD::mdspan<_Tp, _Ep, _Lp, _Ap>> = is_host_accessible_v<_Ap>;

template <typename _Tp, typename _Ep, typename _Lp, typename _Ap>
inline constexpr bool is_device_accessible_v<_CUDA_VSTD::mdspan<_Tp, _Ep, _Lp, _Ap>> = is_device_accessible_v<_Ap>;

_LIBCUDACXX_END_NAMESPACE_CUDA

#endif // _CUDA___MDSPAN_HOST_DEVICE_MDSPAN
24 changes: 24 additions & 0 deletions libcudacxx/test/libcudacxx/cuda/mdspan/device_accessor.fail.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//===----------------------------------------------------------------------===//
//
// Part of the libcu++ 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.
//
//===----------------------------------------------------------------------===//
#include <cuda/__mdspan/host_device_mdspan.h>

#include "test_macros.h"

int main(int, char**)
{
int array[] = {1, 2, 3, 4};
using ext_t = cuda::std::extents<int, 4>;
cuda::device_mdspan<int, ext_t> d_md{array, ext_t{}};
#if !defined(__CUDA_ARCH__)
unused(d_md[0]);
#elif _CCCL_COMPILER(NVRTC)
static_assert(false, "unsupported test for nvrtc");
#endif
return 0;
}
Loading