This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
Re: pretty-ipa merge 14: EH region label sharing code
- From: Richard Guenther <rguenther at suse dot de>
- To: Jan Hubicka <hubicka at ucw dot cz>
- Cc: gcc-patches at gcc dot gnu dot org
- Date: Fri, 24 Apr 2009 19:37:11 +0200 (CEST)
- Subject: Re: pretty-ipa merge 14: EH region label sharing code
- References: <20090424153405.GB17035@kam.mff.cuni.cz>
On Fri, 24 Apr 2009, Jan Hubicka wrote:
> Hi,
> this is last EH redirection preparation bit. It makes code happy about case
> where two regions share same label. This happens by EH redirection but also by
> cfgcleanup and such.
>
> While this is generally easy to maintain linked list of regions sharing
> label, tricky part is eh cleanup where we now can have empty cleanup reached by
> multiple EH handlers and none of them must be same as the one resuming it.
> This is hanled by removing all the reaching EH handlers and replacing them by
> outer handler of the resuming one.
>
> It is also possible that other control flow reaches the empty handler and in
> this case BB is no longer removed and resuming EH is kept in place. This
> happens sometimes when EH region proves to be empty over some paths only
> after edge splitting.
>
> I also bundled in remove_eh_handler_and_replace change that allows removing
> try region without associated catch and also to update prev_try pointers. This
> is really needed for EH merging, but it is bit difficult to split it out of
> the patch.
>
> Bootstrapped/regtested x86_64-linux, OK?
> * tree-eh.c (tree_remove_unreachable_handlers): Handle shared labels.
> (tree_empty_eh_handler_p): Allow non-EH predecestors; allow region
> to be reached by different label than left.
> (update_eh_edges): Update comment; remove edge_to_remove if possible
> and return true if suceeded.
> (cleanup_empty_eh): Accept sharing map; handle shared regions.
> (cleanup_eh): Compute sharing map.
> * except.c (remove_eh_handler_and_replace): Add argument if we should
> update regions.
> (remove_unreachable_regions): Update for label sharing.
> (label_to_region_map): Likewise.
> (get_next_region_sharing_label): New function.
> (remove_eh_handler_and_replace): Add update_catch_try parameter; update
> prev_try pointers.
> (remove_eh_handler): Update.
> (remove_eh_region_and_replace_by_outer_of): New function.
> * except.h (struct eh_region): Add next_region_sharing_label.
> (remove_eh_region_and_replace_by_outer_of,
> get_next_region_sharing_label): Declare.
> * tree-cfgcleanup.c (tree_forwarder_block_p): Simplify.
> *** /aux/hubicka/trunk-write/gcc/tree-eh.c Wed Apr 8 17:08:50 2009
> --- tree-eh.c Fri Apr 24 13:56:55 2009
> *************** tree_remove_unreachable_handlers (void)
> *** 2695,2702 ****
> if (gimple_code (stmt) == GIMPLE_LABEL && has_eh_preds)
> {
> int uid = LABEL_DECL_UID (gimple_label_label (stmt));
> ! int region = VEC_index (int, label_to_region, uid);
> ! SET_BIT (reachable, region);
> }
> if (gimple_code (stmt) == GIMPLE_RESX)
> SET_BIT (reachable, gimple_resx_region (stmt));
> --- 2738,2748 ----
> if (gimple_code (stmt) == GIMPLE_LABEL && has_eh_preds)
> {
> int uid = LABEL_DECL_UID (gimple_label_label (stmt));
> ! int region;
> !
> ! for (region = VEC_index (int, label_to_region, uid);
> ! region; region = get_next_region_sharing_label (region))
> ! SET_BIT (reachable, region);
> }
> if (gimple_code (stmt) == GIMPLE_RESX)
> SET_BIT (reachable, gimple_resx_region (stmt));
> *************** tree_empty_eh_handler_p (basic_block bb)
> *** 2743,2750 ****
> --- 2789,2799 ----
> {
> gimple_stmt_iterator gsi;
> int region;
> + edge_iterator ei;
> + edge e;
> use_operand_p imm_use;
> gimple use_stmt;
> + bool found = false;
>
> gsi = gsi_last_bb (bb);
>
> *************** tree_empty_eh_handler_p (basic_block bb)
> *** 2815,2829 ****
> if (gsi_end_p (gsi))
> return 0;
> }
> ! while (gimple_code (gsi_stmt (gsi)) == GIMPLE_LABEL)
> ! {
> ! if (gimple_label_label (gsi_stmt (gsi))
> ! == get_eh_region_no_tree_label (region))
> ! return region;
> ! gsi_prev (&gsi);
> ! if (gsi_end_p (gsi))
> ! return 0;
> ! }
> return 0;
> }
>
> --- 2864,2880 ----
> if (gsi_end_p (gsi))
> return 0;
> }
> ! if (gimple_code (gsi_stmt (gsi)) != GIMPLE_LABEL)
> ! return 0;
> !
> ! /* Be sure that there is at least on EH region reaching the block directly.
> ! After EH edge redirection, it is possible that block is reached by one handler
> ! but resumed by different. */
> ! FOR_EACH_EDGE (e, ei, bb->preds)
> ! if ((e->flags & EDGE_EH))
> ! found = true;
> ! if (found)
> ! return region;
> return 0;
> }
>
> *************** make_eh_edge_and_update_phi (struct eh_r
> *** 2955,2963 ****
>
> /* Make EH edges corresponding to STMT while updating PHI nodes after removal
> empty cleanup BB_TO_REMOVE joined to BB containing STMT
> ! by EDGE_TO_REMOVE. */
>
> ! static void
> update_eh_edges (gimple stmt, basic_block bb_to_remove, edge edge_to_remove)
> {
> int region_nr;
> --- 3006,3017 ----
>
> /* Make EH edges corresponding to STMT while updating PHI nodes after removal
> empty cleanup BB_TO_REMOVE joined to BB containing STMT
> ! by EDGE_TO_REMOVE.
>
> ! Return if EDGE_TO_REMOVE was really removed. It might stay reachable when
> ! not all EH regions are cleaned up. */
> !
> ! static bool
> update_eh_edges (gimple stmt, basic_block bb_to_remove, edge edge_to_remove)
> {
> int region_nr;
> *************** update_eh_edges (gimple stmt, basic_bloc
> *** 2967,2972 ****
> --- 3021,3027 ----
> edge_iterator ei;
> edge e;
> int probability_sum = 0;
> + bool removed = false;
>
> info.bb_to_remove = bb_to_remove;
> info.bb = gimple_bb (stmt);
> *************** update_eh_edges (gimple stmt, basic_bloc
> *** 2980,2987 ****
> else
> {
> region_nr = lookup_stmt_eh_region (stmt);
> - if (region_nr < 0)
> - return;
This doesn't look correct. foreach_reachable_handler will then index
with negative region_nr into the region array.
> is_resx = false;
> inlinable = inlinable_call_p (stmt);
> }
> --- 3035,3040 ----
> *************** update_eh_edges (gimple stmt, basic_bloc
> *** 2993,3001 ****
> /* And remove edges we didn't marked. */
> for (ei = ei_start (info.bb->succs); (e = ei_safe_edge (ei)); )
> {
> ! if ((e->flags & EDGE_EH) && !e->aux && e != edge_to_remove)
> {
> dominance_info_invalidated = true;
> remove_edge (e);
> }
> else
> --- 3046,3056 ----
> /* And remove edges we didn't marked. */
> for (ei = ei_start (info.bb->succs); (e = ei_safe_edge (ei)); )
> {
> ! if ((e->flags & EDGE_EH) && !e->aux)
> {
> dominance_info_invalidated = true;
> + if (e == edge_to_remove)
> + removed = true;
> remove_edge (e);
> }
> else
> *************** update_eh_edges (gimple stmt, basic_bloc
> *** 3011,3026 ****
> we get fewer consistency errors in the dumps. */
> if (is_resx && EDGE_COUNT (info.bb->succs) && !probability_sum)
> EDGE_SUCC (info.bb, 0)->probability = REG_BR_PROB_BASE;
> }
>
> /* Look for basic blocks containing empty exception handler and remove them.
> This is similar to jump forwarding, just across EH edges. */
>
> static bool
> ! cleanup_empty_eh (basic_block bb)
> {
> int region;
> gimple_stmt_iterator si;
>
> /* When handler of EH region winds up to be empty, we can safely
> remove it. This leads to inner EH regions to be redirected
> --- 3066,3083 ----
> we get fewer consistency errors in the dumps. */
> if (is_resx && EDGE_COUNT (info.bb->succs) && !probability_sum)
> EDGE_SUCC (info.bb, 0)->probability = REG_BR_PROB_BASE;
> + return removed;
> }
>
> /* Look for basic blocks containing empty exception handler and remove them.
> This is similar to jump forwarding, just across EH edges. */
>
> static bool
> ! cleanup_empty_eh (basic_block bb, VEC(int,heap) * label_to_region)
> {
> int region;
> gimple_stmt_iterator si;
> + edge_iterator ei;
>
> /* When handler of EH region winds up to be empty, we can safely
> remove it. This leads to inner EH regions to be redirected
> *************** cleanup_empty_eh (basic_block bb)
> *** 3030,3048 ****
> && all_phis_safe_to_merge (bb))
> {
> edge e;
>
> ! remove_eh_region (region);
>
> ! while ((e = ei_safe_edge (ei_start (bb->preds))))
> {
> basic_block src = e->src;
> ! gcc_assert (e->flags & EDGE_EH);
> if (stmt_can_throw_internal (last_stmt (src)))
> ! update_eh_edges (last_stmt (src), bb, e);
> ! remove_edge (e);
> }
> - if (dump_file)
> - fprintf (dump_file, "Empty EH handler %i removed\n", region);
>
> /* Verify that we eliminated all uses of PHI we are going to remove.
> If we didn't, rebuild SSA on affected variable (this is allowed only
> --- 3087,3153 ----
> && all_phis_safe_to_merge (bb))
> {
> edge e;
> + bool found = false, removed_some = false, has_non_eh_preds = false;
> + gimple_stmt_iterator gsi;
>
Please add a comment what the following loop(s) want to do. EH code
is hard to follow anyway.
> ! for (gsi = gsi_start_bb (bb); !gsi_end_p (gsi); gsi_next (&gsi))
> ! if (gimple_code (gsi_stmt (gsi)) == GIMPLE_LABEL)
> ! {
> ! int uid = LABEL_DECL_UID (gimple_label_label (gsi_stmt (gsi)));
> ! int r = VEC_index (int, label_to_region, uid);
> ! int next;
>
> ! while (r)
> ! {
> ! next = get_next_region_sharing_label (r);
> ! if (r == region)
> ! found = true;
> ! else
> ! {
> ! removed_some = true;
> ! remove_eh_region_and_replace_by_outer_of (r, region);
> ! if (dump_file)
&& (dump_flags & TDF_details)
> ! fprintf (dump_file, "Empty EH handler %i removed and "
> ! "replaced by %i\n", r, region);
> ! }
> ! r = next;
> ! }
> ! }
> ! else
> ! break;
Add vertical space here.
> ! gcc_assert (found || removed_some);
> ! FOR_EACH_EDGE (e, ei, bb->preds)
> ! if (!(e->flags & EDGE_EH))
> ! has_non_eh_preds = true;
> !
> ! /* When block is empty EH cleanup, but it is reachable via non-EH code too,
> ! we can not remove the region it is resumed via, because doing so will
> ! lead to redirection of its RESX edges.
> !
> ! This case will be handled later after edge forwarding if the EH cleanup
> ! is really dead. */
> !
> ! if (found && !has_non_eh_preds)
> ! remove_eh_region (region);
> ! else if (!removed_some)
> ! return false;
> !
> ! for (ei = ei_start (bb->preds); (e = ei_safe_edge (ei)); )
> {
> basic_block src = e->src;
> ! if (!(e->flags & EDGE_EH))
> ! {
> ! ei_next (&ei);
> ! continue;
> ! }
> if (stmt_can_throw_internal (last_stmt (src)))
> ! {
> ! if (!update_eh_edges (last_stmt (src), bb, e))
> ! ei_next (&ei);
> ! }
> ! else
> ! remove_edge (e);
> }
>
> /* Verify that we eliminated all uses of PHI we are going to remove.
> If we didn't, rebuild SSA on affected variable (this is allowed only
> *************** cleanup_empty_eh (basic_block bb)
> *** 3091,3097 ****
> }
> }
> }
> ! delete_basic_block (bb);
> return true;
> }
> return false;
> --- 3196,3203 ----
> }
> }
> }
> ! if (!ei_safe_edge (ei_start (bb->preds)))
> ! delete_basic_block (bb);
> return true;
> }
> return false;
> *************** cleanup_eh (void)
> *** 3111,3116 ****
> --- 3217,3223 ----
> {
> bool changed = false;
> basic_block bb;
> + VEC(int,heap) * label_to_region;
> int i;
>
> if (!cfun->eh)
> *************** cleanup_eh (void)
> *** 3123,3136 ****
>
> if (optimize)
> {
> dominance_info_invalidated = false;
> /* We cannot use FOR_EACH_BB, since the basic blocks may get removed. */
> for (i = NUM_FIXED_BLOCKS; i < last_basic_block; i++)
> {
> bb = BASIC_BLOCK (i);
> if (bb)
> ! changed |= cleanup_empty_eh (bb);
> }
> if (dominance_info_invalidated)
> {
> free_dominance_info (CDI_DOMINATORS);
> --- 3230,3245 ----
>
> if (optimize)
> {
> + label_to_region = label_to_region_map ();
> dominance_info_invalidated = false;
> /* We cannot use FOR_EACH_BB, since the basic blocks may get removed. */
> for (i = NUM_FIXED_BLOCKS; i < last_basic_block; i++)
> {
> bb = BASIC_BLOCK (i);
> if (bb)
> ! changed |= cleanup_empty_eh (bb, label_to_region);
> }
> + VEC_free (int, heap, label_to_region);
> if (dominance_info_invalidated)
> {
> free_dominance_info (CDI_DOMINATORS);
> *** /aux/hubicka/trunk-write/gcc/except.c Wed Apr 22 15:38:18 2009
> --- except.c Wed Apr 22 20:07:02 2009
> *************** static void sjlj_build_landing_pads (voi
> *** 146,152 ****
>
> static void remove_eh_handler (struct eh_region *);
> static void remove_eh_handler_and_replace (struct eh_region *,
> ! struct eh_region *);
>
> /* The return value of reachable_next_level. */
> enum reachable_code
> --- 147,153 ----
>
> static void remove_eh_handler (struct eh_region *);
> static void remove_eh_handler_and_replace (struct eh_region *,
> ! struct eh_region *, bool);
>
> /* The return value of reachable_next_level. */
> enum reachable_code
> *************** remove_unreachable_regions (sbitmap reac
> *** 743,754 ****
> fprintf (dump_file, "Replacing MUST_NOT_THROW region %i by %i\n",
> r->region_number,
> first_must_not_throw->region_number);
> ! remove_eh_handler_and_replace (r, first_must_not_throw);
> first_must_not_throw->may_contain_throw |= r->may_contain_throw;
> }
> else
> bring_to_root (r);
> }
> #ifdef ENABLE_CHECKING
> verify_eh_tree (cfun);
> #endif
> --- 979,990 ----
> fprintf (dump_file, "Replacing MUST_NOT_THROW region %i by %i\n",
> r->region_number,
> first_must_not_throw->region_number);
> ! remove_eh_handler_and_replace (r, first_must_not_throw, false);
> first_must_not_throw->may_contain_throw |= r->may_contain_throw;
> }
> else
> bring_to_root (r);
> }
> #ifdef ENABLE_CHECKING
> verify_eh_tree (cfun);
> #endif
> *************** remove_unreachable_regions (sbitmap reac
> *** 758,768 ****
> /* Return array mapping LABEL_DECL_UID to region such that region's tree_label
> is identical to label. */
>
> ! VEC(int,heap) *
> label_to_region_map (void)
> {
> ! VEC(int,heap) * label_to_region = NULL;
> int i;
>
> VEC_safe_grow_cleared (int, heap, label_to_region,
> cfun->cfg->last_label_uid + 1);
> --- 995,1006 ----
> /* Return array mapping LABEL_DECL_UID to region such that region's tree_label
> is identical to label. */
>
> ! VEC (int, heap) *
> label_to_region_map (void)
> {
> ! VEC (int, heap) * label_to_region = NULL;
> int i;
> + int idx;
>
> VEC_safe_grow_cleared (int, heap, label_to_region,
> cfun->cfg->last_label_uid + 1);
> *************** label_to_region_map (void)
> *** 770,777 ****
> {
> struct eh_region *r = VEC_index (eh_region, cfun->eh->region_array, i);
> if (r && r->region_number == i
> ! && r->tree_label && LABEL_DECL_UID (r->tree_label) >= 0)
> {
> VEC_replace (int, label_to_region, LABEL_DECL_UID (r->tree_label),
> i);
> }
> --- 1008,1021 ----
> {
> struct eh_region *r = VEC_index (eh_region, cfun->eh->region_array, i);
> if (r && r->region_number == i
> ! && r->tree_label && LABEL_DECL_UID (r->tree_label) >= 0)
> {
> + if ((idx = VEC_index (int, label_to_region,
> + LABEL_DECL_UID (r->tree_label))) != 0)
> + r->next_region_sharing_label =
> + VEC_index (eh_region, cfun->eh->region_array, idx);
> + else
> + r->next_region_sharing_label = NULL;
> VEC_replace (int, label_to_region, LABEL_DECL_UID (r->tree_label),
> i);
> }
> *************** num_eh_regions (void)
> *** 786,791 ****
> --- 1030,1049 ----
> return cfun->eh->last_region_number + 1;
> }
>
> + /* Return next region sharing same label as REGION. */
> +
> + int
> + get_next_region_sharing_label (int region)
> + {
> + struct eh_region *r;
> + if (!region)
> + return 0;
> + r = VEC_index (eh_region, cfun->eh->region_array, region);
> + if (!r || !r->next_region_sharing_label)
> + return 0;
> + return r->next_region_sharing_label->region_number;
> + }
> +
> /* Set up EH labels for RTL. */
>
> void
> *************** finish_eh_generation (void)
> *** 2163,2178 ****
>
> /* This section handles removing dead code for flow. */
>
> ! /* Splice REGION from the region tree and replace it by REPLACE etc. */
>
> static void
> remove_eh_handler_and_replace (struct eh_region *region,
> ! struct eh_region *replace)
> {
> struct eh_region **pp, **pp_start, *p, *outer, *inner;
> rtx lab;
>
> outer = region->outer;
> /* For the benefit of efficiently handling REG_EH_REGION notes,
> replace this region in the region array with its containing
> region. Note that previous region deletions may result in
> --- 2718,2763 ----
>
> /* This section handles removing dead code for flow. */
>
> ! /* Splice REGION from the region tree and replace it by REPLACE etc.
> ! When UPDATE_CATCH_TRY is true mind updating links from catch to try
> ! region.*/
>
> static void
> remove_eh_handler_and_replace (struct eh_region *region,
> ! struct eh_region *replace,
> ! bool update_catch_try)
> {
> struct eh_region **pp, **pp_start, *p, *outer, *inner;
> rtx lab;
>
> outer = region->outer;
> +
> + /* When we are moving the region in EH tree, update prev_try pointers. */
> + if (outer != replace && region->inner)
> + {
> + struct eh_region *prev_try = find_prev_try (replace);
> + p = region->inner;
> + while (p != region)
> + {
> + if (p->type == ERT_CLEANUP)
> + p->u.cleanup.prev_try = prev_try;
> + if (p->type != ERT_TRY
> + && p->type != ERT_MUST_NOT_THROW
> + && (p->type != ERT_ALLOWED_EXCEPTIONS
> + || p->u.allowed.type_list)
> + && p->inner)
> + p = p->inner;
> + else if (p->next_peer)
> + p = p->next_peer;
> + else
> + {
> + while (p != region && !p->next_peer)
> + p = p->outer;
> + if (p != region)
> + p = p->next_peer;
> + }
> + }
> + }
Either the patch has or your mailer garbles whitespace here. Please
make sure to have consistent TAB / space usage.
> /* For the benefit of efficiently handling REG_EH_REGION notes,
> replace this region in the region array with its containing
> region. Note that previous region deletions may result in
> *************** remove_eh_handler_and_replace (struct eh
> *** 2228,2234 ****
> *pp_start = inner;
> }
>
> ! if (region->type == ERT_CATCH)
> {
> struct eh_region *eh_try, *next, *prev;
>
> --- 2813,2820 ----
> *pp_start = inner;
> }
>
> ! if (region->type == ERT_CATCH
> ! && update_catch_try)
> {
> struct eh_region *eh_try, *next, *prev;
>
> *************** remove_eh_handler_and_replace (struct eh
> *** 2262,2268 ****
> static void
> remove_eh_handler (struct eh_region *region)
> {
> ! remove_eh_handler_and_replace (region, region->outer);
> }
>
> /* Remove Eh region R that has turned out to have no code in its handler. */
> --- 2848,2854 ----
> static void
> remove_eh_handler (struct eh_region *region)
> {
> ! remove_eh_handler_and_replace (region, region->outer, true);
> }
>
> /* Remove Eh region R that has turned out to have no code in its handler. */
> *************** remove_eh_region (int r)
> *** 2276,2281 ****
> --- 2862,2880 ----
> remove_eh_handler (region);
> }
>
> + /* Remove Eh region R that has turned out to have no code in its handler
> + and replace in by R2. */
> +
> + void
> + remove_eh_region_and_replace_by_outer_of (int r, int r2)
> + {
> + struct eh_region *region, *region2;
> +
> + region = VEC_index (eh_region, cfun->eh->region_array, r);
> + region2 = VEC_index (eh_region, cfun->eh->region_array, r2);
> + remove_eh_handler_and_replace (region, region2->outer, true);
> + }
> +
> /* Invokes CALLBACK for every exception handler label. Only used by old
> loop hackery; should not be used by new code. */
>
> *** /aux/hubicka/trunk-write/gcc/except.h Wed Apr 22 15:38:18 2009
> --- except.h Sun Apr 19 19:53:37 2009
> *************** struct eh_region GTY(())
> *** 34,39 ****
> --- 34,42 ----
> struct eh_region *inner;
> struct eh_region *next_peer;
>
> + /* List of regions sharing label. */
> + struct eh_region *next_region_sharing_label;
> +
> /* An identifier for this region. */
> int region_number;
>
> *************** extern bool can_throw_external (const_rt
> *** 151,161 ****
> --- 154,164 ----
> /* Set TREE_NOTHROW and cfun->all_throwers_are_sibcalls. */
> extern unsigned int set_nothrow_function_flags (void);
>
> extern void init_eh (void);
> extern void init_eh_for_function (void);
>
> extern rtx reachable_handlers (rtx);
> void remove_eh_region (int);
> + void remove_eh_region_and_replace_by_outer_of (int, int);
>
> extern void convert_from_eh_region_ranges (void);
> extern unsigned int convert_to_eh_region_ranges (void);
> *************** struct throw_stmt_node GTY(())
> *** 269,277 ****
> --- 277,286 ----
> gimple stmt;
> int region_nr;
> };
>
> extern struct htab *get_eh_throw_stmt_table (struct function *);
> extern void set_eh_throw_stmt_table (struct function *, struct htab *);
> extern void remove_unreachable_regions (sbitmap, sbitmap);
> extern VEC(int,heap) * label_to_region_map (void);
> extern int num_eh_regions (void);
> + extern int get_next_region_sharing_label (int);
> *** /aux/hubicka/trunk-write/gcc/tree-cfgcleanup.c Mon Feb 23 13:04:19 2009
> --- tree-cfgcleanup.c Thu Apr 9 01:25:26 2009
> *************** static bool
> *** 221,229 ****
> tree_forwarder_block_p (basic_block bb, bool phi_wanted)
> {
> gimple_stmt_iterator gsi;
> - edge_iterator ei;
> - edge e, succ;
> - basic_block dest;
>
> /* BB must have a single outgoing edge. */
> if (single_succ_p (bb) != 1
> --- 221,226 ----
> *************** tree_forwarder_block_p (basic_block bb,
> *** 274,296 ****
> if (dest->loop_father->header == dest)
> return false;
> }
> -
> - /* If we have an EH edge leaving this block, make sure that the
> - destination of this block has only one predecessor. This ensures
> - that we don't get into the situation where we try to remove two
> - forwarders that go to the same basic block but are handlers for
> - different EH regions. */
> - succ = single_succ_edge (bb);
> - dest = succ->dest;
> - FOR_EACH_EDGE (e, ei, bb->preds)
> - {
> - if (e->flags & EDGE_EH)
> - {
> - if (!single_pred_p (dest))
> - return false;
> - }
> - }
> -
> return true;
> }
>
> --- 271,276 ----
>
>
The rest of the patch looks ok. I am somewhat concerned that with all
the EH cleanup and optimizations you implemented we didn't get a single
testcase that verifies the code is working.
_Please_ consider adding some.
Thanks,
Richard.