CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
306 lines
11 KiB
C++
306 lines
11 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// 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>
|
|
#include <cuda/std/type_traits>
|
|
|
|
#include "test_macros.h"
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
// USER 1 CODE
|
|
|
|
namespace user1
|
|
{
|
|
|
|
template <class ElementType>
|
|
class AccessorB;
|
|
|
|
// AccessorA is more type-erased than AccessorB.
|
|
template <class ElementType>
|
|
class AccessorA
|
|
{
|
|
public:
|
|
using offset_policy = AccessorA<ElementType>;
|
|
using element_type = ElementType;
|
|
using reference = ElementType&;
|
|
using data_handle_type = ElementType*;
|
|
|
|
constexpr AccessorA() noexcept = default;
|
|
|
|
template <class OtherElementType,
|
|
cuda::std::enable_if_t<cuda::std::is_convertible_v<OtherElementType (*)[], element_type (*)[]>, int> = 0>
|
|
__host__ __device__ constexpr AccessorA(const AccessorA<OtherElementType>&) noexcept
|
|
{}
|
|
|
|
// Conversion from AccessorB to AccessorA type-erases; it has no preconditions, and can therefore be safely implicit.
|
|
template <class OtherElementType,
|
|
cuda::std::enable_if_t<cuda::std::is_convertible_v<OtherElementType (*)[], element_type (*)[]>, int> = 0>
|
|
__host__ __device__ constexpr AccessorA(const AccessorB<OtherElementType>&) noexcept
|
|
{}
|
|
|
|
__host__ __device__ constexpr reference access(data_handle_type p, size_t i) const noexcept
|
|
{
|
|
return p[i];
|
|
}
|
|
|
|
__host__ __device__ constexpr typename offset_policy::data_handle_type
|
|
offset(data_handle_type p, size_t i) const noexcept
|
|
{
|
|
return p + i;
|
|
}
|
|
};
|
|
|
|
template <class ElementType>
|
|
class AccessorB
|
|
{
|
|
public:
|
|
using offset_policy = AccessorA<ElementType>;
|
|
using element_type = ElementType;
|
|
using reference = ElementType&;
|
|
using data_handle_type = ElementType*;
|
|
|
|
constexpr AccessorB() noexcept = default;
|
|
|
|
template <class OtherElementType,
|
|
cuda::std::enable_if_t<cuda::std::is_convertible_v<OtherElementType (*)[], element_type (*)[]>, int> = 0>
|
|
__host__ __device__ constexpr AccessorB(const AccessorB<OtherElementType>&) noexcept
|
|
{}
|
|
|
|
// Conversion from AccessorA to AccessorB asserts a precondition;
|
|
// it un-type-erases from less specific AccessorA to more specific AccessorB. Thus, it is explicit.
|
|
template <class OtherElementType,
|
|
cuda::std::enable_if_t<cuda::std::is_convertible_v<OtherElementType (*)[], element_type (*)[]>, int> = 0>
|
|
__host__ __device__ constexpr explicit AccessorB(const AccessorA<OtherElementType>&) noexcept
|
|
{}
|
|
|
|
__host__ __device__ constexpr reference access(data_handle_type p, size_t i) const noexcept
|
|
{
|
|
return p[i];
|
|
}
|
|
|
|
__host__ __device__ constexpr typename offset_policy::data_handle_type
|
|
offset(data_handle_type p, size_t i) const noexcept
|
|
{
|
|
return p + i;
|
|
}
|
|
};
|
|
|
|
} // namespace user1
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
// USER 2 CODE
|
|
|
|
namespace user2
|
|
{
|
|
|
|
// Imagine that a different user1 writes AccessorC.
|
|
// They can't change the public interface of AccessorA, but they want to permit a conversion from AccessorC to
|
|
// AccessorA. AccessorA happens to be more type-erased than AccessorC, so this is a conversion without preconditions;
|
|
// therefore, it's not explicit.
|
|
template <class ElementType>
|
|
class AccessorC
|
|
{
|
|
public:
|
|
using offset_policy = AccessorC<ElementType>;
|
|
using element_type = ElementType;
|
|
using reference = ElementType&;
|
|
using data_handle_type = ElementType*;
|
|
|
|
constexpr AccessorC() noexcept = default;
|
|
|
|
template <class OtherElementType,
|
|
cuda::std::enable_if_t<cuda::std::is_convertible_v<OtherElementType (*)[], element_type (*)[]>, int> = 0>
|
|
__host__ __device__ constexpr AccessorC(const AccessorC<OtherElementType>&) noexcept
|
|
{}
|
|
|
|
__host__ __device__ constexpr operator user1::AccessorA<element_type>() const noexcept
|
|
{
|
|
return {};
|
|
}
|
|
|
|
__host__ __device__ constexpr reference access(data_handle_type p, size_t i) const noexcept
|
|
{
|
|
return p[i];
|
|
}
|
|
|
|
__host__ __device__ constexpr typename offset_policy::data_handle_type
|
|
offset(data_handle_type p, size_t i) const noexcept
|
|
{
|
|
return p + i;
|
|
}
|
|
};
|
|
|
|
// Imagine that a different user1 writes AccessorD.
|
|
// They can't change the public interface of AccessorB, but they want to permit a conversion from AccessorD to
|
|
// AccessorB. This is a conversion with preconditions, so it's explicit.
|
|
template <class ElementType>
|
|
class AccessorD
|
|
{
|
|
public:
|
|
using offset_policy = AccessorD<ElementType>;
|
|
using element_type = ElementType;
|
|
using reference = ElementType&;
|
|
using data_handle_type = ElementType*;
|
|
|
|
constexpr AccessorD() noexcept = default;
|
|
|
|
template <class OtherElementType,
|
|
cuda::std::enable_if_t<cuda::std::is_convertible_v<OtherElementType (*)[], element_type (*)[]>, int> = 0>
|
|
__host__ __device__ constexpr AccessorD(const AccessorD<OtherElementType>&) noexcept
|
|
{}
|
|
|
|
__host__ __device__ constexpr explicit operator user1::AccessorB<element_type>() const noexcept
|
|
{
|
|
return {};
|
|
}
|
|
|
|
__host__ __device__ constexpr reference access(data_handle_type p, size_t i) const noexcept
|
|
{
|
|
return p[i];
|
|
}
|
|
|
|
__host__ __device__ constexpr typename offset_policy::data_handle_type
|
|
offset(data_handle_type p, size_t i) const noexcept
|
|
{
|
|
return p + i;
|
|
}
|
|
};
|
|
|
|
} // namespace user2
|
|
|
|
//----------------------------------------------------------------------------------------------------------------------
|
|
// TEST CODE
|
|
|
|
__host__ __device__ void test_host_device_accessor_conversions()
|
|
{
|
|
using user1::AccessorA;
|
|
using user1::AccessorB;
|
|
using WrapperA = cuda::__restrict_accessor<AccessorA<float>>;
|
|
using WrapperAconst = cuda::__restrict_accessor<AccessorA<const float>>;
|
|
using WrapperB = cuda::__restrict_accessor<AccessorB<float>>;
|
|
{
|
|
// Test CTAD with wrapping constructor
|
|
WrapperA wrapper_acc1(AccessorA<float>{});
|
|
static_assert(cuda::std::is_same_v<decltype(wrapper_acc1), WrapperA>);
|
|
WrapperAconst wrapper_acc_const1(AccessorA<const float>{});
|
|
static_assert(cuda::std::is_same_v<decltype(wrapper_acc_const1), WrapperAconst>);
|
|
|
|
// Test CTAD with copy constructor
|
|
WrapperA wrapper_acc2{wrapper_acc1};
|
|
static_assert(cuda::std::is_same_v<decltype(wrapper_acc2), decltype(wrapper_acc1)>);
|
|
WrapperAconst wrapper_acc_const2{wrapper_acc_const1};
|
|
static_assert(cuda::std::is_same_v<decltype(wrapper_acc_const2), decltype(wrapper_acc_const1)>);
|
|
unused(wrapper_acc2);
|
|
unused(wrapper_acc_const2);
|
|
|
|
// Test converting constructor: cuda::__restrict_accessor<AccessorA<const T>>(AccessorA<T>)
|
|
WrapperAconst wrapper_acc_const3{wrapper_acc1};
|
|
unused(wrapper_acc_const3);
|
|
// Test implicit conversion: cuda::__restrict_accessor<AccessorA<T>> -> cuda::__restrict_accessor<AccessorA<const
|
|
// T>>
|
|
auto f = [](const WrapperA& wrapper_acc1) -> WrapperAconst {
|
|
return wrapper_acc1;
|
|
};
|
|
unused(f(WrapperA{}));
|
|
}
|
|
{
|
|
// Test (explicit) converting constructor: cuda::__restrict_accessor<AccessorB<T>>(AccessorA<T>)
|
|
WrapperB wrapper_acc3{WrapperA{}};
|
|
// Test implicit conversion from AccessorB<T> to AccessorA<T> (type erasure)
|
|
auto f1 = [](const WrapperB& wrapper_acc1) -> WrapperA {
|
|
return wrapper_acc1;
|
|
};
|
|
unused(f1(wrapper_acc3));
|
|
|
|
// Test implicit conversion from AccessorB<T> to AccessorA<const T> (type erasure)
|
|
auto f2 = [](const WrapperB& wrapper_acc1) -> WrapperAconst {
|
|
return wrapper_acc1;
|
|
};
|
|
unused(f2(wrapper_acc3));
|
|
|
|
// Test that implicit conversion from AccessorA<T> to AccessorB<T> is forbidden
|
|
auto f3 = [](const WrapperA& wrapper_acc1) -> WrapperB {
|
|
return WrapperB{wrapper_acc1};
|
|
// return wrapper_acc1; // rightfully does not compile
|
|
};
|
|
unused(f3(WrapperA{}));
|
|
}
|
|
}
|
|
|
|
__host__ __device__ void test_conversion()
|
|
{
|
|
using user1::AccessorA;
|
|
using user1::AccessorB;
|
|
using user2::AccessorC;
|
|
using user2::AccessorD;
|
|
using WrapperA = cuda::__restrict_accessor<AccessorA<float>>;
|
|
using WrapperB = cuda::__restrict_accessor<AccessorB<float>>;
|
|
using WrapperC = cuda::__restrict_accessor<AccessorC<float>>;
|
|
using WrapperD = cuda::__restrict_accessor<AccessorD<float>>;
|
|
{
|
|
// Test explicit and implicit conversion from cuda::__restrict_accessor<AccessorC<T>> to
|
|
// cuda::__restrict_accessor<AccessorA<T>>. This works because cuda::__restrict_accessor<AccessorC<T>> publicly
|
|
// inherits from AccessorC<T>, so it inherits AccessorC<T>'s conversion operators.
|
|
WrapperA wrapper_acc1{WrapperC{}};
|
|
auto f1 = [](const WrapperA& wrapper_acc1) -> WrapperA {
|
|
return wrapper_acc1;
|
|
};
|
|
unused(f1(WrapperC{}));
|
|
}
|
|
{
|
|
// Test explicit conversion from cuda::__restrict_accessor<AccessorD<T>> to cuda::__restrict_accessor<AccessorB<T>>.
|
|
// This works because cuda::__restrict_accessor<AccessorD<T>> publicly inherits from AccessorD<T>,
|
|
// so it inherits AccessorD<T>'s conversion operators.
|
|
WrapperB wrapper_acc1{WrapperD{}};
|
|
auto f1 = [](const WrapperD& w) -> WrapperB {
|
|
return WrapperB{};
|
|
// return w; // rightfully does not compile
|
|
};
|
|
unused(f1(WrapperD{}));
|
|
}
|
|
}
|
|
|
|
// Application: conversion of cuda::__restrict_accessor<aligned_accessor<T, N>>
|
|
// to cuda::__restrict_accessor<default_accessor<T>>.
|
|
__host__ __device__ void test_aligned_to_default()
|
|
{
|
|
using WrapperDefault = cuda::__restrict_accessor<cuda::std::default_accessor<float>>;
|
|
using WrapperAligned = cuda::__restrict_accessor<cuda::std::aligned_accessor<float, 16>>;
|
|
WrapperAligned wrapper_align_acc{cuda::std::aligned_accessor<float, 16>{}};
|
|
auto f = [](const WrapperAligned& w) -> WrapperDefault {
|
|
return w;
|
|
};
|
|
unused(f(wrapper_align_acc));
|
|
}
|
|
|
|
// Application: Explicit conversion of cuda::__restrict_accessor<default_accessor<T>>
|
|
// to cuda::__restrict_accessor<aligned_accessor<T, N>>.
|
|
__host__ __device__ void test_default_to_aligned()
|
|
{
|
|
using WrapperDefault = cuda::__restrict_accessor<cuda::std::default_accessor<float>>;
|
|
using WrapperAligned = cuda::__restrict_accessor<cuda::std::aligned_accessor<float, 16>>;
|
|
WrapperDefault wrapper_default_acc{cuda::std::default_accessor<float>{}};
|
|
WrapperAligned wrapper_aligned_acc{wrapper_default_acc};
|
|
auto f = [](const WrapperDefault& w) -> WrapperAligned {
|
|
return WrapperAligned{w};
|
|
// return w; // rightfully does not compile
|
|
};
|
|
unused(wrapper_aligned_acc);
|
|
unused(f(wrapper_default_acc));
|
|
}
|
|
|
|
int main(int, char**)
|
|
{
|
|
test_host_device_accessor_conversions();
|
|
test_conversion();
|
|
test_aligned_to_default();
|
|
test_default_to_aligned();
|
|
return 0;
|
|
}
|