[PATCH v2 1/5] libstdc++: Check prerequisites of layout_*::operator().
Luc Grosheintz
luc.grosheintz@gmail.com
Thu Jul 3 11:42:10 GMT 2025
On 7/3/25 12:45, Jonathan Wakely wrote:
> On Thu, 3 Jul 2025 at 11:12, Tomasz Kaminski <tkaminsk@redhat.com> wrote:
>> On Thu, Jul 3, 2025 at 12:08 PM Luc Grosheintz <luc.grosheintz@gmail.com> wrote:
>>> The reasoning for this approach was:
>>>
>>> 1. The mapping::operator() and mdspan::operator[] have the same
>>> precondition; and mdspan::operator[] calls mapping::operator().
>
> Yes, although a user-defined mapping might not bother to check
> preconditions. So in order for us to implement the required check in
> operator[] we really need to check it there.
>
> We could check it in *both* places, and assume that the compiler will
> see that the second check is entirely redundant.
>
> We could also check in mapping::operator() and then in
> mdspan::operator[] do something like:
>
> if constexpr (!__is_std_mapping<mapping_type>)
> __glibcxx_assert(...);
>
> So if we know there's a check in mapping::operator() then don't bother
> to check in operator[] as well.
This is an excellent description of how it's currently implemented. We
only skip the check in mdspan::operator[] if we know that the equivalent
is performed in 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.
>
> Not if it's just something wrapping a unique_ptr<T[]>, for example.
Very valid points. I'd like to add that out of bounds accesses when
iterating over multi-dimensional arrays is a reasonably frequent bug
while developing scientific codes, e.g. write (i, i) instead of (i, j)
or get an N and M mixed up. If unchecked it manifests itself in cryptic
ways; if checked it's usually trivial to fix.
To me checking in mapping::operator() seems like a good idea, I'm sure
it would have caught bugs in code I wrote.
>>>
>>> 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.
>
> My original preference was option 2, but I've convinced myself that we
> need the checks in operator[]. Rather than repeat them in both places,
> I think I'm OK with option 1 too.
I'll downgrade them to _GLIBCXX_ASSERT_DEBUG and make the check in
mdspan::operator[] unconditional.
>
>>> 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.
>
More information about the Libstdc++
mailing list