Could clang claim all functions are noexcept when exceptions are disabled?

244 views
Skip to first unread message

Peter Kasting

unread,
Jan 30, 2018, 10:43:35 PM1/30/18
to cxx
We guide people to mark move constructors as noexcept, since even though exceptions are disabled, it can affect performance.  This is because things like std::vector check the move constructor type traits to see if they're noexcept, so they can support the strong exception guarantee.

In practice, if exceptions are disabled, all functions are effectively noexcept.  I'm wondering whether it makes sense for the compiler to mark the type traits of all functions as noexcept in that case.  Then we wouldn't need people to do this manually.  We might also get other wins for free, if anyone is checking the noexcept state of anything besides move constructors and doing some kind of performance-affecting change based on the results.

This won't work if there is some semantic reason why "never throws exceptions, and thus marked as noexcept" and "can't throw exceptions due to exception support being disabled" need to be distinct for some function.  Does such a reason exist?

PK

Chris Blume

unread,
Jan 31, 2018, 1:36:45 AM1/31/18
to Peter Kasting, cxx
The compiler cannot automatically mark everything as noexcept for two reasons (although maybe only one is possible inside Chromium):
  • if it did so, querying whether a function is noexcept could result in different behavior
  • a noexcept function can still call a potentially throwing function (EG call into a static library which does throw)
But that's for "automatically". IIUC, it would be possible for a compiler flag to do this.

I don't think that compiler flag exists yet. I also don't know if it is likely to exist.


Chris Blume |
 Software Engineer | cbl...@google.com | +1-614-929-9221


--
You received this message because you are subscribed to the Google Groups "cxx" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cxx+uns...@chromium.org.
To post to this group, send email to c...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/cxx/CAAHOzFBVjGEnMZmz_jPNfJc_yVUGYF1hphM8QaC4r3fJ4ypk1g%40mail.gmail.com.

Peter Kasting

unread,
Jan 31, 2018, 2:28:39 AM1/31/18
to Chris Blume, cxx
I feel strangely like I may have misinterpreted every single sentence below, since it seems as if we're talking past each other.  Feel free to clarify if I did.

On Tue, Jan 30, 2018 at 10:36 PM, Chris Blume <cbl...@google.com> wrote:
The compiler cannot automatically mark everything as noexcept for two reasons (although maybe only one is possible inside Chromium):
  • if it did so, querying whether a function is noexcept could result in different behavior
That's the entire reason to do this: so that when queried, all functions will be treated as if they were explicitly marked noexcept.
  • a noexcept function can still call a potentially throwing function (EG call into a static library which does throw)
My mental model is that if exceptions are disabled, nothing can throw; if library code throws, it crashes the program.  That's why this proposal could even make sense.
 
But that's for "automatically". IIUC, it would be possible for a compiler flag to do this.

By "automatically" I mean "when the toolchain is set to build with exception support disabled" (which is flag-controlled).

PK

Sylvain Defresne

unread,
Jan 31, 2018, 8:05:05 AM1/31/18
to Peter Kasting, Chris Blume, cxx
On Wed, Jan 31, 2018 at 8:28 AM, 'Peter Kasting' via cxx <c...@chromium.org> wrote:
I feel strangely like I may have misinterpreted every single sentence below, since it seems as if we're talking past each other.  Feel free to clarify if I did.

On Tue, Jan 30, 2018 at 10:36 PM, Chris Blume <cbl...@google.com> wrote:
The compiler cannot automatically mark everything as noexcept for two reasons (although maybe only one is possible inside Chromium):
  • if it did so, querying whether a function is noexcept could result in different behavior
That's the entire reason to do this: so that when queried, all functions will be treated as if they were explicitly marked noexcept.

I think nothing prevents you from compiling some file with exceptions enabled and other with exceptions disabled and link them together. That is "exception enabled" is a property of a translation unit, not of a binary. 
  • a noexcept function can still call a potentially throwing function (EG call into a static library which does throw)
My mental model is that if exceptions are disabled, nothing can throw; if library code throws, it crashes the program.  That's why this proposal could even make sense.
 

I think that if exception are disabled for an object file, then any code in that object file will lack the necessary code to cleanly unwind if an exception is raised, but some other code higher in the callstack may catch them.
 
But that's for "automatically". IIUC, it would be possible for a compiler flag to do this.

By "automatically" I mean "when the toolchain is set to build with exception support disabled" (which is flag-controlled).

PK

--
You received this message because you are subscribed to the Google Groups "cxx" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cxx+uns...@chromium.org.
To post to this group, send email to c...@chromium.org.

Primiano Tucci

unread,
Jan 31, 2018, 10:31:41 AM1/31/18
to Peter Kasting, Chris Blume, cxx
On Wed, Jan 31, 2018 at 7:28 AM 'Peter Kasting' via cxx <c...@chromium.org> wrote:
I feel strangely like I may have misinterpreted every single sentence below, since it seems as if we're talking past each other.  Feel free to clarify if I did.

On Tue, Jan 30, 2018 at 10:36 PM, Chris Blume <cbl...@google.com> wrote:
The compiler cannot automatically mark everything as noexcept for two reasons (although maybe only one is possible inside Chromium):
  • if it did so, querying whether a function is noexcept could result in different behavior
That's the entire reason to do this: so that when queried, all functions will be treated as if they were explicitly marked noexcept.
  • a noexcept function can still call a potentially throwing function (EG call into a static library which does throw)
My mental model is that if exceptions are disabled, nothing can throw; if library code throws, it crashes the program.  That's why this proposal could even make sense.

Bunch of things that come to my mind:

1) libc++ and libunwind are built with exceptions enabled (I think just because they don't build otherwise). By forcing every function to be noexcept you might break paths libc++ -> code -> libc++.
One case I'm thinking about is operator new(std::nothrow), which does
operator new(size_t size, const std::nothrow_t&) _NOEXCEPT
{
    void* p = 0;
    try
    {
        p = ::operator new(size);
    }
    catch (...)
    {
    }
    return p;
}

So if you have a constructor (outside of libc++) that invokes a libc++ function that throws, I *think* that by forcefully making such ctor noexecpt you might change the semantic of new(noexcept).
Concretely we don't use new(std::noexcept) but some third_party libraries seem to do that. 
Not sure if this would be the only case?

2) A bunch of tools/test code build with exceptions (angle, gpu's decpp) so we should be careful somehow to not break them

3) It seems that there is one production library that currently builds with exceptions: unrar used by chrome/services/file_util/ 

 
But that's for "automatically". IIUC, it would be possible for a compiler flag to do this.

By "automatically" I mean "when the toolchain is set to build with exception support disabled" (which is flag-controlled).

PK

--
You received this message because you are subscribed to the Google Groups "cxx" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cxx+uns...@chromium.org.
To post to this group, send email to c...@chromium.org.

Chris Blume

unread,
Jan 31, 2018, 11:34:03 AM1/31/18
to Primiano Tucci, Peter Kasting, cxx
Sorry if it seemed like I was talking past you.

I understand the intent is to enable noexcept by default. The -fno-exceptions flag doesn't work exactly the way it would be easy to imagine. It doesn't prevent exceptions from existing. It prevents you from throwing. The code still needs to add unwinding semantics and unspecified exception handlers. I was saying a second flag might be needed for what you want.

Additionally, I can't recall if this is part of the standard as of C++11 but in old C++ an exception not in the exception specifier list actually had to be caught. Technically, it wouldn't crash. It would call std::terminate (which called std::abort and effectively crash). So as strange as it seems, adding noexcept to a function likely mimics that behavior -- it likely adds unhandled exception catching.


Typically, what we want is to not have exception semantics added at all (default noexcept(false)). And we want to check if some code won't throw (noexcept(true)). Those two goals conflict, as-is.


Chris Blume |
 Software Engineer | cbl...@google.com | +1-614-929-9221


Peter Kasting

unread,
Jan 31, 2018, 4:42:25 PM1/31/18
to Chris Blume, Primiano Tucci, cxx
On Wed, Jan 31, 2018 at 8:33 AM, Chris Blume <cbl...@google.com> wrote:
I understand the intent is to enable noexcept by default.

Well, not exactly.  My intent was for the compiler to report that all functions (that were compiled without exception support) are noexcept if they're queried via type traits, which is not precisely the same as making them noexcept for real.
 
The -fno-exceptions flag doesn't work exactly the way it would be easy to imagine. It doesn't prevent exceptions from existing. It prevents you from throwing. The code still needs to add unwinding semantics and unspecified exception handlers. I was saying a second flag might be needed for what you want.

Yes, if -fno-exceptions only means "never throw", and we want true "exceptions don't exist" behavior, then we'd need a different flag.  I'm not proposing going that far.

Additionally, I can't recall if this is part of the standard as of C++11 but in old C++ an exception not in the exception specifier list actually had to be caught. Technically, it wouldn't crash. It would call std::terminate (which called std::abort and effectively crash). So as strange as it seems, adding noexcept to a function likely mimics that behavior -- it likely adds unhandled exception catching.

I wouldn't know without looking it up.  Luckily, this is precisely the kind of case where the distinction between what I was suggesting and what you were understanding is relevant :)

PK

Peter Kasting

unread,
Jan 31, 2018, 4:51:27 PM1/31/18
to Primiano Tucci, Chris Blume, cxx
On Wed, Jan 31, 2018 at 7:31 AM, Primiano Tucci <prim...@chromium.org> wrote:
On Wed, Jan 31, 2018 at 7:28 AM 'Peter Kasting' via cxx <c...@chromium.org> wrote:
My mental model is that if exceptions are disabled, nothing can throw; if library code throws, it crashes the program.  That's why this proposal could even make sense.

BTW, I thank this and other replies for clarifying this model :)

Bunch of things that come to my mind:

1) libc++ and libunwind are built with exceptions enabled (I think just because they don't build otherwise). By forcing every function to be noexcept you might break paths libc++ -> code -> libc++.
One case I'm thinking about is operator new(std::nothrow), which does
operator new(size_t size, const std::nothrow_t&) _NOEXCEPT
{
    void* p = 0;
    try
    {
        p = ::operator new(size);
    }
    catch (...)
    {
    }
    return p;
}

So if you have a constructor (outside of libc++) that invokes a libc++ function that throws, I *think* that by forcefully making such ctor noexecpt you might change the semantic of new(noexcept).

This sounds like it gets at Chris Blume's comments.

I think this is a case where you'd change the behavior if you actually made the function noexcept, but not if you merely make the function reported-as-noexcept, assuming that the difference is that the former gets automatically instrumented with code to catch-and-terminate, but the latter merely affects the traits; since in this case, new() is not querying the traits, so there's no functional difference from today.

I'm requesting that we _report_ (via type traits) that all functions are noexcept, not that we actually add catch-and-terminate code to them.

I think what this effectively means is that when compiling with -fno-exceptions, the nothrow operator should always return true.
 
2) A bunch of tools/test code build with exceptions (angle, gpu's decpp) so we should be careful somehow to not break them

Presumably we build them with exception support enabled and would continue to do so, so we wouldn't risk breakage.

3) It seems that there is one production library that currently builds with exceptions: unrar used by chrome/services/file_util/

Same.

PK

Chris Blume

unread,
Jan 31, 2018, 5:07:28 PM1/31/18
to Peter Kasting, Primiano Tucci, cxx
Oh interesting.

If the function itself isn't marked noexcept, it doesn't build that uncaught exception handler.
And if the noexcept() operator always returned true, we can take the more optimal code path.

Good idea. That solves the thing I mentioned was two conflicting goals.




If we built all the code with -fno-exceptions and knew that absolutely nowhere used 'throw', there wouldn't be any change in behavior.
The compiler typically cannot know the other TUs were compiled with -fno-excpetions but I could imagine a flag to indicate "I promise no other TUs throw" or some metadata passed to the linker to let the linker know it can remove the exception handlers.

This would be the actual "no exceptions" people expect. And then you also get the noexcept() operator.


Chris Blume |
 Software Engineer | cbl...@google.com | +1-614-929-9221


Peter Kasting

unread,
Jan 31, 2018, 5:15:12 PM1/31/18
to Chris Blume, Primiano Tucci, cxx
On Wed, Jan 31, 2018 at 2:07 PM, Chris Blume <cbl...@google.com> wrote:
If we built all the code with -fno-exceptions and knew that absolutely nowhere used 'throw', there wouldn't be any change in behavior.
The compiler typically cannot know the other TUs were compiled with -fno-excpetions but I could imagine a flag to indicate "I promise no other TUs throw" or some metadata passed to the linker to let the linker know it can remove the exception handlers.

Well, I'm not proposing anything that goes so far as removing exception handlers at the linker level.  Presumably, TUs built with -fno-exceptions won't have them, and other TUs were intended to be built with exception support and shouldn't have them removed.

I just want to change what the noexcept operator does, and I think the proposed change is really just "the noexcept() operator always returns true in TUs built with -fno-exceptions".

I didn't realize that was what I was proposing initially or I would have said it that way :)

PK

Chris Blume

unread,
Jan 31, 2018, 5:30:31 PM1/31/18
to Peter Kasting, Primiano Tucci, cxx
If a function in TU 1 is noexcept and -fno-exceptions, it has an unhandled exception handler.
This is because the standard forced it to, with the std::terminate call (note 5).

The only way this function in TU 1 can remove the unhandled exception handler is if it either 1.) isn't marked noexcept, 2.) knows it doesn't call out to any other TU's code, or 3.) knows all the TUs it calls into are also either noexcept or -fno-exceptions and recurse down (the TUs those call into don't either).

The point of this is you can specify your own function to get called instead when std::terminate is called.


I do like the idea of your reworded proposal. It would probably need to go deeper than just the given TU though.
TU1 -fno-exceptions has a function which calls into TU2 which enabled exceptions.
Even though TU1 itself cannot throw, an exception could still come from TU2.
If we forced an optimal code path (say vector's resizing) which assumed exceptions aren't thrown and then an exception is thrown, we defeat the whole purpose of having the optimized path. We assumed the strong exception guarantee and didn't get it.
What we really need isn't "this TU itself cannot throw" but rather "no exception ever enters this TU".


Chris Blume |
 Software Engineer | cbl...@google.com | +1-614-929-9221


Peter Kasting

unread,
Jan 31, 2018, 5:37:00 PM1/31/18
to Chris Blume, Primiano Tucci, cxx
On Wed, Jan 31, 2018 at 2:30 PM, Chris Blume <cbl...@google.com> wrote:
If a function in TU 1 is noexcept and -fno-exceptions, it has an unhandled exception handler.
This is because the standard forced it to, with the std::terminate call (note 5).

The only way this function in TU 1 can remove the unhandled exception handler is if it either 1.) isn't marked noexcept, 2.) knows it doesn't call out to any other TU's code, or 3.) knows all the TUs it calls into are also either noexcept or -fno-exceptions and recurse down (the TUs those call into don't either).

The point of this is you can specify your own function to get called instead when std::terminate is called.

Yes, I think this is all reinforcing your earlier point that we don't want to actually make these functions noexcept, because then we have to have such handlers.
 
I do like the idea of your reworded proposal. It would probably need to go deeper than just the given TU though.
TU1 -fno-exceptions has a function which calls into TU2 which enabled exceptions.
Even though TU1 itself cannot throw, an exception could still come from TU2.
If we forced an optimal code path (say vector's resizing) which assumed exceptions aren't thrown and then an exception is thrown, we defeat the whole purpose of having the optimized path. We assumed the strong exception guarantee and didn't get it.

My claim is that when you're building with -fno-exceptions, that translation unit doesn't care about either the weak or the strong exception guarantee.  Within that TU, we should intentionally violate them to generate more optimal code.

The "whole purpose of having an optimized path" is to provide the strong guarantee for callers that care.  But -fno-exceptions means that you don't care what happens and you expect exceptions to be handled poorly, crash your program, etc.

PK

Peter Kasting

unread,
Jan 31, 2018, 6:54:49 PM1/31/18
to Chris Blume, Primiano Tucci, cxx
It strikes me that perhaps I'm still being unclear with "the noexcept() operator".

In a declaration like:

void f() noexcept(noexcept(g()));

I'm proposing to change the functionality of the inner "noexcept", not the outer one.  That is, the noexcept specifier still works as it has before, and we don't pretend there's an invisible "noexcept" specifier on all functions.

But if someone calls the noexcept operator to query whether the supplied expression throws, the operator always returns the value "true".

PK

dontke...@gmail.com

unread,
Feb 12, 2018, 5:11:34 AM2/12/18
to cxx
Originally
Reply all
Reply to author
Forward
0 new messages