Expand description
L4 module-system solver — slice-keyed re-firing over a compiled
ModuleGraph.
§What this does today
The dependency-tracking core of the fixed-point solver. Given a
compiled ModuleGraph (option declarations + config setters with
slice + assigns_path metadata, all populated by
sui-spec::module_compiler):
- Topologically order the setters by the writes/reads
dependency: setter A writes
services.atticd.enable, setter B reads it viamkIf config.services.atticd.enable …→ A fires before B. - Track dirty paths — the set of
config.*paths that have changed since the last cycle. - Schedule setters whose slice intersects the dirty set — everything else stays untouched. A leaf setter (empty slice) fires exactly once unless its assigns_path itself becomes dirty (e.g. via mkForce in a downstream module).
- Run to quiescence — re-iterate until no more setters need to fire.
§What this does NOT do today (queued)
- Body evaluation — setter bodies are AST node ids; actually
running them through the bytecode VM and producing typed values
requires the sui-eval integration. Today the solver carries a
BodyEvaluatortrait the eval crate will implement; [StubEvaluator] in the test module returns a deterministic placeholder so the solver math is provable in isolation. - Defunctionalization — higher-order setters stay AST-pointer- only. The transform that lowers them to first-order tagged closures lands next.
- NbE structural-equality caching — cache key for the compiled
closure uses
ModuleGraph::canonical_hash; NbE-driven canonicalization (equal-up-to-alpha for setter bodies) is the next optimization layer. - Rayon-per-SCC parallelism — today’s solver is single-threaded (Kahn’s queue order). The opportunistic-parallelism work cited in the eval-engine research (arxiv:2405.11361) lands when bodies evaluate for real.
§Why this matters
The 24-second nixosConfigurations.rio.config.system.build.toplevel
cost is the cppnix module-system fixed point re-evaluating every
setter on every rebuild. With slice-keyed re-firing, a rebuild
where (say) only services.atticd.enable changed re-fires ONLY the
setters whose slice contains that path — for the rio fleet that
drops from ~2000 setters to a small handful. The math is here
today; the eval integration in the next ship makes the perf
visible to operators.
Structs§
- EnvSnapshot
- A read-only snapshot of the current config attrset, projected as
path → bytes. Reflects every setter that has fired in prior iterations of the fixed point. Newer mkForce’d values override older default-priority ones. - Global
Setter Id - A setter’s identity inside a
ModuleGraph:(module_id, setter_id_within_module). Each setter is unique by this pair —module_iddisambiguates because the samesetter_id_within_modulecan collide across modules. - PerModule
Evaluator - Multi-module evaluator that routes each setter to the AstGraph of its containing module. Used in tests + by callers that build a ModuleGraph from multiple AstGraphs.
- Solver
State - Live state of the solver.
- Tree
Walking Evaluator - Production
BodyEvaluatorbacked bycrate::ast_evaluator— the minimum-viable tree-walker over the typedAstGraph.
Enums§
- Solver
Error - Errors from the solver.
Traits§
- Body
Evaluator - Trait the eval engine will implement to actually evaluate setter bodies. Carried in the solver state so the dependency-tracking core can be exercised under stub evaluators in tests without dragging sui-eval into sui-spec.
Functions§
- slice_
path_ intersects - Slice-intersection predicate: does
dirty_pathlie within the subtree rooted atslice_path? Examples:
Type Aliases§
- Config
Path - A canonical config path, e.g.
["services", "atticd", "enable"].