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