[RFC] Clang: true `noexcept` (aka, defaults are often wrong, hardcoded defaults are always wrong)

No, absolutely not, throwing from a noexcept function is not UB and cannot be turned into unreachable. It is not permissible to, for example, delete the range check from std::vector::at, simply because v.at(N) is called within a noexcept function.

After the unwindabort patches, void f() noexcept { throw 1; } compiles to the following IR:

define dso_local void @f() #0 personality ptr @__gxx_personality_v0 {
entry:
  %exception = call ptr @__cxa_allocate_exception(i64 4) #1
  store i32 1, ptr %exception, align 16
  call unwindabort void @__cxa_throw(ptr %exception, ptr @_ZTIi, ptr null) #2
  unreachable
}
attributes #0 = { mustprogress noreturn nounwind uwtable ... }
attributes #1 = { nounwind }
attributes #2 = { noreturn }

(One thing you may do is delete any cleanups which are guaranteed to continue unwinding into an unwindabort.)

1 Like

… and that’s precisely my point. unwindabort is orthogonal to the issue at hand.

To me, that’s the goal of this RFC – to make exactly that situation be UB (as an opt-in) so that you get the improved performance the language leaves on the table by defining that behavior explicitly.

This attribute exists to support exceptions in C. E.g. if a C function calls C++ function that throws, std::terminate is called. I.e. it is a C equivalent of noexcept.
It is mainly used with glibc “leaf” functions so that C++ callers won’t get the calls wrapped in a call-site.
Note that e.g. printf can throw (I don’t remember why, but it is somehow related to POSIX cancellation points).

so that you get the improved performance the language leaves on the table by defining that behavior explicitly.

But that’s exactly the part I’m missing – with future codegen, I no longer see a lot of performance being left on the table anymore, which would warrant adding this option.

The two specific issues discussed were the fact that Clang needs to emit “invoke” in “noexcept” functions, which leads to significant overhead and loss of optimizations (fixed by pending patch series), and that both clang and GCC have a binary-size overhead for a noexcept function to emit the exception table (can be fixed by sharing the trivial-no-call-sites noexcept table, no patch yet).

So the major optimization remaining which this RFC would enable is the replacement of a ‘throw’ itself with ‘unreachable’, and thus the removal of conditional branches/computations leading up to it. I just don’t see that as likely to enable significant real-world code optimization. Do you have some evidence that it will?

Furthermore, doing something like this raises some tricky questions. Taking the particular case of std::vector::at: that function already goes out of its way to ensure that the program will abort without exceptions enabled, via __throw_out_of_range which ifdefs to either throw or abort depending on __cpp_exceptions. If we do add this new compiler flag, should we give it a preprocessor conditional – and should std::vector::at switch to the “abort” mode when it’s enabled?

Correct.

Well, and i don’t see why on earth LLVM does 90% of what it does, it can’t possibly matter :slight_smile:

I have seen such cases, yes.
(I’m not doing this just to fill a checkbox somewhere, just for the sake of it.)

  1. I don’t use libcxx, nor contribute to it
  2. The change is for a clang, it does not mandate anything else to anyone else.
  3. I don’t have a clue what any particular library should do,
    nor do i think i should tell them what they should do
  4. This argument is non-sensical. I have a great analogy, but i think i will be banned if i state it.
    TLDR is, it’s not std::vector::at place to try to workaround this mode,
    because the mode is explicitly intended to circumvent such checks.
    (Note: i don’t care about std::vector::at in particular, you brought it up)

I think you’re right that it’s mostly useful for C code, but it doesn’t actually work like C++ noexcept. It actually cannot: the C personality function, __gcc_personality_v0, only supports cleanups – for implementing __attribute__((cleanup(fn))) – not catching exceptions or termination.

In C mode, this attribute basically does nothing to a function definition. But, on a decl, it tells the caller function that the called fn won’t throw (thus, the compiler can omit exception handling for an __attribute__((cleanup)), for example.)

1 Like