logoalt Hacker News

sprocketzlast Wednesday at 1:01 PM6 repliesview on HN

What is that makes NVRO so much more difficult to implement? Why couldn't they mandate that just like RVO? Do compilers literally just special case a simple return statement of a direct construction or something?


Replies

bluGilllast Wednesday at 1:10 PM

The simple cases are simple. However the complex cases get hard.

    mytype foo() {
       mytype one;
       ...
       if(something) {
          mytype two;
          ...
          return two;
       }
    return one;
    }
Is going to be much harder because you don't know are compile time which is returned and so cannot construct the one you return in the correct place. That is just off the top of my head, I'm not a compiler writer, I'm sure they have figured out the simple versions of the above, but you can start to see the complex versions that they can't.
show 1 reply
fluoridationlast Wednesday at 1:18 PM

N/RVO works by (at the machine language level, of course) rewriting the function signature to return void and take an extra pointer parameter, which is written to before returning. If you're returning a newly-constructed object, the compiler can rewrite that into calling the constructor on the pointer, but if you're returning a named object, the class may have a non-trivial destructor that needs to run after the move, such that it's not possible to rewrite uses of the local object into uses of the pointer.

I'm not too confident on that last part, because such an implementation would mess with semantics in case of an exception, so anyone feel free to correct me on that.

show 3 replies
dataflowlast Wednesday at 1:26 PM

The point of (N)RVO is to directly construct the return value in-place at the calling frame. Which requires knowing what object will land there.

In RVO there is no problem because you know what object is the one you need to put there.

In NRVO there is a problem because you might have one of multiple objects being returned and you need to know which one to construct at the call site; it can't be all of them on top of each other. But you don't necessarily know at the time of construction whether that object will be the one that is actually returned. Doing so requires imperfect code analysis so the standard would need to define the complicated analyses to perform.

whizzterlast Wednesday at 1:28 PM

RVO is easy to detect since it happens only in expressions in return-statements.

NRVO requires the compiler to analyze the flow, like if 2 different variables/constructions can lead to the return (what one do we take, or can we do either later?).

Also, with RVO it's easy to detect and elide destruction calling for things going out of scope whilst NRVO would require more careful management of destruction order,etc.

Basically, NRVO touches a lot of things in "inconventient" places that can easily require reworking internal compiler structures to track destinations whilst RVO was probably far easier to just "hack in".

show 1 reply
quuxplusonelast Wednesday at 5:47 PM

The problem is that "predictable reliable NRVO" is still a research problem. Real-world compilers do NRVO a lot but not in a way that is perfectly predictable — that is to say, not in a way that could be standardized across all compilers (or even between different releases of the same compiler).

A "perfectly predictable" algorithm was proposed in Anton Zhilin's P2025, back in the year 2021: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p20... but unfortunately it had some subtle corner-case problems (which I do not remember), so it was sent back for revision, and never returned with a fix. (Maybe because a fix wasn't possible; again I don't remember what the deal was exactly.)

The Right Path Forward would be for MSVC, GCC, and Clang all to try implementing P2025's algorithm in their front ends. Either something concrete breaks (reminding me what the problem was), or else all three mainstream compilers gain predictable NRVO and then we can "standardize existing practice." But the Right Path Forward requires tedious work by at least three people, which is hard.

locknitpickerlast Wednesday at 1:36 PM

> What is that makes NVRO so much more difficult to implement?

I recall reading that at a high level RVO is implemented by treating the return value as an external object. In simple terms (simplistic terms) RVO then works by

- first instantiating the return variable,

- passing the var by reference to the function,

- and then use return value to actually initialize the variable passed by reference.

The moment there's some funny logic on what to write to that output value, the problem gets far more complex.