7 ms·
Oh man, don't get me started. This was a point in a talk I gave years ago called "Please Please Help the Compiler" (what I thought was a clever cut at the conve
by terrymah 2y ago
Oh man, don't get me started. This was a point in a talk I gave years ago called "Please Please Help the Compiler" (what I thought was a clever cut at the conventional wisdom at the time of "Don't Try to Help the Compiler")
I work on MSVC backend. I argued pretty strenuously at the time that noexcept was costly and being marketed incorrectly. Perhaps the costs are worth it, but none the less there is a cost
The reason is simple: there is a guarantee here that noexcept functions don't throw. std::terminate has to be called. That has to be implemented. There is some cost to that - conceptually every noexcept function (or worse, every call to a noexcept function) is surrounded by a giant try/catch(...) block.
Yes there are optimizations here. But it's still not free
Less obvious; how does inlining work? What happens if you inline a noexcept function into a function that allows exceptions? Do we now have "regions" of noexceptness inside that function (answer: yes). How do you implement that? Again, this is implementable, but this is even harder than the whole function case, and a naive/early implementation might prohibit inlining across degrees of noexcept-ness to be correct/as-if. And guess what, this is what early versions of MSVC did, and this was our biggest problem: a problem which grew release after release as noexcept permeated the standard library.
Anyway. My point is, we need more backend compiler engineers on WG21 and not just front end, library, and language lawyer guys.
I argued then that if instead noexcept violations were undefined, we could ignore all this, and instead just treat it as the pure optimization it was being marketed as (ie, help prove a region can't throw, so we can elide entire try/catch blocks etc). The reaction to my suggestion was not positive.
- rockwotj 2y agoAdded to my list why I compile with -fno-exceptions
- jcelerier 2y ago-fno-exceptions only prevents you from calling throw. If you don't want overhead likely you want -fno-asynchronous-unwind-tables + that clang flag that specifies that extern "C" functions don't throw
- rockwotj 2y agoLooking how the docs for https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#index-fasynchronous-unwind-tables https://gcc.gnu.org/onlinedocs/gcc/Code-Gen-Options.html#ind... How does that affect code generation? It reads as only effecting debug information and binary size to my untrained eyes.
- rockwotj 2y agoAnd based on a few clang discourse threads, it only removes .eh_frame I think this only effects binary size, which I understand smaller binaries can load faster but not being able to get stacktraces for debuggers and profilers seems like a loss
- flohofwoe 2y agoI'm pretty sure I could see a roughly 10% binary size decrease in my C++ projcts just by setting -fno-exceptions, and that was for C++ code that didn't use exceptions in the first place, so there must be more to it then just forbidding throw. Last time I tinkered with this stuff was around 2017 though.
- terrymah 2y agoOh, cool! I googled myself and someone actually archived the slides from the talk I gave. I think it holds up pretty well today https://github.com/TriangleCppDevelopersGroup/TerryMahaffeyCppTalk/blob/master/compiler.pptm https://github.com/TriangleCppDevelopersGroup/TerryMahaffeyC... *edit except the stuff about fastlink *edit 2 also I have since added a heuristic bonus for the "inline" keyword because I could no longer stand the irony of "inline" not having anything to do with inlining *edit 3 ok, also statements like "consider doing X if you have no security exposure" haven't held up well
- jahnu 2y agoProps for the edits ;) I would be very interested in an updated blog post on this if you felt so inclined!
- dataflow 2y agoI assume the /EHr- flag was introduced to mitigate this, right?
- terrymah 2y agoNah that was mostly about extern "C" functions which technically can't throw (so the noexcept runtime stuff would be optimized out) but in practice there is a ton of code marked extern "C" which throws
- dataflow 2y agoextern "C" seems related to the other flags, not 'r'?
- terrymah 2y agoWell, yeah, things can be related to many things, but throwing extern "C"s was one of the motivations as I recall for 'r'. r is about a compiler optimization where we elide the runtime terminate check if we can statically "prove" a function can never throw. To prove it statically we depend on things like extern "C" functions not throwing, even though users can (and do) totally write that code.
- Arech 2y ago> in practice there is a ton of code marked extern "C" which throws Obviously, a random programmer could do any evil things, but does that apply to standard code, such as C standard library used from C++?
- aw1621107 2y ago> I argued then that if instead noexcept violations were undefined, we could ignore all this, and instead just treat it as the pure optimization it was being marketed as (ie, help prove a region can't throw, so we can elide entire try/catch blocks etc). Do you know if the reasoning for originally switching noexcept violations from UB to calling std::terminate was documented anywhere? The corresponding meeting minutes [0] describes the vote to change the behavior but not the reason(s). There's this bit, though: > [Adamczyk] added that there was strong consensus that this approach did not add call overhead in quality exception handling implementations, and did not restrict optimization unnecessarily. Did that view not pan out since that meeting? [0]: https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n3081.pdf https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2010/n30...
- flamedoge 2y ago> did not restrict optimization unnecessarily. well clearly there is a cost
- terrymah 2y agoI think WG21 has been violently against adding additional UB to the language, because of some hacker news articles a decade ago about people being alarmed at null pointer checks being elided or things happening that didn’t match their expectation in signed int overflow or whatever. Generally it seems a view of spread that compiler implementers view undefined behavior as a license to party, that we’re generally having too much fun, and are not to be trusted. In reality undefined behavior is useful in the sense that (like this case) it allows us to not have to write code to consider and handle certain situations - code which may make all situations slower, or allows certain optimizations to exist which work 99% of the time. Regarding “not pan out”: I think the overhead of noexcept for the single function call case is fine, and inlining is and has always been the issue.
- aw1621107 2y ago> I think WG21 has been violently against adding additional UB to the language, because of some hacker news articles a decade ago about people being alarmed at null pointer checks being elided or things happening that didn’t match their expectation in signed int overflow or whatever. Huh, didn't expect the no-UB sentiment to have extended that far back! > Regarding “not pan out”: I think the overhead of noexcept for the single function call case is fine, and inlining is and has always been the issue. Do you know if the other major compilers also face similar issues?
- pjmlp 2y ago> Anyway. My point is, we need more backend compiler engineers on WG21 and not just front end, library, and language lawyer guys. Even better, the current way of working is broken, WG21 should only discuss papers that come with a preview implementation, just like in other language ecosystems. We have had too many features being approved with "on-paper only" designs, to be proven a bad idea when they finally got implemented, some of which removed/changed in later ISO revisions, that already prove the point this isn't working.
- ratmeadow 2y ago> there is a guarantee here that noexcept functions don't throw. std::terminate has to be called. That has to be implemented Could you elaborate on how this causes more overhead than without noexcept? The fact that something has to be done when throwing an exception is true in both cases, right?. Naively it'd seem like without noexcept, you raise the exception; and with noexcept, you call std::terminate instead. Presumably the compiler is already moving your exception throwing instructions off the happy hot path. Very very basic test with Clang: https://godbolt.org/z/6aqWWz4Pe https://godbolt.org/z/6aqWWz4Pe Looks like both variations have similar code structure, with 1 extra instruction for noexcept.
- hifromwork 2y agoPick a different architecture - anything 32bit. Exception handling on 64bit windows works differently, where the overhead is in the PE headers instead of asm directly (and is in general lower). You don't have the setup and teardown in your example Throwing exception has the same overhead in both cases. In case of noexcept function, the function has to (or used to have, depending on architecture setup an exception handling frame and remove it when leaving. >Naively it'd seem like without noexcept, you raise the exception; and with noexcept, you call std::terminate instead Except you may call a normal function from a noexcept function, and this function may still raise an exception.
- bregma 2y agoIf you're on one of the platforms with sane exception handling, it's a matter of emitting different assembly code for the landing pad so that when unwinding it calls std::terminate instead of running destructors for the local scope. Zero additional overhead. If you're on old 32-bit Microsoft Windows using MSVC 6 or something, well, you might have problems. One of the lesser ones being increased overhead for noexcept.
- account42 2y ago> Zero additional overhead. It's zero runtime overhead in the good case but still has an executable size overhead for functions that previously did not need to run any destructors.
- denotational 2y agoI’m curious: where does the overhead of try/catch come from in a “zero-overhead” implementation? Is it just that it forces the stack to be “sufficiently unwindable” in a way that might make it hard to apply optimisations that significantly alter the structure of the CFG? I could see inlining and TCO being tricky perhaps? Or does Windows use a different implementation? Not sure if it uses the Itanium ABI or something else.
- terrymah 2y agoEveryone keeps scanning over the inlining issues, which I think are much larger “Zero overhead” refers to the actual functions code gen; there are still tables and stuff that have to be updated Our implementation of noexcept for the single function case I think is fine now. There is a single extra bit in the exception function info which is checked by the unwinder. Other than requiring exception info in cases where we otherwise wouldn’t The inlining case has always been both more complicated and more of a problem. If your language feature inhibits inlining in any situation you have a real problem
- immibis 2y agoDoesn't every function already need exception unwinding metadata? If the function is marked noexcept, then can't you write the logical equivalent of "Unwinding instructions: Don't." and the exception dispatcher can call std::terminate when it sees that?
- formerly_proven 2y agoIt's kinda funny that C++ even in recent editions generally reaches for the UB gun to enable optimizations, but somehow noexcept ended up to mean "well actually, try/catch std::terminate". I bet most C++-damaged people would expect throwing in a noexcept function to simply be UB and potentially blow their heap off or something instead of being neatly defined behavior with invisible overhead.
- shrimp_emoji 2y agoUnless you're C++-damaged enough to assume it's one of those bullshit gaslighting "it might actually not do anything lol" premature optimization keywords, like `constexpr`.
- cogman10 2y ago`inline` is my favorite example of this. It's a "This does things, not what you think it does, and also it's not used for what you think it is. Don't use it".
- cogman10 2y agoProbably the right thing for noexcept would be to enforce a "noexcept may only call noexcept methods", but that ship has sailed. I also understand that it would necessarily create the red/green method problem, but that's sort of unavoidable.
- tsimionescu 2y ago> I argued then that if instead noexcept violations were undefined, we could ignore all this, and instead just treat it as the pure optimization it was being marketed as (ie, help prove a region can't throw, so we can elide entire try/catch blocks etc). The reaction to my suggestion was not positive. So instead of helping programmers actually write noexcept functions, you wanted to make this an even bigger footgun than it already is? How often are there try/catch blocks that are actually elideable in real-world code? How much performance would actually be gained by doing that, versus the cost of all of the security issues that this feature would introduce? If the compiler actually checked that noexcept code can't throw exceptions (i.e. noexcept functions were only allowed to call other noexcept functions), and the only way to get exceptions in noexcept functions was calls to C code which then calls other C++ code that throws, then I would actually agree with you that this would have been OK as UB (since anyway there are no guarantees that even perfectly written C code that gets an exception wouldn't leave your system in a bad state). But with a feature that already relies on programmer care, and can break at every upgrade of a third party library, making this UB seems far too dangerous for far too little gain.