This is an archive of the discontinued LLVM Phabricator instance.

[clang] Add __is_trivially_equality_comparable
ClosedPublic

Authored by philnik on Mar 29 2023, 11:48 AM.

Details

Summary

This patch adds a new trait to allow standard libraries to forward std::equal calls to memcmp in more cases.

Diff Detail

Event Timeline

philnik created this revision.Mar 29 2023, 11:48 AM
Herald added a project: Restricted Project. · View Herald TranscriptMar 29 2023, 11:48 AM
philnik requested review of this revision.Mar 29 2023, 11:48 AM
Herald added a project: Restricted Project. · View Herald TranscriptMar 29 2023, 11:48 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
philnik updated this revision to Diff 509598.Mar 30 2023, 3:37 AM

Try to fix CI

Herald added a project: Restricted Project. · View Herald TranscriptMar 30 2023, 3:37 AM
Herald added a reviewer: Restricted Project. · View Herald Transcript
shafik added a subscriber: shafik.Mar 30 2023, 4:15 PM
shafik added inline comments.
clang/lib/AST/RecordLayoutBuilder.cpp
3315 ↗(On Diff #509598)
3316–3317 ↗(On Diff #509598)
3351 ↗(On Diff #509598)

Hmmm, is this effectively std::has_unique_object_representations (ensuring that all bits of the object representation contribute to the value)?

clang/lib/AST/Type.cpp
2609

Dependent types should probably return false?

2639

I think this might be missing cases, like complex integers, scoped enumerations, vector types?, pointer types, atomic versions of these types...

philnik updated this revision to Diff 510865.Apr 4 2023, 11:26 AM
philnik marked 5 inline comments as done.

Address comments

Hmmm, is this effectively std::has_unique_object_representations (ensuring that all bits of the object representation contribute to the value)?

These two traits are indeed very similar. The main difference is that has_unique_object_representations requires that the type is trivially copyable, and __is_trivially_equality_comparable requires a defaulted equality comparison operator.

clang/lib/AST/Type.cpp
2639

I think this should be covered by the switch to using hasUniqueObjectRepresentation.

philnik updated this revision to Diff 510887.Apr 4 2023, 12:14 PM

Remove formatting changes

Ah, I like this approach -- it keeps things roughly in sync with checking for unique object representations, which is great. I spotted a few questions, but in general this is heading in the right direction. You should also add a release note to clang/docs/ReleaseNotes.rst so that users know there's a new builtin coming.

I suppose one thing to double-check: do you know of any other STL implementations that might be using this identifier as part of their implementation details? (We've had issues in the past where we accidentally stole a reserved identifier that was being used by libstdc++ and it caused issues.)

clang/include/clang/AST/DeclCXX.h
1449 ↗(On Diff #510887)

Spurious whitespace change.

clang/lib/AST/Type.cpp
2598–2599

Hmmm, is this correct? I thought there was a defaulted equality comparison operator in this case, but it's defined as deleted.

http://eel.is/c++draft/class.compare.default#2

Perhaps this function is looking for a usable defaulted equality comparison operator and not just "does it have one at all"?

2616–2617

Do we have to look for fields with references per http://eel.is/c++draft/class.compare.default#2 ?

4654–4655

Spurious whitespace changes.

philnik updated this revision to Diff 511460.Apr 6 2023, 9:52 AM
philnik marked 4 inline comments as done.

Address comments

Ah, I like this approach -- it keeps things roughly in sync with checking for unique object representations, which is great. I spotted a few questions, but in general this is heading in the right direction. You should also add a release note to clang/docs/ReleaseNotes.rst so that users know there's a new builtin coming.

I suppose one thing to double-check: do you know of any other STL implementations that might be using this identifier as part of their implementation details? (We've had issues in the past where we accidentally stole a reserved identifier that was being used by libstdc++ and it caused issues.)

Neither libstdc++ nor the MSVC STL use the __is_trivially_equality_comparable AFAICT. A sourcegraph search also exclusively finds libc++: https://sourcegraph.com/search?q=context%3Aglobal+__is_trivially_equality_comparable&patternType=standard&sm=1&groupBy=repo (which only started using it in this release cycle, so it shouldn't be a problem).

clang/lib/AST/Type.cpp
2598–2599

Yes, this is looking for a usable one. I've renamed it. (Maybe someone has an idea for a better name?)

2616–2617

Good catch!

Ah, I like this approach -- it keeps things roughly in sync with checking for unique object representations, which is great. I spotted a few questions, but in general this is heading in the right direction. You should also add a release note to clang/docs/ReleaseNotes.rst so that users know there's a new builtin coming.

I suppose one thing to double-check: do you know of any other STL implementations that might be using this identifier as part of their implementation details? (We've had issues in the past where we accidentally stole a reserved identifier that was being used by libstdc++ and it caused issues.)

Neither libstdc++ nor the MSVC STL use the __is_trivially_equality_comparable AFAICT. A sourcegraph search also exclusively finds libc++: https://sourcegraph.com/search?q=context%3Aglobal+__is_trivially_equality_comparable&patternType=standard&sm=1&groupBy=repo (which only started using it in this release cycle, so it shouldn't be a problem).

Perfect, thank you for double-checking!

One thing I can't quite determine is whether we expect to call this type trait often or not. Are either of the uses in libc++ going to be instantiated frequently? I'm trying to figure out whether we want the current implementation of HasNonDeletedDefaultedEqualityComparison() or whether we should bite the bullet up front and add another bit to CXXRecordDecl::DefinitionData so we compute this property once as the class is being formed and don't have to loop over all of the methods and bases each time. We do this for other special member functions, as with: https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/DeclCXX.h#L695

clang/lib/AST/Type.cpp
2618–2619

Why the check for !RecordType? If any field is a reference, we can bail out early, so I think this is better as:

if (FD->getType()->isReferenceType())
  return false;
if (const auto *RD = FD->getType()->getAsCXXRecordDecl())
  return HasNonDeletedDefaultedEqualityComparison(RD);
return true;

WDYT?

2631–2632

Avoids calling the same method twice and avoids using a type name as a declaration identifier.

philnik updated this revision to Diff 511762.Apr 7 2023, 12:03 PM
philnik marked 2 inline comments as done.

Address comments

One thing I can't quite determine is whether we expect to call this type trait often or not. Are either of the uses in libc++ going to be instantiated frequently? I'm trying to figure out whether we want the current implementation of HasNonDeletedDefaultedEqualityComparison() or whether we should bite the bullet up front and add another bit to CXXRecordDecl::DefinitionData so we compute this property once as the class is being formed and don't have to loop over all of the methods and bases each time. We do this for other special member functions, as with: https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/DeclCXX.h#L695

We currently use __is_trivially_equality_comparable only for std::equal (and I'm working on using it for std::find), and I don't think there are a lot more places where we can use it. It's also only relevant for cases where we have raw pointers. Would the enable_if be evaluated, even if the arguments aren't raw pointers in the example below?

template <class T, class U, enable_if_t<is_same_v<remove_cvref_t<T>, remove_cvref_t<U>> && __is_trivially_equality_comparable(T)>>
bool equal(T* first1, T* last1, U* first2);

<non-raw-pointer-overload>

If not, then I don't think it makes sense to save the information, since it will most likely not be evaluated more than once or twice per type.

clang/lib/AST/Type.cpp
2618–2619

I don't really have an opinion here. Your version seems reasonable, so I'm going to take it.

I mainly glossed over the patch and didn't do a real review.

clang/lib/AST/ASTContext.cpp
2672
libcxx/include/__type_traits/is_equality_comparable.h
46 ↗(On Diff #511762)

This does not magically use the builtin right? Does the patch miss that parts that use the builtin or is that a followup? If it's a followup I would prefer move the libc++ code changes of this patch to a separate review.

philnik marked an inline comment as done.Apr 8 2023, 6:42 AM
philnik added inline comments.
libcxx/include/__type_traits/is_equality_comparable.h
46 ↗(On Diff #511762)

No, it doesn't magically use the builtin. I plan to do that in a follow-up. These changes are required though, since clang grabs the name with this patch.

Mordante added inline comments.Apr 8 2023, 11:08 AM
libcxx/include/__type_traits/is_equality_comparable.h
46 ↗(On Diff #511762)

But it would still be possible to have these libc++ changes in a separate review and commit them separately. I would say the libc++ changes are quite trivial and can be landed quite quickly.

One thing I can't quite determine is whether we expect to call this type trait often or not. Are either of the uses in libc++ going to be instantiated frequently? I'm trying to figure out whether we want the current implementation of HasNonDeletedDefaultedEqualityComparison() or whether we should bite the bullet up front and add another bit to CXXRecordDecl::DefinitionData so we compute this property once as the class is being formed and don't have to loop over all of the methods and bases each time. We do this for other special member functions, as with: https://github.com/llvm/llvm-project/blob/main/clang/include/clang/AST/DeclCXX.h#L695

We currently use __is_trivially_equality_comparable only for std::equal (and I'm working on using it for std::find), and I don't think there are a lot more places where we can use it. It's also only relevant for cases where we have raw pointers. Would the enable_if be evaluated, even if the arguments aren't raw pointers in the example below?

template <class T, class U, enable_if_t<is_same_v<remove_cvref_t<T>, remove_cvref_t<U>> && __is_trivially_equality_comparable(T)>>
bool equal(T* first1, T* last1, U* first2);

<non-raw-pointer-overload>

If not, then I don't think it makes sense to save the information, since it will most likely not be evaluated more than once or twice per type.

I believe that we deduce the type of T before we can instantiate any of the deduced template parameters, so I think the enable_if is not evaluated unless T is known to have a raw pointer type. Based on that, I don't think we need to steal any bits anywhere right now -- we can use the current approach and if it turns up on the hot path at some point, we can address the concern then. The clang bits LGTM, but I'll hold off on signing off until someone from libc++ approves as well.

philnik updated this revision to Diff 512201.Apr 10 2023, 11:15 AM
philnik marked 3 inline comments as done.
  • Moved libc++ changes into it's own PR
  • Rebased
  • Fixed enum false-positive
libcxx/include/__type_traits/is_equality_comparable.h
46 ↗(On Diff #511762)
This revision is now accepted and ready to land.Apr 17 2023, 5:18 AM
This revision was automatically updated to reflect the committed changes.
kadircet added a subscriber: kadircet.EditedApr 18 2023, 6:29 AM

Hi folks!

We have a setup in which clang (more specifically clang-tools) is always built from a version close to HEAD and libcxx is fetched from user's checkout of the codebase (which can lag behind HEAD for ~a month).
So the 1 week gap between https://github.com/llvm/llvm-project/commit/380b6a13dad61e1d90e68fdd128af5dc177db3e1 and this patch is not enough for us to prevent most of the breakages (clang-tools we have cannot work on checkouts that are not synced past this commit as they hit the name conflict).

Hence is it possible to revert this change and land it couple weeks later or add the token behind a PP-directive (looking at the way token is introduced having this optionally available seems to be hard though)?

also it would be nice to have general policies about changes that might break backward compat and how long to wait (i guess we would be more careful if the libcxx symbol was actually used in a released version). do we already have any guidance around that so that we can be prepared for those in the future?

cjdb added a subscriber: cjdb.Apr 18 2023, 6:15 PM

This patch is causing breakages downstream because it didn't have do the struct dance, so I've added D148677.