[fedora-java] Rewrite exception region handling in bytecode compiler
Gary Benson
gbenson at redhat.com
Thu Apr 21 09:46:42 UTC 2005
Jakub, can you let me know when this is available in beehive please?
Thanks,
Gary
Andrew Haley wrote:
> Jakub, we need this in the Fedora compiler. It's too late for 4.0.
>
> Andrew.
>
>
Content-Description: forwarded message
> Date: Mon, 18 Apr 2005 21:15:33 +0100
> From: Andrew Haley <aph at redhat.com>
> To: java-patches at gcc.gnu.org, gcc-patches at gcc.gnu.org
> Subject: Rewrite exception region handling in bytecode compiler
>
> PR 20768 shows that the gcj bytecode compiler is miscompiling
> exeception regions. This only occurs when such regions aren't
> properly nested, and Sun's compiler doesn't seem to output such
> regions. However, ecj does, and this has been causing many failures
> of gcj-compiled packages in Fedora Core.
>
>
> If we have an exception range like this:
>
> from to target type
> 2 14 14 Class TestExceptionBug$IndexedStoreException
> 2 12 29 Throwable /* finally */
>
> we compile it to
>
> try
> {
> try
> {
> bytes 2 .. 11;
> }
> catch (java.lang.Throwable)
> {
> goto *.LJpc=29;
> }
> *.LJpc=12:
> bytes 12 .. 13;
> }
> catch (TestExceptionBug$IndexedStoreException)
> {
> goto *.LJpc=14;
> }
> *.LJpc=14:
>
> That is, we have moved the Throwable inside the IndexedStoreException.
> But this is wrong: the JVM Spec makes it clear that the exception
> table is to be searched from top to bottom, and the first matching
> exception used.
>
> It is not legitimate to hoist an exception region inside one higher in
> the table.
>
> What we really need to do in this case is split the exception table
> into properly nested regions, like this:
>
> from to target type
> 2 12 14 Class TestExceptionBug$IndexedStoreException
> 2 12 29 Throwable /* finally */
> 12 14 14 Class TestExceptionBug$IndexedStoreException
>
> Which gives:
>
> try
> {
> try
> {
> bytes 2 .. 11;
> }
> catch (TestExceptionBug$IndexedStoreException)
> {
> goto *.LJpc=14;
> }
> }
> catch (java.lang.Throwable)
> {
> goto *.LJpc=29;
> }
> try
> {
> *.LJpc=12:
> bytes 12 .. 13;
> }
> catch (TestExceptionBug$IndexedStoreException)
> {
> goto *.LJpc=14;
> }
> *.LJpc=14:
>
>
> This patch is a rewrite of the logic that generates exception
> regions. The priority of exceptions in the exception table is
> preserved, and no matter how scrambled the exception table, we
> generate a logically equivalent tree.
>
> In a few cases this logic generates more exception regions than the
> existing code. Some region merging optimizations are possible at a
> later date, but it's more important for this to be correct.
>
> Tested with JOnAS and Eclipse.
>
> Andrew.
>
>
> 2005-04-18 Andrew Haley <aph at redhat.com>
>
> * java-except.h (struct eh_range.handler): Remove unused field.
> (handle_nested_ranges): Remove function declaration.
> (sanity_check_exception_range): Add function declaration.
> * verify.c (verify_jvm_instructions): Remove call to
> handle_nested_ranges.
> * verify-glue.c (verify_jvm_instructions_new): Call
> sanity_check_exception_range.
> * except.c (link_handler, eh_range_freelist, link_handler,
> handle_nested_ranges): Remove.
> (add_handler): Rewrite.
> (sanity_check_exception_range): New function.
> (print_ranges): New function.
>
> Index: except.c
> ===================================================================
> RCS file: /cvs/gcc/gcc/gcc/java/except.c,v
> retrieving revision 1.47
> diff -c -p -r1.47 except.c
> *** except.c 3 Dec 2004 18:11:21 -0000 1.47
> --- except.c 18 Apr 2005 18:58:45 -0000
> ***************
> *** 1,5 ****
> /* Handle exceptions for GNU compiler for the Java(TM) language.
> ! Copyright (C) 1997, 1998, 1999, 2000, 2002, 2003, 2004
> Free Software Foundation, Inc.
>
> This file is part of GCC.
> --- 1,5 ----
> /* Handle exceptions for GNU compiler for the Java(TM) language.
> ! Copyright (C) 1997, 1998, 1999, 2000, 2002, 2003, 2004, 2005
> Free Software Foundation, Inc.
>
> This file is part of GCC.
> *************** The Free Software Foundation is independ
> *** 42,48 ****
> static void expand_start_java_handler (struct eh_range *);
> static struct eh_range *find_handler_in_range (int, struct eh_range *,
> struct eh_range *);
> - static void link_handler (struct eh_range *, struct eh_range *);
> static void check_start_handlers (struct eh_range *, int);
> static void free_eh_ranges (struct eh_range *range);
>
> --- 42,47 ----
> *************** struct eh_range *current_method_handlers
> *** 50,57 ****
>
> struct eh_range *current_try_block = NULL;
>
> - struct eh_range *eh_range_freelist = NULL;
> -
> /* These variables are used to speed up find_handler. */
>
> static int cache_range_start, cache_range_end;
> --- 49,54 ----
> *************** static struct eh_range *cache_next_child
> *** 62,73 ****
>
> struct eh_range whole_range;
>
> #if defined(DEBUG_JAVA_BINDING_LEVELS)
> - extern int binding_depth;
> extern int is_class_level;
> extern int current_pc;
> ! extern void indent ();
>
> #endif
>
> /* Search for the most specific eh_range containing PC.
> --- 59,118 ----
>
> struct eh_range whole_range;
>
> + /* Check the invariants of the structure we're using to contain
> + exception regions. Either returns true or fails an assertion
> + check. */
> +
> + bool
> + sanity_check_exception_range (struct eh_range *range)
> + {
> + struct eh_range *ptr = range->first_child;
> + for (; ptr; ptr = ptr->next_sibling)
> + {
> + gcc_assert (ptr->outer == range
> + && ptr->end_pc > ptr->start_pc);
> + if (ptr->next_sibling)
> + gcc_assert (ptr->next_sibling->start_pc >= ptr->end_pc);
> + gcc_assert (ptr->start_pc >= ptr->outer->start_pc
> + && ptr->end_pc <= ptr->outer->end_pc);
> + (void) sanity_check_exception_range (ptr);
> + }
> + return true;
> + }
> +
> #if defined(DEBUG_JAVA_BINDING_LEVELS)
> extern int is_class_level;
> extern int current_pc;
> ! extern int binding_depth;
> ! extern void indent (void);
> ! static void
> ! print_ranges (struct eh_range *range)
> ! {
> ! if (! range)
> ! return;
> !
> ! struct eh_range *child = range->first_child;
> !
> ! indent ();
> ! fprintf (stderr, "handler pc %d --> %d ", range->start_pc, range->end_pc);
> !
> ! tree handler = range->handlers;
> ! for ( ; handler != NULL_TREE; handler = TREE_CHAIN (handler))
> ! {
> ! tree type = TREE_PURPOSE (handler);
> ! if (type == NULL)
> ! type = throwable_type_node;
> ! fprintf (stderr, " type=%s ", IDENTIFIER_POINTER (DECL_NAME (TYPE_NAME (type))));
> ! }
> ! fprintf (stderr, "\n");
>
> + int saved = binding_depth;
> + binding_depth++;
> + print_ranges (child);
> + binding_depth = saved;
> +
> + print_ranges (range->next_sibling);
> + }
> #endif
>
> /* Search for the most specific eh_range containing PC.
> *************** find_handler (int pc)
> *** 117,230 ****
> return find_handler_in_range (pc, h, cache_next_child);
> }
>
> - /* Recursive helper routine for check_nested_ranges. */
> -
> - static void
> - link_handler (struct eh_range *range, struct eh_range *outer)
> - {
> - struct eh_range **ptr;
> -
> - if (range->start_pc == outer->start_pc && range->end_pc == outer->end_pc)
> - {
> - outer->handlers = chainon (outer->handlers, range->handlers);
> - return;
> - }
> -
> - /* If the new range completely encloses the `outer' range, then insert it
> - between the outer range and its parent. */
> - if (range->start_pc <= outer->start_pc && range->end_pc >= outer->end_pc)
> - {
> - range->outer = outer->outer;
> - range->next_sibling = NULL;
> - range->first_child = outer;
> - {
> - struct eh_range *p = outer;
> - struct eh_range **pr = &(outer->outer->first_child);
> - while (*pr != outer)
> - pr = &(*pr)->next_sibling;
> - *pr = range;
> -
> - while (p)
> - {
> - p->outer = range;
> - p = p->next_sibling;
> - }
> - }
> - return;
> - }
> -
> - /* Handle overlapping ranges by splitting the new range. */
> - if (range->start_pc < outer->start_pc || range->end_pc > outer->end_pc)
> - {
> - struct eh_range *h = xmalloc (sizeof (struct eh_range));
> - if (range->start_pc < outer->start_pc)
> - {
> - h->start_pc = range->start_pc;
> - h->end_pc = outer->start_pc;
> - range->start_pc = outer->start_pc;
> - }
> - else
> - {
> - h->start_pc = outer->end_pc;
> - h->end_pc = range->end_pc;
> - range->end_pc = outer->end_pc;
> - }
> - h->first_child = NULL;
> - h->outer = NULL;
> - h->handlers = build_tree_list (TREE_PURPOSE (range->handlers),
> - TREE_VALUE (range->handlers));
> - h->next_sibling = NULL;
> - h->expanded = 0;
> - h->stmt = NULL;
> - /* Restart both from the top to avoid having to make this
> - function smart about reentrancy. */
> - link_handler (h, &whole_range);
> - link_handler (range, &whole_range);
> - return;
> - }
> -
> - ptr = &outer->first_child;
> - for (;; ptr = &(*ptr)->next_sibling)
> - {
> - if (*ptr == NULL || range->end_pc <= (*ptr)->start_pc)
> - {
> - range->next_sibling = *ptr;
> - range->first_child = NULL;
> - range->outer = outer;
> - *ptr = range;
> - return;
> - }
> - else if (range->start_pc < (*ptr)->end_pc)
> - {
> - link_handler (range, *ptr);
> - return;
> - }
> - /* end_pc > (*ptr)->start_pc && start_pc >= (*ptr)->end_pc. */
> - }
> - }
> -
> - /* The first pass of exception range processing (calling add_handler)
> - constructs a linked list of exception ranges. We turn this into
> - the data structure expected by the rest of the code, and also
> - ensure that exception ranges are properly nested. */
> -
> - void
> - handle_nested_ranges (void)
> - {
> - struct eh_range *ptr, *next;
> -
> - ptr = whole_range.first_child;
> - whole_range.first_child = NULL;
> - for (; ptr; ptr = next)
> - {
> - next = ptr->next_sibling;
> - ptr->next_sibling = NULL;
> - link_handler (ptr, &whole_range);
> - }
> - }
> -
> - /* Free RANGE as well as its children and siblings. */
> -
> static void
> free_eh_ranges (struct eh_range *range)
> {
> --- 162,167 ----
> *************** method_init_exceptions (void)
> *** 252,306 ****
> cache_range_start = 0xFFFFFF;
> }
>
> ! /* Add an exception range. If we already have an exception range
> ! which has the same handler and label, and the new range overlaps
> ! that one, then we simply extend the existing range. Some bytecode
> ! obfuscators generate seemingly nonoverlapping exception ranges
> ! which, when coalesced, do in fact nest correctly.
> !
> ! This constructs an ordinary linked list which check_nested_ranges()
> ! later turns into the data structure we actually want.
>
> ! We expect the input to come in order of increasing START_PC. This
> ! function doesn't attempt to detect the case where two previously
> ! added disjoint ranges could be coalesced by a new range; that is
> ! what the sorting counteracts. */
>
> ! void
> add_handler (int start_pc, int end_pc, tree handler, tree type)
> {
> ! struct eh_range *ptr, *prev = NULL, *h;
>
> for (ptr = whole_range.first_child; ptr; ptr = ptr->next_sibling)
> {
> ! if (start_pc >= ptr->start_pc
> ! && start_pc <= ptr->end_pc
> ! && TREE_PURPOSE (ptr->handlers) == type
> ! && TREE_VALUE (ptr->handlers) == handler)
> {
> ! /* Already found an overlapping range, so coalesce. */
> ! ptr->end_pc = MAX (ptr->end_pc, end_pc);
> ! return;
> }
> ! prev = ptr;
> }
>
> h = xmalloc (sizeof (struct eh_range));
> h->start_pc = start_pc;
> h->end_pc = end_pc;
> h->first_child = NULL;
> ! h->outer = NULL;
> h->handlers = build_tree_list (type, handler);
> h->next_sibling = NULL;
> h->expanded = 0;
> h->stmt = NULL;
>
> ! if (prev == NULL)
> ! whole_range.first_child = h;
> ! else
> ! prev->next_sibling = h;
> ! }
>
> /* if there are any handlers for this range, issue start of region */
> static void
> expand_start_java_handler (struct eh_range *range)
> --- 189,354 ----
> cache_range_start = 0xFFFFFF;
> }
>
> ! /* Split an exception range into two at PC. The sub-ranges that
> ! belong to the range are split and distributed between the two new
> ! ranges. */
> !
> ! static void
> ! split_range (struct eh_range *range, int pc)
> ! {
> ! struct eh_range *ptr;
> ! struct eh_range **first_child, **second_child;
> ! struct eh_range *h;
> !
> ! /* First, split all the sub-ranges. */
> ! for (ptr = range->first_child; ptr; ptr = ptr->next_sibling)
> ! {
> ! if (pc > ptr->start_pc
> ! && pc < ptr->end_pc)
> ! {
> ! split_range (ptr, pc);
> ! }
> ! }
> !
> ! /* Create a new range. */
> ! h = xmalloc (sizeof (struct eh_range));
> !
> ! h->start_pc = pc;
> ! h->end_pc = range->end_pc;
> ! h->next_sibling = range->next_sibling;
> ! range->next_sibling = h;
> ! range->end_pc = pc;
> ! h->handlers = build_tree_list (TREE_PURPOSE (range->handlers),
> ! TREE_VALUE (range->handlers));
> ! h->next_sibling = NULL;
> ! h->expanded = 0;
> ! h->stmt = NULL;
> ! h->outer = range->outer;
> ! h->first_child = NULL;
> !
> ! ptr = range->first_child;
> ! first_child = &range->first_child;
> ! second_child = &h->first_child;
> !
> ! /* Distribute the sub-ranges bewteen the two new ranges. */
> ! for (ptr = range->first_child; ptr; ptr = ptr->next_sibling)
> ! {
> ! if (ptr->start_pc < pc)
> ! {
> ! *first_child = ptr;
> ! ptr->outer = range;
> ! first_child = &ptr->next_sibling;
> ! }
> ! else
> ! {
> ! *second_child = ptr;
> ! ptr->outer = h;
> ! second_child = &ptr->next_sibling;
> ! }
> ! }
> ! *first_child = NULL;
> ! *second_child = NULL;
> ! }
> !
> !
> ! /* Add an exception range.
> !
> ! There are some missed optimization opportunities here. For
> ! example, some bytecode obfuscators generate seemingly
> ! nonoverlapping exception ranges which, when coalesced, do in fact
> ! nest correctly. We could merge these, but we'd have to fix up all
> ! the enclosed regions first and perhaps create a new range anyway if
> ! it overlapped existing ranges.
>
> ! Also, we don't attempt to detect the case where two previously
> ! added disjoint ranges could be coalesced by a new range. */
>
> ! void
> add_handler (int start_pc, int end_pc, tree handler, tree type)
> {
> ! struct eh_range *ptr, *h;
> ! struct eh_range **first_child, **prev;
>
> + /* First, split all the existing ranges that we need to enclose. */
> for (ptr = whole_range.first_child; ptr; ptr = ptr->next_sibling)
> {
> ! if (start_pc > ptr->start_pc
> ! && start_pc < ptr->end_pc)
> {
> ! split_range (ptr, start_pc);
> }
> !
> ! if (end_pc > ptr->start_pc
> ! && end_pc < ptr->end_pc)
> ! {
> ! split_range (ptr, end_pc);
> ! }
> !
> ! if (ptr->start_pc >= end_pc)
> ! break;
> }
>
> + /* Create the new range. */
> h = xmalloc (sizeof (struct eh_range));
> + first_child = &h->first_child;
> +
> h->start_pc = start_pc;
> h->end_pc = end_pc;
> h->first_child = NULL;
> ! h->outer = NULL_EH_RANGE;
> h->handlers = build_tree_list (type, handler);
> h->next_sibling = NULL;
> h->expanded = 0;
> h->stmt = NULL;
>
> ! /* Find every range at the top level that will be a sub-range of the
> ! range we're inserting and make it so. */
> ! {
> ! struct eh_range **prev = &whole_range.first_child;
> ! for (ptr = *prev; ptr;)
> ! {
> ! struct eh_range *next = ptr->next_sibling;
> !
> ! if (ptr->start_pc >= end_pc)
> ! break;
>
> + if (ptr->start_pc < start_pc)
> + {
> + prev = &ptr->next_sibling;
> + }
> + else if (ptr->start_pc >= start_pc
> + && ptr->start_pc < end_pc)
> + {
> + *prev = next;
> + *first_child = ptr;
> + first_child = &ptr->next_sibling;
> + ptr->outer = h;
> + ptr->next_sibling = NULL;
> + }
> +
> + ptr = next;
> + }
> + }
> +
> + /* Find the right place to insert the new range. */
> + prev = &whole_range.first_child;
> + for (ptr = *prev; ptr; prev = &ptr->next_sibling, ptr = ptr->next_sibling)
> + {
> + gcc_assert (ptr->outer == NULL_EH_RANGE);
> + if (ptr->start_pc >= start_pc)
> + break;
> + }
> +
> + /* And insert it there. */
> + *prev = h;
> + if (ptr)
> + {
> + h->next_sibling = ptr;
> + h->outer = ptr->outer;
> + }
> + }
> +
> +
> /* if there are any handlers for this range, issue start of region */
> static void
> expand_start_java_handler (struct eh_range *range)
> Index: java-except.h
> ===================================================================
> RCS file: /cvs/gcc/gcc/gcc/java/java-except.h,v
> retrieving revision 1.18
> diff -c -p -r1.18 java-except.h
> *** java-except.h 12 Feb 2005 15:21:14 -0000 1.18
> --- java-except.h 18 Apr 2005 18:58:45 -0000
> *************** struct eh_range
> *** 54,61 ****
>
> /* The TRY_CATCH_EXPR for this EH range. */
> tree stmt;
> -
> - tree handler;
> };
>
> /* A dummy range that represents the entire method. */
> --- 54,59 ----
> *************** extern struct eh_range * find_handler (i
> *** 67,71 ****
> extern void method_init_exceptions (void);
> extern void maybe_start_try (int, int);
> extern void add_handler (int, int, tree, tree);
> - extern void handle_nested_ranges (void);
> extern void expand_end_java_handler (struct eh_range *);
> --- 65,69 ----
> extern void method_init_exceptions (void);
> extern void maybe_start_try (int, int);
> extern void add_handler (int, int, tree, tree);
> extern void expand_end_java_handler (struct eh_range *);
> + extern bool sanity_check_exception_range (struct eh_range *);
> Index: verify-glue.c
> ===================================================================
> RCS file: /cvs/gcc/gcc/gcc/java/verify-glue.c,v
> retrieving revision 1.5
> diff -c -p -r1.5 verify-glue.c
> *** verify-glue.c 7 Mar 2005 21:10:49 -0000 1.5
> --- verify-glue.c 18 Apr 2005 18:58:45 -0000
> *************** verify_jvm_instructions_new (JCF *jcf, c
> *** 487,493 ****
> instruction_bits[handler_pc] |= BCODE_EXCEPTION_TARGET;
> }
>
> ! handle_nested_ranges ();
>
> method.method = current_function_decl;
> method.signature = build_java_signature (TREE_TYPE (current_function_decl));
> --- 487,493 ----
> instruction_bits[handler_pc] |= BCODE_EXCEPTION_TARGET;
> }
>
> ! gcc_assert (sanity_check_exception_range (&whole_range));
>
> method.method = current_function_decl;
> method.signature = build_java_signature (TREE_TYPE (current_function_decl));
> --
> fedora-devel-java-list mailing list
> fedora-devel-java-list at redhat.com
> https://www.redhat.com/mailman/listinfo/fedora-devel-java-list
More information about the fedora-devel-java-list
mailing list