Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

So. The proposed mechanism is:

1. Every function now receives an additional parameter that is basically a pointer to a std::optional<exception_t> (from now on, "exception holder"; exception_t can hold any value whatsoever).

2. "try" is translated into allocating a (new) exception holder on the stack and passing pointer to it in all function calls inside of the try block. "catch" is translated into checking this exception holder for holding an (appropriate) exception. If "yes", handle it, if "no", go on.

3. Throwing an exception is done by putting the constructed exception into the exception holder passed to you, and returning as usual.

4. Each function call is now followed by checking whether the exception holder holds an exception. If "yes", return immediately, if "no", keep executing.

Example:

    void C(std::optional<exception_t>* exc_holder) {
        Bar bar;
        exc_holder->emplace(0);
    } 

    void B(std::optional<exception_t>* exc_holder) {
        Foo foo;
        C(exc_holder);
        if (*exc_holder) { return; }
    }

    void A(std::optional<exception_t>* exc_holder) {
        std::optional<exception_t> new_exc_holder;
        B(&new_exc_holder);
        if (new_exc_holder)
            if (new_exc_holder.value().is<int&>()) {
                int& p = new_exc_holder.value().as<int&>();
                // catch body...
            } else {
                *exc_holder = std::move(new_exc_holder);
                return;
            }
        }
    }
So, it's basically "if err != nil { return <default_value>, err }" but with slightly less amount of copying error values. Somehow, it ends up with the same performance despite all of this constant branching error-checking and better performance when re-throwing exceptions.


Is that an ABI break due to the extra parameter? I skimmed the paper, but it only seemed to talk about the standard table-based unwind in passing, with no mention of compatibility. Did I miss something?

If it is in fact an ABI break, how much of an obstacle would that be to getting this through the committee? I was also under the impression that up-to-date compilers/runtimes are not something one can take for granted in the embedded world, so recompiling everything is a shaky proposition.


> If it is in fact an ABI break, how much of an obstacle would that be to getting this through the committee?

In my understanding:

Historically, the committee basically said "if your paper breaks the ABI, it won't be considered, don't bother." In Prague last month, the committee voted that C++23 would not break ABI, though that decision is not final. However, they did say that authors should bring papers that would break the ABI, and they will be considered in a general sense.

So, it is now possible, at least.


That's fair. I suppose the next question would be what it would take to get the committee to stop kicking the can down the road and actually vote to break ABI, since "considered" is a fairly weak promise. Perhaps they're waiting to see what people come up with?


I was not in attendance, but from what I read on /r/cpp, at least some people thought that the discussion was brought up in sort of a suboptimal way, and that there was actually a lot of support in the room for breaking the ABI. Regardless, I think that what was decided is clearly in this direction.


I'm curious to see where C++ will go from here on out then. If only they were open to breaking API too to fix things like unordered_map...

By the way, I know Rust doesn't guarantee a stable ABI, but has Rust gotten significant improvements out of not making that guarantee?


The main improvement is not spending time and energy to define such a thing and maintain it.

We have done some breaking stuff too, and that has been useful, but in my mind, that’s less important.


That's fair. Thanks!


Is there an example where changing the exception handling system does not change the ABI?


I'm not familiar enough with the darker corners of C++ implementations to say, but I'd guess not. From what the paper describes it seems table-based exception handling is deeply ingrained into the Itanium ABI so any significant change would be an ABI break, which would make said change more difficult to spread.


I am not sure if all implementers made the same choices, but for example I believe Microsoft concluded that table-based EH is always better and made not only their IA64 ABI but also their amd64 ABI use it. They keep 32-bit x86 at an older implementation for compatibility reasons, but any ABI introduced later gets the new thing.


Wait, is the Itanium ABI not the amd64 ABI? I thought that the 64-bit x86 ABI was called the Itanium ABI despite Itanium having gone kaput.

Did Microsoft release a paper with that conclusion? I'd be curious to read more about their reasoning.


Hmm, I missed the C++ism for this term. Googling around:

> Although it was initially developed for the Itanium architecture, it is not platform-specific and can be layered portably on top of an arbitrary C ABI.

My mistake, my comment is mostly rubbish then.


I didn't realize it was a C++-specific thing, to be honest. I should probably try for more generic wording in the future.


Changing the handling of existing exceptions, no. But adding a new kind of exception is fine. That's what the herbceptions proposal did. However, i think they are in fact proposing to change the ABI here.


It is ABI breaking but there is a way out. Instead of replacing the existing exceptions, this mechanism could be used for checked exceptions only which will necessarily require annotating functions, so ABI breaks are to be expected.


Wouldn't function annotations be an API break, too?

And I'd imagine checked exceptions would get a decent amount of pushback due to Java's implementation.


Yes, but they are opt-in.

I think, given the right abstraction capabilities they might work in C++. They are semantically no different than returning an either<T, error>.


This is pretty much how exceptions are implemented in some bytecode interpreters (e.g. CPython).


> Somehow, it ends up with the same performance despite all of this constant branching error-checking and better performance when re-throwing exceptions.

SJLJ exceptions were also deterministic and slow.

The author's recorded a 2-4% slowdown in the case that exceptions weren't thrown (relative to zero-cost DWARF EH), and a 1-2% slowdown in the case that exceptions were thrown (again, relative to DWARF EH).

That's not an improvement, that's a regression.


Perhaps. But

a) apparently branch prediction is good enough to make all this constant checking for exceptional returns to slow down the execution by only a couple of percents;

b) apparently exception propagation and re-throwing is way, way faster than in the standard implementation;

c) the exclusion of the unwind tables makes the code 3 times smaller on ARM, and 5 times smaller on x86-64. That's pretty huge.

So all in all, I'd say that if those figures actually hold, then this approach is pretty solid. It's just surprising, given how much complaining there is about how Go's idiom of "if err != nil { return nil, err }", or Rust's "let v = some_fun()?" kills performance because of branch mis-prediction and hot path code bloat, so maybe those figures don't actually hold?


> given how much complaining there is about how Go's idiom of "if err != nil { return nil, err }", or Rust's "let v = some_fun()?" kills performance because of branch mis-prediction and hot path code bloat

Was that the main complaint? I thought that Go's thing was was more about being noisy boilerplate and a bug magnet rather than a performance issue. Not sure about Rust, either, since IIRC the ? operator is sugar for the try! macro, which itself was just sugar for bubbling up a Result<Err>.


For "standard" applications, sure, but the paper mentions that for embedded uses code size and determinism can be more important than pure execution speed, and perhaps a few percent slowdown would be considered acceptable for embedded development if it allows the use of exceptions.


Real-time != fast.


Honestly, it looks like someone is just taking the LLVM IR instruction `invoke` literally.




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

Search: