aoughwl/nimony
An opinionated fork of Nimony — the NIF-based reimplementation of the Nim compiler — that tracks upstream master daily and ships a standard library built to our taste. Same compiler core; fewer opinions imposed on you, more of ours baked in.
Repo → github.com/aoughwl/nimony
This page is the canonical record of what our tree fixes and adds over stock upstream nim-lang/nimony. Our master is the branch we use internally and share: it stays current with upstream and carries our own fixes and features on top.
Contents
What’s different
- Fewer bugs, more frontline features. All Nimony work ships here first, and we aggressively push lagging features.
- Stays current. We pull from
nim-lang/nimonymaster ~daily — upstream’s compiler progress with none of the lag. A fork that keeps up, not one that drifts. - A fuller, opinionated stdlib. 60+ modules and counting — batteries the official tree doesn’t ship yet, or ships grudgingly:
terminal(fluent, npm-colors-style string styling),base64,md5,sha1,bitops,complex,deques,heapqueue,editdistance,sequtils,options,random,wordwrap, and more. Ergonomics first.
The convention: every time we fix an issue or add a feature, it gets a row below (and in
doc/CHANGES.mdin the repo). This is the ledger — kept current.
Features Added
Compiler support that makes nimony’s {.passive.} CPS coroutines usable as a real async library. Each links to its full writeup in the Changelog.
| # | Feature | Files | Verified by |
|---|---|---|---|
| F1 | Cross-module .passive | coro_transform.nim cps.nim | tsleep3 tgather2 |
| F2 | delay <call> inside generics | sem.nim | tgenrace |
| F3 | suspend() in a generic .passive proc | sem.nim | cps suite |
| F4 | Proc-pragma macros ({.async.}) | macros_nif.nim semcall.nim macro_plugin.nim | tasyncsugar |
The async runtime — aoughwl/nimony-web
A complete cooperative-async runtime over these coroutines, driven by the host event loop — 46/46 under Node. Each row is documented on the async runtime page.
| Feature | What it gives you | Docs |
|---|---|---|
Future[T] + await | value-returning async, importable across modules | async |
| Dispatcher | callSoon / drainReady / runForever, reentrancy-guarded FIFO | async |
Importable sleepAsync | reusable {.passive.} sleep, called cross-module | async |
Generic gather / all | await many futures, importable & generic | async |
Generic race[T] / any | first-to-finish wins, returns the real T | async |
{.async.} proc sugar | write {.async.} instead of {.passive.} | async |
Incremental compilation & the tooling backend
Nimony’s design already leans on cached, typed NIF artifacts per module — which makes it a natural fit for fast re-checks. This tree pushes that further so that interactive tooling (the nimony-lsp language server’s live as-you-type diagnostics, in particular) has a compile path that stays warm and cheap. These are the concrete wins, most user-visible first.
| Win | What it does | Measured |
|---|---|---|
| Parallel dependency discovery | A breadth-first pre-pass (preNifle) runs nifler over the whole import closure in parallel before the DFS, which then does only cheap in-memory work. Self-healing serial fallback. | discovery wall 0.43s → 0.24s (1.77×) |
| Incremental cursor traversal | nimsem walks the module structure with an incremental cursor instead of re-materializing it (toward #2064). | — |
Warm-worker daemon (nimsem serve) | Persistent semcheck worker; keeps the interner, interface cache and indexes warm across requests. JSONL protocol with a dirty-buffer seam. Foundation for interactive rebuilds. | system interned 1× / session |
| Batch-intern ceiling + proof | Measured the re-intern cost a daemon removes (system.s.idx.nif re-interned 107× on tall.nim) and proved batching 20 modules in one process fixes it. | index intern 91.6ms → 8.6ms |
Why this matters for editors. A whole-project nimony check is ~1.1s cold but only ~10–25ms on an incremental warm re-check. That gap is exactly what lets nimony-lsp publish diagnostics on every keystroke (against the unsaved buffer, in an isolated nimcache) without a background daemon — see the nimony-lsp page. The nimsem serve daemon above is the next-tier backend for when cross-module query latency (go-to-def / references on huge trees) needs to be warm too; it’s wired into nimony-lsp as an opt-in path.
Issues Fixed
Eight compiler fixes over stock upstream. Each row opens its own writeup — symptom, root cause, the fix, files, and the verifying test.
| # | Issue | Verified by |
|---|---|---|
| 1 | .passive helpers didn’t resolve across modules | tsleep3 tgather2 |
| 2 | delay <call> crashed inside a generic proc | cps suite |
| 3 | Macro plugins failed to compile outside the repo | macros suite |
| 4 | .passive capturing a .raises result crashed hexer | cps suite |
| 5 | Proc-pragma macros silently dropped the routine | macros suite |
| 6 | suspend() in a generic .passive proc was mis-typed | cps suite |
| 7 | Generic race[T] spawned via delay failed to link | tgenrace |
| 8 | Imported {.async.} macros: three cross-target failures | tasyncsugar |
Known limits (not yet fixed)
- Raise-across-await — the
.raiseserror-tuple ABI was never threaded through the coroutine lowering (coro_transformtypes the lifted result as rawptr T, notptr (ErrorCode, T)). The crash is gone (issue #4), but real cross-awaitexception propagation is a deferred feature; the nimony-web library propagates errors viaFuture.err. - Dispatcher shutdown — a coroutine that finishes after the entry coroutine returns crashes the dispatcher; keep the entry
mainlast. - WASM async glue — the runtime core is portable; only the timer/pump seam is JS-specific today.
Relationship to upstream
This mirrors and stays in sync with nim-lang/nimony. Compiler fixes are meant to be portable both ways; the standard-library direction is ours to steer. Pull requests are welcome — no BDFL, just taste. See AGENTS.md in the repo for the full toolchain, phase pipeline, and test workflow.