This add an assertions checking if the combination of strides
and extents passed to layout_stride::mapping is unique.

To avoid excessive performance impact, in non-debug mode only
checks that are are O(rank) (i.e. similar cost as operator())
are performed. To achieve that, for the ranks greater or equal,
we perform two separate checks:
* required_span_size() > mdspan::__size(_M_extents) - this is
  required by not sufficient condition, that is O(rank) and
  is always performed.
* building an permutation using selection-sort based algorithm,
  this is O(rank^2) and is performed only in debug modes.

The check for non-zero stride values (performed for all non-zero
ranks) is done only if the size of the multidimensional index space
is not empty. This follows the resolution of LWG4603, and provides
consistent behavior for constructing layout_stride mapping from
layout_left/right directly or from strides extracted from it.

libstdc++-v3/ChangeLog:

        * include/std/mdspan (layout_stride::mapping::_M_check_unique):
        Define.
        (layout_stride::mapping(const extents_type&, span<....>):
        Add __glibcxx_assert on _M_check_unique.
        * testsuite/23_containers/mdspan/layouts/stride_neg.cc:
        Test for the new added assertions.
---
Are we OK with full detection only for ranks <= 2 and some subset of
problematic layouts with rank >= 3? I think this is good sweet spot.
But I see how some may argue for consistency here, but then I would 
put unique dection only in debug for all ranks.

Tested on x86_64-linux locally. *mdspan* test passed in all standard
modes and with _GLIBCXX_DEBUG. OK for trunk?

 libstdc++-v3/include/std/mdspan               | 83 ++++++++++++++++++-
 .../mdspan/layouts/stride_neg.cc              | 42 ++++++++++
 2 files changed, 123 insertions(+), 2 deletions(-)

diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan
index 5e1c4d8b1a4..69dbe091884 100644
--- a/libstdc++-v3/include/std/mdspan
+++ b/libstdc++-v3/include/std/mdspan
@@ -1893,6 +1893,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
          for (size_t __i = 0; __i < extents_type::rank(); ++__i)
            _M_strides[__i]
               = __mdspan::__index_type_cast<index_type>(__strides[__i]);
+
+         __glibcxx_assert(_M_check_unique());
        }
 
       template<typename _OIndexType>
@@ -2027,6 +2029,85 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        }
 
     private:
+      using _Strides = typename __array_traits<index_type,
+                                              extents_type::rank()>::_Type;
+
+      constexpr bool
+      _M_check_unique() const
+      {
+       constexpr size_t __rank = extents_type::rank();
+       if constexpr (__rank == 0)
+         return true;
+       // _GLIBCXX_RESOLVE_LIB_DEFECTS
+       // 4603. `layout_stride` should accept zero strides for empty `extents`
+       else if (__mdspan::__empty(_M_extents))
+         return true;
+       else if constexpr (__rank == 1)
+         return _M_strides[0] > 0;
+       else if (__mdspan::__contains_zero<const index_type, 
__rank>(_M_strides))
+         return false;
+       else if constexpr (__rank == 2)
+         return (_M_strides[1] >= _M_strides[0] * _M_extents.extent(0))
+             || (_M_strides[0] >= _M_strides[1] * _M_extents.extent(1));
+       else
+         {
+           // This is necessary, but not sufficient condition for uniquness.
+           // It requires computation in range of call of operator(), so check
+           // if first, also in non-debug mode.
+           if (required_span_size() < __mdspan::__size(_M_extents))
+             return false;
+
+           // Checking if pertmuation of strides exists is O(rank^2) so enabled
+           // only in debug mode.
+#ifdef _GLIBCXX_DEBUG
+           _Strides __extents_arr, __strides_arr;
+           for (size_t __i = 0; __i < __rank; ++__i)
+             {
+               __strides_arr[__i] = _M_strides[__i];
+               __extents_arr[__i] = _M_extents.extent(__i);
+             }
+           span<index_type> __strides = __strides_arr;
+           span<index_type> __extents = __extents_arr;
+
+           auto __popmin = [](span<index_type>& __vals)
+           {
+             index_type& __front = __vals.front();
+             index_type* __min = &__front;
+             __vals = __vals.subspan(1);
+             for (index_type& __v : __vals)
+               if (__v < *__min)
+                 __min = &__v;
+
+             std::swap(__front, *__min);
+             return __front;
+           };
+
+           // We use eager selection-sort based algorithm to permute the 
__strides.
+           index_type __prev = __popmin(__strides);
+           while (!__strides.empty())
+             {
+               const index_type __next = __popmin(__strides);
+
+               // Find biggest extents that could map to this stride.
+               index_type* __max = nullptr;
+               for (index_type& __ext : __extents)
+                 if (__next >= __prev * __ext)
+                   if (!__max || __ext > *__max)
+                     __max = &__ext;
+
+               // None of the extents passes check, non-unique layout.
+                if (!__max)
+                 return false;
+
+               std::swap(__extents.front(), *__max);
+               __extents = __extents.subspan(1);       
+               __prev = __next;
+             }
+#endif
+           return true;
+         }
+      }
+
 #if __glibcxx_submdspan
       template<typename... _Slices>
        requires (extents_type::rank() == sizeof...(_Slices))
@@ -2047,8 +2128,6 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
        }
 #endif
 
-      using _Strides = typename __array_traits<index_type,
-                                              extents_type::rank()>::_Type;
       [[no_unique_address]] extents_type _M_extents;
       [[no_unique_address]] _Strides _M_strides;
     };
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc 
b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc
index 153560bc82f..96d3c63717a 100644
--- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/stride_neg.cc
@@ -27,5 +27,47 @@ test_stride_negative()
 }
 static_assert(test_stride_negative()); // { dg-error "expansion of" } 
 
+template<size_t Rank>
+  constexpr bool
+  test_is_unique(const std::array<size_t, Rank>& extents, const 
std::array<size_t, Rank>& strides)
+  {
+    auto exts = std::dextents<size_t, Rank>(extents);
+    auto m = std::layout_stride::mapping(exts, strides); // { dg-error 
"expansion of" } 
+    (void) m;
+    return true;
+  }
+
+static_assert(test_is_unique<0>({}, {}));
+static_assert(test_is_unique<1>({0}, {0})); // empty special case
+static_assert(test_is_unique<1>({1}, {0})); // { dg-error "expansion of" }
+
+static_assert(test_is_unique<2>({0, 1}, {0, 0})); // empty special case
+static_assert(test_is_unique<2>({1, 5}, {1, 1}));
+static_assert(test_is_unique<2>({3, 4}, {3, 10}));
+static_assert(test_is_unique<2>({3, 4}, {10, 3}));
+static_assert(test_is_unique<2>({3, 4}, {2, 3})); // { dg-error "expansion of" 
}
+static_assert(test_is_unique<2>({3, 4}, {1, 2})); // { dg-error "expansion of" 
}
+
+static_assert(test_is_unique<3>({0, 1, 2}, {0, 0, 0})); // empty special case
+static_assert(test_is_unique<3>({1, 1, 5}, {1, 1, 1}));
+static_assert(test_is_unique<3>({3, 4, 5}, {2, 11, 35}));
+static_assert(test_is_unique<3>({3, 4, 5}, {35, 2, 11}));
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 3, 11})); // { dg-error 
"expansion of" }
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 2, 12})); // { dg-error 
"expansion of" }
+// Passes required_span_size check, detected only in debug mode
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 2, 25})); // { dg-error 
"expansion of" "" { target debug_mode } }
+static_assert(test_is_unique<3>({3, 4, 5}, {1, 5, 12})); // { dg-error 
"expansion of" "" { target debug_mode } }
+
+static_assert(test_is_unique<4>({0, 1, 2, 3}, {0, 0, 0, 0})); // empty special 
case
+static_assert(test_is_unique<4>({1, 1, 1, 5}, {1, 1, 1, 1}));
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {3, 16, 65, 200}));
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {200, 65, 3, 16}));
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 3, 12, 50})); // { dg-error 
"expansion of" }
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 3, 11, 60})); // { dg-error 
"expansion of" }
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 2, 12, 60})); // { dg-error 
"expansion of" }
+// Passes required_span_size check, detected only in debug mode
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 2, 12, 100})); // { dg-error 
"expansion of" "" { target debug_mode } }
+static_assert(test_is_unique<4>({3, 4, 5, 6}, {1, 5, 12, 60}));  // { dg-error 
"expansion of" "" { target debug_mode } }
+
 // { dg-prune-output "non-constant condition for static assertion" }
 // { dg-prune-output "__glibcxx_assert_fail()" }
-- 
2.54.0

Reply via email to