Working state — a note taken while the work happens, not a specification. The system as it is meant to be is in Architecture.
LANDED on
arc/one-pool. What follows is D2’s report, kept because every claim in it held. The one thing it did not predict is at the bottom, under “What landing it actually cost”.
R6 one-pool: exactly what is left, measured from D2
Written while landing D2 (pit product). This is the “if full one-pool proves
too large, REPORT precisely what it needs” half of that brief, and it is a
report rather than a plan: every claim below is a file and a line in the tree
as of arc/product.
The state D2 leaves
The press is still one pool per unit, on the STATIC link lane. Nothing in
this landing changed that, and changing it halfway would have been worse than
not changing it — see §3. boot_cart.pool_executable still calls
mach_lower.press_unit once per module, and press_unit still emits a
one-row pool (pit-shop/mach_lower.cm:292). The census the press logs on
every executable is unchanged and still honest:
press: linked 50 unit(s), 0 finalized site(s), 656556->651324 instructions,
75828->75031 guards, 4214->1639 literal rows, 1469 names + 6 log sites
stripped
0 finalized site(s) is plans/archive/night-2026-08-04/press-switch.md §4 restated:
the units carry no import op for the linker to finalize, because the press
compiles them on the static lane where a use() lowers to a context load.
What D2 did add to that line is the last two figures: stripping is live on
the product lane (1,469 names and 6 log sites removed from the mcode
product’s 50-unit program), and inert everywhere else.
What one-pool needs, in dependency order
1. The press must compile on the pool lane — and must not do so alone
shop_build.cm sets link_mode from msg.link == "pool" ? "pool" : null
(:1788), and the realize lane threads it through
shop_realize.cm:441, :1068. Passing link: "pool" from realize_fleet
(shop_tools/cement.ce) is a one-line change and is the wrong thing to land
on its own: on the pool lane the linker finalizes cross-unit edges into
["function_ref", d, provider_unit_id, fn_id, member]
(pit-linker/mcode_link.cm:1242), which source/mach_pool.c:4590 lowers into
EM_ABX(MACH_LOAD_MODULE_RESULT, s.module_scratch, provider_unit)
EM_ABX(MACH_LOADK, s.key_scratch, cpool(member))
EM_ABC(MACH_LOAD, ref_dest, module_scratch, key_scratch)
and MACH_LOAD_MODULE_RESULT indexes state.base + bx into the actor’s ONE
results array (source/mach_vm.c:5433). With one unit per pool the provider is
in a different cart section and that index names nothing. Flipping the lane
without §2 and §3 produces a cart that presses cleanly and dies at run time.
2. The import covering — the shop already computes it
mach_pool_emit.emit takes, per row, imports: [...] indexed by dense import
id, where {unit: <executable-local id>} means “the provider is in this same
image” (shoplib/mach_pool_emit.cm:16-25, finalize_imports at :352). The
press currently hands it imports: [] (boot_cart.cm’s link_rows_for),
which is the honest covering for a statically compiled unit set.
The facts to fill it with already exist and are already dense:
record_manifest_unit (pit-shop/shop_build.cm:1199-1218) builds, per unit,
imports: [{request, provider_locator}] in exactly the order
compiler.portable_imports gives the dense ids — the comment at :1209 states
that invariant. executable_manifest.build consumes the same array to compute
initialization order (pit-shop/executable_manifest.cm:240). So the covering is
index_of[provider_locator] over the press’s own row order, and the only work
is carrying that array out to pool_executable, which today receives module
rows carrying locator/mcode/mach and not imports
(shop_build.executable_module_refs, :362).
3. One pool, N units, and the plan row that says which unit
This is the real work, and it is smaller than press-switch.md assumed because the C side is already parameterized.
The emitter is ready. mach_lower.link_fragments(rows, options) is an N-row
fold (pit-shop/mach_lower.cm:160) and mach_pool_emit writes one
unit_instance per pool entry (shoplib/mach_pool_emit.cm:769). Only the two
convenience wrappers (lower_unit, press_unit) hard-code a single row.
The cart format is ready. cement.make_module_table dedups module rows by
mach content hash, so N modules sharing one pool collapse to ONE section
(pit-shop/cement.cm:593-617), and plans_for/unit_row give every plan UNIT
entry that section’s placed coordinates (:761-782). Nothing there needs a new
concept.
C is ready, with one gap. pit_private_image_provider_resident_function
already takes an entry_index (source/image_provider.c:1014) and selects that
pool entry (Pit_MachPoolEntry(binding->binding.image, entry_index, &entry),
:1001). The gap is one caller: plan_run_pool passes a hard-coded 0
(source/start_plan.c:295), and plan_unit_range reads only off/len out of
the plan row’s unit record (:366-390).
So the missing piece is a number in the plan row. Each ENTRY_UNIT row needs
an entry field beside off/len/hash, naming which unit of the shared pool
this plan entry runs. Concretely:
| where | change |
|---|---|
pit-shop/boot_cart.cm | press once per executable; give each module row the shared pool hash plus its pool entry index |
pit-shop/cement.cm plan_record (:552) | copy the placed row and add entry |
pit-shop/cart2.cm | encode the field (plan rows are nota records, so this is additive) |
source/start_plan.c | read entry in plan_unit_range, thread it to plan_run_pool’s entry_index |
engine/boot_walk.cm | nothing — it passes row.unit through whole (:168) |
Absent field = 0 = today’s behaviour, so every existing cart keeps booting and the dev fixpoint is unaffected until the press actually emits a shared pool.
4. What it is worth, and what it costs
The payoff is mach_pool.c:4590 becoming reachable: a cross-module call stops
going through MACH_LOAD_BOUND_MODULE and becomes a direct read of the
provider’s already-initialized stone export record. That instruction is
currently dead code that starts working — the deletion inventory §5.4 says so
and this landing confirms it from the other side.
The cost is a genuine format move: the cart’s module→section relation stops
being 1:1, pit cart info’s per-section attribution has to name several
locators per pool, and every hash in the image changes. It is also the point at
which plans/proposal-notes/deletion-inventory.md row 13 (the static link lane,
~550-600 LOC + ~210 test LOC) becomes deletable, together with E1.
The one thing not to repeat
Do not measure “does one-pool pay” against the DEV cart. A dev cart is small
and is meant to be; the numbers that matter are a product’s. For scale, the
mcode product pressed by this landing is 3,629,072 bytes across 52 pool
sections against the development cart’s 9,674,768 bytes across 131 — and
52 of those 52 sections are exactly the 50 modules of one executable plus the
engine and the walker. Under R6 that is one section.
What landing it actually cost (2026-08-06, arc/one-pool)
§3’s table was right and complete: the entry field, plan_record,
cart2 (nothing — plan rows are nota records and the field rode for free),
start_plan.c, and boot_walk.cm untouched. Three things it did not name.
1. Unit ids had to become PLAN SLOTS. §3 said the covering’s
{unit: <executable-local id>} becomes LOAD_MODULE_RESULT bx. What it did
not follow through is what the VM does with bx: state.base + bx indexes the
running actor’s ONE result array, and boot_walk.cm fills that array BY PLAN
ENTRY INDEX (values[i], one slot per plan row, natives and value rows
included). The linker’s dense 0..N-1 ids are not those indices. So
cement.plan_slots now exports the map build_exec_plan already builds, and
link_rows_for uses it. Costs nothing on the static lane (no unit indexes
results there) and is the difference between the pool lane working and the
cart §1 warns about.
2. The early refusal probe calls plan_record with section BLOBS.
cement.build’s probe_plans = plans_for(sections) hands unit_row the raw
blob array so a plan that cannot be built at all refuses with a name. Copying
the placed row to add entry panicked there (“not an object”). Ask whether it
is a record; the probe’s answer is discarded anyway.
3. A resident window was RE-DERIVED once per unit. This is the one that
would have made one-pool look like a bad idea. provider_function_from_bytes
blake2s the whole window and re-opens its header before
provider_adopt_mapping discovers it already holds the placement. With one
pool per program the walk calls it once per unit against the SAME window: a
101-unit program hashed the same five megabytes a hundred times. Measured on
the dev cart: 18.6 s to breed the root shop actor. provider_reopen_resident
keys on (placement_id, base, length) and skips both — 0.24 s after,
against 0.066 s for the old 131-section cart.
The numbers
| artifact | before | after |
|---|---|---|
| dev root cart | 9,682,960 B / 131 sections | 15,048,720 B / 15 (13 programs + engine + walker) |
mcode product | 3,629,072 B / 52 sections | 3,297,296 B / 3 (−9.1%) |
--app pit-shop/logger | 1,069,072 B / 26 sections | 938,000 B / 3 (−12.3%) |
--pool-units unit reproduces the logger cart at 1,069,072 B / 26 sections,
byte for byte — the ROM-window shape is a lever, not a memory.
The dev cart is the one that grew, and the reason is exact: its 131
sections were shared across 14 executables (lang/messaging ×13,
lang/logging ×13, pit-compiler/* ×2…), and one pool per executable
replaces cross-executable dedup with within-executable interning. The trade is
a win per program and a loss per fleet. R6’s literal reading — a cart is ONE
pool, not one per program — would recover it and then some: 336 plan rows over
13 pools would become 336 rows over 1, and a module two executables share
would be one entry instead of two. That needs pool_executable to stop being
the press boundary (it is called per executable by both cement_root_cart and
cement.ce’s press_fleet), and it needs a rule for what unit_instance is
for an entry two executables share with different plan slots — finding 1 says
those are different numbers. Not attempted here.
Still not done: link: "pool"
The press still compiles on the static lane, so finalized_sites is still 0
and mach_pool.c:4590 is still dead. What one-pool removed is the structural
blocker (§1): a finalized provider is now an entry of the same pool, and its
executable-local id is now the number the result array is indexed by. What
remains is the compile lane itself — realize_fleet passing link: "pool" —
which is a second realization of the boot closure under a different key, and
which the dev lane must NOT take (per-unit pools cannot resolve a cross-unit
edge).
Source: plans/proposal-notes/one-pool.md