>> So if you want unwinding, you’ve got to have an equivalent to setjmp (or throw / catch)

> Unwinding is a somewhat cleaner interface than setjmp/longjmp, and it's a useful primitive in its own right

I think the distinction is becoming subtle enough that we need to define our terms here. I’d distinguish three mutually interexpressible (is that a word?) systems:

(1) Marks (for lack of a better term): C. There’s an object that designates an activation (jmp_buf) that you can ask for (setjmp), unwind to (longjmp), and must not let escape. Upside: disjoint error filters do not require cross-ABI cooperation. Downside: finalizers do.

(2) Panics: Forth ’94, Lua, Go. There is a singular dynamically scoped recovery point where you can unwind and pass some data, which it will be able to rethrow further if it wishes. Upside: finalizers do not require cross-ABI cooperation (unless you hardwire backtraces). Downside: disjoint error filters do.

(3) Exceptions: C++, Java, arguably Common Lisp THROW/CATCH as a degenerate (no-subtyping) case. There is a single system of errors mandated by the platform (or language, as the case may be) and a stack of handlers of two types: catch, associated with an error type, which stops propagation of its subtypes and runs user code; finally, not associated with anything, which suspends propagation of anything, runs user code, then resumes propagation. Upside: implements the semantics of C++ directly in the system. Downside: anybody who wants anything else will have to fight the system.

(4) Filters: Win32 SEH (both old- and new-style), Itanium ABI (IIUC). Basically (3) but a catch-type handler is matched by running user code, either in the middle of an unwind (simpler) or before it (does not destroy stack traces). Upside: you get some degree of language interop compared to (3). Downside: the interop is still anemic compared to the complexity.

(4a) Declarative (4): Win32 SEH (new-style), Itanium ABI. Upside: security (?), speed (?). Downside: uninvolved code (C, JIT) needs to get involved; better get your stack maps right the first time (*cough* Itanium’s 30% size overhead *cough*).

To me, only (1) and (2) sound like real options on the VM level. If what you mean by “unwinding” instead of longjmp is (2) instead of (1), I’d say the advantages and disadvantages here are basically mirror images of one another and so far I can’t see a reason to prefer either. If you mean to replace (1) with something else, please elaborate—my options (3) and (4) are somewhat fuzzy and possibly nonexhaustive.

> especially if you can attach a notion of a stack trace to the unwind primitive.

As a registered Conditionist[1,2] who thinks all systems of non-resumable exceptions suck, I take exception to this (pun not intended). More directly, if you’re going to turn SIGFPE or SIGILL or SIGTRAP into exceptions, the debugger / crash recorder / etc. needs a stack trace available on an ununwound stack, and those tools are the only consumers of a stack trace in non-insane production code anyway.

See also the part about the stack map format in (4a).

So far, then, I feel that the stack trace tie-in is just not a good idea.

> At a low-level language primitive level, unwind can be seen [as] a mechanism to have [...] normal and exceptional return addresses[.]

I’m unaware of double-barrelled CPS being used as anything but a theoretical tool, but it would be interesting to see. [This is the part that made me think you meant (2) by “unwinding”, as it fits this model most naturally.]

>> if you want asynchronous interrupts, you’ve got to have an equivalent to signals.

> ... not really. There's a few different kind of signals. Synchronous processor signals (e.g., SIGSEGV, SIGFPE) could be supported via something akin to unwind rather than signal handlers (and this is essentially how they work on Windows).

Which is why I said “asynchronous” :) (in part as a reference to VMS / NT APCs). But OK, let’s have a digression: I agree that SEH is better than POSIX signals for synchronous processor traps, with two caveats:

- Whatever you replace a SIGILL handler with must be able to resume instead of unwinding, as the use case might be instruction or syscall emulation (SEH can do this, but your proposal doesn’t mention it);

- Whatever you replace a SIGSEGV handler with must also be able to resume and will probably find the memory location more interesting than the call stack (this is a problem with POSIX signals[3] that Linux solves with userfaultfd, and also a problem with SEH which Windows solves with an inferior hack[4]).

(End of digression.)

> The asynchronous events can instead be supported by basically having some primitive notion of an event loop, dropping those signals as events in the event loop.

I don’t expect I want to live in your proposed world—in an event loop, the flow controls you—but I probably want to see this world in more detail first.

Your mental model for async notifications also seems to be an I/O-bound program that wants to have the environment notify it about I/O. So had mine been—and then I wrote a CPU emulator, which is by necessity CPU-bound and cannot (non-hackily) afford to check for events with a syscall but still wants to hear about them, and suddenly SIGIO was looking mightily attractive.

This is niche. It is also solvable by having a second thread run select() and set the same flag my SIGIO handler currently uses and the emulation loop polls. A case where you wanted to gracefully terminate a thread, though (a case that I have admittedly not yet encountered, and that is even more niche), would require nothing less than an actual signal.

Again, niche. I’m not at all advocating for using signals everywhere POSIX does. But it’s not hard to think of cases where nothing will do but a notification that can actually interrupt your instructions and not just system calls. Maybe such cases are too obscure to handle (Java certainly seems unhappy with asynchronous thread termination), but “YAGNI signals” is a different argument from “signals evil”.

> Signals already interoperate pretty sketchily with event loops

AFAICS this is a problem of Unix getting select() and threads too late and the retrofit being subpar, not an inherent flaw. (And once again, I entirely agree that most POSIX signals should be turned into other kinds of notifications... It’s just that not all of them can.)

> Interfaces like fork and POSIX signals are pretty notorious for being generally wrong ways to do what you actually wanted to do [...].

Signals, see above. My argument about fork was very different: I don’t like it; I just don’t think it‘s guilty of GGP‘s specific charge of being error-prone—certainly not more so than shared-memory concurrency.

[1] https://news.ycombinator.com/item?id=31196046

[2] https://news.ycombinator.com/item?id=33983418

[3] https://sourceware.org/legacy-ml/libc-alpha/2018-03/msg00214...

[4] http://bytepointer.com/resources/pietrek_vectored_exception_...