
Low-cost Deterministic C++ Exceptions for Embedded Systems (2019) [pdf] - pjmlp
https://www.research.ed.ac.uk/portal/files/78829292/low_cost_deterministic_C_exceptions_for_embedded_systems.pdf
======
Joker_vD
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.

~~~
aw1621107
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.

~~~
ctz
Is there an example where changing the exception handling system _does not_
change the ABI?

~~~
aw1621107
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.

~~~
asveikau
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.

~~~
aw1621107
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.

~~~
asveikau
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.

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

------
fwsgonzo
Seems to be better exceptions than what is already there. And fully compliant
as far as I can tell. Someone just had to go out there and do it. From doing
my own benchmarks on exceptions, I already know you should basically never use
them. The bloat and unexplained slowdowns in real programs is just a headache
you don't want.

In the end, I still use exceptions, so this gives me hope. I like C++
exceptions as a feature, but I'm not happy about how it's implemented right
now.

~~~
dgellow
> I like C++ exceptions as a feature

In your opinion, what are situations/examples that justify the use of
exceptions?

I haven't used a language with exceptions since 3 years (i.e: since I switched
to Go), and I haven't yet faced a situation where I felt a need for them.

Also, I started to learn C++ as a hobby a few months ago, and I have to say
that I still don't understand when I should use exceptions or not, things seem
fine so far by just returning some error values.

~~~
ailideex
Exceptions are ideal for happy path programming. You keep state in the stack
and build your software so things get thrown away. For some types of
programming this is not that important but for OLTP it is very handy.

[https://ninenines.eu/articles/dont-let-it-
crash/](https://ninenines.eu/articles/dont-let-it-crash/)

Basically this approach is handle errors if you know what to do with them,
otherwise leave the error for something else.

In erlang, where you have one process per transaction, and nothing handles the
error, the process crashes.

In C++, you could just have an exception handler around a transaction, and
then when an exception reaches that point you throw away the transaction, but
log that it failed, you could have exception handlers below this to try and do
rollbacks etc.

This is overly simplified but it gives a basic idea.

~~~
dgellow
Thanks for your answer. What is OLTP in that context? Is it
[https://en.wikipedia.org/wiki/Online_transaction_processing](https://en.wikipedia.org/wiki/Online_transaction_processing)?
First time I see that acronym.

~~~
ailideex
OLTP in this context is indeed online transaction processing.

This general approach of «let it "crash"[1]» or happy path programming is what
I used when I wrote OTLP systems for mobile networks which were performing
financial transactions. The exception handling would allow you to write fairly
complex business logic and relying on exception handling to recover to a
stable state.

[1]: Crashing in Erlang terms is not at all what people generally mean with
crashing. It is a very unfortunate word because in part many of the problems
in node.js comes from joyent not understanding what "let it crash" meant - it
did not mean let the unix process crash. Erlang does one process per
transaction and they are incredibly lightweight. Some details from joyent can
be found here: [https://www.joyent.com/node-
js/production/design/errors](https://www.joyent.com/node-
js/production/design/errors)

------
Ericson2314
This is still kinda bad. What you really want is multiple return addresses.
This is liked "double-barelled continuations", but the two continuation
closures share the same activation record (and inductively, the same stack)

See [http://www.ccs.neu.edu/home/shivers/papers/mrlc-
jfp.pdf](http://www.ccs.neu.edu/home/shivers/papers/mrlc-jfp.pdf)

------
wrd83
This paper is truly interesting. People have been fighting the performance
cost between exceptions and returning error codes for years, So the paper just
designs an instruction level mapping of generating code as if exceptions would
be returning error codes.

The approach is somewhat simple and really not that clever, yet the outcome
looks impressive enough. This surprises me as it's just a mechanism of what
people have been arguing over years.

Nicely done!

------
zvrba
They propose a major language change: all functions would be noexcept by
default, and only those explicitly marked with "throws" would be allowed to
throw exceptions.

Interesting paper, but it's not gonna fly in practice.

------
twic
This proposal is sort of based on herbceptions [1]:

> Sutter [19] has only very recently proposed re-using the existing function
> return mechanism in place of the traditional stack unwinding approaches,
> requiring no additional data or instructions to be stored, and little to no
> overhead in unwinding the stack. Furthermore, by removing stack unwinding’s
> runtime reliance on tables encoded in the program binary itself, the issue
> of time and spatial determinism is solved. As it is possible to determine
> the worst-case execution times for programs not using exceptions, it follows
> that exception implementations making use of the same return mechanism must
> also be deterministic in stack unwinding.

> Given these clear advantages, we have based our implementation on this
> design, with a core difference being the replacement of their use of
> registers with function parameters, allowing for much easier
> interoperability with C code, which can simply provide the parameter as
> necessary.

> A limitation with [19] is that they require all exceptions be of the same
> type, leaving much of the standard exception-handling functionality up to
> the user. Our novel approach includes a method of throwing and catching
> exceptions of arbitrary types (as with existing exception handling), without
> imposing any meaningful execution-time penalties when exceptions do not
> occur.

At least, it uses a comparable implementation strategy. Herbceptions used a
different surface syntax, adding a new kind of exception and a new way to
declare them.

I rather like that herbceptions are more restricted in type. Restricting the
type means that a programmer catching an exception has more things they can do
with it. It's currently impossible to sensibly handle exceptions of unknown
type in C++. The change is an improvement, not a limitation.

The point about interoperation with C is interesting. How would that work? If
you wanted to call a C++ function from C, would you have to kludge the
prototype to add the exception pointer?

The herbceptions paper proposed trying to get the C ABI changed to support the
same exception mechanism:

> 4.6.11 Wouldn’t it be good to coordinate this ABI extension with C (WG14)?

> Yes, and that is in progress. This paper proposes extending the C++ ABI with
> essentially an extended calling convention. Review feedback has pointed out
> that we have an even broader opportunity here to do something that helps C
> callers of C++ code, and helps C/C++ compatibility, if C were to pursue a
> compatible extension. One result would be the ability to throw exceptions
> from C++→C→C++ while being type-accurate (the exception’s type is preserved)
> and correct (C code can respond correctly because it understands it is an
> error, even if it may not understand the error’s type).

[1] [http://www.open-
std.org/jtc1/sc22/wg21/docs/papers/2018/p070...](http://www.open-
std.org/jtc1/sc22/wg21/docs/papers/2018/p0709r1.pdf)

------
Jahak
Interesting, thanks.

