computedriven computedriven/docs/sflr-r2.md
Companion to receipts/SFLR-R2-OPEN.md (the contract, falsifiers first) and the generated receipts/SFLR-R2-DYNAMIC-<stamp>.md (the numbers). This file is the method and the limit…

Floor Census R2 — method, and what the numbers do not claim

Companion to receipts/SFLR-R2-OPEN.md (the contract, falsifiers first) and the generated receipts/SFLR-R2-DYNAMIC-<stamp>.md (the numbers). This file is the method and the limits. Nothing here carries a figure; figures live only in the generated receipt, because prose that carries a number is how the published and printed law counts drifted apart in this tree once already.

The shape of the experiment

Every system under test is a program with one contract:

PROG <contexts> <iterations>   run contexts×iterations×2 governed transitions
                               IN PROCESS, print transitions=<T>, exit 0
PROG <mode> --witness          answer the external battery, one line each

Cost per transition is (counts(2K) − counts(K)) / (T(2K) − T(K)). Everything fixed cancels: process startup, runtime initialisation, JIT warm-up, the first GC, BEAM's ~50 ms boot. That is the only reason a 200-cycle figure can be taken from a managed runtime at all.

The round-robin loop is deliberately identical to cd-kernellet's BENCH op: for each context in turn, one attach then one detach. A transition is one attach or one detach.

Why in-process, and not through the ABI

A real caller drives cd-kernellet through stdin/stdout: two syscalls per command. A goroutine handoff or a BEAM message send is user-space. Comparing CD-through-its-ABI against a channel send would compare a syscall-bound path to a user-space one, and CD would lose for a reason that has nothing to do with its architecture. So the comparison uses the in-image BENCH loop, and the ABI syscall cost is a separate, deployment-level question this round does not answer.

The four workload classes

  • class N — naked. Each system does the minimum its own idiom does: no

generation check, no possession check, no evidence ring. Prices the concurrency abstraction alone.

  • class S — semantic. Every system implements the full check set and must

pass the same external witness battery before it is given a number.

  • class R — rollback. Class S in C three ways: in-place (no guarantee),

copy-in/copy-out (cd-kernellet's shape), in-place plus an undo log. Isolates what the guarantee costs from what one particular way of getting it costs.

  • class X — suspend. A unit must park mid-transition awaiting an external

event. CD is BLOCKED by construction and the row says so.

S − N within one system is what the guarantees cost there. CD − plain C within class S is what the floor costs over a hand-written state machine with the same guarantees. Those two deltas are the result; everything else is context.

What the instrument cannot see, and what it distorts

  • No kernel work is counted. perf_event_paranoid is 2, so

exclude_kernel=1 is forced. That hides the syscall and the context switch — exactly what makes a kernel-scheduled unit expensive. Wall-clock ns/transition and context-switches/transition are printed beside every row for that reason, and no row may be read from the instruction column alone.

  • A `rep`-string instruction retires once however many bytes it moves. The

instruction column therefore flatters cd-kernellet's fastmem build against a vectorised memcpy doing identical work. L1D accesses and cycles are printed beside it; the CD:C ratio is quoted from those, not from instructions.

  • Instructions retired is not deterministic on x86. A hardware interrupt's

iret counts as a userspace instruction, so the count drifts with interrupt rate and therefore with load. It is far steadier than cycles here, but steadier is not exact. Branches retired is carried as the deterministic cross-check.

  • The PMU has finite counters. Ask for more hardware events than it has and

the kernel time-slices them and returns partial counts. Every event is opened with PERF_FORMAT_TOTAL_TIME_ENABLED|RUNNING, the hardware events are split into groups that fit, and any counter the kernel shared is discarded, never scaled.

  • This host is heterogeneous. Zen 5 cores and Zen 5c cores differ in clock

and in nothing the workload cares about except that they are different cores. Every run names the CPU it was pinned to and its class.

  • This host is shared. Other sessions run on it. Cycles carry their

observed spread and the concurrent load average; instructions are primary.

Memory: three denominators, never one

312 B is the slope of a static .data section, exact, read from the ELF. A BEAM process figure is a heap allocation. A goroutine's 2 KiB is a stack reservation. One column for all three would be a category error, and whichever column was chosen would decide the winner. So the receipt prints:

  1. marginal bytes per additional context — measured as the peak-RSS

difference between a small and a large N, so every constant cancels;

  1. total including the runtime — the deployment number;

  2. bytes touched per transition — the L1D-access column, which is the one

that predicts the density ceiling.

Peak RSS is read by each SUT from its own /proc/self/status VmHWM. The harness's ru_maxrss is not usable: it counts the pages the child inherited from its Python parent before exec, which is about 11 MB and swamps anything small — it made the C slope come out negative before this was found.

What the round does not claim

The unclaim list is in the contract, §2, and it is the load-bearing half. In short: not that a Locus Context can do what a thread does; not that its 312 B "beats" a BEAM process's, because it holds no application state at all; not memory isolation; not multicore; and not that a density ceiling measured against this cache generalises off this host.

The ceiling in particular is a worst case: the workload touches every context every lap. A skewed access pattern would not cliff at the same N, and that measurement is not in this receipt.

Recorded architectural finding: the copy is not the guarantee

Ruled 2026-09-04, after a control in R2 disproved the natural reading.

The invariant cd-kernellet actually holds is:

a transition RECEIVES owned semantic values
  on success   it returns successor values
  on refusal   it returns the original admissible values
the caller ALWAYS installs what was returned

dispatch writes the returned Lineage back to the table unconditionally — refusal or not. So the property does not come from the copy. It comes from cd-core's failure paths returning the same values they were given (Err((v, adm, e)) feeding the back! macro), which linear types make unavoidable: a transition body never holds mutable access to the caller's value, so it has no half-written version to return.

The whole-context move is one implementation consequence of expressing that discipline in Rust's move semantics. It is not the invariant.

R2 proved the distinction the only way it can be proved — by building the wrong thing. A C build that copies out, mutates, and copies back unconditionally pays the full 312-byte cost twice per transition and still fails the refusal witness (E-L6b). Cost without guarantee. And the converse also holds: where a transition body takes &mut and rolls back explicitly, LLVM narrows the saved state to the fields the body actually touches, so the same witness is bought for about six extra L1D accesses instead of forty.

Two consequences follow, and they belong to later rounds:

  • The evidence ring is 41% of a Locus Context (measured, R2.1 §EV_RING) and

is moved on every attach and every detach, neither of which reads or writes it. That is the largest and most obviously unnecessary part of the traffic.

  • Narrowing the move to the components a transition actually consumes would

keep the structural property — the body still never sees a half-written caller state — while removing most of the 2.4× overhead. That is R4.

Open in the interactive atlas