Hacker News new | past | comments | ask | show | jobs | submit login

I opened the document and immediately searched for "Sutter" to see how this interacts with Herbceptions. It turns out, nicely. Well done!

That said, the rationale seems a bit bogus to me:

> The C calling convention has the caller allocate the space for the returned value before calling a function. union { T value; E error; } takes the size of whichever the bigger of types T or E is, which is as optimal as it can be. However the additional discriminant in struct { union { T value; E error; }; _Bool failed; }; could take up to eight additional bytes more than that, or worse, depending on packing. This may not seem like much, but it is more than optimal.

> For fails(E) returns, it is proposed for at least AArch64, ARM, x86 and x64, that the discriminant be returned via the CPU's carry flag. This is because compilers can often fold the setting or clearing of the CPU's carry flag into the ordering of other operations, thus making this a zero runtime overhead choice of discriminant.

If the ABI for returning discriminated unions is inefficient, then change the ABI. Don't add a whole new language feature and then only give that an efficient ABI.

As for the errno delaying stuff, how about adding a new function consume_errno(), which (up to "as if") reads errno and then sets it to an undefined value? Compilers would then be free not to set the real errno at all if they could prove that a setting of errno was always followed by a call to consume_errno.

I chuckled at this:

> There are two specific subcategories of C++ types which C can transport safely:

... followed by descriptions of Rust types which are Copy, and Rust types which aren't!

The concept of being "insufficiently zero overhead" is also a good one, a bit like being a little bit pregnant.




> If the ABI for returning discriminated unions is inefficient, then change the ABI. Don't add a whole new language feature and then only give that an efficient ABI.

That's exactly what they did; there's nothing preventing the compiler from reusing that signalling mechanism for generic (binary) discriminated unions.


I'm not sure that's true. For ZOE, the compiler knows whether it's returning a failure or not, and thus whether to set/clear the carry flag. The callee immediately handles it so there's no need to store the carry flag. For a generic user defined union, neither of those is true; the compiler won't be able to deduce which field is active in all situations and won't be able to preserve the carry flag (even if it wasn't modified by other instructions, how would you handle 2 discriminated union variables?) so the flag needs to be stored in the union. The compiler could automatically add a flag variable and update it appropriately, but then again, that can be done today in c++ ... call it std::variant.


Even that is too much ABI change. It would take another 20 years for real world embrace the change.

For the record. It took 20 years for GCC to change std::string implementation to COW-based to SSO-based.


> If the ABI for returning discriminated unions is inefficient, then change the ABI.

The problem is that the C (and C++) type system doesn't have the notion of a discriminated union. It gives you the ability to overlap fields, and whatever arrangements you make on top of that are your own. But it also means that there's no way for ABI to know which types of the union are valid and which aren't.

So their choices were either to add full-fledged variant records to the language (which would complicate matters a great deal, since full-fledged variant records mean that types no longer have a static size, which is a rather fundamental concept in C++), or to special-case it like they did to solve the problem at hand.


> So their choices were either to add full-fledged variant records to the language (which would complicate matters a great deal, since full-fledged variant records mean that types no longer have a static size, which is a rather fundamental concept in C++),

Variant records (or safe unions, or enums-with-data, or ADTs, however you want to describe them) can absolutely have a static size: they're the size of the largest variant, plus the discriminant, however you choose to store that.


At that point, you're throwing away the space optimization opportunity that the paper is talking about in the first place.


It's a bit weird they are especially concerned about the exact size this type of discriminated union anyways, since the primary use is to be used on the one-at-a-time on the stack, which is one of the cases where space often does not translate directly into runtime cost (unless say if you are packing a lot of them into the array).

Sometimes there is no "space" cost at all (at least in memory) for ABIs which allow the full union to be passed back in registers. Currently the x86 ABI allows you to pass two pointer-sized things back without using the stack at all (in RDX:RAX in x86 code), which I suspect aligns purposely with their requirement that the error struct be not bigger than two machine words.

I think a bit of what is happening here is that the C++ committee can't change the ABI: that is platform specific, not part of the standard and not in their purview. What they can do is introduce a new type of call which would have to be accommodated by the existing ABIs and this presents an opportunity to add this boolean-value-in-flag-register to the ABIs which have to be updated anyways.


My impression was that the discriminant would be "as if" it's there, but it wouldn't actually be allocated, or at least its allocation can be avoided if there is a sentinel success value for the error case (0 -> success, not-0 -> failure and the specific value indicates what type of failure).




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: