ddb@mrvax.DEC (DAVID DYER-BENNET MRO1-2/L14 DTN 231-4076) (10/24/84)
I will propose a semantics for setjmp/longjmp that is compiler-independent, implementable in a not-too-bad worst-case way on all machines with which I am familiar, and reasonably consistent with current implementations. I see in previous articles enough information to convince me that we can't ask that all local variables by restored in longjmp to their values at the time of the setjmp, or to their last value in the routine containing the setjmp. If there is a way to achieve this that is affordable, I'd far prefer it to my proposal. So the problem comes down to characterizing which variables will be restored, and which will have random values. I propose that only variables not altered since the setjmp call be guaranteed restored; other variables will have random values (not necessarily their last set value, the value they had at the time of the setjmp, or anything else). This is obviously achievable by simply saving and restoring all registers at setjmp/longjmp; on some hardware considerable improvements in this can be made. So, is this enough to be useful? And, is more achievable transportably? I don't see how to achieve more transportably; if someone can, good. I find this enough to be useful, but only marginally. It is at least fairly easy to know what I can count on. I consider a little bit that I can count on better than a lot that isn't there when I need it. -- David Dyer-Bennet -- ...decwrl!dec-rhea!dec-mrvax!ddb
gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (10/29/84)
> I see in previous articles enough information to convince me that we can't > ask that all local variables by restored in longjmp to their values at the > time of the setjmp, or to their last value in the routine containing the > setjmp. If there is a way to achieve this that is affordable, I'd far > prefer it to my proposal. > > So the problem comes down to characterizing which variables will be > restored, and which will have random values. I propose that only > variables not altered since the setjmp call be guaranteed restored; other > variables will have random values (not necessarily their last set value, > the value they had at the time of the setjmp, or anything else). > This is obviously achievable by simply saving and restoring all registers > at setjmp/longjmp; on some hardware considerable improvements in this can > be made. > > So, is this enough to be useful? And, is more achievable transportably? I agree with Dennis Ritchie that longjmp should resume after the call to setjmp with all accessible data containing values as of the time of the longjmp. This can always be implemented, although some C run-time designs seem to have not saved enough information on the call stack to support longjmp. Such implementations need to be changed. Any system where a subroutine call saves all the non-scratch registers (e.g., PDP-11, Gould) is in good shape, as is any system where the number of registers actually saved is recorded in the call frame (e.g. VAX). Other systems may have to have their calling sequences re-engineered, but that is what they deserve for taking shortcuts.
johnl@godot.UUCP (11/01/84)
Doug Gwyn argues that longjmp() should always restore all variables in the calling routine, and that on machines where this is impossible (because you can't tell how many registers were saved at each call) the calling sequence "should be fixed." No dice. On machines without stack hardware, such as most notably the IBM 370 series, it's hard enough to set up a fast C calling sequence without adding extra restrictions on unwindability. When designing VM/IX (the version of Unix that runs under VM/370) it took them months to get a calling sequence that worked and was only a few instructions. Changing it so that longjmp() could unwind it would add several instructions to every call and return, or else require storing and loading many registers which don't change, a similar penalty. This seems a lot to pay for an occasional call to longjmp(). Sounds to me much like the traditional bad habit in much Berkeley software of assuming that you can dereference a zero pointer and find a zero there -- again much hardware makes this difficult or impossible, and in that case I believe we all agree that programs that depend on such behavior aren't portable. There seems to be a consensus that the semantics for longjmp() should promise that all variables in the calling routine except those declared to be "register" are restored after a longjmp(), which is both easy to understand and straightforward to implement on all hardware of which I am aware. It also happens to be the de-facto standard now, so claims that programs "can't work" if longjmp() behaves that way are hard to believe. Incendiarily, John Levine, ima!johnl or Levine@YALE.ARPA
stevel@haddock.UUCP (11/01/84)
How can one make setjmp longjmp work with a compiler that optimizes register usage to the point of holding values, both intermediate and automatics in registers across expressions. I know that pcc and the Ritchie compiler don't do this but some newer ones do. Forcing one to know what was going on in all the registers and where the current live value for automatic variable is limits how one can use graph coloring algoithms to allocate registers. Steve Ludlum, decvax!yale-co!ima!stevel, {amd|ihnp4!cbosgd}!ima!stevel
david@imd.UUCP (11/01/84)
>***** imd:net.lang.c / brl-tgr!gwyn / 9:56 am Oct 30, 1984 >I agree with Dennis Ritchie that longjmp should resume after the call >to setjmp with all accessible data containing values as of the time of >the longjmp. This can always be implemented, although some C run-time >designs seem to have not saved enough information on the call stack to >support longjmp. Such implementations need to be changed. Any system >where a subroutine call saves all the non-scratch registers (e.g., PDP-11, >Gould) is in good shape, as is any system where the number of registers >actually saved is recorded in the call frame (e.g. VAX). Other systems >may have to have their calling sequences re-engineered, but that is what >they deserve for taking shortcuts. Perhaps setjmp/longjmp can always be implemented. However its correct implementation is at the expense of performance. However, I would disagree that the "pdp-11 is in good shape". Either it works 100% of the time, or it should not be relied on. The following bugs indicate to me that it would be a lot of work to get setjmp/longjmp to work ALL THE TIME on the pdp11. The following code shows a problem with setjmp/longjmp in that if a routine does not follow proper register saving conventions, then longjmp cannot know that a register was saved temporarily. Our (system 3, I believe) ritchie pdp11 C compiler (C rel 2.3; UTS rel 1.3) generates a push and restore of register 2 around structure copies. If a longjmp is needed during the structure copy, longjmp will have no idea that this register was temporarily used for the structure copy. In the following code, the register variables are set to -1, and never used. Hence after a longjmp, they should all be -1. However, longjmp does not realize that register 2 was saved on the stack for the structure copy (as it was not done via csav). Hence, when the longjmp occurs, register variable 'k' is restored to an incorrect value (which I believe is the number of words left for the structure copy). [Note that the following code works correctly with a pdp11 pcc compiler, but fails with the ritchie compiler, since the code that pcc is generating is so silly]. struct { char array[16000]; } astruct, bstruct; int jmpbuf[32]; int alarmrtn (); main () { register int i, j, k; for (;;) { i = j = k = -1; /* set all register vars to -1. */ if (setjmp (jmpbuf) == 0) break; printf ("i = %d, j = %d, k = %d\n", i, j, k); } signal (14, alarmrtn); /* cause longjmp after 1 second. */ alarm (1); /* set alarm higher for a busy */ for (;;) /* system to be sure gets into loop.*/ astruct = bstruct; /* structure copy of 16000 bytes. */ } alarmrtn () { longjmp (jmpbuf, 1); } A similar problem occurs in libc.a or other assembly language routines that do not use proper csav subroutine linkage. For example our Strncmp(3) in libc.a [which may not be the distributed strncmp] is written in assembler, and again saves and restores register 2. However, even if one fixes these problems, one has to fix csav! Our csav is setting up a new frame (r5) prior to saving the registers, hence if one longjmps from an intr/alarm from 2 instructions inside csav, [easiest way is with a debugger] all register variables get clobbered! Also, it is possible on the pdp11 to leave the floating point in the wrong int/long (seti/setl) mode after a longjmp. This is probably worse than registers getting clobbered. This can be caused by a similar longjmp occuring during an intr/alarm of the C code: long = double; This bug can give core dumps! Unfortunately, the obvious fix requires putting in floating point into programs that have setjmp, even if they do not use floating point. [Which may break "ed" on pdp's without floating point when one hits <intr>]. So, even though I use setjmp/longjmp when I have to, didn't your mother ever tell you to stay away from setjmps/longjmps? :-) These views are my own, and are probably wrong! David Marx ima!imd!david
gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (11/01/84)
David Marx posted a nice article about the problems with longjmp'ing from a signal handler on PDP-11s. The problem is not quite so bad on systems with atomic linkage sequences but it is still tricky. This study reinforces a conclusion I drew long ago: About all one should do in response to an asynchronous trap is to set a flag for periodic testing from the main program loop. (That gives one MUCH better control over program internal operation.) It makes one wonder what good longjmp is after all... That was the only article so far that inclines me to surrender the desire for specifying clean longjmp semantics. There is not much point in insisting on that if it is unrealizable.
Anonymous@inmet.UUCP (11/02/84)
This message is empty.
thomson@uthub.UUCP (Brian Thomson) (11/03/84)
While I agree that setjmp/longjmp SHOULD behave according to Gospel, I have a great deal of sympathy for the implementation designer who doesn't want to sacrifice the efficiency of his calling sequence for the sake of longjmp. There is a similar issue of whether to use a stack frame pointer. Your call/return sequence will be faster if you don't, and you can address everything relative to the sp. All the frame pointer buys you is the ability to get a calling trace from your favourite core image debugger. Is it worth it? Some 780 instruction timings I did years ago indicated that the CALLS/RET pair take ~15 microseconds minimum, while JSR/RTS execute in 3. That's a big penalty for the sake of setjmp(), calling traces, and, oh yes, the infamous nargs(). -- Brian Thomson, CSRI Univ. of Toronto {linus,ihnp4,uw-beaver,floyd,utzoo}!utcsrgv!uthub!thomson
bsa@ncoast.UUCP (Brandon Allbery) (11/06/84)
> Article <1@imd.UUCP>, from david@imd.UUCP +---------------- | >I agree with Dennis Ritchie that longjmp should resume after the call | >to setjmp with all accessible data containing values as of the time of | >the longjmp. This can always be implemented, although some C run-time | >designs seem to have not saved enough information on the call stack to | >support longjmp. Such implementations need to be changed. Any system | >where a subroutine call saves all the non-scratch registers (e.g., PDP-11, | >Gould) is in good shape, as is any system where the number of registers | >actually saved is recorded in the call frame (e.g. VAX). Other systems | >may have to have their calling sequences re-engineered, but that is what | >they deserve for taking shortcuts. Ummm... it's easy to push the register mask when you moveml the registers to be saved on entry to a subroutine in the 68000, but moveml mask,(a7)- and moveml mask,(a7)+ expect the mask reversed from each other! How do you propose to invert a 16-bit mask end-for-end (i.e. 11100...001 <=> 100...00111 ) and still have a fast calling sequence? --bsa -- Brandon Allbery @ North Coast Xenix | the.world!ucbvax!decvax!cwruecmp! 6504 Chestnut Road, Independence, Ohio | {atvax!}ncoast!{tdi1!}bsa (216) 524-1416 \ 44131 | E1439@CSUOHIO.BITNET (friend's acct.) ---------------------------------------+--------------------------------------- `Confusion is my natural state.' -- Brandon Allbery @ North Coast Xenix | the.world!ucbvax!decvax!cwruecmp! 6504 Chestnut Road, Independence, Ohio | {atvax!}ncoast!{tdi1!}bsa (216) 524-1416 \ 44131 | E1439@CSUOHIO.BITNET (friend's acct.) ---------------------------------------+--------------------------------------- `Confusion is my natural state.'
henry@utzoo.UUCP (Henry Spencer) (11/08/84)
> Ummm... it's easy to push the register mask when you moveml the registers > to be saved on entry to a subroutine in the 68000, but moveml mask,(a7)- > and moveml mask,(a7)+ expect the mask reversed from each other! How > do you propose to invert a 16-bit mask end-for-end (i.e. 11100...001 > <=> 100...00111 ) and still have a fast calling sequence? Easy; you're pushing a constant. The inversion gets done at compile time. Bear in mind also that the only thing that ever has to decipher that mask is longjmp(), which doesn't have to be fast. Alas, even pushing a constant on every function call is a lot of expense when every call has to pay it just so a few can do longjmp(). -- Henry Spencer @ U of Toronto Zoology {allegra,ihnp4,linus,decvax}!utzoo!henry
gwyn@brl-tgr.ARPA (Doug Gwyn <gwyn>) (11/09/84)
> Ummm... it's easy to push the register mask when you moveml the registers > to be saved on entry to a subroutine in the 68000, but moveml mask,(a7)- > and moveml mask,(a7)+ expect the mask reversed from each other! How > do you propose to invert a 16-bit mask end-for-end (i.e. 11100...001 > <=> 100...00111 ) and still have a fast calling sequence? Just great -- more computer architecture brain damage! The only way I can see that makes sense to try is to record the NUMBER of registers saved (equivalently, the last register number) and use a quick scheme to generate the appropriate mask upon cret. (I am not very familiar with the 68000 and so I do not know if it has any fast way to set up such a mask; one way that would work is to index a mask table.) In any case, I have given up on setjmp/longjmp. I never had any use for them anyway. What a lossage.
ark@alice.UUCP (Andrew Koenig) (11/10/84)
Suppose you could arrange for a sorted table that contained triples: (routine start addr, routine end addr, register mask). You might need to make special provisions in the linker for this. While you're at it, make the linker sort the table and store the table start and end addresses in some canonical place. Then, as longjmp unwinds the stack, it can use the return address from each stack frame to do a binary search in this table to figure out what registers to restore.
chuck@dartvax.UUCP (Chuck Simmons) (11/13/84)
> Alas, even pushing a constant on every function call is a lot of expense > when every call has to pay it just so a few can do longjmp(). > > Henry Spencer @ U of Toronto Zoology I find it hard to believe that your compiler is so good at optimizing code that a single instructuion (okay, 2 instructions on our honeywell) on each function call is going to make that much difference. dartvax!chuck
henry@utzoo.UUCP (Henry Spencer) (11/13/84)
> > Alas, even pushing a constant on every function call is a lot of expense > > when every call has to pay it just so a few can do longjmp(). > > I find it hard to believe that your compiler is so good at optimizing > code that a single instructuion (okay, 2 instructions on our honeywell) > on each function call is going to make that much difference. I'm on a pdp11, where the calling sequence is not all that great, maybe eight or ten instructions (I forget...). Even adding one instruction to this is perhaps a 5-15% difference in speed. This is *not trivial* when we are talking about the most commonly-used control structure in C code -- if, while, and such don't even come close -- and when you bear in mind that the function-call sequence has a history of being a major factor in the speed of C code. No joke: when you profile a program on a machine like the 11, where the C calling sequence stubs show up in the profile, they usually aren't all that far down the sorted list. Function-call overhead is a major expense in C programs. -- Henry Spencer @ U of Toronto Zoology {allegra,ihnp4,linus,decvax}!utzoo!henry