The surprising codegen impact of noexcept

Create: March 2, 2020

Would spamming the noexcept keyword make your code faster? Sometimes. But not always. Consider the following snippet of code:

int g();

int f() {
  return g();
}

I intentionally do not define g in this translation unit since otherwise, the compiler will be too smart and inline everything. Nevertheless, all the major C++ compilers can figure out that f only contains a tail-call to g and generate codes like this:

f():
        jmp     g()

Now lets consider the following code:

int g();

int f() noexcept {
  return g();
}

Since the compilers have no idea if g would throw or not, they are forced to generate codes that invoke std::terminate in case bad things happened. Here is the result codegen from various compilers:

msvc

$ip2state$int f(void) DB 02H
        DB      08H
        DB      00H
$cppxdata$int f(void) DB 060H
        DD      imagerel $ip2state$int f(void)

int f(void) PROC                                      ; f, COMDAT
$LN5:
        sub     rsp, 40                             ; 00000028H
        call    int g(void)                         ; g
        npad    1
        add     rsp, 40                             ; 00000028H
        ret     0
int f(void) ENDP                                      ; f

gcc

f():
        sub     rsp, 8
        call    g()
        add     rsp, 8
        ret

clang

f():
        push    rax
        call    g()
        pop     rcx
        ret
        mov     rdi, rax
        call    __clang_call_terminate
__clang_call_terminate:
        push    rax
        call    __cxa_begin_catch
        call    std::terminate()

Conclusion

Don't spam noexcept if you don't have a project-wise "no exception" policy. And be especially careful with higher-order functions that may invoke user-defined functions. All in all, noexcept is a part of the type system and the contract of your API. Only add noexcept to function that you want to guarantee not to throw.