Sigil 0.17.6
A correctness release. The headline is that the default way to build an application works again: sigil build produced a broken binary for the whole 0.17 series, and that is fixed. Alongside it, the exception-cleanup work started in 0.17.5 is now complete, so dynamic-wind and logging behave correctly even when an exception unwinds through a goroutine boundary; garbage collection gains frame-scheduling and server-heap controls plus a soundness fix in its incremental step; and an internal define-syntax that captures an enclosing binding now works. Existing projects keep building without changes.
The default sigil build produces a runnable app again
sigil build bundles your application by appending its archive onto a base binary, and that base is meant to dispatch to your app's entry point. Since 0.17.1, once the CLI itself became native-compiled, the base ran the CLI instead of your app, so a freshly built app failed at startup with unbound variable 'option' and never ran. Native builds through SIGIL_BACKEND=native were unaffected, which is why it stayed hidden. The generated program entry now checks for a bundled application and runs it, falling back to its own entry only when there is none, so sigil init followed by sigil build produces a working app again. A build-and-run check for a default bundle now runs in the release suite so this cannot slip through again. If you want a lean, optimized standalone binary rather than the bundled form, --native remains the way to get one.
Exception cleanup is now correct across goroutine boundaries
0.17.5 made dynamic-wind cleanup run when an exception unwinds through it. 0.17.6 finishes the harder cases:
- A caught exception that crosses a
yieldinside a goroutine now runs the right cleanup thunks, because each fiber carries its own winding state instead of sharing one. This also fixes a separate crash where adynamic-windspanning a yield could corrupt the scheduler on normal completion. - Writing to an async port (for example, logging) while an exception is unwinding no longer recurses without bound and crashes the process. During unwinding, such a write completes synchronously instead of trying to suspend with no live prompt to suspend to. This protects any async program that logs on an error path, which is most of them.
- The scheduler now restores its current-scheduler context on the exception exit path, not only on normal exit, which was the underlying cause of the logging recursion above.
Two latent crashes that turned out to be already fixed by this exception-handling family are now covered by regression tests so they cannot return: an idle-tick car crash in long-running servers, and a sigil test deadlock when a test reloads (sigil core).
Garbage collection: frame scheduling, server heap controls, and a soundness fix
Building on the pause and memory observability from 0.17.5:
(gc-frame-hint!)runs a minor collection only when the young generation is filling up, so a game loop can end each frame with one call and turn random mid-frame pauses into scheduled ones.- Server programs get heap controls:
SIGIL_GC_HEAP_GROW_FACTORto trade a little collection frequency for a lower steady-state heap,(gc-idle-hint!)to collect between requests, and an opt-inmalloc_trimafter a major collection so freed pages return to the operating system. All are additive and off by default. - The
%gc-step!incremental collection path had a latent unsoundness: an object allocated while a collection was marking could be freed while still live. It now allocates such objects black and re-scans roots at mark termination, with a tri-color audit mode that checks the invariant. Default collection was never affected; this matters for code that drives collection in steps.
Internal define-syntax that captures an enclosing binding works
A define-syntax written inside a function body whose template refers to a surrounding binding used to produce #<undefined> or crash, while the same macro at module level worked. Body-position capture now works when you run your code (eval, the REPL, sigil test). One honest limitation: this capture does not yet survive into a fully compiled, bundled binary, and capturing a per-call parameter is separate, larger work; both are tracked for a future release.