Sigil 0.18.3
A toolchain correctness release. Sigil now resolves its runtime packages coherently, builds test suites without manufacturing dependency cycles, runs every discovered test file exactly once and reports a verdict for each one, and recompiles edits even when they land within the same second. Native builds also improve substantially: cross-module optimization summaries are active and bound to the exact compiler and bytecode they describe, native WebAssembly output is smaller and self-contained, and projects can grow past the old 256-module ceiling.
Most projects build without source changes. Native caches rebuild once because the internal runtime ABI moves from version 3 to version 5. A build that supplies a backend flag contradicting its selected configuration now stops with a clear error instead of producing an artifact whose kind disagrees with the package declaration.
Dependency resolution is order-independent and keeps the runtime together
The order of dependencies in package.sgl could change the result of sigil deps install. A transitive dependency resolved first could also silently hold sigil-lib on an older line while sigil-stdlib moved forward, producing a lockfile that installed successfully and failed later while loading.
Resolution is now order-independent for these cases, and sigil-lib and sigil-stdlib always come from the same Sigil worktree. The rule is deliberately narrow: independent packages from the Sigil monorepo can still use different compatible release lines. When an ordinary advisory constraint loses to a version already selected, the resolver continues as before but now warns with the discarded range, the version that won, and the source of the losing constraint. Set SIGIL_DEPS_TRACE_DROPPED=1 for the complete constraint trace.
--tests builds model libraries and test products separately
On 0.18.2, sigil build --tests could stop immediately with Dependency cycle detected. The graph collapsed a package library and its tests into one node, manufacturing a cycle when a package's tests depended on a test runner that ultimately used the package's library.
Libraries and tests are now distinct build products in the dependency graph. Real development-dependency cycles are still detected, while ordinary test harnesses build correctly. This fixes both sigil build <package> --tests and the native test-harness path used by sigil test. Graph failures also distinguish a real cycle from a missing dependency or duplicate node instead of blaming every malformed graph on a cycle.
sigil test runs each discovered file once and tells you what happened
Several runner defects could make a suite look healthy without running the suite: an exit inside one file terminated the whole process, nested dependency directories could trap discovery in a symlink cycle, workspace and explicit discovery could execute the same file twice, and duplicate basenames made the file ledger ambiguous. Test files also shared one module, so a macro defined by one file could poison a later file depending on filesystem enumeration order.
Each test file now loads in its own module and inside an exit trap. A load-time exit becomes that file's load error, a run-time exit becomes a failed test, and the remaining files continue. Discovery skips dependency stores at every depth, rejects ancestor symlink cycles, deduplicates the final file set, and labels every result with its path. Empty files receive an explicit EMPTY verdict, and a truncated run reports how many planned files never reached a verdict. The summary, ledger, and exit status now describe the same execution.
Source edits within the same second are no longer missed
The auto-compiler and incremental package builder compared timestamps only to whole-second precision. A quick edit could therefore leave older bytecode classified as current and execute the previous program. Freshness checks now use nanosecond timestamps throughout both paths, so an edited source always recompiles even when its timestamp shares the same second with the cached output.
Cross-module optimization summaries are active and provenance-bound
Auto-compiled bytecode now publishes its .sgs optimization summary beside the final .sgb instead of leaving the summary under a temporary orphan name. The compiler can use those summaries for cross-compilation-unit bounds-check elimination, with a safe per-unit fallback whenever a summary is absent or rejected.
An SGS v2 summary is accepted only when it has a matching sibling bytecode file and its format, bytecode SHA-256, compiler SHA-256, and payload-integrity digest all validate. Summary discovery is restricted to compiler-owned cache and output directories, never ordinary library or package source paths. This prevents stale, copied, corrupted, or source-tree summaries from supplying facts to a different compilation.
Optimizer settings are keyed, and unsafe loop hoists stay disabled
The selected CPS optimization level now participates in build-cache identity, so changing optimization settings cannot reuse bytecode built under another level. Ambient optimizer controls are cleared from ordinary builds unless explicitly represented by the builder.
This release also closes a latent LICM miscompile exposed when optimization first ran in production: a potentially raising loop-invariant expression could move ahead of the guard that made it safe, turning handled input into an unconditional error. The speculative rewrite now admits only explicitly total, non-allocating operations and refuses captured-cell dereferences without a no-write proof. The pass remains deliberately conservative where the compiler cannot establish those facts.
Native WebAssembly builds are smaller and self-contained
Native WebAssembly output now runs Binaryen -Oz after Asyncify in a separate, ordered step, validates the optimized file before replacing the original, and fails loudly if optimization produces no valid module. In measured production consumers this reduced Enclave from 16.9 MB to 14.2 MB and Slate from 39.8 MB to 31.3 MB, with corresponding gzip reductions of 0.9 MB and 2.9 MB.
Native configurations no longer stage the bytecode library tree, lib.bundle, or bytecode manifest beside a self-contained WASM artifact. Bytecode configurations retain their existing staging behavior. The native-WASM standard-library entrypoint also no longer resolves a host-only wait capability while compiling for the browser.
Backend declarations are enforced
A configuration's declared backend is now an artifact-kind contract. If a CLI --backend option or inherited SIGIL_BACKEND value contradicts that declaration, the build exits before compilation and names both selections. Matching flags remain accepted, and projects that do not override the declared backend are unchanged.
The VM module registry grows beyond 256 modules
The fixed 256-entry module registry is now a growable heap-backed registry. Large workspaces and split applications can register thousands of modules without degrading into a misleading library not found failure after the old ceiling. The expanded registry remains a garbage-collector root set, including entries beyond the original boundary.
This changes the internal native ABI from version 3 in 0.18.2 to version 5 in 0.18.3. Native archives and compiler caches rebuild once on upgrade; bytecode source compatibility is unchanged.
Pipe-spawned processes can own a process group
process-spawn gains new-process-group: #t. On Unix the child starts a new session before exec, so process-signal! can signal the child and everything it spawned while stdout and stderr remain separate pipes. process-pgid returns the live process-group id before the child is reaped. Default spawn behavior is unchanged, and the option is a no-op on Windows where group signalling is unavailable.
Macro failures no longer crash the runtime
A macro that raised while expanding the first element of a list could make the expander write through the empty-list immediate. Instrumented builds reported a misaligned write; release builds could segfault with no useful diagnostic. Expansion now returns through the existing error path without touching an uninitialized list tail, so the original macro error reaches the caller normally.
Additive WebAssembly DOM integrity query
The browser DOM bridge gains wasm-dom-child-at? and dom-child-at?. They check child identity and exact list boundaries without mutating the DOM. This is the primitive used by sigil-web-client 0.15.4 to detect out-of-band DOM drift and return #f before a retained-tree update can falsely report success.