This is an archive of the discontinued LLVM Phabricator instance.

[analyzer] Implement copy elision.
ClosedPublic

Authored by NoQ on Jun 1 2018, 7:29 PM.

Details

Summary

This patch builds on top of D47616 to elide pre-C++17 elidable constructors during analysis. They are present in the AST and in the CFG but they'll be skipped during analysis, as planned in http://lists.llvm.org/pipermail/cfe-dev/2018-March/057318.html

It also elides destructors that correspond to elided constructors, and for that i had to add another program state trait, because the universal state trait i added previously had no room for this flag.

The main idea is straightforward. Construction context for the elidable constructor can already be retrieved from construction context of the inner "temporary" constructor. Therefore upon constructing the "temporary", we can use the target region of the elided constructor (which isn't necessarily a temporary region). Then we flag the elidable constructor's CXXConstructExpr in the program state and track what target region was already computed for it. Upon encountering the elidable construct-expression during analysis, we don't perform construction, but only assign the value to the expression.

Temporary materialization becomes funny because temporary can now be materialized into a non-temporary region. But the algorithm has not changed much.

Massive changes in the tests indicate that copy elision is in fact working as intended. I also extended tests to track whether there were conservatively evaluated calls (those glob tests in cxx17-mandatory-elision.cpp). They indicate the problem with C++17 CFG that still contains destructors for C++17-mandatory-ly elided constructors. I could easily mark them as "elided", but it'd be better to never add them to the CFG in the first place.

I'm not sure we need an -analyzer-config flag for disabling this feature, though i wouldn't mind adding one.

Diff Detail

Repository
rC Clang

Event Timeline

NoQ created this revision.Jun 1 2018, 7:29 PM
MTC added a subscriber: MTC.Jun 4 2018, 12:35 AM

So having an analyzer-config option would be useful for those codebases that are expected to be compiled with less advanced compilers that do not elide copies or not doing it aggressively enough. Maybe it is worth to ask on the mailing list of the community wants to have an option for this? I am not sure though how often the end users end up fine tuning the analyzer. It might be nice to have a guide how to do that (and in what circumstances does each of the config values make sense).

So having an analyzer-config option would be useful for those codebases that are expected to be compiled with less advanced compilers that do not elide copies or not doing it aggressively enough.

I'm not sure it makes sense. From my understanding, that's just how c++17 is defined, and that's unrelated to what compiler implementation is used.

I am not sure though how often the end users end up fine tuning the analyzer.

My guess will be "never" (apart from turning on checkers)

So having an analyzer-config option would be useful for those codebases that are expected to be compiled with less advanced compilers that do not elide copies or not doing it aggressively enough.

I'm not sure it makes sense. From my understanding, that's just how c++17 is defined, and that's unrelated to what compiler implementation is used.

Yeah, this is guaranteed for C++17. My only concern are the projects that need to support platforms where no comforming C++17 compiler is available (I think this might be common in the embedded world). I do not have any numbers though how frequent those projects are. That is the reason why I was wondering if it is worth asking. It is also possible that the authors of those project do not care about the analyzer.

But since this config would most probably be removed at some point, I am fine with not adding it.

george.karpenkov accepted this revision.Jun 5 2018, 6:41 PM
This revision is now accepted and ready to land.Jun 5 2018, 6:41 PM
NoQ added a comment.Jun 5 2018, 7:58 PM

From my understanding, that's just how c++17 is defined, and that's unrelated to what compiler implementation is used.

Yeah, but this patch is specifically about supporting the pre-C++17 AST, where copy elision was already a well-defined concept, but it wasn't mandatory.

In C++17 copy-constructors are simply omitted from the AST (but CXXBindTemporaryExpr might still be accidentally present, and additionally more different construction syntax patterns may appear, eg. we may elide all the way through ?: operators). Support for C++17 was added in the previous patches.

This patch addresses pre-C++17 AST that does include copy-constructors, but they are marked as "(elidable)". They are here because whether to elide them or not was implementation-defined, and even if the elided constructor had arbitrary side effects the compiler was still allowed to choose not to elide it. In this case we decided not to skip in in CFG, but only skip it in the analyzer, because that'd allow the Environment to work with the AST in a straightforward and intuitive manner as it always did. Hence the patch.


So there are two reasons why to add an option:

  • To be able to disable the new feature as a workaround to a bug in this feature.
  • To be able to actually maintain two different behaviors forever as an opt-in feature.

I'm totally fine with the former.

I'm not sure if it'll be easy and worthwile to try doing the latter. We might as well try and see if it will be easy to maintain. I hope it'll mostly work because most syntax patterns with elidable constructors will also appear anyway with non-elidable constructors, albeit much more rarely.

I guess i'll add the flag and see how it goes.

Just for the record, there is a common example where relying on copy elision might bite and google do not recommend relying on it for correctness: https://abseil.io/tips/120

The main purpose of sharing is to add some more context to the discussion, I do not consider this to be an argument, because I can still see that this practice as opinionated.

NoQ added a comment.Jun 11 2018, 1:29 PM

Just for the record, there is a common example where relying on copy elision might bite and google do not recommend relying on it for correctness: https://abseil.io/tips/120

The main purpose of sharing is to add some more context to the discussion, I do not consider this to be an argument, because I can still see that this practice as opinionated.

Ah, NRVO, thanks for pointing it out! We can't do that yet, this patch only enables simple RVO. Also it seems that NRVO isn't mandatory even in C++17.

In any case, it's clear that relying on any sort of copy elision should be treated as a bug, but not necessarily a high-priority bug, and i'm not seeing any better way of detecting this bug other than suggesting the user to run a separate analysis with copy elision disabled and see if any of our usual kinds of bugs are found.

P.S. It seems that one of my currently-on-review patches has introduced a performance regression, i'm investigating it.

NoQ updated this revision to Diff 151279.Jun 13 2018, 5:50 PM

I added an option to disable copy elision on CFG side in D47616. The analyzer makes use of it automagically: elided constructors are replaced with temporary constructors in the CFG and the old behavior is restored.

Tests now test both behaviors and demonstrate that C++17 mandatory copy elision works exactly like pre-C++17 optional copy elision, despite underlying AST being completely different. Well, most of the time: there's still a bug that causes us to think that we need to call a destructor on the elided temporary. Thankfully, such destructor would be evaluated conservatively.

NoQ added a comment.Jun 13 2018, 5:54 PM

P.S. It seems that one of my currently-on-review patches has introduced a performance regression, i'm investigating it.

Never mind, that was an old version of the patch, i.e. i accidentally fixed that regression before uploading the patch to phabricator.

This revision was automatically updated to reflect the committed changes.