[cfe-dev] Smart Pointer Lifetime Optimizations

Richard Smith via cfe-dev cfe-dev at lists.llvm.org
Thu Jun 11 09:57:15 PDT 2020


On Wed, 10 Jun 2020 at 19:52, John McCall via cfe-dev <
cfe-dev at lists.llvm.org> wrote:

> On 10 Jun 2020, at 18:30, Richard Smith wrote:
>
> On Wed, 10 Jun 2020 at 13:06, John McCall via cfe-dev <
> cfe-dev at lists.llvm.org> wrote:
>
> On 10 Jun 2020, at 15:55, Richard Smith wrote:
>
> On Wed, 10 Jun 2020 at 12:18, John McCall via cfe-dev <
> cfe-dev at lists.llvm.org> wrote:
>
> On 10 Jun 2020, at 14:32, Richard Smith wrote:
>
> On Mon, 8 Jun 2020 at 19:52, John McCall via cfe-dev
> <cfe-dev at lists.llvm.org>
> wrote:
>
> It definitely changes observable semantics, but it’s not
> *obviously*
> non-conforming; [expr.call]p7 gives us a lot of flexibility here:
>
> It is implementation-defined whether the lifetime of a parameter
> ends when the function in which it is defined returns or at the
> end of the enclosing full-expression.
>
> This is the non-conformance I'm referring to:
> https://godbolt.org/z/cgf5_2
>
> Even given [expr.call]p7, we are still required to destroy
> automatic-storage-duration objects in reverse construction order by
> [stmt.jump]p2:
>
> "On exit from a scope (however accomplished), objects with automatic
> storage duration (6.7.5.3) that have been constructed in that scope
> are
> destroyed in the reverse order of their construction."
>
> Don’t temporaries not have automatic storage duration formally?
>
> The intent is that they don't; we have a longstanding open issue to
> introduce a notion of "full-expression storage duration" to describe
> temporary objects. But in the absence of such a language change, it's
> unclear which utterances about "automatic storage duration" apply to
> temporaries.
>
> But in any case, I think that's immaterial here, because function
> parameters are local variables, not temporary objects, and do have
> automatic storage duration even in the hypothetical world where there's a
> different storage duration for temporaries.
>
> That’s why [class.temporary]p7 has to spell out the interordering
>
> of destruction of lifetime-extended temporaries.
>
> [expr.call]p7 is the most specific statement about the destruction
> of parameters. Under normal principles of interpretation, it should
> take priority.
>
> Well, p7 says nothing about the relative ordering of parameter
> destructions, only the points where such destruction may occur.
>
> Not relative with each other, but I think it has to be understood
> as allowing differences relative to temporaries created by the calling
> full-expression.
>
> Oh, definitely. Prior to C++17, that's a problem (as described in
> http://itanium-cxx-abi.github.io/cxx-abi/argument-destruction.pdf). In
> C++17 and later, though, there is never a case where we elide a copy of a
> temporary into a parameter, so we never have a function parameter that
> might need to outlive its call due to the "longer of the two lifetimes"
> rule. The new model is that a prvalue of class type simply initializes its
> target in-place, without even notionally creating and copying from a
> temporary.
>
> That’s great, but we can still have temporaries created during the
> evaluation of other arguments, and callee-destroy does still mean that
> they won’t be destroyed in reverse order of construction, and that has to
> be okay.
>
> For example:
>
> struct A {
>   A(const std::string &);
>   ~A();
> };
>
> void foo(A x, A y);
>
> void test() {
>   foo(A(“a”), A(“b”));
> }
>
> Assuming left-to-order argument evaluation order, the caller constructs:
> 1. the std::string for “a”
> 2. the parameter x
> 3. the std::string for “b”
> 4. the parameter y
>
> If parameters are destroyed at the end of the call, we’ll destroy them
> in the order 4,2,3,1 instead of 4,3,2,1. That has to be okay.
>
Yes, that must be OK. I think in general there is no requirement that
full-expression-storage-duration objects and automatic-storage-duration
objects are destroyed in reverse construction order relative to each other.
Another example of that:

A make_a(B &&);
A &a = make_a(B()); // full-expression-storage-duration B is destroyed long
before automatic-storage-duration A.

(For what it's worth, I tried suggesting on the core reflector that we
could relax the parameter destructor ordering rule, but that met with some
opposition.)

> Doing the parameters in reverse order seems like a more serious problem,
>
> but one that we can address very specifically. On targets where we
> normally emit left-to-right (everywhere except MS, right?), we can
> just destroy arguments right-to-left in the caller. I think we
> probably do this already, because we probably push cleanups
> left-to-right.
>
> We can't destroy [[trivial_abi]] parameters in the caller.
>
> Argh, sorry, I meant “callee” here.
>
> The one exception about argument order is with
> assignment operators, where the standard forces us to emit the RHS
> first. Simple assignment operators can only be declared as non-static
> member functions with one parameter, so there can only be one by-value
> parameter in the first place. Compound assignment operators could in
> theory be overloaded with two by-value parameters, but of course
> they’ll usually have a reference on the LHS instead. If we really
> feel strongly about this case, we could destroy left-to-right and
> thus make this only a problem when someone takes the address of an
> oddly-defined overloaded compound assignment operator. Or we could
> call it a standard bug, yeah.
>
> I'm arguing on the core reflector right now that this case is a standard
> bug :) I think it would be great if we only got this wrong for an
> address-taken non-member operator$= function (that's the only case where
> right-to-left argument evaluation order is mandated and observable).
>
> Yeah.
>
> Reflecting on that some more: suppose
>
> * we define two entry points for a non-member 'operator$=' function where
> one parameter is callee-cleanup and the other parameter has non-trivial
> destruction (incredibly rare already)
> * for functions with callee-cleanup parameters, we destroy the first
> callee-cleanup parameters *and all subsequent parameters* right-to-left in
> the callee
> - except that for the second entry point for 'operator$=' we instead
> destroy the last callee-cleanup parameter and all prior parameters
> left-to-right in the callee
>
> Then we can get destruction order correct in all cases, and the only cost
> is that the incredibly rare case of a non-member operator$= taking two
> parameters of class type (with some additional conditions) gets an extra
> symbol.
>
> Yeah, we can do this. I guess we could emit them as thunks wrapping a
> common implementation, since parameters are always semantically outside
> anything associated with the function definition?
>
Yes, I think so.

> This (especially the second bullet) would be an ABI change for existing
> users of [[trivial_abi]]. (Alternative simpler rule: if any parameter is
> callee-cleanup then all parameter destruction happens in the callee. I'm
> not sure if that's better or worse, but it seems simpler.)
>
> It does, yeah. But I guess we’d still need two entrypoints to get the
> ordering right? Seems really unfortunate.
>
Yes, but I think we would only need two entry points for non-member
`operator$=` functions that use callee cleanup. I think that's rare enough
that the cost wouldn't be prohibitive.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20200611/119b38d1/attachment-0001.html>


More information about the cfe-dev mailing list