#43 landed — the roster is a RECORD, instances at computed offsets #178

Manually merged
tvidal merged 1 commit from feat/178-43-landed-the-roster-is-a-record-instances-at into master 2026-07-29 09:30:00 +02:00
Owner

#43 landed — the roster is a RECORD, instances at computed offsets

Along the way:

  • placement(#43): the roster is a RECORD — declared instances at computed offsets

Touches 34 files: plan_sizes.md, src/rust/crates/ck_codegen/src/arg.rs, src/rust/crates/ck_driver/src/compile/fn_main.rs, src/rust/crates/ck_driver/src/compile/mod.rs, src/rust/crates/ck_driver/src/compile/placement.rs, src/rust/crates/ck_driver/src/compile/seat_resolve.rs and 28 more.

#43 landed — the roster is a RECORD, instances at computed offsets Along the way: - placement(#43): the roster is a RECORD — declared instances at computed offsets Touches 34 files: `plan_sizes.md`, `src/rust/crates/ck_codegen/src/arg.rs`, `src/rust/crates/ck_driver/src/compile/fn_main.rs`, `src/rust/crates/ck_driver/src/compile/mod.rs`, `src/rust/crates/ck_driver/src/compile/placement.rs`, `src/rust/crates/ck_driver/src/compile/seat_resolve.rs` and 28 more.
Author
Owner

Split. The decision in each_placed_start_carries_its_roster_position_id is a pure function now, with its own tests in src/rust/crates/ck_load/src/desugar/boot.rs.

Split. The decision in `each_placed_start_carries_its_roster_position_id` is a pure function now, with its own tests in `src/rust/crates/ck_load/src/desugar/boot.rs`.
Author
Owner

It runs per token. The buffer behind plex_seats lives on the struct in src/rust/crates/ck_load/src/loader.rs now and is cleared rather than reallocated.

It runs per token. The buffer behind `plex_seats` lives on the struct in `src/rust/crates/ck_load/src/loader.rs` now and is cleared rather than reallocated.
Author
Owner

Updated. The point about src/rust/crates/ck_load/src/desugar/boot.rs turned out to be a real bug, so thank you.

Updated. The point about `src/rust/crates/ck_load/src/desugar/boot.rs` turned out to be a real bug, so thank you.
tvidal requested changes 2026-07-29 09:25:00 +02:00
Dismissed
tvidal left a comment

Two things inline. The one in src/rust/crates/ck_load/src/desugar/boot.rs is the one I care about.

Two things inline. The one in `src/rust/crates/ck_load/src/desugar/boot.rs` is the one I care about.
@ -317,0 +425,4 @@
/// binding uses, never a second one — and each range covers exactly the
/// placeholder the driver settles.
#[test]
fn each_placed_start_carries_its_roster_position_id() {
Owner

each_placed_start_carries_its_roster_position_id reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?

`each_placed_start_carries_its_roster_position_id` reads, decides and writes in one function. The deciding part is the piece I would want to test on its own. Worth splitting?
@ -477,6 +492,7 @@ impl Ctx<'_> {
actor_provides,
receiver_binders,
registered_actors,
plex_seats: Vec::new(),
Owner

plex_seats allocates a fresh buffer per call. If this runs per frame or per token, can the buffer be reused?

`plex_seats` allocates a fresh buffer per call. If this runs per frame or per token, can the buffer be reused?
tvidal approved these changes 2026-07-29 09:25:00 +02:00
tvidal left a comment

That is it for src/rust/crates/ck_load/src/desugar/boot.rs. Thanks for the second pass.

That is it for `src/rust/crates/ck_load/src/desugar/boot.rs`. Thanks for the second pass.
Owner ruling #43: "a declared roster instance's location is a NUMBER THE
COMPILER KNOWS — never a runtime handle to thread through frames. The
bootstrap's per-actor mmap is scaffolding, not the language." This lands it,
and the shape of the landing is the whole point: there is NO placement law
here. The program is a plex, the roster is a RECORD, the declared instances are
its FIELDS, and a field's address is what a field's address always is —

    actor_size = header + sizeof(state) + Σ concurrency(R) × block_size(R)
    offset(k)  = Σ over j < k of actor_size(j)     ← the record-offset law
    plex bytes = Σ over every placed instance      ← §4's program_size

— folded over exactly the per-actor Σ `ckc sizes` and `--mem` already compose
(`ck_types::arena_size_with_mailbox` + the mailbox width seats). It is the same
pair of numbers the generated `ck_start` used to hand `Floor::mmap`, read off
the driver's own settlement rather than re-derived, so the field the compiler
places and the region the actor initializes are the same bytes by construction
rather than by agreement between two folds. Every number is DERIVED; there is
no size constant anywhere in the new code (#25).

(1) THE PLEX. `ck_lower::program_plex` names one composed region per program.
Its SIZE is not a constant in that module — it is the roster's own Σ, carried
as data on the seat, because it is a fact of one program and not a law of the
compiler. The backend puts it in the object through the ONE generic mechanism
it already has for a compiler-known region (`ExprKind::CellBase` →
`ck_codegen::cell`, the same `(symbol, size)` pair the raise/arena/test cells
use); zero-initialization is load-bearing exactly as a fresh mapping's zeroes
were, because `ck_start_at` installs mailbox headers over slot words that must
start free. A program declaring no registered instance never seats a placement,
never declares the symbol, and is byte-identical to before (#34's opt-in law,
unmoved).

(2) THE CHANNEL is the seat channel, widened by one kind — not a new intrinsic,
not new surface. `SeatFacts` already carries the CONST seat: "a generated
`0::usize` placeholder standing for a number the driver settles and surface
text cannot spell." A PLACEMENT seat is the same sentence for the one number
that is an ADDRESS rather than a count, and it lowers to `CellBase(plex) +
Offset(field)`. `emit_arg` gained `Offset` beside `CellBase` under one widened
sentence — every address-yielding form is one word, whatever region it points
into.

(3) THE START SPLITS where the scaffolding ends, and nothing is duplicated:

    fn ck_start_at(sup, region) -> ActorId   // seat, install, register, spawn, arm
    fn ck_start(sup)            -> ActorId   // acquire a region, then ck_start_at

`ck_start_at` is the whole lifecycle and knows nothing about where its region
came from. The boot expands a DECLARED start to `Mod::ck_start_at(sup.id,
0::usize)` with the placeholder seated to that instance's field; a dynamic
`Spawn::spawn` or a child-start rewrite still calls `ck_start`, unchanged. The
seat's key is the ROSTER-POSITION ID (#38) — the DI binding's own numbering,
never a second one — and the offsets accumulate in that same
`Roster::instance_ids` order. A TRANSIENT row (receive-less: it never
registers, its region ends with its own `spawn`) consumes neither an id nor a
field and keeps the acquiring path: placement places the roster's persistent
instances, it does not outlaw the rest (that is sizes S3's job).

(4) THE KILL, per #17 composed with #43, in its own file now
(`desugar/kill.rs`, the R3 split): a placed region cannot be released and must
not be. `stop` on a placed instance is `despawn` + `disarm` + DEREGISTER, and
that is all — no munmap, no zeroing, no ceremony. The footprint does not move
because the footprint was always the maximum. The REGISTRY remains the liveness
truth: the bytes outlive the instance, so if the address were the truth a send
after a kill would quietly land in a dead actor's live memory. It does not —
`Supervisor::must_base` still raises `Registry::Gone`, pinned by a new cell.

(5) THE ADDRESS AT THE CALL SURFACE. A provider forwarder's `base` becomes the
constant:

    fn ck_op_bump(host: usize, n: Int) -> Int {
      let live = Supervisor::must_base(ck_sup(host), 0::usize); // LIVENESS
      let base = live;   // PLACEMENT-seated (#43) — the field address
      ck_ins_bump(Counter { … }, n)
    }

The registry read stays exactly where it was, for exactly what it is. What
stops being a runtime question is only WHERE, never WHETHER. The change is
FAIL-SAFE by construction: un-seated, the alias IS the registry base, i.e.
byte-for-byte the pre-placement lowering — a program with no placement for that
actor is untouched rather than handed a null.

(6) THE FOOTPRINT. `ckc sizes` grows a program-level `program plex (declared
instances)` node: one child per field naming its path, its roster id, its
offset and its own Σ, with the parent the sum. It belongs beside the timer
table because it belongs to no one actor — but unlike the table it is not
overhead, it IS the actors, placed. Gated in `sizes_query.rs` over a fixture
built so the three claims cannot hold by accident (two instances of one actor
plus one of a differently-sized one): each field's size equals that actor's own
reported region, the offsets are the running Σ in declaration order, and the
total is Σ over the fields.

(7) THE REFUSAL. A declared instance whose Σ carries an unbounded component has
no field, and §1 rule 1 says a bound the compiler cannot discharge is a compile
ERROR — **E0761**, anchored at the roster line that made the promise. It is not
reachable from any cell today (the E0760 door refuses those programs earlier),
so it is landed as the honest alternative to a guessed offset, not as a pinned
behaviour.

WHAT IS NOT DECIDED HERE. #43 queues E0508's re-cut "at the placement slice".
It is REPORTED, not taken: with the address constant, `host` survives in the
forwarder for one reason only — reading `c.sup` for the liveness probe — so
narrowing E0508 means ruling on where a receiver-less caller's registry cell
comes from. That is the owner's question, and no check moved in this commit.

GATES (targeted, this tip): capability 2/2 with CELL_COUNT 362 → 364 (BROKEN 41
and UNMARKED 149 unmoved — both new cells are WORKS) · architecture 35/35 ·
corpus 1/1 · sizes_pin 1/1 (37 pins byte-identical: no corpus app declares a
roster) · sizes_query 20/20 · test_cmd_run 3/3 · runtime_registry, actor_lifecycle,
stdlib_faults, reproducible_emit, check green · ck_load 121 + ck_lower 23 unit ·
clippy `--workspace --all-targets -D warnings` 0 · fmt clean · dead_code deny
clean. Two consecutive builds of a rostered probe emit BYTE-IDENTICAL objects
with `ck_program_plex` at 0x92 = 146 B — the two 73 B fields the report names.

Rust LOC: +659/−214 tracked, plus 517 new (placement.rs 316, kill.rs 152 —
moved out of lifecycle.rs — program_plex.rs 49). Net Rust is UP: this slice adds
the mechanism and deletes nothing, because what it retires is GENERATED CKLANG
(the boot's per-instance `Floor::mmap`), not Rust. `CONDEMNED_ALLOC_SITES` is
unmoved at 392 for the same reason — its rows are hand-written `.ck` call sites.
tvidal manually merged commit ab94595e80 into master 2026-07-29 09:30:00 +02:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
northgale/cklang-bootstrap!178
No description provided.