From: Luc Grosheintz Date: Mon, 8 Dec 2025 20:23:41 +0000 (+0100) Subject: libstdc++: Implement submdspan and submdspan_mapping for layout_left. [PR110352] X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=ead579d3c52a3b1d8ba5f337a2c7c3bd3749c011;p=thirdparty%2Fgcc.git libstdc++: Implement submdspan and submdspan_mapping for layout_left. [PR110352] Implements `submdspan` and `submdspan_mapping` for layout_left as described in P3663 (Future proofing mdspan). When computing the offset of the submdspan, one must check that the lower bound of the slice range isn't out-of-range. There's a few cases when the lower bound is never out-of-range: - full_extent and exts.extent(k) != 0, - collapsing slice types. If those conditions are known to hold, no checks are generated. Similarly, if all slices are full_extent, there's no need to call mapping(0,...,0) for standardized mappings. The implementation prepares to use the symmetry between layout_left and layout_right and introduces concepts like a "layout side", i.e. left, right or unknown/strided. The tests use an iterator to replace nested for-loops. Which also makes it easier to write the core test logic in a rank-independent manner. PR libstdc++/110352 libstdc++-v3/ChangeLog: * include/std/mdspan (__mdspan::__is_submdspan_mapping_result) (__mdspan::__submdspan_mapping_result, __mdspan::__fwd_prod) (__mdspan::__acceptable_slice_type, __mdspan::__slice_begin) (__mdspan::__suboffset, __mdspan::_LayoutSide, __mdspan::__mapping_side) (__mdspan::_StridesTrait, __mdspan::__substrides_generic) (__mdspan::__substrides_standardized, __mdspan::__substrides) (__mdspan::__is_unit_stride_slice, __mdspan::_SliceKind) (__mdspan::__make_slice_kind, __mdspan::__make_slice_kind_array) (__mdspan::__is_block, __mdspan::__padded_block_begin_generic) (__mdspan::__padded_block_begin, __mpdspan::_SubMdspanMapping) (__mdspan::__submdspan_mapping_impl): Define. (__mdspan::__dynamic_slice_extent, __mdspan::__static_slice_extent) (__mdspan::__subextents): Move earlier in the file. (layout_left::mapping::submdspan_mapping, __mdspan::__sliceable_mapping) (__mdspan::__submapping, submdspan): Define. * src/c++23/std.cc.in: Add submdspan. * testsuite/23_containers/mdspan/submdspan/generic.cc: New test. * testsuite/23_containers/mdspan/submdspan/selections/left.cc: Instantiate selection tests for layout_left. * testsuite/23_containers/mdspan/submdspan/selections/testcases.h: Generic tests different selections. * testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc: New test. * testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc: New test. Reviewed-by: Tomasz KamiƄski Signed-off-by: Luc Grosheintz --- diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan index f0f6630b472..0d477b7fa9e 100644 --- a/libstdc++-v3/include/std/mdspan +++ b/libstdc++-v3/include/std/mdspan @@ -374,6 +374,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION [[no_unique_address]] _Mapping mapping = _Mapping(); size_t offset{}; }; + + template + constexpr bool __is_submdspan_mapping_result = false; + + template + constexpr bool __is_submdspan_mapping_result> = true; + + template + concept __submdspan_mapping_result = __is_submdspan_mapping_result<_Mapping>; + #endif // __glibcxx_submdspan template @@ -589,6 +599,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } } + template + consteval _IndexType + __fwd_prod(span __values) + { + _IndexType __ret = 1; + for(auto __value : __values) + __ret *= __value; + return __ret; + } + // Preconditions: _r < _Extents::rank() template constexpr typename _Extents::index_type @@ -866,6 +886,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __exts.extent(_Index)}; } + template + concept __acceptable_slice_type = same_as<_Slice, full_extent_t> + || same_as<_Slice, _IndexType> || __is_constant_wrapper<_Slice> + || __is_strided_slice<_Slice>; + template consteval auto __subrank() @@ -890,6 +915,443 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __map[__i++] = __k; return __map; } + + template + constexpr auto + __slice_begin(_Slice __slice) + { + if constexpr (same_as<_Slice, full_extent_t>) + return 0; + else if constexpr (__is_strided_slice<_Slice>) + return __slice.offset; + else + return __slice; // collapsing slice + } + + template + constexpr size_t + __suboffset(const _Mapping& __mapping, const _Slices&... __slices) + { + using _IndexType = typename _Mapping::index_type; + auto __any_past_the_end = [&](index_sequence<_Is...>) + { + auto __is_past_the_end = [](const auto& __slice, const auto& __ext) + { + using _Slice = remove_cvref_t; + if constexpr (is_convertible_v<_Slice, _IndexType>) + return false; + else if constexpr (same_as<_Slice, full_extent_t> + && __ext.static_extent(0) != 0 + && __ext.static_extent(0) != dynamic_extent) + return false; + else + return __mdspan::__slice_begin(__slice) == __ext.extent(0); + }; + + const auto& __exts = __mapping.extents(); + return ((__is_past_the_end(__slices...[_Is], + __mdspan::__extract_extent<_Is>(__exts))) || ...); + }; + + if constexpr ((same_as<_Slices, full_extent_t> && ...)) + return __mdspan::__offset(__mapping); + + if (__any_past_the_end(std::make_index_sequence())) + return __mapping.required_span_size(); + return __mapping(__mdspan::__slice_begin(__slices)...); + } + + template + consteval size_t + __static_slice_extent() + { + if constexpr (same_as<_Slice, full_extent_t>) + return _Extent; + else if constexpr (same_as<_Slice, constant_wrapper<_IndexType(0)>>) + return 0; + else if constexpr (__is_constant_wrapper + && __is_constant_wrapper) + return 1 + ((typename _Slice::extent_type{}) - 1) + / (typename _Slice::stride_type{}); + else + return dynamic_extent; + } + + template + constexpr typename _Extents::index_type + __dynamic_slice_extent(const _Extents& __exts, _Slice __slice) + { + if constexpr (__is_strided_slice<_Slice>) + return __slice.extent == 0 ? 0 : 1 + (__slice.extent - 1) / __slice.stride; + else + return __exts.extent(_K); + } + + template + requires (sizeof...(_Slices) == sizeof...(_Extents)) + constexpr auto + __subextents(const extents<_IndexType, _Extents...>& __exts, + _Slices... __slices) + { + constexpr auto __inv_map = __mdspan::__inv_map_rank<_IndexType, _Slices...>(); + auto __impl = [&](std::index_sequence<_Indices...>) + { + using _SubExts = extents<_IndexType, + __mdspan::__static_slice_extent<_IndexType, + _Extents...[__inv_map[_Indices]], + _Slices...[__inv_map[_Indices]]>()...>; + if constexpr (_SubExts::rank_dynamic() == 0) + return _SubExts{}; + else + { + using _StaticSubExtents = __mdspan::_StaticExtents< + __mdspan::__static_extents<_SubExts>()>; + auto __create = [&](std::index_sequence<_Is...>) + { + constexpr auto __slice_idx = [__inv_map](size_t __i) consteval + { + return __inv_map[_StaticSubExtents::_S_dynamic_index_inv(__i)]; + }; + + return _SubExts{__mdspan::__dynamic_slice_extent<__slice_idx(_Is)>( + __exts, __slices...[__slice_idx(_Is)])...}; + }; + constexpr auto __dyn_subrank = _SubExts::rank_dynamic(); + return __create(std::make_index_sequence<__dyn_subrank>()); + } + }; + + return __impl(std::make_index_sequence<__inv_map.size()>()); + } + + enum class _LayoutSide + { + __left, + __right, + __unknown + }; + + template + consteval _LayoutSide + __mapping_side() + { + if constexpr (__is_left_padded_mapping<_Mapping> + || __mapping_of) + return _LayoutSide::__left; + if constexpr (__is_right_padded_mapping<_Mapping> + || __mapping_of) + return _LayoutSide::__right; + else + return _LayoutSide::__unknown; + } + + template<_LayoutSide _Side, size_t _Rank> + struct _StridesTrait + { + static constexpr const _LayoutSide _S_side = _Side; + + static constexpr size_t + _S_idx(size_t __k) noexcept + { + if constexpr (_Side == _LayoutSide::__left) + return __k; + else + return _Rank - 1 - __k; + } + + // Unifies the formulas for computing strides for padded and unpadded + // layouts. + template + static constexpr typename _Mapping::index_type + _S_padded_extent(const _Mapping& __mapping, size_t __k) + { + if (__k == 0) + return __mapping.stride(_S_idx(1)); + else + return __mapping.extents().extent(_S_idx(__k)); + } + + template + static consteval auto + _S_inv_map() + { + static_assert(_Side != _LayoutSide::__unknown); + auto __impl = [&](std::index_sequence<_Is...>) + { + return __mdspan::__inv_map_rank<_IndexType, _Slices...[_S_idx(_Is)]...>(); + }; + return __impl(std::make_index_sequence<_Rank>()); + } + }; + + template + constexpr auto + __substrides_generic(const _Mapping& __mapping, const _Slices&... __slices) + { + using _IndexType = typename _Mapping::index_type; + if constexpr (_SubExts::rank() == 0) + return array<_IndexType, _SubExts::rank()>{}; + else + { + auto __stride = [&__mapping](size_t __k, auto __slice) -> _IndexType + { + if constexpr (__is_strided_slice) + if (__slice.stride < __slice.extent) + return __mapping.stride(__k) * __slice.stride; + return __mapping.stride(__k); + }; + + auto __impl = [&](std::index_sequence<_Is...>) + { + constexpr auto __inv_map + = __mdspan::__inv_map_rank<_IndexType, _Slices...>(); + return array<_IndexType, _SubExts::rank()>{ + __stride(__inv_map[_Is], __slices...[__inv_map[_Is]])...}; + }; + return __impl(std::make_index_sequence<_SubExts::rank()>()); + } + }; + + template + constexpr auto + __substrides_standardized(const _Mapping& __mapping, + const _Slices&... __slices) + { + using _IndexType = typename _Mapping::index_type; + using _Trait = _StridesTrait<__mapping_side<_Mapping>(), + _Mapping::extents_type::rank()>; + using _SubTrait = _StridesTrait<__mapping_side<_Mapping>(), _SubExts::rank()>; + + constexpr size_t __sub_rank = _SubExts::rank(); + + std::array<_IndexType, __sub_rank> __ret; + if constexpr (__sub_rank > 0) + { + constexpr auto __inv_map + = _Trait::template _S_inv_map<_IndexType, _Slices...>(); + auto __loop = [&](std::index_sequence<_Ks...>) + { + size_t __i0 = 0; + size_t __stride = 1; + auto __body = [&](size_t __k, auto __slice) + { + for (size_t __i = __i0; __i < __inv_map[__k]; ++__i) + __stride *= _Trait::_S_padded_extent(__mapping, __i); + + size_t __krev = _SubTrait::_S_idx(__k); + if constexpr (__is_strided_slice) + { + if (__slice.stride < __slice.extent) + __ret[__krev] = __stride * __slice.stride; + else + __ret[__krev] = __stride; + } + else + __ret[__krev] = __stride; + + __i0 = __inv_map[__k]; + }; + + ((__body(_Ks, __slices...[_Trait::_S_idx(__inv_map[_Ks])])),...); + }; + __loop(std::make_index_sequence<__sub_rank>()); + } + return __ret; + } + + + template + constexpr auto + __substrides(const _Mapping& __mapping, const _Slices&... __slices) + { + if constexpr (__mdspan::__mapping_side<_Mapping>() == _LayoutSide::__unknown) + return __mdspan::__substrides_generic<_SubExts>(__mapping, __slices...); + else + return __mdspan::__substrides_standardized<_SubExts>(__mapping, __slices...); + } + + template + concept __is_unit_stride_slice = (__mdspan::__is_strided_slice<_Slice> + && __mdspan::__is_constant_wrapper + && _Slice::stride_type::value == 1) + || std::same_as<_Slice, full_extent_t>; + + // These are (forced) exclusive categories: + // - full & collapsing: obvious, + // - unit_strided_slice: strided_slice{a, b, cw<1>}, but not `full`, + // - strided_slice: strided_slice{a, b, c} with c != cw<1>. + enum class _SliceKind + { + __strided_slice, + __unit_strided_slice, + __full, + __collapsing + }; + + template + consteval _SliceKind + __make_slice_kind() + { + if constexpr (std::same_as<_Slice, full_extent_t>) + return _SliceKind::__full; + else if constexpr (__mdspan::__is_strided_slice<_Slice>) + { + if constexpr (__mdspan::__is_unit_stride_slice<_Slice>) + return _SliceKind::__unit_strided_slice; + else + return _SliceKind::__strided_slice; + } + else + return _SliceKind::__collapsing; + } + + template + consteval array<_SliceKind, sizeof...(_Slices)> + __make_slice_kind_array() + { + return array<_SliceKind, sizeof...(_Slices)>{ + __mdspan::__make_slice_kind<_Slices>()...}; + } + + // __block_size - 1 + // [full, ..., full, unit_slice , *] + consteval bool + __is_block(span __slice_kinds, size_t __block_size) + { + if (__block_size == 0) + return false; + + if (__block_size > __slice_kinds.size()) + return false; + + for (size_t __i = 0; __i < __block_size - 1; ++__i) + if (__slice_kinds[__i] != _SliceKind::__full) + return false; + + auto __last = __slice_kinds[__block_size - 1]; + return __last == _SliceKind::__full + || __last == _SliceKind::__unit_strided_slice; + } + + // __u __u + __sub_rank-2 + // [unit_slice, i, ..., k, full, ..., full, unit_slice, *] + static consteval size_t + __padded_block_begin_generic(span __slice_kinds, + size_t __sub_rank) + { + if (__slice_kinds[0] != _SliceKind::__full + && __slice_kinds[0] != _SliceKind::__unit_strided_slice) + return dynamic_extent; + else if (__slice_kinds.size() == 1) + return dynamic_extent; + else + { + size_t __u = 1; + while(__u < __slice_kinds.size() + && __slice_kinds[__u] == _SliceKind::__collapsing) + ++__u; + + if (__mdspan::__is_block(__slice_kinds.subspan(__u), __sub_rank -1)) + return __u; + return dynamic_extent; + } + } + + template<_LayoutSide _Side, size_t _Nm> + static consteval size_t + __padded_block_begin(span __slice_kinds, size_t __sub_rank) + { + if constexpr (_Side == _LayoutSide::__left) + return __mdspan::__padded_block_begin_generic(__slice_kinds, __sub_rank); + else + { + std::array<_SliceKind, _Nm> __rev_slices; + for(size_t __i = 0; __i < _Nm; ++__i) + __rev_slices[__i] = __slice_kinds[_Nm - 1 - __i]; + auto __rev_slice_kinds = span(__rev_slices); + + auto __u = __mdspan::__padded_block_begin_generic(__rev_slice_kinds, + __sub_rank); + return __u == dynamic_extent ? dynamic_extent : _Nm - 1 - __u; + } + } + + template<_LayoutSide _Side> + struct _SubMdspanMapping; + + template<> + struct _SubMdspanMapping<_LayoutSide::__left> + { + using _Layout = layout_left; + template using _PaddedLayout = layout_left_padded<_Pad>; + + template + static consteval size_t + _S_pad() + { + using _Extents = typename _Mapping::extents_type; + constexpr auto __sta_exts = __mdspan::__static_extents<_Extents>(0, _Us); + if constexpr (!__mdspan::__all_static(__sta_exts)) + return dynamic_extent; + else + return __mdspan::__fwd_prod(__sta_exts); + } + + template + static consteval bool + _S_is_unpadded_submdspan(span __slice_kinds, size_t __sub_rank) + { return __mdspan::__is_block(__slice_kinds, __sub_rank); } + }; + + template + constexpr auto + __submdspan_mapping_impl(const _Mapping& __mapping) + { return submdspan_mapping_result{__mapping, 0}; } + + template + requires (sizeof...(_Slices) > 0) + constexpr auto + __submdspan_mapping_impl(const _Mapping& __mapping, _Slices... __slices) + { + using _IndexType = typename _Mapping::index_type; + static_assert((__acceptable_slice_type<_Slices, _IndexType> && ...)); + + constexpr auto __side = __mdspan::__mapping_side<_Mapping>(); + constexpr auto __rank = sizeof...(_Slices); + using _Trait = _SubMdspanMapping<__side>; + using _SliceView = span; + + constexpr auto __slice_kinds = __mdspan::__make_slice_kind_array<_Slices...>(); + auto __offset = __mdspan::__suboffset(__mapping, __slices...); + auto __sub_exts = __mdspan::__subextents(__mapping.extents(), __slices...); + using _SubExts = decltype(__sub_exts); + constexpr auto __sub_rank = _SubExts::rank(); + if constexpr (__sub_rank == 0) + return submdspan_mapping_result{ + typename _Trait::_Layout::mapping(__sub_exts), __offset}; + else if constexpr (_Trait::_S_is_unpadded_submdspan( + _SliceView(__slice_kinds), __sub_rank)) + return submdspan_mapping_result{ + typename _Trait::_Layout::mapping(__sub_exts), __offset}; + else if constexpr ( + constexpr auto __u = __padded_block_begin<__side>( + _SliceView(__slice_kinds), __sub_rank); + __u != dynamic_extent) + { + constexpr auto __pad = _Trait::template _S_pad<_Mapping, __u>(); + using _Layout = typename _Trait::template _PaddedLayout<__pad>; + return submdspan_mapping_result{ + typename _Layout::mapping(__sub_exts, __mapping.stride(__u)), + __offset}; + } + else + { + auto __sub_strides + = __mdspan::__substrides<_SubExts>(__mapping, __slices...); + return submdspan_mapping_result{ + layout_stride::mapping(__sub_exts, __sub_strides), __offset}; + } + } #endif // __glibcxx_submdspan } @@ -1032,6 +1494,14 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __glibcxx_assert(__mdspan::__is_representable_extents(_M_extents)); } +#if __glibcxx_submdspan + template + requires (extents_type::rank() == sizeof...(_Slices)) + friend constexpr auto + submdspan_mapping(const mapping& __mapping, _Slices... __slices) + { return __mdspan::__submdspan_mapping_impl(__mapping, __slices...); } +#endif // __glibcxx_submdspan + [[no_unique_address]] extents_type _M_extents{}; }; @@ -2700,68 +3170,24 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __impl(make_index_sequence<__rank>()); } - template - consteval size_t - __static_slice_extent() - { - if constexpr (same_as<_Slice, full_extent_t>) - return _Extent; - else if constexpr (same_as<_Slice, constant_wrapper<_IndexType(0)>>) - return 0; - else if constexpr (__is_constant_wrapper - && __is_constant_wrapper) - return 1 + ((typename _Slice::extent_type{}) - 1) - / (typename _Slice::stride_type{}); - else - return dynamic_extent; - } + template + using __full_extent_t = std::full_extent_t; - template - constexpr typename _Extents::index_type - __dynamic_slice_extent(const _Extents& __exts, _Slice __slice) + // Enables ADL-only calls from submdspan. + void submdspan_mapping() = delete; + + template + concept __sliceable_mapping = requires(const _Mapping __m, _Slices... __slices) { - if constexpr (__is_strided_slice<_Slice>) - return __slice.extent == 0 ? 0 : 1 + (__slice.extent - 1) / __slice.stride; - else - return __exts.extent(_K); - } + { submdspan_mapping(__m, __slices...) } -> __submdspan_mapping_result; + }; - template - requires (sizeof...(_Slices) == sizeof...(_Extents)) + template constexpr auto - __subextents(const extents<_IndexType, _Extents...>& __exts, - _Slices... __slices) + __submapping(const _Mapping& __mapping, _Slices... __slices) { - constexpr auto __inv_map = __mdspan::__inv_map_rank<_IndexType, _Slices...>(); - auto __impl = [&](index_sequence<_Indices...>) - { - using _SubExtents = extents<_IndexType, - (__mdspan::__static_slice_extent<_IndexType, - _Extents...[__inv_map[_Indices]], - _Slices...[__inv_map[_Indices]]>())...>; - if constexpr (_SubExtents::rank_dynamic() == 0) - return _SubExtents{}; - else - { - using _StaticSubExtents = __mdspan::_StaticExtents< - __mdspan::__static_extents<_SubExtents>()>; - auto __create = [&](index_sequence<_Is...>) - { - constexpr auto __slice_idx = [__inv_map](size_t __i) consteval - { - return __inv_map[_StaticSubExtents::_S_dynamic_index_inv(__i)]; - }; - - return _SubExtents{ - (__mdspan::__dynamic_slice_extent<__slice_idx(_Is)>( - __exts, __slices...[__slice_idx(_Is)]))...}; - }; - constexpr auto __dyn_subrank = _SubExtents::rank_dynamic(); - return __create(make_index_sequence<__dyn_subrank>()); - } - }; - - return __impl(make_index_sequence<__inv_map.size()>()); + __mdspan::__check_valid_slices(__mapping.extents(), __slices...); + return submdspan_mapping(__mapping, __slices...); } } @@ -2792,6 +3218,25 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION }; return __impl(__mdspan::__slice_cast<_IndexType>(__raw_slices)...); } + + template + requires (sizeof...(_RawSlices) == _Extents::rank() + && __mdspan::__sliceable_mapping, + __mdspan::__full_extent_t<_RawSlices>...>) + constexpr auto + submdspan( + const mdspan<_ElementType, _Extents, _Layout, _Accessor>& __md, + _RawSlices... __raw_slices) + { + using _IndexType = typename _Extents::index_type; + auto [__mapping, __offset] = __mdspan::__submapping( + __md.mapping(), __mdspan::__slice_cast<_IndexType>(__raw_slices)...); + return std::mdspan( + __md.accessor().offset(__md.data_handle(), __offset), + std::move(__mapping), + typename _Accessor::offset_policy(__md.accessor())); + } #endif // __glibcxx_submdspan _GLIBCXX_END_NAMESPACE_VERSION diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/std.cc.in index 4962d4fc598..ef0da5d685a 100644 --- a/libstdc++-v3/src/c++23/std.cc.in +++ b/libstdc++-v3/src/c++23/std.cc.in @@ -1899,8 +1899,8 @@ export namespace std using std::submdspan_mapping_result; using std::submdspan_canonicalize_slices; using std::submdspan_extents; + using std::submdspan; #endif - // FIXME mdsubspan } #endif diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/generic.cc b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/generic.cc new file mode 100644 index 00000000000..682ff62254c --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/generic.cc @@ -0,0 +1,71 @@ +// { dg-do compile { target c++26 } } +#include + +namespace adl +{ + struct NoFriend + { + template + class mapping + { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + }; + }; + + struct NoFull + { + template + class mapping + { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + + private: + friend constexpr auto + submdspan_mapping(mapping, int) + { return std::submdspan_mapping_result{mapping{}, 0}; } + }; + }; + + struct WrongReturnValue + { + template + class mapping + { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + + private: + friend constexpr int + submdspan_mapping(mapping, std::full_extent_t) + { return 42; } + }; + }; +} + +template + concept submdspan_exists = requires (MdSpan md, Slices... slices) + { + std::submdspan(md, slices...); + }; + +template +constexpr bool +test_invalid_mapping() +{ + using Extents = std::extents; + using MdSpan = std::mdspan; + static_assert(submdspan_exists == Expected); + static_assert(submdspan_exists == Expected); + static_assert(!submdspan_exists); + static_assert(!submdspan_exists); + return true; +} +static_assert(test_invalid_mapping()); +static_assert(test_invalid_mapping()); +static_assert(test_invalid_mapping()); +static_assert(test_invalid_mapping()); diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/selections/left.cc b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/selections/left.cc new file mode 100644 index 00000000000..d6a85d0380b --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/selections/left.cc @@ -0,0 +1,9 @@ +// { dg-do run { target c++26 } } +#include "testcases.h" + +int +main() +{ + test_all(); + return 0; +} diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/selections/testcases.h b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/selections/testcases.h new file mode 100644 index 00000000000..d7b751d700c --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/selections/testcases.h @@ -0,0 +1,360 @@ +#include + +#include +#include +#include "../../layout_traits.h" +#include + +constexpr size_t dyn = std::dynamic_extent; +constexpr auto all = std::full_extent; + +template + constexpr bool is_strided_slice = false; + +template + constexpr bool is_strided_slice> = true; + +template + constexpr void + fill(const MDSpan& md) + { + using IndexType = typename MDSpan::index_type; + auto exts = md.extents(); + if constexpr (exts.rank() == 3) + for(IndexType i = 0; i < exts.extent(0); ++i) + for(IndexType j = 0; j < exts.extent(1); ++j) + for(IndexType k = 0; k < exts.extent(2); ++k) + md[i, j, k] = 100 * i + 10 * j + k; + } + +template + class multi_index_generator + { + struct sentinel + { }; + + class iterator + { + public: + constexpr + iterator(const std::array& shape) + : M_shape(shape) + { } + + constexpr iterator& + operator++() + { + if constexpr (Rank > 0) + { + ++M_indices[Rank-1]; + for(size_t i = Rank; i > 1; --i) + if (M_indices[i-1] == M_shape[i-1]) + { + M_indices[i-1] = 0; + ++M_indices[i-2]; + } + } + return *this; + } + + constexpr auto + operator*() + { return M_indices; } + + private: + friend constexpr bool + operator==(const iterator& it, sentinel) + { + if constexpr (Rank > 0) + return it.M_indices[0] == it.M_shape[0]; + else + return true; + } + + std::array M_indices{}; + std::array M_shape; + }; + + public: + constexpr + multi_index_generator(std::array shape) + : M_shape(shape) + { } + + constexpr iterator + begin() const + { return iterator(M_shape); } + + constexpr sentinel + end() const + { return sentinel{}; } + + private: + std::array M_shape; + }; + +constexpr bool +test_multi_index() +{ + auto shape = std::array{3, 5, 7, 1}; + auto gen = multi_index_generator(shape); + auto it = gen.begin(); + auto end = gen.end(); + for (int i = 0; i < shape[0]; ++i) + for (int j = 0; j < shape[1]; ++j) + for (int k = 0; k < shape[2]; ++k) + for (int l = 0; l < shape[3]; ++l) + { + VERIFY(it != end); + VERIFY(*it == std::array{i, j, k, l}); + ++it; + } + return true; +} +static_assert(test_multi_index()); + +struct +collapse +{ }; + +template + consteval auto + inv_collapsed_index_map() + { + constexpr size_t rank = sizeof...(Slices); + auto is_collapsing = std::array{std::same_as...}; + constexpr auto collapsed_rank = ((!std::same_as) + ... + 0); + + std::array ret; + if constexpr (collapsed_rank > 0) + for(size_t k = 0, i = 0; i < rank; ++i) + if (!is_collapsing[i]) + ret[k++] = i; + return ret; + } + +static_assert(inv_collapsed_index_map() + == std::array{}); + +static_assert(inv_collapsed_index_map() + == std::array{1}); + +template + constexpr std::vector + make_selection(IndexType extent, const Slice& slice) + { + if constexpr (std::convertible_to) + return {static_cast(slice)}; + else if constexpr (std::same_as) + { + auto ret = std::vector(static_cast(extent)); + std::ranges::iota(ret, 0); + return ret; + } + else if constexpr (is_strided_slice) + { + auto ret = std::vector{}; + size_t n = static_cast(slice.extent); + for(size_t i = 0; i < n; i += slice.stride) + ret.push_back(slice.offset + i); + return ret; + } + else + { + auto [begin, end] = slice; + auto ret = std::vector(static_cast(end - begin)); + std::ranges::iota(ret, begin); + return ret; + } + } + +template + constexpr bool + check_selection(std::index_sequence, auto md, Slices... slices) + { + auto exts = md.extents(); + auto outer_shape = std::array{exts.extent(0), exts.extent(1), exts.extent(2)}; + + constexpr auto full_index = inv_collapsed_index_map(); + auto make_slice = [](size_t i, auto slice) + { + if constexpr (std::same_as) + return i; + else + return slice; + }; + + auto loop_body = [&](std::index_sequence, auto ijk, + auto... slices) + { + auto submd = submdspan(md, slices...[I]...); + auto selection = std::tuple{make_selection(exts.extent(I), slices...[I])...}; + auto inner_shape = std::array{ + std::get(selection).size()... + }; + + for (auto ij : multi_index_generator(inner_shape)) + { + ((ijk[full_index[J]] = get(selection)[ij[J]]),...); + VERIFY(submd[ij] == md[ijk]); + } + }; + + for (auto ijk : multi_index_generator(outer_shape)) + loop_body(std::make_index_sequence(), ijk, + make_slice(ijk[I], slices...[I])...); + return true; + } + +template + constexpr bool + check_selection(std::mdspan md, Slices... slices) + { + auto indices = std::make_index_sequence(); + return check_selection(indices, md, slices...); + } + +template + constexpr bool + check_selection(std::extentsexts, Slices... slices) + { + auto run = [&](auto m) + { + auto storage = std::vector(m.required_span_size()); + auto md = std::mdspan(storage.data(), m); + fill(md); + return check_selection(md, slices...); + }; + + if constexpr (std::same_as) + { + auto m = typename Layout::mapping(exts, std::array{15, 2, 50}); + return run(m); + } + else + { + auto m = typename Layout::mapping(exts); + return run(m); + } + } + +template + constexpr bool + test_scalar_selection(auto exts) + { + check_selection(exts, collapse{}, collapse{}, collapse{}); + return true; + } + +template + constexpr bool + test_full_lines(auto exts) + { + check_selection(exts, all, collapse{}, collapse{}); + check_selection(exts, collapse{}, all, collapse{}); + check_selection(exts, collapse{}, collapse{}, all); + return true; + } + +template + constexpr bool + test_full_blocks(auto exts) + { + check_selection(exts, all, all, collapse{}); + check_selection(exts, all, collapse{}, all); + check_selection(exts, collapse{}, all, all); + return true; + } + +template + constexpr bool + test_cubes(auto exts) + { + auto s0 = std::pair{0, 2}; + auto s1 = std::pair{1, 4}; + auto s2 = std::pair{3, 7}; + + check_selection(exts, all, all, all); + check_selection(exts, all, all, s2); + check_selection(exts, s0, all, all); + check_selection(exts, s0, all, s2); + check_selection(exts, s0, s1, s2); + return true; + } + +template + constexpr bool + test_strided_line_selection(auto exts) + { + auto check = [&](auto s) + { + check_selection(exts, collapse{}, s, collapse{}); + }; + + check(std::strided_slice(0, 2, 2)); + check(std::strided_slice(0, 3, 2)); + check(std::strided_slice(1, 3, 2)); + check(std::strided_slice(1, std::cw<3>, std::cw<2>)); + return true; + } + +template + constexpr bool + test_strided_box_selection(auto exts) + { + auto s0 = std::strided_slice(0, 3, 2); + auto s1 = std::strided_slice(1, 4, 2); + auto s2 = std::strided_slice(0, 7, 3); + + check_selection(exts, s0, s1, s2); + return true; + } + +template + constexpr bool + test_all_cheap() + { + constexpr auto dyn_exts = std::extents(3, 5, 7); + constexpr auto sta_exts = std::extents{}; + + test_scalar_selection(dyn_exts); + test_scalar_selection(sta_exts); + static_assert(test_scalar_selection(dyn_exts)); + static_assert(test_scalar_selection(sta_exts)); + + test_full_lines(dyn_exts); + test_full_lines(sta_exts); + static_assert(test_full_lines(dyn_exts)); + static_assert(test_full_lines(sta_exts)); + + test_strided_box_selection(dyn_exts); + test_strided_box_selection(sta_exts); + static_assert(test_strided_box_selection(dyn_exts)); + static_assert(test_strided_box_selection(sta_exts)); + return true; + } + +template + constexpr bool + test_all_expensive() + { + auto run = [](auto exts) + { + test_full_blocks(exts); + test_cubes(exts); + }; + + run(std::extents(3, 5, 7)); + run(std::extents{}); + return true; + } + +template + constexpr bool + test_all() + { + test_all_cheap(); + test_all_expensive(); + return true; + } diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc new file mode 100644 index 00000000000..a37d3cd588f --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc @@ -0,0 +1,136 @@ +// { dg-do run { target c++26 } } +#include + +#include // TODO remove +#include "../layout_traits.h" +#include + +constexpr size_t dyn = std::dynamic_extent; + +template + constexpr auto + call_submdspan_mapping(const Mapping& m, std::tuple slices) + { + auto impl = [&](std::index_sequence) + { return submdspan_mapping(m, get(slices)...); }; + return impl(std::make_index_sequence()); + } + +template + constexpr bool + test_layout_unpadded_return_types() + { + constexpr auto padding_side = DeducePaddingSide::from_typename(); + using Traits = LayoutTraits; + + { + auto m0 = typename Layout::mapping(std::extents()); + auto result = submdspan_mapping(m0); + using layout_type = typename decltype(result.mapping)::layout_type; + static_assert(std::same_as); + } + + auto exts = Traits::make_extents(std::dims<5, int>(3, 5, 7, 11, 13)); + auto m = typename Layout::mapping(exts); + auto all = std::full_extent; + auto s251 = std::strided_slice{2, 5, std::cw<1>}; + + { + auto slices = std::tuple{0, 0, 0, 0, 0}; + auto result = call_submdspan_mapping(m, Traits::make_tuple(slices)); + using layout_type = typename decltype(result.mapping)::layout_type; + static_assert(std::same_as); + } + + { + auto slices = std::tuple{all, all, all, s251, 0}; + auto result = call_submdspan_mapping(m, Traits::make_tuple(slices)); + using layout_type = typename decltype(result.mapping)::layout_type; + static_assert(std::same_as); + } + + { + auto s0 = std::strided_slice{1, 1, std::cw<1>}; + auto slices = std::tuple{s0, all, all, s251, 0}; + auto result = call_submdspan_mapping(m, Traits::make_tuple(slices)); + using layout_type = typename decltype(result.mapping)::layout_type; + static_assert(is_same_padded); + } + + { + auto s0 = std::strided_slice{1, 2, std::cw<1>}; + auto slices = std::tuple{s0, all, all, s251, 0}; + auto result = call_submdspan_mapping(m, Traits::make_tuple(slices)); + using layout_type = typename decltype(result.mapping)::layout_type; + static_assert(is_same_padded); + } + + { + auto s0 = std::strided_slice{1, 2, std::cw<1>}; + auto slices = std::tuple{s0, 0, all, s251, 0}; + auto result = call_submdspan_mapping(m, Traits::make_tuple(slices)); + using layout_type = typename decltype(result.mapping)::layout_type; + static_assert(is_same_padded); + } + + { + auto s0 = std::strided_slice{1, 2, 1}; + auto slices = std::tuple{s0, all, all, s251, 0}; + auto result = call_submdspan_mapping(m, Traits::make_tuple(slices)); + using layout_type = decltype(result.mapping)::layout_type; + static_assert(std::same_as); + } + + { + auto slices = std::tuple{1, all, all, s251, 0}; + auto result = call_submdspan_mapping(m, Traits::make_tuple(slices)); + using layout_type = decltype(result.mapping)::layout_type; + static_assert(std::same_as); + } + + { + auto s3 = std::strided_slice{2, std::cw<7>, std::cw<2>}; + auto slices = std::tuple{all, all, all, s3, 0}; + auto result = call_submdspan_mapping(m, Traits::make_tuple(slices)); + using layout_type = decltype(result.mapping)::layout_type; + static_assert(std::same_as); + } + return true; + } + +template + constexpr bool + test_layout_unpadded_padding_value() + { + using Traits = LayoutTraits()>; + auto s0 = std::strided_slice{size_t(1), size_t(2), std::cw}; + auto s3 = std::strided_slice{size_t(2), size_t(5), std::cw}; + auto all = std::full_extent; + + auto check = [&](auto exts, size_t expected) + { + auto m = typename Layout::mapping(Traits::make_extents(exts)); + auto slices = std::tuple{s0, size_t(0), all, s3, size_t(0)}; + auto result = call_submdspan_mapping(m, Traits::make_tuple(slices)); + auto padding_value = decltype(result.mapping)::padding_value; + VERIFY(padding_value == expected); + }; + + check(std::extents(std::cw<3>, std::cw<5>, std::cw<7>, 11, 13), 3*5); + check(std::extents(std::cw<3>, std::cw<5>, 7, 11, 13), 3*5); + check(std::extents(std::cw<3>, 5, 7, 11, 13), dyn); + check(std::extents(3, 5, 7, 11, 13), dyn); + return true; + } + +int +main() +{ + test_layout_unpadded_return_types(); + static_assert(test_layout_unpadded_return_types()); + + test_layout_unpadded_padding_value(); + static_assert(test_layout_unpadded_padding_value()); + return 0; +} + diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc new file mode 100644 index 00000000000..1fc10a832eb --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc @@ -0,0 +1,104 @@ +// { dg-do compile { target c++26 } } +#include + +#include + +template + constexpr bool + check_slice_range(Slices... slices) + { + auto m = typename Layout::mapping>{}; + auto storage = std::vector(m.required_span_size()); + auto md = std::mdspan(storage.data(), m); + + auto submd = submdspan(md, slices...); // { dg-error "expansion of" } + (void) submd; + return true; + } + +template + constexpr bool + test_int_under() + { + check_slice_range(1, -1, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_int_under()); // { dg-error "expansion of" } + +template + constexpr bool + test_int_over() + { + check_slice_range(1, 5, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_int_over()); // { dg-error "expansion of" } + +template + constexpr bool + test_tuple_under() + { + check_slice_range(1, std::tuple{-1, 2}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_tuple_under()); // { dg-error "expansion of" } + +template + constexpr bool + test_tuple_reversed() + { + check_slice_range(1, std::tuple{3, 2}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_tuple_reversed()); // { dg-error "expansion of" } + +template + constexpr bool + test_tuple_over() + { + check_slice_range(1, std::tuple{0, 6}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_tuple_over()); // { dg-error "expansion of" } + +template + constexpr bool + test_strided_slice_zero() + { + check_slice_range(1, std::strided_slice{1, 1, 0}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_strided_slice_zero()); // { dg-error "expansion of" } + +template + constexpr bool + test_strided_slice_offset_under() + { + check_slice_range(1, std::strided_slice{-1, 1, 1}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_strided_slice_offset_under()); // { dg-error "expansion of" } + +template + constexpr bool + test_strided_slice_offset_over() + { + check_slice_range(1, std::strided_slice{6, 0, 1}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_strided_slice_offset_over()); // { dg-error "expansion of" } + +template + constexpr bool + test_strided_slice_extent_over() + { + check_slice_range(1, std::strided_slice{1, 5, 1}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_strided_slice_extent_over()); // { dg-error "expansion of" } + +// { dg-prune-output "static assertion failed" } +// { dg-prune-output "__glibcxx_assert_fail" } +// { dg-prune-output "non-constant condition" } +// { dg-prune-output "no matching function" } +// { dg-prune-output "does not satisfy placeholder constraints" }