[gnu.gcc.bug] old code vs. ANSI C setjmp and GCC 1.28

donn@CS.UTAH.EDU (Donn Seeley) (10/05/88)

Version: GNU C version 1.28 (68k, MIT syntax) compiled by GNU C version 1.27.
Machine type: HP9000/350 (25 MHz 68020) running Berkeley 4.3 Unix
Behavior:
	The semantics of setjmp() and longjmp() under Berkeley Unix on
	the VAX have traditionally been very liberal.  When the BSD
	longjmp() performs a non-local goto to a function instance which
	previously called setjmp(), all local variables are restored to
	the values they had when control was last in that function.
	Sun and (most if not all) AT&T implementations are faster but
	sacrifice intuitiveness -- variables with explicit storage
	class 'register' are not restored as in the Berkeley version
	unless the compiler ignores the 'register' specification and
	puts them on the stack; otherwise, register variables are
	changed to their values as of the last call to setjmp() (with
	the proper argument).  Sun flatly states that '[register
	variable] values are unpredictable'.  With the advent of ANSI
	C, even the guarantee of restoration of automatic variables
	disappears -- the new keyword 'volatile' must be used to
	distinguish variables whose value should be restored at
	longjmp() time.  The draft still requires that if the value of
	a variable does not change between the call to setjmp() and the
	point where control leaves the function for the last time
	before the longjmp() call, its value is predictable, so in most
	implementations, variables will have some sane value after
	longjmp(), although not necessarily the expected value.  (Of
	course my draft is not getting any newer...)

	The rules on using setjmp() are terribly arcane and are often
	misunderstood by programmers.  I recently undertook to examine
	all the uses of setjmp() in our version of the Berkeley source
	tree and found plenty of code that would not survive under AT&T
	(or Sun!) setjmp() semantics (including /bin/sh and /bin/csh),
	but there was still more code that quite reasonably did not
	anticipate the ANSI C setjmp() semantics.  Since we compile our
	Unix code with GCC and -traditional, we can't make use of the
	'volatile' keyword even if we would like to, and even if we
	were willing to hack all our makefiles to turn off optimization
	on Unix source files that contain setjmp() calls, we couldn't
	protect naive user code from complicated disasters.  The best
	alternative seemed to be a compiler trick -- since GCC already
	detects functions that call setjmp(), why not extend it with a
	flag that causes functions that call setjmp() to resume after
	longjmp() with all their variables restored?  We can then make
	this flag standard when the compiler is invoked as /bin/cc and
	naively written functions that call setjmp() are once again
	safe, as they are under Berkeley Unix on the VAX.  (The list
	of source files that can't hack ANSI C setjmp() has been sent
	to Berkeley...)

	Here's an edited typescript showing what happens when a
	function in traditional C meets a compiler that uses ANSI C
	setjmp() semantics:

------------------------------------------------------------------------
Script started on Tue Oct  4 03:44:05 1988
% cat sj.c
#include <setjmp.h>
jmp_buf reslab;
char setintr;

foo()
{
	int reenter;

	reenter = 0;
	(void) setjmp(reslab);
	reenter++;
	if (reenter == 1)
		foo2();
}
% cc-1.28 -v -O -S sj.c
gcc version 1.28
 /usr/src/gnu/gcc-1.28/cpp -nostdinc -v -I/usr/include -undef -D__GNU__ -D__GNUC__ -Dmc68000 -Dhp300 -Dunix -D__OPTIMIZE__ -traditional -D__HAVE_FPU__ sj.c /tmp/cc000986.cpp
GNU CPP version 1.28
 /usr/src/gnu/gcc-1.28/cc1 /tmp/cc000986.cpp -quiet -dumpbase sj.c -fwritable-strings -fno-defer-pop -O -traditional -version -o sj.s
GNU C version 1.28 (68k, MIT syntax) compiled by GNU C version 1.27.
% cat sj.s
#NO_APP
.text
	.even
.globl _foo
_foo:
	link a6,#0
	pea _reslab
	jbsr _setjmp
	addqw #4,sp
	jbsr _foo2	# So what happened to reenter?!?
	unlk a6
	rts
.comm _setintr,2
.comm _reslab,68
%
------------------------------------------------------------------------

Suggested fixes:
	The fix we chose limits optimization somewhat by forcing all
	local variables and parameters to reside on the stack; since
	the AT&T / Sun longjmp() restores the stack, this guarantees
	that local variable and parameter values are restored.  The
	compiler flag we selected is -fno-opt-setjmp; it does NOT
	suppress all optimization in a function that calls setjmp(),
	but merely restricts register allocation (thanks to RMS for
	this suggestion).  The effect is not unlike declaring all
	local variables and parameters with 'volatile'.

	Changes to c-tree.h:

------------------------------------------------------------------------
*** /tmp/,RCSt1000999	Tue Oct  4 03:53:39 1988
--- c-tree.h	Tue Oct  4 00:59:11 1988
***************
*** 68,73 ****
--- 68,75 ----
  
  extern tree make_index_type ();
  
+ extern void change_regvars_to_stackvars();
+ 
  extern tree double_type_node, long_double_type_node, float_type_node;
  extern tree char_type_node, unsigned_char_type_node, signed_char_type_node;
  
***************
*** 78,83 ****
--- 80,86 ----
  
  extern int current_function_returns_value;
  extern int current_function_returns_null;
+ extern int current_function_uses_setjmp;
  
  extern void yyerror();
  extern int lineno;
***************
*** 122,124 ****
--- 125,131 ----
  /* Nonzero means do some things the same way PCC does.  */
  
  extern int flag_traditional;
+ 
+ /* Nonzero means don't optimize functions that contain calls to setjmp.  */
+ 
+ extern int flag_no_opt_setjmp;
------------------------------------------------------------------------

	Changes to c-decl.c:

------------------------------------------------------------------------
*** /tmp/,RCSt1001021	Tue Oct  4 03:56:58 1988
--- c-decl.c	Tue Oct  4 00:58:43 1988
***************
*** 31,36 ****
--- 31,37 ----
  #include "flags.h"
  #include "c-tree.h"
  #include "c-parse.h"
+ #include "rtl.h"
  
  /* In grokdeclarator, distinguish syntactic contexts of declarators.  */
  enum decl_context
***************
*** 175,180 ****
--- 176,187 ----
  
  int current_function_returns_null;
  
+ /* Set to 0 at beginning of a function definition, set to 1 if
+    flag_no_opt_setjmp is set and a call to setjmp or _setjmp is
+    seen.  */
+ 
+ int current_function_uses_setjmp;
+ 
  /* Set to nonzero by `grokdeclarator' for a function
     whose return type is defaulted, if warnings for this are desired.  */
  
***************
*** 199,204 ****
--- 206,215 ----
  
  int flag_traditional;
  
+ /* Nonzero means don't optimize functions that contain calls to setjmp.  */
+ 
+ int flag_no_opt_setjmp;
+ 
  /* Nonzero means warn about implicit declarations.  */
  
  int warn_implicit;
***************
*** 241,246 ****
--- 252,259 ----
      flag_cond_mismatch = 1;
    else if (!strcmp (p, "-fno-asm"))
      flag_no_asm = 1;
+   else if (!strcmp (p, "-fno-opt-setjmp"))
+     flag_no_opt_setjmp = 1;
    else if (!strcmp (p, "-traditional"))
      flag_traditional = 1, dollars_in_ident = 1;
    else if (!strcmp (p, "-ansi"))
***************
*** 3299,3302 ****
--- 3312,3338 ----
  
    /* Let the error reporting routines know that we're outside a function.  */
    current_function_decl = NULL;
+ 
+   /* Recover after processing a function that calls setjmp.  */
+   current_function_uses_setjmp = 0;
+ }
+ 
+ /* Support for -fno-opt-setjmp:  convert register parameters and
+    variables to their stack forms.  */
+ 
+ void
+ change_regvars_to_stackvars ()
+ {
+   struct binding_level *level;
+   tree decl;
+ 
+   if (! current_binding_level)
+     return;
+   for (level = current_binding_level;
+       level != global_binding_level;
+       level = level->level_chain)
+     for (decl = level->names; decl; decl = TREE_CHAIN (decl))
+       if ((TREE_CODE (decl) == VAR_DECL || TREE_CODE (decl) == PARM_DECL)
+ 	  && GET_CODE (DECL_RTL (decl)) == REG)
+ 	put_var_into_stack (decl);
  }
------------------------------------------------------------------------

	Changes to expr.c:

------------------------------------------------------------------------
*** /tmp/,RCSt1001026	Tue Oct  4 03:57:25 1988
--- expr.c	Tue Oct  4 03:08:56 1988
***************
*** 22,27 ****
--- 22,28 ----
  #include "config.h"
  #include "rtl.h"
  #include "tree.h"
+ #include "c-tree.h"
  #include "flags.h"
  #include "insn-flags.h"
  #include "insn-codes.h"
***************
*** 3635,3640 ****
--- 3636,3649 ----
      {
        frame_pointer_needed = 1;
        may_call_alloca = 1;
+     }
+ 
+   if (flag_no_opt_setjmp && is_setjmp && ! current_function_uses_setjmp)
+     {
+       /* If a call to setjmp is seen, try to preserve values of variables
+ 	 by forcing register variables onto the stack.  */
+       current_function_uses_setjmp = 1;
+       change_regvars_to_stackvars ();
      }
  
    /* Don't let pending stack adjusts add up to too much.
------------------------------------------------------------------------

	Changes to stmt.c:

------------------------------------------------------------------------
*** /tmp/,RCSt1001031	Tue Oct  4 03:57:38 1988
--- stmt.c	Tue Oct  4 03:23:06 1988
***************
*** 1430,1435 ****
--- 1430,1438 ----
    struct nesting *thisblock = block_stack;
    tree type = TREE_TYPE (decl);
  
+   /* No register variables if current_function_uses_setjmp is set.  */
+   extern int current_function_uses_setjmp;
+ 
    /* External function declarations are supposed to have been
       handled in assemble_variable.  Verify this.  */
  
***************
*** 1467,1473 ****
  		&& TREE_CODE (type) == REAL_TYPE)
  	   && ! TREE_VOLATILE (decl)
  	   && ! TREE_ADDRESSABLE (decl)
! 	   && (TREE_REGDECL (decl) || ! obey_regdecls))
      {
        /* Automatic variable that can go in a register.  */
        DECL_RTL (decl) = gen_reg_rtx (DECL_MODE (decl));
--- 1470,1477 ----
  		&& TREE_CODE (type) == REAL_TYPE)
  	   && ! TREE_VOLATILE (decl)
  	   && ! TREE_ADDRESSABLE (decl)
! 	   && (TREE_REGDECL (decl) || ! obey_regdecls)
! 	   && ! current_function_uses_setjmp)
      {
        /* Automatic variable that can go in a register.  */
        DECL_RTL (decl) = gen_reg_rtx (DECL_MODE (decl));
------------------------------------------------------------------------

	Here's the difference that the new option makes to the example
	above (thanks to Mike Hibler for culling this example from the
	tangled csh sources):

------------------------------------------------------------------------
% cc-1.28 -fno-opt-setjmp -v -O -S sj.c
gcc version 1.28
 /usr/src/gnu/gcc-1.28/cpp -nostdinc -v -I/usr/include -undef -D__GNU__ -D__GNUC__ -Dmc68000 -Dhp300 -Dunix -D__OPTIMIZE__ -traditional -D__HAVE_FPU__ sj.c /tmp/cc000992.cpp
GNU CPP version 1.28
 /usr/src/gnu/gcc-1.28/cc1 /tmp/cc000992.cpp -quiet -dumpbase sj.c -fwritable-strings -fno-defer-pop -fno-opt-setjmp -O -traditional -version -o sj.s
GNU C version 1.28 (68k, MIT syntax) compiled by GNU C version 1.27.
% cat sj.s
#NO_APP
.text
	.even
.globl _foo
_foo:
	link a6,#-4
	clrl a6@(-4)
	pea _reslab
	jbsr _setjmp
	addqw #4,sp
	addql #1,a6@(-4)	# Voila
	moveq #1,d1
	cmpl a6@(-4),d1
	jne L2
	jbsr _foo2
L2:
	unlk a6
	rts
.comm _setintr,2
.comm _reslab,68
% exit
script done on Tue Oct  4 03:45:52 1988
------------------------------------------------------------------------

	Here is some boilerplate for the internals document and the
	manual page (format as appropriate):

------------------------------------------------------------------------
    `-fno-opt-setjmp'
	  Do not optimize local variables or parameters in functions
	  that call `setjmp'.  Without this option, local variables or
	  parameters that are declared without the `volatile' keyword
	  may be unpredictable after a longjmp().  This is especially
	  useful with `-traditional' since the latter flag disables the
	  use of `volatile'.
------------------------------------------------------------------------

	While we're at it, it'd nice if something like the following
	could be added to the entry for -fno-defer-pop:

------------------------------------------------------------------------
	  Without this option, programs like `adb' which can't handle
	  the fancy `gdb' or `dbx' symbol table entries or which must
	  deal with stripped binaries may be unable to print argument
	  lists in stack traces.
------------------------------------------------------------------------

	This was obvious to some of us, but we've had complaints from
	users that this was not at all obvious to them...

	Enjoy,

	Donn Seeley    University of Utah CS Dept    donn@cs.utah.edu
	40 46' 6"N 111 50' 34"W    (801) 581-5668    utah-cs!donn