Page MenuHomePhabricator

[libc++] Introduce __is_pointer_in_range

Authored by philnik on Feb 4 2023, 2:54 PM.


Group Reviewers
Restricted Project

This checks whether a pointer is within a range, even during constant evaluation. This allows running optimized code paths during constant evaluation, instead of falling back to the general-purpose implementation all the time. This is also a central place for comparing unrelated pointers, which is technically UB.

Diff Detail

Unit TestsFailed

170 mslibcxx CI C++03 > llvm-libc++-shared-cfg-in.libcxx/
Script: -- : 'RUN: at line 1'; /usr/bin/python3.10 /home/libcxx-builder/.buildkite-agent/builds/cdc6839a4101-1/llvm-project/libcxx-ci/libcxx/test/libcxx/lint/
150 mslibcxx CI C++11 > llvm-libc++-shared-cfg-in.libcxx/
Script: -- : 'RUN: at line 1'; /usr/bin/python3.10 /home/libcxx-builder/.buildkite-agent/builds/857ec993b307-1/llvm-project/libcxx-ci/libcxx/test/libcxx/lint/
160 mslibcxx CI C++2b > llvm-libc++-shared-cfg-in.libcxx/
Script: -- : 'RUN: at line 1'; /usr/bin/python3.10 /home/libcxx-builder/.buildkite-agent/builds/686a0c420892-1/llvm-project/libcxx-ci/libcxx/test/libcxx/lint/
140 mslibcxx CI GCC 12 / C++latest > llvm-libc++-shared-gcc-cfg-in.libcxx/
Script: -- : 'RUN: at line 1'; /usr/bin/python3.10 /home/libcxx-builder/.buildkite-agent/builds/c139304203ad-1/llvm-project/libcxx-ci/libcxx/test/libcxx/lint/
140 mslibcxx CI Modular build > llvm-libc++-shared-cfg-in.libcxx/
Script: -- : 'RUN: at line 1'; /usr/bin/python3.10 /home/libcxx-builder/.buildkite-agent/builds/c8abfc28ba77-1/llvm-project/libcxx-ci/libcxx/test/libcxx/lint/

Event Timeline

philnik created this revision.Feb 4 2023, 2:54 PM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 4 2023, 2:54 PM
philnik requested review of this revision.Feb 4 2023, 2:54 PM
Herald added a project: Restricted Project. · View Herald TranscriptFeb 4 2023, 2:54 PM
Herald added a reviewer: Restricted Project. · View Herald Transcript
philnik added inline comments.Feb 4 2023, 2:56 PM

Am I correct here that it's impossible to get aliasing pointers during constant evaluation that are not comparable normally?

philnik updated this revision to Diff 494871.Feb 5 2023, 1:21 AM

Generate files

Mordante edited the summary of this revision. (Show Details)Feb 5 2023, 3:49 AM

Note I fixed a typo in the commit message.

Thanks for working on this, I think it's useful to have, but it requires a bit more work.


I miss the module map entry.


Can you add a link to the documentation of this attribute?


Or are empty ranges prohibited?

Based on the code below it is indeed prohibited. If that is intended I really dislike the name of the function.
The function does not test whether the pointer is in the range, but whether the pointer is in the range and can be dereferenced.Maybe __is_pointer_in_dereferencable_range would be a better name.


I think we should document this is technically UB, but all supported compilers accept this.


Is this really safe on all supported platforms?


I really like to see more test. This only compares pointers where _Tp and _Up are the same type. Since the code tests different pointer types I really would like to see tests for volatile pointers against non-volatile pointers. Test with object pointers against function pointers, data member pointer, member function pointers.

philnik updated this revision to Diff 496142.Feb 9 2023, 8:24 AM
philnik marked 3 inline comments as done.

Address comments


I don't really see the point here. All the clang attributes are described at Having a link to the same page for every attribute we use seems redundant. We can add a link at the top of the file if you think that's useful.


No, empty ranges are not prohibited. This just checks that it is valid to compare the two pointers. It doesn't actually check that __begin is smaller than __end. IOW __builtin_constant_p(ptr < ptr) is always true, since the expression is known to result in false (

AFAIK a range is normally [begin, end), i.e. excluding the end pointer. Returning true for the end pointer would be inconsistent with any other function taking a range.


I think it should be. Do you have anything specific in mind?


I don't think it makes sense to call __is_pointer_in_range with function pointers or member pointers, so I added static_asserts instead.

Mordante added inline comments.Feb 11 2023, 6:02 AM

I think it would help readers to quickly find the documentation, instead of remember where the clang attributes are exactly available.


Yes a range is [begin, end), and an empty for an empty range begin == end.
I'm happy with the fix.


You can compare integral types with different sizes, but you can't just cast pointers from one to another.

Concretely I wonder whether we violate alignment rules.
For example`_Tp == int`, _Up = signed char and __ptr = 0xFFF01. This will create a const int* at an odd numbered address. (This is valid on x86, but that platform has less alignment restrictions than other platforms.)

philnik updated this revision to Diff 497246.Feb 14 2023, 2:09 AM
philnik marked 5 inline comments as done.

Address comments


Yeah, I'm not sure. Casting all pointers to char should make it safe.

ldionne requested changes to this revision.Mon, Feb 27, 9:05 AM
ldionne added inline comments.

By the way, I think this is highly relevant to the existing issue that std::less is supposed to be blessed to compare unrelated pointers, but we currently don't do anything special with it. I think we should have a single solution for both of those issues.


The new code is not equivalent to the old one. The old code does data() <= __p && __p <= data() + size(), the new code looks for data() <= __p && __p < data() + size() (notice <= vs <).


I would handle the !is-less-than-comparable case here instead of in __is_pointer_in_range. __is_pointer_in_range is more general purpose, and it doesn't make sense for it to return false when trying to compare unrelated pointers -- instead it should be a compiler error to do so.

In other words, __is_pointer_in_range should be equivalent to first <= p < last, except blessed by the compiler -- nothing less, nothing more. If that expression would not be well formed, then __is_pointer_in_range should also not be well formed, and we shouldn't try to give it a value.

This revision now requires changes to proceed.Mon, Feb 27, 9:05 AM
philnik updated this revision to Diff 501134.Tue, Feb 28, 7:32 AM
philnik marked 2 inline comments as done.

Address comments

philnik updated this revision to Diff 501228.Tue, Feb 28, 10:53 AM

Try to fix CI

ldionne accepted this revision.Thu, Mar 2, 8:33 AM

LGTM, but this needs to be rebased on the patch with the __less refactoring before going in.


That way, we technically don't have to change anything but std::less in the future.

We could also use __less instead here, and make __less be the canonical place where we bless comparison of unrelated pointers. We would then also use __less from std::less. Let's do that part as a separate patch, it's actually a nice cleanup.


Here and below.


For symmetry with above?


Could we move this test to test_cv_quals?

This revision is now accepted and ready to land.Thu, Mar 2, 8:33 AM