| ▲ | dataflow 3 hours ago |
| > N/RVO works by (at the machine language level, of course) rewriting the function signature to return void and take an extra pointer parameter This sounds wrong, are you sure? Would you mind demonstrating with an example on godbolt? Whether NRVO applies or not, the ABI should be the same, AFAIK. |
|
| ▲ | OskarS 2 hours ago | parent | next [-] |
| Yes, it works exactly like this, this is a demo on godbolt [0]. rdi stores the pointer in both cases, makeS1() uses RVO, makeS2() takes it explicitly and constructs with placement new. I will say before testing this i didn't realize the RVO calling convention was to return the pointer you pass in, but apparently so. If makeS2() returned void, it's just a tail call to the constructor, but makeS1() has to spill rbx and use it to save the pointer. [0]: https://godbolt.org/z/ovd1n99P8 |
| |
| ▲ | dataflow 22 minutes ago | parent [-] | | No, all you're showing in that example is that a pointer is passed as part of the ABI. You're not showing that RVO relates to that in any way whatsoever. If you write the same function in a manner that (N)RVO can't kick in, does the pointer no longer get passed? The reason this should sound dubious is that you're suggesting the caller needs to know the callee's body in order to know how to call it, but it should be possible for the two to be compiled entirely independently, and in fact mutual recursions should be fine too. After all, the callee knows where the return value has to land either way, regardless of when/how it's constructed or destroyed. |
|
|
| ▲ | fluoridation 3 hours ago | parent | prev [-] |
| Yes, of that I'm sure. This optimization is only possible if the compiler has control of both sides of a call. If the function may be callable from other translation units or modules I imagine it generates a thin wrapper that's externally callable. |
| |
| ▲ | dgrunwald 2 hours ago | parent [-] | | The optimization is often possible even if the computer does not see the call, because most (all?) ABIs have always required hidden pointer parameters for class types with non-trivial destructors. https://godbolt.org/z/9WvnEvEYh
Note how `std::unique_ptr<int>` effectively passed as a `int**`; and that the by-value unique_ptr is not destroyed at the end of the function -- destroying parameters is instead the caller's job (and commonly only happens at the end of the full expression containing the call -- though this choice is implementation-defined).
But that can only work if the caller can see the updated value of the parameter (to avoid double-free for `clear`) -> thus the need to pass the parameter by hidden pointer. |
|