I've also been thinking a good deal about a grand EH reorg; I'll try to post something for you to comment on later today.
Here ya go. Thoughts? r~
New constructs: { exc_ptr, filter } = EH_LANDING_PAD; Placeholder for the landing-pad rtl. Has 2 outputs for both the exception pointer and the filter. EH_DISPATCH (filter); Placeholder for the switch statement that we'll generate to jump between the alternatives for different catches. ERT_NOP A new type of exception region that merely implies a different landing pad to the containing region. Inserting one of these below the "current" region is how we'll split critical edges, instead of copying regions. exc_ptr and filter members in each eh_region The code within each region will use these decls. When falling through from an inner region to an outer region, these variables must be initialized. These replace the EXC_PTR_EXPR and FILTER_EXPR constructs that we currently use. RESX (exc_ptr, filter); Of course we have this now, but we also store the region number in the statement directly, instead of via add_stmt_to_eh_region. Which leads to special cases all over. There does not seem to be a real point to this. Also, we need to propagate ext_ptr and filter up to the next level, if we're not calling _Unwind_Resume. Examples: Catch with nested cleanup: struct S { ~S(); }; void bar(); int x, y; void foo() { try { S s; bar(); } catch (int) { x++; } catch (float) { y++; } } Compiles to (before eh_dispatch/resx expansion) EH Region Tree: 3 catch int -> post-land = BB8 4 catch float -> post-land = BB9 2 try -> { e2, f2 }, land = BB6, post-land = BB7 1 cleanup -> { e1, f1 }, land = BB4, post-land = BB5 5 must_not_throw BB1: bar(); [EH 1] # succ 2 4(eh) BB2: S::~S(&s); [EH 2] # succ 3 BB3: return; # succ exit BB4: { e1, f1 } = EH_LANDING_PAD; [EH 1] # succ 5 BB5: S::~S(&s); [EH 5] RESX (e1, f1); # succ 6(eh) BB6: { e2, f2 } = EH_LANDING_PAD; [EH 2] # succ 7 BB7: EH_DISPATCH (f2); [EH 2] # succ 8(ab) 9(ab) 10(ab) BB8: __cxa_begin_catch (e2); x++; __cxa_end_catch (e2); # succ 3 BB9: __cxa_begin_catch (e2); y++; __cxa_end_catch (e2); # succ 3 BB10: RESX (e2, f2); [EH 2] After inlining, we have a new pass that expands both RESX and EH_DISPATCH: BB5: e2 = e1; f2 = f1; # succ 7 BB7: switch (f2) { case 1: goto BB8; case 2: goto BB9; default: goto BB10 } # succ 8 9 10 BB10: _Unwind_Resume (e2); There are a couple of advantages that ought to be clear immediately. First, no more silliness that tree_empty_eh_handler_p has to clean up. Second, everything can be properly put into SSA form. Third, we're not too late to easily generate switch statements. See /* ??? It is mighty inconvenient to call back into the switch statement generation code in expand_end_case. Rapid prototyping sez a sequence of ifs. */ which has been in except.c for nearly 10 years. Critical edge splitting: struct S { ~S() throw(); }; void bar(); void foo() { S s1; bar(); bar(); S s0; bar(); } Compiles to: EH Region Tree: 1 cleanup -> { e1, f1 }, land = BB7, post-land = BB8 2 cleanup -> { e2, f2 }, land = BB5, post-land = BB6 BB0: bar(); [EH 1] # succ 1 7(eh) BB1: bar(); [EH 1] # succ 2 7(eh) BB2: bar(); [EH 2] # succ 3 5(eh) BB3: S::~S(&s0); S::~S(&s1); # succ 4 BB4: return; BB5: { e2, f2 } = EH_LANDING_PAD; # succ 6 BB6: S::~S(&s0); RESX (e2, f2); # succ 7(eh) BB7: { e1, f1 } = EH_LANDING_PAD; # succ 8 BB8: S::~S(&s1); RESX (e1, f1) Expand RESX and we get BB6: S::~S(&s0); e1 = e2; f1 = f2; # succ 8 BB8: S::~S(&s1); _Unwind_Resume (e1); We decide we want to split edge BB1->BB7, so we modify: EH Region Tree: 1 cleanup -> { e1, f1 }, land = BB7, post-land = BB8 2 cleanup -> { e2, f2 }, land = BB5, post-land = BB6 3 nop -> { e1, f1 }, land = BB9, post-land = BB8 BB1: bar(); [EH 3] # succ 2 9(eh) BB9: { e1, f1 } = EH_LANDING_PAD; # succ 8 Which, if I'm not mistaken, duplicates far less code than you do at present to split these edges. Exception optimization: void f(); void g() throw(); void h() throw(); void foo() { try { try { f(); } catch (int) { g(); } } catch (int) { h(); } } Compiles to: EH Region Tree: 4 catch int 1 try -> { e1, f1 }, land = BB6, post-land = BB7 3 catch int 2 try -> { e2, f2 }, land = BB2, post-land = BB3 BB0: f(); # succ 1 2(eh) [EH 2] BB1: return; # succ exit BB2: { e2, f2 } = EH_LANDING_PAD; [EH 2] # succ 3 BB3: EH_DISPATCH (f2); [EH 2] # succ 4(ab) 5(ab) BB4: __cxa_begin_catch (e2); g(); __cxa_end_catch (e2); # succ 1 BB5: RESX (e2, f2); [EH 2] # succ 6(eh) BB6: { e1, f1 } = EH_LANDING_PAD; [EH 1] # succ 7 BB7: EH_DISPATCH (f1); [EH 1] # succ 8(ab) 9(ab) BB8: __cxa_begin_catch (e1); h(); __cxa_end_catch (e1); # succ 1 BB9: RESX (e1, f1); Expanding RESX and EH_DISPATCH: BB3: switch (f2) { case 0: goto BB4; default: goto BB5; } # succ 4 5 BB5: e1 = e2; f1 = f2; # succ 7 BB7: switch (f1) { case 0: goto BB8; default: goto BB9; } # succ 8 9 BB9: _Unwind_Resume (e1); See below for more discussion. Implementation: EH_LANDING_PAD The existing code in except.c should be easily adaptable to expanding this gimple stmt. All we need to do is pass along the two output arguments and use those instead of the hard-coded crtl->eh.exc_ptr and crtl->eh.filter. EH_DISPATCH: Should be expanded after inlining, and maybe before EH optimization. Again, the existing code within except.c can be adapted. We just need to call assign_filter_values to get all the numbers assigned. At which point it is trivial to build the actual switch. RESX: Should be expanded after inlining, and probably before EH optimization. All we need to do here is either notice that there's no outer region and expand to _Unwind_Resume, or copy the exc_ptr/filter variables around and goto. EH optimization: This pass would remove shadowed exception handlers and other unreachable code. It's supposed to be currently handled in reachable_next_level, and consists of some moderately complicated type checking every time we ask stmt_throws_internal. Instead of having the complicated code run all the time, we'd have a special pass. (As an aside, the dead handler elimination we attempt in reachable_next_level doesn't work because the C++ front end tells us that __cxa_end_catch can throw, which is only true when the thrown object has a destructor that can throw. With a single extra check in the front end, we can set the NOTHROW bit on that __cxa_end_catch call and re-enable this.) It could be implemented as a special form of value-range propagation. We know that the runtime will only enter a landing pad under certain conditions, and the values that the FILTER variable will contain under those conditions. With that, it's trivial to propagate the values around. For instance, in the example above, we know that the only value that F1 and F0 will ever be assigned by EH_LANDING_PAD is 0. From that we can determine that BB5 and BB9 are dead. If there were a cleanup handler somewhere in the middle, we'd have to bail, because the FILTER values for a cleanup can be anything (I think; but in any case it might have a different number as we'd generate for "int"). We'll need something slightly different than the min/max values currently supported by VRP. We'll need to track exact sets of values, but happily only for (generally) small numbers.