--icBackendStage:merge reads every module's .c.nif (the cg stages'
emit-everywhere output), computes the global live set and, for each
externally-linked definition that several cg processes emitted, the single
artifact allowed to embed its body; it writes ic.backend.merge.nif for the
emit stage to consume. This is the cross-process replacement for the
whole-program backend's in-process icSharedDefOwner/DCE coordination.
Mechanism:
- cgen marks every unique program-wide definition (callConv != ccInline and
not a dispatcher) with a new 'u' cdef flag. Its complement -- inline procs
(static per-TU) and method dispatchers (main-only) -- is emitted into every
using TU and must never be deduplicated, so it carries no flag. The flag is
inert for the whole-program path (renderMarkedC/computeLiveFromCArtifacts
ignore it).
- cnif.computeMergeDecision does one mark&sweep pass over all artifacts
(same liveness as computeLiveFromCArtifacts) plus owner assignment: the
owner of a 'u' definition is the lexicographically smallest artifact that
emits it -- a pure function of the claimant set, stable across rebuilds.
writeMergeDecision/readMergeDecision serialize the result as
(merge (live ...) (owners (own Symbol StrLit)*)).
- generateMergeStage is a pure artifact operation (no module graph loaded):
glob the nimcache's .c.nif, compute, write the decision.
Validated on a diamond (lib.shared called from sibling modules a and b, both
with top-level demands): cg emits shared into a, b and lib; merge assigns
owner = lib (smallest claimant) so a/b will prototype it, while the inline
nimFrame stays out of the owners map (kept everywhere). Whole-program backend
path unchanged (dispatch guarded on icBackendStage); koch ic thallo green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>