Exception unwinding through a noexcept function

Hi,

Currently, it seems that exception unwinding through a noexcept function behaves basically as-if the noexcept function had try { code(); } catch (...) { std::terminate(); }. As a result, std::terminate() is called from a catch handler and unwinding is effectively considered done at that point, which means that we have lost the ability to extract information about where the exception was originated in the first place. For instance, we normally provide a stack trace of where the exception originated when an exception fails to be caught on macOS. However, when an exception propagates through a noexcept function, we fail to provide a relevant stack trace. Instead it is as-if the exception had been caught and the program terminated, which means that we’ve lost the context of where the original exception had been thrown from. Another issue this creates is that these try-catch create some amount of code bloat (I think this was raised a while back in Code generation for noexcept functions).

Can someone comment on whether it would make sense to instead implement this functionality by detecting that a frame is a noexcept function during unwinding, so that libunwind would effectively keep unwinding all the way up without trying to match catch handlers. This would have the benefits that (1) we don’t need to generate calls to __clang_call_terminate when calling a potentially-throwing function from a noexcept function, and (2) we’d process the throw + terminate-due-to-going-through-noexcept-frame as a single unwinding, which means that we’d keep the knowledge of where the exception was originated from.

Cheers,
Louis

This definitely makes sense, and I actually have about 90% of this implemented, but got distracted by other things.

Let me try to resurrect that…

Oh, wow! That would actually solve a very very long standing issue for us. Please let me know if there’s anything at all I can do to help. This is pretty far from my area of expertise, but I can probably find qualified people to review the change.

Having a fancy stack trace can be achieved with a custom personality routine (related component: libc++abi, instead of libunwind).

Note: std::{,__}terminate ultimately calls abort(). If a program installs a SIGABRT signal handler, it can dump a stack trace including the information about the throw frame. If an OS gives user control of the signal handler, then this behavior will not help, but this may be useful for some users deploying specific services in production.

This would have the benefits that (1) we don’t need to generate calls to __clang_call_terminate when calling a potentially-throwing function from a noexcept function, and

Unable to eliminate __clang_call_terminate is a missing clang/llvm codegen optimization. GCC produces a header-only .gcc_except_table (usually 4 bytes) which achieves the same effect.

I recall that there was a thread in 2020/2021 and jyknight mentioned that this was on his plate(?).

Posted an RFC under “IR & Optimizations” category: