Skip to content

Linker daemon: thin reld-link client + broker-versioned daemon holding the object graph (running-process broker v2, zccache-embedded instrumentation) #19

Description

@zackees

Related: #17 (bridge / multi-engine strategy), #11 (I0–I7 incremental), #2 (meta). Governed by locked decision D16 (docs/plan/01-DECISIONS.md) and IX-T0 (docs/plan/09-INCREMENTAL.md): no daemon is built on assumption — measure first. This issue proposes the daemon's architecture and tooling stack, and the investigation rounds that must gate it. It does not authorize building the daemon before IX-T0 says a daemon is warranted.

Vision

reld-link (and the ELF/Mach-O driver names) becomes a thin client. All linking logic lives in a persistent linker daemon, spawned and lifecycle-managed via running-process create and versioned through the running-process / soldr broker v2 daemon architecture. The daemon holds the object graph (symbol graph + section-contribution graph) hot between edits, which is the precondition — not the optimization — for millisecond warm incremental relink (see #11's "decisive structural fact": incremental cost tracks binary size, not change size, and that floor is unbeatable with on-disk state).

This is the concrete tooling realization of the I2/IX "daemon + snapshot + build-system-supplied change set" plan already in 09-INCREMENTAL.md.

Proposed architecture

  • Thin CLI, fat daemon. The reld-link client does the minimum: locate/attach to the broker, hand off argv + cwd + env + a content digest of inputs, stream back stdout/stderr + exit code. No parsing, layout, or I/O logic in the client. A client that can't reach a daemon must degrade to a cold in-process link (a daemon panic/absence is a recoverable event → fresh cold link, per 09-INCREMENTAL.md).
  • Broker-versioned daemons (running-process / soldr broker v2). Multiple daemon versions coexist; the broker routes a client to the daemon matching its reld version + toolchain, exactly as soldr's broker v2 does. Enables zero-downtime reld upgrades and per-project daemon isolation.
  • Linkers as dynamic libraries. Each engine (native ELF, the lld bridge from Bridge linkers: run + benchmark reld on Windows and macOS now by delegating to lld (rust-lld) per platform #17, future native COFF/Mach-O, radlink) is a dynamic lib the daemon loads, not a subprocess. This lets one resident daemon host multiple engine versions, hot-swap them, and keep the object graph in-process across an engine change. (Ties into Bridge linkers: run + benchmark reld on Windows and macOS now by delegating to lld (rust-lld) per platform #17's "router over engines" framing — the router lives in the daemon.)
  • Object-graph serialization. The graph must save to disk and reload as a bulk snapshot (never reconstructed incrementally — 09-INCREMENTAL.md). Serialize with protobuf (prost) for fast, schema-versioned ser/deser — OR evaluate a faster zero-copy option (rkyv, capnproto, bincode+mmap) in the investigation. Every snapshot must be keyed by a content digest (D16 / 09-INCREMENTAL.md: "any daemon state not keyed by a content digest is a latent correctness bug").
  • All synchronization in the daemon. The daemon owns concurrency, the output mmap (it is a lock-holder and must release/reacquire correctly — 09-INCREMENTAL.md), and the no-global-state / no-exit() invariants that stalled lld-as-a-library (I0-I7: Incremental linking (do not start before P4 is green) #11 I0).

Instrumentation stack — via zccache embedded

The daemon uses the zccache embedded crate for its runtime services, so snapshotting and profiling are uniform with the rest of the toolchain:

  • async runtime
  • mimalloc pprof — heap snapshots (the allocator-level view that makes daemon "snapshots" meaningful)
  • async off-CPU tracing (where the daemon blocks — I/O, lock waits: the I7 "bookkeeping wall" is exactly this)
  • on-CPU tracing (where cycles go inside a warm link)
  • symbolization — resolved via running-process (it already has the process/symbol machinery)

All of the above should live behind the zccache embedded crate so the daemon depends on one instrumentation surface. mimalloc pprof may not yet be in zccache embedded — if so, that is filed as a dependency sub-issue (see below).

Investigation rounds (this needs several; each gates the next)

  1. R0 — does IX-T0 even justify a daemon? Per D16, run the per-phase timing split first. If parse/setup is ~5% of link time on our workloads, the parse-caching daemon is not built. This issue's architecture only proceeds past R0 if the measurement says so.
  2. R1 — broker fit. Can running-process create + soldr broker v2 host a long-lived, versioned, per-project daemon with the isolation and hot-swap we need? Map the broker API to the client/daemon lifecycle above.
  3. R2 — engine-as-dynamic-lib. ABI/versioning for loading engines as dylibs; how the object graph survives an engine hot-swap; symbol/panic isolation across the dylib boundary.
  4. R3 — serialization format bake-off. protobuf/prost vs zero-copy alternatives, measured on a real graph: snapshot size, save time, cold-reload time, schema-evolution story. Digest-keying design.
  5. R4 — instrumentation integration. Wire zccache-embedded async/tracing/symbolization; confirm mimalloc-pprof availability (or land the sub-issue); prove a heap+CPU+off-CPU snapshot of a warm link is actually actionable.
  6. R5 — fallback & recovery. Daemon-absent / panic / stale-snapshot / killed-mid-build all degrade to a correct cold link; null-relink and drift gates from I0-I7: Incremental linking (do not start before P4 is green) #11 hold.

Done when

A written architecture doc (new docs/plan/ entry) exists answering R1–R5, R0 has been measured, and the daemon scope is recorded as an update to D16a (00-OVERVIEW.md already reserves this). No daemon code merges ahead of that record.

Dependency sub-issue

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions