Page MenuHomePhabricator

[CodeGen][ObjC] Fix an assertion failure caused by copy elision

Authored by ahatanak on Nov 2 2017, 12:45 PM.



The assertion failure occurs when a setter is called using the dot syntax to set a property of a class type that doesn't have trivial copy/move constructors or assignment operators. It happens only when clang is compiling for c++1z and copy elision allows avoiding copying a temporary to the argument passed to the setter method in Sema.

For example:

struct S2 {
  id f1;

@interface C
@property S2 f;
@implementation C

void test(C *c) {
  c.f = S2();

When IRGen visits the ObjCMessageExpr for the call to the setter, it tries to emit a copy of an S2 object that has been constructed (this happens in AggExprEmitter::VisitOpaqueValueExpr) and the assertion in CodeGenFunction::EmitAggregateCopy fails because S2 doesn't have the trivial special functions needed to do the copy.

This is the AST of the ObjCMessageExpr:

`-ObjCMessageExpr 0x7fe03c06b730 <col:5> 'void' selector=setF:
|         |-OpaqueValueExpr 0x7fe03c06b698 <col:3> 'C *'
|         | `-ImplicitCastExpr 0x7fe03c06b5e0 <col:3> 'C *' <LValueToRValue>
|         |   `-DeclRefExpr 0x7fe03c06b5b8 <col:3> 'C *__strong' lvalue ParmVar 0x7fe03c06b408 'c' 'C *__strong'
|         `-OpaqueValueExpr 0x7fe03c06b6e8 <col:9, col:12> 'struct S2'
|           `-CXXBindTemporaryExpr 0x7fe03c06b678 <col:9, col:12> 'struct S2' (CXXTemporary 0x7fe03c06b670)
|             `-CXXTemporaryObjectExpr 0x7fe03c06b638 <col:9, col:12> 'struct S2' 'void (void)'

To avoid the crash, I modified CodeGenFunction::EmitAnyExprToTemp to return the value for the OpaqueValueExpr (which must have already been evaluated when it's visited, I think) so that it doesn't have to call EmitAggregateCopy to make a copy.

I'm actually unsure whether we should fix Sema and change the AST representation or fix IRGen as I did in this patch. I'm open to suggestions if anyone has a better idea to fix the crash.

Diff Detail


Event Timeline

ahatanak created this revision.Nov 2 2017, 12:45 PM
smeenai added a subscriber: smeenai.Nov 2 2017, 1:32 PM

This is tracked by rdar://problem/34363596.

Hmm. The OVE-ing of the RHS of a property assignment is just there to make the original source structure clearer. Maybe the right solution here is to set a flag in the OVE that says that it's a unique semantic reference to its source expression, and then change IRGen to just recurse through OVEs with that flag set (and not pre-bind it).

You should make sure that we don't get this assertion with mandatory copy-elision and ?:, which does actually use its LHS multiple times semantically (and which cannot safely perform mandatory copy-elision on it).

Any updates here? We were hitting a similar error internally, which this patch fixed.

I'm planning to work on a new patch this week.

ahatanak updated this revision to Diff 136895.Mar 2 2018, 9:31 PM

Add a flag to OpaqeuValueExpr that indicates whether it is a unique reference to its subexpression. If the flag is set, IRGen will avoid binding the OVE and copying the result to the destination and instead just visit the OVE's sub-expression.

The flag is set only when the PseudoObjectExpr is an assignment and the RHS is an rvalue of a C++ class type, which is sufficient to fix the assertion failure this patch is trying to fix.

rjmccall added inline comments.Mar 3 2018, 12:02 AM
875 ↗(On Diff #136895)

Humor me and pack this in the bitfields in Stmt, please. :)

932 ↗(On Diff #136895)

Can we assert that there's a source expression?

432 ↗(On Diff #136895)

I think you can unconditionally set this here, actually. You just need to teach the other two exhaustive emitters in IRGen (scalar and complex) to look through unique OVEs. Plenty of other things in IRGen could benefit from being able to peephole through unique OVEs.

Also, you can set it on the OVE for the base expression if this is a simple assignment or load.

Oh, and you need to serialize this bit.

ahatanak set the repository for this revision to rC Clang.Mar 6 2018, 12:57 PM
ahatanak updated this revision to Diff 137250.Mar 6 2018, 1:08 PM
ahatanak marked 3 inline comments as done.

Address review comments.

ahatanak added inline comments.Mar 6 2018, 1:12 PM
432 ↗(On Diff #136895)

All the builders created in checkPseudoObjectRValue and checkPseudoObjectAssignment (only when the assignment is simple) set the OVE's IsUnique bit to true.

rjmccall added inline comments.Mar 6 2018, 1:25 PM
875 ↗(On Diff #136895)


4815 ↗(On Diff #137250)

Oh! So it's an interesting point that the RHS might be used as the result expression, which means its use might not really be unique anymore. It happens to work in some sense for non-trivial C++ class types (when they're passed indirectly, as under the Itanium ABI) because the temporary outlives the call; on the other hand, the call is allowed to mutate its argument, and it's not clear that it's okay to have those mutations be reflected in code that's using the result of the assignment. Similarly, managed types (like non-trivial C structs, or ARC pointers) might be consumed by the call; if we're going to pass them, perhaps we need to copy the value before doing so.

What do you think?

I think the technical implication is that what you're doing here shouldn't be necessary; the OVE arguably should not be unique if its value is used in multiple places, and that includes being used as a result.

Sorry, this patch is not ready yet. There are several regression tests that are failing because of the assert in setIsUnique.

ahatanak updated this revision to Diff 137418.Mar 7 2018, 9:43 AM
ahatanak marked an inline comment as done.

Don't set the IsUnique bit of an OpaqueValueExpr that is used as the result expression.

4815 ↗(On Diff #137250)

I made changes so that OVEs used as the result expression are not marked as unique. I wasn't sure whether treating such OVEs as unique would actually cause problems, but it's probably better to be on the safe side.

I also add a call to setIsUnique(false) in OpaqueValueExpr's constructor, which fixed the failing tests I was seeing.

rjmccall accepted this revision.Mar 19 2018, 1:19 PM

Alright, LGTM.

This revision is now accepted and ready to land.Mar 19 2018, 1:19 PM
ahatanak set the repository for this revision to rC Clang.Mar 19 2018, 6:49 PM
This revision was automatically updated to reflect the committed changes.