Issue #988: async (ADR-async-4, async-2): lowering - tasks, wait points, handles, par_iter execution, –overlaps, measurements

Open and answer this issue on GitHub.

blocked · opened 2026-10-30 · updated 2026-10-30 · 0 comments

Part of #983. Records: ADR-async-4 D2, D4-D8, D10, D14; ADR-async-2 D2, D5, D6; ADR-async-3 D7; ADR-async-1 D9, D11, D12, D14. Spec: Part I 8.1.2, 8.2; Part II 12.6, 12.7; Part III --overlaps. Needs #986 (checker) and #987 (std/runtime).

To build

  1. Lower async e: start the task where it is written (left to right); join it at the wait point the checker placed (before the statement, the call or the operation); hold its outcome until then. The callee receives a plain T.
  2. A pausing task is polled by its group’s future (ADR-328 D10; emit_core_args.nika scope code is the starting point); a non-pausing one runs on the pool at yes, and at no when the main line next pauses or waits (ADR-292 D6 start order).
  3. Groups: async { } and the innermost block join every unjoined task (child handles included, a handle left live by a timed-out join(within:) too) at their end, in written order, before an early exit continues.
  4. Handle forms: async(handle) → ChildHandle; async(deferred) starts at start(); async(detached) → the existing spawn lowering (TaskHandle); h.cancel(); h.join(within: d) and h.join(within: d, on_timeout: task::OnTimeout::Cancel) (async-4 D14).
  5. limit: (a counting gate per form, nested gates all held), within: — a deadline counted from the form’s start, on every form, value or handle (async-3 D7): cancel the form’s running tasks, task::TimedOut where the value is first needed, at the join, or at the group’s end — and admission:.
  6. par_iter: pieces for a computing chain, one task per element for a pausing one (the ledger’s verdict), with the cost rule of async-2 D6 (no failure slot for a lambda that cannot throw, no block reset for one that does not keep memory).
  7. async xs.par_iter() … .collect() as one task.
  8. Chaining: a task that names a pending binding joins it inside its own body; in a diamond the first mention in written order receives a failure, the other waiters are cancelled.
  9. A panic cancels the siblings and the group throws task::Crashed; task::OverBudget fails only its task.
  10. --overlaps prints each task, its window, wait point and group, and per par_iter call the execution chosen and the call that made a lambda pause.
  11. Measurements owed (ADR-async-2 D6; callgrind counts only, release on both sides, LTO stated, the same output first; table in benches/): par_iter().map(…).collect() over 10^6 elements against Rayon, (a) with a lambda that can throw, (b) with a lambda that allocates. The lowering of step 6 is accepted only with these rows.
  12. Tests in tests/language/src/async_*.nika (both user_parallelism settings): a value waited for at first use; f(async g(), async h()); async a() + async b(); a group waits on early return; limit: 2 never more than two in flight; async(within:) throws task::TimedOut counted from the start (also on a handle form, at the join); join(within:) times out and the task finishes afterwards; chaining; a panic cancels siblings; a pausing par_iter keeps order.

Generated by Claude Code

Back to Open Issues