[llvm-dev] help me understand how nounwind attribute on functions works?
Reid Kleckner via llvm-dev
llvm-dev at lists.llvm.org
Thu Feb 9 10:32:26 PST 2017
I'll defend the LLVM IR representation that was chosen for noexcept. The
design of LLVM's EH constructs is all about representing EH metadata as
real instructions with real semantics that normal program transformations
can analyze. LLVM doesn't have any EH side tables, it's all part of the IR.
This is a huge design win.
This includes terminating after noexcept. It's a very literal
implementation: if an exception is thrown from any call site in a noexcept
function, then we call terminate. This is easy to analyze, and *most*
importantly, easy to inline. We don't need to reason about wacky function
attributes conjuring up new calls to target-specific runtime functions
(i.e. what is std::terminate called on your platform) from the middle-end.
If we represented noexcept as data or a function attribute, inlining would
not be a simple matter of chaining return to the invoke/call successor and
resume to the invoke unwind destination.
For Windows EH, we had to walk some things back to turn some things that
were code back into data, and by and large that has been a Bad Thing for
the middle end. It likes code. It's easy for GVN to reason about
llvm.eh.typeid.for and eliminate duplicate catch clauses. We miss some of
these optimizations on Windows as a result today.
Considering all that, I really think that the committee made a mistake to
make throwing through noexcept terminate the program. Making it UB would
have been fine. We could have done as you suggest, where throwing from a
non-inlined nounwind function terminates the program as a QoI matter
without pessimizing inlining of noexcept functions.
The world as it is today kind of sucks. I would say that we should just
pattern match away our calls to std::terminate in the backend and emit the
more compact tables, but that is actually a behavior change. It will cause
cleanups between the thrown exception and the noexcept function to stop
running. Changing that behavior would require an opt-out mechanism. That's
not a big deal, but what it really requires is someone who cares. Maybe you
can be that person. :)
On the subject of making LLVM EH more efficient, we should restart Amaury's
project to move all code only reachable from landingpads to .text.cold. It
would require emitting more .eh_frame tables for the out-of-line cold code,
but I think it would be a nice win.
On Thu, Feb 9, 2017 at 9:40 AM, James Y Knight <jyknight at google.com> wrote:
> It is my belief that all functions compiled with -fno-exceptions (which is
> the default for C code, but notably not the ONLY option) *ought to* (for
> QoI reasons, not a standards requirement) get the equivalent of noexcept
> behavior. That is: guaranteed to abort if an exception is thrown through it.
> This doesn't happen now on x86-64, due to the default async unwind tables
> as you mention. That used to be the case on x86-32...although it seems that
> async unwind tables are now on by default there too, in some cases.
> Unfortunately, clang implements noexcept very inefficiently, and so doing
> that in clang right now would have a huge amount of overhead.
> Clang implements C++ noexcept by inserting explicit catch code, which then
> calls terminate. GCC, on the other hand, just sets up the exception table
> appropriately to make the unwinder itself do that. It would be really good
> to fix that, but it's my understanding that it'd be somewhat difficult in
> llvm's current exceptions model.
> On Thu, Feb 9, 2017 at 12:12 PM, David Chisnall via llvm-dev <
> llvm-dev at lists.llvm.org> wrote:
>> On 9 Feb 2017, at 08:41, Reid Kleckner via llvm-dev <
>> llvm-dev at lists.llvm.org> wrote:
>> > On Wed, Feb 8, 2017 at 5:45 PM, Mehdi Amini <mehdi.amini at apple.com>
>> > What isn’t clear to me still is : why shouldn't this be transitive?
>> > In the example you’re showing, for a caller of f() in bar, what is the
>> advantage of knowing that f() is nounwind if it an exception can still be
>> thrown? What does it allow?
>> > We know an exception cannot unwind out of f. An exception can be thrown
>> inside something that f calls, but it must be caught before it unwinds
>> beyond f.
>> Why? With the x86-64 ABI, for example, f is required to emit async
>> unwind tables. It has a personality function that knows how to handle
>> cleanups, but there are none here, so the generic unwinder will happily
>> unwind through the code. The only ways to prevent exceptions from being
>> propagated are to explicitly catch them, or to use something like a C++
>> exception specifier so that the personality function will explicitly block
>> exception propagation.
>> In this example, given that doThing is not marked as noexcept, we will
>> emit unwind tables that explicitly allow unwinding through f; however, when
>> the unwinder tries to unwind into the next stack frame it will find that
>> there is no unwind record. Depending on how lazy we are in defining the PC
>> ranges in the unwind table, this will either cause stack corruption or a
>> run-time abort from well-formed code.
>> LLVM Developers mailing list
>> llvm-dev at lists.llvm.org
-------------- next part --------------
An HTML attachment was scrubbed...
More information about the llvm-dev