[RFC][C++]: Option to emit a run-time call when throw statement is used with `-fno-exceptions` instead of diagnosing an error.

The current behavior of `throw <object-instance>` is to diagnose it as a hard error
unless it's in a system header. This makes integrating codebases that use exceptions
difficult without resorting to macro tricks and alike.

The proposal is to add a driver option to allow the compiler to turn such cases into
runtime calls to a function like `__cxa_throw_noexcept(...)` stepping into another
frame, with the opaque handle pointing to the exception that would be thrown in that
case. This is more flexibile than std::terminate() approaches as it allows the
application vendor to define such function in their code (if the driver flag is
enabled) and implement a more graceful failure (ie. symbolicated stack trace) without
the need to change existing code.

Rationale is that is makes large codebases compiled include components that make use
of throw without splitting them into separate archives, in source form requires
changing the code throwing the exceptions which is not always easy to merge back into
an upstream project. This also opens the possibility of a less vague variant of such
ABI function if RTTI data is enabled but exceptions are disabled where the application
can implement custom logic for handling specific types of objects.

Majority of cases would still require termination but this option allows existing code
to compile with exceptions disabled and without having to maintain downstream forks
of various projects because they use exceptions. In such cases the application is free
to decide what to do without the need for any refactoring and while this opens up room
for fairly dangerous errors, this assumes that whoever is using that compiler flag is
aware of exception basics and that such an ABI function cannot resume execution normally,
it is better to allow the author to decide how to handle the case (ie. crash dump, signal,
call continuation or any other form of context switch). With or without RTTI such a
function could also include source context of the throw in case where exceptions are
fatal and source/location data is desired in the crash report.

If such flag is enabled, it is up to the application to provide an implementation
of `__cxa_throw_noexcept` dealing with that behavior which also acknowledges that the
author understands the semantics of it (ie. the current frames are likely trashed, there
may be lock guards held, which would have to be accounted for somehow). The proposal
is aimed at environments with a alternative standard library which may do this accounting
in application defined fashion such as storing non-critical (non-spinlocks) locks inside
a thread control block, in which case such cases could even be considered recoverable
giving `throw` an opportunity to exist even in embedded environments.

Thank you.
- Kristina

Would it be enough to just emit the code to implement the throw anyway, and let the user provide their own implementation of the C++ EH runtime that terminates the program?

That’s what we already do when we encounter throw in a system header. Consider this example:

$ cat asdf/t.h
inline void f() {
        throw 0;

$ cat t.cpp
#include "t.h"
int main() {

$ clang -S t.cpp  -fno-exceptions -isystem asdf -o - | grep cxa
        callq   __cxa_allocate_exception
        callq   __cxa_throw

Alternatively, we could pursue a model more like MSVC, where /EHs- just turns off C++ destructor cleanups, which eliminates all implicit EH costs. You can use ‘throw’ and ‘try’, but since no cleanups will run, you can’t reasonably recover from an exception. It just gives you an opportunity to end the program in an appropriate way.

Another reasonable model would be to compile try/catch to nothing, to guarantee that the first throw will end the program.

I am in favor of doing things the way MSVC does it (for now). I ask that you try not to design out what I’m pitching to the C++ standards committee:

Leaving no room for a lower-level language: A C++ Subset


The polls that I’ve had on that leaned towards having a throw call go to std::terminate.