[PATCH] Make llvm.eh.begincatch use an outparam

Kaylor, Andrew andrew.kaylor at intel.com
Fri Feb 27 17:06:40 PST 2015


> There are no mysteries left in the throw structures except for the second bit in a catchable type's flags, I can't seem to turn it on from a normal C++ throw statement.

I haven’t looked at the throw structures much, so I probably won’t be very helpful.

That’s an interesting question though.  I can write code that won’t compile if I try to catch my type by value or try to throw a pointer to the type, but I can still try to catch a pointer to the type, so I guess it doesn’t meet the criteria for that flag.

I can tell you that I wasn’t able to find any place where ICC uses anything other than 0, 1 and 4 in the catchable type’s flags (though I could have missed something).

-Andy

From: David Majnemer [mailto:david.majnemer at gmail.com]
Sent: Friday, February 27, 2015 1:17 PM
To: Kaylor, Andrew
Cc: Reid Kleckner; reviews+D7920+public+4a2276adf4c6c5f0 at reviews.llvm.org; llvm-commits; John McCall (rjmccall at apple.com)
Subject: Re: [PATCH] Make llvm.eh.begincatch use an outparam



On Fri, Feb 27, 2015 at 12:29 PM, Kaylor, Andrew <andrew.kaylor at intel.com<mailto:andrew.kaylor at intel.com>> wrote:
>In Clang's current IR, what information would you use to size the exception object slot? Currently it looks a bit like this:
>
>; 'catch (A e)' where A is an aggregate.
>%e.addr = alloca %struct.A
>...
>%ehobj = call i8* @llvm.eh.begincatch(i8* %exn)
>%ehobj.A = bitcast i8* %ehobj to %struct.A*
>call void @A_copy_ctor(%struct.A* %e.addr, %struct.A* %ehobj.A)
>... ; catch body
>call void @A_dtor(%struct.A* %e.addr)

We could use the bitcast to figure out the size needed.  You’re right that the runtime handles the calling of the copy constructor in this case.



>What about this case?
>
>void f() {
>  try {
>    might_throw();
>  } catch (int e1) {
>    try {
>      might_throw2();
>    } catch (int e2) {
>      use_both_exceptions(e1, e2);
>    }
>  }
>}
>
>I think we need distinct storage for e1 and e2.

I suppose you’re right.

I’ve been doing a lot of poking around in the debugger to figure out how existing compilers support this stuff.  I don’t know if maybe I’ve been inconsistent in the optimization levels I’ve used, but what I’ve seen is that the Microsoft compiler has used different stack slots for each catch handler in the cases I’ve looked at while the Intel compiler has put everything in the same stack slot.  I’ll run your test case above through ICC to see what it does.  I’m guessing that it will probably use two different stack slots.

So, this is leading me to think that maybe your proposed change to llvm.eh.begincatch is useful.  Have you talked to John McCall about this?  As I recall he had some ideas about possibly wanting to use the llvm.eh.begincatch intrinsic for non-Windows purposes somewhere down the road.

> … from a "throw information" table that David is currently looking at.

Does this mean that David is working on generating the .xdata tables?  If so, I’m OK with that.  I’m not sure if I’ve shared everything I know about that, but I’ve got extensive notes from research I’ve done to this point.  I’d be happy to offer assistance.

I just finished writing a dumper for all the throw data structures, it currently lives in llvm-vtabledump: http://llvm.org/viewvc/llvm-project?view=revision&revision=230713

There are no mysteries left in the throw structures except for the second bit in a catchable type's flags, I can't seem to turn it on from a normal C++ throw statement.

However, I was able to get MSVC to give me such a _CT structure when compiling:
namespace std {
    template <typename T>
    void *__GetExceptionInfo(T);
}

void *f() { return std::__GetExceptionInfo<int &&>(0); }


On the other hand, if I’m reading too much into the comment, that’s fine too.  I’ve actually been planning to do the .xdata table generation myself.

I'm just focusing on the throw side of the equation.


-Andy


From: llvm-commits-bounces at cs.uiuc.edu<mailto:llvm-commits-bounces at cs.uiuc.edu> [mailto:llvm-commits-bounces at cs.uiuc.edu<mailto:llvm-commits-bounces at cs.uiuc.edu>] On Behalf Of Reid Kleckner
Sent: Friday, February 27, 2015 11:11 AM
To: reviews+D7920+public+4a2276adf4c6c5f0 at reviews.llvm.org<mailto:reviews%2BD7920%2Bpublic%2B4a2276adf4c6c5f0 at reviews.llvm.org>
Cc: llvm-commits
Subject: Re: [PATCH] Make llvm.eh.begincatch use an outparam

On Fri, Feb 27, 2015 at 10:46 AM, Andy Kaylor <andrew.kaylor at intel.com<mailto:andrew.kaylor at intel.com>> wrote:
I don't think this is necessary.

I've got an extra level of indirection that shouldn't be there in my current handling of the exception object, but I think the basic idea is sound.  My intention is to assign a fixed spot (element 1) in the frame allocation block to the exception object and then bitcast it to the correct type in the handler.  The llvm.eh.begin catch call tells me where the handler code wants to access the exception object and the bitcasts are already being generated.

So like I said, what the currently committed code is doing is wrong, but it's pretty close.  Here's what we are currently generating for the catch handler in cppeh-catch-scalar:

define i8* @_Z4testv.catch(i8*, i8*) {
catch.entry:

  %eh.alloc = call i8* @llvm.framerecover(i8* bitcast (void ()* @_Z4testv to i8*), i8* %1)
  %eh.data = bitcast i8* %eh.alloc to %struct._Z4testv.ehdata*
  %eh.obj.ptr = getelementptr inbounds %struct._Z4testv.ehdata* %eh.data, i32 0, i32 1
  %eh.obj = load i8** %eh.obj.ptr
  %i = getelementptr inbounds %struct._Z4testv.ehdata* %eh.data, i32 0, i32 2
  %2 = bitcast i8* %eh.obj to i32*
  %3 = load i32* %2, align 4
  store i32 %3, i32* %i, align 4
  %4 = load i32* %i, align 4
  call void @_Z10handle_inti(i32 %4)
  ret i8* blockaddress(@_Z4testv, %try.cont)

}

The GEP for %i is an unnecessary artifact as %i is not (and cannot be) referenced outside of the catch handler.  Also, there is a load-store-load sequence that confuses things but is just an artifact of having started with unoptimized IR.  The big issue, however, is that the indirection with %eh.obj.ptr shouldn't be there.  I didn't have that originally and then I guess one day I wasn't thinking clearly and convinced myself that I needed it.  Without that, I think you can see how this ought to work with the existing form of llvm.eh.begincatch.

Here is what I think we should be generating:

define i8* @_Z4testv.catch(i8*, i8*) {
catch.entry:

  %eh.alloc = call i8* @llvm.framerecover(i8* bitcast (void ()* @_Z4testv to i8*), i8* %1)
  %eh.data = bitcast i8* %eh.alloc to %struct._Z4testv.ehdata*
  ; "%4 = call i8* @llvm.eh.begincatch(i8* %exn11)" gets mapped to the instruction below
  %eh.obj = getelementptr inbounds %struct._Z4testv.ehdata* %eh.data, i32 0, i32 1
  %2 = bitcast i8* %eh.obj to i32*
  %i = load i32* %2, align 4
  call void @_Z10handle_inti(i32 %i)
  ret i8* blockaddress(@_Z4testv, %try.cont)

}

I need to do some experimentation to see what happens when the exception object is bigger than a pointer size, but I'm fairly certain that this model can handle that just by putting some padding in the frame allocation structure.

Right, this change came out of me trying to come up with a solution to that problem.

In Clang's current IR, what information would you use to size the exception object slot? Currently it looks a bit like this:

; 'catch (A e)' where A is an aggregate.
%e.addr = alloca %struct.A
...
%ehobj = call i8* @llvm.eh.begincatch(i8* %exn)
%ehobj.A = bitcast i8* %ehobj to %struct.A*
call void @A_copy_ctor(%struct.A* %e.addr, %struct.A* %ehobj.A)
... ; catch body
call void @A_dtor(%struct.A* %e.addr)

LLVM doesn't know anything about C++ copy constructors, and it shouldn't. Furthermore, this copy constructor call is completely missing from MSVC's catch block code. The thrower is actually responsible for emitting "throw info" describing all the ways you could catch the exception and how to copy it if it is caught. Consider:

struct A { int a; A(); A(const A &); ~A();};
struct B : A { int b; B(); B(const B &); ~B();};
extern "C" void use_e(A *);
extern "C" void f() {
  try {
    throw B();
  } catch (A e) {
    use_e(&e);
  }
}

The only reference to A::A(const A &) is from a "throw information" table that David is currently looking at. There are no A() constructor calls in 'f' or it's subfunctions, just a call to ~A() to cleanup e.

What I'm getting at is that the *runtime* is responsible for storing things into the EH object stack slot. I think the best way to represent that in the IR is to escape a stack object to an intrinsic. That tells the optimizer that "stores can happen here", and that subsequent reads will be properly initialized.

Note also that I am intentionally using the same stack location for the exception object in all catch handlers.  I can't see any advantage to not doing so.

What about this case?

void f() {
  try {
    might_throw();
  } catch (int e1) {
    try {
      might_throw2();
    } catch (int e2) {
      use_both_exceptions(e1, e2);
    }
  }
}

I think we need distinct storage for e1 and e2. The __CxxFrameHandler3 tables appear to support this, since each catch handler entry has space for a distinct frame offset.

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/llvm-commits/attachments/20150228/62360a84/attachment.html>


More information about the llvm-commits mailing list