Skip to content

A shared-memory region draws each page's frame at its first touch - #1270

Draft
Japabu wants to merge 5 commits into
pmm-batchfrom
shm-first-touch
Draft

Japabu wants to merge 5 commits into
pmm-batchfrom
shm-first-touch

Conversation

@Japabu

@Japabu Japabu commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #1268 (base pmm-batch, itself on #1250, on main 29c5f2d): this PR's diff is five commits, the code,
what it owes on metal, review r1, the batched object run and review r3, head e37586c. It is retargeted to main once those land.

Problem

SharedMemObject::create drew and zeroed every frame of a region before it returned, so a region cost its whole size
in time and memory however little of it a process touched, where private memory takes a frame at each page's first
touch. On the spawn path that is a build child's four SHM_CREATEs, by inference from the SDK its directory windows
(2 MiB each): 95 us a call on the T14 and 133 us on the AMD bench with one child at a time, 17 and 19 % of a child's
CPU, and most of what the all-CPU build's children add in CPU (#1265's diagnosis).

Behavior

The orchestrator's decision (recorded in #1265, a-shared-memory-region-draws-and-zeroes-every-frame-when-it-is-made):

  • A region is promised whole when it is made and draws each page's frame at its first touch. create makes a slot
    a page (shm::slot::Slot), each a hole, and promises them all, so a region is still refused at creation
    (ResourceExhausted) and never at a touch.
  • One entry point. Every consumer of a page's frame goes through SharedMemObject::pages(), a run of at most a
    fault's 16 pages, or page(), which asks it for one: an object range's fault (its whole run), the kernel's read
    and write (a copied range's source among them), an inbox's ring words, the clock page. A touch takes each
    installed frame with one Acquire load. If any page it asks for is a hole, it takes the region's first-touch lock
    (sync::Lock), looks again, and has the holes still left drawn by one call of pmm::alloc_promised_zeroed_into:
    one hold of the frame allocator's lock for the draw, the zeroing outside it, and one Release store a page to
    install, all under the region's lock. So a page draws exactly once, against its own promise, and a fault's run
    takes one hold of the allocator's lock, not one a page. The lock order is space, region, allocator, and nothing
    else takes the region's lock.
  • One draw. A region's holes are drawn by pmm::alloc_promised_zeroed_into, the batched form of the promised
    draw pmm::alloc_promised() that map7b's N4 (A served file is mapped read-only and shared, and a fault on its absent page waits for its pager #1269) calls for a MEMORY volume's hole, into its pager's index under
    the pager's lock; both draw through Frames::alloc_promised. Two stores, since a pager's page is revocable and a
    region's never is.
  • Who may call page() is stated there: it may draw, so never in interrupt context and never holding the
    region's or the allocator's lock. Audited at this head: the fault path holds the space lock, where private faults already draw; a
    copied range's source is read under it too; the clock page's one page is drawn by the boot's own page(0); an
    inbox's rings are written in interrupt context, so they are made with create_whole, which draws every page, a
    run at a time. A device's run is unchanged (create_run).
  • A region's end frees what was drawn, a superframe's worth (PER_SUPER) to a hold of the allocator's lock after
    their futex waits end, and gives back the promises of the pages never touched.
  • pmm::alloc_promised_many (from A draw of many promised frames takes the allocator's lock once #1268) and pmm::free_all lose their only callers and go; a fault's run keeps
    A draw of many promised frames takes the allocator's lock once #1268's batch.

Checks

Metal (owed, not run)

The benches are down, so this lands on review, the host checks and the guest rows, and the speed-up is not yet
measured on metal
. Owed on metal, recorded in the first-touch record, which this PR marks built (#1265 landed
first): an A/B of main before and after this on the T14 and the AMD bench, of osbench work
(work.build_shaped.j1, .j8 on the T14, .j16 on the AMD bench), spawn_cost, osbench procs and osbench files. Main after must show work.build_shaped.j1 and .j<cpus> faster on
both, and no proc or fs row slower beyond the before run's spread.

Risks

  • A new caller of pages() or page() in interrupt context on a region made with create would take the region's
    and the allocator's locks there. The rule is at pages(), and the one such caller today, an inbox's rings, is made
    whole.
  • An object range's fault now draws its run's untouched neighbours with the page it touched, as private memory's
    run already does: a touch beside a touched page draws up to 16 frames of a region that its user may never touch.

🤖 Generated with Claude Code

@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #1270 at 14bc8a0 (own commits de14b11, 14bc8a0)

Architecture

The ownership is right and matches the orchestrator's decisions recorded in #1265:

Promising the whole region at creation keeps refusal at SHM_CREATE. Deleting alloc_promised_many and free_all is right: git grep finds no remaining callers or references.

The manual needs no edit. docs/src/architecture/memory.md already says commitment is strict and pages are populated lazily, and this change brings shared regions in line with it.

Net size:

  • Production: about +85 lines (slot.rs +61, shm.rs about +43, inbox +2, pmm.rs −36).
  • Tests and registration: about +140 (the loom model's 125 lines, the control entry and the feature declarations).

page() callers, confirmed at this head:

  • The object arm of Space::populate (space.rs:784), under the space lock, where private faults already draw.
  • read and write through each_piece: a copied range's source under the child's space lock (space.rs:849), and the inbox's ring, which is made whole.
  • ring_word, on a ring made whole.
  • The clock's boot page(0) (clock.rs:67).
  • A Sub, into its whole region.

No caller holds the allocator's lock, which is taken only inside pmm's with. No handler reaches page() on a region made with create, and the mask-windows witness in Lock::lock would catch a future one.

Promise accounting is correct at creation, at a region's end and for a partly touched region. At the end, drawn frames go back through free_drawn and holes through unpromise, and the two together cover every page. The first-touch race is not correct; see blocker 1.

Findings

BLOCKER 1: a toucher that loses the race draws against a promise that is not its page's. kernel/src/object/shm.rs:229-234, kernel/src/object/shm/slot.rs:40-52.

  • What happens. Both touchers call pmm::alloc_promised() before the compare-exchange. A race on one page therefore takes two frames off the machine's promised count, although the page holds one promise. pmm::discard returns one afterwards. Until then the count is one below the promises outstanding, or k−1 below with k touchers.

  • What can fail. Three asserts treat that state as a kernel bug:

    • Frames::alloc_promised (kernel/pure/frames.rs:766, promised > 0);
    • alloc_promised_each (:776);
    • unpromise (:748).

    Any of them fires if, in that window, the machine's outstanding promises fall short. Two examples: the loser's own draw when this page's promise is the last one, or another job's fault-around or region end that needs exactly what is left.

  • Reachability. This is my inference; I have not demonstrated it. Every running process's committed stack keeps many promises outstanding, so I do not claim it is reachable in practice. Even so, correctness rests on spare promises held by unrelated jobs, and nothing states or guarantees them.

  • Contracts broken.

    • alloc_promised's contract says "A zeroed frame a promise held" (pmm.rs:289); the loser's draw holds none.
    • discard's summary, "promise the page a frame again" (pmm.rs:278), is false for this caller.
    • The PR body says the loser "gives its frame and its promise back". The loser never held a promise.
    • The loom model has no promise count, so it cannot see any of this.
  • Fix. Make every draw one that the page's own promise holds. The simplest form I see is a double-checked draw under a per-region sync::Lock:

    1. Fast path: an Acquire load of the slot.
    2. On a hole: take the lock, check again, draw, and store with Release.

    That removes the losing toucher, discard's new caller and its widened safety text. The lock order becomes space, then region, then allocator, and nothing else takes the region lock. It also turns the batch in blocker 2 into a plain loop.

    A per-slot claim, with the other touchers waiting for the install, also works. It adds a custom wait, which then needs the failure timeout the instructions require. Either way, the model should assert one draw per page and keep a negative control for the publication.

BLOCKER 2: the batched draw of a run's holes is recorded only as conditional on a metal reading. issues/a-served-directory-costs-each-process-a-two-mebibyte-window.md:18-25, and the PR body's first risk.

My recommendation on the author's risk: do not batch in this PR; record the batch as owed to the object fault-around.

SUGGESTION 1: kernel/src/object/shm.rs:324-325 writes 512 twice, once as the chunk size and once as the array length. A named constant, such as frames::PER_SUPER as #1268's batch uses, would keep the two from drifting.

SUGGESTION 2: kernel/src/mm/pmm.rs:252: the re-flowed promise doc has a 128-column line. Rewrap it.

Evidence

I ran these checks in a detached worktree at 14bc8a0. Every clippy run used the adopted lints and -D warnings.

Check Result
cargo test --manifest-path kernel/loom/Cargo.toml --test shm_slot exit 0, 3 passed
The same with --features slot-install-relaxed (the negative control) exit 101; a_frame_is_handed_over_with_what_its_drawer_wrote and two_touches_keep_one_frame_and_give_the_other_back fail on loom's causality violation, as the CI table expects
kernel-loom clippy, --all-targets, with and without the control exit 0
cargo test -p toyos-build --lib ci:: exit 0, 21 passed
cargo test -p kernel --lib --features sched-check exit 0, 461 passed
Kernel clippy: the library's tests with sched-check; x86-64 default, boot+test actuators, boot actuators, instruments, both heap-band shapes; aarch64 default, boot+test actuators, boot actuators, instruments exit 0 each

The author's checks-2.log at the same head agrees: all 84 model steps are green, and the new control reaches 2 verdicts.

  • Loom model: sound for what it models. The negative control exposes the ordering defect it names. It cannot expose blocker 1.
  • Guest run 43093481070: still queued when this review was written, so there is no guest result. The fix for blocker 1 changes page(), so landing needs a new run at the fixed head anyway. The 14 rows it carries cover what QEMU can establish for this change.
  • Metal: the owed bullet names the rows, both benches and what main must show. The body says plainly that the speed-up is not yet measured on metal. Both meet the benches-down rule.
  • Manual: no edit needed; see Architecture.

SEND BACK

Japabu added a commit that referenced this pull request Nov 11, 2026
… lock

Two touches of a hole both drew before racing to install, so the loser drew
against a promise its page did not hold, and the machine's promised count
fell short of the promises outstanding until it gave the frame back. Each
region now has a first-touch lock (`sync::Lock`): a touch takes an installed
frame with one Acquire load, and a hole is drawn by the touch that, holding
the lock, still finds it a hole, and installed with a Release store. Every
draw is the page's own promise's; `discard` keeps #1250's caller and Safety
text. The lock order is space, region, allocator, and nothing else takes the
region's lock. The loom model asserts one draw a page, and
`slot-install-relaxed` still reds the publication.

The batched draw of an object run's holes is recorded without condition,
owed by the object fault-around (#1259): whichever of #1259 and this lands
second draws a run's holes in one hold. The free batch is a superframe's
worth by name, and the promise doc is rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #1270, round 2, at 71b2f15 (code changed since 14bc8a0)

Architecture

Round 2 builds the design the orchestrator decided for blocker 1:

  • one Acquire load for an installed frame;
  • on a hole, the region's sync::Lock, a second look, pmm::alloc_promised() and a Release store;
  • no losing toucher.

Slot::frame gets the lock as a guard-returning closure (slot.rs:42), so kernel-loom still compiles the slot without naming the kernel's lock. No new mechanism or state was added. discard's widened caller and Safety text are gone, and pmm.rs's discard now matches the base exactly. The window record carries the batch that blocker 2's decision asks for.

Net size:

  • Round 2: production about +4 lines, the loom model −7.
  • Whole PR against pmm-batch: production about +88 lines (slot.rs 65, shm.rs +52, inbox +2, pmm.rs −31).

Round-1 findings

Blocker 1: resolved. Each case below is checked at this head.

  • One draw a page. slot.rs:42-57 makes every install under the region's lock. A holder's Relaxed second look is ordered after every earlier holder's install, transitively through the lock, so it sees any install that happened. A hole it finds there is a page nobody drew, and only that holder draws it. _held is dropped after the Release store. A slot draws at most once in the region's life.

  • Promise accounting is exact.

    • create promises pages.
    • Each slot's single draw takes one off the machine's promised count (frames.rs:765-769).
    • At the end, free_drawn returns the n drawn frames against committed, and unpromise(holes) returns the rest. n + holes = pages.
    • No draw is ever made without its page's own promise, so the outstanding-promise shortfall round 1 described cannot occur.
    • The three asserts round 1 named (frames.rs:748, :766, :776) are no longer reachable through a first touch.
  • Clock page at boot (clock.rs:66-67). create has already taken the allocator's lock through promise and the heap charge. page(0) takes the region's lock, a sync::Lock of the same kind, uncontended, and the draw it guards ran under that same allocator lock before this change.

  • Inbox rings (inbox/mod.rs:366, shm.rs:113-119). create_whole installs every slot in syscall context before the ring's Arc reaches the inbox. ring_word, read and write therefore take the fast path and never reach the lock, in interrupt context included.

  • A region's end (shm.rs:319-349). Drop holds &mut self and takes no region lock. The Relaxed drawn() is ordered by the last Arc's drop.

  • Sub (shm.rs:238) delegates to whole.page, so it takes the whole region's lock. Each page() call takes one region lock, and two are never nested.

  • Lock order: space, then region, then allocator.

    • A holder of a region lock runs only alloc_promised: the allocator's lock, then a 4 KiB zero. It takes nothing else.
    • The allocator's lock is pmm.rs's private FRAMES, taken only in try_with and init. Its closures are Frames methods or slice pushes, and none reaches page().
  • Every caller of page(), and why none holds the allocator's lock or runs in a handler:

    • The object arm of Space::populate (space.rs:785).
    • A copied range's fill (space.rs:849).
    • The two above run under the space lock. They are reached from handle_page_fault, process::fault_in and user_ptr::translate_user, whose gate() asserts no lock is held and interrupts are enabled.
    • The clock's boot.
    • The inbox, which is made whole.

    No handler reaches page() on a region made by create. If a future one does, the mask-windows witness in Lock::lock fires.

Blocker 2: resolved as the orchestrator decided.

Suggestions 1 and 2: both taken (PER_SUPER at shm.rs:325-326; pmm.rs:251-253 rewrapped).

Loom model

  • What it asserts. two_touches_draw_once_and_are_handed_one_frame asserts three things: one draw, both touchers handed the same frame, and that frame in the slot.
  • The serialized path is explored. Loom covers the interleaving where both touches miss the fast path and serialize on the mutex. By inspection, dropping the second look gives two draws in that interleaving, and the assertion catches it.
  • The control fails for the right reason. slot-install-relaxed reds the two tests that read a handed-over frame with "Causality violation: Concurrent read and write accesses". The access is touch()'s read of the frame's contents, which is the publication defect. Under the control the mutex still orders the locked path, so draws == 1 holds and neither failure is a draw-count failure.
  • The mutex is a fair substitute. A loom Mutex stands in for sync::Lock. The ticket lock's acquire and release have their own model (ticket_lock, control lock-acquire-off), so the two models compose.

Findings

No blockers.

SUGGESTION: kernel/src/object/shm/slot.rs:1-6, 39-41, 48-49, kernel/src/object/shm.rs:53-55. These comments repeat themselves:

  • "the lock every first touch of its pages takes" is said three times.
  • The slot's module header restates frame()'s doc.
  • "against its own promise" is shm.rs's contract, already in page()'s doc. The slot knows no promise.
  • slot.rs:48 says the lock orders the second look "after any install its last holder made". Correctness rests on every earlier holder's install, not only the last one's. "after every install made under it" is exact.

Cutting the header to its first sentence plus the loom line would remove the repetition.

Evidence

My runs were in a detached worktree at 71b2f15 with a clean tree. Every clippy run used the adopted lints and -D warnings.

Check Result
cargo test --manifest-path kernel/loom/Cargo.toml --test shm_slot exit 0, 3 passed
The same with --features slot-install-relaxed exit 101; two_touches_draw_once_and_are_handed_one_frame and a_frame_is_handed_over_with_what_its_drawer_wrote fail on loom's causality violation; a_touch_after_the_first_draws_nothing passes
cargo test -p kernel --lib --features sched-check exit 0, 461 passed
Kernel library clippy, --lib --tests: sched-check, and sched-check,protocol-port with the kernel controls exit 0 each
x86-64 kernel clippy: default, boot+test actuators, boot actuators, instruments, both heap-band shapes exit 0 each
aarch64 kernel clippy: default, boot+test actuators, boot actuators, instruments exit 0 each
kernel-loom clippy --all-targets, with and without the control exit 0 each
  • Author's evidence. checks-3.log at 71b2f15, with a clean tree, agrees with these results. It adds cargo test -p toyos-build --lib ci:: (21 passed) and cargo run -- --ci host models (exit 0; the control slot-install-relaxed reaches 2 verdicts).
  • Guest rows. Run 43098612390 at 71b2f15 was still queued at 21:01 UTC: created 20:53 UTC, and its one job had not started. 43093481070 was cancelled before it ran, so no guest result exists yet for any head of this PR. The rows cover the paths this round touches: every boot draws the clock page through the new lock, inbox rows use create_whole, shm_release_reclaims covers a region's end, and the image and sub rows cover Sub and spawn.
  • Metal: not a landing condition while the benches are down, per the orchestrator.

Verdict

No code blocker remains. Round 1's condition still holds: run 43098612390, or a replacement at this head, must finish green before landing. It had not started at the time of this review.

LAND

Japabu added a commit that referenced this pull request Nov 11, 2026
… lock

Two touches of a hole both drew before racing to install, so the loser drew
against a promise its page did not hold, and the machine's promised count
fell short of the promises outstanding until it gave the frame back. Each
region now has a first-touch lock (`sync::Lock`): a touch takes an installed
frame with one Acquire load, and a hole is drawn by the touch that, holding
the lock, still finds it a hole, and installed with a Release store. Every
draw is the page's own promise's; `discard` keeps #1250's caller and Safety
text. The lock order is space, region, allocator, and nothing else takes the
region's lock. The loom model asserts one draw a page, and
`slot-install-relaxed` still reds the publication.

The batched draw of an object run's holes is recorded without condition,
owed by the object fault-around (#1259): whichever of #1259 and this lands
second draws a run's holes in one hold. The free batch is a superframe's
worth by name, and the promise doc is rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Review of #1270, round 3, at 4c68367 (base pmm-batch c00a628, on main 8188095)

Architecture

The batch is in the right place, and nothing new had to be added to support it.

  • SharedMemObject::pages() is still the one way to a region's frames. page() is pages() for one page.
  • slot::frames owns the hand-over protocol and knows nothing about pmm. The draw it is handed is pmm's existing batched draw, with zeroing moved outside the allocator's hold, as A draw of many promised frames takes the allocator's lock once #1268's alloc_promised_many did before this PR deleted it.
  • RUN is tied to kernel::vm::AROUND. A fault's run therefore takes one hold of the region's lock and one hold of the allocator's lock, not 16 of each.
  • populate's object arm asks for its whole run [lo, hi). fill writes only empty leaves. Every non-empty leaf of an object range maps a frame that came through pages(), so that frame is already installed. The only holes drawn are therefore at leaves that this same fill maps. Pages that are already mapped cost one Acquire load each and draw nothing.

N4 (#1269): I agree with the orchestrator that this needs no change.

  • pmm::alloc_promised() already has a single-frame caller on main (paged.rs:732), and it returns a typed Frame.
  • A pager's one hole would gain nothing from a one-element slice.
  • Both forms draw through Frames::alloc_promised.

Net size:

  • Whole PR against pmm-batch:
    • Production, comments included: about +145 lines. shm.rs +81, slot.rs +77, pmm.rs −20, space.rs +5, inbox +2.
    • Tests and registration: about +179 lines. The loom model is 154, the feature declarations 15, the CI entry and the loom lib.rs 5 each.
  • Round 3 alone:
    • Production: about +57 lines. shm.rs +29, slot.rs +12, pmm.rs +11, space.rs +5.
    • The loom model grows by 36 lines.
    • The 16 record lines moved from the window record to the first-touch record.

The manual still needs no edit. The memory page already says population is lazy.

Each point asked

One draw a page, against its own promise.

  • Every install happens under the region's lock.
  • The second look holds that lock, so it is ordered after every earlier install through the ticket lock's Release and Acquire. A slot is written once, so coherence makes its Relaxed load return the frame.
  • holes is counted by that look. alloc_promised_each calls back exactly count times (frames.rs:775-779). The install filter selects exactly the slots that look counted.
  • Overlapping runs serialize on the lock, and the later run draws only what is still a hole. A Sub delegates to whole.pages, so every view of a slot set shares one lock.
  • A partly installed run draws its holes only.
  • A run crossing the region's end:
    • pages() asserts n <= (size - offset) / PAGE_SIZE.
    • populate's run is clipped by around to the entry, and map bounds the entry by object.size() (space.rs:478).
    • create_whole uses min(remaining, RUN).
    • So no caller crosses the end, and a crossing would be a kernel bug, which fails loudly.
  • Each hole still holds its own promise from create, so alloc_promised_each's count <= promised holds.

Zeroing outside the allocator's hold.

  • The frames come off the allocator unaliased.
  • alloc_promised_zeroed_into zeroes them before draw returns.
  • They are published only by the Release store, or to a holder of the same lock.
  • No reader can see a frame before both its zeroing and its install.

Lock order: space, then region, then allocator.

  • The region's sync::Lock turns preemption off and leaves interrupts on.
  • Its holder runs only alloc_promised_zeroed_into, which allocates nothing on the heap and takes only the allocator's lock.
  • No allocator closure reaches pages().
  • A copied range's fill takes the space's lock, then the source region's lock, then the allocator's. It does so after alloc_promised_into has let go of the allocator's lock.
  • No region lock nests inside another.

Interrupt context. Every caller at this head:

  • populate, reached from the fault path and from Space::translate, behind gate();
  • a copy source's read, under the space lock;
  • the clock's boot page(0);
  • the inbox's ring_word, read and write, on a ring made by create_whole. These take the fast path and never reach the lock.

Lock::lock's mask-windows witness would catch a future handler that reaches a hole.

Composition with #1245. This is the fill argument under Architecture. When only some of the run's pages are holes, those holes are drawn in one call, and the other pages are read from their slots.

Loom model.

  • two_runs_draw_each_page_once asserts what it claims.
  • Each run has a page of its own, so each run draws at least once. draws <= 2 therefore means exactly one draw per run, which catches drawing each hole on its own.
  • Dropping the second look makes a fourth draw, which trips k < PAGES.
  • A second look that does not refresh out panics, or fails the slots assertion.
  • Neither run can take the shared frame off the fast path, so the model is not a publication test. It correctly passes under the control, which agrees with the two Fails that build/ci.rs lists.

Findings

Resolved during review: the guest run said to test this head tested 0c76f7a.

  • What happened:
    • When I started, the body said "run 43113647566 at 4c68367". That run's headSha was 0c76f7a.
    • guest-rows.yml checks out the dispatched ref's commit at dispatch time.
    • 0c76f7a has none of round 3's code: pages(), slot::frames, alloc_promised_zeroed_into, the object arm's run draw, and create_whole's run-at-a-time draw.
  • Now:
    • The body names run 43116588754 at 4c68367 and says that 43113647566 does not cover the batch. 43113647566 has since passed at 0c76f7a.
    • 43116588754 carries the same four rows. It was queued when I wrote this.
    • Its green is the landing condition for the guest rows.

BLOCKER: the batched-draw record still says a region is drawn by the function this PR deletes.

  • Where: issues/per-cpu-scheduler-and-pmm-state-shares-lines-every-cpu-writes.md:70-72. It came in through A draw of many promised frames takes the allocator's lock once #1268's record commit c00a628.
  • What it says now: "A shared-memory region's frames (pmm::alloc_promised_many) and a fault's whole run (pmm::alloc_promised_into) are drawn a superframe's worth to a hold".
  • Why it is wrong after this PR:
    • This PR deletes alloc_promised_many.
    • A region's frames are now drawn at first touch, at most a fault's run to a hold.
    • Root CLAUDE.md requires records that a change affects directly to stay accurate.
    • r1 and r2 could not see this, because the reference arrived with the rebase.
  • Fix: one clause. For example, say that a region's frames were drawn this way until the first-touch record's change, and drop the deleted name.

SUGGESTION 1: kernel/src/object/shm/slot.rs:64, the .expect.

  • .expect("slot: fewer frames drawn than holes") cannot fire. drawn is sliced to holes, and the filter yields exactly the slots that the same look counted.
  • A draw that wrote too few frames would leave stale zeros in the slice and would not trip it.
  • Zipping the filtered slots with drawn would state the real invariant without a check that cannot fail.

SUGGESTION 2: the header of kernel/loom/tests/shm_slot.rs.

  • Lines 10-11 say each page draws "against its own promise". The model counts no promise, and r2 made the same point about slot.rs.
  • Line 25 names one test that must red, while build/ci.rs registers two.
  • "Each page draws one frame", plus naming both tests, would match what the model asserts.

Evidence

My runs were in a detached worktree at 4c68367, clean tree, with its own target directory. Every clippy run used the adopted lints and -D warnings.

Check Result
cargo test --manifest-path kernel/loom/Cargo.toml --test shm_slot exit 0, 4 passed
The same with --features slot-install-relaxed exit 101. two_touches_draw_once_and_are_handed_one_frame and a_frame_is_handed_over_with_what_its_drawer_wrote fail on loom's causality violation. two_runs_draw_each_page_once and a_touch_after_the_first_draws_nothing pass.
cargo test -p kernel --lib --features sched-check exit 0, 465 passed
Kernel library clippy, --lib --tests: sched-check, and sched-check,protocol-port with the kernel controls exit 0 each
x86-64 kernel clippy: default, boot+test actuators, boot actuators, instruments, both heap-band shapes exit 0 each
aarch64 kernel clippy: default, boot+test actuators, boot actuators, instruments exit 0 each
kernel-loom clippy --all-targets, with and without the control exit 0 each
cargo run -- --ci host models exit 0. 84 steps, all green. Control slot-install-relaxed: 2 verdicts reached.
  • Author's evidence: checks-4.log at 4c68367, with a clean tree, agrees with my runs. It ran no --ci host models at this head, and the body says so. My run above supplies it.
  • Guest rows: no result exists yet for 4c68367.
    • Run 43116588754 is queued at this head.
    • 43113647566 passed at 0c76f7a. That covers rounds 1 and 2's code, not the batch.
  • Metal: not a landing condition while the benches are down. The owed A/B is recorded in the first-touch record, with its rows and what main must show.

SEND BACK

Japabu added a commit that referenced this pull request Nov 11, 2026
…e install zips holes with draws

The frame allocator's record named `pmm::alloc_promised_many`, which this
PR deletes, as how a region is drawn; it now says a region's untouched
pages are drawn a run at a time, at first touch. `slot::frames` installs by
zipping the holes with what was drawn, with no check that cannot fail, and
the loom header says what the model asserts and names both tests its
control reds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Japabu

Japabu commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Orchestrator verification of round 3's fixes at 917b8ef: the record clause no longer names the deleted alloc_promised_many; S1 is the zip of filtered holes with drawn frames in place of the expect (+4/−4 in slot.rs, read with git diff); S2 the loom header. Lands after #1250 and #1268, on guest run 43117350636 (at 917b8ef).

Japabu added a commit that referenced this pull request Nov 11, 2026
… lock

Two touches of a hole both drew before racing to install, so the loser drew
against a promise its page did not hold, and the machine's promised count
fell short of the promises outstanding until it gave the frame back. Each
region now has a first-touch lock (`sync::Lock`): a touch takes an installed
frame with one Acquire load, and a hole is drawn by the touch that, holding
the lock, still finds it a hole, and installed with a Release store. Every
draw is the page's own promise's; `discard` keeps #1250's caller and Safety
text. The lock order is space, region, allocator, and nothing else takes the
region's lock. The loom model asserts one draw a page, and
`slot-install-relaxed` still reds the publication.

The batched draw of an object run's holes is recorded without condition,
owed by the object fault-around (#1259): whichever of #1259 and this lands
second draws a run's holes in one hold. The free batch is a superframe's
worth by name, and the promise doc is rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 11, 2026
…e install zips holes with draws

The frame allocator's record named `pmm::alloc_promised_many`, which this
PR deletes, as how a region is drawn; it now says a region's untouched
pages are drawn a run at a time, at first touch. `slot::frames` installs by
zipping the holes with what was drawn, with no check that cannot fail, and
the loom header says what the model asserts and names both tests its
control reds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 12, 2026
… lock

Two touches of a hole both drew before racing to install, so the loser drew
against a promise its page did not hold, and the machine's promised count
fell short of the promises outstanding until it gave the frame back. Each
region now has a first-touch lock (`sync::Lock`): a touch takes an installed
frame with one Acquire load, and a hole is drawn by the touch that, holding
the lock, still finds it a hole, and installed with a Release store. Every
draw is the page's own promise's; `discard` keeps #1250's caller and Safety
text. The lock order is space, region, allocator, and nothing else takes the
region's lock. The loom model asserts one draw a page, and
`slot-install-relaxed` still reds the publication.

The batched draw of an object run's holes is recorded without condition,
owed by the object fault-around (#1259): whichever of #1259 and this lands
second draws a run's holes in one hold. The free batch is a superframe's
worth by name, and the promise doc is rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 12, 2026
…e install zips holes with draws

The frame allocator's record named `pmm::alloc_promised_many`, which this
PR deletes, as how a region is drawn; it now says a region's untouched
pages are drawn a run at a time, at first touch. `slot::frames` installs by
zipping the holes with what was drawn, with no check that cannot fail, and
the loom header says what the model asserts and names both tests its
control reds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 12, 2026
… lock

Two touches of a hole both drew before racing to install, so the loser drew
against a promise its page did not hold, and the machine's promised count
fell short of the promises outstanding until it gave the frame back. Each
region now has a first-touch lock (`sync::Lock`): a touch takes an installed
frame with one Acquire load, and a hole is drawn by the touch that, holding
the lock, still finds it a hole, and installed with a Release store. Every
draw is the page's own promise's; `discard` keeps #1250's caller and Safety
text. The lock order is space, region, allocator, and nothing else takes the
region's lock. The loom model asserts one draw a page, and
`slot-install-relaxed` still reds the publication.

The batched draw of an object run's holes is recorded without condition,
owed by the object fault-around (#1259): whichever of #1259 and this lands
second draws a run's holes in one hold. The free batch is a superframe's
worth by name, and the promise doc is rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu added a commit that referenced this pull request Nov 12, 2026
…e install zips holes with draws

The frame allocator's record named `pmm::alloc_promised_many`, which this
PR deletes, as how a region is drawn; it now says a region's untouched
pages are drawn a run at a time, at first touch. `slot::frames` installs by
zipping the holes with what was drawn, with no check that cannot fail, and
the loom header says what the model asserts and names both tests its
control reds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Japabu and others added 5 commits November 12, 2026 09:00
`SharedMemObject::create` drew and zeroed every frame of a region before it
returned, so a region cost its whole size however little of it a process
touched: a served directory's 2 MiB window, four of them a build child, was
95 us a window on the T14 and 133 us on the AMD bench with one child at a
time, and most of what the all-CPU build's children add in CPU, by the spawn
diagnosis. The orchestrator decided (recorded in #1265): promised whole at
creation, drawn at first touch, with a device's run and an inbox's rings
drawn whole.

A region's pages are slots (`shm::slot::Slot`), each a hole until its first
touch. `SharedMemObject::page()`, which every consumer of a page's frame goes
through (an object range's fault, the kernel's reads and writes, an inbox's
words), draws a hole's zeroed frame with `pmm::alloc_promised()` against the
promise `create` made, and installs it with one compare-exchange; a toucher
that loses the race gives its frame and its promise back (`pmm::discard`)
and is handed the winner's. That draw is the one map7b's N4 calls for a
MEMORY volume's hole, into its pager's index. `page()` states who may call
it: never in interrupt context, never holding the allocator's lock. An
inbox's rings are made with `create_whole`, which touches every page at
once, since the kernel writes them in interrupt context; the clock page's
one page is drawn by the boot's own `page(0)`. A region's end frees what was
drawn, 512 at a time, and gives back the promises of the pages never
touched.

`pmm::alloc_promised_many` and `pmm::free_all` lose their only callers and
go. The slot is compiled a second time by `kernel-loom`: `shm_slot` models
two touches racing for one page (one frame each is handed, every other draw
goes back once, a frame comes with its zeros), and `slot-install-relaxed`
is its negative control.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The benches are down, so its A/B is owed rather than run: the window record
names the rows, benches and what main must show.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… lock

Two touches of a hole both drew before racing to install, so the loser drew
against a promise its page did not hold, and the machine's promised count
fell short of the promises outstanding until it gave the frame back. Each
region now has a first-touch lock (`sync::Lock`): a touch takes an installed
frame with one Acquire load, and a hole is drawn by the touch that, holding
the lock, still finds it a hole, and installed with a Release store. Every
draw is the page's own promise's; `discard` keeps #1250's caller and Safety
text. The lock order is space, region, allocator, and nothing else takes the
region's lock. The loom model asserts one draw a page, and
`slot-install-relaxed` still reds the publication.

The batched draw of an object run's holes is recorded without condition,
owed by the object fault-around (#1259): whichever of #1259 and this lands
second draws a run's holes in one hold. The free batch is a superframe's
worth by name, and the promise doc is rewrapped.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cator's lock

Main's populate already fills an object range's whole run (#1245), so a
region written whole after it is made, such as a 32 MiB spawn image, drew
a frame a page, one hold of the frame allocator's lock each: 8,192 where
drawing the region whole took 16. The orchestrator decided to build the
batch here rather than wait for #1259.

`SharedMemObject::pages()` answers a run of pages at once, at most a
fault's run, and `page()` goes through it. For a region, `slot::frames`
takes every frame installed with one Acquire load each; if any page it was
asked for is a hole it takes the region's first-touch lock, looks again,
and has the holes still left drawn by one call,
`pmm::alloc_promised_zeroed_into`: one hold of the allocator's lock for the
draw, zeroing outside it, and one Release store a page to install. The
object arm of `populate` asks for its run this way, and `create_whole`
draws an inbox's rings a run at a time.

The loom model gains two runs sharing a page: each page draws once, the
shared page's one frame is handed to both, and each run draws in one call
at most; `slot-install-relaxed` still reds the hand-over models. The
first-touch record says it is built and carries what it owes on metal; the
window record's two bullets go, the batch being built and the reading
moved to the record of the change it measures. The slot's comments are
trimmed as review r2 suggested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e install zips holes with draws

The frame allocator's record named `pmm::alloc_promised_many`, which this
PR deletes, as how a region is drawn; it now says a region's untouched
pages are drawn a run at a time, at first touch. `slot::frames` installs by
zipping the holes with what was drawn, with no check that cannot fail, and
the loom header says what the model asserts and names both tests its
control reds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant