Files
MobileGL/docs/Disaggregated/notes/p05/p05-results/program-artifacts-v1.md
T
swung0x48 aba329cc53 [Docs] (Disaggregated): P6's plan and contract draft; the working notes move from a local directory into the repository
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.
2026-09-18 23:31:39 -04:00

67 lines
13 KiB
Markdown

# p05-program-artifacts — results v1
Tree `~/w7/p05-program-artifacts`, branch `p05/program-artifacts`, base `feat/disaggregated @ 6672778b`.
Build: `build-linux/` Release, `/usr/sbin/clang++` (clang 22), ccache (`CCACHE_BASEDIR=/home/swung/w7`), libstdc++ (`__GLIBCXX__ 20260625`), `MOBILEGL_BUILD_TEST=ON`, `MOBILEGL_BUILD_INTEGRATION_TEST=ON`.
Not pushed. `~/w7/pipe` untouched (only read: `ctest -N`, `nm`, and a direct run of one existing test binary for the A/B below).
## Commits (oldest first)
| hash | subject |
|---|---|
| `a180dc57` | `[Refactor] (Program): extract ProgramArtifacts.h - move TypeFacts, ResourceReflection, XfbVarying, LinkArtifacts and SpirvArtifacts to namespace scope with in-class aliases so every existing spelling compiles unchanged; pure move` |
| `3ce1264c` | `[Refactor] (Program): stop ProgramObject.h including SpvcSession.h - it names no spirv-cross or SPIRV-Reflect symbol` |
| `77beb7d6` | `[Feat] (Program): add the reflection-archive field tables and sizeof trip wires to ProgramArtifacts.h` |
Files touched (vs `6672778b`): `MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h` (new, 576 lines), `MobileGL/MG_State/GLState/ProgramState/ProgramObject.h` (-386/+15), `MobileGL/MG_Test/Program/ProgramArtifactsTest.cpp` (new), `MobileGL/MG_Test/Program/CMakeLists.txt` (+21). Nothing else — commit 2 needed no consumer include additions (all 8 includers + `ProgramObject.cpp` built without `SpvcSession.h` forwarded).
## What landed (B.2 checklist)
- `ProgramArtifacts.h`: banner copied from `ProgramObject.h`; includes exactly `<Includes.h>` and `<set>`; PURITY comment; `namespace MobileGL::MG_State::GLState { inline constexpr Uint kInvalidUniformOffset = ~0u; TypeFacts; ResourceReflection; the 4 `using` reflections; XfbVarying; LinkArtifacts (+ its comment block, with the one added I5 sentence); SpirvArtifacts (+ comment block); VisitFields x5; trip wires }`. Struct bodies moved verbatim (re-indented one level), no field added/removed/reordered/re-typed; `program` and `uniformInitialValues` keep their glslang types (D5).
- `ProgramObject.h`: `#include "ProgramArtifacts.h"` after `"ShaderObject.h"`; old `:41-104` replaced by the 9-line alias block (fully qualified RHS); `kInvalidUniformOffset` re-exported as `static constexpr Uint kInvalidUniformOffset = MobileGL::MG_State::GLState::kInvalidUniformOffset;` (comment kept); old `:1144-1171` and `:1173-1449` deleted; commit 2 drops `#include <MG_Util/ShaderTranspiler/SpvcSession.h>`. `MAX_UNIFORM_LOCATIONS` and the `glslang::` accessors stay. `m_artifacts`/`m_spirv` private, `Artifacts()`/`Spirv()` untouched (I5).
- `VisitFields`: free constrained templates (`requires std::same_as<std::remove_const_t<Self>, T>`), contract `v(const char*, Field&)`, tables never recurse; counts TypeFacts 20, ResourceReflection 14 (`type` as a value), XfbVarying 11, LinkArtifacts 57 (every member except `program`), SpirvArtifacts 8.
- Trip wires: `static_assert(is_trivially_copyable_v<TypeFacts> && sizeof(TypeFacts) == 44)`; under `#if defined(__GLIBCXX__) && !defined(_GLIBCXX_DEBUG) && (SIZE_MAX == UINT64_MAX)`: `MGL_RESOURCEREFLECTION_SIZE 128`, `MGL_XFBVARYING_SIZE 128`, `MGL_LINKARTIFACTS_SIZE 1056`, `MGL_SPIRVARTIFACTS_SIZE 88` (measured on this build via `== 1` notes); `#elif defined(_LIBCPP_VERSION) && (SIZE_MAX == UINT64_MAX) && defined(MGL_ARTIFACT_SIZES_LIBCXX_PINNED)` left inert for the integrator (C.4); the four `static_assert`s guarded by `#ifdef MGL_LINKARTIFACTS_SIZE` with the "add the field to VisitFields(...) then update this number" messages.
- Tests (`ProgramArtifacts.*`, label `unit`, first include is the artifacts header): `AliasesAreTheSameTypes` (9 `is_same_v` static_asserts + `kInvalidUniformOffset`), `TypeFactsIsPodOf44Bytes`, `VisitFieldsCoversEveryMember` (20/14/11/57/8, const == non-const, names distinct), `VisitFieldsPassesConstnessThrough` (extra: const walk yields const refs, mutable walk writes through), `SizesArePinnedOnThisToolchain` (`EXPECT_EQ` under the same `#if`, `RecordProperty("sizeof_*")` x5 always, plus a printf line `sizeof: TypeFacts=44 ResourceReflection=128 XfbVarying=128 LinkArtifacts=1056 SpirvArtifacts=88`).
## Verification — every command and its actual result
All run in WSL Arch, cwd `~/w7/p05-program-artifacts`, after commit 3 (`77beb7d6`) unless stated.
| # | command | result |
|---|---|---|
| 1 | `cmake --build build-linux -j 16` (after each commit) | commit 1: OK (186 steps, whole ProgramObject.h fan-out); commit 2: OK (183 steps, zero consumer edits needed); commit 3: OK; post-commit re-run: `ninja: no work to do.` Logs `~/w7/pa-build{1,2,3}.log` |
| 2 | `ctest --test-dir build-linux -L unit --no-tests=error -j 8` | `100% tests passed, 0 tests failed out of 1463` (3 `***Skipped`: `LogLevel.DebugBuildKeepsEverything`, `LogLevel.AssertIsLiveInDebugBuilds`, `XfbFrontendOrderInvarianceTest.DumpBSpirvForDisassembly` — skip by design). Log `~/w7/pa-unit3.log`. (After commit 1 alone: 1460/1460.) |
| 3 | `ctest --test-dir build-linux -L integration-gpu --no-tests=error -j 8` | `99% tests passed, 5 tests failed out of 868`: the four `*IsActuallyArmedWhenTheEnvironmentPinsItOn*` (DirectGLES UnlocatedIoBlocks, DirectVulkan PrimGenReroute, DirectGLES + DirectVulkan PointSizeDemotion) — the known `-j` flake — and `DirectVulkan.IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput`. Log `~/w7/pa-igpu3.log` |
| 4 | `ctest --test-dir build-linux -L integration-gpu --rerun-failed -j 1` | `80% tests passed, 1 tests failed out of 5`: the four flake lanes pass; `DirectVulkan.IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput` still fails (`IterationRPProgram203Scenario.cpp:371,377`, golden half-float texel mismatch, `mismatchTexels` 1 vs 0). Log `~/w7/pa-igpu3-rerun.log` |
| 5 | A/B of #4's failure on the UNTOUCHED base binaries: `MOBILEGL_BACKEND_TYPE=DirectVulkan __EGL_VENDOR_LIBRARY_FILENAMES=/usr/share/glvnd/egl_vendor.d/50_mesa.json ~/w7/pipe/build-linux/MobileGL/MG_IntegrationTest/MobileGLIntegrationTest --gtest_filter=IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput --gtest_also_run_disabled_tests` (cwd = that binary's `MG_IntegrationTest` dir; the env is what the ctest ENVIRONMENT property injects) x3, and the same for my binary x3 | base (built 22:06 at `6672778b`): FAILED 3/3; mine: FAILED 3/3. **Pre-existing on this WSL/lavapipe machine, independent of this package** (the package touches no code path — only type scope — and the library `.text` is byte-identical, see #13). Its DirectGLES twin is `***Skipped` on both. |
| 6 | `ctest -N` name diff: `ctest --test-dir build-linux -N \| grep -E '^\s+Test\s+#' \| sed 's/^ *Test *#[0-9]*: //' \| sort` vs the same on `~/w7/pipe/build-linux`; `comm -23 before after` | before 2326, after 2331; `comm -23` **empty**; `comm -13` = exactly the 5 new `ProgramArtifacts.*` names. NOTE: the brief's pattern `'^\s+Test #'` (single space) only matches test numbers >= 1000 (ctest right-aligns: `Test #984:` vs `Test #1234:`); with it the diff is garbage (two spurious `QueryTest.*` "additions", my tests invisible). Use `'^\s+Test\s+#'`. |
| 7 | `ctest --test-dir build-linux -L unit -N \| grep -c ProgramArtifacts` | `5` (the label reaches `ctest -L unit`) |
| 8 | ODR: `grep -rn "^\s*struct \(TypeFacts\|ResourceReflection\|XfbVarying\|LinkArtifacts\|SpirvArtifacts\)\b" MobileGL` | exactly 5 hits, all in `ProgramArtifacts.h` (lines 31, 63, 95, 160, 359) |
| 9 | `grep -n "#include" MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h` | 2 lines: `<Includes.h>` (line 10), `<set>` (line 15) |
| 10 | `grep -c "glslang::" MobileGL/MG_State/GLState/ProgramState/ProgramArtifacts.h` | `2` (lines 165 `SharedPtr<glslang::TProgram> program;` and 232 `Vector<glslang::TIntermediate::TUniformInitializer> uniformInitialValues;`) |
| 11 | Closure probe: `printf '#include <MG_State/GLState/ProgramState/ProgramArtifacts.h>\n' > /tmp/probe_pa.cpp; clang++ -std=gnu++23 -Iinclude -IMobileGL -IMobileGL/MG_Pipe -I3rdparty/xxHash -I3rdparty/Vulkan-Headers/include -H -E -o /dev/null /tmp/probe_pa.cpp 2>&1 \| grep -E '^\.+ ' \| grep -E 'ShaderObject.h\|SpvcSession.h\|MG_Util/ShaderTranspiler/\|MobileGL/Config.h\|MobileGL/MG_Backend/\|BufferState/'` | **empty** (0 forbidden of 653 closure lines). The only `MobileGL/` headers in the closure: `Defines.h`, `Includes.h`, `ProgramArtifacts.h`, `MG_Util/Debug/Log.h`, `MG_Util/GLExtensions.h`, `MG_Util/PlatformStubs.h`, `MG_Util/Types.h`. |
| 12 | `clang++ ... -fsyntax-only /tmp/probe_pa.cpp` | OK (header self-contained; `ProgramArtifactsTest.cpp` includes it first as the in-tree proof) |
| 13 | `git diff --stat 6672778b -- MobileGL/MG_Backend MobileGL/MG_Impl` | **empty** (also after commit 2 — no include-line additions were needed) |
| 14 | Symbol attribution (no `scripts/symbol_report.py` in this tree — done by hand with `nm --defined-only -S` + `c++filt` + `size --format=sysv`, script `scratchpad/wf3/p05-program-artifacts/symcmp.py`; normalisation folds `GLState::ProgramObject::{TypeFacts,ResourceReflection,XfbVarying,LinkArtifacts,SpirvArtifacts}` -> `GLState::X` on both sides, multiset compare on (name, type, size)); before = `~/w7/pipe/build-linux/libMobileGL.so` (6672778b, 22:06), after = `build-linux/libMobileGL.so` | `symbol-report: .text 10792579 -> 10792579` (byte-identical); `30511 -> 30511 defined symbols: 0 added, 0 removed, 1 resized, 43 renamed`; identical after commit 1 alone (`~/w7/pa-sym1.txt`) and after commit 3 (`~/w7/pa-sym3.txt`). The 1 "resized" is `r [122] -> [107] typeinfo name for std::_Sp_counted_ptr_inplace<...LinkArtifacts, allocator<void>, _Lock_policy 2>` — the RTTI **name string** (rodata, not code), 15 bytes shorter because `13ProgramObject` left the mangled name. Fully attributed; commits 2 and 3 add no symbol (VisitFields is uninstantiated outside the test binary). |
| 15 | Red-for-its-reason (scratch copy of the header under `/tmp/pa-neg`, tree untouched): add `Int addedWithoutATableEntry = 0;` to `LinkArtifacts`, `clang++ -I/tmp/pa-neg ... -fsyntax-only /tmp/probe_pa.cpp` | exit 1: `error: static assertion failed ... 'sizeof(LinkArtifacts) == 1056': LinkArtifacts changed size: add the field to VisitFields(LinkArtifacts) ..., then update this number` / `note: expression evaluates to '1064 == 1056'` |
| 16 | `git status --short` (excluding `build-linux/`) after each commit | clean |
| 17 | Line endings: `grep -c $'\r'` on the two headers and the test | 0 / 0 / 0 |
## Deviations from the brief (with reasons)
1. **Comment respellings in the moved text (commit 1).** Five comment lines that moved verbatim spell `glslang::TType`, `glslang::TLayoutMatrix`, `glslang::TBasicType`, `glslang::TObjectReflection`, `glslang::TProgram`; with them the header had 8 `glslang::` lines, and the gate/D5 pins the count at exactly 2. Respelled comment-only as `glslang TType` etc. (no code token changed). The brief's "verbatim" and "exactly 2" cannot both hold literally; the gate wins (`ROADMAP.md:7`).
2. **`LinkArtifacts` field count is 57 of 58, not 52 of 53.** Counted mechanically from the moved source (`program` + 57 others; a lines-ending-in-`;` count under-reads because several members carry trailing comments). `VisitFieldsCoversEveryMember` expects 57. The brief's 53 is a stale scout number.
3. **`ctest -N` grep pattern** corrected to `'^\s+Test\s+#'` (see #6) — the brief's pattern silently drops tests numbered < 1000.
4. **One extra test** `VisitFieldsPassesConstnessThrough` (adds only; brief allows additions) — pins that the single table really serves both directions.
5. **`MobileGL/MG_Test/Program/CMakeLists.txt` stanza** appended at the end of the file (after the existing `gtest_discover_tests` block) rather than "copied next to" `AsyncCompileTest`; content is the `AsyncCompileTest` shape with `DISCOVERY_TIMEOUT 30 PROPERTIES LABELS unit` as specified.
6. **`scripts/symbol_report.py` absent** in this tree (owned by `p05-include-gate`); the by-hand equivalent above records the numbers. The brief's `--strip-scope 'MobileGL::MG_State::GLState::ProgramObject::'` as a plain textual strip is NOT the right normalisation (it leaves a bare `LinkArtifacts` on the before side versus `MobileGL::MG_State::GLState::LinkArtifacts` on the after side and reports 43 added / 43 removed); the integrator's run of the real script should fold `GLState::ProgramObject::X` -> `GLState::X` (or strip only `ProgramObject::`). Flagging so C.4 does not read a false 43/43.
## Notes for the integrator
- libc++ branch of the trip wires is inert until `MGL_ARTIFACT_SIZES_LIBCXX_PINNED` is defined with the NDK numbers (C.4); `ProgramArtifacts.SizesArePinnedOnThisToolchain` prints and `RecordProperty`s all five sizes on every platform for that purpose.
- The glibc++ guard is exactly the brief's (`__GLIBCXX__ && !_GLIBCXX_DEBUG && SIZE_MAX == UINT64_MAX`); it does not exclude `_GLIBCXX_USE_CXX11_ABI=0` (COW `std::string`, 8 bytes) — no MobileGL toolchain uses that, mentioned only for completeness.
- `DirectVulkan.IterationRPProgram203Scenario.FixedCompleteInputProducesFixedCompleteGoldenOutput` is red on this WSL machine at the base commit (#5) — expect it in the integrator's baseline too.
## Unfinished
Nothing in scope. Not done by design/ownership: libc++ size pin (integrator, C.4); `scripts/check_include_closure.py --probe artifacts-header` run (script lives in `p05/include-gate`; the manual `-H` probe #11 is its equivalent); narrowing `ProgramTranslationCache.h`/`ProgramInterface.cpp` to the new header and the `UniformInitializer` re-typing (P7 follow-ups per the brief).