https://gcc.gnu.org/g:ead579d3c52a3b1d8ba5f337a2c7c3bd3749c011
commit r16-5989-gead579d3c52a3b1d8ba5f337a2c7c3bd3749c011 Author: Luc Grosheintz <[email protected]> Date: Mon Dec 8 21:23:41 2025 +0100 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 <[email protected]> Signed-off-by: Luc Grosheintz <[email protected]> Diff: --- libstdc++-v3/include/std/mdspan | 559 ++++++++++++++++++--- libstdc++-v3/src/c++23/std.cc.in | 2 +- .../23_containers/mdspan/submdspan/generic.cc | 71 +++ .../mdspan/submdspan/selections/left.cc | 9 + .../mdspan/submdspan/selections/testcases.h | 360 +++++++++++++ .../mdspan/submdspan/submdspan_mapping.cc | 136 +++++ .../mdspan/submdspan/submdspan_neg.cc | 104 ++++ 7 files changed, 1183 insertions(+), 58 deletions(-) diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan index f0f6630b4729..0d477b7fa9e8 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<typename _Tp> + constexpr bool __is_submdspan_mapping_result = false; + + template<typename _Mapping> + constexpr bool __is_submdspan_mapping_result<submdspan_mapping_result<_Mapping>> = true; + + template<typename _Mapping> + concept __submdspan_mapping_result = __is_submdspan_mapping_result<_Mapping>; + #endif // __glibcxx_submdspan template<typename _IndexType, size_t... _Extents> @@ -589,6 +599,16 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } } + template<typename _IndexType, size_t _Nm> + consteval _IndexType + __fwd_prod(span<const _IndexType, _Nm> __values) + { + _IndexType __ret = 1; + for(auto __value : __values) + __ret *= __value; + return __ret; + } + // Preconditions: _r < _Extents::rank() template<typename _Extents> constexpr typename _Extents::index_type @@ -866,6 +886,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __exts.extent(_Index)}; } + template<typename _Slice, typename _IndexType> + concept __acceptable_slice_type = same_as<_Slice, full_extent_t> + || same_as<_Slice, _IndexType> || __is_constant_wrapper<_Slice> + || __is_strided_slice<_Slice>; + template<typename _IndexType, typename... _Slices> consteval auto __subrank() @@ -890,6 +915,443 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __map[__i++] = __k; return __map; } + + template<typename _Slice> + 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<typename _Mapping, typename... _Slices> + constexpr size_t + __suboffset(const _Mapping& __mapping, const _Slices&... __slices) + { + using _IndexType = typename _Mapping::index_type; + auto __any_past_the_end = [&]<size_t... _Is>(index_sequence<_Is...>) + { + auto __is_past_the_end = [](const auto& __slice, const auto& __ext) + { + using _Slice = remove_cvref_t<decltype(__slice)>; + 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<sizeof...(__slices)>())) + return __mapping.required_span_size(); + return __mapping(__mdspan::__slice_begin(__slices)...); + } + + template<typename _IndexType, size_t _Extent, typename _Slice> + 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<typename _Slice::extent_type> + && __is_constant_wrapper<typename _Slice::stride_type>) + return 1 + ((typename _Slice::extent_type{}) - 1) + / (typename _Slice::stride_type{}); + else + return dynamic_extent; + } + + template<size_t _K, typename _Extents, typename _Slice> + 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<typename _IndexType, size_t... _Extents, typename... _Slices> + 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 = [&]<size_t... _Indices>(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 = [&]<size_t... _Is>(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<typename _Mapping> + consteval _LayoutSide + __mapping_side() + { + if constexpr (__is_left_padded_mapping<_Mapping> + || __mapping_of<layout_left, _Mapping>) + return _LayoutSide::__left; + if constexpr (__is_right_padded_mapping<_Mapping> + || __mapping_of<layout_right, _Mapping>) + 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<typename _Mapping> + 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<typename _IndexType, typename... _Slices> + static consteval auto + _S_inv_map() + { + static_assert(_Side != _LayoutSide::__unknown); + auto __impl = [&]<size_t... _Is>(std::index_sequence<_Is...>) + { + return __mdspan::__inv_map_rank<_IndexType, _Slices...[_S_idx(_Is)]...>(); + }; + return __impl(std::make_index_sequence<_Rank>()); + } + }; + + template<typename _SubExts, typename _Mapping, typename... _Slices> + 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<decltype(__slice)>) + if (__slice.stride < __slice.extent) + return __mapping.stride(__k) * __slice.stride; + return __mapping.stride(__k); + }; + + auto __impl = [&]<size_t... _Is>(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<typename _SubExts, typename _Mapping, typename... _Slices> + 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 = [&]<size_t... _Ks>(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<decltype(__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<typename _SubExts, typename _Mapping, typename... _Slices> + 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<typename _Slice> + concept __is_unit_stride_slice = (__mdspan::__is_strided_slice<_Slice> + && __mdspan::__is_constant_wrapper<typename _Slice::stride_type> + && _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<typename _Slice> + 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<typename... _Slices> + 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<const _SliceKind> __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<const _SliceKind> __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<const _SliceKind, _Nm> __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<const _SliceKind>(__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<size_t _Pad> using _PaddedLayout = layout_left_padded<_Pad>; + + template<typename _Mapping, size_t _Us> + 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<size_t _Nm> + static consteval bool + _S_is_unpadded_submdspan(span<const _SliceKind, _Nm> __slice_kinds, size_t __sub_rank) + { return __mdspan::__is_block(__slice_kinds, __sub_rank); } + }; + + template<typename _Mapping> + constexpr auto + __submdspan_mapping_impl(const _Mapping& __mapping) + { return submdspan_mapping_result{__mapping, 0}; } + + template<typename _Mapping, typename... _Slices> + 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<const _SliceKind, __rank>; + + 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<typename... _Slices> + 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<typename _IndexType, size_t _Extent, typename _Slice> - 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<typename _Slice::extent_type> - && __is_constant_wrapper<typename _Slice::stride_type>) - return 1 + ((typename _Slice::extent_type{}) - 1) - / (typename _Slice::stride_type{}); - else - return dynamic_extent; - } + template<typename _Slice> + using __full_extent_t = std::full_extent_t; - template<size_t _K, typename _Extents, typename _Slice> - constexpr typename _Extents::index_type - __dynamic_slice_extent(const _Extents& __exts, _Slice __slice) + // Enables ADL-only calls from submdspan. + void submdspan_mapping() = delete; + + template<typename _Mapping, typename... _Slices> + 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<typename _IndexType, size_t... _Extents, typename... _Slices> - requires (sizeof...(_Slices) == sizeof...(_Extents)) + template<typename _Mapping, typename... _Slices> 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 = [&]<size_t... _Indices>(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 = [&]<size_t... _Is>(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<typename _ElementType, typename _Extents, typename _Layout, + typename _Accessor, typename... _RawSlices> + requires (sizeof...(_RawSlices) == _Extents::rank() + && __mdspan::__sliceable_mapping<typename _Layout::mapping<_Extents>, + __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 4962d4fc5988..ef0da5d685a8 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 000000000000..682ff62254cf --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/generic.cc @@ -0,0 +1,71 @@ +// { dg-do compile { target c++26 } } +#include <mdspan> + +namespace adl +{ + struct NoFriend + { + template<typename Extents> + class mapping + { + public: + using extents_type = Extents; + using index_type = typename extents_type::index_type; + }; + }; + + struct NoFull + { + template<typename Extents> + 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<typename Extents> + 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<typename MdSpan, typename... Slices> + concept submdspan_exists = requires (MdSpan md, Slices... slices) + { + std::submdspan(md, slices...); + }; + +template<typename Layout, bool Expected> +constexpr bool +test_invalid_mapping() +{ + using Extents = std::extents<int, 3>; + using MdSpan = std::mdspan<double, Extents, Layout>; + static_assert(submdspan_exists<MdSpan, int> == Expected); + static_assert(submdspan_exists<MdSpan, std::full_extent_t> == Expected); + static_assert(!submdspan_exists<MdSpan>); + static_assert(!submdspan_exists<MdSpan, int, int>); + return true; +} +static_assert(test_invalid_mapping<std::layout_left, true>()); +static_assert(test_invalid_mapping<adl::NoFriend, false>()); +static_assert(test_invalid_mapping<adl::NoFull, false>()); +static_assert(test_invalid_mapping<adl::WrongReturnValue, false>()); 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 000000000000..d6a85d0380b0 --- /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<std::layout_left>(); + 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 000000000000..d7b751d700c0 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/selections/testcases.h @@ -0,0 +1,360 @@ +#include <mdspan> + +#include <vector> +#include <numeric> +#include "../../layout_traits.h" +#include <testsuite_hooks.h> + +constexpr size_t dyn = std::dynamic_extent; +constexpr auto all = std::full_extent; + +template<typename T> + constexpr bool is_strided_slice = false; + +template<typename O, typename E, typename S> + constexpr bool is_strided_slice<std::strided_slice<O, E, S>> = true; + +template<typename MDSpan> + 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<typename Int, size_t Rank> + class multi_index_generator + { + struct sentinel + { }; + + class iterator + { + public: + constexpr + iterator(const std::array<Int, Rank>& 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<Int, Rank> M_indices{}; + std::array<Int, Rank> M_shape; + }; + + public: + constexpr + multi_index_generator(std::array<Int, Rank> shape) + : M_shape(shape) + { } + + constexpr iterator + begin() const + { return iterator(M_shape); } + + constexpr sentinel + end() const + { return sentinel{}; } + + private: + std::array<Int, Rank> 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<typename... Slices> + consteval auto + inv_collapsed_index_map() + { + constexpr size_t rank = sizeof...(Slices); + auto is_collapsing = std::array{std::same_as<Slices, collapse>...}; + constexpr auto collapsed_rank = ((!std::same_as<Slices, collapse>) + ... + 0); + + std::array<size_t, collapsed_rank> 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<collapse, collapse, collapse>() + == std::array<size_t, 0>{}); + +static_assert(inv_collapsed_index_map<collapse, decltype(all), collapse>() + == std::array<size_t, 1>{1}); + +template<typename IndexType, typename Slice> + constexpr std::vector<IndexType> + make_selection(IndexType extent, const Slice& slice) + { + if constexpr (std::convertible_to<Slice, IndexType>) + return {static_cast<IndexType>(slice)}; + else if constexpr (std::same_as<Slice, std::full_extent_t>) + { + auto ret = std::vector<IndexType>(static_cast<size_t>(extent)); + std::ranges::iota(ret, 0); + return ret; + } + else if constexpr (is_strided_slice<Slice>) + { + auto ret = std::vector<IndexType>{}; + size_t n = static_cast<size_t>(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<IndexType>(static_cast<size_t>(end - begin)); + std::ranges::iota(ret, begin); + return ret; + } + } + +template<typename Layout, size_t... I, typename... Slices> + constexpr bool + check_selection(std::index_sequence<I...>, 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<Slices...>(); + auto make_slice = [](size_t i, auto slice) + { + if constexpr (std::same_as<decltype(slice), collapse>) + return i; + else + return slice; + }; + + auto loop_body = [&]<size_t... J>(std::index_sequence<J...>, 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<size_t, full_index.size()>{ + std::get<full_index[J]>(selection).size()... + }; + + for (auto ij : multi_index_generator(inner_shape)) + { + ((ijk[full_index[J]] = get<full_index[J]>(selection)[ij[J]]),...); + VERIFY(submd[ij] == md[ijk]); + } + }; + + for (auto ijk : multi_index_generator(outer_shape)) + loop_body(std::make_index_sequence<full_index.size()>(), ijk, + make_slice(ijk[I], slices...[I])...); + return true; + } + +template<typename Layout, typename...MD, typename... Slices> + constexpr bool + check_selection(std::mdspan<MD...> md, Slices... slices) + { + auto indices = std::make_index_sequence<sizeof...(slices)>(); + return check_selection<Layout>(indices, md, slices...); + } + +template<typename Layout, typename IndexType, size_t... Extents, + typename... Slices> + constexpr bool + check_selection(std::extents<IndexType, Extents...>exts, Slices... slices) + { + auto run = [&](auto m) + { + auto storage = std::vector<double>(m.required_span_size()); + auto md = std::mdspan(storage.data(), m); + fill(md); + return check_selection<Layout>(md, slices...); + }; + + if constexpr (std::same_as<Layout, std::layout_stride>) + { + 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<typename Layout> + constexpr bool + test_scalar_selection(auto exts) + { + check_selection<Layout>(exts, collapse{}, collapse{}, collapse{}); + return true; + } + +template<typename Layout> + constexpr bool + test_full_lines(auto exts) + { + check_selection<Layout>(exts, all, collapse{}, collapse{}); + check_selection<Layout>(exts, collapse{}, all, collapse{}); + check_selection<Layout>(exts, collapse{}, collapse{}, all); + return true; + } + +template<typename Layout> + constexpr bool + test_full_blocks(auto exts) + { + check_selection<Layout>(exts, all, all, collapse{}); + check_selection<Layout>(exts, all, collapse{}, all); + check_selection<Layout>(exts, collapse{}, all, all); + return true; + } + +template<typename Layout> + 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<Layout>(exts, all, all, all); + check_selection<Layout>(exts, all, all, s2); + check_selection<Layout>(exts, s0, all, all); + check_selection<Layout>(exts, s0, all, s2); + check_selection<Layout>(exts, s0, s1, s2); + return true; + } + +template<typename Layout> + constexpr bool + test_strided_line_selection(auto exts) + { + auto check = [&](auto s) + { + check_selection<Layout>(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<typename Layout> + 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<Layout>(exts, s0, s1, s2); + return true; + } + +template<typename Layout> + constexpr bool + test_all_cheap() + { + constexpr auto dyn_exts = std::extents(3, 5, 7); + constexpr auto sta_exts = std::extents<int, 3, 5, 7>{}; + + test_scalar_selection<Layout>(dyn_exts); + test_scalar_selection<Layout>(sta_exts); + static_assert(test_scalar_selection<Layout>(dyn_exts)); + static_assert(test_scalar_selection<Layout>(sta_exts)); + + test_full_lines<Layout>(dyn_exts); + test_full_lines<Layout>(sta_exts); + static_assert(test_full_lines<Layout>(dyn_exts)); + static_assert(test_full_lines<Layout>(sta_exts)); + + test_strided_box_selection<Layout>(dyn_exts); + test_strided_box_selection<Layout>(sta_exts); + static_assert(test_strided_box_selection<Layout>(dyn_exts)); + static_assert(test_strided_box_selection<Layout>(sta_exts)); + return true; + } + +template<typename Layout> + constexpr bool + test_all_expensive() + { + auto run = [](auto exts) + { + test_full_blocks<Layout>(exts); + test_cubes<Layout>(exts); + }; + + run(std::extents(3, 5, 7)); + run(std::extents<int, 3, 5, 7>{}); + return true; + } + +template<typename Layout> + constexpr bool + test_all() + { + test_all_cheap<Layout>(); + test_all_expensive<Layout>(); + 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 000000000000..a37d3cd588f6 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc @@ -0,0 +1,136 @@ +// { dg-do run { target c++26 } } +#include <mdspan> + +#include <iostream> // TODO remove +#include "../layout_traits.h" +#include <testsuite_hooks.h> + +constexpr size_t dyn = std::dynamic_extent; + +template<typename Mapping, typename... Slices> + constexpr auto + call_submdspan_mapping(const Mapping& m, std::tuple<Slices...> slices) + { + auto impl = [&]<size_t... I>(std::index_sequence<I...>) + { return submdspan_mapping(m, get<I>(slices)...); }; + return impl(std::make_index_sequence<sizeof...(Slices)>()); + } + +template<typename Layout> + constexpr bool + test_layout_unpadded_return_types() + { + constexpr auto padding_side = DeducePaddingSide::from_typename<Layout>(); + using Traits = LayoutTraits<padding_side>; + + { + 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<layout_type, Layout>); + } + + 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<layout_type, Layout>); + } + + { + 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<layout_type, Layout>); + } + + { + 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<padding_side, layout_type>); + } + + { + 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<padding_side, layout_type>); + } + + { + 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<padding_side, layout_type>); + } + + { + 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<layout_type, std::layout_stride>); + } + + { + 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<layout_type, std::layout_stride>); + } + + { + 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<layout_type, std::layout_stride>); + } + return true; + } + +template<typename Layout> + constexpr bool + test_layout_unpadded_padding_value() + { + using Traits = LayoutTraits<DeducePaddingSide::from_typename<Layout>()>; + auto s0 = std::strided_slice{size_t(1), size_t(2), std::cw<size_t(1)>}; + auto s3 = std::strided_slice{size_t(2), size_t(5), std::cw<size_t(1)>}; + 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<std::layout_left>(); + static_assert(test_layout_unpadded_return_types<std::layout_left>()); + + test_layout_unpadded_padding_value<std::layout_left>(); + static_assert(test_layout_unpadded_padding_value<std::layout_left>()); + 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 000000000000..1fc10a832ebb --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc @@ -0,0 +1,104 @@ +// { dg-do compile { target c++26 } } +#include <mdspan> + +#include <vector> + +template<typename Layout, typename... Slices> + constexpr bool + check_slice_range(Slices... slices) + { + auto m = typename Layout::mapping<std::extents<int, 3, 5, 7>>{}; + auto storage = std::vector<double>(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<typename Layout> + constexpr bool + test_int_under() + { + check_slice_range<Layout>(1, -1, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_int_under<std::layout_left>()); // { dg-error "expansion of" } + +template<typename Layout> + constexpr bool + test_int_over() + { + check_slice_range<Layout>(1, 5, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_int_over<std::layout_left>()); // { dg-error "expansion of" } + +template<typename Layout> + constexpr bool + test_tuple_under() + { + check_slice_range<Layout>(1, std::tuple{-1, 2}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_tuple_under<std::layout_left>()); // { dg-error "expansion of" } + +template<typename Layout> + constexpr bool + test_tuple_reversed() + { + check_slice_range<Layout>(1, std::tuple{3, 2}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_tuple_reversed<std::layout_left>()); // { dg-error "expansion of" } + +template<typename Layout> + constexpr bool + test_tuple_over() + { + check_slice_range<Layout>(1, std::tuple{0, 6}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_tuple_over<std::layout_left>()); // { dg-error "expansion of" } + +template<typename Layout> + constexpr bool + test_strided_slice_zero() + { + check_slice_range<Layout>(1, std::strided_slice{1, 1, 0}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_strided_slice_zero<std::layout_left>()); // { dg-error "expansion of" } + +template<typename Layout> + constexpr bool + test_strided_slice_offset_under() + { + check_slice_range<Layout>(1, std::strided_slice{-1, 1, 1}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_strided_slice_offset_under<std::layout_left>()); // { dg-error "expansion of" } + +template<typename Layout> + constexpr bool + test_strided_slice_offset_over() + { + check_slice_range<Layout>(1, std::strided_slice{6, 0, 1}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_strided_slice_offset_over<std::layout_left>()); // { dg-error "expansion of" } + +template<typename Layout> + constexpr bool + test_strided_slice_extent_over() + { + check_slice_range<Layout>(1, std::strided_slice{1, 5, 1}, 2); // { dg-error "expansion of" } + return true; + } +static_assert(test_strided_slice_extent_over<std::layout_left>()); // { 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" }
