This is the mail archive of the gcc-patches@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[RFC] C++ vs forced unwinding


To refresh your memory, forced unwinding is a not-an-exception 
that runs only cleanups, but can't be caught.  It's intended to
be used by e.g. longjmp_unwind and pthread cancellation.

The code for this was never completed, as is clear from the todo
comments left about in except.c.  Now that glibc *really* wants
to use this, I've been prompted to finish the job.

There are a couple of points that I didn't expect:

First, some Standard C functions suddenly can throw exceptions,
of a kind.  Posix specifies a list of entry points that are
primary and secondary thread cancellation points.

Second, functions declared throw() will still propagate forced
unwind exceptions.  If we don't represent this, the cleanups get
deleted as unreachable.

Both of these points mean extra overhead for applications that
don't need forced unwinding, so I've conditionalized everything
under a new flag -fforced-unwind-exceptions.  I was thinking that
we could have -pthread imply this, since in theory folks are
supposed to use that for their threaded applications.

Third, presumably we need a way to indicate that a function
does not transitively call a function which is a cancellation
point.  Presumably this would be a new attribute.  Something
else to think about here is that forced unwinding does not only
apply to thread cancellation -- a user could use longjmp_unwind
for whatever purpose they wanted.  This last has prompted me to
wait before doing anything about this.

Fourth, how does libstdc++ fit in with this?  I presume that
the folks that like threads would like for libstdc++ functions
to be cancellable.  I also presume that other folk won't want
the extra overhead.  How should we control this?

It's still possible to do better than I have here.  I've tried
to disturb things at little as possible for now, and that meant
smashing (REG_EH_REGION 0) (which means nothrow) rather than try
to intuit that no catch handlers, but all cleanups, are reachable.
I'll persue this on mainline.

I'd really like this to be applied to the 3.3 branch, so that
we'll have an officially released compiler usable for glibc+nptl 
before the end of the year.  Mark, is this unreasonable now?


r~


	* except.c (convert_from_eh_region_ranges_1): Smash REG_EH_REGION
	notes for nothrow calls if flag_forced_unwind_exceptions.
	(build_post_landing_pads): Mind flag_forced_unwind_exceptions.
	(sjlj_find_directly_reachable_regions): Likewise.
	(reachable_handlers): Likewise.
	(can_throw_external): Likewise.
	(collect_one_action_chain): Record cleanups after catch-all and
	must-not-throw if flag_forced_unwind_exceptions.
	* flags.h (flag_forced_unwind_exceptions): Declare.
	* toplev.c (flag_forced_unwind_exceptions): New.
	(lang_independent_options): Add it.

	* g++.dg/eh/forced1.C: New.
	* g++.dg/eh/forced2.C: New.

Index: except.c
===================================================================
RCS file: /cvs/gcc/gcc/gcc/except.c,v
retrieving revision 1.233.2.1
diff -c -p -d -r1.233.2.1 except.c
*** except.c	17 Mar 2003 06:12:13 -0000	1.233.2.1
--- except.c	1 Apr 2003 00:12:13 -0000
*************** convert_from_eh_region_ranges_1 (pinsns,
*** 1109,1129 ****
  	}
        else if (INSN_P (insn))
  	{
! 	  if (cur > 0
! 	      && ! find_reg_note (insn, REG_EH_REGION, NULL_RTX)
! 	      /* Calls can always potentially throw exceptions, unless
! 		 they have a REG_EH_REGION note with a value of 0 or less.
! 		 Which should be the only possible kind so far.  */
! 	      && (GET_CODE (insn) == CALL_INSN
! 		  /* If we wanted exceptions for non-call insns, then
! 		     any may_trap_p instruction could throw.  */
! 		  || (flag_non_call_exceptions
! 		      && GET_CODE (PATTERN (insn)) != CLOBBER
! 		      && GET_CODE (PATTERN (insn)) != USE
! 		      && may_trap_p (PATTERN (insn)))))
  	    {
! 	      REG_NOTES (insn) = alloc_EXPR_LIST (REG_EH_REGION, GEN_INT (cur),
  						  REG_NOTES (insn));
  	    }
  
  	  if (GET_CODE (insn) == CALL_INSN
--- 1109,1148 ----
  	}
        else if (INSN_P (insn))
  	{
! 	  rtx note;
! 	  switch (cur)
  	    {
! 	    default:
! 	      /* An existing region note may be present to suppress
! 		 exception handling.  Anything with a note value of -1
! 		 cannot throw an exception of any kind.  A note value
! 		 of 0 means that "normal" exceptions are suppressed,
! 		 but not necessarily "forced unwind" exceptions.  */
! 	      note = find_reg_note (insn, REG_EH_REGION, NULL_RTX);
! 	      if (note)
! 		{
! 		  if (flag_forced_unwind_exceptions
! 		      && INTVAL (XEXP (note, 0)) >= 0)
! 		    XEXP (note, 0) = GEN_INT (cur);
! 		  break;
! 		}
! 
! 	      /* Calls can always potentially throw exceptions; if we wanted
! 		 exceptions for non-call insns, then any may_trap_p
! 		 instruction can throw.  */
! 	      if (GET_CODE (insn) != CALL_INSN
! 		  && (!flag_non_call_exceptions
! 		      || GET_CODE (PATTERN (insn)) == CLOBBER
! 		      || GET_CODE (PATTERN (insn)) == USE
! 		      || !may_trap_p (PATTERN (insn))))
! 		break;
! 
! 	      REG_NOTES (insn) = alloc_EXPR_LIST (REG_EH_REGION,
! 						  GEN_INT (cur),
  						  REG_NOTES (insn));
+ 
+ 	    case 0:
+ 	      break;
  	    }
  
  	  if (GET_CODE (insn) == CALL_INSN
*************** build_post_landing_pads ()
*** 1681,1689 ****
  	    struct eh_region *c;
  	    for (c = region->u.try.catch; c ; c = c->u.catch.next_catch)
  	      {
- 		/* ??? _Unwind_ForcedUnwind wants no match here.  */
  		if (c->u.catch.type_list == NULL)
! 		  emit_jump (c->label);
  		else
  		  {
  		    /* Need for one cmp/jump per type caught. Each type
--- 1700,1714 ----
  	    struct eh_region *c;
  	    for (c = region->u.try.catch; c ; c = c->u.catch.next_catch)
  	      {
  		if (c->u.catch.type_list == NULL)
! 		  {
! 		    if (flag_forced_unwind_exceptions)
! 		      emit_cmp_and_jump_insns
! 			(cfun->eh->filter, const0_rtx, GT, NULL_RTX,
! 			 word_mode, 0, c->label);
! 		    else
! 		      emit_jump (c->label);
! 		  }
  		else
  		  {
  		    /* Need for one cmp/jump per type caught. Each type
*************** build_post_landing_pads ()
*** 1744,1753 ****
  	  break;
  
  	case ERT_CLEANUP:
- 	case ERT_MUST_NOT_THROW:
  	  region->post_landing_pad = region->label;
  	  break;
  
  	case ERT_CATCH:
  	case ERT_THROW:
  	  /* Nothing to do.  */
--- 1769,1803 ----
  	  break;
  
  	case ERT_CLEANUP:
  	  region->post_landing_pad = region->label;
  	  break;
  
+ 	case ERT_MUST_NOT_THROW:
+ 	  /* See maybe_remove_eh_handler about removing region->label.  */
+ 	  if (flag_forced_unwind_exceptions && region->label)
+ 	    {
+ 	      region->post_landing_pad = gen_label_rtx ();
+ 
+ 	      start_sequence ();
+ 
+ 	      emit_label (region->post_landing_pad);
+ 	      emit_cmp_and_jump_insns (cfun->eh->filter, const0_rtx, GT,
+ 				       NULL_RTX, word_mode, 0, region->label);
+ 
+ 	      region->resume
+ 	        = emit_jump_insn (gen_rtx_RESX (VOIDmode,
+ 						region->region_number));
+ 	      emit_barrier ();
+ 
+ 	      seq = get_insns ();
+ 	      end_sequence ();
+ 
+ 	      emit_insn_before (seq, region->label);
+ 	    }
+ 	  else
+ 	    region->post_landing_pad = region->label;
+ 	  break;
+ 
  	case ERT_CATCH:
  	case ERT_THROW:
  	  /* Nothing to do.  */
*************** sjlj_find_directly_reachable_regions (lp
*** 1925,1930 ****
--- 1975,1993 ----
  	  if (rc != RNL_NOT_CAUGHT)
  	    break;
  	}
+ 
+       /* Forced unwind exceptions aren't blocked.  */
+       if (flag_forced_unwind_exceptions && rc == RNL_BLOCKED)
+ 	{
+           struct eh_region *r;
+ 	  for (r = region->outer; r ; r = r->outer)
+ 	    if (r->type == ERT_CLEANUP)
+ 	      {
+ 		rc = RNL_MAYBE_CAUGHT;
+ 		break;
+ 	      }
+ 	}
+ 
        if (rc == RNL_MAYBE_CAUGHT || rc == RNL_CAUGHT)
  	{
  	  lp_info[region->region_number].directly_reachable = 1;
*************** reachable_next_level (region, type_throw
*** 2579,2586 ****
  	for (c = region->u.try.catch; c ; c = c->u.catch.next_catch)
  	  {
  	    /* A catch-all handler ends the search.  */
- 	    /* ??? _Unwind_ForcedUnwind will want outer cleanups
- 	       to be run as well.  */
  	    if (c->u.catch.type_list == NULL)
  	      {
  		add_reachable_handler (info, region, c);
--- 2642,2647 ----
*************** reachable_handlers (insn)
*** 2765,2771 ****
    while (region)
      {
        if (reachable_next_level (region, type_thrown, &info) >= RNL_CAUGHT)
! 	break;
        /* If we have processed one cleanup, there is no point in
  	 processing any more of them.  Each cleanup will have an edge
  	 to the next outer cleanup region, so the flow graph will be
--- 2826,2846 ----
    while (region)
      {
        if (reachable_next_level (region, type_thrown, &info) >= RNL_CAUGHT)
! 	{
! 	  /* Forced unwind exceptions are neither BLOCKED nor CAUGHT.
! 	     Make sure the cleanup regions are reachable.  */
! 	  if (flag_forced_unwind_exceptions)
! 	    {
! 	      while ((region = region->outer) != NULL)
! 		if (region->type == ERT_CLEANUP)
! 		  {
! 		    add_reachable_handler (&info, region, region);
! 		    break;
! 		  }
! 	    }
! 	  break;
! 	}
! 
        /* If we have processed one cleanup, there is no point in
  	 processing any more of them.  Each cleanup will have an edge
  	 to the next outer cleanup region, so the flow graph will be
*************** can_throw_external (insn)
*** 2887,2892 ****
--- 2962,2971 ----
    if (INTVAL (XEXP (note, 0)) <= 0)
      return false;
  
+   /* Forced unwind excptions are not catchable.  */
+   if (flag_forced_unwind_exceptions && GET_CODE (insn) == CALL_INSN)
+     return true;
+ 
    region = cfun->eh->region_array[INTVAL (XEXP (note, 0))];
  
    type_thrown = NULL_TREE;
*************** collect_one_action_chain (ar_hash, regio
*** 3241,3252 ****
  	{
  	  if (c->u.catch.type_list == NULL)
  	    {
  	      /* Retrieve the filter from the head of the filter list
  		 where we have stored it (see assign_filter_values).  */
! 	      int filter
! 		= TREE_INT_CST_LOW (TREE_VALUE (c->u.catch.filter_list));
! 
! 	      next = add_action_record (ar_hash, filter, 0);
  	    }
  	  else
  	    {
--- 3320,3345 ----
  	{
  	  if (c->u.catch.type_list == NULL)
  	    {
+ 	      int filter;
+ 
+ 	      /* Forced exceptions run cleanups, always.  Record them if
+ 		 they exist.  */
+ 	      next = 0;
+ 	      if (flag_forced_unwind_exceptions)
+ 		{
+ 		  struct eh_region *r;
+ 		  for (r = c->outer; r ; r = r->outer)
+ 		    if (r->type == ERT_CLEANUP)
+ 		      {
+ 			next = add_action_record (ar_hash, 0, 0);
+ 			break;
+ 		      }
+ 		}
+ 
  	      /* Retrieve the filter from the head of the filter list
  		 where we have stored it (see assign_filter_values).  */
! 	      filter = TREE_INT_CST_LOW (TREE_VALUE (c->u.catch.filter_list));
! 	      next = add_action_record (ar_hash, filter, next);
  	    }
  	  else
  	    {
*************** collect_one_action_chain (ar_hash, regio
*** 3291,3296 ****
--- 3384,3396 ----
  	 requires no call-site entry.  Note that this differs from
  	 the no handler or cleanup case in that we do require an lsda
  	 to be generated.  Return a magic -2 value to record this.  */
+       if (flag_forced_unwind_exceptions)
+ 	{
+ 	  struct eh_region *r;
+ 	  for (r = region->outer; r ; r = r->outer)
+ 	    if (r->type == ERT_CLEANUP)
+ 	      return 0;
+ 	}
        return -2;
  
      case ERT_CATCH:
Index: flags.h
===================================================================
RCS file: /cvs/gcc/gcc/gcc/flags.h,v
retrieving revision 1.93
diff -c -p -d -r1.93 flags.h
*** flags.h	20 Oct 2002 19:18:29 -0000	1.93
--- flags.h	1 Apr 2003 00:12:14 -0000
*************** extern int flag_unwind_tables;
*** 485,490 ****
--- 485,494 ----
  
  extern int flag_asynchronous_unwind_tables;
  
+ /* Nonzero means allow for forced unwinding.  */
+ 
+ extern int flag_forced_unwind_exceptions;
+ 
  /* Nonzero means don't place uninitialized global data in common storage
     by default.  */
  
Index: toplev.c
===================================================================
RCS file: /cvs/gcc/gcc/gcc/toplev.c,v
retrieving revision 1.690.2.14
diff -c -p -d -r1.690.2.14 toplev.c
*** toplev.c	19 Mar 2003 04:42:23 -0000	1.690.2.14
--- toplev.c	1 Apr 2003 00:12:15 -0000
*************** int flag_unwind_tables = 0;
*** 727,732 ****
--- 727,736 ----
  
  int flag_asynchronous_unwind_tables = 0;
  
+ /* Nonzero means allow for forced unwinding.  */
+ 
+ int flag_forced_unwind_exceptions;
+ 
  /* Nonzero means don't place uninitialized global data in common storage
     by default.  */
  
*************** static const lang_independent_options f_
*** 1090,1095 ****
--- 1094,1101 ----
     N_("Generate unwind tables exact at each instruction boundary") },
    {"non-call-exceptions", &flag_non_call_exceptions, 1,
     N_("Support synchronous non-call exceptions") },
+   {"forced-unwind-exceptions", &flag_forced_unwind_exceptions, 1,
+    N_("Support forced unwinding, e.g. for thread cancellation") },
    {"profile-arcs", &profile_arc_flag, 1,
     N_("Insert arc based program profiling code") },
    {"test-coverage", &flag_test_coverage, 1,
Index: testsuite/g++.dg/eh/forced1.C
===================================================================
RCS file: testsuite/g++.dg/eh/forced1.C
diff -N testsuite/g++.dg/eh/forced1.C
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- testsuite/g++.dg/eh/forced1.C	1 Apr 2003 00:12:16 -0000
***************
*** 0 ****
--- 1,71 ----
+ // { dg-do run }
+ // { dg-options "-fforced-unwind-exceptions" }
+ 
+ // Test that forced unwinding runs all cleanups, and only cleanups.
+ 
+ #include <unwind.h>
+ #include <stdlib.h>
+ 
+ static int test = 0;
+ 
+ static _Unwind_Reason_Code
+ force_unwind_stop (int version, _Unwind_Action actions,
+                    _Unwind_Exception_Class exc_class,
+                    struct _Unwind_Exception *exc_obj,
+                    struct _Unwind_Context *context,
+                    void *stop_parameter)
+ {
+   if (actions & _UA_END_OF_STACK)
+     {
+       if (test != 5)
+         abort ();
+       exit (0);
+     }
+ 
+   return _URC_NO_REASON;
+ }
+ 
+ static void force_unwind ()
+ {
+   _Unwind_Exception *exc = new _Unwind_Exception;
+   exc->exception_class = 0;
+   exc->exception_cleanup = 0;
+                    
+   _Unwind_ForcedUnwind (exc, force_unwind_stop, 0);
+                    
+   abort ();
+ }
+ 
+ struct S
+ {
+   int bit;
+   S(int b) : bit(b) { }
+   ~S() { test |= bit; }
+ };
+   
+ static void doit ()
+ {
+   try {
+     S four(4);
+ 
+     try {
+       S one(1);
+       force_unwind ();
+   
+     } catch(...) { 
+       test |= 2;
+     }
+ 
+   } catch(...) {
+     test |= 8;
+   }
+ }
+ 
+ int main()
+ { 
+   try {
+     doit ();
+   } catch (...) {
+   }
+   abort ();
+ }
Index: testsuite/g++.dg/eh/forced2.C
===================================================================
RCS file: testsuite/g++.dg/eh/forced2.C
diff -N testsuite/g++.dg/eh/forced2.C
*** /dev/null	1 Jan 1970 00:00:00 -0000
--- testsuite/g++.dg/eh/forced2.C	1 Apr 2003 00:12:16 -0000
***************
*** 0 ****
--- 1,87 ----
+ // { dg-do run }
+ // { dg-options "-fforced-unwind-exceptions" }
+ 
+ // Test that forced unwinding runs all cleanups, and only cleanups.
+ 
+ #include <unwind.h>
+ #include <stdlib.h>
+ 
+ static int test = 0;
+ 
+ static _Unwind_Reason_Code
+ force_unwind_stop (int version, _Unwind_Action actions,
+                    _Unwind_Exception_Class exc_class,
+                    struct _Unwind_Exception *exc_obj,
+                    struct _Unwind_Context *context,
+                    void *stop_parameter)
+ {
+   if (actions & _UA_END_OF_STACK)
+     {
+       if (test != 5)
+         abort ();
+       exit (0);
+     }
+ 
+   return _URC_NO_REASON;
+ }
+ 
+ // Note that neither the noreturn nor the nothrow specification
+ // affects forced unwinding.
+ 
+ static void __attribute__((noreturn))
+ force_unwind () throw()
+ {
+   _Unwind_Exception *exc = new _Unwind_Exception;
+   exc->exception_class = 0;
+   exc->exception_cleanup = 0;
+                    
+   _Unwind_ForcedUnwind (exc, force_unwind_stop, 0);
+                    
+   abort ();
+ }
+ 
+ struct S
+ {
+   int bit;
+   S(int b) : bit(b) { }
+   ~S() { test |= bit; }
+ };
+   
+ static void doit_3 ()
+ {
+   S one(1);
+   force_unwind ();
+ }
+ 
+ static void doit_2 ()
+ {
+   try {
+     doit_3 ();
+   } catch (...) {
+     test |= 2;
+   }
+ }
+ 
+ static void doit_1 ()
+ {
+   S four(4);
+   doit_2 ();
+ }
+ 
+ static void doit ()
+ {
+   try {
+     doit_1 ();
+   } catch(...) {
+     test |= 8;
+   }
+ }
+ 
+ int main()
+ { 
+   try {
+     doit ();
+   } catch (...) {
+   }
+   abort ();
+ }


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]