Open Quuxplusone opened 12 years ago
About the "there's a catch-all so the cleanup is redundant", check out
RecognizePersonality and isCatchAll in InstructionCombining.cpp. They
seem to expect __objc_personality_v0 not __gnu_objc_personality_v0.
As for the rest of the bug report, at a glance the IR is wrong since it doesn't
conform to the http://llvm.org/docs/ExceptionHandling.html#restrictions, namely
this bit:
"In order for inlining to behave correctly, landing pads must be prepared to
handle selector results that they did not originally advertise. Suppose that a
function catches exceptions of type A, and it’s inlined into a function that
catches exceptions of type B. The inliner will update the landingpad
instruction for the inlined landing pad to include the fact that B is also
caught. If that landing pad assumes that it will only be entered to catch an A,
it’s in for a rude awakening. Consequently, landing pads must test for the
selector results they understand and then resume exception propagation with the
resume instruction if none of the conditions match."
This should not be required for something that is a catchall - by definition,
it catches every kind of exception and should not resume. I have a slightly
more worrying example of error here:
define i32 @finally() noreturn uwtable {
entry:
%0 = load i8** @except, align 8, !tbaa !1
invoke void @objc_exception_throw(i8* %0) noreturn
to label %invoke.cont unwind label %lpad
invoke.cont: ; preds = %entry
unreachable
lpad: ; preds = %entry
%1 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gnu_objc_personality_v0 to i8*)
cleanup
tail call void @runCleanup(i8* undef)
resume { i8*, i32 } %1
}
declare void @objc_exception_throw(i8*)
declare i32 @__gnu_objc_personality_v0(...)
define i32 @main() nounwind uwtable {
entry:
%call = invoke i32 @finally()
to label %return unwind label %lpad
lpad: ; preds = %entry
%0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gnu_objc_personality_v0 to i8*)
catch i8* null
br label %return
return: ; preds = %entry, %lpad
ret i32 0
}
The inner function has a cleanup, the outer function a catchall. The correct
behaviour for this program is to exit with a status 0. After optimisation, we
get:
define i32 @main() nounwind uwtable {
entry:
%0 = load i8** @except, align 8, !tbaa !1
invoke void @objc_exception_throw(i8* %0) noreturn
to label %invoke.cont.i unwind label %lpad.i
invoke.cont.i: ; preds = %entry
unreachable
lpad.i: ; preds = %entry
%1 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gnu_objc_personality_v0 to i8*)
cleanup
store i32 1, i32* @cleanupRun, align 4, !tbaa !0
resume { i8*, i32 } %1
}
The cleanup landing pad has been moved to the outer function, and the catchall
is gone completely. This program now aborts at run time because the unwinder
finds only cleanups before falling off the end of the stack.
"This should not be required for something that is a catchall - by definition,
it catches every kind of exception and should not resume". You misunderstood
what the docs are saying. The docs say that the catch-all handler, like every
other handler, should check that the selector value was "catch-all" before
running the catch-all code. It's not about stuff that happens after the catch
all, it's about stuff that may happen before it after inlining, eg the cleanup
running. If you modify the IR in this way, does it then work?
I am not 100% sure what the correct behaviour should be. It's a catchall, so
it should never be resuming even after inlining. Even if it is merged with
other handlers, then the catchall code should run unconditionally. I tried
modifying it like this:
define i32 @main() nounwind uwtable {
entry:
%call = invoke i32 @finally()
to label %return unwind label %lpad
lpad: ; preds = %entry
%0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gnu_objc_personality_v0 to i8*)
catch i8* null
%1 = extractvalue { i8*, i32 } %0, 1
%2 = icmp eq i32 0, %1
br i1 %2, label %return, label %rethrow
return: ; preds = %entry, %lpad
ret i32 0
rethrow:
resume { i8*, i32 } %0
}
This code is now wrong, because the catchall is a barrier beyond which no
exceptions should propagate, however even with this modification, the same
incorrect results occur during optimisation. It is transformed into the
following, which unconditionally propagates exceptions:
define i32 @main() nounwind uwtable {
entry:
%0 = load i8** @except, align 8, !tbaa !1
invoke void @objc_exception_throw(i8* %0) noreturn
to label %invoke.cont.i unwind label %lpad.i
invoke.cont.i: ; preds = %entry
unreachable
lpad.i: ; preds = %entry
%1 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gnu_objc_personality_v0 to i8*)
cleanup
store i32 1, i32* @cleanupRun, align 4, !tbaa !0
resume { i8*, i32 } %1
}
I meant something like this:
Original:
define i32 @main() nounwind uwtable {
entry:
%call = invoke i32 @finally()
to label %return unwind label %lpad
lpad: ; preds = %entry
%0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__gnu_objc_personality_v0 to i8*)
catch i8* null
br label %return
return: ; preds = %entry, %lpad
ret i32 0
}
Modified:
define i32 @main() nounwind uwtable {
entry:
%call = invoke i32 @finally()
to label %return unwind label %lpad
lpad: ; preds = %entry
%0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)*
@__gnu_objc_personality_v0 to i8*)
catch i8* null
%selector = extractvalue { i8*, i32 } %0, 1
%typeid = call i32 @llvm.eh.typeid.for(i8* 0)
%is_catch_all = icmp eq i32 %selector, %typeid
br %is_catch_all, label %return, label %resume
resume:
resume { i8*, i32 } %0
return: ; preds = %entry, %lpad
ret i32 0
}
Modified like this:
define i32 @main() nounwind uwtable {
entry:
%call = invoke i32 @finally()
to label %return unwind label %lpad
lpad: ; preds = %entry
%0 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gnu_objc_personality_v0 to i8*)
catch i8* null
%selector = extractvalue { i8*, i32 } %0, 1
%typeid = call i32 @llvm.eh.typeid.for(i8* null)
%is_catch_all = icmp eq i32 %selector, %typeid
br i1 %is_catch_all, label %return, label %rethrow
return: ; preds = %entry, %lpad
ret i32 0
rethrow:
resume { i8*, i32 } %0
}
THe result is still that the optimiser removes the ret, and makes the catchall-
>stop into a cleanup->resume:
define i32 @main() nounwind uwtable {
entry:
%0 = load i8** @except, align 8, !tbaa !1
invoke void @objc_exception_throw(i8* %0) noreturn
to label %invoke.cont.i unwind label %lpad.i
invoke.cont.i: ; preds = %entry
unreachable
lpad.i: ; preds = %entry
%1 = landingpad { i8*, i32 } personality i8* bitcast (i32 (...)* @__gnu_objc_personality_v0 to i8*)
cleanup
store i32 1, i32* @cleanupRun, align 4, !tbaa !0
resume { i8*, i32 } %1
}
Can you please attach the complete (modified) IR.
Attached e.ll
(4559 bytes, application/octet-stream): IR that is incorrectly inlined
Something seems to be horribly broken in the inliner...
Attached lp.ll
(3644 bytes, text/plain): Comprehensive testcase for what the inliner is supposed to do
The later inlinings look like bugs, but the first inlining actually looks
totally reasonable to me.
David, I'm worried about the line "This call is only valid if we landed with
the catchall, but because a cleanup will always be matched first we actually
landed with the cleanup." This sounds like a bug in the personality function.
The personality function is required to not merely decide whether to land, but
to correctly decide what the landing pad's action should be; landing with a
selector value of 0 is a statement that it's landing there *only* to run
cleanups, which is obviously not the case.
Now, arguably the backend should make a point of not saying that there are
cleanups when there's a catchall handler in effect, but that doesn't really fix
the personality bug, because the personality would still be returning the wrong
answer for a non-total catch.
Hi John,
(In reply to comment #11)
> David, I'm worried about the line "This call is only valid if we landed with
> the catchall, but because a cleanup will always be matched first we actually
> landed with the cleanup." This sounds like a bug in the personality function.
> The personality function is required to not merely decide whether to land, but
> to correctly decide what the landing pad's action should be; landing with a
> selector value of 0 is a statement that it's landing there *only* to run
> cleanups, which is obviously not the case.
It's a question of ordering. Personality functions will jump to the first
matching selector. It is up to the compiler to ensure that the selectors are
ordered from most specific to most general (unless they were ordered in a
different way in the source language). This means that a catchall and a
cleanup should always be ordered with the catchall first in the case where both
are present. Doing otherwise would result in a significant performance hit (I
speak here as someone who has implemented the personality functions for three
languages, including C++) and be unacceptable. Indeed - the ABI spec clearly
says that the first match should be taken, because a more general match may
follow, e.g. an NSString* followed by an NSObject* followed by an id catch in
Objective-C.
This inlining places the cleanup before the catchall, and so results in the
block being entered via the cleanup. The personality function then enters the
first matching rule: the cleanup.
Now, I could implement work-arounds for the LLVM behaviour in the personality
functions that I maintain, but that wouldn't make us work with personality
functions that don't implement this hack, and would make all LLVM-compatible
personality functions slower than their LLVM-incompatible counterparts.
(In reply to comment #12)
> It's a question of ordering. Personality functions will jump to the first
> matching selector. It is up to the compiler to ensure that the selectors are
> ordered from most specific to most general (unless they were ordered in a
> different way in the source language). This means that a catchall and a
> cleanup should always be ordered with the catchall first in the case where
both
> are present. Doing otherwise would result in a significant performance hit (I
> speak here as someone who has implemented the personality functions for three
> languages, including C++) and be unacceptable.
You are basically claiming that GCC's LSDA layout requires cleanup actions to
always be the outermost action for a landing pad. I am quite skeptical; among
other things, this would make it impossible to share action-table entries
between the landing pads in code like this:
try {
foo();
std::string s; // needs a cleanup
bar();
} catch (E1 *e) {}
At the very least, I'd like to see some evidence from the GCC documentation
that this is intended.
I am also skeptical that it is a significant performance hit when interpreting
the actions table to simply remember that you've seen a cleanup action and
keep scanning. Among other things, interpreting another entry in the
actions table is quite fast compared to everything else the personality is
doing — e.g. finding the appropriate lpad in the first place.
> Indeed - the ABI spec clearly says that the first match should be taken,
> because a more general match may follow, e.g. an NSString* followed
> by an NSObject* followed by an id catch in Objective-C.
If you're talking about the Itanium ABI's suggested implementation,
(1) it's clearly talking about handlers and (2) it doesn't talk about GCC's
LSDA layout.
> This inlining places the cleanup before the catchall, and so results in the
> block being entered via the cleanup. The personality function then enters the
> first matching rule: the cleanup.
The IR has no concept of 'ordering' a cleanup w.r.t. handler clauses;
it's just a flag on the landingpad instruction. That's completely fine.
> Now, I could implement work-arounds for the LLVM behaviour in the personality
> functions that I maintain, but that wouldn't make us work with personality
> functions that don't implement this hack, and would make all LLVM-compatible
> personality functions slower than their LLVM-incompatible counterparts.
For what it's worth, what I said above about just flagging whether
you saw a cleanup is exactly what GCC's C++ personality function does.
I really don't think you're relying on specified behavior here.
e.ll
(4559 bytes, application/octet-stream)lp.ll
(3644 bytes, text/plain)