Logan Chien
(This is the follow up of the threads: [LLVMdev] Unwind behaviour in Clang/LLVM)
I feel that there are two problems with the existing infrastructure:
nounwind
attribute is ambiguous
for (1) not throwing exceptions and (2) not performing stack unwinding. I feel
that it will be better to separate this in two different attributes.uwtable
and nounwind
.
Although, I think it fine to keep the current definition of
nounwind
, however, the
uwtable
attribute will be much useless
to its user. Besides, even if we wish to keep the current definition, to avoid
the undefined behavior, we have to slightly change the code generator to emit
[can't unwind]
whenever there is a
nounwind
attribute.I am writing my thoughts below, please have a look, and feel free to challenge or send me the feedback. Thanks.
tl;dr
To make my argument clear, I would like to use a different notation from LLVM IR:
no-throw
attribute guarantees that
the function will not throw any exception object.no-unwind
attribute guarantees
that the function won't read the prior calling sequence with the stack
unwinder.For simplification, if the function is not tagged with
no-throw
then it is may-throw. Similarly, if the
function is not tagged with no-unwind
, then it is
may-unwind.
ehtable
attribute guarantees that
the exception handling table, such as the LSDA handler data and (by
implementation) the stack unwinding information for exception, will be
generated for exception handling. If the table includes stack unwinding
infomration, then it is guaranteed that the unwinding information is sufficient
enough to go back to previous propagation barrier (landingpad, cleanup filter,
or can't unwind.)uwtable
attribute guarantees that
the unwind table will be generated for stack unwinding for profilers,
debuggers, (possibly) exception handling mechansim and etc. This attribute
guarantees that the complete stack trace can be obtained as long as the calling
sequence does not contain the external function or the function marked with
no-unwind
.It is possible for some exception handling implemenation requires uwtable
to unwind the stack, it is not necessary to do
so. On the other hand, even if the stack unwind information for exception
handling is encoded in ehtable
, it might be
insufficient to implement the stack unwinder. We will come back with this
later.
If a function has both no-uwnind
and
uwtable
, then there must be a mechanism
(might be an agreement between the compiler and the run-time library) to
signal the stack unwinder to stop before passing through the function.
Otherwise, the undefined behavior might be happened. Similarly, we
need a similar mechanism for the function with both
no-throw
and
ehtable
With these notations, we can do some simple reasoning:
no-unwind
implies
no-throw
— The contraposition to the previous
statement.Please notice that no-throw
does not imply
no-unwind
. We will come back with this later.
In this section, I would like to discuss the properties of
no-unwind
and no-throw
,
and the rules to infer the attributes if the programmer didn't specified them
in function definition. These properties may be used by some optimization
passes, such as PruneEH
.
First, since no-unwind
implies no-throw
, we can add no-throw
attribute to the functions which have no-unwind
attribute.
Second, it is clear that the external functions should be considered as
may-throw unless it is explicitly tagged with
no-throw
. Similarly, the external functions should
be may-unwind unless it is explicitly tagged with
no-unwind
.
For function definition, we can inspect the instructions:
call
or
invoke
instruction to a may-unwind callee function and
-fasynchronous-unwind-table
compiler option isn't given, then we
can add the no-unwind
attribute to the
function.call
or
invoke
instruction to a may-throw callee function, then
we can add the no-throw
attribute to the
function (safe approximation.)no-throw
to the
function attribute.Please notice that we have to deliberately separate the attribute
into no-throw
and
no-unwind
because the landingpad
instruction can't give any guarantee on no-unwind
attribute.
There are two function attributes related with unwinding and exception handling in the existing LLVM infrastructure. Here are the descriptions copied from the LLVM reference manual:
nounwind
— This function
attribute indicates that the function never returns with an unwind or
exceptional control flow. If the function does unwind, its runtime behavior is
undefined.uwtable
— This attribute
indicates that the ABI being targeted requires that an unwind table entry be
produce for this function even if we can show that no exceptions passes by it.
This is normally the case for the ELF x86-64 abi, but it can be disabled for
some compilation units.From my interpretation, the specification for
nounwind
guarantees that the function will
neither throw an exception nor unwind the stack, i.e.
nounwind
= no-throw
+
no-unwind
. The specification for
uwtable
guarantees some unwind table
will be generated; however, it does not specify which kind of unwind table
should be generated. IIRC, the ARM backend actually implements
ehtable
, which has only limited capability to
unwind the stack.
The things are getting tricky when it comes to the
implementation. There is a PruneEH
pass, which will
try to remove the unnecessary exception handling informantion. For example,
the following code:
define void @foo() { entry: ret void }
will be converted to:
define void @foo() nounwind { entry: ret void }
Here's a much more complex example:
define void @foo() { declare void @_Z3barv() declare i32 @__gxx_personality_v0(...) define void @_Z3foov() { entry: invoke void @_Z3barv() to label %try.cont unwind label %lpad lpad: %0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) catch i8* null ret void try.cont: ret void }
is converted to
declare void @_Z3barv() declare i32 @__gxx_personality_v0(...) ; Function Attrs: nounwind define void @_Z3foov() #0 { entry: invoke void @_Z3barv() to label %try.cont unwind label %lpad lpad: %0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gxx_personality_v0 to i8*) catch i8* null ret void try.cont: ret void } attributes #0 = { nounwind }
Some careful reader might have noticed the problem here. The
nounwind
attribute is added to
@_Z3foov()
simply because the landingpad
can
catch the exception object. However, in my notation, only
no-throw
can be added to this function, and
no-throw
does not imply
no-unwind
. It is possible for
@_Z3foov()
to unwind the stack. For example, the function
@_Z3barv()
may call _Unwind_Backtrace()
to get the
backtrace.
Besides, some optimization might make the situation even worse.
AFAIK, most of the stack unwinding implementations rely on the value in
the link register or the return address on the stack.
However, there is an optimization in LLVM code generation which will not
save the link register if the callee function has
noreturn
attribute [1].
Even though the optimization will only be applied when the callee function has
nounwind
attribute as well, the problem still
occurs because PruneEH
will (incorrectly) add nounwind
to some function which actually unwinds.
Besides, I am in doubt about whether we can apply this optimization when the
caller function has the uwtable
attribute and
requires the unwind table.
The mixture of uwtable
and nounwind
will cause another problem.
IMO, it is incorrect to decide whether to emit
[can't unwind]
with !needsUnwindTableEntry()
[2]. The needsUnwindTableEntry()
is
defined as either the function has uwtable
attribute or the function does not have nounwind
.
Thus, !needsUnwindTableEntry()
imples not having uwtable
attribute and having nounwind
. As the result, the [can't unwind]
won't be generated for the following function:
define void @foo() uwtable nounwind { entry: call void @bar() ret void }
The stack unwinder might continue to unwind the stack because there isn't any mark in the unwind table to stop the stack unwinding. And, unfortunately, according to the LLVM reference manual, this will result in undefined behavior. In fact, I did encounter some real example [3] which will fall into an infinite loop during the phase 1 unwinding.
In conclusion, I would like to suggest that we need to put more efforts to define a precise specification for exception handling and stack unwinding mechanism, so that the optimization passes and the run-time environment can interact with each other without problems.
In summary, IMO, these are the topic have to be discussed:
nounwind
into no-throw
and
no-unwind
?uwtable
in the
LLVM reference manual?uwtable
is used with nounwind
If we have some decision, I am willing to write the patch. :-)