This is an archive of the discontinued LLVM Phabricator instance.

[Clang] Implement P0848 (Conditionally Trivial Special Member Functions)
ClosedPublic

Authored by royjacobson on Jun 26 2022, 2:41 PM.

Details

Summary

This patch implements P0848 in Clang.

During the instantiation of a C++ class, in Sema::ActOnFields, we evaluate constraints for all the SMFs and compare the constraints to compute the eligibility. We defer the computation of the type's [copy-]trivial bits from addedMember to the eligibility computation, like we did for destructors in D126194. canPassInRegisters is modified as well to better respect the ineligibility of functions.

Note: Because of the non-implementation of DR1734 and DR1496, I treat deleted member functions as 'eligible' for the purpose of [copy-]triviallity. This is unfortunate, but I couldn't think of a way to make this make sense otherwise.

Diff Detail

Event Timeline

royjacobson created this revision.Jun 26 2022, 2:41 PM
Herald added a project: Restricted Project. · View Herald TranscriptJun 26 2022, 2:41 PM
h-vetinari added inline comments.
clang/www/cxx_status.html
930

Should be class="unreleased", I believe

The implementation is more or less complete, but I still need to add an AST test for canPassInRegisters.

clang/www/cxx_status.html
930

Thanks, nice catch!

Implementation ready for CR.

royjacobson retitled this revision from [WIP][Clang] Implement P0848 (Conditionally Trivial Special Member Functions) to [Clang] Implement P0848 (Conditionally Trivial Special Member Functions).Jul 13 2022, 2:43 PM
royjacobson edited the summary of this revision. (Show Details)
royjacobson added reviewers: Restricted Project, erichkeane, cor3ntin, BRevzin, CaseyCarter, aaron.ballman.
royjacobson published this revision for review.Jul 14 2022, 3:53 PM
Herald added a project: Restricted Project. · View Herald TranscriptJul 14 2022, 3:53 PM
Herald added a subscriber: cfe-commits. · View Herald Transcript
cor3ntin added inline comments.Jul 15 2022, 5:29 AM
clang/lib/Sema/SemaDecl.cpp
17863–17869
17916

That could be a separate (static) function

17937

You should probably be explicit about the type here.

17960–17961

Is that codepath ever taken?
I wonder if there is a way to do that that is not n^2. Maybe not.

Thanks for working on that!
The patch looks very good overall

BRevzin added inline comments.Jul 15 2022, 6:54 AM
clang/test/AST/conditionally-trivial-smfs.cpp
40

It's possible that I just don't understand what these tests actually mean but... where is the test for DefaultConstructorCheck<2> having a deleted default constructor, or DefaultConstructorCheck<3> having a non-defaulted one?

It'd also be worthwhile to have at least one test with constaints that subsume each other instead of being mutually exclusive.

royjacobson edited the summary of this revision. (Show Details)

Address Corentin's feedback.

royjacobson marked 4 inline comments as done and an inline comment as not done.Jul 15 2022, 10:10 AM
royjacobson added inline comments.
clang/lib/Sema/SemaDecl.cpp
17960–17961

It's not, I didn't mean to leave it there. Thanks for the catch :)

Generally finding a maximal set in a partial ordering is O(n^2) in the worst case which is when no two objects are comparable.

We could optimize this a bit for some cases: We can group the functions by their kind, and we can skip comparing functions if we already know that they are not maximal. But since the number of SMFs expected in a class is very (1-3) small, I didn't think those possible improvements are worth the extra complexity.

Another possible optimization is we could short-circuit this evaluation if we know that a type is not trivially [copyable], since that's the only outcome we really care about, but then the AST is left in a very awkward state.

clang/test/AST/conditionally-trivial-smfs.cpp
40

Should I check this? Except destructors, the other SMFs are found during overload resolution using the usual lookup that already takes into account delete/default/constraints etc.

This patch is about making sure that we set the triviality attributes correctly according to the eligible functions, so this is what I added tests for.

Most of this testing is done in the sema test, but I need this AST test as well to make sure we get the canPassInRegisters attribute correctly - we're doing some custom processing over the functions without the usual type attributes because there are some weird compatibility edge cases.

BRevzin added inline comments.Jul 15 2022, 10:14 AM
clang/test/AST/conditionally-trivial-smfs.cpp
40

One of the motivations for the paper is to ensure that like given:

template <class T>
struct optional {
    optional(optional const&) requires copyable<T> && trivially_copyableT> = default;
    optional(optional const&) requires copyable<T>;
};

optional<string> is copyable (but not trivially copyable), optional<int> is trivially copyable, and optional<unique_ptr<int>> isn't copyable. I'm not sure what in here checks if that works.

royjacobson marked an inline comment as done.Jul 15 2022, 10:40 AM
royjacobson added inline comments.
clang/test/AST/conditionally-trivial-smfs.cpp
40

That's more-or-less the check in constrained-special-member-functions.cpp:50, I think.

Didn't acknowledge it in my first response - but yeah, I need some more complicated subsumption test cases

royjacobson marked an inline comment as not done.

Add a test case with more subsumption

h-vetinari added inline comments.Jul 17 2022, 2:46 PM
clang/docs/ReleaseNotes.rst
494–498

Is that lack of compliance worth a note in cxx_status?

cor3ntin added inline comments.Jul 20 2022, 5:43 AM
clang/lib/Sema/SemaDecl.cpp
17870
17888
17960–17961

Thanks, I guess this is fine, I cannot really think of a better way either

@aaron.ballman @erichkeane I think this should be on your radar, it would be great to have it in 15. I did review it and I think it looks good but there are enough dragons here that i'd rather have more eye balls on it.

I believe the calculation of the constraints here means this isn't conforming with concepts. I believe this means we have to delay calculating the constraint differences until this property is actually queried, and cannot do it early like this has.

clang/lib/Sema/SemaDecl.cpp
17874

I don't think you'd want to compare with equality here, it ends up not desugaring/etc. I believe ASTContext::HasSameType should be what you want.

17876

Probably same here.

17899

This seems problematic, doesn't it? Checking this constraint will (once I figure out how to get deferred instantiation to work) cause instantiation, which can cause issues with incomplete types/CRTP/etc.

I think the result is that we cannot 'calculate' this until it is queried, else we will cause incorrect errors.

royjacobson added inline comments.Jul 20 2022, 8:59 AM
clang/lib/Sema/SemaDecl.cpp
17870

It's in an anonymous namespace. Is it a Clang convention to use static functions instead? I saw both used in Sema, I think

17874

Good catch! added a test for this.

17899

Making this queried on demand is a relatively big change to how we handle type triviality, so I want to be sure we actually need to do this to be conformant.

When I started working on this I checked what GCC does and it instantiates those constraints during class completion as well. For example this CRTP case: https://godbolt.org/z/EdoYf96zq. MSVC seem to do it as well.

So maybe it's OK to check the constraints of SMFs specifically?

17960–17961

Ok, did another look on this and it's because AnotherMethodIsMoreConstrained is the output of IsAtLeastAsConstrained, so that's why the extra check. Added it back :)

Fixed the tests, fixed the type comparison checks.

cor3ntin added inline comments.Jul 20 2022, 9:33 AM
clang/lib/Sema/SemaDecl.cpp
17899

I think this is done on completeness already in this patch, unless i misunderstood the code.
I don't think doing it on demand is a great direction, as this does not only affect type traits but also code gen, etc. It would create instanciations in unexpected places. wouldn't it.
Does the standard has wording suggesting it should be done later than on type completeness?

erichkeane added inline comments.Jul 20 2022, 9:37 AM
clang/lib/Sema/SemaDecl.cpp
17899

The problem, at least for the deferred concepts, is that it breaks in the CRTP as required to implement ranges. So something like this: https://godbolt.org/z/hPqrcqhx5 breaks.

I'm currently working to 'fix' that, so if this patch causes us to 'check' constraints early, it'll go back to breaking that example. The example that Roy showed seems to show that it is actually checking 'delayed' right now (that is, on demand) in GCC/MSVC. I don't know of the consequence/standardeeze that causes that though.

cor3ntin added inline comments.Jul 20 2022, 9:46 AM
clang/lib/Sema/SemaDecl.cpp
17899

Thanks,
Follow up stupid question then, do we care about the triviality of dependant types?
I think doing the check on complete non-dependant types might be a better solution than trying to do it lazily on triviality checks?

erichkeane added inline comments.Jul 20 2022, 9:48 AM
clang/lib/Sema/SemaDecl.cpp
17899

I would think it would be not a problem on non-dependent types. BUT concepts are only allowed on templated functions (note not only on function-templates!) anyway, so I don't think this would be a problem?

royjacobson added inline comments.Jul 20 2022, 11:31 AM
clang/lib/Sema/SemaDecl.cpp
17899

Erich, I'm a bit confused by your response. I think my example demonstrates that for default constructors (and other SMFs) GCC and MSVC instantiate the constraints on class completion and not on demand. This is what I would like to do as well, if we don't have a good reason not to. (For destructors, performing the checks is even explicit in the standard.)

Not doing this can introduce some REALLY bad edge cases. What does this do if we defer the triviality computation?

c++

template <class T>
struct Base<T> {
  Base() = default;
  Base() requires (!std::is_trivial_v<T>);
};

struct Child : Base<Child> { };

We defer the computation of the constraints on Base, and complete Child somehow, but if Child is complete then std::is_trivial_v<Child> should be well-formed, right? But we get a logical contradiction instead.

erichkeane added inline comments.Jul 20 2022, 11:55 AM
clang/lib/Sema/SemaDecl.cpp
17899

Erich, I'm a bit confused by your response

It is entirely possible we're talking past eachother, or misunderstanding eachothers examples. I'm totally open to that being part of this issue.

In that example, if we calculate the triviality at 'Base Class completion', Child is not yet complete, right? So the is_trivial_v would be UB. If you instead require sizeof(T), we typically give a diagnostic.

In this case, you'd at MINIMUM have to wait to calculate the requires-clause until after Child is complete. And it isn't clear to me that we're delaying it until then.

That is what I intend to show with: https://godbolt.org/z/1YjsdY73P

As far as doing it 'on demand', I definitely see your circular argument here, but I think the is_trivial_v test is UB if we calculate it at `Base' completion.

royjacobson added inline comments.Jul 20 2022, 12:05 PM
clang/lib/Sema/SemaDecl.cpp
17899

I'm arguing for checking the constraints at the completion of Base, and for making is_trivial_v/sizeof ill formed in this context.

Your GCC example is a bit misleading, I think. They check the sizeof(T) > 0 constraint at the completion of Base but they just swallow the error and declare the constraint unsatisfied or something. They should've probably issued a diagnostic or something. But if you look at which constructor they choose, they choose the unconstrained one: https://godbolt.org/z/rKj4q5Yx9

erichkeane added inline comments.Jul 20 2022, 12:14 PM
clang/lib/Sema/SemaDecl.cpp
17899

Hmm. I think based on that example, I agree with you. We can do the instantiation and mark it unviable at the end of Base. I think I'm getting confused by Clang's lack of deferred instantiation in this part.

Thanks for talking this through with me!

I AM concerned about how this will work with my deferred instantiations, so a test that validates that (and has a FIXME that shows it is broken right now) would be appreciated.

cor3ntin added inline comments.Jul 20 2022, 12:22 PM
clang/lib/Sema/SemaDecl.cpp
17899

I'm arguing for checking the constraints at the completion of Base, and for making is_trivial_v/sizeof ill formed in this context.

Your GCC example is a bit misleading, I think. They check the sizeof(T) > 0 constraint at the completion of Base but they just swallow the error and declare the constraint unsatisfied or something. They should've probably issued a diagnostic or something. But if you look at which constructor they choose, they choose the unconstrained one: https://godbolt.org/z/rKj4q5Yx9

I don't think gcc is wrong per http://eel.is/c++draft/temp.constr.constr#temp.constr.op-5.sentence-3
The constrained overload is sfinea away silently, no diagnostic should be emitted.
It's no different from https://godbolt.org/z/ThMPrhs93 , for example

Add a test for the CRTP constraints timings

royjacobson added inline comments.Jul 20 2022, 2:21 PM
clang/lib/Sema/SemaDecl.cpp
17899

I added the test, could you take a look and tell me if that's what you meant or if you want some changes? @erichkeane
I hope it isn't disruptive to the deferred patch itself, it's a bit concerning indeed but I'm moderately optimistic :)

@cor3ntin thanks for the explanation!

cor3ntin added inline comments.Jul 21 2022, 11:44 PM
clang/test/SemaCXX/constrained-special-member-functions.cpp
104

Have you rebased since D127593 landed?

royjacobson added inline comments.Jul 22 2022, 12:46 AM
clang/test/SemaCXX/constrained-special-member-functions.cpp
104

Yes. It's related but it's different DRs. And unfortunately they seem much more ABI breaking to fix - GCC don't implement them, for example

shafik added a subscriber: shafik.Jul 22 2022, 10:41 PM
shafik added inline comments.
clang/lib/AST/DeclCXX.cpp
899

I think we should spell out Special Member Function instead of using SMF

clang/lib/Sema/SemaDecl.cpp
17875

What happens if we have further parameters with default arguments? Unless I am missing something they are still special member functions but the proposal does not seem to cover them.

royjacobson added inline comments.Jul 23 2022, 2:25 PM
clang/lib/Sema/SemaDecl.cpp
17875

That's an excellent question.

I'm not sure what to do about default arguments. In a context where the additional parameters matter, you're not using them as constructors anymore, right? So why would this affect the type traits?
On the one hand [over.match.best] is against this idea of comparing constraints when the parameters differ. So also every context where this actually matters the overload resolution would probably be ambiguous anyway?

@BRevzin, what do you think? Is the wording intentional to include copy/move constructors with default arguments as well?

I checked with GCC and they seem to handle default arguments separately: https://godbolt.org/z/1ch3M7MjP

royjacobson marked an inline comment as done.

Rebase on main and slightly update docs.

@erichkeane @cor3ntin

If you have time for review right now, I think it's reasonably ready and I would still like to merge it this week so it can land in Clang15.

Rebase on main and re-target LLVM16 in cxx_status

royjacobson added inline comments.Aug 17 2022, 1:29 PM
clang/docs/ReleaseNotes.rst
494–498

I'm not very opinionated about this, but I tend to not significant enough. I mean, 7 years later and only MSVC have even bothered to implement them.

clang/lib/Sema/SemaDecl.cpp
17875

FWIW, I filed https://github.com/cplusplus/CWG/issues/110

I'm not on the reflector myself, so I don't know if there's been a follow-up there.

I would like to see this land. This also fixes https://github.com/llvm/llvm-project/issues/57046.

Are there any pending concerns of the reviewers which we need to address ? Happy to help in any way to move this patch forward.

CC: @erichkeane @cor3ntin

This looks good to me.

I *think* landing things without doing silent sfinae for incomplete types is okay-ish, but I'd like to here from other folks.

@royjacobson Have you looked into that? Do you know how involved would it be?

Either way, we should make sure that if that bit is dealt with separately, we have an issue or something to track it, maybe a note in cxx_status.

clang/docs/ReleaseNotes.rst
494–498

We might has well, I think it's a good way to not lose track of it.

clang/lib/Sema/SemaDecl.cpp
17875

Yes, i think it's reasonable to not concerns ourselves with default parameters until wg21 decides otherwise

clang/test/SemaCXX/constrained-special-member-functions.cpp
201

Have you been able to investigate that?

Add disclaimer to cxx_status about the standing DRs.

royjacobson added inline comments.Aug 18 2022, 11:00 AM
clang/docs/ReleaseNotes.rst
494–498

Added as a footnote.

clang/test/SemaCXX/constrained-special-member-functions.cpp
201

No, I haven't looked at this - we already have a bunch of those all over the code, see https://github.com/llvm/llvm-project/issues/48857

So I don't think this is about this patch specifically - I just call Sema::CheckFunctionConstraints and inherit its current behavior.

@aaron.ballman I think this looks good, you want to double check?

aaron.ballman added inline comments.Aug 19 2022, 12:16 PM
clang/lib/AST/DeclCXX.cpp
1443–1447
clang/lib/Frontend/InitPreprocessor.cpp
677

Does any of the not-yet-implemented bits (including from the DRs) impact the ability to use conditionally trivial special member functions? If so, we might want to be careful about aggressively bumping this value. (It's more palatable for us to come back and bump the value later than it is for us to claim we implement something fully when we know we don't -- the goal of the feature test macros is so that users don't have to resort to compiler version checks, which is what users have to use when they fall into that "not fully implemented" space.)

clang/lib/Sema/SemaDecl.cpp
17870

Yeah, we prefer putting types into an anonymous namespace but making functions static: https://llvm.org/docs/CodingStandards.html#anonymous-namespaces

17890

You may want to run the patch through clang-format before you land, just to be sure (https://clang.llvm.org/docs/ClangFormat.html#script-for-patch-reformatting).

clang/test/AST/conditionally-trivial-smfs.cpp
344

Please add a newline to the end of the file.

clang/www/cxx_status.html
930

FWIW, the way we've started handling this in recent history is to use "partial" and a details tag instead of a footnote, as in: https://github.com/llvm/llvm-project/blob/main/clang/www/cxx_status.html#L915.

Address Aaron's comments, add the consteval test.

royjacobson marked 11 inline comments as done.Aug 21 2022, 2:12 PM
royjacobson added inline comments.
clang/lib/Frontend/InitPreprocessor.cpp
677

I don't think they're very significant, and the benefits of enabling it seem large enough for me - for example, std::expected works with libstdc++ and passes their unit tests but is gated by this macro.

We still have a non-trivial amount of concept bugs to go over, but I support enabling this.

clang/www/cxx_status.html
930

It felt a bit too long of an explanation to put in the tiny table box, but I don't feel very strongly about it either way.

cor3ntin added inline comments.Aug 21 2022, 11:44 PM
clang/lib/Frontend/InitPreprocessor.cpp
677

I think it's better to be conservative, It's the lesser of two not great options.
I'm hoping we can get to fix the issues in the clang 16 cycle , but in the meantime we should not claim conformance if we are not, in fact, conforming.

clang/www/cxx_status.html
930

I agree with Aaron.
I think it's better to be consistent, the column resize when the details are expanded.

aaron.ballman added inline comments.Aug 22 2022, 6:57 AM
clang/lib/Frontend/InitPreprocessor.cpp
677

We still have a non-trivial amount of concept bugs to go over, but I support enabling this.

I think we should specify the updated macro value only after we think concepts have no known major issues with them. (Unknown issues happen and there's not much we can do about them, this is more that we shouldn't specify that we conform to a particular feature test macro when we knowingly still don't have it right yet.)

erichkeane added inline comments.Aug 22 2022, 7:01 AM
clang/lib/Frontend/InitPreprocessor.cpp
677

Yeah, I don't think we can set this until we can at least compile a basic libstdc++ ranges use, which the deferred instantiation still holds up. That would be my 'bare minimum' test for whether we can set that.

royjacobson added inline comments.Aug 22 2022, 10:28 AM
clang/lib/Frontend/InitPreprocessor.cpp
677

But we're already defining the __cpp_concepts macro, even with the current known bugs. The version bump, introduced by https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p2493r0.html is specifically about the conditionally trivial SMFs paper.

We can decide that we want the version bump to mean 'no more known concept bugs in clang' instead. But that would extend the meaning of the macro and would be confusing to users who want to rely on the documented, intended meaning of the version.

Also I think telling library writers 'this feature works now, but we didn't set its feature test macro' will make them use more compiler version checks, not less.

erichkeane added inline comments.Aug 22 2022, 10:35 AM
clang/lib/Frontend/InitPreprocessor.cpp
677

The feature test macros aren't supposed to mean "I support the feature from the paper that updated it". They are intended to mean "I support the feature from the paper that updated it AND everything before it".

I don't believe we need to be bug-free, but something as extreme as "we can't compile a large number of uses of concepts because we don't implement a central design consideration" (which, btw, was clarified in Core after the 2019 date IIRC) means we shouldn't be updating this.

Remove the __cpp_concepts version bump.

royjacobson added inline comments.Aug 22 2022, 10:48 AM
clang/lib/Frontend/InitPreprocessor.cpp
677

It seems like I'm in the minority here, so I removed the version change and added a comment with a reference to this discussion to the code.

aaron.ballman added inline comments.Aug 22 2022, 10:50 AM
clang/lib/Frontend/InitPreprocessor.cpp
677

But we're already defining the __cpp_concepts macro, even with the current known bugs.

FWIW, I consider that a bug. We should have never defined this macro in the first place -- we're not there yet on the base feature.

I don't believe we need to be bug-free, but something as extreme as "we can't compile a large number of uses of concepts because we don't implement a central design consideration" (which, btw, was clarified in Core after the 2019 date IIRC) means we shouldn't be updating this.

+1

Clarify ambiguous doc.

cor3ntin accepted this revision.Aug 22 2022, 11:38 AM

I would wait for @aaron.ballman or @erichkeane to sign off on it, but this looks good to me.
Thanks again for working on this!

This revision is now accepted and ready to land.Aug 22 2022, 11:38 AM
aaron.ballman accepted this revision.Aug 23 2022, 7:12 AM

LGTM, thank you for this!!

Rebase on main.

This revision was landed with ongoing or failed builds.Aug 23 2022, 11:49 AM
This revision was automatically updated to reflect the committed changes.

Hi,

We are seeing build failures on Fuchsia build bots after this change was landed. and we suspect this patch causes clang to miscompile certain code. I filed bug https://github.com/llvm/llvm-project/issues/57351 and included a reproducer. Could you take a look? If it is confirmed to be a bug and take long time to fix, could you revert this change please?

royjacobson reopened this revision.Aug 25 2022, 12:15 PM
This revision is now accepted and ready to land.Aug 25 2022, 12:15 PM

Default constructors can be template functions. Update patch to handle them correctly, and add
asserts to LookupSpecialMember that can catch similar problems.

I added handling of FunctionTemplateDecls in SemaDecl because they can be default constructors. This caused errors with std::pair for example - it has two enable_ifs constructors to control explicit/implicit construction.

I will try committing again if the pre-commit CI finishes green. Running nm on the provided reproducer from FuchsiaOS resulted in the names list.

cor3ntin added inline comments.Aug 25 2022, 12:26 PM
clang/test/AST/conditionally-trivial-smfs-1.cpp
1 ↗(On Diff #455686)

can you rename that file back to clang/test/AST/conditionally-trivial-smfs.cpp ? Thanks!

We call LookupSpecialMember before we compute eligibility, so the assert fails. Remove it.

Formatting.

This revision was landed with ongoing or failed builds.Aug 25 2022, 2:53 PM
This revision was automatically updated to reflect the committed changes.