[LLVMdev] Does nounwind have semantics?
baldrick at free.fr
Mon Jul 22 01:37:36 PDT 2013
On 22/07/13 10:23, Andrew Trick wrote:
> On Jul 22, 2013, at 12:56 AM, Duncan Sands <baldrick at free.fr
> <mailto:baldrick at free.fr>> wrote:
>> my understanding is different. I'm pretty sure that what I'm about to say is
>> the traditional way these things have been viewed in LLVM. That doesn't mean
>> that it's the best way to view these things.
>>> - nounwind means no dwarf EH. Absence
>> I guess you mean presence.
>> of nounwind means absence of dwarf EH. It
>>> would be unwise for optimization passes to reason about the semantics beyond
>>> that. I was momentarily mislead by the LICM code that handles MayThrow specially.
>> nounwind has nothing to do with dwarf, since exceptions themselves need have
>> nothing to do with dwarf (which is just one way of implementing exception
>> handling). Don't forget setjmp/longjmp exception handling, and also exception
>> handling by returning an extra invisible parameter (which I mentioned in
>> another email) which IIRC was actually implemented by someone at the codegen
>> level at some point as it was faster than dwarf for code that throws exceptions
>> a lot. An additional point is that while in C++ you create an exception object,
>> not all languages associate an object with an exception, some just want to do
>> the equivalent of a non-local goto. Creating an exception object means
>> allocating memory, mucking around with global data structures and obviously
>> writing memory. A non-local goto doesn't have to do more than unwind the stack
>> until it gets to the right frame then do a jump. It's not clear to me that that
>> should be considered as writing memory.
>> Here's my take: a call to an function marked nounwind either never returns
>> (eg infinite loop or exits the program) or returns normally. It doesn't
>> "return" by unwinding the stack out of the caller. On the other hand a
>> function that is not marked nounwind may "return" by unwinding the stack;
>> control in this case doesn't continue in the caller, it continues at least one
>> further up the stack. Thus in this case the instructions after the call
>> instruction are not executed. Note I'm talking about an ordinary call here,
>> not an invoke. In the case of an invoke control may continue in the caller
>> function, but only at a well-defined point (the landing pad).
> Good explanation. Your definition of nounwind is completely logical. I would
> prefer not to rely on it though because
> - Realistically, the semantics won’t be well tested.
this is true. Those who need this will have to do careful testing and bug
fixing themselves. But to my mind this is not a reason to abandon these
semantics. A good reason to abandon these semantics is if they created trouble
for a large number of users, e.g. clang, for example by making it hard to do
important optimizations. Do they? Are these semantics actively harmful?
If not I'd prefer to keep them since they are fairly clean.
> - It doesn’t seem terribly important to treat nonlocal gotos as readonly (though
> maybe it is to you :)
It's not a problem for me, I just like to keep orthogonal things orthogonal.
Maybe that's a mistake and we should rename readonly to "nothing funky happens".
> - When it is important to optimize memory access around nonlocal gotos, I prefer
> to expose control flow to the optimizer explicitly.
> e.g. why not just use invokes for all your may-throw calls, then you’re free to
> mark them readonly?
Let's say that we are in function foo. We call bar. We were called by qaz.
I'm not talking about non-local gotos that jump from bar to somewhere in us
(foo). These indeed need to be modelled with invoke. I'm talking about those
that jump from bar to somewhere in qaz. That means that the place in qaz that
called us (foo) will need to use an invoke. But from the point of view of foo,
the call to bar never returns to foo, it just instantly leaps across foo all the
way up to qaz and execution continues there. So foo doesn't need to do anything
much, all the heavy lifting is in bar and in qaz. All foo has to do is keep in
mind that control may never get to the next instruction after the call to bar.
>>> - Things that throw exceptions or trap in defined ways are not readonly.
>> See above for why throwing an exception doesn't have to write memory. Dwarf
>> exception handling, and anything which can be used to implement C++ exception
>> handling, is clearly writing memory and thus cannot be used inside a readonly
>> function. So yes, any function Clang produces that throws an exception is not
>> going to be readonly. But as I mentioned above some languages have no
>> exception object and just unwind the stack. For these the expression "throwing
>> an exception" (which implicitly includes the idea that there is an exception
>> object) is not really appropriate; "unwinds the stack" is the basic concept
>> here. This is basically orthogonal to readonly.
>>> - Runtime checks for overflow, div-by-zero, bounds checks, etc. should be
>>> implemented at the IR level as branches to noreturn calls because it can be done
>>> that way and I haven’t seen concrete evidence that it’s too expensive. Don’t try
>>> to do something fancy with intrinsics and attributes unless absolutely required.
>> I agree with this.
More information about the llvm-dev