Two things land together: the plan and contract draft P6 needs before it starts, and the
working notes that until now existed on exactly one machine.
1. P6's plan and contract draft (neither started; not one line of code)
docs/Disaggregated/P6-SPAWN-PLAN.md the package plan
docs/Disaggregated/P6-CONTRACT-DRAFT.md the contract draft (moves to
MG_Remote/CONTRACT-P6.md when c6 lands)
The three findings that mattered, all written into the documents:
a. P6 is SMALLER than its roadmap cell suggests. The transport primitives are already in
the tree and already tested: SocketDoorbell including hangup detection and the two
lost-wakeup fences (Doorbell.cpp:211-360, four cases in FdPassingTest), SCM_RIGHTS fd
passing, ShmSegment::Adopt, and the SurfaceOp / WindowKind schema already defined in
protocol.fbs. inproc uses a real second shm mapping rather than an alias, and
SessionRings.h:22-28 says that was done so today is not the first time it runs. P6 is
assembly plus a control plane plus a process, not "write a socket transport".
b. The evidence for "P6 is only a transport swap" has EXPIRED. It was bought by P5c, whose
audit found 59 direct memory accesses in a tree that already looked clean. P5e changed
the tree again. So the first package, a6, is a read-only audit that writes no code, and
every contract row that depends on it is marked PENDING a6 rather than decided in
advance.
c. P6 does not get a game running on the phone. ANativeWindow* is meaningless in the child,
and protocol.fbs:186-196's own comment says Android transfers the window out of band -
that band is P12. P6 lands pbuffer / surfaceless / offscreen, which is what HeadlessGL,
the split-equivalent lane and trace replay need, and which is exactly what P6's own exit
gate measures. A real window reaching the server is a named refusal. This gets its own
section because "P6 is done" would otherwise read as "the game runs now".
The contract adds rule G: a role may not name a process-local handle. It extends P5c's
rule E (a role may not name the other role's memory) from memory to identity - EGLDisplay /
EGLSurface / EGLContext / ANativeWindow* / fd number / pid / mapped address either cross as
a token this contract defines or they do not cross, and a descriptor crosses only through
SCM_RIGHTS, where the kernel renumbers it.
What P5e left for P6 gets its own section. The latch semantics are already designed
(ARCHITECTURE.md:504) and are copied unchanged. What is new is that after run-ahead the
client no longer waits while the peer process can DIE: the client holds records that were
published and will never be applied, and Android's freezer suspends the child without
closing anything. So slow and dead must be distinguishable WITHOUT a timeout threshold.
That is P6's only new design; everything else is carriage. It shares a predicate with E1's
outstanding re-specification (ID-122), and the two should be designed together.
Two gates carried over from P5e's lessons: G2 requires integration-spawn's test-name set to
match integration-split's name for name, because a differing set means the lane is quietly
running less; and the arm-proof gate requires every entry to record the child pid and
transport=spawn in its OWN private log - ID-124 established that this kind of false green
really happens, and a spawn lane that actually ran monolith would pass every other gate.
S2's shape must be settled before it is written, and the document says why: E1 is the
cautionary case.
2. The working notes move into the repository (docs/Disaggregated/notes/, 415 .md files)
ROADMAP / CURRENT_STAGE_PROGRESS / MEASUREMENTS cite ruling numbers, audit line numbers and
the provenance of gate figures from ~/w7/notes/, a directory that exists on one machine and
is under no version control. A record cited hundreds of times that lives only locally is not
a record, so it moves in whole, with its directory structure intact.
Only .md moves. Scripts, run logs, census and gate result JSON, patches, APKs and nm dumps
stay where they are: they are evidence DATA rather than documents - large, regenerated per
run, and mostly carrying absolute paths and device serials that mean something only on the
machine that produced them. The mapping and this split are written in
docs/Disaggregated/notes/README.md.
Two exceptions are recorded there too: P6's BRIEF has been promoted to a first-class
document and keeps no copy under notes/, so the two cannot drift apart; and recovered/ holds
material salvaged from failed workflow runs, which may duplicate the like-named phase
directories and is kept because a few of those were the only complete version at the time.
CI's citation lint takes docs/Disaggregated/*.md only (non-recursive), and notes/ is
deliberately outside it: those notes' file:line citations were written against their own
code heads, and resolving them against today's HEAD would fail wholesale - which is not an
error, it is their correct state as a historical record. To check one, use the revision it
declares (--rev).
3. Inconsistencies fixed in passing
- README.md's status line still said P5e was in progress while the stage table already said
closed.
- CURRENT_STAGE_PROGRESS's next-step item 1 still listed the exit gate as pending, though
(a) and (b) were run in the previous commit; rewritten to closed plus the one item that
actually remains (E1).
- ROADMAP's P6 row still had the pre-phase plan in three of its cells; it now carries the
package order, the scope boundary and the two new gates.
Checked with check_doc_citations.py: 125 citations across docs/Disaggregated/*.md, 0 problems.
13 KiB
P1 package A (p1-core) — c1, the contract commit
Tree ~/w7/p1-core, branch p1/core, base feat/disaggregated @ 087685d1.
Commit
bf86b1ede6f9a43b4114fe2603f68a0bc2aa233c—[Feat] (Pipe): land the P1 contract - PipeInputsSwitch.h with MGB_CTX, the 63-field PipeInputs block with type-identical accessors, FillPoints.def and its G5b generator, the MOBILEGL_PIPE_PUSH/VERIFY options and the three verify knobs; pull build unchanged- Tag:
p1/contract(annotation-less tag on that commit;git describe --tags→p1/contract). Not pushed. - 15 files, +1834 / −30. Working tree clean apart from the three untracked build directories.
| action | file |
|---|---|
| CREATE | MobileGL/MG_Pipe/PipeInputsSwitch.h (D3 verbatim, 28 lines) |
| CREATE | MobileGL/MG_Pipe/FillPoints.def (69 verb rows in GLFunctionsTable declaration order, 9 classes, 138 (class, field) rows) |
| CREATE | MobileGL/MG_Pipe/generated/PipeFillPoints.inc (generated: MGPipeVerb 69, kMGPipeVerbNames, MGPipeVerbClass 9, kMGPipeVerbClassNames, kMGPipeVerbClass[], MGPipeFieldMask{Words[2]}, MGPipeFieldMaskHas, kMGPipeClassFieldMask[9] with the 7 sticky bits OR'ed in, static_assert(kMGPipeVerbCount == 69)) |
| CREATE | MobileGL/MG_Backend/MGPipe/PipeInputs.h (D4: all 63 accessors — 49 V, 7 O, 7 F — with GLContext-identical signatures; gPipeInputs inline; MOBILEGL_PIPE_POISON derived once; MGP_INPUT_CHECK / MGP_INPUT_VERIFY_READ; MGP_INPUT_STORAGE_LIST + VisitStorage; static_assert(sizeof < 20 KiB)) |
| CREATE | MobileGL/MG_Backend/MGPipe/PipeInputs.cpp (MGPipeVerbName, MGPipeInputPoisonFatalForVerb, MGPipeFindInputField / MGPipeFindVerb; under MOBILEGL_PIPE_VERIFY: MGPipeInputsFieldEqual, MGPipeApplyVerifyCorruption) |
| CREATE | MobileGL/MG_Impl/Pipe/PipeFill.h (D7 MGP_FILL(Verb), ((void)0) under pull) |
| CREATE | MobileGL/MG_Impl/Pipe/PipeFill.cpp (PipeInputs::IsLive, the seven F-class forwarders, MGPipeFillForVerb = serial bump + verb + identity + one-time sticky stamps; no field copies) |
| MODIFY | MobileGL/MG_Pipe/Coverage.def (D12: GetBoundTransformFeedbackLifetimeId, HasOpenTransformFeedbackSpan rows after RecordError; dead-row comment on GetBoundTransformFeedbackName; the :36-40 note reworded to "when the inventory is re-vendored"; MGP_COVERAGE_STICKY_LIST(X) with the seven D6 rows and reasons) |
| MODIFY | MobileGL/MG_Pipe/generated/PipeFilled.inc (63 fields, 7 true sticky flags with reasons, kMGPipeInputStickyFieldCount = 7) |
| MODIFY | MobileGL/MG_Pipe/MGPipe.h (#include "generated/PipeFillPoints.inc" after PipeFilled.inc, G5b comment) |
| MODIFY | scripts/gen_pipe.py (parse_function_table(), parse_fill_points(), sticky-list parsing in parse_coverage(), gen_filled real sticky flags, gen_fill_points(), PipeFillPoints.inc written between PipeFilled.inc and PipeCoverage.inc, summary line "… 63 PipeInputs fields (7 sticky), 69 verbs, 9 classes") |
| MODIFY | CMakeLists.txt (options after :24; VERIFY⇒PUSH block + conditional SOURCE_FILES append after the disaggregated fallback endif() at :452; the two MOBILEGL_COMPILE_DEF appends after :525) |
| MODIFY | MobileGL/Config.h, MobileGL/ConfigLoader.cpp (D2 PipeVerifyFatal / PipeVerifyCorrupt / PipePoisonOmit under #if MOBILEGL_PIPE_PUSH) |
| MODIFY | MobileGL/MG_Test/Pipe/PipeCatalogueTest.cpp (:225 61u → 63u) |
Not touched (per brief): MG_Backend/DirectGLES/**, MG_Backend/DirectVulkan/**, PipeCalls.def, PipeFields.def, MG_Impl/GLImpl/**, tools/trace_replay/fixtures/**.
Verification (all run in WSL on ~/w7/p1-core; logs under ~/w7/p1-core-verify-*.log, ~/w7/p1-core-build-*-c1.log)
Tree creation: bash wsl_p1_tree.sh core feat/disaggregated verify → worktree on p1/core @ 087685d1, submodules ok, build-linux / build-push / build-verify all rc=0 at the base (955/955).
Before any edit (sanity of the tooling): symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0 → .text 10792579 -> 10792579 (+0), 27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed.
After the edits, in this order:
python3 scripts/gen_pipe.py→ wrotePipeFilled.inc,PipeFillPoints.inc;python3 scripts/gen_pipe.py --check→gen_pipe: 71 calls (11 screen, 60 context), 63 verify payloads, 63 PipeInputs fields (7 sticky), 69 verbs, 9 classes/inventory 477 rows … 0 UNMAPPED/generated files are up to date, rc=0.cmake --build build-linux -j 20(reconfigured itself on theCMakeLists.txtchange;MOBILEGL_PIPE_PUSH=1occurs 0 times inbuild-linux/compile_commands.json) → rc=0.python3 scripts/symbol_report.py --before ~/w7/p1-before-libMobileGL.so --after build-linux/libMobileGL.so --threshold 0→.text 10792579 -> 10792579 (+0, +0.000%),.data +0,.bss +0,.rodata +0,Total +0,27799 -> 27799 defined symbols: 0 added, 0 removed, 0 resized, 0 renamed,27060 normalised names unchanged; file bytes identical (19100448).nm --defined-only build-linux/libMobileGL.so | grep -c MGPipe= 0, same as the baseline. G1 holds: the pull build is symbol-identical with a .text delta of 0 (the new.cppfiles are not listed in the pull build — D5/D13: appended toSOURCE_FILESonly underMOBILEGL_PIPE_PUSH; the new headers are included by nobody in the pull build;Config.h's three members are behind#if MOBILEGL_PIPE_PUSH; theMGPipe.hinclude ofPipeFillPoints.incadds onlyinline constexprtables that no pull TU odr-uses).cmake --build build-push -j 20→ rc=0, links (MOBILEGL_PIPE_PUSH=1on all 496 compile commands;nm -C build-push/libMobileGL.soexportsMGPipeFillForVerb,PipeInputs::IsLive,gPipeInputs). Configure log:MobileGL: PipeInputs push ON, appending the MGPipe fill sources.cmake --build build-verify -j 20→ rc=0, links (MOBILEGL_PIPE_PUSH=1andMOBILEGL_PIPE_VERIFY=1on all 496; exportsMGPipeInputsFieldEqual,MGPipeApplyVerifyCorruption). Configure log:MobileGL: MOBILEGL_PIPE_VERIFY=ON forces MOBILEGL_PIPE_PUSH ON for this configure.ctest -L unit --no-tests=error -j 12: build-linux100% tests passed, 0 tests failed out of 1466; build-push100% … out of 1466; build-verify100% … out of 1466.(cd build-linux && ctest -N | grep -E '^\s+Test\s+#[0-9]+:' | sed 's/^ *Test *#[0-9]*: //' | sort)→ 2334 names;comm -23 ~/w7/p1-before-ctest-names.txt -→ 0 removed,comm -13→ 0 added. (Note: the brief's regex'^\s+Test #'only matches test numbers ≥ 1000 — ctest pads smaller numbers with extra spaces — and would report 999 spurious removals; the baseline file itself holds all 2334 names, so the diff above uses the padding-tolerant regex.)grep -rc "pGLContext" MobileGL/MG_Backend | sort | diff - ~/w7/p1-before-pglcontext.txt→ only the two new files appear, both:0(MG_Backend/MGPipe/PipeInputs.h:0,PipeInputs.cpp:0); every catalogued file's count is unchanged (A converts no site).grep -c pGLContexton the two MGPipe files →0 0.grep -c "MGP_FILL(" MobileGL/MG_Impl/GLImpl/*/*.cpp→ 0 (fill points are c3).- Integration smoke in the push and verify libraries (
ctest -L integration-gpu -R 'ClearThenReadPixels|GenerateMipmap|Mipmap' -j 4): build-push100% tests passed, 0 failed out of 10, build-verify100% … out of 10, zeroFatal{in either log — the inert filler and the unarmed poison do not disturb a run in which no backend site readsgPipeInputsyet.
Not run (not in the task's proof list; long): the full ctest -L integration-gpu in build-push (brief C.1 step 1 lists it for A's own tree — recommended after c3 when the fill points exist, since at c1 nothing calls MGPipeFillForVerb).
Deviations from the brief (and brief-vs-tree facts)
MGP_INPUT_CHECKis live at c1 (underMOBILEGL_PIPE_POISON) rather than "expanding to nothing until step 3" (C.1 step 1 wording). Reason: B's and C's verification text expects a push run on the contract tree to hitFatal{UnmigratedPipeInput}at the first read ("that is the poison working"); with an empty check the first read of an O-class field would be a null dereference instead. The check costs nothing in a non-poison push build (((void)0)), and c2 now only has to add the filler copies.MGP_INPUT_VERIFY_READexpands to nothing as specified (c4 arms it).- Sticky stamping is in c1 (three lines in
MGPipeFillForVerb: the seven sticky fields getFilledGen = 1on the first live fill) rather than c2. It is generation bookkeeping, not a field copy, and it keeps the F-class forwarders usable under poison from the contract on. MGPipeInputsFieldEqualandMGPipeApplyVerifyCorruptionare in c1 (PipeInputs.cpp,#if MOBILEGL_PIPE_VERIFY), per C.1 step 1's "PipeInputs.cpp(equality, corruption injector)"; the comparator proper (SnapshotFromGLContext, entry compare, compare-at-read hook, arming line, knob parsing) is NOT in c1, as the task instruction requires. Both are one call ofPipeInputs::VisitStorageoverMGP_INPUT_STORAGE_LIST(56 storage rows + 7 forwarded = 63, static_asserted) instead of two 63-way switches.IsCapabilityEnabledIndexed's two arrays are oneIndexedCapabilitiesstruct so the list stays one row per field.PipeFields.defis untouched in c1. C.1 step 1 says "Coverage.def+PipeFields.defedits" but step 5 (c5) owns the six G4 field lists / overloads / coverage check, and the task instruction for c1 does not listPipeFields.def. Deferred to c5. Consequence:kMGPipeVerifiedPayloadCountstays 63 at c1 andRenderStateParameters/PixelStoreParametersstill go through the memcmp fallback insideMGPipeInputsFieldEqual(both arms are copied by assignment into zero-initialised globals, so padding is equal; c5 removes the fallback anyway).PipeInputs::IsLive()forwards to the live context (defined inPipeFill.cpp, returnspGLContext != nullptr) instead of returning a fill-timem_liveflag;m_liveis still recorded at fill for the filler's own use. Reason: D5 listsIsLiveamongPipeFill.cpp's contents and D9 saysMGB_CTX_LIVE"is always true once a context exists" under push, which a fill-time flag cannot promise before the first verb (e.g.InvalidateCompileEnvat backend init,BackendObject_DirectVulkan.cpp:389,787).ContextIdentity()is the fill-time address as specified.GetBufferBindingPointCountis notconstexprinPipeInputs(GLContext's is): it is an out-of-line forwarder per D4's F class. No backend uses it in a constant expression.MGPipeFieldMask::Words[2]as the brief says, generated asmax(2, ceil(fields/64))so a 129th field grows the mask instead of silently truncating (static_assert(kMGPipeInputFieldCount <= 2 * 64)in the.inc).- Two extra
gen_pipe.pychecks beyond the brief's list: verb rows must be inGLFunctionsTabledeclaration order (not just the same set), soMGPipeVerb's numbering is the table's by construction; a class listed twice inMGP_FILL_CLASS_LISTis refused.--self-test,scan_live_accessors()andcheck_field_lists_cover_struct_members()are c5, as the brief's step 5 says. - Tree-vs-brief line facts:
CMakeLists.txt"after :451" — the disaggregated fallback'sendif()is at:452; the block was inserted after it (before theif (MOBILEGL_BUILD_DISAGGREGATED)sources block at:454). TheMOBILEGL_COMPILE_DEFappends went after theendif()at:525as stated. The brief's ctest-name regex issue is item 6 above. - Accessor namespaces:
BufferTargetandFramebufferTargetlive innamespace MobileGL(notMG_State::GLState, where the brief's text implies all frontend types sit); the header'susingaliases spell them accordingly. Everything else (VertexArrayObject,ProgramObject,ITextureObject,BufferObject,FramebufferObject,TextureUnit,ImageTextureBinding,CurrentVertexAttributeValue) isMG_State::GLState::; value types areMobileGL::. IsCapabilityEnabledwith a cap ≥CapabilityInputCountreturnsfalse(defensive bound; GLContext would index its own table); indexed getters mirrorRenderState's bounds handling (MOBILEGL_ASSERT+ element 0 / early return;GetCurrentVertexAttributemirrorsCore.cpp:246-253withMGLOG_E_ONCE+ a static default).
Unfinished / next for A
- c2: per-class copies and stamps in
MGPipeFillForVerb(mask =kMGPipeClassFieldMask[kMGPipeVerbClass[verb]]),MOBILEGL_PIPE_POISON_OMITparsing. - c3: the 83
MGP_FILL(X);statements (§B.7). - c4:
SnapshotFromGLContext, entry compare,MGPipeVerifyReadHookbehindMGP_INPUT_VERIFY_READ, arming/knob log lines,PipeVerifyFatal. - c5:
PipeFields.deffield lists,MGPipeFieldEqualoverloads,check_field_lists_cover_struct_members,scan_live_accessors,--self-test. - c6: the unit tests listed in C.1.
- Full
ctest -L integration-gpuin build-push (after c3).