notes/p7/magma-a.md: the per-site table (retired / declined / kept-fatal with the
reason), every red-once with its captured output, the gate readings, and what is
handed on.
The three places package A's own prose still named `uniform-buffer-byte-tail@P7`
lose the marker. They are comments, not refusal sites, and leaving the marker in
would have made a tree-wide `grep @P7` count them against packages B and C. The
census now reads 15 -> 7, and all seven are real: WireFramebuffer.inc x4 and
VulkanRenderer.cpp x1 (package B), WireDraw.inc x2 (package C).
Three findings worth the integrator's attention, all in the note:
1. Most of this cluster was unreachable, and that is provable rather than
assumed. The frontend validates buffer-range offsets against the very caps the
backend then re-checks - GL_{UNIFORM,SHADER_STORAGE,TEXTURE}_BUFFER_OFFSET_ALIGNMENT
and GL_MAX_UNIFORM_BLOCK_SIZE are all published from the same Vulkan limits -
so five of the eight sites could only ever have fired on a malformed peer. The
two that CAN fire are `uniform-buffer-byte-tail` (GL constrains a range's
offset but not its size) and `unaligned-atomic-buffer-range` (GL gives
GL_ATOMIC_COUNTER_BUFFER no queryable alignment, so the frontend only enforces
offset % 4 while the backend needs minStorageBufferOffsetAlignment).
2. fatal_census does not move for any of this, and no re-baseline is due. Wave 0
already routed these funnels through MGPipeSessionFailHook, so the abort lives
in FatalFunnel.cpp and retiring a WireDescriptorFatal call site changes no
census number: 79 abort sites / 43 family words, unchanged, RC=0. The brief
expected the count to fall because the sites were bare aborts before wave 0.
3. DirectVulkan.Split.Full.IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput
is red on this worktree's build-split and is NOT ours: it reproduces 3/3 with
package A's backend diff fully reverted, and the mismatch bits are identical
to the S1 exposure texel in magma-two-process-first-run.md. The contract books
that one against the tcp arm, and ~/w7/pipe's own build-split passes it, so
whether it is a split-arm regression between 460a3a72 and 610fa281 or a
configure-dependent S1 needs the integrator.
Gates on the finished branch: integration-magma-split 75/75, -spawn 54/54, -tcp
56/56, -buffers 21/21, full-split 512 entries 454 PASS / 57 SKIP / 1 red (the
pre-existing one above; base was 453/57), ctest -L unit 2408/2408,
spawn_lane_parity RC=0, link_ratchet unchanged at 186, fatal_census RC=0, and G1
pull .text = a52203 with nm --defined-only identical to p7-before/pull-syms.txt
(added=0 removed=0).
The per-slice red-once records were written with each slice. This adds what only exists
once the four are together: the gate table (unit 2403, magma split/spawn/tcp 75/54/56 with
the two death-case skips now gone, integration-split 187, integration-spawn 103,
full-split 513 entries 0 red 55 skipped = 458 pass against the 453/57 bar, census
unchanged, parity green, @P7 15/4 -> 13/3, ratchet 186 monotone, G1 .text 0xa52203 with
every section +0 and the pull .so byte-identical), the ratchet bucket split with an honest
note that I did not re-measure the buckets at the branch point, and a numbered list of the
nine things this package changes that live outside its own files.
Three of those nine need an integrator decision rather than a note: §5.4's white-box case
is blocked by R-6 and not by this package; §5.3's g_programResourceCaches deletion is
achieved in the disaggregated build only, because the archive member has to be guarded for
G1; and buffer-window's family word is UnmigratedVerb where ProtocolCorruption is the
honest one, which costs a row in MG_Pipe and an adapter arm that §3.3 deliberately has not
spent.
CONTRACT-P7 §5.3, OQ-8. MagmaProgramSource::EnsureStorageBlocks re-ran SPIRV-Reflect over
every stage module of the program PER DRAW: the class is a borrowed view that
WireDraw.inc's WireProgramSource constructs afresh for each wire draw, so its `mutable`
memo was cold every time it was read. The list it rebuilt is a pure function of the linked
program, so it is now built once at link time and travels in the archive.
LinkArtifacts gains storageBlocks{name, binding, dataSize} with its own VisitFields table,
the codec version goes 2 -> 3, and the schema fingerprint and wireFingerprint move with
it - which is expected, and is what makes a mixed-version pair refuse at the handshake
instead of at the first program.
FILLED IN PHASE A, and that is not where it looks like it belongs. The only function that
sees both finished halves is ProgramSpirvTask's L1 cache insert, but its writes reach the
TRANSLATION CACHE only: phase A's artifacts have already been moved into the ProgramObject
by then. A field filled there would be present on a cache hit and absent on every cold
link. So the fill is glslang-derived, and the equivalence is MEASURED rather than assumed:
ProgramTest.TheArchivesStorageBlockOrderIsTheOneSpirvReflectProduces runs the old
consumer's own algorithm over the real modules and compares element for element, on a
multi-stage program with an arrayed SSBO and an atomic-counter block - the three cases
that could have diverged (the instance array collapses to one entry, the synthesized
counter block IS in the backend's space although GL's enumeration excludes it, and the
cross-stage dedupe walks stages in linked order).
MEASURED, same binary and same window (frames=1 draws=1 acc=18, identical bytes),
DirectVulkan x inproc over the SSBO scenarios: spvReflectCreateShaderModule on the wire
draw path 4 -> 0. The 0 is structural: the call site is gone and MagmaProgramSource.h no
longer includes spirv_reflect.h at all, which is the gate a future edit breaks on purpose.
THE MONOLITH CONSUMER IS SWITCHED TOO, under #if symmetry, so the disaggregated build
retires g_programResourceCaches, ProgramResourceCache, StorageBlockResource,
BufferVariableResource, AddBufferVariablesRecursive, NormalizeDescriptorName,
GetProgramResourceCache and the rehash hazard - the open-addressed-map dangling reference
that was a reproducible segfault in ProgramPipelineScenario's two storage-block cases.
Nothing outside DirectVulkan.cpp read any of it (measured; GL_BUFFER_VARIABLE and
GL_ACTIVE_VARIABLES are answered by ProgramInterface.cpp out of a different index space),
so the two arms differ in exactly the two functions their callers use.
THE PULL BUILD KEEPS ALL OF IT, and the integrator should see why: the archive member is
MOBILEGL_BUILD_DISAGGREGATED-only because one more Vector in LinkArtifacts changes its
size, its implicit destructor and its move constructor, and G1 pins the pull build's .text
byte for byte. So §5.3's deletion is achieved in the disaggregated build and not in the
pull one; retiring the pull half is a one-line change the day that pin is retired or the
member moves. Filed as P13 in notes/p7/magma-c.md.
One improvement rides along: the monolith binding getter now asks the program for a
rebind on every read, over an immutable list, where it used to read a binding patched into
a mutable cache - so a rebind can no longer be lost to a cache rebuild that raced a
state-version bump.
G1: .text 0xa52203 unchanged, 27837 -> 27837 defined symbols, 0 added / 0 removed / 0
resized / 0 renamed, file byte-identical. An unguarded call site caught the guard rule
once mid-slice by failing the pull compile, which is the loud version.
RED-ONCE: publishing nothing reds the order-equivalence case (with the reference list
printed) and 25 of 60 storage-block scenarios - the MONOLITH DirectVulkan arm beside all
three transports, which is the evidence that the monolith consumer really was switched.
Restored: 61/61.
CONTRACT-P7 §3.2's `vertex-layout` and `vertex-format-conversion` rows.
BuildWireVertexInput answered eight different conditions with one `false`, and the single
caller answered that with a P7-marked MagmaWireFatal. So a Magma split session that met a
GL_FIXED array, a dvec on a device with native fp64, or any format without
VK_FORMAT_FEATURE_VERTEX_BUFFER_BIT did not decline the draw: it killed the process. The
monolith arm on the same device has always masked the attribute out and then declined the
draw loudly and once.
The split is by what the reason is ABOUT, not by severity. `buffer-window` is a statement
about the RECORD - an enabled attribute the program reads whose slot lies outside the
window set_vertex_buffers published - so it stays a named Fatal through the hook; it is
what caught the Redmi VAO hash collision fixed in 376c04be. Every other reason is a
statement about what Vulkan can express on this device, and those now set
unsupportedAttribMask and continue, with the monolith arm's own pre-flight in WireDraw.inc
declining the draw when a masked bit is one the program actually reads.
The contract says "the other six"; there are seven. `offset-overflow` post-dates the
audit's count and is an arithmetic guard rather than a shape one. It is masked with the
rest: an attribute whose base offsets cannot be added is one Vulkan cannot fetch, the
monolith arm never computes that sum at all, and masking is strictly safer than a Fatal
for a number no application can reach on purpose.
unsupportedAttribMask now enters the wire layout hash, for the reason the monolith entry's
hash already carries it: two VAOs can otherwise agree on every binding, attribute and
divisor and differ only in which enabled attribute was masked, and a blind hash would hand
one of them the other's pipeline. Omitting it was safe only while the mask was always zero.
vertex-format-conversion becomes a decline for a narrower reason: the converter is only
ever asked for a conversion BuildWireVertexInput already chose, so a refusal means the two
disagree - a defect on this side, not a peer's malformed record.
@P7 census: 15 sites / 4 files -> 13 / 3. Renderer/WireDraw.inc leaves the list entirely.
fatal_census.py unchanged at 79 abort sites / 43 family words / 0 unmarked, as expected:
wave 0 already funnelled these, so retiring a MagmaWireFatal CALL moves no abort count.
New MagmaVertexLayoutScenario, DirectVulkan only (Espryt hands GL_FIXED to a driver that
supports it, so the same name would assert a different fact there). DataType::Fixed32 has
no arm in ToVkVertexFormat at all, which makes it the one unmappable shape reachable on
host lavapipe - §2.5 records that no other P7-marked refusal is. One case has the program
READ the array and asserts the draw declines; the other leaves it enabled but unread and
asserts the draw lands, which is what stops the first being satisfied by "decline every
draw that mentions GL_FIXED". Registered on all three transports and picked up by the
ambient monolith DirectVulkan discovery, so the A/B is direct.
RED-ONCE: `format-map` put back on the protocol verdict reds the three split arms with
`Subprocess aborted` / `Fatal{UnmigratedVerb, "Magma:vertex-layout-buffer-window"}` while
the monolith control stays green - exactly the divergence this closes. Restored: 10/10.
The gating Magma lanes grow with the new entries: split 73 -> 75, spawn 52 -> 54, tcp
54 -> 56. Growth only.
Open for the integrator: the honest family word for buffer-window is ProtocolCorruption,
and MGPipeFatalFamily carries only UnmigratedVerb and RoleViolation. Adding a third costs
a row there plus an adapter arm, and §3.3 keeps the three Magma funnels on UnmigratedVerb
deliberately so the refusal census stays comparable to P5b. Flagged, not decided.
CONTRACT-P7 §5.4, OQ-10. InitServerRoleCommon computes the whole capability mask and
never ORed this bit, so MGPipeResourceOpsHaveSubDataResident answered false under every
transport and BOTH backends were pinned to the ordered in-place host memcpy - which made
Magma's SubDataResident arm (VkBufferManager.cpp's g_vulkanWireResourceOps) dead code on
the wire even though the arm has been there since P5f.
The bit is derived from g_resourceOpsAtStep2->SubDataResident, i.e. from the pointer this
function already pinned at step 2 and that step 5 already refuses to see swapped - never
from ActiveBackendType. That is ID-39's lesson and not a style preference: a mask is a
statement about THIS backend's registered table, and a bit derived from the enum would
keep claiming the capability for a build, a flavour or a future backend whose table does
not carry the arm.
`rsd=` joins the emit[] bracket on the PipeStats summary line, counted at the emitter once
per buffer_subdata_resident record rather than once per call, because a write past the
content chunk cap is cut into several records. It is the only observable that separates
the resident arm from the memcpy beside it: the bytes in the buffer are identical either
way, which is exactly how the capability stayed unpublished for a phase with every lane
green.
WHAT THIS DOES NOT YET PROVE, and R-6 is the reason rather than an omission. §5.4 asks for
a white-box case showing opcode 49 on the wire. None can exist at this tree: the resident
arm is reached only from a GPU-RESIDENT store, residency comes only from
AdoptPersistentMap, and every persistent-map acquisition declines under split by R-6
(PipeApply.cpp:2331 "A SPLIT BUILD RUNS AT TIER T2 AND DECLINES EVERY ACQUISITION,
ALWAYS"; T0/T1 are P11's and AdoptTierIsEmulate is a named Fatal for them). So the
capability is wired AHEAD of its consumer, deliberately, and `rsd=` is what will show the
records the day P11 lands a tier that adopts. Filed for the integrator in
notes/p7/magma-c.md.
What IS proved is both halves of the bit itself. RemoteClientTest's Magma bootstrap case
now asserts the published bit EQUALS `ops->SubDataResident != nullptr` - the two compared
to each other rather than both to true, so the case also covers a backend without the arm
on the day one exists - and a new CtWireScenario case reads the bit out of the client's
own caps mirror, through the same accessor the frontend reads, on both backends and all
three transports.
RED-ONCE, both levels. Deleting the OR reds
CapsMirrorTest.MagmaTransportPublishesRealBufferConsumersWithoutRunAhead
(RemoteClientTest.cpp:692, "Which is: false / Which is: true") and all SIX arms of
DirectGLES/DirectVulkan x {Split,Spawn,Tcp} Ct.TheServerPublishesTheResidentSubData
CapabilityFromItsOwnTable ("the client's caps mirror did not receive
kCapResidentSubData"). Restored: green.
Also carries the P4a death-helper comment from the previous commit's sweep: it no longer
says Magma installs no death table, because it now does and still frees no slot.
CONTRACT-P7 §5.5. DirectVulkan had no StateObjectDeathOps at all, so under a Magma
inproc session NotifyStateObjectDestroyed reached nobody and the server was never told
that a frontend object had died. Package L measured the shape: with the WireTables
install condition made backend-agnostic, CtWireScenario's two death cases PASS on Magma
spawn and tcp - the remote client has no local backend, so the client-side emitter
installed there answers the notice - and FAIL on inproc, where the install condition
deliberately stands aside for "the backend's own dispatcher" and Magma had none.
g_magmaStateObjectDeathOps is that dispatcher. It mirrors g_glesStateObjectDeathOps
(DirectGLES/Managers.cpp:335) with ONE arm rather than two: Espryt's other arm destroys
a backend twin keyed by lifetime id, and Magma keeps no such twin - its two
client-minted kinds are keyed on {slot, gen} by MagmaPipeIdentityTables and the other
six are still reached from their frontend objects - so what is left is exactly the emit
of object_death off the apply thread. Installed from
BackendObject_DirectVulkan::Initialize(), i.e. ServerLoop::CreateBackend, step 1 of
InitServerRoleCommon: the one place BOTH Magma server roles pass through, and before any
client session exists. #if MOBILEGL_BUILD_DISAGGREGATED && MOBILEGL_PIPE_PUSH, so the
pull build compiles nothing new.
The two skip guards in CtWireScenario go with it. Every other "Magma installs none" site
the audit found is comment text whose ARGUMENT survives, and each is reworded rather than
deleted: the table emits the wire record and frees no client slot, so P4a's
backend-neutral NotifyAndFree is still the only thing that returns one, HandleRecycle's
C-1 case still measures the only protection the allocator has, and MagmaPipeArms' age
sweep is still the only thing that retires an identity slot.
The framebuffer death case now reads the client's allocator beside the server's tally.
That correction matters: the plan called this a slot leak provable with PipeSlotPeek, and
it is not - it has been backend-neutral since P4a. What leaked was the SERVER's twin, and
a framebuffer has no wire delete opcode at all, so object_death is its only delivery.
Two installers cannot race although the global is last-writer-wins with no stacking:
Magma's runs only in a process that owns a DirectVulkan backend, and
InstallClientWireTables' arm runs only under Transport == Spawn, which is by construction
the process that owns none.
RED-ONCE: `if (false) InstallStateObjectDeathOps();` reds the two Split cases alone
(`Expected: (afterCounters.deaths) > (deathsBefore), actual: 0 vs 0`, CtWireScenario.cpp:264)
and leaves spawn and tcp green - the exact split of responsibility the comment claims.
Restored: 8/8. Recorded in docs/Disaggregated/notes/p7/magma-c.md.
X1: 26.3 and create-instancing pass under inproc on both backends, zero declined
faults, tracker line confirms tag=0xb4. F1: the R-8 line is gone in every run, yet
OpenRA reads 0.976494 on a cold driver cache (three pm-clear runs identical), 1.0
warm, 1.0 cold monolith, and FRAMESINFLIGHT=1 flips it half the time - a GPU-timing
race on the wire readback/present path, handed to package B as slice 0.
`notes/p7/f1-caps-placeholder.md`, per CONTRACT-P7 §9's "one red-once per package
recorded in notes/p7/". It carries the call chain from `MobileGL::Initialize` down
to `CapsMirror::ServerConsumes(0x400)`, the three corrections to the brief's reading
(it is not a race; P6.5's fix already covers inproc; a bounded wait alone deadlocks),
the three red-once arms, and the two things still owed to the integrator.
The arm worth reading twice is A. On lavapipe the BASE build carries the R-8 line on
every transport, with and without the delay knob, and OpenRA still scores
ssim 1.000000 / mismatch 0. So the eleven dropped resource_creates cost nothing
visible there, F1's red-once is the LOG LINE and the new Fatal rather than an SSIM,
and this package must not be read as having explained the device's 0.976494 /
0.988998. That still needs the phone.
shader-image 63/56/6, ssbo 124/0/0, dsa 368/0/2, texture 815/221/18, packed-pixels
4732/0/0 (Pass/NotSupported/Fail); every block complete (no unrun, hung or crashed);
library p7w1-d260f110, glcts from VK-GL-CTS 0b04c470e with the FBO colour-texture
patch, API probe confirms GL 4.6. The AFTER reading compares inproc against this at
0.5 pp per block with new crashes as a separate hard red.
Wave 2 package X1. Three CI split-subset traces died on the Redmi under inproc and
passed under monolith on the same device and APK, on the client thread, in the
replayer's memcpy into a pointer MobileGL had handed it: SIGSEGV / SEGV_ACCERR on the
destination's FIRST byte, destination page-aligned.
The tombstone names the same address twice, in two different address spaces:
x0 b4000073ac9a5000 ... x2 0000000001800000
signal 11 (SIGSEGV), code 2 (SEGV_ACCERR), fault addr 0x00000073ac9a5000
x0 is the pointer the application writes through, carrying bionic's 0xb4 heap tag
(the same tombstone reports tagged_addr_ctrl PR_TAGGED_ADDR_ENABLE). fault addr is
si_addr, which the kernel reports UNTAGGED.
TrackWriteMap took shadowBase from MappedData() - a tagged heap pointer - kept the tag
through the page masking, mprotect()ed the mapped range PROT_READ and published the
TAGGED base/end into the slot. The handler compared si_addr against it:
if (base <= kSlotSettingUp || address < base) continue;
0x73ac9a5000 < 0xb4000073ac9a5000 is true for every fault, forever. Ownership test (2)
never matched, and every fault on a page the tracker had itself protected was chained
to debuggerd. mprotect was fine throughout - do_mprotect_pkey untags its own argument -
so the right pages really were read-only; only the recognition was broken. x86-64 has
no top-byte tag, which is why every host lane has always been green.
The fix is two rows and one ordering:
* UntagAddress(), applied to the shadow base at registration, to the shadow base in
PushBlocksFor (so trackedBase - shadowBase stays in one space) and to si_addr in
the handler. Written WITHOUT an #if defined(__aarch64__): masking bits 56-63 is
the identity for every valid userspace address off arm64, and an arch guard would
have put the only gate that can catch this on the one platform CI never runs.
Dereferences keep the tagged pointer they came from.
* SlotOwnsFault(), the ownership row the handler and the unit case both read.
* REGISTRATION ARMS NOTHING. AcquireMemoryRange registered and only then returned
Bytes() + range.start, so glMapBufferRange handed the application a PROT_READ
pointer whose writability depended on a handler reaching the writing thread - and
the application is free to write a mapped arena from a worker that blocks every
signal. Nothing is lost by waiting: every page bit is already set at registration,
so the first push ships the whole interior regardless, and it is that push which
arms the pages. 6144 pointless faults per 24 MB arena go with it, and the
invariant the file rests on (bit set <=> page writable) is now true from
registration onward instead of only from the first push.
Diagnostic, independent of the fix: one MGLOG_I_ONCE at the first registration naming
the mapping the way the device run needed and did not have (allocator, pointer, ITS
TAG, extent, range, published span, pages, alignment arm), and a decline record in the
handler - relaxed atomic stores plus one async-signal-safe write(2) - readable through
DeclinedFaults(). A non-zero decline count whose si_addr lies inside nearest-tracked
once the top byte is masked off is this defect's signature and nothing else's.
Not the mechanism, and checked: libsigchain (the tombstone's "F libc : Fatal signal 11"
is bionic's own handler, so the chain ran), the 16 KiB-page refusal (the device is a
4 KiB kernel and the protection demonstrably took), RefaultOfANonWriteAccess (matched
is false long before it is consulted), and the P5d shadow allocator (correct - it is
what makes the destination page-aligned in the tombstone).
Red-once, two and orthogonal (MG_Test/Wire/PersistentMapFaultTest.cpp, 6 cases):
dropping the two UntagAddress calls in SlotOwnsFault reds only
AKernelsUntaggedFaultAddressIsOwnedByATaggedShadowsSlot, which runs the device's own
two addresses through the real predicate on an x86-64 host; restoring the mprotect at
the end of TrackWriteMap reds only AMapHandsBackARangeWritableWithoutTheFaultHandler
(the child dies by SIGSEGV) and RegistrationLeavesEveryPageOfTheRangeReadWrite.
AnArmedPageIsStillAnsweredByTheHandlerAndMadeWritable is the anti-vacuity case.
Gates: ctest -L unit 2408/2408; integration-split 186/186; integration-spawn 102/102;
integration-magma-split 73/73 (PersistentCoherentMapScenario green in the Split, Spawn,
PersistentMapArm and SmallRing variants); fatal_census.py 0 unmarked. G1 holds - pull
.text 10822147 = 0xa52203 unchanged, symbol report 0 added / 0 removed / 0 resized /
0 renamed, nm --defined-only identical at 30570 and nm -D at 11933. Every line is under
MOBILEGL_BUILD_DISAGGREGATED (CMakeLists.txt:592-659), so the pull build sees none of it.
Evidence and the device experiment: docs/Disaggregated/notes/p7/x1-persistent-map-segv.md
CONTRACT-P7: the lane counts (magma-split 73 / spawn 52 / tcp 54; full-suite 510 x 3
with one StreamLink x Magma red), the census baseline (79 abort sites over 20 files,
43 family words, 0 unmarked), the ratchet baseline (186 = p7-magma 101 / espryt 14 /
both 60 / core 11; espryt's clearable count is 9) and the @P7 count (15 sites / 4
files) replace the 待填 markers.
Device window #1a (Redmi 2f7cbe2e, APK p7w1-d260f110, reboot-clean): E0a monolith
39/39 deterministic, three below threshold on monolith itself (create-indirect,
1.21.11 main-menu, iris-photon - Adreno refuses its pipelines with VK_ERROR_UNKNOWN);
E0a inproc: OpenRA 0.976494 (run-ahead) / E1 lockstep 0.988998 - both historical
numbers were OpenRA, and its client log shows a TextureResources verb answered from
caps generation 0 (package F1); 26.3 / create-* SIGSEGV in the replayer's memcpy on
BOTH backends and with the persistent-map push off (package X1); iterationt x2 die
by name on Magma:mipmap-shader-format-or-shape (package B); bsl-esc-menu hits
scudo OOM under inproc. Gate 3's denominator is 36; run-ahead and present credit
are off the suspect list.
CONTRACT-P6.md 12.1 has carried "a ratchet recomputes the 184 in CI and asserts it only
ever falls" as an obligation since P6. The roadmap handed it to P6.5; the P6.5 line never
received it, and scripts/ci/ has never had anything that computes this number. ID-P7-7
gives it to P7 wave 0. This is it.
scripts/link_ratchet.py mechanises a6's hand experiment: partition the per-object output
of the single MobileGL target into SERVER / FRONTEND / SHARED, then compute
(SERVER undefined) - (SERVER defined) - (SHARED defined) intersect (FRONTEND defined),
which is the undefined-symbol list a real link of a server-only image would print. The
partition is an explicit table with a rationale per row, because it IS the module boundary
the build system does not have (SOURCE_FILES is one flat list; boundaries are P13's work).
An object matching no row is a hard failure, as is a build whose CMakeCache lacks the three
disaggregated options, or an empty role set - every way this gate could go green without
having measured anything is an explicit exit 2.
Measured on 78b7d6be: 186 symbols. The three backend buckets reproduce a6's hand-made
101 / 14 / 60 exactly, as do its per-object referrer and definer distributions
(UniformManager 90, VulkanRenderer 60, Managers 53, DirectGLES 51; TextureObject 59,
SamplerObject 30, BufferObject 18, FramebufferObject 16, PipeFill 9). Nothing from a6's
184 has been cleared; the +2 are both P6.5's:
Client::ClientSession::StartSpawned() - fork-per-session
GLState::ProgramArtifactsSchemaFingerprint() - archive v2 caps
The baseline stores the LIST, not a count (ID-P7-7): a count-only ratchet goes green on the
day one symbol is cleared and another added. --assert-monotone fails only on symbols absent
from the baseline and names each one's defining object and every referring object; symbols
that disappeared are reported with an instruction to re-baseline in the same commit, and
never fail.
The CI step goes in build-linux-split, not build-linux: build-linux takes the three options
from MOBILEGL_CI_DISAGGREGATED, which is ON only on feat/disaggregated and
codex/fix-github-ci, so on any other branch it builds a monolith and the gate would measure
a SERVER set with no transport half. The JSON is uploaded even when the gate fires.
Two findings for the integrator, both written up in the note:
- a6's four published bucket totals sum to 181 against its own headline of 184. These
buckets are a strict partition and always sum to the headline; the difference is in
a6's core bucket, which it listed selectively.
- 5 of the 14 "P3b/P4b" symbols are MG_Pipe slot-allocator and apply-thread plumbing
that sits under MG_Impl/Pipe/ by file location alone. P3b/P4b cannot clear them by
migrating Espryt functionality; they need P13's module move. That bucket's floor is
5, not 0, so the plan's 0.5-day Tier 1 estimate for "D12's 14 symbols" should be
re-cut against 9. All 7 symbols from that object carry a `# P13` annotation.
red-once (R-16) is recorded verbatim in the note. Worth knowing: the plan's own example
(SamplerObject::GetMinFilter) does NOT turn this gate red, because it is already in the
baseline - the ratchet reddens on new symbols, so a negative control has to pick one that
is not in the 186. A reach to GetBorderColorForm() from DirectGLES/Utils.cpp does, naming
the symbol, its definer and its referrer, exit 1; reverted and green again.
Tier 2 replays the split-arm registrations - ~100 scenarios, the set Espryt's disaggregation work
has carried since P5 - and it measured green on Magma across all three transports. That is a real
result but it is NOT exit gate 1. The gate says the INTEGRATION BINARY is green under Magma split,
and the set that names is the one the monolith DirectVulkan. arm runs: the whole suite minus
MGL_ITEST_MONOLITH_MECHANISM_FILTER. 510 cases against tier 2's 102, and the readback matrices,
the same-texture copy shapes and the odd-offset uniform ranges are in it and nowhere else in the
disaggregated registry.
integration-magma-full-{split,spawn,tcp}, in no gating step, private log per entry through a list
of its own. THE RESULT: 453/0/57 on inproc, 453/0/57 on spawn, 454/1/57 on tcp - and not one of
the 1530 entries reaches any of the tree's fifteen @P7 named refusals. The single red is
IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput on the TCP arm:
one texel of 262656, texel 0, which is the case's own exposure value
(IterationRPProgram203Scenario.cpp:371, actual half bits (0x357a, 0x4b65) against golden
(0x3a74, 0x4821)). Deterministic 5/5 on tcp; 5/5 GREEN on inproc, on spawn and on the monolith
control. spawn and tcp differ only in MOBILEGL_IPC_DATA, so the suspicion is the STREAM data path
and not "two processes"; the GLES tcp lane is green in the same run, so it is not the fixture.
Not fixed here - it is what the census is for.
TWO ENVIRONMENT DIFFERENCES FROM THE GATED TIERS, both measured rather than assumed:
* NO MGITEST_SPLIT_LANE=1. That marker arms ScenarioFixture's destructor assertion - a case in
an armed split lane must have moved the client encoder's record ordinal - which is right for
a curated lane and wrong for a whole-binary census. With it set, the first run produced 13
reds, all `EmitSeq was 20 at SetUp and is 20 now`: AdvertisedLimitsScenario (8),
SpirvShaderBinaryScenario (4) and ProgramPipelineScenario.AReservedName - a limits query is
answered from the client's CapsMirror and a shader-binary error surface never reaches a verb.
Those 13 would red on a DirectGLES census too. What it gives up is stated in the comment: this
tier does not prove its records crossed, and the tcp topology proof is inert in it.
* DualBlockScenario.* filtered out. Its green REQUIRES MOBILEGL_IPC_ROLE_SPLIT_STATE=1 and a
census must not set that knob; without it the case is red by name, which is its documented
negative-control behaviour. The monolith arm skips it for the same class of reason.
CtWireScenario gets MOBILEGL_TEST_SERVER_COUNTERS=1 in this tier - a switch that makes a counter
readable, not an oracle that asserts - because without it the spawn arm's ApplierReset case reds
on "server control-record telemetry missing", which says nothing about Magma. The strict-arming,
pmap and persistent-map-arm markers stay out.
The inproc-only exclusion in spawn_lane_parity.py is now PER TIER: in the gated tier the two
scenarios are registered on the inproc arm alone, in the census they are on every arm and simply
skip, so excluding them there would manufacture a spurious "extra".
Measured after the change: every pre-existing lane unchanged and green (magma-split 73,
spawn 52, tcp 54, all-* 102/102/104, GLES 186/102/104, buffers 19, runahead 14, caches 7), the
dual-block census 257 green / 2 skipped / 0 red with an empty ratchet, both new CI steps green,
parity RC=0 across all three tiers, G14 4155 -> 6095 with removed=0.
The note now carries the grouped red list, the 57 named skips by reason, the four pieces of
evidence that the tcp red is a defect and not plumbing, and six findings - the first being that
wave 2's clusters cannot be scheduled from this lane's red list, because on lavapipe there is
essentially none.
Three changes, one theme - making the two halves comparable, then gating what measured green.
retrace-split's "Retrace and validate under MOBILEGL_TRANSPORT" step exported no Magma knob at
all, while the monolith retrace job (:2183) exports three iterationRP repairs for
minecraft-1.21.4-fabric-iris-iterationrp-in-world x DirectVulkan. Those repairs are tri-state
quirks defaulting to device auto-detection and lavapipe is on no auto list, so that cell of the
split matrix (154 cells, and it contains that case x DirectVulkan x {inproc, spawn}) was being
compared against a monolith control running a different repair set. The same backend+case guard
is now copied verbatim into the split job.
Two gating steps for the lanes this wave added, and ONLY because they measured green on the
first run: integration-magma-{spawn,tcp} run like the existing magma-split step, and the
nineteen application-buffer cases get require_green BY NAME on both new arms - the tcp arm's
required list includes TcpServer.Start / Stop on purpose, because ctest lists the fixture
entries under the label and a lane whose supervisor failed to start has nothing to say about the
nineteen. The informational integration-magma-all-* tier is deliberately in no gating step.
Both steps were executed locally, not merely linted: spawn 19/19 and tcp 19/19 with
"require-green: N PASS, 0 skip, 0 failed, complete discovery". All 99 run: blocks in the
workflow pass bash -n.
The note records the first run in full: per-lane PASS/FAIL/SKIP, the two blocker groups the
first registration attempt produced (both lane plumbing, neither a server abort, wrong pixels
nor a hang), the arming evidence, both red-once demonstrations, the parity and G14 numbers,
what was NOT verified (no device, no retrace run, no G1) and six findings for the integrator -
the first of which is that P7 exit gate 1 has no host-side red list at all, which the wave 2
plan is built on top of.
The application id is top.mobilegl.plugin.p7w1.trace (flavour suffix after the id
suffix), pgrep -f dex2oat matched the shell running the loop and never exited, and
adb.exe cannot stat an MSYS /c/ path once MSYS_NO_PATHCONV is set. All three were
hit on the first execution of section 3.
One reboot-clean session on Redmi 2f7cbe2e, copy-pasteable, with the shell each
block runs in marked: pin and wake/Doze, APK install identity, P7-7 E0-E3, the
P6.5 residual 39-case DirectGLES TCP golden matrix, the CTS $BASE for monolith x
DirectVulkan, and the temp-environment restoration. Every step states in one line
which result supports which hypothesis, and where the evidence is archived.
Four things the runbook had to settle rather than restate:
E1's proof. `running lockstep` is NOT the line that proves lockstep took effect:
ClientSession.cpp:1873 guards it with `RunAhead != 0 && m_barrierArmed`, so it is
the "the knob is set but the server lacks kCapRunAheadApply" warning and
MOBILEGL_IPC_RUN_AHEAD=0 prints nothing at all from LatchRunAheadFromCaps. The
proof is a line present and a line absent: `Config: IPC ... run-ahead=0 ...`
(ConfigLoader.cpp:459) with zero `run-ahead ARMED` - the shape p5f/magma-runahead.md
already recorded for the RA=0 arm.
E1 will exit non-zero, by design. --transport inproc adds --require-inproc, whose
gate demands run-ahead=1 in the Config: IPC line (trace-replay-ci.sh:601), and the
proof runs AFTER result.json is pulled and checked. So the SSIM and the archive are
usable and the arm is verified by hand against the table above; dropping the --env
to make it green would delete the experiment.
E0's budget. 39 cases x 2 transports x 3 repeats is 234 replays, roughly 3-4 h,
which does not fit beside the TCP matrix and CTS in one window. It is split into a
one-pass census that produces the divergent-case list and a three-repeat pass over
that list plus two controls - the determinism question only has meaning for cases
that already diverge.
CTS is flagged, not promised. No caselist is in-tree, and the local VK-GL-CTS
checkout has an empty external/openglcts/data with no mustpass file anywhere, so
the five blocks cannot be cut in-tree; the exact pre-window procedure to fetch a
tagged checkout and commit tools/cts/caselists/p7-*.txt is written out instead.
And KHR-GL46 is unproven on the Android runner - every device reference result in
the skill is KHR-GL33, the 4.5 numbers are desktop lavapipe, and the platform hands
the package's version straight to eglCreateContext - so the runbook carries a
five-minute pre-window probe and an explicit integrator decision: if 4.6 is
unreachable the baseline is taken on KHR-GL33 and the AFTER reading must use the
same version, because 0.5 pp between two different API versions means nothing.
ROADMAP.md: the Ph / P3b-P4b / P7 rows carry the audit's verdicts and the factual
corrections it found - the @P7 refusal list is 15 sites in 4 files (the row printed 8),
the 29-commit DirectVulkan shape wave was never recorded, the device divergence is two
observations with no per-case attribution and no lockstep control, SetupDrawSnapshot /
content-addressed CSO are declined for this stage, "XFB scatter to client" is retired
in favour of the two real holes, readback/pack state splits into closed and open
halves, the NDK size pin is obsolete, PACK-PBO readback landed in 70df57bb, the ABI
fingerprint landed in P6.5, and OQ-8 / OQ-10 / OQ-13 / OQ-21 get their current answers.
P6-ENDSTATE-REVIEW.md:284 qualifies an Init.cpp citation the lint reported ambiguous.
MG_Remote/CONTRACT-P7.md: rules I (no std::abort on the Magma wire arm) and J (every
fix reds once on a two-process arm), the five exit gates in testable form, the
retire-vs-decline table for every @P7 name, the ratchet's definition and P7 target,
the nine-subsystem denominator for the 40% checkpoint, the wave-2 file partition,
and the amendments to CONTRACT-P6. Census, ratchet and lane numbers are marked
待填 until wave 0's packages report them.
Plan for driving the roadmap to P7 close-out, from a per-item audit of the three
rows against a96875bf (19 reader groups, two refuters per done/obsolete verdict,
one completeness critic): what is landed, what is declined and why, what remains
with file anchors, five waves, the P7 subsystem denominator for the 40%
re-baseline checkpoint, and eleven integrator decisions (ID-P7-1..11).
The three findings the roadmap text did not carry: Magma has zero two-process
integration coverage (every split arm hardcodes DirectGLES), the 184-symbol
ratchet has no instrument, and the device DirectVulkan divergence was never
diagnosed (the lockstep control was never run under golden).
MG_Test/Util/PipeStatsTest.cpp: the pull flavour (MOBILEGL_PIPE_PUSH=OFF) did not
compile since the P6 wrap-up - the window-arithmetic case used the push-only
PipeStats::Gauge API unguarded. CI's pull control builds with
MOBILEGL_BUILD_TEST=OFF and never saw it. The gauge lines are now #if
MOBILEGL_PIPE_PUSH; the case name stays registered in both flavours for G2/G14.
The device gate: Redmi 2f7cbe2e, reboot-clean, paired A/B in one thermal
window (arms interleaved, best-of-3 then best-of-2), rd12 + DirectGLES.
inproc vs spawn client CPU p50 7.880/7.889 vs 7.876/7.869 (-0.15%) and
7.987/7.989 vs 7.992/8.009 (+0.16%) - a tie inside the 0.2-4.2% within-
arm spread, sign flipping with arm order, recorded and not gated.
monolith is ~30% dearer. 22/22 runner exits clean, 44/44 PINNED, zero
Fatal.
The three mandatory numbers: socket doorbell vs condvar is a tie too -
client 121.1 waits/frame identical across transports, server 10 866-11
963 (inproc) vs 10 415-10 416 (spawn) waits/frame, and the contract's
'converting that into ~4 blocking reads may be cheaper' did not pay off;
the only column where the socket is worse is clipark/f (6.07 vs 5.60/5.75).
SEG_STAGE bytes/frame: 130.8 KB median / 8.01 MB steady (OpenRA), 816.2
KB / 1.53 MB (rd12). Records/frame post-chunking: 211/206 and 7 497/7
504. maxrec stays 1808 - the 8 MiB chunk budget is never reached. Both
framings reported throughout (the startup window runs ~7x the steady
state) rather than the flattering one.
MEASUREMENTS.md gains section 12 with the full protocol and tables; the
6.3 'no post-chunking distribution exists' gap now points there.
CURRENT_STAGE_PROGRESS.md marks gates 7/8 met and records the closeout
work plus two structural limits (spawn client windowed fields are zero
by construction - OnPresent runs in the server process - though its run
totals are valid stores; the stats dump is role-derived). ROADMAP marks
P6 closed in the stage table, the header and the milestone.
Evidence: notes/p6/gate7-device-ab.md, gate8-doorbell-device.md,
gate8-wire-counters.md, git-worktree-fix.md. check_doc_citations: 82
citations in the three documents, 0 problems.
ROADMAP gains the section "the immediate next item": a design, not an
implementation, for running the frontend on an x86_64 WSL process and the
backend on the Redmi over TCP, with the integration lane and the CI retrace
subset as the acceptance. The P6.5 row now points at it.
What the design rests on, verified in the tree: the control plane already
treats the rendezvous as a NAME with two ordered connections (SocketTransport),
ClientSession::StartOverSocket already separates "how the socket came" from the
handshake, MGPBlobRef addresses kSegStage only through one ResolveOrFatal, the
reply header and the event heads are already wire-shaped, LogLine exists in
CtrlMsg with no producer, goldens are shared between desktop and device, and
the trace APK can already exec libMobileGLServer.so from nativeLibraryDir.
What it does not rest on, stated rather than assumed: ILink is declared and
unused beyond LinkProgress, ShmLink does not exist, both sessions hold
SessionSegments directly - the lk seam body from the end-state review was never
built and is this wave's first package (lk2). The manifest has no INTERNET
permission.
The mechanism: StreamLink as mirrored private segments replicated by messages -
same sizes both ends, same offsets, so the codec, the watermark rules, the apply
thread and the encoder do not change; RingControl becomes Progress frames sent
on flush-on-idle (the recorded amendment to "appliedSeq is never lazy": late by
at most one flush, never early); replies become seq-stamped messages; the event
ring is mirrored with the P5e blocking rule intact. Cross-architecture makes
the PipeFields.def layout digest mandatory (wf). Server logs on another machine
make LogLine forwarding mandatory (lf) so the 34 server-log readers stay
unchanged. A --serve supervisor forks one child per session on the device,
started by a foreground Service in the trace APK.
Packages lk2 -> wf -> ct -> sl -> lf -> sv -> t65, each with red-once; the gate
list, the mandatory numbers (kWaitReply per frame x RTT, SEG_STAGE bytes/frame
against the link, PRESENT_CREDIT 1..3), the two-sided configuration runbook and
the risks (RTT per reply row, single large uploads, AP isolation) are in the
section. check_doc_citations: 74 citations, 0 problems.
The end state is re-ruled: client and server may sit on different machines,
OSes and architectures, joined over TCP. The AVF pVM / AF_VSOCK end state the
P6 end-state review assumed is withdrawn (retraction at its head, form D added
to its table, the vsock probe and both external unknowns struck).
The transport stack becomes two independently selectable axes, negotiated in
the handshake and mixable: control plane (ITransport: fork / unix:<path> /
tcp://host:port) and data plane (ILink: ShmLink / StreamLink). On one machine
control over TCP with data over shared segments is a legal pair; ShmLink brings
its own AF_UNIX aux rendezvous for SCM_RIGHTS, so ITransport narrows to control
frames. `auto` is decided by a successful segment delivery, never by inspecting
addresses, and a fall-back is named. Nothing above Transport/ may branch on the
link kind. ARCHITECTURE.md gains 11.9 for this; CONTRACT-P6.md gains 10.3.
ROADMAP: P6.5 and Ph get rows (both were referenced by the contract and absent
from the stage table); the IPC track becomes P6 -> P6.5 -> Ph -> P12 -> P9 ->
P10 -> P11(same-machine arm). Every later row is reconciled against the tree:
items already landed are struck (client vertex arrays, client-minted query
handles, credit default 1, SEG_EVENT overflow mechanism), the unreachable P7
gate (`nm | grep glslang` on a server image that IS libMobileGL.so) is replaced
by the 184-symbol ratchet share, fixed-width wire form moves from P7 to P6.5,
P12 is cut down per the review (server-owned display, no Surface passing), and
the debt table's stale claims are corrected: class C is down to two slots, not
15 and not 511; PACK-PBO and the 2 MiB reply cap are P9's, not P6's.
Verified against the tree and recorded honestly: the contract's three hs rows
(LinkTerms, Refuse, the fingerprint split) never landed - protocol.fbs has
neither - so CURRENT_STAGE_PROGRESS.md's claim that S6 ran red is withdrawn and
the rows move to P6.5; the 184-symbol ratchet gate does not exist in CI; the
server process never initialises PipeStats, so gate 8's `srv=0 srvpark=0` is a
structural zero (fix in progress in the tree, not in this commit).
check_doc_citations.py: 159 citations, one pre-existing basename ambiguity.
All ten P6 packages (a6/c6/lk/so/sm/cp/hs/dl/st/t6) are on feat/disaggregated
through 16bfab10. The two docs now say so honestly:
- ROADMAP.md (cumulative): the status line, the P6 stage row, and a new
milestone bullet record every package landed, exit-gate items 1-6 met, and
items 7 (device paired A/B in one thermal window) and 8 (the three mandatory
perf numbers) still pending -- so P6 is "implementation complete, exit
perf/device numbers pending", NOT closed.
- CURRENT_STAGE_PROGRESS.md: rewritten to hold ONLY the current stage. All
P5* history is gone (it lives in ROADMAP.md and the P5* reports); what
remains is P6's package table, the CONTRACT-P6 section-9 gate with each
item's honest status, the device results, the per-role log split, and the
explicitly-not-P6 list.
The shared control page both roles map had a mixed-writer cache line:
{appliedSeq, submittedSeq, retiredSeq, completedFrameSerial, presentAckSerial}
sat together, but submittedSeq is producer-written (Ring.h's "THE FIVE
WATERMARKS") while the other four are consumer-written. That is why no
single-writer progress block could be aliased over it, and it is what this
package fixes: submittedSeq moves up beside cmdHead, the other thing the
producer writes, and the four that remain become a named LinkProgress member -
the struct c6 declared in ILink.h - so a data plane with no shared page can own
the same four values and every wait predicate in SessionRings.h keeps ONE
implementation at source level.
The two event flags deliberately stay in the doorbell group. Promoting them
would put the client's per-drain exchange(0) on eventRingFull (EventRing.h:200)
onto the line the client's own spin predicate reads 1e5-1e6 times a frame.
73 call sites rewritten, 15 in the library and 58 in tests. The rewrite is
receiver-scoped rather than field-scoped, because SplitRuntimePeek.h:77 has its
own struct with a field of the same name and SplitRuntimePeek.cpp:146 has both
on one line - a field-scoped rename would have silently broken the peek struct
that MagmaRunAheadScenario reads nine times.
THE ASSERTS ARE THE PART THAT NEEDED A SECOND PASS. The first revision shipped
four aggregate asserts and an adversarial review broke them by counterexample:
swapping retiredSeq and completedFrameSerial inside LinkProgress compiled
clean, passed all four, and passed the runtime layout test too - because
appliedSeq stays at offset 0. Three wire-visible offsets on a shared page were
pinned by nothing. Nothing else in the tree covers it either: the ABI
fingerprint takes no input from this page, and sizeof(RingControl) is 4096
whatever the members do, because alignas(4096) makes it so.
So the block is now per-field as CONTRACT-P6 §8.3 actually asked, plus a
standard-layout precondition, a tail bound that alignas cannot satisfy for us,
and the LinkEventFlags mirror. Each was run red: the field swap reds the
per-field asserts by name, a fifth member inside LinkProgress reds sizeof.
One of them cannot go red, and Ring.h now says so rather than implying
otherwise. A SMALL member inserted between Progress and serverEpoch lands at
offset 288, inside Progress's own line, and serverEpoch's alignas(64) absorbs
up to 32 bytes of it - so both line asserts still pass. The review's proposed
fix does not catch it either; I tried it. A gate nobody can falsify is worse
than no gate, so the limit is written down beside the asserts.
Also: RingTest.ControlPageLayoutIsTheSharedContract said nothing about the one
field this package moved, and now asserts the producer/consumer separation and
the event flags' exclusion itself rather than borrowing the claim from Ring.h.
Ring.h grew 30 lines, so the Ring.h:NN citations this package and c6 authored
are re-anchored to named blocks instead of line numbers. ARCHITECTURE.md §11.2
is updated for the new layout, and a factual error introduced in the previous
revision of that paragraph - submittedSeq annotated as releasing staging, which
retiredSeq does - is corrected.
Gates: pull 0 added / 0 removed, .text size 10822147 unchanged; split 0/0,
.text size 11681395 unchanged; unit suite 2326/2328 with the same two
pre-existing environmental failures as the baseline. The five edits behind
MGITEST_SPLIT_RUNTIME_PEEK were proven compiled by a red-once, not assumed.
Note on the .text claim: that is a SIZE, not the bytes - field offsets moved,
so immediates moved with them. And this page's layout IS observable across
binaries; what is unobservable is behaviour within one binary. The contract
already books the layout change deliberately, before spawn exists to care.
CONTRACT-P6 §8.5 requires lk to publish its blast radius before it starts,
because an earlier draft's ratchet baseline was wrong by a factor of three and
a ratchet installed on the wrong number is red on its first CI run for reasons
that have nothing to do with a leak. The itemisation is mechanical, per file,
with the grep spelling stated: 250 seam-identifier references outside
Transport/ across 23 files (127 production, 123 test), and 44 member-access
sites that the regroup itself must rewrite, across 9 files - 17 of them in
MG_IntegrationTest, which is why lk builds with the integration tests ON.
The earlier build did not, and a missed edit there would have been green.
THE CORRECTION. Measuring that radius turned up a mistake in c6, landed two
commits ago. §8.3 and ILink.h both said lk should promote eventRingFull and
eventDropped into the watermark group and called the result single-writer.
eventRingFull has TWO writers: the server latches it with store(1) when
SEG_EVENT fills (EventRing.h:140) and the CLIENT clears it with an exchange(0)
on EVERY DRAIN (:200). Promoting it would put a per-drain read-modify-write by
the client on the same cache line as appliedSeq - which the server
release-stores every 64 records and the client's spin predicate reads 1e5-1e6
times a frame. That is new false sharing on the hottest line in the system,
and it would land in exactly the numbers this package's gate has to hold
constant. It is also the same class of error the earlier draft made with
submittedSeq, one field over.
So LinkProgress is now the four consumer-written watermarks and nothing else -
genuinely single-writer - and the two flags stay in the doorbell/generation
group, where mixed writers already live beside consumerParked/producerParked,
crossing the seam through a separate EventFlags() accessor on its own line.
The contract records the correction and its reasoning rather than quietly
restating the rule.
ALSO CORRECTED, from running the baseline suite: a6's link-experiment report
said the silently-empty build stamp had "no log and no gate to say so". That
is false. SessionHandshakeTest.cpp:140 asserts BuildStamp[0] != '\0' and :180
asserts that perturbing it changes the fingerprint, and BOTH GO RED in this
configuration right now - the worktree's .git holds a Windows path WSL's git
cannot resolve, so GIT_COMMIT_HASH_SHORT is "". The finding is therefore
stronger, not weaker: the gate that catches it already exists and is already
red. What is silent is the BUILD, and hs owes a named configure-time failure
rather than a new gate.
Baseline for lk, recorded: 3878 tests, two failures, both environmental and
understood - the fingerprint one above, and PipeCatalogue.FrontendNeverTakes-
AnApplierAddress, which walks up from cwd looking for MobileGL/MG_Impl and so
fails for any out-of-source build directory.
No behaviour change. The headers are still included by nothing.
c6 is P6's contract package. It lands CONTRACT-P6.md plus two headers and
changes nothing: no caller moves, no byte moves on any wire.
THE CONTRACT supersedes P6-CONTRACT-DRAFT.md, which was written before its own
audit and is wrong in four places - the ABI row, "the latch is implemented
unchanged", "cp is transport only", and "a transport swap and nothing else".
a6 measured all four. It carries 21 rulings, of which the load-bearing one is
D1: the spawned child keeps Transport == Spawn and anti-recursion becomes a
separate Dial axis. ARCHITECTURE.md:488's literal instruction is amended,
because 226 live lines test `Transport != Monolith` to select the SERVER arm
and forcing Monolith flips 148 MG_Backend lines to frontend glue in the one
process that has no frontend.
That ruling can be stated normatively without changing behaviour because of
something a6 found and the contract exploits: TransportMode::Spawn is
UNREACHABLE at this head. ConfigLoader.cpp:344 is the only writer for the
string "spawn" and it writes Monolith. So every rule conditioned on
Transport == Spawn constrains a state no process can enter - and the same fact
is why ConfigLoader's named refusal cannot currently falsify itself, which is
sm's to fix and §9.5's arm-proof gate to catch.
THE SEAM. ITransport says in its own header that the hot path bypasses it;
below it there was no interface of any kind, because Ring.h, ReplySlot.h and
EventRing.h each take a raw void* plus a capacity. ILink.h is that missing
thing. It is setup-time, not hot-path: no method is called per record or inside
a spin predicate, because Doorbell::Wait's predicate runs 1e5-1e6 times a frame
reading watermarks straight off the page, and because the only lane that could
measure a virtual there has four times the dispersion of the effect - a gate
that can only print "within noise" is ID-124 one level down.
StreamLink.h is declared and implemented by nobody, and that is its job: ILink's
adequacy is a claim only a compile can settle. Writing it forced three methods
the brief did not name - ResolveSpan, FlushProgress, and the reply pair - so it
has already earned its keep. Delete one ILink method it declares and the build
goes red.
Deliberately NOT here, each for a reason the contract records: Watermark::Reached
is not redefined (SessionRings.h owns it); Ring.cpp's reserve/pop bodies are not
relocated into a header that would then need the logger; RingControl is not
regrouped, because that rename is 109 production sites with five behind a
non-Android #if, so it is lk's with a published itemisation; and the schema rows
are specified but left to hs, because deprecating SurfaceReply.defaultFb while
appending `result` changes CreateSurfaceReply's arity and the existing
six-argument call would silently bind its defaultFb 0 to result.
INERTNESS IS STRUCTURAL, not measured. Zero object dependency files reference
either header, zero build rules do, and nothing in the tree includes them.
Verified anyway on both flavours: pull and split each have 0 symbols added,
0 removed, and .text byte-identical (10822147 / 11681395). check_doc_citations
resolves all 61 citations.
a6 is the read-only audit P6-SPAWN-PLAN puts first. Seven static items, each
produced by one auditor and re-resolved line by line by one adversarial
verifier; 198 rows, 65 of them contested, and only the surviving ones carry
into the report. Item 5 - can the server link without MG_Impl - is the one
experiment, and it is answered.
The headline is that P6's transport half is smaller than the plan assumed
(fc and fs really did land) and its process/role half is much larger. Four
statements in P6-SPAWN-PLAN / P6-CONTRACT-DRAFT are false at this head:
- `spawn` is not an observable state at all. ConfigLoader.cpp:339-346
recognises it and then writes Transport = Monolith, so "the child was
forced to monolith" and "the parent fell back" are the same value and
that named refusal cannot falsify itself. Meanwhile 226 live lines test
`Transport != Monolith` to select the SERVER arm, 148 of them in
MG_Backend - so ARCHITECTURE.md:488's rule, implemented literally, flips
the whole backend to frontend glue in the one process with no frontend.
- `cp` is not transport-only: the InitCapabilities REQUEST has no wire form,
and the codec refuses to encode the kind a live client still calls.
- the device-lost latch is not implemented; it was booked as carriage.
- the server cannot link without MG_Impl: 184 symbols, 177 of them real
function calls, owed 101 to P7 / 14 to P3b-P4b / 60 shared / 6 to P6.
There is also no module target to link, so the question could not have
been answered by editing a link line.
Four more blockers the earlier review missed: the apply thread aborts in
ConsumerDoorbell before popping its first record (contract 3.9's ruling, which
lk should retire rather than amend); neither side of the control plane is
wired to a socket; the control-reply wait has no deadline, so a frozen peer
wedges the GL thread inside eglMakeCurrent; and SurfaceReply cannot carry a
MobileGLResult, so a client cannot tell a dead server from a legitimate
refusal.
The abort census makes dl's funnel a redesign rather than a rename: 92 sites,
30 distinct family words against a 7-value wire enum, and three unbounded
peer-driven allocations with no try/catch anywhere under MG_Remote.
P6-ENDSTATE-REVIEW.md is the re-examination of P6 against the gfxstream-shaped
end state that produced a6's three added items. a6 overturned four of its own
claims - the MG_State eight's shape, the abort count, m_windowHandle's
injection points and a predicate name collision - and those are corrected in
place rather than left to be rediscovered.
Docs only. No code, no gate, no behaviour.
MEASUREMENTS 7.3 records what 82683d4a did, so the 30 s in that row stays as
written; a note under the table now says which value is live and where the
reason for the change is argued, so a reader looking up the barrier budget by
grep does not come away with the superseded number.
lavapipe/llvmpipe stall the apply thread for tens of seconds inside texture-handle
registration (vkCreateImageView -> lvp_CreateImageView -> llvmpipe_register_texture,
a JIT/futex path): one measured stall is 29.3-39.7 s and a whole inproc retrace run
accumulates ~44.6 s of it. At a 30 s bound those runs red intermittently on
Fatal{BarrierTimeout, "ResourceRespecify"} while the same stall under monolith is
merely slow - monolith has no cap on that path. Since the monolith shares the stall,
the bound, not the workload, was the defect.
kBarrierTimeoutMs becomes 120000, >3x the largest observed stall, which still leaves
the watchdog job: a record that is never retired still terminates the run and the
Fatal still names the seq. The comment now says deadlock detector rather than
performance gate and carries the measurement.
The fake-clock case that pins the budget moves with it: FenceWaitBudgetHonorsGl-
TimeoutAndFiniteTransportChunks expected 30000/30001/90000 and now expects
120000/120001/180000, so the assertion still pins the value instead of drifting.
Texts that wrote the old number down are updated: ARCHITECTURE 17.1, CONTRACT-P5E
2.4 (which now names kBarrierTimeoutMs rather than the literal), and the two
bring-up comments in PipeRoute.h / WireTables.cpp that dated a self-wait to
"thirty seconds".
Every place that stated "P5 does not chunk" as current behaviour except the
Fatal texts (previous commit) now records the stage chunk budget and what it
does not cover.
- CONTRACT-P5.md section 5: the STAGE_MB row names MGPipeStageChunkBytes() =
clamp(segment/4, 4096, segment), the two buffer walks (1e7c372e) and one
texture level's whole-width slabs (9469d48e, assembled by
StagedTextureStore::AdoptRun), and the Fatal that stays the backstop for a row
with no cut; the RING_MB row says the record bound is about the record's own
bytes.
- BRIEF-P5.md's R-10 section is no longer headed "does not chunk": it keeps the
record bound and the maxrec proof obligation unchanged and records that
content is cut, with the encoder Fatal as the fallback.
- ROADMAP.md's staging debt row and open question 11, ARCHITECTURE.md's
record-bound bullet and SEG_STAGE table note, README.md's knob row,
MEASUREMENTS.md section 6.3 and CURRENT_STAGE_PROGRESS.md's open item: the
explicit MOBILEGL_IPC_STAGE_MB=256 profile is no longer needed for the two cut
paths, and the carrier design for the rest stays P8.
- Historical results (notes/**/p5-results/, p5b/) are left exactly as recorded.