In a compiler's intermediate representation, exceptions are typically modeled as multiple returns. E.g. in LLVM the `invoke` op specifies a label to go to if an exception is returned http://llvm.org/docs/LangRef.html#invoke-instruction
By the time this reaches the backend, the exception handling is usually converted into "zero cost" exceptions where raising an exception calls a handler which uses a lookup table to work out how to unwind the stack and what destructors to run etc. Here's a really good explanation I found https://mortoray.com/2013/09/12/the-true-cost-of-zero-cost-e...
This "zero cost" exception handling has no performance penalty on normal CPUs in normal execution as exceptions are exceptional so the conditional call to the handler easy on the branch predicter and the clutter of unwinding doesn't fill the caches.
(Zero cost execption handling appeared first iirc in Metrowerks compilers, but quickly became the standard way on most modern platforms and is now in the ABIs.)
The multiple-return approach may well be much better for total program size.
Whether you can use the multiple-return approach on bare ARM - when the ARM ABI specifies zero cost exception handling - is nothing I know about.
GCC does employ DWARF exception handling (the zero-cost EH model) on ARM7TDMI. While the cost is zero in time, it is not zero in space. The EH tables, unwinder code, and support code for them have a non-zero space cost. While the space cost is normally considered negligible in a workstation or server environment, in a microcontroller environment it most certainly is not.
To add clarity to this explanation where I think it's needed: The llvm 'invoke' op is effectively a pseudo-op and does not necessarily indicate what a backend will do. It looks something like this (the exact details, I forget):
Notice that there is no conditional. However, if in function-to-call, you have:
throw exception()
This will be translated into a table lookup that checks which exception handler is valid for the current block. This is the slow part and is generally no worse (theoretically) than frame-based exception handling.
In my experience (desktop and server, haven't tried it on a microcontroller) exceptions have for all practical purposes zero cost until you actually throw one, at which point the cost is ten thousand clock cycles. That figure has stayed surprisingly constant over two decades of hardware and compilers for three different languages. (And, obviously, it's a negligible cost for an exceptional situation, but you wouldn't want to use it for flow control in a crunch-heavy inner loop.)
FWIW, the Symbian file server used an exception to get out of a crucial loop. It was a bit of laughable trivia, but a bunch of us set out to prove it was a bad idea... but on profiling it was provably noise. We moved from setjmp implementation to EABI zero-cost and it was still no issue.
Just a data-point and a good case of profiling beating intuition :)
Theoretically, it's just as fast as frame-based exception handling which follows pointers up the stack. However, things that might impact the performance are things like code locality. For example, if the exceptional code hasn't been loaded into memory since it hasn't been needed, your program will need to stall while the code is loaded into memory.
So basically, assume it is fast as hell (zero-cost) until you hit an exceptional case (so don't use exceptions for flow control.) In fact, I no longer use exceptions at all. I just "make a note and move on". Having objects that don't cause catastrophic failures when they are "null" is a good first step.
I'm not sure what you mean by "whether you can use the multiple-return approach", since the ABI is usually irrelevant on bare metal. The only issue would be tooling support and the standard library.
By the time this reaches the backend, the exception handling is usually converted into "zero cost" exceptions where raising an exception calls a handler which uses a lookup table to work out how to unwind the stack and what destructors to run etc. Here's a really good explanation I found https://mortoray.com/2013/09/12/the-true-cost-of-zero-cost-e...
This "zero cost" exception handling has no performance penalty on normal CPUs in normal execution as exceptions are exceptional so the conditional call to the handler easy on the branch predicter and the clutter of unwinding doesn't fill the caches.
(Zero cost execption handling appeared first iirc in Metrowerks compilers, but quickly became the standard way on most modern platforms and is now in the ABIs.)
The multiple-return approach may well be much better for total program size.
Whether you can use the multiple-return approach on bare ARM - when the ARM ABI specifies zero cost exception handling - is nothing I know about.