[cfe-dev] Smart Pointer Lifetime Optimizations

John McCall via cfe-dev cfe-dev at lists.llvm.org
Wed Jun 10 19:52:11 PDT 2020


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.

>>> 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?

> 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.

John.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20200610/d58367cc/attachment.html>


More information about the cfe-dev mailing list