[cfe-dev] Smart Pointer Lifetime Optimizations
John McCall via cfe-dev
cfe-dev at lists.llvm.org
Thu Jun 11 12:10:50 PDT 2020
On 11 Jun 2020, at 12:57, Richard Smith wrote:
> 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.
Right.
> 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.
Right.
The basic problem here is that storage duration stops being a
particularly good proxy for lifetime / destruction order when you
start talking about locals. The real categories seem to be: dynamic,
global, thread, local-scope, full-expression, and (optionally)
parameters.
Hmm. There was a discussion about throwing destructors in bugzilla a
few days ago, and we were talking about how to properly destroy
initialized
objects if a destructor throws during the teardown of the
full-expression.
It just occurred to me that this is *much* thornier than I’d given it
credit
for because you can have arbitrarily many initialized objects and they
can
be arbitrarily intermingled with the temporaries, because of lifetime
extension of `std::initializer_list` and other recursive/branching
extensions. So e.g. if you have:
```
struct Temp {
Temp(const char *);
~Temp() noexcept(false);
};
struct Element {
Element(Temp);
~Element();
};
std::initializer_list<Element> &&list = {
{ Temp(“a”) },
{ Temp(“b”) },
{ Temp(“c”) }
};
```
Then the normal destruction order is:
Temp_c, Temp_b, Temp_a, /*end of scope*/, Element_c, Element_b,
Element_a
But if Temp_c’s destructor throws, then I guess the exceptional
destructor order is:
Element_c, Element_b, Temp_b, Element_a, Temp_a
Unless there’s an argument that the whole `std::initializer_list`
becomes
a single object at some point? This doesn’t affect normal aggregate
list-initialization of an object because each initialization is a
separate
full-expression, but I don’t think that’s true of a
`std::initializer_list`
that’s just in the middle of an expression, right?
>> 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.
Right, good point. As long as the ABI divergence is specific to
functions
with callee cleanup, it’s acceptable.
John.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20200611/c1fc9a46/attachment.html>
More information about the cfe-dev
mailing list