[LLVMdev] RFC: Exception Handling Proposal II
John McCall
rjmccall at apple.com
Sun Nov 28 15:47:33 PST 2010
On Nov 28, 2010, at 1:57 PM, Bill Wendling wrote:
> On Nov 28, 2010, at 2:59 AM, John McCall wrote:
>
>> On Nov 28, 2010, at 2:20 AM, Bill Wendling wrote:
>>
>>> On Nov 27, 2010, at 4:57 PM, John McCall wrote:
>>>
>>>> On Nov 25, 2010, at 3:03 AM, Duncan Sands wrote:
>>>>> I'm pointing out that if the invoke instruction
>>>>> is removed and catch information is attached to entire basic blocks, then if no
>>>>> care is taken then it is perfectly possible to use %x before it is defined as
>>>>> explained in my previous email, blowing up the entire LLVM system. Clearly the
>>>>> solution is to not allow this by not allowing values defined in a basic block
>>>>> to be used in a handler for that block;
>>>>
>>>> If we take this route — and I think we should, although I'd like to see region
>>>> chaining in first — I see two reasonable solutions. The first is what you've
>>>> said, that effectively there's an edge from the beginning of the block; the
>>>> second is a slight twist, that the edge leaves from the end of the phis. I
>>>> think the latter will greatly simplify every transformation which ever inserts
>>>> a phi, and in particular mem2reg. Since phis can't throw, it should be
>>>> equivalent anyway.
>>>
>>> Wouldn't this route make otherwise value programs invalid? For instance:
>>>
>>> entry:
>>> %x = alloca i32
>>> br label try
>>>
>>> try: unwinds to %landing.pad
>>> %x1 = add i32 37, 927
>>> call @foo()
>>> br label %normal.edge
>>>
>>> landing.pad: landingpad
>>> ; Code that prints the value of %x1
>>>
>>> We expect the value printed to be 964, but if we don't allow %x1 to be accessible then we get garbage or an assert...
>>
>> What do you mean by "otherwise valid"?
>
> ?? Uh...in the current EH implementation, if foo() throws, then %x1 would have 964 as the value, even in "landing.pad". And the value could be printed out with no problems. In other words, this is completely valid code with proper semantics:
>
> #include <iostream>
> extern void foo();
> int main() {
> int x = 0;
> try {
> x = 37 + 927;
> foo();
> } catch (...) {
> std::cout << x << '\n';
> }
> }
>
> Any solution that violates this isn't a solution.
These well-formedness constraints on IR are not constraints on actual user code, they're constraints on frontends and transforms. Frontends have to translate actual user code into well-formed IR, and transforms have to preserve well-formedness.
In your example, a C++ frontend is going to generate this code with %x as an alloca, like so:
(I've taken the liberty of changing "x = 37 + 927;" to "extern int count; x = count;". This makes the operation non-constant but still non-throwing. I don't think it substantively changes the example other than to remove a potential source of confusion, but if you disagree, the same argument works with the original example)
entry:
%x = alloca i32
store i32 0, i32* x
br label %try
try: unwinds to %lp
%count = load i32* @count
store i32 %count, i32* x
call void @foo()
br label %return
lp:
dispatch [ catchall i8* null, label %catch ]
catch:
# I've removed the begin/end catch calls for simplicity, if you think they're relevant I can elaborate
%t = load i32* %x
call void @operator<<(%"std::ostream"* @std::cout, i32 %t)
br label %return
return:
ret i32 0
This is well-formed SSA; the alloca instruction %x is in the entry block and thus dominates both the store in %try and the load in %catch. mem2reg wants to eliminate %x and replace the load in %catch with a fixed value. This involves looking at the value stored in %x at all predecessor points, which is a challenge because one of those predecessors is an arbitrary point within the block %try, and %x actually has two values over that extent. So mem2reg would have to split %try and make a phi in %lp, like so:
try1: unwinds to %lp
%count = load i32* @count
br label %try2
try2: unwinds to %lp
call void foo()
br label %return
lp:
%t = phi i32 [ i32 0, label %try1 ], [ i32 %count, label %try2 ]
#etc.
That's a lot of added complexity for mem2reg (and other transformations that use it as a subroutine), but it's pretty much inherent in any design where edges don't always come from terminators.
Now, in this specific case, mem2reg could say "hey, %count can't throw, I don't need the phi at all". But it would still need to split %lp to make the IR well-formed; it's just that %lp1 wouldn't have an "unwinds" clause. And in the general case, it needs a phi.
John.
More information about the llvm-dev
mailing list