<div dir="ltr"><div dir="ltr">On Wed, 10 Jun 2020 at 19:52, John McCall via cfe-dev <<a href="mailto:cfe-dev@lists.llvm.org">cfe-dev@lists.llvm.org</a>> wrote:<br></div><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><u></u>
<div>
<div style="font-family:sans-serif"><div style="white-space:normal">
<p dir="auto">On 10 Jun 2020, at 18:30, Richard Smith wrote:</p>
</div>
<div style="white-space:normal"><blockquote style="border-left:2px solid rgb(119,119,119);color:rgb(119,119,119);margin:0px 0px 5px;padding-left:5px"><p dir="auto">On Wed, 10 Jun 2020 at 13:06, John McCall via cfe-dev <<br>
<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a>> wrote:<br>
</p>
<blockquote style="border-left:2px solid rgb(153,153,153);color:rgb(153,153,153);margin:0px 0px 5px;padding-left:5px"><p dir="auto">On 10 Jun 2020, at 15:55, Richard Smith wrote:</p>
<blockquote style="border-left:2px solid rgb(187,187,187);color:rgb(187,187,187);margin:0px 0px 5px;padding-left:5px"><p dir="auto">On Wed, 10 Jun 2020 at 12:18, John McCall via cfe-dev <<br>
<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a>> wrote:<br>
</p>
<blockquote style="border-left:2px solid rgb(187,187,187);color:rgb(187,187,187);margin:0px 0px 5px;padding-left:5px"><p dir="auto">On 10 Jun 2020, at 14:32, Richard Smith wrote:</p>
<blockquote style="border-left:2px solid rgb(187,187,187);color:rgb(187,187,187);margin:0px 0px 5px;padding-left:5px"><p dir="auto">On Mon, 8 Jun 2020 at 19:52, John McCall via cfe-dev<br>
<<a href="mailto:cfe-dev@lists.llvm.org" target="_blank">cfe-dev@lists.llvm.org</a>><br>
wrote:</p>
<blockquote style="border-left:2px solid rgb(187,187,187);color:rgb(187,187,187);margin:0px 0px 5px;padding-left:5px"><p dir="auto">It definitely changes observable semantics, but it’s not<br>
*obviously*<br>
non-conforming; [expr.call]p7 gives us a lot of flexibility here:<br>
<br>
It is implementation-defined whether the lifetime of a parameter<br>
ends when the function in which it is defined returns or at the<br>
end of the enclosing full-expression.<br>
</p>
</blockquote><p dir="auto">This is the non-conformance I'm referring to:<br>
<a href="https://godbolt.org/z/cgf5_2" style="color:rgb(187,187,187)" target="_blank">https://godbolt.org/z/cgf5_2</a><br>
<br>
Even given [expr.call]p7, we are still required to destroy<br>
automatic-storage-duration objects in reverse construction order by<br>
[stmt.jump]p2:<br>
<br>
"On exit from a scope (however accomplished), objects with automatic<br>
storage duration (6.7.5.3) that have been constructed in that scope<br>
are<br>
destroyed in the reverse order of their construction."</p>
</blockquote><p dir="auto">Don’t temporaries not have automatic storage duration formally?<br>
</p>
</blockquote><p dir="auto">The intent is that they don't; we have a longstanding open issue to<br>
introduce a notion of "full-expression storage duration" to describe<br>
temporary objects. But in the absence of such a language change, it's<br>
unclear which utterances about "automatic storage duration" apply to<br>
temporaries.<br>
<br>
But in any case, I think that's immaterial here, because function<br>
parameters are local variables, not temporary objects, and do have<br>
automatic storage duration even in the hypothetical world where there's a<br>
different storage duration for temporaries.<br>
<br>
That’s why [class.temporary]p7 has to spell out the interordering</p>
<blockquote style="border-left:2px solid rgb(187,187,187);color:rgb(187,187,187);margin:0px 0px 5px;padding-left:5px"><p dir="auto">of destruction of lifetime-extended temporaries.<br>
<br>
[expr.call]p7 is the most specific statement about the destruction<br>
of parameters. Under normal principles of interpretation, it should<br>
take priority.</p>
</blockquote><p dir="auto">Well, p7 says nothing about the relative ordering of parameter<br>
destructions, only the points where such destruction may occur.</p>
</blockquote><p dir="auto">Not relative with each other, but I think it has to be understood<br>
as allowing differences relative to temporaries created by the calling<br>
full-expression.<br>
</p>
</blockquote><p dir="auto">Oh, definitely. Prior to C++17, that's a problem (as described in<br>
<a href="http://itanium-cxx-abi.github.io/cxx-abi/argument-destruction.pdf" style="color:rgb(119,119,119)" target="_blank">http://itanium-cxx-abi.github.io/cxx-abi/argument-destruction.pdf</a>). In<br>
C++17 and later, though, there is never a case where we elide a copy of a<br>
temporary into a parameter, so we never have a function parameter that<br>
might need to outlive its call due to the "longer of the two lifetimes"<br>
rule. The new model is that a prvalue of class type simply initializes its<br>
target in-place, without even notionally creating and copying from a<br>
temporary.</p>
</blockquote></div>
<div style="white-space:normal">
<p dir="auto">That’s great, but we can still have temporaries created during the<br>
evaluation of other arguments, and callee-destroy does still mean that<br>
they won’t be destroyed in reverse order of construction, and that has to<br>
be okay.</p>
<p dir="auto">For example:</p>
<pre style="background-color:rgb(247,247,247);border-radius:5px;margin-left:15px;margin-right:15px;max-width:90vw;overflow-x:auto;padding:5px" bgcolor="#F7F7F7"><code style="background-color:rgb(247,247,247);border-radius:3px;margin:0px;padding:0px" bgcolor="#F7F7F7">struct A {
A(const std::string &);
~A();
};
void foo(A x, A y);
void test() {
foo(A(“a”), A(“b”));
}
</code></pre>
<p dir="auto">Assuming left-to-order argument evaluation order, the caller constructs:<br>
1. the <code style="background-color:rgb(247,247,247);border-radius:3px;margin:0px;padding:0px 0.4em" bgcolor="#F7F7F7">std::string</code> for “a”<br>
2. the parameter <code style="background-color:rgb(247,247,247);border-radius:3px;margin:0px;padding:0px 0.4em" bgcolor="#F7F7F7">x</code><br>
3. the <code style="background-color:rgb(247,247,247);border-radius:3px;margin:0px;padding:0px 0.4em" bgcolor="#F7F7F7">std::string</code> for “b”<br>
4. the parameter <code style="background-color:rgb(247,247,247);border-radius:3px;margin:0px;padding:0px 0.4em" bgcolor="#F7F7F7">y</code></p>
<p dir="auto">If parameters are destroyed at the end of the call, we’ll destroy them<br>
in the order 4,2,3,1 instead of 4,3,2,1. That has to be okay.</p></div></div></div></blockquote><div>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:<br></div><div><br></div><div>A make_a(B &&);</div><div>A &a = make_a(B()); // full-expression-storage-duration B is destroyed long before automatic-storage-duration A.</div><div><br></div><div>(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.)</div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div><div style="font-family:sans-serif"><div style="white-space:normal"><blockquote style="border-left:2px solid rgb(119,119,119);color:rgb(119,119,119);margin:0px 0px 5px;padding-left:5px"><blockquote style="border-left:2px solid rgb(153,153,153);color:rgb(153,153,153);margin:0px 0px 5px;padding-left:5px"><blockquote style="border-left:2px solid rgb(187,187,187);color:rgb(187,187,187);margin:0px 0px 5px;padding-left:5px"><p dir="auto">Doing the parameters in reverse order seems like a more serious problem,</p>
<blockquote style="border-left:2px solid rgb(187,187,187);color:rgb(187,187,187);margin:0px 0px 5px;padding-left:5px"><p dir="auto">but one that we can address very specifically. On targets where we<br>
normally emit left-to-right (everywhere except MS, right?), we can<br>
just destroy arguments right-to-left in the caller. I think we<br>
probably do this already, because we probably push cleanups<br>
left-to-right.</p>
</blockquote><p dir="auto">We can't destroy [[trivial_abi]] parameters in the caller.</p>
</blockquote><p dir="auto">Argh, sorry, I meant “callee” here.<br>
</p>
<blockquote style="border-left:2px solid rgb(187,187,187);color:rgb(187,187,187);margin:0px 0px 5px;padding-left:5px"><blockquote style="border-left:2px solid rgb(187,187,187);color:rgb(187,187,187);margin:0px 0px 5px;padding-left:5px"><p dir="auto">The one exception about argument order is with<br>
assignment operators, where the standard forces us to emit the RHS<br>
first. Simple assignment operators can only be declared as non-static<br>
member functions with one parameter, so there can only be one by-value<br>
parameter in the first place. Compound assignment operators could in<br>
theory be overloaded with two by-value parameters, but of course<br>
they’ll usually have a reference on the LHS instead. If we really<br>
feel strongly about this case, we could destroy left-to-right and<br>
thus make this only a problem when someone takes the address of an<br>
oddly-defined overloaded compound assignment operator. Or we could<br>
call it a standard bug, yeah.</p>
</blockquote><p dir="auto">I'm arguing on the core reflector right now that this case is a standard<br>
bug :) I think it would be great if we only got this wrong for an<br>
address-taken non-member operator$= function (that's the only case where<br>
right-to-left argument evaluation order is mandated and observable).</p>
</blockquote><p dir="auto">Yeah.<br>
</p>
</blockquote><p dir="auto">Reflecting on that some more: suppose<br>
<br>
* we define two entry points for a non-member 'operator$=' function where<br>
one parameter is callee-cleanup and the other parameter has non-trivial<br>
destruction (incredibly rare already)<br>
* for functions with callee-cleanup parameters, we destroy the first<br>
callee-cleanup parameters *and all subsequent parameters* right-to-left in<br>
the callee<br>
- except that for the second entry point for 'operator$=' we instead<br>
destroy the last callee-cleanup parameter and all prior parameters<br>
left-to-right in the callee<br>
<br>
Then we can get destruction order correct in all cases, and the only cost<br>
is that the incredibly rare case of a non-member operator$= taking two<br>
parameters of class type (with some additional conditions) gets an extra<br>
symbol.</p>
</blockquote></div>
<div style="white-space:normal">
<p dir="auto">Yeah, we can do this. I guess we could emit them as thunks wrapping a<br>
common implementation, since parameters are always semantically outside<br>
anything associated with the function definition?</p></div></div></div></blockquote><div>Yes, I think so. </div><blockquote class="gmail_quote" style="margin:0px 0px 0px 0.8ex;border-left:1px solid rgb(204,204,204);padding-left:1ex"><div><div style="font-family:sans-serif"><div style="white-space:normal">
</div>
<div style="white-space:normal"><blockquote style="border-left:2px solid rgb(119,119,119);color:rgb(119,119,119);margin:0px 0px 5px;padding-left:5px"><p dir="auto">This (especially the second bullet) would be an ABI change for existing<br>
users of [[trivial_abi]]. (Alternative simpler rule: if any parameter is<br>
callee-cleanup then all parameter destruction happens in the callee. I'm<br>
not sure if that's better or worse, but it seems simpler.)</p>
</blockquote></div>
<div style="white-space:normal">
<p dir="auto">It does, yeah. But I guess we’d still need two entrypoints to get the<br>
ordering right? Seems really unfortunate.</p></div></div></div></blockquote><div>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.<br></div></div></div>