9 ms·
Let's write a setjmp
- Simran-B 4y agoIs it considered harmful, and if so, why?
- deleted 4y ago[deleted]
- pm215 4y agoMostly for the usual "don't reinvent a wheel the language standard library already provides" reasons, I think. For instance glibc's x86setjmp/sigsetjmp have been updated to support shadow stacks, but if you'd rolled your own you'd have to do that yourself: https://elixir.bootlin.com/glibc/glibc-2.35/source/sysdeps/i386/setjmp.S https://elixir.bootlin.com/glibc/glibc-2.35/source/sysdeps/i...
- j16sdiz 4y agoYes, but the author was trying to avoid using libc. > Yesterday I wrote that setjmp is handy and that it would be nice to have without linking the C standard library. Related article from the same author. https://nullprogram.com/blog/2023/02/11/ https://nullprogram.com/blog/2023/02/11/
- pm215 4y agoPersonally I think that's throwing the baby out with the bathwater for most use cases. You could rephrase my comment as "if you're already committed to reinventing half of libc's wheels, this one is not really any harder or more awkward than most, but if you're not aiming for that overall goal then reinventing just this one wheel is a bad plan" if you like.
- adrian_b 4y ago"setjmp" and "longjmp" are the mechanism for implementing exceptions in C, i.e. they are just another form of writing "catch" and "throw". The implementation of exceptions, i.e. of jumps over multiple levels of nested functions and blocks, is much simpler in C than in C++, because there are no destructors that must be called when unwinding the stack, so it is enough to restore the CPU registers to the values correct for the program point where the exception must be caught. This is needed because at the point where the exception is thrown it is not known whether any of the nested functions that must be skipped has modified any of the registers that a function is expected to preserve and where the original values of the registers have been saved. Any kind of exceptions can be easily misused, which is why it is recommended to be careful with the use of "setjmp" and "longjmp", but they are not more harmful than the use of exceptions in any other language. The only additional problem of C is that since there are no implicitly called destructors, like in C++ and similar languages, in C the programmer must do what the compiler would do in C++. This means that if the nested functions that are skipped by a "longjmp" have allocated heap memory, opened files or sockets etc., such resources must be freed in the exception handler marked by a "setjmp". Therefore the C programmer must keep track of the resources that might have been allocated in the nested functions, e.g. by recording the allocations in some global table.
- ur-whale 4y ago> "setjmp" and "longjmp" are the mechanism for implementing exceptions in C Correct, but reductive. They can be used for many other things.
- adrian_b 4y agoThe more general POV is that "setjmp" defines a continuation and "longjmp" invokes it. While the most frequent use of explicit continuations is for implementing exceptions, you are right that there are many other programming techniques based on explicit continuations (for instance coroutines).
- someweirdperson 4y ago> They can be used for many other things. The underlying primitive offers even more flexibility, but exceptions (in the sense of programming languages) can also be used for many other things than exceptions (in the sense of natural language, to handle exceptional cases).
- bogomipz 4y ago>"... but exceptions (in the sense of programming languages) can also be used for many other things than exceptions ..." Can you elaborate? What are the other ways exceptions are used when not used to handle exceptional situations?
- randomNumber7 4y agoIt's like goto, but you can jump over function boundaries. Checking out the other comments here its not considered harmful...
- kazinator 4y agolongjmp is like return, which can jump out of a function, to the immediate caller. However, longjmp can return directly to a grandparent, great-grandparent, ... this happens without any unwind support; you have to invent your own conventions for that and implement them. Only carefully-written code which knows about the conventions of your particular longjmp wrapper library will get the unwinding.
- pjmlp 4y agoBesides external Assembler, MSVC's way is to use intrinsics. https://learn.microsoft.com/en-us/cpp/intrinsics/compiler-intrinsics?view=msvc-170 https://learn.microsoft.com/en-us/cpp/intrinsics/compiler-in... Which is kind of nice and already proven in 1961 as a better way to do low level coding in Burroughs B5000, nowadays still sold as Unisys ClearPath MCP (naturally with improvements). Intrinsics can be better understood by the compiler type system, both for safety and optimization algorithms.
- flohofwoe 4y agoDo the intrinsics allow to load and store registers from and to memory? That's kind of the whole point why assembly is required. Most cross-platform co-routine libraries I've seens for C or C++ use a small separate assembly file for the stack switching magic on MSVC, so if it's possible to do the same with intrinsics, then it's definitely not a well known technique ;) PS: at least the list of cross-platform intrinsics looks way too high level for a self-rolled setjmp/longjmp: https://learn.microsoft.com/en-us/cpp/intrinsics/intrinsics-available-on-all-architectures?view=msvc-170 https://learn.microsoft.com/en-us/cpp/intrinsics/intrinsics-...
- megous 4y agosetjmp/longjmp don't switch stacks (they just "move" up the current stack)
- jagrsw 4y agoWhile some programs/libs (most notably libjpeg) use setjmp/longjmp, I tend to avoid them: * Does setjmp/longjmp save/restore all registers (eg simd) of modern CPU variations? Can incosistiences happen here? * As a goto, it restores some context (registers) but not others (memory), which can be counterintuitive. Does it work with volatile variables * Does it play well with less standard C features like attribute(cleanup) or with C++ features like exception handling or class destructors? To avoid issues, it may be best to stick to very procedural and basic C when using setjmp/longjmp? * Except registers and memory, there are also IO, FS, Net (at least) contexts, and these are not restored (cannot be really), so this notion of "restore certain vars to their original states" might not work well with certain types of code Bc some programming gods recommend using setjmp/longjmp, my hesitance is likely unfounded.
- adrian_b 4y ago"setjmp/longjmp" must save/restore only those registers that are defined by the C ABI as being preserved across function calls. The program point to where "longjmp" jumps is viewed by the compiler as a function return point, so the compiler assumes that all the other registers hold undefined values. For most CPUs, the C ABI defines the SIMD registers as being not preserved across function calls, so "setjmp" does not need to save them. Had some of them been defined as preserved registers, "setjmp" would have also saved those. "setjmp/longjmp" cannot be used in any C++ program, unless all the nested functions that occur between a "setjmp" and a "longjmp" are C functions. This may happen in C libraries which are linked into C++ programs and which may use "setjmp/longjmp" internally, without interfering with C++ objects. As you say, "attribute(cleanup)" is not something specified in the C language standard, so you cannot expect that "longjmp" will invoke the cleanup function, unless you use a specific libc implementation, whose documentation explicitly says that its "longjmp" will invoke the cleanup functions, when the program is compiled with a certain C compiler. I am not aware of any such "longjmp" implementation. If you want to use exceptions in a C program, you must use "setjmp/longjmp". If you do not want to use exceptions in a C program, you have no need for them. The same happens in any other programming language. If you do not want to use exceptions in a C++ program, you have no need to use keywords like "try" and "throw".
- matthews2 4y agoRelated and also good fun, writing your own fibers! https://graphitemaster.github.io/fibers/ https://graphitemaster.github.io/fibers/
- IncRnd 4y agoI remember doing this 30ish years ago. It was all the rage. Lots of people did that to get multi-threading on the OS of the time.
- speps 4y agoI'm not sure you need to use assembly for writing the MSVC version, it supports the "naked" calling convention: https://learn.microsoft.com/en-us/cpp/cpp/rules-and-limitations-for-naked-functions https://learn.microsoft.com/en-us/cpp/cpp/rules-and-limitati... Similarly, it has the "noreturn" decl spec: https://learn.microsoft.com/en-us/cpp/cpp/noreturn https://learn.microsoft.com/en-us/cpp/cpp/noreturn It'd be interesting if that works the same as in GCC for the article 's objective.
- flohofwoe 4y agoMSVC doesn't support inline assembly for x86-64 (the declspecs are just hinting the compiler to not generate function entry/exit code and that it shouldn't be confused by the function not returning to the caller - not sure if declspec(naked) even works on x86-64 because I think it only makes sense with inline assembly).
- thdespou 4y agoThis is probably the geekiest article I can digest as a Front end engineer.
- mjburgess 4y agoAfter learning about effect systems, and generalising effects, my view on `setjmp` has changed considerably -- it seems effect systems "effectively" offer a design pattern for their use in languages without them. ie., it feels that there's an async-await.h, try-catch.h, etc. to be written which would serve as c-ish design patterns. I'd be interested, then, in to what degree other langs can do the same. As a side point, i've not yet read a good defence of why programming in C is so fun and satisfying in this way. People reduce the Rust/C issue to memory saftey... but isn't there something inherently wonderful about `while(*this++ = *that++)` (etc. etc.) ? (A sense of fun beaten out of you by too many annotation guaranteeing its saftey?)
- flohofwoe 4y agoThese days the 'fashionable' way to implement async-await seems to be via compiler magic by transforming async functions into a 'switch-case state machine' and a hidden context pointer argument. In vanilla C (without compiler magic) I've mostly seen it implemented via 'green threads' aka 'fibers' aka 'stack-switching', but TBH I'm not sure if this can be implemented with the standard setjmp/longjmp, I've mostly seen it implemented without (and instead use two small assembly functions for the context switch). One downside of stack-switching is that it doesn't work on WASM.
- chaosite 4y agostack switching with setjmp/longjmp can be implemented like this: https://stackoverflow.com/a/8817009/1116739 https://stackoverflow.com/a/8817009/1116739 But it's messy enough that you'd want a library/framework to help you handle it.
- mananaysiempre 4y agoPOSIX pre-2008 had (and Linux/Glibc and {Free,Net,DragonFly}BSD still have) <ucontext.h> with proper stack switching functions, used as a fallback in a number of coroutine libraries. The fallback status is due to a self-inflicted inefficiency: they save and restore the signal mask, thus still need to go through the kernel (why e.g. Linux does not put signal mask manipulation in the vDSO, I don’t know). POSIX yanked them and now recommends rewriting to use POSIX threads instead, which is asinine.
- smcameron 4y ago> The solution is to qualify r with volatile, which forces the compiler to store the variable on the stack and never cache it in a variable. Should the last word of that sentence be "register"?
- flykespice 4y agoI didn't know of setjump usage until I studied Lua's source code, they used it everywhere as a replacement for exceptions in C
- fanf2 4y agoThis is fun :-) I was pleased to learn about __builtin_longjmp. There's a small aside in this article about the signal mask, which skates past another horrible abyss - which might even make it sensible to DIY longjmp. Some of the nastiness can be seen in the POSIX rationale for sigsetjmp (https://pubs.opengroup.org/onlinepubs/9699919799.2018edition/functions/sigsetjmp.html https://pubs.opengroup.org/onlinepubs/9699919799.2018edition...) which says that on BSD-like systems, setjmp and _setjmp correspond to sigsetjmp and setjmp on System V Unixes. The effect is that setjmp might or might not involve a system call to adjust the signal mask. The syscall overhead might be OK for exceptional error recovery, such as the arena out of memory example, but it's likely to be more troublesome if you are implementing coroutines. But why would they need to mess with the signal mask? Well, if you are using BSD-style signals or you are using sigaction correctly, a signal handler will run with its signal masked. If you decide to longjmp out of the handler, you also need to take care to unmask the signal. On BSD-like systems, longjmp does that for you. The problem is that longjmp out of a signal handler is basically impossible to do correctly. (There's a whole flamewar in the wg14 committee documents on this subject.) So this is another example of libc being optimized for the unusual, broken case at the cost of the typical case.
- stevefan1999 4y agoI wonder if setjmp/longjmp can be implemented in hardware, i.e. introduce an instruction set that points, save and switch the current registers called register window that can be switched by saving a pointer. This way both setjmp and longjmp are basically few cycles and exception handling would be hella fast.
- snarfy 4y agoIntel has e.g. push all/pop all instructions. It doesn't save everything needed for setjmp/longjmp (like fpu state) but does a decent job.
- CalChris 4y agoThe DEC VAX-11 instruction set had SVPCTX and LDPCTX for the kernel which made context switch simple. But they were reserved instructions; so they couldn't be used for setjmp() and longjmp(). VAX-11 also had queue instructions which made rescheduling simple. This was basically: SETIPL SVPCTX INSQUE FFS REMQUE LDPCTX SETIPL Yeah, there was a little more. See page 188 of ... http://bitsavers.informatik.uni-stuttgart.de/pdf/dec/vax/vms/training/EY-0016E-SG-0001_VMS_Internals_Student_Workbook_1982.pdf http://bitsavers.informatik.uni-stuttgart.de/pdf/dec/vax/vms...
- magicalhippo 4y agoThis takes me back to the late 90s when I was a teen and decided to try to implement preemptive multi-threading in Turbo Pascal. I'd gotten a book on assembly so was somewhat versed in that, and took on the challenge. Having to save and restore the registers was obvious, but as I recall it the main challenge was figuring out the right sequence to do that while also preserving the CPU flags. I only used the timer interrupt to switch between threads in a round-robin way, so interactivity wasn't the best, but it worked. Was quite pleased with myself for accomplishing that. I think it was a pretty decent challenge, and one that was quite instructive. Not just did you need to know some details about the various CPU instructions (like which ones affected flags), but it was also a kind of a puzzle to arrange it all the right way.
- FpUser 4y agoHa, I did exactly the same in exactly same language but that was the end of 80s. Interactivity was just fine as I've increased frequency of timer interrupt to 4096 times / s and would pass control to standard handler every so often so it would work as standard at about 18 times/s. When not calling old interrupt I would execute threads switch and control logic. Turbo Pascal supported built in assembly so writing all low level stuff was piece of cake. I also used built in assembly to implement graphics.
- kazinator 4y agoThe ISO C longjmp has a silly feature. It takes an argument that is returned to the setjmp caller, but if it is specified as 0, it is rewritten to 1. It means that a longjmp instruction sequence has to contain an extra conditional check which substitutes a nonzero value for a zero value. Instead of the completely trivial, branch-free instruction sequence it wants to be. Of all the places where it would be good for C to compensate for a programmer mistake, why do it here? Why can't it just be that the value is the value. If you pass zero to longjmp, your logic breaks; the caller of setjmp gets a zero, and so it looks like a context has just been saved. Meanwhile, if you perform an input operation on a read/write stream whose most recent operation was output, and you didn't fflush or use a positioning operation, you have undefined behavior.
- kazinator 4y agoTXR Lisp contains BSD licensed code for setjmp-like saving and restoring, for a modest number of platforms, in a single .S (assembly language with preprocessing) file. https://www.kylheku.com/cgit/txr/tree/jmp.S https://www.kylheku.com/cgit/txr/tree/jmp.S The header file for that is unfortunately not cleanly separated; the top part of this file declares the context structures and the two functions jmp_save and jpm_restore. https://www.kylheku.com/cgit/txr/tree/unwind.h https://www.kylheku.com/cgit/txr/tree/unwind.h Unlike longjmp, jmp_restore does not check for a zero argument and does not rewrite it to 1.