On Thu, Jul 3, 2025 at 12:08 PM Luc Grosheintz <luc.groshei...@gmail.com> wrote:
> > > On 7/1/25 22:56, Jonathan Wakely wrote: > > On Tue, 1 Jul 2025 at 11:32, Tomasz Kaminski <tkami...@redhat.com> > wrote: > >> > >> Hi, > >> More of the review will be later, but I have noticed that you have > added preconditions checks > >> to the layouts, and then avoid checking them inside the operator[] of > the mdspan. This is general > >> sounds good. > >> > >> However, the precondition on mdspan::operator[] is now hardened: > https://eel.is/c++draft/views.multidim#mdspan.mdspan.members-3. > >> This implies that breaking this precondition is a contract violation, > and thus the user may expect a contract violation handler to be invoked > >> for it. Amongst the information provided to the handler via > contract_violation object ( > https://eel.is/c++draft/support.contract.violation) > >> is source_location, that includes the name. > > > > Even without contracts, our __glibcxx_assert macro includes the > > __PRETTY_FUNCTION__ string in the abort message. > > > >> Given that I think we want to always check the extents in operator[] of > mdspan, and thus remove > >> the checks from layout (to avoid duplication). > > > > I think it's probably QoI whether the contract checks happen directly > > in the operator[] function or in something that it calls. If doing the > > checks in operator[] has no extra runtime cost (e.g. due to them > > getting duplicated), then I think it is better to do them there, so > > that errors are reported from the "correct" place. But if it hurts > > performance, I don't think it's essential to check them there. > > The reasoning for this approach was: > > 1. The mapping::operator() and mdspan::operator[] have the same > precondition; and mdspan::operator[] calls mapping::operator(). > > 2. The place I chose to check the precondition is where we already > have both the index and the extent in L1 and almost certainly in a > register. The hope was that together with branch prediction, this > will be a reasonably cheap place to put the check. > > 3. The layouts are highly valuable on their own. I've implemented > that piece of logic numerous times in different contexts; and it's > wonderful that soon we can convert `i, j, k` to a linear index easily > using the standard library. > Therefore, I didn't want to skip them in mapping::operator() because > they're a guard against out of bounds accesses, e.g. in a user-defined > dynamically allocated, owning, multi-dimensional array. > I think such types would have their own bounds checks, contracts, preconditions. > > 4. Finally, my assumption was that for performance critical code > one would be forced to turn off bounds checks. Hence, any place that > doesn't duplicate the check would be acceptable (until measured > otherwise). > In HPC workloads it wouldn't be uncommon to have 1M elements and 1M > iterations and 10s - 100s of different accesses per element per > iteration; per CPU core. In this loop often there's little more than > a few additions and multiplications. > > There's a few paths forwards: > > 1. Remove the check from mapping::operator() and unconditionally > check in mdspan::operator[]. > I would go for option 1. > > 2. Leave it as is and return when we do optimization or hardening. > > 3. Start measuring to figure out the cost of these checks; and then > decide. > > I'm open to all three. > > > > > > >> > >> Hope, the above makes sense. > >> > >> Regards, > >> Tomasz > >> > >> On Fri, Jun 27, 2025 at 11:12 AM Luc Grosheintz < > luc.groshei...@gmail.com> wrote: > >>> > >>> Previously the prerequisite that the arguments passed to operator() are > >>> a multi-dimensional index (of extents()) was not checked. > >>> > >>> This commit adds the __glibcxx_asserts and the required tests. > >>> > >>> libstdc++-v3/ChangeLog: > >>> > >>> * include/std/mdspan: Check prerequisites of > >>> layout_*::operator(). > >>> * testsuite/23_containers/mdspan/layouts/class_mandate_neg.cc: > >>> Add tests for prerequisites. > >>> > >>> Signed-off-by: Luc Grosheintz <luc.groshei...@gmail.com> > >>> --- > >>> libstdc++-v3/include/std/mdspan | 4 +++ > >>> .../mdspan/layouts/class_mandate_neg.cc | 26 > +++++++++++++++++++ > >>> 2 files changed, 30 insertions(+) > >>> > >>> diff --git a/libstdc++-v3/include/std/mdspan > b/libstdc++-v3/include/std/mdspan > >>> index c72a64094b7..39d02ac08df 100644 > >>> --- a/libstdc++-v3/include/std/mdspan > >>> +++ b/libstdc++-v3/include/std/mdspan > >>> @@ -441,6 +441,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > >>> _IndexType __mult = 1; > >>> auto __update = [&, __pos = 0u](_IndexType __idx) mutable > >>> { > >>> + __glibcxx_assert(cmp_less(__idx, > __exts.extent(__pos))); > >>> __res += __idx * __mult; > >>> __mult *= __exts.extent(__pos); > >>> ++__pos; > >>> @@ -651,6 +652,8 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > >>> auto __update = [&, __pos = __exts.rank()](_IndexType) > mutable > >>> { > >>> --__pos; > >>> + __glibcxx_assert(cmp_less(__ind_arr[__pos], > >>> + __exts.extent(__pos))); > >>> __res += __ind_arr[__pos] * __mult; > >>> __mult *= __exts.extent(__pos); > >>> }; > >>> @@ -822,6 +825,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > >>> { > >>> auto __update = [&, __pos = 0u](_IndexType __idx) mutable > >>> { > >>> + __glibcxx_assert(cmp_less(__idx, > __m.extents().extent(__pos))); > >>> __res += __idx * __m.stride(__pos++); > >>> }; > >>> (__update(__indices), ...); > >>> diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/class_mandate_neg.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/class_mandate_neg.cc > >>> index 7091153daba..f5aab22aff0 100644 > >>> --- > a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/class_mandate_neg.cc > >>> +++ > b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/class_mandate_neg.cc > >>> @@ -45,4 +45,30 @@ auto b5 = B<5, std::layout_stride, > std::layout_right>(); // { dg-error "require > >>> auto b6 = B<6, std::layout_stride, std::layout_left>(); // { > dg-error "required from" } > >>> auto b7 = B<7, std::layout_stride, std::layout_stride>(); // { > dg-error "required from" } > >>> > >>> +template<typename Layout> > >>> + constexpr bool > >>> + test_linear_index_0d() > >>> + { > >>> + auto m = typename Layout::mapping<std::extents<int, 0>>{}; > >>> + (void) m(0); // { dg-error "expansion of" } > >>> + return true; > >>> + } > >>> +static_assert(test_linear_index_0d<std::layout_left>()); // { > dg-error "expansion of" } > >>> +static_assert(test_linear_index_0d<std::layout_right>()); // { > dg-error "expansion of" } > >>> +static_assert(test_linear_index_0d<std::layout_stride>()); // { > dg-error "expansion of" } > >>> + > >>> +template<typename Layout> > >>> + constexpr bool > >>> + test_linear_index_3d() > >>> + { > >>> + auto m = typename Layout::mapping<std::extents<int, 3, 5, 7>>{}; > >>> + (void) m(2, 5, 5); // { dg-error "expansion of" } > >>> + return true; > >>> + } > >>> +static_assert(test_linear_index_3d<std::layout_left>()); // { > dg-error "expansion of" } > >>> +static_assert(test_linear_index_3d<std::layout_right>()); // { > dg-error "expansion of" } > >>> +static_assert(test_linear_index_3d<std::layout_stride>()); // { > dg-error "expansion of" } > >>> + > >>> // { dg-prune-output "must be representable as index_type" } > >>> +// { dg-prune-output "non-constant condition for static assertion" } > >>> +// { dg-prune-output "__glibcxx_assert" } > >>> -- > >>> 2.49.0 > >>> > > > >