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]

[PATCH] Repair __builtin_setjmp/__builtin_longjmp


Hi,

The __builtin_setjmp/__builtin_longjmp support has been broken since Tree-SSA 
was merged, see for example http://gcc.gnu.org/ml/gcc/2006-06/msg00577.html.
Now, as mentioned in the head comment of __builtin_setjmp, the Ada compiler 
does rely on it for proper operation, namely error recovery.

In GCC 3.x the support is piggybacked on non-local gotos: the __builtin_setjmp 
receiver labels are marked non-local, which causes the CFG machinery to 
create abnormal edges from every function call to them, which in turn coaxes 
the optimizers into doing the right thing; this works pretty well.

In GCC 4.x the support is still there at the RTL level, but there is strictly 
nothing at the tree level; this leads to wrong code at -O for very simple 
testcases (e.g. wrong result for setjmp-3.c, infinite loop for setjmp-4.c) 
because constants are wrongly propagated or blocks wrongly removed by the 
tree optimizers.

The attached patch is aimed at repairing the breakage by using essentially the 
same strategy as in GCC 3.x, i.e. to be piggybacked on non-local gotos.  In 
the process it fixes the bad interaction between non-local gotos and inlining 
(sticky abnormal edge for non-local-goto-1.c, ICE for non-local-goto-2.c).

However, the Ada compiler can be configured to use an EH scheme for its 
runtime based upon this support instead of the default DWARF-2 EH support 
(configure with --disable-libada, bootstrap, go in objdir/gcc and issue 'make 
gnatlib-sjlj').  Compiling the Ada runtime in this SJLJ mode revealed that 
the above approach based purely on non-local gotos is not usable in practice 
because it creates numerous abnormal call edges between numerous basic blocks 
in the tree CFG, resulting in gazillions of gigantic phi nodes that quickly 
eat up all the memory.

Therefore, the approach used in the patch at the tree level is twofold:
- reuse the non-local goto support to implement a per-function dispatcher 
block that is the destination of all the abnormal call edges,
- reuse the computed goto support to make the dispatcher block the source of 
all the abnormal edges to the __builtin_setjmp receivers.
The exact scheme is documented in the code.  As a result, only one basic block 
is both the destination and the source of all the abnormal edges and the 
memory footprint is little affected.

Bootstrapped/regtested on i586-suse-linux.  ACATS was also run at -O2 against 
the SJLJ EH runtime and the results are exactly on par with those of the 
DWARF-2 EH runtime.

OK for mainline?


2006-09-21  Eric Botcazou  <ebotcazou@adacore.com>

	* builtins.c (expand_builtin_setjmp): Delete.
	(expand_builtin) <BUILT_IN_SETJMP>: Mark as unreachable.
	<BUILT_IN_SETJMP_SETUP>: New case.
	<BUILT_IN_SETJMP_DISPATCHER>: Likewise.
	<BUILT_IN_SETJMP_RECEIVER>: Likewise.
	* builtins.def (BUILT_IN_SETJMP_SETUP): New built-in stub.
	(BUILT_IN_SETJMP_DISPATCHER): Likewise.
	(BUILT_IN_SETJMP_RECEIVER): Likewise.
	* gimple-low.c (struct lower_data): New field calls_builtin_setjmp.
	(lower_function_body): Initialize it to false.  If it is set to true
	at the end of the processing, emit the setjmp dispatcher.
	(lower_stmt) <CALL_EXPR>: Invoke lower_builtin_setjmp if the callee
	is __builtin_setjmp and set calls_builtin_setjmp to true as well.
	<MODIFY_EXPR>: Fall through to above case if there is a CALL_EXPR
	on the rhs of the assignment.
	(lower_builtin_setjmp): New function.
	* tree.c (build_common_builtin_nodes): Build BUILT_IN_SETJMP_SETUP,
	BUILT_IN_SETJMP_DISPATCHER and BUILT_IN_SETJMP_RECEIVER nodes.
	* tree-cfg.c (make_exit_edges) <CALL_EXPR>: Use specific predicate
	to detect calls that can go to non-local labels.  Use specific
	helper to create the abnormal edges associated with them.
	<MODIFY_EXPR>: Likewise.
	(make_abnormal_goto_edges): New function extracted from...
	(make_goto_expr_edges): ...here.  Call it for computed gotos.
	(simple_goto_p): Minor tweak.
	(tree_can_make_abnormal_goto): New predicate.
	(tree_redirect_edge_and_branch): Return zero on all abnormal edges.
	(tree_purge_dead_abnormal_call_edges): New function.
	* tree-flow.h (tree_can_make_abnormal_goto): Declare.
	(tree_purge_dead_abnormal_call_edges): Likewise.
	(make_abnormal_goto_edges): Likewise.
	* tree-inline.c (expand_call_inline): Simplify statement frobbing.
	Purge all dead abnormal edges if the call was in the last statement.
	* tree-optimize.c (has_abnormal_outgoing_edge_p): New predicate.
	(execute_fixup_cfg): If there are non-local labels in the function,
	scan the basic blocks and split them at calls that can go to non-local
	labels or add missing abnormal call edges.  Write down the CFG in the
	dump file.
	(pass_fixup_cfg): Remove TODO_dump_func flag.

ada/
        * decl.c (gnat_to_gnu_entity) <E_Procedure>: Do not DECL_IS_PURE on
        the node of the callee if SJLJ exceptions are used.
        * trans.c (Handled_Sequence_Of_Statements_to_gnu): Set TREE_NO_WARNING
        on the declaration node of JMPBUF_SAVE.
        * utils.c (init_gigi_decls): Set DECL_IS_PURE on the declaration nodes
        of Get_Jmpbuf_Address_Soft and Get_GNAT_Exception.
        * utils2.c (build_call_0_expr): Do not set TREE_SIDE_EFFECTS.


2006-09-21  Eric Botcazou  <ebotcazou@adacore.com>

	* gcc.dg/non-local-goto-1.c: New test.
	* gcc.dg/non-local-goto-2.c: Likewise.
	* gcc.dg/setjmp-3.c: Likewise.
	* gcc.dg/setjmp-4.c: Likewise.


-- 
Eric Botcazou
Index: builtins.c
===================================================================
--- builtins.c	(revision 117106)
+++ builtins.c	(working copy)
@@ -81,7 +81,6 @@ static int apply_result_size (void);
 #if defined (HAVE_untyped_call) || defined (HAVE_untyped_return)
 static rtx result_vector (int, rtx);
 #endif
-static rtx expand_builtin_setjmp (tree, rtx);
 static void expand_builtin_update_setjmp_buf (rtx);
 static void expand_builtin_prefetch (tree);
 static rtx expand_builtin_apply_args (void);
@@ -729,65 +728,6 @@ expand_builtin_setjmp_receiver (rtx rece
   emit_insn (gen_rtx_ASM_INPUT (VOIDmode, ""));
 }
 
-/* __builtin_setjmp is passed a pointer to an array of five words (not
-   all will be used on all machines).  It operates similarly to the C
-   library function of the same name, but is more efficient.  Much of
-   the code below (and for longjmp) is copied from the handling of
-   non-local gotos.
-
-   NOTE: This is intended for use by GNAT and the exception handling
-   scheme in the compiler and will only work in the method used by
-   them.  */
-
-static rtx
-expand_builtin_setjmp (tree arglist, rtx target)
-{
-  rtx buf_addr, next_lab, cont_lab;
-
-  if (!validate_arglist (arglist, POINTER_TYPE, VOID_TYPE))
-    return NULL_RTX;
-
-  if (target == 0 || !REG_P (target)
-      || REGNO (target) < FIRST_PSEUDO_REGISTER)
-    target = gen_reg_rtx (TYPE_MODE (integer_type_node));
-
-  buf_addr = expand_normal (TREE_VALUE (arglist));
-
-  next_lab = gen_label_rtx ();
-  cont_lab = gen_label_rtx ();
-
-  expand_builtin_setjmp_setup (buf_addr, next_lab);
-
-  /* Set TARGET to zero and branch to the continue label.  Use emit_jump to
-     ensure that pending stack adjustments are flushed.  */
-  emit_move_insn (target, const0_rtx);
-  emit_jump (cont_lab);
-
-  emit_label (next_lab);
-
-  /* Because setjmp and longjmp are not represented in the CFG, a cfgcleanup
-     may find that the basic block starting with NEXT_LAB is unreachable.
-     The whole block, along with NEXT_LAB, would be removed (see PR26983).
-     Make sure that never happens.  */
-  LABEL_PRESERVE_P (next_lab) = 1;
-     
-  expand_builtin_setjmp_receiver (next_lab);
-
-  /* Set TARGET to one.  */
-  emit_move_insn (target, const1_rtx);
-  emit_label (cont_lab);
-
-  /* Tell flow about the strange goings on.  Putting `next_lab' on
-     `nonlocal_goto_handler_labels' to indicates that function
-     calls may traverse the arc back to this label.  */
-
-  current_function_has_nonlocal_label = 1;
-  nonlocal_goto_handler_labels
-    = gen_rtx_EXPR_LIST (VOIDmode, next_lab, nonlocal_goto_handler_labels);
-
-  return target;
-}
-
 /* __builtin_longjmp is passed a pointer to an array of five words (not
    all will be used on all machines).  It operates similarly to the C
    library function of the same name, but is more efficient.  Much of
@@ -6077,18 +6017,61 @@ expand_builtin (tree exp, rtx target, rt
       break;
 
     case BUILT_IN_SETJMP:
-      target = expand_builtin_setjmp (arglist, target);
-      if (target)
-	return target;
+      /* This should have been lowered to the builtins below.  */
+      gcc_unreachable ();
+
+    case BUILT_IN_SETJMP_SETUP:
+      /* __builtin_setjmp_setup is passed a pointer to an array of five words
+          and the receiver label.  */
+      if (validate_arglist (arglist, POINTER_TYPE, POINTER_TYPE, VOID_TYPE))
+	{
+	  rtx buf_addr = expand_expr (TREE_VALUE (arglist), subtarget,
+				      VOIDmode, EXPAND_NORMAL);
+	  tree label = TREE_OPERAND (TREE_VALUE (TREE_CHAIN (arglist)), 0);
+	  rtx label_r = label_rtx (label);
+
+	  /* This is copied from the handling of non-local gotos.  */
+	  expand_builtin_setjmp_setup (buf_addr, label_r);
+	  nonlocal_goto_handler_labels
+	    = gen_rtx_EXPR_LIST (VOIDmode, label_r,
+				 nonlocal_goto_handler_labels);
+	  /* ??? Do not let expand_label treat us as such.  */
+	  FORCED_LABEL (label) = 0;
+	  return const0_rtx;
+	}
+      break;
+
+    case BUILT_IN_SETJMP_DISPATCHER:
+       /* __builtin_setjmp_dispatcher is passed the dispatcher label.  */
+      if (validate_arglist (arglist, POINTER_TYPE, VOID_TYPE))
+	{
+	  tree label = TREE_OPERAND (TREE_VALUE (arglist), 0);
+	  rtx label_r = label_rtx (label);
+
+	  /* Remove the dispatcher label from the list of non-local labels
+	     since the receiver labels have been added to it above.  */
+	  remove_node_from_expr_list (label_r, &nonlocal_goto_handler_labels);
+	  return const0_rtx;
+	}
+      break;
+
+    case BUILT_IN_SETJMP_RECEIVER:
+       /* __builtin_setjmp_receiver is passed the receiver label.  */
+      if (validate_arglist (arglist, POINTER_TYPE, VOID_TYPE))
+	{
+	  tree label = TREE_OPERAND (TREE_VALUE (arglist), 0);
+	  rtx label_r = label_rtx (label);
+
+	  expand_builtin_setjmp_receiver (label_r);
+	  return const0_rtx;
+	}
       break;
 
       /* __builtin_longjmp is passed a pointer to an array of five words.
 	 It's similar to the C library longjmp function but works with
 	 __builtin_setjmp above.  */
     case BUILT_IN_LONGJMP:
-      if (!validate_arglist (arglist, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
-	break;
-      else
+      if (validate_arglist (arglist, POINTER_TYPE, INTEGER_TYPE, VOID_TYPE))
 	{
 	  rtx buf_addr = expand_expr (TREE_VALUE (arglist), subtarget,
 				      VOIDmode, EXPAND_NORMAL);
@@ -6103,6 +6086,7 @@ expand_builtin (tree exp, rtx target, rt
 	  expand_builtin_longjmp (buf_addr, value);
 	  return const0_rtx;
 	}
+      break;
 
     case BUILT_IN_NONLOCAL_GOTO:
       target = expand_builtin_nonlocal_goto (arglist);
Index: builtins.def
===================================================================
--- builtins.def	(revision 117106)
+++ builtins.def	(working copy)
@@ -690,6 +690,11 @@ DEF_BUILTIN_STUB (BUILT_IN_INIT_TRAMPOLI
 DEF_BUILTIN_STUB (BUILT_IN_ADJUST_TRAMPOLINE, "__builtin_adjust_trampoline")
 DEF_BUILTIN_STUB (BUILT_IN_NONLOCAL_GOTO, "__builtin_nonlocal_goto")
 
+/* Implementing __builtin_setjmp.  */
+DEF_BUILTIN_STUB (BUILT_IN_SETJMP_SETUP, "__builtin_setjmp_setup")
+DEF_BUILTIN_STUB (BUILT_IN_SETJMP_DISPATCHER, "__builtin_setjmp_dispatcher")
+DEF_BUILTIN_STUB (BUILT_IN_SETJMP_RECEIVER, "__builtin_setjmp_receiver")
+
 /* Implementing variable sized local variables.  */
 DEF_BUILTIN_STUB (BUILT_IN_STACK_SAVE, "__builtin_stack_save")
 DEF_BUILTIN_STUB (BUILT_IN_STACK_RESTORE, "__builtin_stack_restore")
Index: gimple-low.c
===================================================================
--- gimple-low.c	(revision 117106)
+++ gimple-low.c	(working copy)
@@ -49,14 +49,18 @@ struct lower_data
   /* A TREE_LIST of label and return statements to be moved to the end
      of the function.  */
   tree return_statements;
+
+  /* True if the function calls __builtin_setjmp.  */
+  bool calls_builtin_setjmp;
 };
 
 static void lower_stmt (tree_stmt_iterator *, struct lower_data *);
 static void lower_bind_expr (tree_stmt_iterator *, struct lower_data *);
 static void lower_cond_expr (tree_stmt_iterator *, struct lower_data *);
 static void lower_return_expr (tree_stmt_iterator *, struct lower_data *);
+static void lower_builtin_setjmp (tree_stmt_iterator *);
 
-/* Lowers the body of current_function_decl.  */
+/* Lower the body of current_function_decl.  */
 
 static unsigned int
 lower_function_body (void)
@@ -113,6 +117,35 @@ lower_function_body (void)
       tsi_link_after (&i, x, TSI_CONTINUE_LINKING);
     }
 
+  /* If the function calls __builtin_setjmp, we need to emit the computed
+     goto that will serve as the unique dispatcher for all the receivers.  */
+  if (data.calls_builtin_setjmp)
+    {
+      tree disp_label, disp_var, arg;
+
+      /* Build 'DISP_LABEL:' and insert.  */
+      disp_label = create_artificial_label ();
+      /* This mark will create forward edges from every call site.  */
+      DECL_NONLOCAL (disp_label) = 1;
+      current_function_has_nonlocal_label = 1;
+      x = build1 (LABEL_EXPR, void_type_node, disp_label);
+      tsi_link_after (&i, x, TSI_CONTINUE_LINKING);
+
+      /* Build 'DISP_VAR = __builtin_setjmp_dispatcher (DISP_LABEL);'
+	 and insert.  */
+      disp_var = create_tmp_var (ptr_type_node, "setjmpvar");
+      t = build_addr (disp_label, current_function_decl);
+      arg = tree_cons (NULL, t, NULL);
+      t = implicit_built_in_decls[BUILT_IN_SETJMP_DISPATCHER];
+      t = build_function_call_expr (t,arg);
+      x = build2 (MODIFY_EXPR, void_type_node, disp_var, t);
+
+      /* Build 'goto DISP_VAR;' and insert.  */
+      tsi_link_after (&i, x, TSI_CONTINUE_LINKING);
+      x = build1 (GOTO_EXPR, void_type_node, disp_var);
+      tsi_link_after (&i, x, TSI_CONTINUE_LINKING);
+    }
+
   gcc_assert (data.block == DECL_INITIAL (current_function_decl));
   BLOCK_SUBBLOCKS (data.block)
     = blocks_nreverse (BLOCK_SUBBLOCKS (data.block));
@@ -139,7 +172,7 @@ struct tree_opt_pass pass_lower_cf = 
 };
 
 
-/* Lowers the EXPR.  Unlike gimplification the statements are not relowered
+/* Lower the EXPR.  Unlike gimplification the statements are not relowered
    when they are changed -- if this has to be done, the lowering routine must
    do it explicitly.  DATA is passed through the recursion.  */
 
@@ -171,7 +204,7 @@ lower_omp_directive (tree_stmt_iterator 
 }
 
 
-/* Lowers statement TSI.  DATA is passed through the recursion.  */
+/* Lower statement TSI.  DATA is passed through the recursion.  */
 
 static void
 lower_stmt (tree_stmt_iterator *tsi, struct lower_data *data)
@@ -207,8 +240,6 @@ lower_stmt (tree_stmt_iterator *tsi, str
       
     case NOP_EXPR:
     case ASM_EXPR:
-    case MODIFY_EXPR:
-    case CALL_EXPR:
     case GOTO_EXPR:
     case LABEL_EXPR:
     case SWITCH_EXPR:
@@ -223,6 +254,27 @@ lower_stmt (tree_stmt_iterator *tsi, str
     case OMP_CONTINUE:
       break;
 
+    case MODIFY_EXPR:
+      if (TREE_CODE (TREE_OPERAND (stmt, 1)) == CALL_EXPR)
+	stmt = TREE_OPERAND (stmt, 1);
+      else
+	break;
+      /* FALLTHRU */
+
+    case CALL_EXPR:
+      {
+	tree decl = get_callee_fndecl (stmt);
+	if (decl
+	    && DECL_BUILT_IN_CLASS (decl) == BUILT_IN_NORMAL
+	    && DECL_FUNCTION_CODE (decl) == BUILT_IN_SETJMP)
+	  {
+	    data->calls_builtin_setjmp = true;
+	    lower_builtin_setjmp (tsi);
+	    return;
+	  }
+      }
+      break;
+
     case OMP_PARALLEL:
       lower_omp_directive (tsi, data);
       return;
@@ -234,7 +286,7 @@ lower_stmt (tree_stmt_iterator *tsi, str
   tsi_next (tsi);
 }
 
-/* Lowers a bind_expr TSI.  DATA is passed through the recursion.  */
+/* Lower a bind_expr TSI.  DATA is passed through the recursion.  */
 
 static void
 lower_bind_expr (tree_stmt_iterator *tsi, struct lower_data *data)
@@ -403,7 +455,7 @@ block_may_fallthru (tree block)
     }
 }
 
-/* Lowers a cond_expr TSI.  DATA is passed through the recursion.  */
+/* Lower a cond_expr TSI.  DATA is passed through the recursion.  */
 
 static void
 lower_cond_expr (tree_stmt_iterator *tsi, struct lower_data *data)
@@ -498,6 +550,8 @@ lower_cond_expr (tree_stmt_iterator *tsi
   tsi_next (tsi);
 }
 
+/* Lower a return_expr TSI.  DATA is passed through the recursion.  */
+
 static void
 lower_return_expr (tree_stmt_iterator *tsi, struct lower_data *data)
 {
@@ -534,6 +588,129 @@ lower_return_expr (tree_stmt_iterator *t
   tsi_link_before (tsi, t, TSI_SAME_STMT);
   tsi_delink (tsi);
 }
+
+/* Lower a __builtin_setjmp TSI.
+
+   __builtin_setjmp is passed a pointer to an array of five words (not
+   all will be used on all machines).  It operates similarly to the C
+   library function of the same name, but is more efficient.
+
+   It is lowered into 3 other builtins, namely __builtin_setjmp_setup,
+   __builtin_setjmp_dispatcher and __builtin_setjmp_receiver, but with
+   __builtin_setjmp_dispatcher shared among all the instances; that's
+   why it is only emitted at the end by lower_function_body.
+
+   After full lowering, the body of the function should look like:
+
+    {
+      void * setjmpvar.0;
+      int D.1844;
+      int D.2844;
+
+      [...]
+
+      __builtin_setjmp_setup (&buf, &<D1847>);
+      D.1844 = 0;
+      goto <D1846>;
+      <D1847>:;
+      __builtin_setjmp_receiver (&<D1847>);
+      D.1844 = 1;
+      <D1846>:;
+      if (D.1844 == 0) goto <D1848>; else goto <D1849>;
+
+      [...]
+
+      __builtin_setjmp_setup (&buf, &<D2847>);
+      D.2844 = 0;
+      goto <D2846>;
+      <D2847>:;
+      __builtin_setjmp_receiver (&<D2847>);
+      D.2844 = 1;
+      <D2846>:;
+      if (D.2844 == 0) goto <D2848>; else goto <D2849>;
+
+      [...]
+
+      <D3850>:;
+      return;
+      <D3853>: [non-local];
+      setjmpvar.0 = __builtin_setjmp_dispatcher (&<D3853>);
+      goto setjmpvar.0;
+    }
+
+   NOTE: This is intended for use by GNAT and the exception handling
+   scheme in the compiler and will only work in the method used by
+   them.  */
+
+static void
+lower_builtin_setjmp (tree_stmt_iterator *tsi)
+{
+  tree stmt = tsi_stmt (*tsi);
+  tree cont_label = create_artificial_label ();
+  tree next_label = create_artificial_label ();
+  tree dest, t, arg;
+
+  /* NEXT_LABEL is the label __builtin_longjmp will jump to.  Its address is
+     passed to both __builtin_setjmp_setup and __builtin_setjmp_receiver.  */
+  FORCED_LABEL (next_label) = 1;
+
+  if (TREE_CODE (stmt) == MODIFY_EXPR)
+    {
+      dest = TREE_OPERAND (stmt, 0);
+      stmt = TREE_OPERAND (stmt, 1);
+    }
+  else
+    dest = NULL_TREE;
+
+  /* Build '__builtin_setjmp_setup (BUF, NEXT_LABEL)' and insert.  */
+  t = build_addr (next_label, current_function_decl);
+  arg = tree_cons (NULL, t, NULL);
+  t = TREE_VALUE (TREE_OPERAND (stmt, 1));
+  arg = tree_cons (NULL, t, arg);
+  t = implicit_built_in_decls[BUILT_IN_SETJMP_SETUP];
+  t = build_function_call_expr (t, arg);
+  SET_EXPR_LOCUS (t, EXPR_LOCUS (stmt));
+  tsi_link_before (tsi, t, TSI_SAME_STMT);
+
+  /* Build 'DEST = 0' and insert.  */
+  if (dest)
+    {
+      t = build2 (MODIFY_EXPR, void_type_node, dest, integer_zero_node);
+      SET_EXPR_LOCUS (t, EXPR_LOCUS (stmt));
+      tsi_link_before (tsi, t, TSI_SAME_STMT);
+    }
+
+  /* Build 'goto CONT_LABEL' and insert.  */
+  t = build1 (GOTO_EXPR, void_type_node, cont_label);
+  tsi_link_before (tsi, t, TSI_SAME_STMT);
+
+  /* Build 'NEXT_LABEL:' and insert.  */
+  t = build1 (LABEL_EXPR, void_type_node, next_label);
+  tsi_link_before (tsi, t, TSI_SAME_STMT);
+
+  /* Build '__builtin_setjmp_receiver (NEXT_LABEL)' and insert.  */
+  t = build_addr (next_label, current_function_decl);
+  arg = tree_cons (NULL, t, NULL);
+  t = implicit_built_in_decls[BUILT_IN_SETJMP_RECEIVER];
+  t = build_function_call_expr (t, arg);
+  SET_EXPR_LOCUS (t, EXPR_LOCUS (stmt));
+  tsi_link_before (tsi, t, TSI_SAME_STMT);
+
+  /* Build 'DEST = 1' and insert.  */
+  if (dest)
+    {
+      t = build2 (MODIFY_EXPR, void_type_node, dest, integer_one_node);
+      SET_EXPR_LOCUS (t, EXPR_LOCUS (stmt));
+      tsi_link_before (tsi, t, TSI_SAME_STMT);
+    }
+
+  /* Build 'CONT_LABEL:' and insert.  */
+  t = build1 (LABEL_EXPR, void_type_node, cont_label);
+  tsi_link_before (tsi, t, TSI_SAME_STMT);
+
+  /* Remove the call to __builtin_setjmp.  */
+  tsi_delink (tsi);
+}
 
 
 /* Record the variables in VARS into function FN.  */
Index: tree.c
===================================================================
--- tree.c	(revision 117106)
+++ tree.c	(working copy)
@@ -6698,6 +6698,26 @@ build_common_builtin_nodes (void)
 			"__builtin_nonlocal_goto",
 			ECF_NORETURN | ECF_NOTHROW);
 
+  tmp = tree_cons (NULL_TREE, ptr_type_node, void_list_node);
+  tmp = tree_cons (NULL_TREE, ptr_type_node, tmp);
+  ftype = build_function_type (void_type_node, tmp);
+  local_define_builtin ("__builtin_setjmp_setup", ftype,
+			BUILT_IN_SETJMP_SETUP,
+			"__builtin_setjmp_setup", ECF_NOTHROW);
+
+  tmp = tree_cons (NULL_TREE, ptr_type_node, void_list_node);
+  ftype = build_function_type (ptr_type_node, tmp);
+  local_define_builtin ("__builtin_setjmp_dispatcher", ftype,
+			BUILT_IN_SETJMP_DISPATCHER,
+			"__builtin_setjmp_dispatcher",
+			ECF_PURE | ECF_NOTHROW);
+
+  tmp = tree_cons (NULL_TREE, ptr_type_node, void_list_node);
+  ftype = build_function_type (void_type_node, tmp);
+  local_define_builtin ("__builtin_setjmp_receiver", ftype,
+			BUILT_IN_SETJMP_RECEIVER,
+			"__builtin_setjmp_receiver", ECF_NOTHROW);
+
   ftype = build_function_type (ptr_type_node, void_list_node);
   local_define_builtin ("__builtin_stack_save", ftype, BUILT_IN_STACK_SAVE,
 			"__builtin_stack_save", ECF_NOTHROW);
Index: tree-inline.c
===================================================================
--- tree-inline.c	(revision 117106)
+++ tree-inline.c	(working copy)
@@ -1930,6 +1930,7 @@ expand_call_inline (basic_block bb, tree
   edge e;
   block_stmt_iterator bsi, stmt_bsi;
   bool successfully_inlined = FALSE;
+  bool purge_dead_abnormal_edges;
   tree t_step;
   tree var;
 
@@ -2024,30 +2025,36 @@ expand_call_inline (basic_block bb, tree
 #endif
 
   /* We will be inlining this callee.  */
-
   id->eh_region = lookup_stmt_eh_region (stmt);
 
   /* Split the block holding the CALL_EXPR.  */
-
   e = split_block (bb, stmt);
   bb = e->src;
   return_block = e->dest;
   remove_edge (e);
 
-  /* split_block splits before the statement, work around this by moving
-     the call into the first half_bb.  Not pretty, but seems easier than
-     doing the CFG manipulation by hand when the CALL_EXPR is in the last
-     statement in BB.  */
+  /* split_block splits after the statement; work around this by
+     moving the call into the second block manually.  Not pretty,
+     but seems easier than doing the CFG manipulation by hand
+     when the CALL_EXPR is in the last statement of BB.  */
   stmt_bsi = bsi_last (bb);
+  bsi_remove (&stmt_bsi, false);
+
+  /* If the CALL_EXPR was in the last statement of BB, it may have
+     been the source of abnormal edges.  In this case, schedule
+     the removal of dead abnormal edges.  */
   bsi = bsi_start (return_block);
-  if (!bsi_end_p (bsi))
-    bsi_move_before (&stmt_bsi, &bsi);
-  else
+  if (bsi_end_p (bsi))
     {
-      tree stmt = bsi_stmt (stmt_bsi);
-      bsi_remove (&stmt_bsi, false);
       bsi_insert_after (&bsi, stmt, BSI_NEW_STMT);
+      purge_dead_abnormal_edges = true;
     }
+  else
+    {
+      bsi_insert_before (&bsi, stmt, BSI_NEW_STMT);
+      purge_dead_abnormal_edges = false;
+    }
+
   stmt_bsi = bsi_start (return_block);
 
   /* Build a block containing code to initialize the arguments, the
@@ -2147,9 +2154,8 @@ expand_call_inline (basic_block bb, tree
        tsi_delink() will leave the iterator in a sane state.  */
     bsi_remove (&stmt_bsi, true);
 
-  bsi_next (&bsi);
-  if (bsi_end_p (bsi))
-    tree_purge_dead_eh_edges (return_block);
+  if (purge_dead_abnormal_edges)
+    tree_purge_dead_abnormal_call_edges (return_block);
 
   /* If the value of the new expression is ignored, that's OK.  We
      don't warn about this for CALL_EXPRs, so we shouldn't warn about
Index: tree-optimize.c
===================================================================
--- tree-optimize.c	(revision 117106)
+++ tree-optimize.c	(working copy)
@@ -237,9 +237,26 @@ struct tree_opt_pass pass_free_cfg_annot
   0,					/* todo_flags_finish */
   0					/* letter */
 };
-/* Pass: fixup_cfg - IPA passes or compilation of earlier functions might've
-   changed some properties - such as marked functions nothrow.  Remove now
-   redundant edges and basic blocks.  */
+
+/* Return true if BB has at least one abnormal outgoing edge.  */
+
+static inline bool
+has_abnormal_outgoing_edge_p (basic_block bb)
+{
+  edge e;
+  edge_iterator ei;
+
+  FOR_EACH_EDGE (e, ei, bb->succs)
+    if (e->flags & EDGE_ABNORMAL)
+      return true;
+
+  return false;
+}
+
+/* Pass: fixup_cfg.  IPA passes, compilation of earlier functions or inlining
+   might have changed some properties, such as marked functions nothrow or
+   added calls that can potentially go to non-local labels.  Remove redundant
+   edges and basic blocks, and create new ones if necessary.  */
 
 static unsigned int
 execute_fixup_cfg (void)
@@ -262,8 +279,37 @@ execute_fixup_cfg (void)
 	  }
 	tree_purge_dead_eh_edges (bb);
       }
-    
+
+  if (current_function_has_nonlocal_label)
+    FOR_EACH_BB (bb)
+      {
+	for (bsi = bsi_start (bb); !bsi_end_p (bsi); bsi_next (&bsi))
+	  {
+	    tree stmt = bsi_stmt (bsi);
+	    if (tree_can_make_abnormal_goto (stmt))
+	      {
+		if (stmt == bsi_stmt (bsi_last (bb)))
+		  {
+		    if (!has_abnormal_outgoing_edge_p (bb))
+		      make_abnormal_goto_edges (bb, true);
+		  }
+		else
+		  {
+		    edge e = split_block (bb, stmt);
+		    bb = e->src;
+		    make_abnormal_goto_edges (bb, true);
+		  }
+		break;
+	      }
+	  }
+      }
+
   cleanup_tree_cfg ();
+
+  /* Dump a textual representation of the flowgraph.  */
+  if (dump_file)
+    dump_tree_cfg (dump_file, dump_flags);
+
   return 0;
 }
 
@@ -280,7 +326,7 @@ struct tree_opt_pass pass_fixup_cfg =
   0,					/* properties_provided */
   0,					/* properties_destroyed */
   0,					/* todo_flags_start */
-  TODO_dump_func,			/* todo_flags_finish */
+  0,					/* todo_flags_finish */
   0					/* letter */
 };
 
Index: tree-cfg.c
===================================================================
--- tree-cfg.c	(revision 117106)
+++ tree-cfg.c	(working copy)
@@ -489,9 +489,8 @@ make_edges (void)
 	      /* If this function receives a nonlocal goto, then we need to
 		 make edges from this call site to all the nonlocal goto
 		 handlers.  */
-	      if (TREE_SIDE_EFFECTS (last)
-		  && current_function_has_nonlocal_label)
-		make_goto_expr_edges (bb);
+	      if (tree_can_make_abnormal_goto (last))
+		make_abnormal_goto_edges (bb, true);
 
 	      /* If this statement has reachable exception handlers, then
 		 create abnormal edges to them.  */
@@ -507,10 +506,8 @@ make_edges (void)
 		  /* A MODIFY_EXPR may have a CALL_EXPR on its RHS and the
 		     CALL_EXPR may have an abnormal edge.  Search the RHS for
 		     this case and create any required edges.  */
-		  tree op = get_call_expr_in (last);
-		  if (op && TREE_SIDE_EFFECTS (op)
-		      && current_function_has_nonlocal_label)
-		    make_goto_expr_edges (bb);
+		  if (tree_can_make_abnormal_goto (last))
+		    make_abnormal_goto_edges (bb, true);  
 
 		  make_eh_edges (last);
 		}
@@ -836,76 +833,60 @@ label_to_block_fn (struct function *ifun
   return VEC_index (basic_block, ifun->cfg->x_label_to_block_map, uid);
 }
 
+/* Create edges for an abnormal goto statement at block BB.  If FOR_CALL
+   is true, the source statement is a CALL_EXPR instead of a GOTO_EXPR.  */
+
+void
+make_abnormal_goto_edges (basic_block bb, bool for_call)
+{
+  basic_block target_bb;
+  block_stmt_iterator bsi;
+
+  FOR_EACH_BB (target_bb)
+    for (bsi = bsi_start (target_bb); !bsi_end_p (bsi); bsi_next (&bsi))
+      {
+	tree target = bsi_stmt (bsi);
+
+	if (TREE_CODE (target) != LABEL_EXPR)
+	  break;
+
+	target = LABEL_EXPR_LABEL (target);
+
+	/* Make an edge to every label block that has been marked as a
+	   potential target for a computed goto or a non-local goto.  */
+	if ((FORCED_LABEL (target) && !for_call)
+	    || (DECL_NONLOCAL (target) && for_call))
+	  {
+	    make_edge (bb, target_bb, EDGE_ABNORMAL);
+	    break;
+	  }
+      }
+}
+
 /* Create edges for a goto statement at block BB.  */
 
 static void
 make_goto_expr_edges (basic_block bb)
 {
-  tree goto_t;
-  basic_block target_bb;
-  bool for_call;
   block_stmt_iterator last = bsi_last (bb);
+  tree goto_t = bsi_stmt (last);
 
-  goto_t = bsi_stmt (last);
-
-  /* If the last statement is not a GOTO (i.e., it is a RETURN_EXPR,
-     CALL_EXPR or MODIFY_EXPR), then the edge is an abnormal edge resulting
-     from a nonlocal goto.  */
-  if (TREE_CODE (goto_t) != GOTO_EXPR)
-    for_call = true;
-  else
+  /* A simple GOTO creates normal edges.  */
+  if (simple_goto_p (goto_t))
     {
       tree dest = GOTO_DESTINATION (goto_t);
-      for_call = false;
-
-      /* A GOTO to a local label creates normal edges.  */
-      if (simple_goto_p (goto_t))
-	{
-	  edge e = make_edge (bb, label_to_block (dest), EDGE_FALLTHRU);
+      edge e = make_edge (bb, label_to_block (dest), EDGE_FALLTHRU);
 #ifdef USE_MAPPED_LOCATION
-	  e->goto_locus = EXPR_LOCATION (goto_t);
+      e->goto_locus = EXPR_LOCATION (goto_t);
 #else
-	  e->goto_locus = EXPR_LOCUS (goto_t);
+      e->goto_locus = EXPR_LOCUS (goto_t);
 #endif
-	  bsi_remove (&last, true);
-	  return;
-	}
-
-      /* Nothing more to do for nonlocal gotos.  */
-      if (TREE_CODE (dest) == LABEL_DECL)
-	return;
-
-      /* Computed gotos remain.  */
+      bsi_remove (&last, true);
+      return;
     }
 
-  /* Look for the block starting with the destination label.  In the
-     case of a computed goto, make an edge to any label block we find
-     in the CFG.  */
-  FOR_EACH_BB (target_bb)
-    {
-      block_stmt_iterator bsi;
-
-      for (bsi = bsi_start (target_bb); !bsi_end_p (bsi); bsi_next (&bsi))
-	{
-	  tree target = bsi_stmt (bsi);
-
-	  if (TREE_CODE (target) != LABEL_EXPR)
-	    break;
-
-	  if (
-	      /* Computed GOTOs.  Make an edge to every label block that has
-		 been marked as a potential target for a computed goto.  */
-	      (FORCED_LABEL (LABEL_EXPR_LABEL (target)) && !for_call)
-	      /* Nonlocal GOTO target.  Make an edge to every label block
-		 that has been marked as a potential target for a nonlocal
-		 goto.  */
-	      || (DECL_NONLOCAL (LABEL_EXPR_LABEL (target)) && for_call))
-	    {
-	      make_edge (bb, target_bb, EDGE_ABNORMAL);
-	      break;
-	    }
-	}
-    }
+  /* A computed GOTO creates abnormal edges.  */
+  make_abnormal_goto_edges (bb, false);
 }
 
 
@@ -2517,13 +2498,31 @@ computed_goto_p (tree t)
 }
 
 
-/* Checks whether EXPR is a simple local goto.  */
+/* Return true if T is a simple local goto.  */
+
+bool
+simple_goto_p (tree t)
+{
+  return (TREE_CODE (t) == GOTO_EXPR
+	  && TREE_CODE (GOTO_DESTINATION (t)) == LABEL_DECL);
+}
+
+
+/* Return true if T can make an abnormal transfer of control flow.
+   Transfers of control flow associated with EH are excluded.  */
 
 bool
-simple_goto_p (tree expr)
+tree_can_make_abnormal_goto (tree t)
 {
-  return (TREE_CODE (expr) == GOTO_EXPR
-	  && TREE_CODE (GOTO_DESTINATION (expr)) == LABEL_DECL);
+  if (computed_goto_p (t))
+    return true;
+  if (TREE_CODE (t) == MODIFY_EXPR)
+    t = TREE_OPERAND (t, 1);
+  if (TREE_CODE (t) == WITH_SIZE_EXPR)
+    t = TREE_OPERAND (t, 0);
+  if (TREE_CODE (t) == CALL_EXPR)
+    return TREE_SIDE_EFFECTS (t) && current_function_has_nonlocal_label;
+  return false;
 }
 
 
@@ -4059,7 +4058,7 @@ tree_redirect_edge_and_branch (edge e, b
   edge ret;
   tree label, stmt;
 
-  if (e->flags & (EDGE_ABNORMAL_CALL | EDGE_EH))
+  if (e->flags & EDGE_ABNORMAL)
     return NULL;
 
   if (e->src != ENTRY_BLOCK_PTR
@@ -5361,6 +5360,41 @@ tree_flow_call_edges_add (sbitmap blocks
   return blocks_split;
 }
 
+/* Purge dead abnormal call edges from basic block BB.  */
+
+bool
+tree_purge_dead_abnormal_call_edges (basic_block bb)
+{
+  bool changed = tree_purge_dead_eh_edges (bb);
+
+  if (current_function_has_nonlocal_label)
+    {
+      tree stmt = last_stmt (bb);
+      edge_iterator ei;
+      edge e;
+
+      if (!(stmt && tree_can_make_abnormal_goto (stmt)))
+	for (ei = ei_start (bb->succs); (e = ei_safe_edge (ei)); )
+	  {
+	    if (e->flags & EDGE_ABNORMAL)
+	      {
+		remove_edge (e);
+		changed = true;
+	      }
+	    else
+	      ei_next (&ei);
+	  }
+
+      /* See tree_purge_dead_eh_edges below.  */
+      if (changed)
+	free_dominance_info (CDI_DOMINATORS);
+    }
+
+  return changed;
+}
+
+/* Purge dead EH edges from basic block BB.  */
+
 bool
 tree_purge_dead_eh_edges (basic_block bb)
 {
Index: decl.c
===================================================================
RCS file: /gnat.dev/cvs/Dev/gnat/decl.c,v
retrieving revision 1.63.2.174
diff -u -p -r1.63.2.174 decl.c
--- decl.c	12 Jul 2006 19:01:39 -0000	1.63.2.174
+++ decl.c	21 Sep 2006 13:19:37 -0000
@@ -3813,22 +3813,20 @@ gnat_to_gnu_entity (Entity_Id gnat_entit
 	if (TREE_CODE (gnu_return_type) == VOID_TYPE)
 	  pure_flag = false;
 
-	/* The semantics of Ada's "pure" essentially matches that of "const"
+	/* The semantics of "pure" in Ada essentially matches that of "const"
 	   in the back-end.  In particular, both properties are orthogonal to
 	   the "nothrow" property.  But this is true only if the EH circuitry
 	   is explicit in the internal representation of the back-end.  If we
 	   are to completely hide the EH circuitry from it, we need to declare
-	   that calls to pure Ada subprograms that can throw are attached to
-	   a particular EH region; since those regions are themselves defined
-	   by calls to support routines, consider that these internal calls
-	   modify a global object that pure Ada subprograms must read.  Which
-	   downgrades them from "const" to "pure" for the back-end.  */
+	   that calls to pure Ada subprograms that can throw have side effects
+	   since they can trigger an "abnormal" transfer of control flow; thus
+	   they can be neither "const" nor "pure" in the back-end sense.  */
 	gnu_type
 	  = build_qualified_type (gnu_type,
-				  (TYPE_QUALS (gnu_type)
-				   | (Exception_Mechanism == Setjmp_Longjmp
-				      ? 0 : TYPE_QUAL_CONST * pure_flag)
-				   | (TYPE_QUAL_VOLATILE * volatile_flag)));
+				  TYPE_QUALS (gnu_type)
+				  | (Exception_Mechanism == Back_End_Exceptions
+				     ? TYPE_QUAL_CONST * pure_flag : 0)
+				  | (TYPE_QUAL_VOLATILE * volatile_flag));
 
 	Sloc_to_locus (Sloc (gnat_entity), &input_location);
 
@@ -3901,10 +3899,6 @@ gnat_to_gnu_entity (Entity_Id gnat_entit
 					    extern_flag, attr_list,
 					    gnat_entity);
 
-            /* See above for the rationale.  */
-	    if (Exception_Mechanism == Setjmp_Longjmp)
-	      DECL_IS_PURE (gnu_decl) = pure_flag;
-
 	    DECL_STUBBED_P (gnu_decl)
 	      = Convention (gnat_entity) == Convention_Stubbed;
 	  }
Index: trans.c
===================================================================
RCS file: /gnat.dev/cvs/Dev/gnat/trans.c,v
retrieving revision 1.68.2.168
diff -u -p -r1.68.2.168 trans.c
--- trans.c	20 Sep 2006 18:58:44 -0000	1.68.2.168
+++ trans.c	21 Sep 2006 13:19:38 -0000
@@ -2229,6 +2229,11 @@ Handled_Sequence_Of_Statements_to_gnu (N
 					  build_call_0_expr (get_jmpbuf_decl),
 					  false, false, false, false, NULL,
 					  gnat_node);
+      /* The __builtin_setjmp receivers will immediately reinstall it.  Now
+	 because of the unstructured form of EH used by setjmp_longjmp, there
+	 might be forward edges going to __builtin_setjmp receivers on which
+	 it is uninitialized, although they will never be actually taken.  */
+      TREE_NO_WARNING (gnu_jmpsave_decl) = 1;
       gnu_jmpbuf_decl = create_var_decl (get_identifier ("JMP_BUF"),
 					 NULL_TREE, jmpbuf_type,
 					 NULL_TREE, false, false, false, false,
Index: utils2.c
===================================================================
RCS file: /gnat.dev/cvs/Dev/gnat/utils2.c,v
retrieving revision 1.21.4.56
diff -u -p -r1.21.4.56 utils2.c
--- utils2.c	4 Aug 2006 12:51:31 -0000	1.21.4.56
+++ utils2.c	21 Sep 2006 13:19:38 -0000
@@ -1432,7 +1432,8 @@ build_call_0_expr (tree fundecl)
 		      build_unary_op (ADDR_EXPR, NULL_TREE, fundecl),
 		      NULL_TREE, NULL_TREE);
 
-  TREE_SIDE_EFFECTS (call) = 1;
+  /* We rely on build3 to compute TREE_SIDE_EFFECTS.  This makes it possible
+     to propagate the DECL_IS_PURE flag on parameterless functions.  */
 
   return call;
 }
Index: utils.c
===================================================================
RCS file: /gnat.dev/cvs/Dev/gnat/utils.c,v
retrieving revision 1.48.2.128
diff -u -p -r1.48.2.128 utils.c
--- utils.c	12 Sep 2006 14:53:41 -0000	1.48.2.128
+++ utils.c	21 Sep 2006 13:19:39 -0000
@@ -609,6 +609,8 @@ init_gigi_decls (tree long_long_float_ty
     (get_identifier ("system__soft_links__get_jmpbuf_address_soft"),
      NULL_TREE, build_function_type (jmpbuf_ptr_type, NULL_TREE),
      NULL_TREE, false, true, true, NULL, Empty);
+  /* Avoid creating superfluous edges to __builtin_setjmp receivers.  */
+  DECL_IS_PURE (get_jmpbuf_decl) = 1;
 
   set_jmpbuf_decl
     = create_subprog_decl
@@ -625,6 +627,8 @@ init_gigi_decls (tree long_long_float_ty
      NULL_TREE,
      build_function_type (build_pointer_type (except_type_node), NULL_TREE),
      NULL_TREE, false, true, true, NULL, Empty);
+  /* Avoid creating superfluous edges to __builtin_setjmp receivers.  */
+  DECL_IS_PURE (get_excptr_decl) = 1;
 
   /* Functions that raise exceptions. */
   raise_nodefer_decl
/* { dg-do run } */
/* { dg-options "-O2" } */

extern void abort (void);

int global;

static foo(void) __attribute__((noinline));

static foo(void)
{
  global = 1;
}

static bar(void)
{
  foo ();
}

int execute(int cmd)
{
  __label__ start;

  void raise(void)
  {
    goto start;
  }

  int last;

  bar ();

  last = 0;

start:

  if (last == 0)
    while (1)
      {
        last = 1;
        raise ();
      }

  if (last == 0)
    return 0;
  else
    return cmd;
}

int main(void)
{
  if (execute (1) == 0)
    abort ();

  return 0;
}
/* { dg-do run } */
/* { dg-options "-O2" } */

extern void abort (void);

int global;

static foo(void) __attribute__((noinline));

static foo(void)
{
  global = 1;
}

static bar(void)
{
  foo ();
  global = 0;
}

int execute(int cmd)
{
  __label__ start;

  void raise(void)
  {
    goto start;
  }

  int last;

  bar ();

  last = 0;

start:

  if (last == 0)
    while (1)
      {
        last = 1;
        raise ();
      }

  if (last == 0)
    return 0;
  else
    return cmd;
}

int main(void)
{
  if (execute (1) == 0)
    abort ();

  return 0;
}
/* { dg-do run } */
/* { dg-options "-O" } */

#include <setjmp.h>

extern void abort (void);

jmp_buf buf;

void raise(void)
{
  __builtin_longjmp (buf, 1);
}

int execute(int cmd)
{
  int last = 0;

  if (__builtin_setjmp (buf) == 0)
    while (1)
      {
        last = 1;
        raise ();
      }

  if (last == 0)
    return 0;
  else
    return cmd;
}

int main(void)
{
  if (execute (1) == 0)
    abort ();

  return 0;
}
/* { dg-do run } */
/* { dg-options "-O" } */

#include <setjmp.h>

extern void abort (void);

jmp_buf buf;

void raise(void)
{
  __builtin_longjmp (buf, 1);
}

int execute(int cmd)
{
  int last = 0;

  __builtin_setjmp (buf);

  if (last == 0)
    while (1)
      {
        last = 1;
        raise ();
      }

  if (last == 0)
    return 0;
  else
    return cmd;
}

int main(void)
{
  if (execute (1) == 0)
    abort ();

  return 0;
}

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