← board

Async, coroutines, and yield

Motivation

A shared-language arc: a resumable-execution mechanism plus an event loop, usable from Pascal, Nil Python, and future frontends. Cooperative concurrency is also arguably the right model for ESP32 (predictable, low-RAM, no preemption).

Approach — PIVOTED to stackful coroutines (2026-06-16)

The original plan (developer/plan-async-coroutines.md) was a compiler state-machine / resumable-frame transform (stackless, C#/Python async style). That is a brutal CPS transform on a stack-machine codegen with no SSA, plus viral function coloring and yield-location restrictions. Defer it.

Instead: stackful coroutines (fibers / green threads). A coroutine = its own heap stack + a saved register context; switching is a tiny per-target asm routine.

Layers (build order)

  1. CoSwitch (asm) + TCoroutine (heap stack). Save/restore BSS_EXC_TOP per coroutine (the setjmp exception chain is per-stack — must swap on switch, or a cross-coroutine raise unwinds the wrong frames).
  2. Cooperative scheduler: ready queue, Spawn/Yield/RunUntilDone. Single OS thread first (cooperative within one thread is race-free); M:N is much later.
  3. Channels / mailboxes (optional).
  4. Async-I/O reactor: a "blocking" recv registers its fd and Yields; the scheduler's select/poll/epoll wakes it — makes Synapse-style code async transparently. The payoff.
  5. ESP32 reactor (UART / sockets).
  6. (Optional, later) async/await sugar over Spawn/Yield; stackless transform only for the RAM-critical embedded hot path.

Gotchas specific to PXX

Sequencing: allocator groundwork first (many small coroutine stacks → feature-unified-heap-allocator). Generators (feature-generators-yield) lead.

Acceptance

A coroutine/yield test suspends and resumes correctly on the stackful mechanism; the async-I/O reactor drives a Synapse socket without blocking the scheduler; self-host fixedpoint + cross-bootstrap unaffected (library-only).

Log