Skip to main content

Module module_solver

Module module_solver 

Source
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):

  1. Topologically order the setters by the writes/reads dependency: setter A writes services.atticd.enable, setter B reads it via mkIf config.services.atticd.enable … → A fires before B.
  2. Track dirty paths — the set of config.* paths that have changed since the last cycle.
  3. 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).
  4. 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 BodyEvaluator trait 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.
GlobalSetterId
A setter’s identity inside a ModuleGraph: (module_id, setter_id_within_module). Each setter is unique by this pair — module_id disambiguates because the same setter_id_within_module can collide across modules.
PerModuleEvaluator
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.
SolverState
Live state of the solver.
TreeWalkingEvaluator
Production BodyEvaluator backed by crate::ast_evaluator — the minimum-viable tree-walker over the typed AstGraph.

Enums§

SolverError
Errors from the solver.

Traits§

BodyEvaluator
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_path lie within the subtree rooted at slice_path? Examples:

Type Aliases§

ConfigPath
A canonical config path, e.g. ["services", "atticd", "enable"].