Page MenuHomePhabricator

GCC ABI incompatibility when passing object with trivial copy ctor, trivial dtor, and non-trivial move ctor

Authored by rsmith on Jul 6 2017, 7:24 AM.



Fixes PR19668 and PR23034.

Diff Detail


Event Timeline

v.g.vassilev created this revision.Jul 6 2017, 7:24 AM
rjmccall edited edge metadata.Jul 6 2017, 4:58 PM

If this is a common algorithm across all ABIs, can we just put Sema / the AST in charge of computing it? It's not a cheap thing to recompute retroactively, especially for an imported type, and it seems like it's easily derived from the things that go into DerivedData.

rnk edited edge metadata.Jul 7 2017, 10:16 AM

I discussed this with @rsmith and we think the correct fix is to update the DefinitionData bits computed when adding special members. I haven't reviewed this patch in detail, but we came to the conclusion that Clang's optimization to avoid implicit special member declarations in most situations makes it impossible to get the correct answer in all situations here. The explicit loop over RD->ctors() is a bug in itself, because important ones may not be there.

I'm never able to remember the details, but we believe that's the "right" fix. This would change the values reported by some of the older pre-c++11 GCC type traits (the __has_* ones, I think), but modern versions of libstdc++ no longer use them.

We're hesitant to accept a workaround in the meantime because it means we'll have to fix the bug again later and create another ABI break with ourselves. However, we shouldn't let the perfect be the enemy of the good. In practice, I suspect we are not very good about maintaining ABI stability in corner cases this deep.

Compute only once if there is a non-deleted move or copy constructor and store them in the DefinitionData of the CXXRecordDecl.

Remove accidentally added change in SemaDecl.cpp

Thank you, I like this approach much better, and the IRGen changes seem fine to me. I'd like to defer to someone else (probably Richard) to review whether the changes to completeDefinition() are correct; I'm not up-to-date with how we handle lazy declarations.

rsmith added inline comments.Jul 24 2017, 2:09 PM

This is wrong. "Has a non-deleted copy or move constructor" is not the same thing as "does not have a deleted copy or move constructor". And you will also need to check for the case where the class notionally has such a constructor, but where we have lazily not yet declared it -- in that case, you may need to do arbitrary work (including template instantiation etc) to figure out whether the constructor should be deleted.

Please move this check into Sema and pass a bool to completeDefinition with the result of the check. That way you will have the tools available to compute the correct value in the case where you need to trigger the declaration of a constructor to determine the right answer.

Move the checks in Sema.

rsmith added inline comments.Jul 27 2017, 11:40 AM

Perhaps it would make sense to put this whole "does the language permit passing in registers?" calculation in Sema and store that in the DefinitionData rather than storing the "HasNonDeletedCopyOrMoveConstructor" flag.

14816–14817 ↗(On Diff #108505)

This seems like a good motivation for the flag to be a "can pass in registers" flag: that makes it much more obvious that it's meaningless on a dependent class, since such a class will never be passed / returned at all.

14821–14822 ↗(On Diff #108505)

You don't need to do this: triggering the declaration will delete the member if relevant.

However, we should refactor ShouldDeleteSpecialMember so that it can be called without actually having built a special member declaration: this patch removes all laziness in declaring copy or move constructors.

14825 ↗(On Diff #108505)

This needs to be guarded by a getLangOpts().CPlusPlus11 check.

14831–14839 ↗(On Diff #108505)

We should do this check before we call ShouldDeleteSpecialMember to get the benefit of the early exit. If we switch to tracking the ABI flag, we can also bail out early if we see a non-trivial copy ctor / move ctor / dtor.

14841–14852 ↗(On Diff #108505)

Do we need this? The above code will have already declared as deleted the relevant operator, so this seems like it can never trigger.

15147–15148 ↗(On Diff #108505)

This call also needs to pass in the relevant flag.

v.g.vassilev marked 6 inline comments as done.

Address some of the comments.

14841–14852 ↗(On Diff #108505)

You are right, we do not need it.

Put back accidentally removed test case.

Move back the triviality checks in CGCXXABI. Explain why.

rjmccall added inline comments.Jul 27 2017, 6:01 PM

Does canPassInRegisters() not subsume all of these earlier checks?

v.g.vassilev added inline comments.Aug 2 2017, 2:09 PM

No, if I remove them here I get a lot of test failures. I cannot move them (yet?) in Sema, because I need to call Sema::CheckCompletedCXXClass in Sema::ActOnFields to compute the triviality of the decl. Only then it would be safe move these checks in CanPassInRegisters.

rjmccall added inline comments.Aug 2 2017, 10:36 PM

I feel it's pretty unfortunate to name a property "canPassInRegisters" when it actually means something quite different. People will expect this to mean what it says.

EricWF added a subscriber: EricWF.Aug 3 2017, 12:55 AM
v.g.vassilev marked an inline comment as done.

We set the record's property denoting whether we can pass the decl by registers as a last step of Sema::CheckCompletedCXXClass. We cannot do it any earlier than that because we have not computed the triviality information.

This patch still eagerly defines too many implicit members. As discussed with @rsmith, he will take care of the non-trivial refactoring of Sema::ShouldDeleteSpecialMember to fix this regression.

This patch passes all tests in codegen but still fails a few which are sensitive to the amount of implicit members being created:

Failing Tests (13):
    Clang :: CodeCompletion/ordinary-name-cxx11.cpp
    Clang :: Misc/ast-dump-color.cpp
    Clang :: Misc/ast-dump-decl.cpp
    Clang :: Misc/ast-dump-invalid.cpp
    Clang-Unit :: AST/./ASTTests/CXXMethodDecl.CXXMethodDeclWithNoExceptSpecification
    Clang-Unit :: ASTMatchers/./ASTMatchersTests/ConstructorDeclaration.IsImplicit
    Clang-Unit :: ASTMatchers/./ASTMatchersTests/ConstructorDeclaration.Kinds
    Clang-Unit :: ASTMatchers/./ASTMatchersTests/DeclarationMatcher.MatchNot
    Clang-Unit :: ASTMatchers/./ASTMatchersTests/DeclarationMatcher.hasMethod
    Clang-Unit :: ASTMatchers/./ASTMatchersTests/DeclaratorDecl.MatchesDeclaratorDecls
    Clang-Unit :: ASTMatchers/./ASTMatchersTests/Matcher.References
    Clang-Unit :: ASTMatchers/./ASTMatchersTests/TypeMatcher.MatchesClassType
    Clang-Unit :: ASTMatchers/Dynamic/./DynamicASTMatchersTests/RegistryTest.VariadicOp

  Expected Passes    : 10786
  Expected Failures  : 17
  Unsupported Tests  : 195
  Unexpected Failures: 13
rjmccall added inline comments.Aug 6 2017, 9:48 PM

This should probably be called something like "computeCanPassInRegisters" to discourage other code from calling it directly.

It should also just be a static function in this file unless it needs to be a member for some access-control reason.

rsmith edited edge metadata.Aug 7 2017, 5:11 PM

As requested by Vassil, I'm going to upload another version of this that avoids declaring implicit special members so frequently.

rsmith commandeered this revision.Aug 7 2017, 5:12 PM
rsmith edited reviewers, added: v.g.vassilev; removed: rsmith.
rsmith updated this revision to Diff 110115.Aug 7 2017, 6:30 PM
rsmith edited the summary of this revision. (Show Details)

Remove added calls to DeclareImplicit* and ShouldDeleteSpecialMember. In their place, figure out whether an implicit special member would be deleted or not by querying the AST.

This requires teaching CXXRecordDecl to track whether an implicit copy constructor for a class would be deleted, in the same way we do for the move constructor and move assignment operator; as with those other two cases, we fall back to an "ask Sema" state if the computation is not simple, and in that case Sema eagerly declares the special member in question to compute the answer. As a result, this can cause us to declare some copy constructors that we didn't declare previously, but in the important common cases we will still declare them lazily.

I also removed some incorrect assumptions from the Win64 ABI code; this changed the behavior of one testcase from uncopyable-args.cpp (implicitly_deleted_copy_ctor::A is now passed indirect).

rnk added a comment.Aug 7 2017, 6:49 PM

I also removed some incorrect assumptions from the Win64 ABI code; this changed the behavior of one testcase from uncopyable-args.cpp (implicitly_deleted_copy_ctor::A is now passed indirect).

That's probably not correct, let me take a look... I remember breaking the rules for small types here.

rsmith added a comment.EditedAug 7 2017, 10:25 PM
In D35056#834705, @rnk wrote:

I also removed some incorrect assumptions from the Win64 ABI code; this changed the behavior of one testcase from uncopyable-args.cpp (implicitly_deleted_copy_ctor::A is now passed indirect).

That's probably not correct, let me take a look... I remember breaking the rules for small types here.

I forgot to say: the new behavior matches MSVC -- at least for the testcase whose result changed (which is a new testcase I added here). The immediate bug was that we didn't consider the possibility that a user-declared copy assignment operator could cause the implicit copy constructor to be deleted, but there may have been other bugs also caused by this part of CodeGen attempting to work out deletedness for itself rather than asking.

v.g.vassilev edited edge metadata.Aug 8 2017, 7:01 AM

I do not feel qualified enough to review this patch but I added few minor comments.


Is there a reason for not keeping the default (for the file) 1 empty line between methods? Looks like if we add one new line before and after hasSimpleMoveAssignment is will be all consistent.


It would be very useful if we somehow assert if this function is called before the class triviality is computed?

And thanks for working on this!!

rnk accepted this revision.Aug 8 2017, 10:15 AM
In D35056#834705, @rnk wrote:

I also removed some incorrect assumptions from the Win64 ABI code; this changed the behavior of one testcase from uncopyable-args.cpp (implicitly_deleted_copy_ctor::A is now passed indirect).

That's probably not correct, let me take a look... I remember breaking the rules for small types here.

Nevermind, everything looks good there. Thanks for untangling the mess. I only have comments on comments.


Isn't this "... at least one *trivial*, non-deleted copy or move constructor..."?


This doesn't seem to match what we've computing, and it doesn't seem quite right. MSVC will pass a class with deleted, trivial copy ctors indirectly. Would it be correct to rephrase like this?
"If RD has at least one trivial, non-deleted copy constructor, it is passed directly. Otherwise, it is passed indirectly."


Oh dear. :(

This revision is now accepted and ready to land.Aug 8 2017, 10:15 AM
rsmith added inline comments.Aug 8 2017, 11:06 AM

Changed to:

/// \brief True if this class can be passed in a non-address-preserving
/// fashion (such as in registers) according to the C++ language rules.
/// This does not imply anything about how the ABI in use will actually
/// pass an object of this class.

Done. (I was following the local style, but you're right that we don't do this elsewhere in the class outside the hasSimple functions, excluding groups of methods that are much more closely tied together such as *_begin/*_end.)


You're right. I think "it is passed directly" is overspecifying, though, so how about:

// If a class has at least one non-deleted, trivial copy constructor, it
// is passed according to the C ABI. Otherwise, it is passed indirectly.

Many of the functions we unconditionally call below will assert if the class does not have a complete definition (eg, needsImplicitCopyConstructor).


Can you check that MSVC 2013 is compatible with the code we produce here? (I've checked 2015 passes this indirectly on Compiler Explorer.)

rnk added inline comments.Aug 8 2017, 11:12 AM

Yes, 2013 passes this object directly as we do here.

rsmith closed this revision.Aug 8 2017, 12:13 PM

Committed as r310401.