Coroutine Secrets
2025-02-17
Slides from video.
Concurrency is coroutining.
What is called “concurrency” today is based on the notion of timesharing.
Timesharing is a way of amortizing the use of a single CPU across many programs.
Timesharing does not make a single CPU concurrent.
Time sharing context switches so quickly that it only looks like actions are happening at the same time.
Time sharing is coroutining. Hence, concurrency is coroutining.
Time shared coroutines do not directly coroutine with each other. They coroutine with the dispatcher. The dispatcher coroutines with each program separately.
The dispatcher sets a timer and then runs a program. The program can invoke yield, causing the program to return control to the dispatcher, or, the timer times out, and the OS dispatcher is invoked by the hardware to force a context switch.
Timer induced yield is called preemption.
Coroutines are just green threads. Processes are the same, except for a layer of protection.
The main differences between green threads and red threads are
Extra hardware, and
extra software.
Originally, CPUs - which I call GPMs, for General Purpose Machines - consisted of a single bare bones CPU chip along with a handful of memory chips.
It was a simple concept.
Can we use a simpler model of coroutines? Today's functional programming machines are complicated and require large amounts of software scaffolding.
There is no need to use functional programming to solve every problem.
Can we use a simpler model for at least some problems?
Coroutines don't need to be recursive. We just save a bookmark pointer in a register or memory cell and change the program counter. Each coroutine is like an Actor with private state. If we don't allow unbounded recursion, we can statically allocate coroutines in memory.
Does this make hardware cheaper and more abundant?
Can we have more CPUs on a chip?
Does this make software simpler?
Can we use single-threading again?
Coroutines don't strictly employ recursion.
But,,, if a coroutine uses internal functions, the functions might recur.
We create a static stack for each coroutine.
Yield saves state on the stack. It pushes the registers, etc.
Resume pops the state from the stack.
Compilers can compile yield syntax into unrolled save/pop sequences.
Here are some examples of bare coroutines.
The source code can be found here.
This is what actually happens inside operating systems. Operating systems include a lot of extra code for twiddling memory protection and context switching hardware.
Operating system code is run in a different hardware protection ring, which means extra time to switch between rings.
A single program does not actually need internal memory protection.
We call memory violations in a single program just a "bug".
On the other hand, it is nice to have memory protection to help with debugging for program development. Can memory protection be grafted into programming languages instead of only being reserved for operating systems?
Here's an example of coroutines written in JavaScript.
It's only one page of code. There's three routines, A, B, and a dispatcher.
Here's the same example written in Python. A, B, and a dispatcher.
And here's the same example written in Lisp. A, B, and a dispatcher.
In the future, can we compile yield syntax into unrolled save/pop?
In the meantime, before we get to this built into compilers, programmers can write explicit code for yield and resume. Or, programmers can use t2t (text to text transpilation), Ohm, PEG, whatever, to create nano DSLs and syntax skins.
I call nano-DSLs “SCNs”, for Solution Centric Notations.
Functional programming is just a specialized paradigm and a bunch of syntaxes for creating assembler code for functional programming machines.
Can we develop a different syntax for creating assembler code for simpler GPMs - General Purpose Machines? Can we create bare coroutines? Can we create 0D as I call it?
Most current programming languages support closures. Maybe that's enough for simulating bare metal coroutines? Maybe we can use Forth, Common Lisp, Python, Javascript, WASM, Rust, whatever, as assemblers for new programming languages [see 31:50]?
See Also
References: https://guitarvydas.github.io/2024/01/06/References.html
Blog: guitarvydas.github.io
Videos: https://www.youtube.com/@programmingsimplicity2980
Discord: https://discord.gg/65YZUh6Jpq
Leanpub: [WIP] https://leanpub.com/u/paul-tarvydas
Gumroad: tarvydas.gumroad.com
Twitter: @paul_tarvydas
Substack: paultarvydas.substack.com















