Sigil 0.19.1
A compiler correctness release. Three optimizer defects could make a guard test a different value than the one you wrote, so a program takes the wrong branch with no crash and no failing test. One has shipped since 0.14.6. All three need optimize: 1 or higher, which means no test suite could catch them: sigil test, sigil eval and every dev build run at optimize: 0, where the code is correct. Rebuild and relock anything built at optimize: 1 or above.
Also in this release: reader errors say what went wrong, an unrecognized define-library clause is refused instead of silently dropped, uncaught errors go to stderr, and a failed dict-ref names the key.
Existing projects build without changes.
A guard could test a different variable than the one you wrote
If a module-level cell is assigned exactly once inside a branch, the optimizer could treat reads that happen before the assignment as though it had already run. Code like this is the shape at risk:
(when (and (number? now-ms) (number? *last*)) (set! *last* now-ms))
(number? *last*) gets answered with the result of (number? now-ms), while the arithmetic that follows reads the real cell. The guard passes and the guarded operation runs on the unguarded value. A guarded stamp raises, which is loud; a compare-and-set latch quietly becomes (eq? v v) and never fires, which is not.
Present since 0.14.6 (2026-04-27). It is intermittent by nature: the same source flips between correct and miscompiled as surrounding code changes, and the rewrite it needs happens in roughly 0.1% of cases, which is why it went four months unnoticed. Generated code is unchanged everywhere else, verified byte-identical across a 187-module build.
If you are stuck on an older toolchain, SIGIL_CPS_NO_CSE=1 will not help: sigil build clears it for cache determinism. Only optimize: 0 avoids the pass.
Bounds checks could be removed on the same bad reasoning
The same faulty rule fed bounds-check elimination, which could drop a check and emit an unchecked vector access. Demonstrated on a purpose-built case and fixed here. No out-of-bounds access was produced from it, and a sweep of 671 modules found no real-code elimination resting on the bad reasoning. The fix costs no optimization: eliminations are unchanged on the largest sample measured.
Delimited continuations could reach the same wrong conclusion
The optimizer missed two ways control can arrive at a point: the resume edge of abort-to-prompt and the result continuation of call-with-prompt. A missing path turns a merge into a straight line, so the value a guard tests is whatever the continuation was resumed with. Anything using delimited continuations, including guard and the async scheduler, was exposed at optimize: 1 and above. Generated code is unchanged, byte-identical across a 185-module build.
A reader that fails now says why
A #lang file that failed to parse reported read-language-source failed for #lang sigil/doc: with nothing after the colon. The reader's own diagnostic was dropped, so anyone writing in a #lang got the language name and no reason. Reader errors now carry their text through, and an empty diagnostic is reported as empty rather than dressed up as a message.
call-with-output-string works at the prompt
call-with-output-string, open-output-string and get-output-string are available in the REPL and in eval without an import. They live in (sigil io), which the interactive environment does not import, so building a string from a port failed with an error that did not suggest the fix.
An unrecognized define-library clause is refused
Unknown clauses used to be discarded silently, so a single misplaced paren could close the library body early and throw away the rest of the file. The result compiled cleanly and was missing definitions, surfacing much later as an unbound variable in a different package. It is now an error.
Smaller fixes
Uncaught errors print to stderr instead of stdout, so piped or captured output no longer has error text mixed into it. A failed dict-ref names the key it looked for, and flags a keyword used where a symbol was expected or the reverse.
Known issues
Three suite-level failures predate this release, each confirmed by reproducing them on the previous compiler, so make test does not currently pass end to end. None gates this release and fixes for all three are tracked.
test-release-suitesegfaults. The Makefile marks it mandatory for optimizer changes, so it is named here rather than left to be discovered.test-async-dnstimes out. The resolver does not yield; the timeout is the symptom.- The musl target gate fails under
make test-buildand passes when run on its own. Cause not established.
Upgrading
Rebuild anything built at optimize: 1 or higher, and relock. sigil-stdlib is compiled by the compiler, so a stdlib pinned before this release keeps carrying the old codegen even after you upgrade the toolchain.