add channels to El — buffered MPMC channel with send/recv/close

Introduces Go-style channels as El's mid-flight communication primitive,
completing the threading model: threads can now not only spawn/join but
also communicate while running.

Part 1 — seed layer (el_runtime.c / el_runtime.h):
- Add __thread_create/__thread_join/__mutex_new/__mutex_lock/__mutex_unlock
  as C seed primitives (dlsym-based thread dispatch, pthread mutex table)
- Add __channel_new/__channel_send/__channel_recv/__channel_try_recv/__channel_close
  as MPMC channel seed primitives backed by mutex + condvar + circular buffer
- Bounded channels (cap > 0): circular buffer, sender blocks when full
- Unbounded channels (cap == 0): dynamic array, grows on demand, never blocks
- channel_close wakes all blocked recvers/senders; recv drains then returns ""

Part 2 — El API (runtime/channel.el):
- channel_new/send/recv/try_recv/close — thin wrappers over seed layer
- channel_pipeline — spawn N worker threads reading from in_ch, applying
  fn_name, writing to out_ch; workers exit on "" sentinel from close
- channel_drain — collect all messages from a closed channel into [String]
- channel_fan_out — send a [String] list into a channel then close it

Part 3 — codegen.el:
- Register all 10 seed builtins (__thread_* + __channel_*) in builtin_arity
  so the arity checker validates call sites at compile time
This commit is contained in:
Will Anderson
2026-05-03 15:50:13 -05:00
parent 9d0e1f64d4
commit d1af4b0f8b
4 changed files with 537 additions and 0 deletions
+33
View File
@@ -749,6 +749,39 @@ el_val_t trace_span_start(el_val_t name);
el_val_t trace_span_end(el_val_t span_handle);
el_val_t emit_event(el_val_t name, el_val_t duration_ms);
/* ── Threading seed primitives ────────────────────────────────────────────────
* These are the low-level C primitives that back thread.el and channel.el.
* El programs call them via their El wrappers (spawn, join, __mutex_new, etc.)
* rather than directly.
*
* __thread_create(fn_name, arg) — dlsym-resolves fn_name, spawns pthread,
* returns a slot index (Int) usable with __thread_join.
* __thread_join(tid) — joins the thread, returns its String result.
* __mutex_new() — allocates a mutex, returns handle (Int).
* __mutex_lock(m) — locks mutex m (blocks until available).
* __mutex_unlock(m) — unlocks mutex m. */
el_val_t __thread_create(el_val_t fn_name, el_val_t arg);
el_val_t __thread_join(el_val_t tid);
el_val_t __mutex_new(void);
void __mutex_lock(el_val_t m);
void __mutex_unlock(el_val_t m);
/* ── Channel seed primitives ─────────────────────────────────────────────────
* Buffered MPMC channels. All values are Strings; handles are Ints.
*
* __channel_new(capacity) — create channel; cap=0 means unbounded.
* __channel_send(ch, msg) — push msg; blocks if bounded and full.
* __channel_recv(ch) — pop msg; blocks until available; "" on closed+empty.
* __channel_try_recv(ch) — non-blocking pop; "" if empty.
* __channel_close(ch) — mark closed; wakes all blocked recvers/senders. */
el_val_t __channel_new(el_val_t capacity);
void __channel_send(el_val_t ch, el_val_t msg);
el_val_t __channel_recv(el_val_t ch);
el_val_t __channel_try_recv(el_val_t ch);
void __channel_close(el_val_t ch);
#ifdef __cplusplus
}
#endif