Why can't the compiler optimize a single throw statement in a try-catch block?
Asked Answered
W

2

14

I was just playing around with some C++ code at Compiler Explorer and noticed some unexpected behavior when compiling a simple try/catch block. Both of the following snippets were compiled with gcc using optimization flag -O3 (live example).

In the first program, the whole try/catch block gets removed, since there is no noticeable difference in the behavior, resulting in the same assembly as just a return 0;. This is exactly what I would have expected the compiler to produce.

int main() {
    try {
        int x{ 10 };
    }
    catch (...) {
    }

    return 0;
}
main:
        xor     eax, eax
        ret

In the second snippet I throw an exception instead of just initializing an int. After that exception got thrown, I would continue in the catch-all handler, which is empty. After that there is only the return 0; statement. This means that the observable behavior for this program would be the same as the first one. However, the assembly shows, that there is still the whole try/catch going on.

int main() {
    try {
        throw 10;
    }
    catch (...) {
    }

    return 0;
}
main:
        push    rcx
        mov     edi, 4
        call    __cxa_allocate_exception
        xor     edx, edx
        mov     esi, OFFSET FLAT:_ZTIi
        mov     DWORD PTR [rax], 10
        mov     rdi, rax
        call    __cxa_throw
        mov     rdi, rax
        call    __cxa_begin_catch
        call    __cxa_end_catch
        xor     eax, eax
        pop     rdx
        ret

Now I am wondering why the compiler can't recognize that the whole try/catch block is "dead code" (or is it not?), or if it can recognize it, why it is not optimizing it away.

Whitish answered 8/1 at 17:14 Comment(7)
There is a global "in flight" exception. Your throw 10; will copy that 10 literal to the in flight int exception with the value 10. I recall that the exception machinery can be hooked into. Perhaps it can't be optimized out, even though your code is complete and self-contained, and doesn't hook into that exception machinery.Metropolis
I think this is the "lowest hanging fruit" syndrome. Of all the possible optimizations in C++ code, being able to optimize away an exception entirely probably carries the least possible likelyhood of happening. As such, the suffering compiler developers have chosen to apply their limited energies towards more productive avenues, and focus on improving other kinds of optimizations. It's also doubtful that this is the only kind of an optimization that seems obvious to a meatbag's brain, but is surprisingly difficult, logically, to define and implement.Cameleer
MSVC is able to do that: godbolt.org/z/orsceoW3W (main just zeros EAX).Rubbish
@MarekR, are you looking at LN7@main? In the main PROC it's still throwing; though it does get rid of the catch.Foucault
gcc.gnu.org/bugzilla/show_bug.cgi?id=38658Lengthwise
@MarekR: As ChrisMM points out, that' just the catch/return block. If you change it to catch(char c) (which doesn't catch the throw of an int so the program crashes instead), MSVC's code-gen is the same: godbolt.org/z/56nd1cffdInveigle
Related Clang issues: #34400, #17841 and MSVC issue: #407146.Uchida
V
0

According to a class I took on optimization:

In the first code snippet, the compiler is able to determine (at compile-time) that there was no exception thrown within your try block. This means it will optimize away the whole try-catch block. In the second snippet, you are explicitly throwing an exception, which the compiler must generate code to handle, even when the catch block is empty.

The reason why the compiler doesn't "optimize away" the try/catch block in the snippet is because it can't statically determine if an exception is going to be thrown at runtime. The presence of a throw statement within a try block will prevent the compiler from optimizing away its associated catch block even if the catch block is empty.

Vanir answered 8/4 at 23:9 Comment(1)
Actually, in this case the compiler can statically determine that an exception will be thrown on runtime and optimize the code accordingly, it is just not worth the effort implementing.Egyptian
M
0

Yes, such optimization is possible in theory, but I highly doubt such pattern appears in real-world programs (as opposed to code snippets written only to torture the compiler).

Perhaps compiler developers simply don't want to devote their time on something not helpful to the real world.

It’s generally considered best practice to reserve C++ exceptions only for truly exceptional circumstances. Modern C++ compilers implement “zero-cost exceptions”, meaning there is almost no cost when no exception is thrown. This comes at the cost of very slow stack unwinding when an exception is actually thrown. Modern C++ compilers also optimize catch blocks aggressively for smaller code size, at the expense of more processor cycles.

This is in contrast to some other languages, e.g. in Python exceptions are raised much more frequently, and proper use of exceptions can be considered “Pythonic”.

Marion answered 7/5 at 12:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.