Sujet : Re: Continuations
De : cr88192 (at) *nospam* gmail.com (BGB)
Groupes : comp.archDate : 13. Jul 2024, 10:39:50
Autres entêtes
Organisation : A noiseless patient Spider
Message-ID : <v6ti18$3gru4$2@dont-email.me>
References : 1
User-Agent : Mozilla Thunderbird
On 7/13/2024 2:50 AM, Lawrence D'Oliveiro wrote:
Has there ever been a hardware architecture that managed the flow of
control via “continuations”?
That is, you do away with the hardware concept of a stack. Instead, you
have call frames that, while defined to some extent by the architecture,
can be located anywhere in memory (allocation managed by the OS, runtime
etc as part of the ABI). A call frame has a current program counter, and
maybe some other context like local variables and a static link for
lexical binding. Instead of a “return from subroutine” instruction, you
have a “load new call frame” instruction.
You might have a location defined in a call frame for a “pointer to parent
call frame” field, in which case “return from subroutine” just loads this
pointer into the current-call-frame register. But you could just as easily
have pointers to other call frames defining other kinds of
interrelationships between them. And note that transferring to a different
call frame does not automatically invalidate the previous one. If it stays
valid, then there is no reason why you couldn’t, at some point, come back
to it and resume execution from where it left off.
The beauty of continuations is that they are a single generalized control
construct that can be used to implement specific language features like
regular routine calls, loops, exceptions and coroutines, all built from
the same common abstraction. One thing that is difficult to do with them
is arbitrary gotos. (I consider that a feature, not a bug.)
Very few high-level languages (outside of the Lisp family, anyway) seem to
have implemented continuations as an explicit language concept. This is an
integral part of Scheme, not so much it seems of non-Scheme Lisps. I
implemented it in my PostScript revival language
<https://bitbucket.org/ldo17/gxscript/>, and I am still trying to come up
with a useful example, like for instance an implementation of coroutines,
that doesn’t do my head in. ;)
I had looked into them before for my project, but:
There is no real way to support full continuations without moving the whole ABI over to continuations;
Doing an ABI based around continuations would basically wreck performance;
Conventional thread-based multitasking is cheaper to implement and gives better performance.
There is basically no real way to get dynamically allocated stack-frames and argument copying to be performance competitive with adjusting a stack pointer and passing arguments in registers. Function calls are also, not exactly rare.
Worse still, one is very likely going to need a garbage collector (in the off chance anyone actually uses continuations, if the continuation is captured via call/cc or similar, can no longer automatically free it).
There are limits to how much cleverness can realistically be put into hardware; and dynamic memory allocation and GC are not really things which map over to hardware.
There are exit-only continuations, but this effectively decays into an alternative form of try/catch (and generally exit-only continuations and try/catch exceptions can use the same underlying mechanism). Some of my languages had these in addition to try/catch. This mostly just requires a mechanism to unwind the stack frame and transfer control to catch handlers (which will either handle the exception or pass it up the call chain).
An promise/join mechanism can also address some other use cases, and is also cheaper to implement (and as well, once joined, a promise can disappear freeing any memory that would have been associated with it; avoiding the implicit need for a GC).
For the most part, I prefer to shy away from features for which there is no known way to implement them without irrecoverably breaking performance.
I do allow features which "optionally" break performance:
For example, while dynamic types are not good for performance, they only have an effect if the program uses them; and with special support from the compiler or ISA, can still be faster than having the program roll its own by bit-twiddling pointers or using tagged unions (sometimes the need to be able to encode type information at runtime is unavoidable).
Nevermind, say, if mixing the C typesystem and a dynamic typesystem is sort of an "oil and water" situation.
...