Mímir v2 — the Rust code graph
Mímir v2 is nornir's code graph. One producer (a rustc driver) writes Arrow IPC files per compiled crate. One consumer folds them into an Iceberg/Parquet lake. One in-memory room (GraphBLAS) answers questions through named reader surfaces and lenses. Every node id is a SCIP moniker string.
This document describes the code on main as it is today. File references are repo-relative paths in nornir unless another repo is named. A short list of planned items sits at the end, marked as planned.
New to Mímir? Start with overview.md (what it is, for reviewers).
Chapters:
| file | content |
overview.md |
what Mímir is, the pipeline, the model, time, vectors, compute, what is not there, and the old review against today |
README.md (this file) |
flow, producer, ids, lake, room, readers, lenses, what is not there, the system boundary and the possible system calls |
tables.md |
every table and every column |
compiler-to-graph.md |
from rustc to the tables to the GraphBLAS matrices, with one fn traced end to end |
lens-guide.md |
the user guide: install, dump, run every lens, write .nornir/architecture.ron, read architecture-report/1, every lens in one table |
lenses.md |
the reference: every lens with how to run it, what it reads, the algorithm, the output, the tests |
lens-upgrade-plan.md |
PLAN: every lens on the lossless graph of schema 8 (what was lossy, the real wrong results it caused, the upgrade per lens, the order of work) |
macro-lenses-plan.md |
the plan behind the macro lenses (M1–M7, all built: lenses.md §4.27–4.34): architecture recovery, layering, seams, concept ownership, ports and adapters, reflexion against a target model, refactor pricing |
exec-graph.md |
the executable graph as built (schema 8): the lossless code_mir table and its derived view, keys, the instance pass, mimir-exec (reach, values, the system-call order relation, async bodies, entries, phases), the three stores, what was measured (lens gate, sizes like for like, the losslessness check), what is not covered, node ids for a run-time recorder |
exec-graph-plan.md |
the plan the chapter above came from (kept for its design decisions D1 to D6); where the two differ, exec-graph.md is right |
1. Glossary
| word | meaning |
| unit | one crate compiled once by cargo (a lib, a bin, a test harness, a dependency) |
| dump | the Arrow IPC files mimir-driver writes for one unit |
| moniker | a node id: a SCIP symbol string, rust-analyzer cargo <package> <version> <descriptors> |
| member | a repo the nornir workspace registry lists |
| consumer | the nornir server loop that clones a member, checks out a commit, runs the dump and writes the lake |
| lake | the skade warehouse (Iceberg tables, Parquet files) under <builds>/mimir_v2/lake |
| event table | an append-only table of + / - rows; the graph at a commit is the fold of its events |
| open set | the rows of a repo with a + and no later - |
| universe | the dependency ring: dependency and toolchain crates, dumped once per crate@version and toolchain, kept apart from the members' rows |
| room | mimir-room's in-memory Snapshot: node table, spans, one GraphBLAS matrix per edge kind |
| scope | which repos at which sequence number, universe on or off |
| surface | a named reader question (Viz.CrateGraph, Knowledge.Source, …) answered over gRPC CodeRoom.Answer |
| lens | a named analysis over the room (generic_cost, zombie_features, change_risk, …) |
| skade | our Iceberg/Parquet warehouse crate |
| gix | the pure-Rust git library used to read file text at a commit |
2. Flow
repo clone at commit C
│
▼
cargo-mimir-dump ──► cargo check --workspace --all-targets
RUSTC_WRAPPER=mimir-driver RUSTUP_TOOLCHAIN=nightly-2026-10-05
target dir: target/mimir env: MIMIR_OUT MIMIR_REPO MIMIR_SHA …
│
│ per unit: <crate>@<version>-<hash>.nodes.arrow
│ <crate>@<version>-<hash>.edges.arrow
│ <crate>@<version>-<hash>.spans.arrow
│ <crate>@<version>-<hash>.symbols.arrow
│ <crate>@<version>-<hash>.stats.json
▼
┌────────────────────────────────────────────┐ ┌──────────────────────────────────┐
│ nornir code room (server) │ │ dwarves `cargo lens graph-package` │
│ consumer: read_out_dir → delta vs open set │ │ reads *.edges.arrow, kind CALLS │
│ ONE skade atomic release per (repo, sha) │ │ joins with cargo metadata + lock │
│ lake: <builds>/mimir_v2/lake (Parquet) │ │ writes target/dwarves/ │
│ mimir-room: fold → Snapshot (GraphBLAS) │ │ graph-package.znippy │
│ reader::door surfaces + mimir_lens::answer │ │ (see dwarves docs/v2/README.md) │
│ gRPC CodeRoom.Answer :7878 │ └──────────────────────────────────┘
│ proxied by the dwarves forge on :7900 │
└────────────────────────────────────────────┘
│
▼
dwarves-gui (Coderoom tab, graph source pane) → facett CoderoomPane
3. The producer
3.1 cargo-mimir-dump
crates/mimir-driver/src/bin/cargo-mimir-dump.rs. It is a cargo subcommand (cargo mimir-dump) that does two things:
- One
cargo metadata, written to<out>/crate-versions.json(MIMIR_CRATE_VERSIONS). cargo check --workspace --all-targets(orcargo buildwith--build) with the driver as the rustc wrapper.
| setting | value | where |
| toolchain | nightly-2026-10-05 (RUSTUP_TOOLCHAIN) |
crates/mimir-driver/src/lib.rs MIMIR_NIGHTLY, crates/mimir-driver/rust-toolchain.toml |
| wrapper | RUSTC_WRAPPER=mimir-driver (every crate, dependencies included); --workspace-wrapper uses RUSTC_WORKSPACE_WRAPPER instead (members only) |
cargo-mimir-dump.rs |
| target dir | <workspace>/target/mimir by default (--target-dir); nightly fingerprints are kept apart from stable's |
cargo-mimir-dump.rs |
| output dir | --out DIR, passed as MIMIR_OUT |
cargo-mimir-dump.rs |
| index dir | MIMIR_INDEX_DIR = <target dir>/mimir-index |
cargo-mimir-dump.rs |
| repo name | --repo NAME, passed as MIMIR_REPO (the directory name by default) |
cargo-mimir-dump.rs |
| commit | git rev-parse HEAD, passed as MIMIR_SHA |
cargo-mimir-dump.rs |
| other flags | --manifest-path, --workspace-only, --build, --std, --driver PATH, -- <cargo check args> |
cargo-mimir-dump.rs USAGE |
| exit code | 0 ok; cargo's code when cargo check fails; 3 when cargo metadata fails |
cargo-mimir-dump.rs |
The nornir consumer runs it with --keep-going, so every crate that compiles is dumped even when another does not (crates/mimir-ingest/src/driver.rs).
3.2 mimir-driver
crates/mimir-driver/src/main.rs. A clippy-shaped rustc driver: it drops cargo's rustc from argv, compiles in process with its linked nightly, and registers a rustc_driver::Callbacks whose after_analysis hook walks the TyCtxt and returns Compilation::Continue, so rustc still emits the metadata cargo expects. Proc macros, build scripts and --print calls are plain rustc. The walk is crates/mimir-driver/src/walk.rs (and walk/).
3.3 Helpers
| binary | role | where |
mimir-capture <stdout-file> -- <program> <args…> |
runs a tool, copies its stdout byte for byte to a file while streaming it; used on the container road, where the engine's log API frames stdout and would cut cargo metadata's one-line JSON |
crates/mimir-driver/src/bin/mimir-capture.rs |
mimir-super <job.json> |
the super build: one cargo-mimir-dump per repo of a workspace, run together over one target dir with cargo's -Zfine-grain-locking and one shared jobserver; writes <out>/.super/{cargo.json,cargo.err,exit,ms} per repo |
crates/mimir-driver/src/bin/mimir-super.rs |
3.4 The per-unit files
For every compiled unit the driver writes, under MIMIR_OUT:
<crate>@<version>-<hash>.nodes.arrow table code_nodes
<crate>@<version>-<hash>.edges.arrow table code_edges
<crate>@<version>-<hash>.spans.arrow table code_spans
<crate>@<version>-<hash>.symbols.arrow table code_symbols
<crate>@<version>-<hash>.stats.json counts and timings
<crate>@<version>-<hash>.mir.arrow table code_mir (schema 8, one row per body)
<crate>@<version>-<hash>.inst.arrow table code_instances (schema 8, one row per instance)
<crate>@<version>-<hash>.data.arrow table code_data (schema 8, one row per static)
<hash> is the short hash of the rustc invocation. A toolchain crate (core, std, …) has a URL as its version; its files say @lang (walk.rs, the stem computation).
Format: Arrow IPC file format (arrow_ipc::writer::FileWriter), one RecordBatch per file, written to a temporary name and renamed (walk.rs write_ipc). Every batch carries two schema metadata keys: mimir.schema = 8 and mimir.table = the table name (crates/mimir-schema/src/lib.rs SCHEMA_VERSION, META_SCHEMA, META_TABLE).
The three lossless files have zstd buffers and up to 2,048 rows per batch; a unit may lack any of them. A program's --exec unit has only these three and <stem>.exec.json.
The columns of the tables, the node kinds and the edge kinds are in tables.md. In short:
| table | one row per | key columns |
code_nodes |
item (crate, mod, fn, struct, field, param, feature, …) | id, kind, level, name, parent, repo, crate, version, file, start_line, end_line, cfg, origin, provenance, visibility and doc columns |
code_edges |
semantic edge (from, to, kind) |
from, to, kind, count, provenance, negated/partial (GATED_BY), access (TOUCHES_TABLE) |
code_spans |
position of a node or of one edge site | target (node/edge), key, file, start_line, end_line |
code_symbols |
linked symbol | id, symbol, demangled, role, crate, version, binary, section, addr, size |
MIR facts have ONE lossless form since schema 8: the code_mir rows of <stem>.mir.arrow, one row per body, for every crate. The mir_body node of a fn (level part, in code_nodes) stays: its payload, the block fingerprints, is derived from those rows. The driver writes no mir_block node and no CFG_SUCC edge. A reader that wants the blocks derives them from the same rows with mimir_schema::code_mir::legacy and gets the bytes schema 7 wrote.
Positions live apart from identity: an edge row has no line numbers; its sites are code_spans rows with target = "edge" and key = from ␞ to ␞ KIND (mimir_schema::edge_key, separator U+001E). Moving code changes spans, never an edge.
For the whole path from the compiler to the lens matrices, with the rustc queries, the MIR payloads, the loaders and the semirings, see compiler-to-graph.md.
4. Ids: SCIP monikers, no SCIP tooling
Every node id is a SCIP symbol string in the shape rust-analyzer scip emits:
rust-analyzer cargo <package> <version> <descriptors>
The one formatter is crates/mimir-schema/src/moniker.rs. Nothing else assembles that string, so two producers that name the same definition write the same bytes.
| descriptor suffix | form | used for |
| Namespace | name/ |
modules, the crate root crate/ |
| Type | name# |
struct, enum, union, trait, type alias, variant |
| Term | name. |
const, static, field |
| Method | name(). |
fn, method |
| TypeParameter | [name] |
generic params, impl self/trait slots |
| Parameter | (name) |
fn params |
| Meta | name: |
derive/attribute macros, non-lib target prefix ( bin=x: ), mono instances, cargo features ( feature=x: ) |
| Macro | name! |
bang macros |
A non-lib target (bin, test, bench, example) gets one leading Meta descriptor naming the target, because rust-analyzer gives those the lib's ids. Impl scopes are rust-analyzer's: impl#[Self]new()., impl#[Dog][Greet]greet()..
What this is not: there is no index.scip, no scip-* indexer and no SCIP protobuf anywhere in the path. Only the id grammar is SCIP's. Readers that need the package of a callee parse it out of the moniker (the dwarves graph-package join does this in crates/mimir-reach/src/lens_graph.rs moniker_package).
5. Ingest into the lake
The nornir server's code room (src/code_room/) is the only writer of the lake.
5.1 The consumer loop
src/code_room/consumer_loop.rs, crates/mimir-ingest/src/consumer.rs. For a member:
- Its own clone under
<builds>/mimir_v2/clones/<member>(git::sync_clone); the server's registry clone is not touched (src/code_room/mod.rs). - Checkout of the next commit after the member's offset (
checkout::checkout_changed). cargo-mimir-dumpon that tree (section 3.1), on one of three roads:
| road | setting | where |
| installed | NORNIR_MIMIR_DRIVER = path of cargo-mimir-dump (its sibling mimir-driver is the wrapper); without it: <nornir home>/mimir-driver/, beside the binary, then PATH |
src/code_room/driver.rs |
| wrapped | NORNIR_MIMIR_DRIVER_HOST = a command line every tool is started through (for example a podman run … <toolchain image>); NORNIR_MIMIR_TOOLCHAIN names the nightly when it is not the pinned one |
src/code_room/driver.rs |
| container | NORNIR_MIMIR_CONTAINER_IMAGE (+ NORNIR_MIMIR_CONTAINER_DRIVER, a host dir with cargo-mimir-dump, mimir-driver, mimir-capture built for the image; NORNIR_MIMIR_CONTAINER_BINDS): every tool runs in that image through draupnir, under mimir-capture; tried first, the host road taken when it does not work |
src/code_room/container.rs, containers/mimir-driver/Containerfile |
read_out_dirreads the unit files back (crates/mimir-ingest/src/driver.rs): every<stem>.stats.jsonunit with its lossless files where it has them, and every program's<stem>.exec.jsonunit, under the ownerpackage@version:bin:name+exec.- Delta against the open set: rows the new dump has and the open set lacks become
+events, rows the open set has and the dump lacks become-events. - One skade atomic release: the event tables, the consumer offset and the ledger rows (and the universe manifest when it moved) advance together or not at all (
crates/mimir-ingest/src/log.rscommit, under one commit lock).
5.2 Where and how it is stored
| item | value | where |
| warehouse | skade, namespace mimir_v2 |
crates/mimir-ingest/src/schema.rs NAMESPACE |
| directory | <builds>/mimir_v2/lake |
src/code_room/mod.rs |
| clones | <builds>/mimir_v2/clones/<member> |
src/code_room/mod.rs |
| data files | Parquet, zstd level 3, 256 k-row row groups | log.rs write_props |
| catalog | skade's (redb) | skade |
| event prefix | repo, seq, sha, op, krate, payload_hash, key_dup, rev ahead of the payload columns of section 3.4 |
schema.rs PREFIX |
| universe rows | repo = "@universe", in their own universe_*_events tables once the lake is split |
schema.rs UNIVERSE_REPO, PROP_RING |
The tables (details in tables.md):
| stream | tables |
| source (driver dumps) | code_node_events, code_edge_events, code_span_events; the lossless code_mir_events, code_instance_events, code_data_events |
| universe ring | universe_node_events, universe_edge_events, universe_span_events, universe_mir_events, universe_instance_events, universe_data_events, universe_manifest |
binary (mimir-binary) |
code_symbol_events, bin_node_events, bin_edge_events, bin_ingest_runs, bin_offsets |
| bookkeeping | consumer_offsets, ingest_runs, consumer_feed_runs, backfill_runs |
Provenance (source_only / binary_only / both) is derived at read time by joining the source and binary streams on the moniker; it is not stored as a fact of the room.
The lossless tables are event tables of the source stream like the graph's: the same delta, the same atomic release, the same ring. A row is keyed by its identity (def, inst, id, made stable) and re-emitted when its content changes; moved code changes no content. They hold no mir_block row and no CFG_SUCC edge. They are read on demand only (crates/mimir-ingest/src/lossless.rs): EventLog::code_mir, code_instances and code_data give the open rows of one repo, or of one crate of the ring, bounded by a byte cap. A lake stamped before schema 8 is wiped at open and read again from git (tables.md, section 2).
6. mimir-room: the in-memory snapshot
crates/mimir-room/src/lib.rs, snapshot.rs, scope.rs.
A Scope is a map repo → seq plus universe: bool. Folding the log at a scope gives an immutable Snapshot:
| part | content | where |
Monikers |
every node id, sorted; the position is the node's dense ordinal | snapshot.rs |
NodeTable |
interned strings and every payload column of code_nodes by name |
snapshot.rs |
| spans and sites | node spans and edge sites (file, lines) of the scope's repos | snapshot.rs, attrs.rs |
Matrices |
one boolean CSR GrB_Matrix per EdgeKind; unions over several kinds and transposes built on first use and kept |
snapshot.rs |
| symbols | the linked symbols (size, section, role, artifact) | snapshot.rs |
| provenance | derived per node (mimir_schema::derive_provenance) |
lib.rs |
A room holds a fn's mir_body node and nothing of the lossless tables: no fold reads them, so they cost a room no memory and no load time. The one lens that needs the MIR blocks (the clone lens) reads them beside the room, for one repo and under a byte cap: MirSide::load calls EventLog::mir_block_payloads, which derives the schema-7 block payloads from the repo's code_mir rows (crates/mimir-room/src/mir.rs).
Room = Arc<Snapshot>. A built snapshot is cached per process, keyed by the log identity (the event tables' UUIDs) and the resolved scope; a new commit to any event table moves the log's stamp and drops that log's entries.
The graph engine is crates/mimir-graph: a small safe layer over SuiteSparse:GraphBLAS through crates/graphblas-sys. It imports boolean adjacency from CSR or from Arrow (from_ord, to_ord) batches and provides the semiring operations the lenses use: reachability forward and reverse with many seeds, fan-in/fan-out, fewest hops (MIN_PLUS), worst value on a path (MAX_SECOND), scope masks, A·Aᵀ similarity, connected components, diff. Every handle is RAII (GrB_free on drop); the library is initialised once per process.
The typed query API (no query language):
| question | call |
| what is this node | Room::node, attr, display_path, span, sites |
which node is a::b::c |
Room::lookup |
| node sets | Room::mask, mask_of, Mask::union |
| walks | Room::reach, dependents, dependents_by, hops_from, min_hops_to, path |
| weights | Room::bottleneck, worst_on_path |
| edges out, collapsed | Room::edges, Room::quotient (by crate, module, repo, file) |
| binary | Room::symbols, symbols_of, instances_of |
| identity | Room::duplicate_keys, Room::diff |
Every answer carries Evidence: which tables fed the room, which edge kinds the query walked, and what is missing. With the universe off, walks stop at the reference stubs of dependency and std items and the evidence says so (query.rs). A SYSCALLS walk says MISSING until the universe is folded in: the boundary producer writes its edges into the universe (section 10).
7. Readers: surfaces, the code room door, and the panes
src/code_room/reader/wired.rs dispatches a surface name to a function over a Reach (the room plus the workspace it was read from). The surfaces on main:
Knowledge.SymbolLookup Knowledge.DefinedIn Knowledge.Callers Knowledge.Callees
Knowledge.CallPath Knowledge.FeatureGates Knowledge.Context Knowledge.Map
Knowledge.Symbols Knowledge.Source Knowledge.TestReach
Viz.Knowledge Viz.Architecture Viz.CrateGraph Viz.CallGraph Viz.FunctionTier
Viz.CodeMap Viz.Binary Viz.TestMatrix.inventory
Test.List Test.Affected Arch.History Mimir.SqlTemplate Mimir.SystemBoundary
Two of them are what the code room panes draw:
| surface | answer | where |
Viz.CrateGraph |
the room collapsed by crate: one node per name@version, one join per pair with its DEPENDS_ON, CALLS and USES_TYPE counts (room.quotient(GroupBy::Crate, …)) |
src/code_room/reader/wired/panes.rs crate_graph |
Knowledge.Source { moniker } |
where the item is (repo, file, span) and the text of the span, read with gix from the consumer's clone at the commit the room was consumed at (scope().offset_shas[repo]), so line numbers match the spans |
panes.rs source, blob_at |
Wire: gRPC CodeRoom.Answer on the nornir server (loopback :7878 by default, --addr / NORNIR_SERVER_ADDR; src/server_grpc.rs). The dwarves forge proxies the same service on its own port (:7900, dwarves crates/dwarves/src/warehouse_proxy.rs CodeRoomProxy), so one URL gives a client the forge and the room.
Who asks: dwarves-gui's Coderoom tab (dwarves-gui/src/coderoom.rs) mounts facett's shared CoderoomPane (facett-coderoom-view) behind a CoderoomDataSeam that asks Viz.CrateGraph and Coderoom.SnapshotDiff; the graph source pane (dwarves-gui/src/graph_source.rs) asks Knowledge.Source for the node a person clicked. Neither reads a table or a file; the seam is dwarves-gui/src/room.rs (ForgeRoom, CodeRoom.Answer through the forge).
8. Lenses
Start with the user guide, lens-guide.md: install, dump, run, the target architecture, the reports, every lens in one table. The reference manual is lenses.md (https://www.gunnar.rs/nordisk/nornir/tree/docs/v2/mimir/lenses.md). It covers the nornir lenses behind src/mimir_lens/mod.rs answer(lens, reach, params) and the graph lenses of crates/mimir-lens/ over a Mímir v2 dump (doors dwarves lens … and cargo lens …), with inputs, algorithms, outputs, tests and worked examples.
9. What is NOT there
| item | state | where |
symbol_facts, call_edges, feature_gate_facts, scip_occurrences, test_inventory, warehouse_access_edges, architecture_wiring, knowledge_scans, semantic_clusters, lens_runs, feed_runs |
v1 tables with no writer; listed so no reader is written for them again | src/code_room/status.rs V1_TABLES |
universal SCIP import (index.scip from any scip-* indexer) and the syn tarball scan |
removed with v2; only the moniker grammar stayed | crates/mimir-schema/src/moniker.rs |
Coderoom.Compat, Coderoom.WhatBreaks |
answered not_wired: the world ring (crates.io versions of other crates) has no producer |
dwarves-gui/src/coderoom.rs module doc |
| a git remote URL per repo in the lake | not stored; the registry row has it | src/registry.rs |
| file text in the lake | not stored; read on demand with gix (section 7) |
panes.rs |
10. The system boundary and the possible system calls
The kernel tooling as a whole (Build Thing, Heimdall tooling, Mímir, and the files between them) is described in heimdall docs/v2/kernel-tooling.md.
10.1 The boundary in the universe
crates/mimir-ingest/src/boundary/ writes the system boundary into the dependency universe, once per toolchain and target, after a consume (src/code_room/consumer_loop.rs ensure_boundary → boundary::ensure):
| owner | rows | where |
@syscall@<arch>.r1:table |
one syscall node per call of the architecture's kernel table (ty = its class, doc = number and entry point) plus <indirect> |
boundary/mod.rs table_rows |
@syscall@<owner>.<key>.r1:map |
per dependency owner: SYSCALLS edges from its ffi nodes (through the glibc / musl map), from fns that hand the raw door a number, and from the raw door to <indirect>; key is the libc on x86_64 and <arch>-<libc> on every other architecture |
boundary/mod.rs map_edges, map_owner, Target::map_key |
@kernel@<release>+<hash>:btf |
the kernel's types and functions from BTF; CALLS from each syscall node to its entry function | boundary/mod.rs kernel_rows, boundary/btf.rs |
The syscall tables:
| arch | source | file |
x86_64 |
linux-6.12.104 arch/x86/entry/syscalls/syscall_64.tbl (common + 64), numbers past it from the UAPI <asm/unistd_64.h> of linux-libc-dev 7.0.0-38 |
boundary/x86_64.rs (generated by boundary/gen/gen_x86_64.py) |
aarch64 |
linux-6.12.104 scripts/syscall.tbl with the ABIs arm64 builds (common, 64, renameat, rlimit, memfd_secret), numbers past it from the UAPI <asm-generic/unistd.h> of linux-libc-dev 7.0.0-38 |
boundary/aarch64.rs (generated by boundary/gen/gen_aarch64.py) |
The libc map names x86_64's calls. aarch64 has no legacy calls (open, stat, fork, pipe …): boundary::calls_on(arch, name) turns each one into the call(s) the C library makes there instead (boundary::GENERIC_STAND_INS: open → openat, rename → renameat, renameat2, …), so no mapped call is dropped on aarch64. A legacy call with no stand-in (iopl, modify_ldt, the x86 thread-area calls) has no edge.
10.2 The possible system calls (the max)
crates/mimir-room/src/possible.rs, Room::possible_syscalls(entries, opts), and the CLI verb:
mimir-room <lake> possible [ENTRY…] [--bin NAME] [--target TRIPLE] [--binary NAME] [--out FILE] [--runtime off] [--repo NAME]…
ENTRY is a moniker or a readable path of a fn; --bin NAME takes the main of that bin target (moniker ending bin=NAME:main(). , - read as _: the crate name). The room is opened with the universe folded in. The verb refuses to write a file when an entry is unknown or the room holds no syscall node of the target's architecture.
With --bin the program is a linked executable (PossibleOpts::runtime, off with --runtime off): std's rt::lang_start and rt::lang_start_internal are entries too when the room holds them, and the calls the C runtime's start-up makes before main are possible, with the path <C runtime start-up, before main> (mimir_ingest::boundary::libc_map::startup: musl on x86_64 arch_prctl, set_tid_address; glibc adds set_robust_list and, uncertain, rseq, prlimit64, brk, mmap, mprotect, readlinkat, getrandom). A source graph never sees that code.
The walk is breadth-first over CALLS and SYSCALLS from the entries; a syscall node is an end (the walk does not go on into kernel functions). It runs twice: first over resolved edges only (what it reaches is direct), then from everything reached, widened at every unresolved site:
| site | how the driver writes it | how the walk widens it |
a trait method with no resolved impl (dyn Trait, a generic bound) |
CALLS to the trait's own method (walk.rs resolve keeps the trait method when the args are generic or dyn) |
every method of that name in every impl that IMPLEMENTS the trait, in the whole room |
a call through a value: a closure, a Box<dyn Fn…>, a generic F: Fn… |
CALLS to the Fn / FnMut / FnOnce method rustc records for the overloaded call (resolved to the impl when the type is concrete) |
the impls of that Fn* trait, and every fn a static or const initializer names (once) |
| a call through a fn pointer | CALLS to core::ops::function::Fn::call (a fn pointer implements Fn) |
as above |
a fn named as a value (x.map(a::tiny)) |
CALLS from the body that names it |
none needed: the edge is there |
our own extern "C" item (no SYSCALLS edge: the producer maps dependencies only) |
an ffi node |
the libc map, calls_on for the architecture |
the raw door (libc::syscall, a rustix raw stub) |
SYSCALLS to <indirect> |
none possible: listed, the number is chosen at run time |
Closure bodies are part of the fn that defines them (walk.rs BodyVisitor), so their calls are reached with that fn.
The output is the Graph JSON that dwarves-tune-graph's read_graph reads, with extra fields its reader ignores:
| field | type | content |
format |
string | mimir.possible-syscalls/1 |
binary, arch |
string | the program's name, the architecture |
modules |
array of {name, path, reached_functions} |
per crate (name@version) the fns reached; path = the repo or @universe |
entries |
array of string | the entry points (readable paths) |
syscalls |
map name → {nr, direct, path, site, sites, uncertain} |
path = the shortest chain entry → … → the fn that makes the call (a direct maker first); site = that fn with its crate; sites = how many reached fns make it; uncertain = every maker's hop is UNCERTAIN |
unnamed |
array of number | empty (a source graph names every call) |
unresolved_sites |
array of {module, function, offset, direct, callee, reason, targets} |
every widened site and every raw door reached; offset is 0; targets = how many targets the site was widened to |
missing_libs |
array | empty (the room holds every crate) |
unresolved_imports |
array of string | reached C functions the libc map has no answer for (they may make any call) |
dlopen |
bool | a reached C function is dlopen / dlmopen |
indirect_sites |
number | widened sites (dispatch + value calls) |
reached_functions |
number | fns, methods and ffi items reached |
The evidence (stderr of the verb) says MISSING when the universe is off, when no syscall table exists for the architecture, and when a reached C function is unmapped.
The binary walk and the source-graph stubs inside dwarves-tune-graph (walk.rs, srcgraph/) stay the owner's and are not used here.
10.3 Dependency sources from Holger
The walk covers third-party crates because the room holds them: the driver compiles and dumps every crate a program links (RUSTC_WRAPPER, §3.1). Their exact sources come from a registry. With MIMIR_CRATE_MIRROR set to a cargo sparse index that mirrors crates.io (Holger's pull-through sparse+<holger>/production/crates-io/, or the dev pair's sparse+http://127.0.0.1:18444/cache/index/), they come from that mirror and never from the network (crates/mimir-ingest/src/crate_mirror.rs):
| setting | effect |
MIMIR_CRATE_MIRROR=sparse+… |
DriverDumper::with_env_crate_mirror writes <target dir>/mimir-cargo-home/config.toml ([registries.holger], [source.crates-io] replace-with = "holger"); every cargo the dumper starts (clean, unit graph, the dump, the super build's entries) gets CARGO_HOME there |
MIMIR_CRATE_MIRROR_TOKEN_ENV=<VAR> |
the read token is taken from <VAR> and handed to cargo as CARGO_REGISTRIES_HOLGER_TOKEN (credential-provider = "cargo:token"); it never lands in a file or an argv |
MIMIR_CRATE_MIRROR_CAINFO=<file> |
[http] cainfo for a mirror behind a private CA |
Before the dump, cargo fetch --locked gets every Cargo.lock entry from the mirror, so a crate the mirror cannot serve fails there, by name. Holger is read through the registry door it already serves (the sparse index and the download). Cargo checks each .crate against the lockfile's checksum. A repo's own .cargo/config.toml (a vendored source) still wins. The code room's dumpers (src/code_room/consumer_loop.rs) and the consume example take the mirror from the environment.
Proof (2026-11-04, Holger's scan worker as in §10.2, Holger's dev pair with the rust-remote download fix): 758 registry crates came from Holger's /cache and none from crates.io; the max is 118 calls (77 direct), the scan probe's 23 and the job probe's 30 measured calls are all inside it, 0 gaps; 25 of the 118 are made in a registry crate Holger served (libc 0.2.191, rustix: io_uring_setup, landlock_create_ruleset, epoll_*, futex, mmap, …).
11. Consumers outside nornir
| consumer | reads | where |
dwarves cargo lens graph-package |
the driver's *.edges.arrow (kind CALLS) of a dump it runs or is given; never the lake |
dwarves crates/dwarves/src/lens_graph.rs |
| dwarves-gui | CodeRoom.Answer and the Mimir.* lens services through the forge |
dwarves-gui/src/room.rs, mimir_lens.rs |
| facett | plain node/edge rows handed to CoderoomPane, Graph3D, WarehouseDeck; no Mímir reader of its own |
facett facett-coderoom-view/src/lib.rs |
Next (planned, not built)
- A second producer: a universal SCIP import of any
scip-*indexer'sindex.scipinto the samecode_nodes/code_edgestables, same ids. - A viewer trait in facett (
crates,symbols,calls,span,text) with a znippy reader and aCodeRoom.Answerreader behind it.