David Chisnall via llvm-dev
2017-Feb-09 17:12 UTC
[llvm-dev] help me understand how nounwind attribute on functions works?
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> wrote: > 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. David
James Y Knight via llvm-dev
2017-Feb-09 17:40 UTC
[llvm-dev] help me understand how nounwind attribute on functions works?
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> > wrote: > > 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. > > David > > _______________________________________________ > LLVM Developers mailing list > llvm-dev at lists.llvm.org > http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20170209/06ff4d36/attachment.html>
Reid Kleckner via llvm-dev
2017-Feb-09 18:32 UTC
[llvm-dev] help me understand how nounwind attribute on functions works?
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> >> wrote: >> > 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. >> >> David >> >> _______________________________________________ >> LLVM Developers mailing list >> llvm-dev at lists.llvm.org >> http://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-dev >> > >-------------- next part -------------- An HTML attachment was scrubbed... URL: <http://lists.llvm.org/pipermail/llvm-dev/attachments/20170209/17fa5211/attachment.html>